diff --git a/COPYING b/COPYING
new file mode 100644
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,339 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                          675 Mass Ave, Cambridge, MA 02139, USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	Appendix: How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,6 @@
+Copyright 2008 by Audrey Tang.
+
+Files under the "xpdf" directory are copyrighted by Glyph & Cog, LLC.
+
+This package is subject to the GNU General Public License, version 2.
+See the COPYING file for details.
diff --git a/Makefile.pdfdump b/Makefile.pdfdump
new file mode 100644
--- /dev/null
+++ b/Makefile.pdfdump
@@ -0,0 +1,92 @@
+#------------------------------------------------------------------------
+
+.SUFFIXES: .cc .c
+
+.cc.o:
+	g++ -O -I. -Ixpdf -c $< -o $@
+
+.c.o:
+	gcc -O -I. -Ixpdf -c $< -o $@
+
+OBJS = \
+	xpdf/Annot.o \
+	xpdf/Array.o \
+	xpdf/BuiltinFont.o \
+	xpdf/BuiltinFontTables.o \
+	xpdf/CMap.o \
+	xpdf/Catalog.o \
+	xpdf/CharCodeToUnicode.o \
+	xpdf/CoreOutputDev.o \
+	xpdf/Decrypt.o \
+	xpdf/Dict.o \
+	xpdf/Error.o \
+	xpdf/FixedPoint.o \
+	xpdf/FoFiBase.o \
+	xpdf/FoFiEncodings.o \
+	xpdf/FoFiTrueType.o \
+	xpdf/FoFiType1.o \
+	xpdf/FoFiType1C.o \
+	xpdf/FontEncodingTables.o \
+	xpdf/Function.o \
+	xpdf/GHash.o \
+	xpdf/GList.o \
+	xpdf/GString.o \
+	xpdf/Gfx.o \
+	xpdf/GfxFont.o \
+	xpdf/GfxState.o \
+	xpdf/GlobalParams.o \
+	xpdf/JArithmeticDecoder.o \
+	xpdf/JBIG2Stream.o \
+	xpdf/JPXStream.o \
+	xpdf/Lexer.o \
+	xpdf/Link.o \
+	xpdf/NameToCharCode.o \
+	xpdf/Object.o \
+	xpdf/Outline.o \
+	xpdf/OutputDev.o \
+	xpdf/PDFCore.o \
+	xpdf/PDFDoc.o \
+	xpdf/PDFDocEncoding.o \
+	xpdf/PSTokenizer.o \
+	xpdf/Page.o \
+	xpdf/Parser.o \
+	xpdf/SecurityHandler.o \
+	xpdf/Splash.o \
+	xpdf/SplashBitmap.o \
+	xpdf/SplashClip.o \
+	xpdf/SplashFTFont.o \
+	xpdf/SplashFTFontEngine.o \
+	xpdf/SplashFTFontFile.o \
+	xpdf/SplashFont.o \
+	xpdf/SplashFontEngine.o \
+	xpdf/SplashFontFile.o \
+	xpdf/SplashFontFileID.o \
+	xpdf/SplashOutputDev.o \
+	xpdf/SplashPath.o \
+	xpdf/SplashPattern.o \
+	xpdf/SplashScreen.o \
+	xpdf/SplashState.o \
+	xpdf/SplashT1Font.o \
+	xpdf/SplashT1FontEngine.o \
+	xpdf/SplashT1FontFile.o \
+	xpdf/SplashXPath.o \
+	xpdf/SplashXPathScanner.o \
+	xpdf/Stream.o \
+	xpdf/TextOutputDev.o \
+	xpdf/UnicodeMap.o \
+	xpdf/UnicodeTypeTable.o \
+	xpdf/XRef.o \
+	xpdf/gfile.o \
+	xpdf/gmem.o \
+	xpdf/gmempp.o \
+	xpdf/parseargs.o \
+	xpdf/pdfdump.o
+
+all: $(OBJS)
+	-@rm dist/build/pdfdump/pdfdump
+	g++ -o dist/build/pdfdump/pdfdump $(OBJS)
+
+clean:
+	-@rm dist/build/pdfdump/pdfdump xpdf/*.o
+
+#------------------------------------------------------------------------
diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,8 @@
+"pdf2line" is a simple command-line utility to convert PDF into text.
+
+It is based on "pdftotext" from the Xpdf suite, but with a different
+layout algorithm that preserves relative column position and line spacing.
+
+To use it for anything other than ASCII text, you'd need to have a
+/usr/local/etc/xpdfrc or ~/.xpdfrc configured in a way that allows
+Xpdf's "pdftotext" to run correctly for your purpose.
diff --git a/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,14 @@
+#!/usr/bin/env runhaskell
+\begin{code}
+import System.Cmd
+import Distribution.Simple
+
+main = defaultMainWithHooks $ simpleUserHooks
+    { buildHook = myBuild (buildHook simpleUserHooks) }
+
+myBuild doBuild x y z w = do
+    rv <- doBuild x y z w
+    system "make -f Makefile.pdfdump"
+    return rv
+
+\end{code}
diff --git a/pdf2line.cabal b/pdf2line.cabal
new file mode 100644
--- /dev/null
+++ b/pdf2line.cabal
@@ -0,0 +1,190 @@
+name:               pdf2line
+version:            0.0.1
+copyright:          2008 Audrey Tang; distributed with parts of "xpdf" by Glyph & Cog, LLC.
+license:            GPL
+license-file:       LICENSE
+author:             Audrey Tang <audreyt@audreyt.org>
+maintainer:         Audrey Tang <audreyt@audreyt.org>
+synopsis:           Simple command-line utility to convert PDF into text
+description:        Simple command-line utility to convert PDF into text
+stability:          experimental
+build-type:         Simple
+category:           Text
+extra-source-files: README COPYING Makefile.pdfdump
+                    xpdf/Annot.cc
+                    xpdf/Annot.h
+                    xpdf/Array.cc
+                    xpdf/Array.h
+                    xpdf/BuiltinFont.cc
+                    xpdf/BuiltinFont.h
+                    xpdf/BuiltinFontTables.cc
+                    xpdf/BuiltinFontTables.h
+                    xpdf/CMap.cc
+                    xpdf/CMap.h
+                    xpdf/Catalog.cc
+                    xpdf/Catalog.h
+                    xpdf/CharCodeToUnicode.cc
+                    xpdf/CharCodeToUnicode.h
+                    xpdf/CharTypes.h
+                    xpdf/CompactFontTables.h
+                    xpdf/CoreOutputDev.cc
+                    xpdf/CoreOutputDev.h
+                    xpdf/Decrypt.cc
+                    xpdf/Decrypt.h
+                    xpdf/Dict.cc
+                    xpdf/Dict.h
+                    xpdf/Error.cc
+                    xpdf/Error.h
+                    xpdf/ErrorCodes.h
+                    xpdf/FixedPoint.cc
+                    xpdf/FixedPoint.h
+                    xpdf/FoFiBase.cc
+                    xpdf/FoFiBase.h
+                    xpdf/FoFiEncodings.cc
+                    xpdf/FoFiEncodings.h
+                    xpdf/FoFiTrueType.cc
+                    xpdf/FoFiTrueType.h
+                    xpdf/FoFiType1.cc
+                    xpdf/FoFiType1.h
+                    xpdf/FoFiType1C.cc
+                    xpdf/FoFiType1C.h
+                    xpdf/FontEncodingTables.cc
+                    xpdf/FontEncodingTables.h
+                    xpdf/Function.cc
+                    xpdf/Function.h
+                    xpdf/GHash.cc
+                    xpdf/GHash.h
+                    xpdf/GList.cc
+                    xpdf/GList.h
+                    xpdf/GMutex.h
+                    xpdf/GString.cc
+                    xpdf/GString.h
+                    xpdf/Gfx.cc
+                    xpdf/Gfx.h
+                    xpdf/GfxFont.cc
+                    xpdf/GfxFont.h
+                    xpdf/GfxState.cc
+                    xpdf/GfxState.h
+                    xpdf/GlobalParams.cc
+                    xpdf/GlobalParams.h
+                    xpdf/ImageOutputDev.cc
+                    xpdf/ImageOutputDev.h
+                    xpdf/JArithmeticDecoder.cc
+                    xpdf/JArithmeticDecoder.h
+                    xpdf/JBIG2Stream.cc
+                    xpdf/JBIG2Stream.h
+                    xpdf/JPXStream.cc
+                    xpdf/JPXStream.h
+                    xpdf/Lexer.cc
+                    xpdf/Lexer.h
+                    xpdf/Link.cc
+                    xpdf/Link.h
+                    xpdf/NameToCharCode.cc
+                    xpdf/NameToCharCode.h
+                    xpdf/NameToUnicodeTable.h
+                    xpdf/Object.cc
+                    xpdf/Object.h
+                    xpdf/Outline.cc
+                    xpdf/Outline.h
+                    xpdf/OutputDev.cc
+                    xpdf/OutputDev.h
+                    xpdf/PDFCore.cc
+                    xpdf/PDFCore.h
+                    xpdf/PDFDoc.cc
+                    xpdf/PDFDoc.h
+                    xpdf/PDFDocEncoding.cc
+                    xpdf/PDFDocEncoding.h
+                    xpdf/PSOutputDev.cc
+                    xpdf/PSOutputDev.h
+                    xpdf/PSTokenizer.cc
+                    xpdf/PSTokenizer.h
+                    xpdf/Page.cc
+                    xpdf/Page.h
+                    xpdf/Parser.cc
+                    xpdf/Parser.h
+                    xpdf/PreScanOutputDev.cc
+                    xpdf/PreScanOutputDev.h
+                    xpdf/SecurityHandler.cc
+                    xpdf/SecurityHandler.h
+                    xpdf/Splash.cc
+                    xpdf/Splash.h
+                    xpdf/SplashBitmap.cc
+                    xpdf/SplashBitmap.h
+                    xpdf/SplashClip.cc
+                    xpdf/SplashClip.h
+                    xpdf/SplashErrorCodes.h
+                    xpdf/SplashFTFont.cc
+                    xpdf/SplashFTFont.h
+                    xpdf/SplashFTFontEngine.cc
+                    xpdf/SplashFTFontEngine.h
+                    xpdf/SplashFTFontFile.cc
+                    xpdf/SplashFTFontFile.h
+                    xpdf/SplashFont.cc
+                    xpdf/SplashFont.h
+                    xpdf/SplashFontEngine.cc
+                    xpdf/SplashFontEngine.h
+                    xpdf/SplashFontFile.cc
+                    xpdf/SplashFontFile.h
+                    xpdf/SplashFontFileID.cc
+                    xpdf/SplashFontFileID.h
+                    xpdf/SplashGlyphBitmap.h
+                    xpdf/SplashMath.h
+                    xpdf/SplashOutputDev.cc
+                    xpdf/SplashOutputDev.h
+                    xpdf/SplashPath.cc
+                    xpdf/SplashPath.h
+                    xpdf/SplashPattern.cc
+                    xpdf/SplashPattern.h
+                    xpdf/SplashScreen.cc
+                    xpdf/SplashScreen.h
+                    xpdf/SplashState.cc
+                    xpdf/SplashState.h
+                    xpdf/SplashT1Font.cc
+                    xpdf/SplashT1Font.h
+                    xpdf/SplashT1FontEngine.cc
+                    xpdf/SplashT1FontEngine.h
+                    xpdf/SplashT1FontFile.cc
+                    xpdf/SplashT1FontFile.h
+                    xpdf/SplashTypes.h
+                    xpdf/SplashXPath.cc
+                    xpdf/SplashXPath.h
+                    xpdf/SplashXPathScanner.cc
+                    xpdf/SplashXPathScanner.h
+                    xpdf/Stream-CCITT.h
+                    xpdf/Stream.cc
+                    xpdf/Stream.h
+                    xpdf/TextOutputDev.cc
+                    xpdf/TextOutputDev.h
+                    xpdf/UTF8.h
+                    xpdf/UnicodeMap.cc
+                    xpdf/UnicodeMap.h
+                    xpdf/UnicodeMapTables.h
+                    xpdf/UnicodeTypeTable.cc
+                    xpdf/UnicodeTypeTable.h
+                    xpdf/XRef.cc
+                    xpdf/XRef.h
+                    xpdf/XpdfPluginAPI.cc
+                    xpdf/XpdfPluginAPI.h
+                    xpdf/about-text.h
+                    xpdf/aconf.h
+                    xpdf/aconf2.h
+                    xpdf/config.h
+                    xpdf/gfile.cc
+                    xpdf/gfile.h
+                    xpdf/gmem.cc
+                    xpdf/gmem.h
+                    xpdf/gmempp.cc
+                    xpdf/gtypes.h
+                    xpdf/parseargs.c
+                    xpdf/parseargs.h
+                    xpdf/pdfdump.cc
+
+executable:         pdf2line
+main-is:            pdf2line.hs
+build-depends:      base, bytestring, binary, FindBin, containers, filepath, process
+hs-source-dirs:     .
+
+executable:         pdfdump
+main-is:            pdf2line.hs
+build-depends:      base, bytestring, binary, FindBin, containers, filepath, process
+hs-source-dirs:     .
diff --git a/pdf2line.hs b/pdf2line.hs
new file mode 100644
--- /dev/null
+++ b/pdf2line.hs
@@ -0,0 +1,149 @@
+{-# OPTIONS_GHC -O2 -fglasgow-exts -funbox-strict-fields #-}
+
+module Main where
+import Data.Binary
+import Data.Binary.Get
+import System.Process
+import Control.Monad
+import Data.IORef
+import System.FilePath
+import System.IO
+import System.IO.Unsafe
+import System.Environment (getArgs)
+import System.Environment.FindBin
+import Data.ByteString (ByteString)
+import qualified Data.IntMap as IM
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Lazy as L
+import qualified Data.ByteString.Char8 as C
+
+__AlignRight__ :: Bool
+__AlignRight__ = False
+
+main :: IO ()
+main = do
+    args <- getArgs
+    hSetBinaryMode stdout True
+    case args of
+        []  -> putStrLn "Usage: pdf2line input.pdf ... > output.txt"
+        ["-"] -> do
+            hSetBinaryMode stdin True
+            res <- L.getContents
+            mapM_ dumpPage (fromDoc $! decode res)
+        _   -> forM_ args $! \inFile -> do
+            let pdfdump = __Bin__ </> "pdfdump"
+            (_,out,err,pid) <- runInteractiveCommand $! pdfdump ++ " \"" ++ inFile ++ "\""
+            res <- L.hGetContents out
+            L.length res `seq` waitForProcess pid
+            when (L.null res) $! do
+                L.hPutStr stderr =<< L.hGetContents err
+            mapM_ dumpPage (fromDoc $! decode res)
+
+dumpPage :: Page -> IO ()
+dumpPage page
+    | IM.null pg    = return ()
+    | otherwise     = do
+        _CurrentLine <- newIORef maxBound
+        forM_ (IM.toAscList pg) $! \(lineNum, MkLine pt strs) -> do
+            linePrev <- readIORef _CurrentLine
+            replicateM_ ((lineNum - linePrev + (pt `div` 4)) `div` pt) (S.putStr _NewLine)
+            _CurrentColumn <- newIORef 0
+            forM_ (IM.toAscList strs) $! \(col, str) -> do
+                cur <- readIORef _CurrentColumn
+                S.putStr $! S.take (col - cur) _Spaces
+                S.putStr str
+                writeIORef _CurrentColumn (col + S.length str)
+            S.putStr _NewLine
+            writeIORef _CurrentLine (lineNum+pt)
+        S.putStr _NewPage
+    where
+    pg = fromPage page
+
+_Spaces, _NewLine, _NewPage :: ByteString
+_Spaces  = S.replicate 4096 0x20
+_NewLine = C.pack "\r\n"
+_NewPage = C.pack "\r\n\x0C\r\n"
+
+-- A Page is a IntMap from line-number to a map from column-number to bytestring.
+newtype Doc = MkDoc { fromDoc :: [Page] } deriving Show
+newtype Page = MkPage { fromPage :: IM.IntMap Line } deriving Show
+data Line = MkLine
+    { linePt    :: !Int
+    , lineStrs  :: !(IM.IntMap S.ByteString)
+    }
+    deriving Show
+
+instance Binary Doc where
+    put = undefined
+    get = liftM MkDoc getList
+        where
+        getList = do
+            rv  <- isEmpty
+            if rv then return []
+                  else liftM2 (:) get getList
+
+data Chunk = MkChunk
+    { c_right   :: !Int
+    , c_upper   :: !Int
+    , c_pt      :: !Int
+    , c_str     :: !ByteString
+    }
+    deriving Show
+
+instance Binary Page where
+    put = error "put Page is not defined"
+    get = getChunk maxBound []
+        where
+        getChunk minPt chunks = do
+            rv  <- isEmpty
+            if rv then done else do
+                w8  <- getWord8
+                case w8 of
+                    0x6C    -> do -- 'l'
+                        skip 1
+                        col <- if __AlignRight__
+                            then skip 9 >> getInt 6
+                            else getInt 6 >>= ((skip 9 >>) . return)
+                        skip 21
+                        ln      <- getInt 6
+                        skip 3
+                        pt      <- getInt 6
+                        skip 7
+                        sz      <- getInt 4
+                        skip 1
+                        str     <- getByteString sz
+                        w8'     <- getWord8
+                        case w8' of
+                            0x0D    -> skip 1
+                            0x0A    -> return ()
+                            _       -> fail $! "Bad parse: " ++ show w8'
+                        let pt' = min minPt pt
+                        getChunk pt' (MkChunk col ln pt str:chunks)
+                    0x0D    -> skip 1 >> done
+                    0x0A    -> done
+                    _       -> fail $! "Bad parse: " ++ show w8
+            where
+            done = return $! pageOf (foldl (buildPage minPt) (MkBuild (MkPage IM.empty) 0) chunks)
+        getInt :: Int -> Get Int
+        getInt (n+1) = liftM2 mkInt getWord8 (getInt n)
+            where
+            mkInt digit rest = fromEnum (digit - 0x30) * (10 ^ n) + rest
+        getInt _     = return 0
+        buildPage minPt (MkBuild (MkPage pg) base) (MkChunk col ln pt str)
+            = MkBuild (MkPage (IM.insert base' entry pg)) base'
+            where
+            sz      = S.length str
+            width   = if __AlignRight__
+                then ((col * 2) `div` minPt) - sz
+                else ((col * 2) `div` minPt)
+            base'   = if abs (ln - base) + (minPt `div` 4) < minPt then base else ln
+            entry   = case IM.lookup base' pg of
+                Just (MkLine pt' strs)  -> MkLine (max pt pt') (IM.insert width str strs)
+                _                       -> MkLine pt (IM.singleton width str)
+
+data Build = MkBuild
+    { pageOf    :: !Page
+    , baseOf    :: !Int
+    }
+    deriving Show
+
diff --git a/xpdf/Annot.cc b/xpdf/Annot.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Annot.cc
@@ -0,0 +1,1556 @@
+//========================================================================
+//
+// Annot.cc
+//
+// Copyright 2000-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <math.h>
+#include "gmem.h"
+#include "GList.h"
+#include "Error.h"
+#include "Object.h"
+#include "Catalog.h"
+#include "Gfx.h"
+#include "GfxFont.h"
+#include "Lexer.h"
+#include "Annot.h"
+
+//------------------------------------------------------------------------
+
+#define annotFlagHidden    0x0002
+#define annotFlagPrint     0x0004
+#define annotFlagNoView    0x0020
+
+#define fieldFlagReadOnly           0x00000001
+#define fieldFlagRequired           0x00000002
+#define fieldFlagNoExport           0x00000004
+#define fieldFlagMultiline          0x00001000
+#define fieldFlagPassword           0x00002000
+#define fieldFlagNoToggleToOff      0x00004000
+#define fieldFlagRadio              0x00008000
+#define fieldFlagPushbutton         0x00010000
+#define fieldFlagCombo              0x00020000
+#define fieldFlagEdit               0x00040000
+#define fieldFlagSort               0x00080000
+#define fieldFlagFileSelect         0x00100000
+#define fieldFlagMultiSelect        0x00200000
+#define fieldFlagDoNotSpellCheck    0x00400000
+#define fieldFlagDoNotScroll        0x00800000
+#define fieldFlagComb               0x01000000
+#define fieldFlagRichText           0x02000000
+#define fieldFlagRadiosInUnison     0x02000000
+#define fieldFlagCommitOnSelChange  0x04000000
+
+#define fieldQuadLeft   0
+#define fieldQuadCenter 1
+#define fieldQuadRight  2
+
+// distance of Bezier control point from center for circle approximation
+// = (4 * (sqrt(2) - 1) / 3) * r
+#define bezierCircle 0.55228475
+
+//------------------------------------------------------------------------
+// AnnotBorderStyle
+//------------------------------------------------------------------------
+
+AnnotBorderStyle::AnnotBorderStyle(AnnotBorderType typeA, double widthA,
+				   double *dashA, int dashLengthA,
+				   double rA, double gA, double bA) {
+  type = typeA;
+  width = widthA;
+  dash = dashA;
+  dashLength = dashLengthA;
+  r = rA;
+  g = gA;
+  b = bA;
+}
+
+AnnotBorderStyle::~AnnotBorderStyle() {
+  if (dash) {
+    gfree(dash);
+  }
+}
+
+//------------------------------------------------------------------------
+// Annot
+//------------------------------------------------------------------------
+
+Annot::Annot(XRef *xrefA, Dict *acroForm, Dict *dict, Ref *refA) {
+  Object apObj, asObj, obj1, obj2, obj3;
+  AnnotBorderType borderType;
+  double borderWidth;
+  double *borderDash;
+  int borderDashLength;
+  double borderR, borderG, borderB;
+  double t;
+  int i;
+
+  ok = gTrue;
+  xref = xrefA;
+  ref = *refA;
+  type = NULL;
+  appearBuf = NULL;
+  borderStyle = NULL;
+
+  //----- parse the type
+
+  if (dict->lookup("Subtype", &obj1)->isName()) {
+    type = new GString(obj1.getName());
+  }
+  obj1.free();
+
+  //----- parse the rectangle
+
+  if (dict->lookup("Rect", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 4) {
+    xMin = yMin = xMax = yMax = 0;
+    if (obj1.arrayGet(0, &obj2)->isNum()) {
+      xMin = obj2.getNum();
+    }
+    obj2.free();
+    if (obj1.arrayGet(1, &obj2)->isNum()) {
+      yMin = obj2.getNum();
+    }
+    obj2.free();
+    if (obj1.arrayGet(2, &obj2)->isNum()) {
+      xMax = obj2.getNum();
+    }
+    obj2.free();
+    if (obj1.arrayGet(3, &obj2)->isNum()) {
+      yMax = obj2.getNum();
+    }
+    obj2.free();
+    if (xMin > xMax) {
+      t = xMin; xMin = xMax; xMax = t;
+    }
+    if (yMin > yMax) {
+      t = yMin; yMin = yMax; yMax = t;
+    }
+  } else {
+    error(-1, "Bad bounding box for annotation");
+    ok = gFalse;
+  }
+  obj1.free();
+
+  //----- parse the flags
+
+  if (dict->lookup("F", &obj1)->isInt()) {
+    flags = obj1.getInt();
+  } else {
+    flags = 0;
+  }
+  obj1.free();
+
+  //----- parse the border style
+
+  borderType = annotBorderSolid;
+  borderWidth = 1;
+  borderDash = NULL;
+  borderDashLength = 0;
+  borderR = 0;
+  borderG = 0;
+  borderB = 1;
+  if (dict->lookup("BS", &obj1)->isDict()) {
+    if (obj1.dictLookup("S", &obj2)->isName()) {
+      if (obj2.isName("S")) {
+	borderType = annotBorderSolid;
+      } else if (obj2.isName("D")) {
+	borderType = annotBorderDashed;
+      } else if (obj2.isName("B")) {
+	borderType = annotBorderBeveled;
+      } else if (obj2.isName("I")) {
+	borderType = annotBorderInset;
+      } else if (obj2.isName("U")) {
+	borderType = annotBorderUnderlined;
+      }
+    }
+    obj2.free();
+    if (obj1.dictLookup("W", &obj2)->isNum()) {
+      borderWidth = obj2.getNum();
+    }
+    obj2.free();
+    if (obj1.dictLookup("D", &obj2)->isArray()) {
+      borderDashLength = obj2.arrayGetLength();
+      borderDash = (double *)gmallocn(borderDashLength, sizeof(double));
+      for (i = 0; i < borderDashLength; ++i) {
+	if (obj2.arrayGet(i, &obj3)->isNum()) {
+	  borderDash[i] = obj3.getNum();
+	} else {
+	  borderDash[i] = 1;
+	}
+	obj3.free();
+      }
+    }
+    obj2.free();
+  } else {
+    obj1.free();
+    if (dict->lookup("Border", &obj1)->isArray()) {
+      if (obj1.arrayGetLength() >= 3) {
+	if (obj1.arrayGet(2, &obj2)->isNum()) {
+	  borderWidth = obj2.getNum();
+	}
+	obj2.free();
+	if (obj1.arrayGetLength() >= 4) {
+	  if (obj1.arrayGet(3, &obj2)->isArray()) {
+	    borderType = annotBorderDashed;
+	    borderDashLength = obj2.arrayGetLength();
+	    borderDash = (double *)gmallocn(borderDashLength, sizeof(double));
+	    for (i = 0; i < borderDashLength; ++i) {
+	      if (obj2.arrayGet(i, &obj3)->isNum()) {
+		borderDash[i] = obj3.getNum();
+	      } else {
+		borderDash[i] = 1;
+	      }
+	      obj3.free();
+	    }
+	  } else {
+	    // Adobe draws no border at all if the last element is of
+	    // the wrong type.
+	    borderWidth = 0;
+	  }
+	  obj2.free();
+	}
+      }
+    }
+  }
+  obj1.free();
+  if (dict->lookup("C", &obj1)->isArray() && obj1.arrayGetLength() == 3) {
+    if (obj1.arrayGet(0, &obj2)->isNum()) {
+      borderR = obj2.getNum();
+    }
+    obj1.free();
+    if (obj1.arrayGet(1, &obj2)->isNum()) {
+      borderG = obj2.getNum();
+    }
+    obj1.free();
+    if (obj1.arrayGet(2, &obj2)->isNum()) {
+      borderB = obj2.getNum();
+    }
+    obj1.free();
+  }
+  obj1.free();
+  borderStyle = new AnnotBorderStyle(borderType, borderWidth,
+				     borderDash, borderDashLength,
+				     borderR, borderG, borderB);
+
+  //----- get the annotation appearance
+
+  if (dict->lookup("AP", &apObj)->isDict()) {
+    if (dict->lookup("AS", &asObj)->isName()) {
+      if (apObj.dictLookup("N", &obj1)->isDict()) {
+	if (obj1.dictLookupNF(asObj.getName(), &obj2)->isRef()) {
+	  obj2.copy(&appearance);
+	  ok = gTrue;
+	} else {
+	  obj2.free();
+	  if (obj1.dictLookupNF("Off", &obj2)->isRef()) {
+	    obj2.copy(&appearance);
+	  }
+	}
+	obj2.free();
+      }
+      obj1.free();
+    } else {
+      if (apObj.dictLookupNF("N", &obj1)->isRef()) {
+	obj1.copy(&appearance);
+      }
+      obj1.free();
+    }
+    asObj.free();
+  }
+  apObj.free();
+}
+
+Annot::~Annot() {
+  if (type) {
+    delete type;
+  }
+  appearance.free();
+  if (appearBuf) {
+    delete appearBuf;
+  }
+  if (borderStyle) {
+    delete borderStyle;
+  }
+}
+
+void Annot::generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm) {
+  Object mkObj, ftObj, appearDict, drObj, obj1, obj2, obj3;
+  Dict *mkDict;
+  MemStream *appearStream;
+  GfxFontDict *fontDict;
+  GBool hasCaption;
+  double w, dx, dy, r;
+  double *dash;
+  GString *caption, *da;
+  GString **text;
+  GBool *selection;
+  int dashLength, ff, quadding, comb, nOptions, topIdx, i, j;
+
+  // must be a Widget annotation
+  if (type->cmp("Widget")) {
+    return;
+  }
+
+  appearBuf = new GString();
+
+  // get the appearance characteristics (MK) dictionary
+  if (annot->lookup("MK", &mkObj)->isDict()) {
+    mkDict = mkObj.getDict();
+  } else {
+    mkDict = NULL;
+  }
+
+  // draw the background
+  if (mkDict) {
+    if (mkDict->lookup("BG", &obj1)->isArray() &&
+	obj1.arrayGetLength() > 0) {
+      setColor(obj1.getArray(), gTrue, 0);
+      appearBuf->appendf("0 0 {0:.2f} {1:.2f} re f\n",
+			 xMax - xMin, yMax - yMin);
+    }
+    obj1.free();
+  }
+
+  // get the field type
+  fieldLookup(field, "FT", &ftObj);
+
+  // get the field flags (Ff) value
+  if (fieldLookup(field, "Ff", &obj1)->isInt()) {
+    ff = obj1.getInt();
+  } else {
+    ff = 0;
+  }
+  obj1.free();
+
+  // draw the border
+  if (mkDict) {
+    w = borderStyle->getWidth();
+    if (w > 0) {
+      mkDict->lookup("BC", &obj1);
+      if (!(obj1.isArray() && obj1.arrayGetLength() > 0)) {
+	mkDict->lookup("BG", &obj1);
+      }
+      if (obj1.isArray() && obj1.arrayGetLength() > 0) {
+	dx = xMax - xMin;
+	dy = yMax - yMin;
+
+	// radio buttons with no caption have a round border
+	hasCaption = mkDict->lookup("CA", &obj2)->isString();
+	obj2.free();
+	if (ftObj.isName("Btn") && (ff & fieldFlagRadio) && !hasCaption) {
+	  r = 0.5 * (dx < dy ? dx : dy);
+	  switch (borderStyle->getType()) {
+	  case annotBorderDashed:
+	    appearBuf->append("[");
+	    borderStyle->getDash(&dash, &dashLength);
+	    for (i = 0; i < dashLength; ++i) {
+	      appearBuf->appendf(" {0:.2f}", dash[i]);
+	    }
+	    appearBuf->append("] 0 d\n");
+	    // fall through to the solid case
+	  case annotBorderSolid:
+	  case annotBorderUnderlined:
+	    appearBuf->appendf("{0:.2f} w\n", w);
+	    setColor(obj1.getArray(), gFalse, 0);
+	    drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * w, gFalse);
+	    break;
+	  case annotBorderBeveled:
+	  case annotBorderInset:
+	    appearBuf->appendf("{0:.2f} w\n", 0.5 * w);
+	    setColor(obj1.getArray(), gFalse, 0);
+	    drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * w, gFalse);
+	    setColor(obj1.getArray(), gFalse,
+		     borderStyle->getType() == annotBorderBeveled ? 1 : -1);
+	    drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * w);
+	    setColor(obj1.getArray(), gFalse,
+		     borderStyle->getType() == annotBorderBeveled ? -1 : 1);
+	    drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * w);
+	    break;
+	  }
+
+	} else {
+	  switch (borderStyle->getType()) {
+	  case annotBorderDashed:
+	    appearBuf->append("[");
+	    borderStyle->getDash(&dash, &dashLength);
+	    for (i = 0; i < dashLength; ++i) {
+	      appearBuf->appendf(" {0:.2f}", dash[i]);
+	    }
+	    appearBuf->append("] 0 d\n");
+	    // fall through to the solid case
+	  case annotBorderSolid:
+	    appearBuf->appendf("{0:.2f} w\n", w);
+	    setColor(obj1.getArray(), gFalse, 0);
+	    appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re s\n",
+			       0.5 * w, dx - w, dy - w);
+	    break;
+	  case annotBorderBeveled:
+	  case annotBorderInset:
+	    setColor(obj1.getArray(), gTrue,
+		     borderStyle->getType() == annotBorderBeveled ? 1 : -1);
+	    appearBuf->append("0 0 m\n");
+	    appearBuf->appendf("0 {0:.2f} l\n", dy);
+	    appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy);
+	    appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w);
+	    appearBuf->appendf("{0:.2f} {1:.2f} l\n", w, dy - w);
+	    appearBuf->appendf("{0:.2f} {0:.2f} l\n", w);
+	    appearBuf->append("f\n");
+	    setColor(obj1.getArray(), gTrue,
+		     borderStyle->getType() == annotBorderBeveled ? -1 : 1);
+	    appearBuf->append("0 0 m\n");
+	    appearBuf->appendf("{0:.2f} 0 l\n", dx);
+	    appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy);
+	    appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w);
+	    appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, w);
+	    appearBuf->appendf("{0:.2f} {0:.2f} l\n", w);
+	    appearBuf->append("f\n");
+	    break;
+	  case annotBorderUnderlined:
+	    appearBuf->appendf("{0:.2f} w\n", w);
+	    setColor(obj1.getArray(), gFalse, 0);
+	    appearBuf->appendf("0 0 m {0:.2f} 0 l s\n", dx);
+	    break;
+	  }
+
+	  // clip to the inside of the border
+	  appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n",
+			     w, dx - 2 * w, dy - 2 * w);
+	}
+      }
+      obj1.free();
+    }
+  }
+
+  // get the resource dictionary
+  acroForm->lookup("DR", &drObj);
+
+  // build the font dictionary
+  if (drObj.isDict() && drObj.dictLookup("Font", &obj1)->isDict()) {
+    fontDict = new GfxFontDict(xref, NULL, obj1.getDict());
+  } else {
+    fontDict = NULL;
+  }
+  obj1.free();
+
+  // get the default appearance string
+  if (fieldLookup(field, "DA", &obj1)->isNull()) {
+    obj1.free();
+    acroForm->lookup("DA", &obj1);
+  }
+  if (obj1.isString()) {
+    da = obj1.getString()->copy();
+  } else {
+    da = NULL;
+  }
+  obj1.free();
+
+  // draw the field contents
+  if (ftObj.isName("Btn")) {
+    caption = NULL;
+    if (mkDict) {
+      if (mkDict->lookup("CA", &obj1)->isString()) {
+	caption = obj1.getString()->copy();
+      }
+      obj1.free();
+    }
+    // radio button
+    if (ff & fieldFlagRadio) {
+      //~ Acrobat doesn't draw a caption if there is no AP dict (?)
+      if (fieldLookup(field, "V", &obj1)->isName()) {
+	if (annot->lookup("AS", &obj2)->isName(obj1.getName())) {
+	  if (caption) {
+	    drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter,
+		     gFalse, gTrue);
+	  } else {
+	    if (mkDict) {
+	      if (mkDict->lookup("BC", &obj3)->isArray() &&
+		  obj3.arrayGetLength() > 0) {
+		dx = xMax - xMin;
+		dy = yMax - yMin;
+		setColor(obj3.getArray(), gTrue, 0);
+		drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy),
+			   gTrue);
+	      }
+	      obj3.free();
+	    }
+	  }
+	}
+	obj2.free();
+      }
+      obj1.free();
+    // pushbutton
+    } else if (ff & fieldFlagPushbutton) {
+      if (caption) {
+	drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter,
+		 gFalse, gFalse);
+      }
+    // checkbox
+    } else {
+      // According to the PDF spec the off state must be named "Off",
+      // and the on state can be named anything, but Acrobat apparently
+      // looks for "Yes" and treats anything else as off.
+      if (fieldLookup(field, "V", &obj1)->isName("Yes")) {
+	if (!caption) {
+	  caption = new GString("3"); // ZapfDingbats checkmark
+	}
+	drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter,
+		 gFalse, gTrue);
+      }
+      obj1.free();
+    }
+    if (caption) {
+      delete caption;
+    }
+  } else if (ftObj.isName("Tx")) {
+    //~ value strings can be Unicode
+    if (fieldLookup(field, "V", &obj1)->isString()) {
+      if (fieldLookup(field, "Q", &obj2)->isInt()) {
+	quadding = obj2.getInt();
+      } else {
+	quadding = fieldQuadLeft;
+      }
+      obj2.free();
+      comb = 0;
+      if (ff & fieldFlagComb) {
+	if (fieldLookup(field, "MaxLen", &obj2)->isInt()) {
+	  comb = obj2.getInt();
+	}
+	obj2.free();
+      }
+      drawText(obj1.getString(), da, fontDict,
+	       ff & fieldFlagMultiline, comb, quadding, gTrue, gFalse);
+    }
+    obj1.free();
+  } else if (ftObj.isName("Ch")) {
+    //~ value/option strings can be Unicode
+    if (fieldLookup(field, "Q", &obj1)->isInt()) {
+      quadding = obj1.getInt();
+    } else {
+      quadding = fieldQuadLeft;
+    }
+    obj1.free();
+    // combo box
+    if (ff & fieldFlagCombo) {
+      if (fieldLookup(field, "V", &obj1)->isString()) {
+	drawText(obj1.getString(), da, fontDict,
+		 gFalse, 0, quadding, gTrue, gFalse);
+	//~ Acrobat draws a popup icon on the right side
+      }
+      obj1.free();
+    // list box
+    } else {
+      if (field->lookup("Opt", &obj1)->isArray()) {
+	nOptions = obj1.arrayGetLength();
+	// get the option text
+	text = (GString **)gmallocn(nOptions, sizeof(GString *));
+	for (i = 0; i < nOptions; ++i) {
+	  text[i] = NULL;
+	  obj1.arrayGet(i, &obj2);
+	  if (obj2.isString()) {
+	    text[i] = obj2.getString()->copy();
+	  } else if (obj2.isArray() && obj2.arrayGetLength() == 2) {
+	    if (obj2.arrayGet(1, &obj3)->isString()) {
+	      text[i] = obj3.getString()->copy();
+	    }
+	    obj3.free();
+	  }
+	  obj2.free();
+	  if (!text[i]) {
+	    text[i] = new GString();
+	  }
+	}
+	// get the selected option(s)
+	selection = (GBool *)gmallocn(nOptions, sizeof(GBool));
+	//~ need to use the I field in addition to the V field
+	fieldLookup(field, "V", &obj2);
+	for (i = 0; i < nOptions; ++i) {
+	  selection[i] = gFalse;
+	  if (obj2.isString()) {
+	    if (!obj2.getString()->cmp(text[i])) {
+	      selection[i] = gTrue;
+	    }
+	  } else if (obj2.isArray()) {
+	    for (j = 0; j < obj2.arrayGetLength(); ++j) {
+	      if (obj2.arrayGet(j, &obj3)->isString() &&
+		  !obj3.getString()->cmp(text[i])) {
+		selection[i] = gTrue;
+	      }
+	      obj3.free();
+	    }
+	  }
+	}
+	obj2.free();
+	// get the top index
+	if (field->lookup("TI", &obj2)->isInt()) {
+	  topIdx = obj2.getInt();
+	} else {
+	  topIdx = 0;
+	}
+	obj2.free();
+	// draw the text
+	drawListBox(text, selection, nOptions, topIdx, da, fontDict, quadding);
+	for (i = 0; i < nOptions; ++i) {
+	  delete text[i];
+	}
+	gfree(text);
+	gfree(selection);
+      }
+      obj1.free();
+    }
+  } else if (ftObj.isName("Sig")) {
+    //~unimp
+  } else {
+    error(-1, "Unknown field type");
+  }
+
+  if (da) {
+    delete da;
+  }
+
+  // build the appearance stream dictionary
+  appearDict.initDict(xref);
+  appearDict.dictAdd(copyString("Length"),
+		     obj1.initInt(appearBuf->getLength()));
+  appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form"));
+  obj1.initArray(xref);
+  obj1.arrayAdd(obj2.initReal(0));
+  obj1.arrayAdd(obj2.initReal(0));
+  obj1.arrayAdd(obj2.initReal(xMax - xMin));
+  obj1.arrayAdd(obj2.initReal(yMax - yMin));
+  appearDict.dictAdd(copyString("BBox"), &obj1);
+
+  // set the resource dictionary
+  if (drObj.isDict()) {
+    appearDict.dictAdd(copyString("Resources"), drObj.copy(&obj1));
+  }
+  drObj.free();
+
+  // build the appearance stream
+  appearStream = new MemStream(appearBuf->getCString(), 0,
+			       appearBuf->getLength(), &appearDict);
+  appearance.free();
+  appearance.initStream(appearStream);
+
+  if (fontDict) {
+    delete fontDict;
+  }
+  ftObj.free();
+  mkObj.free();
+}
+
+// Set the current fill or stroke color, based on <a> (which should
+// have 1, 3, or 4 elements).  If <adjust> is +1, color is brightened;
+// if <adjust> is -1, color is darkened; otherwise color is not
+// modified.
+void Annot::setColor(Array *a, GBool fill, int adjust) {
+  Object obj1;
+  double color[4];
+  int nComps, i;
+
+  nComps = a->getLength();
+  if (nComps > 4) {
+    nComps = 4;
+  }
+  for (i = 0; i < nComps && i < 4; ++i) {
+    if (a->get(i, &obj1)->isNum()) {
+      color[i] = obj1.getNum();
+    } else {
+      color[i] = 0;
+    }
+    obj1.free();
+  }
+  if (nComps == 4) {
+    adjust = -adjust;
+  }
+  if (adjust > 0) {
+    for (i = 0; i < nComps; ++i) {
+      color[i] = 0.5 * color[i] + 0.5;
+    }
+  } else if (adjust < 0) {
+    for (i = 0; i < nComps; ++i) {
+      color[i] = 0.5 * color[i];
+    }
+  }
+  if (nComps == 4) {
+    appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:c}\n",
+		       color[0], color[1], color[2], color[3],
+		       fill ? 'k' : 'K');
+  } else if (nComps == 3) {
+    appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:s}\n",
+		       color[0], color[1], color[2],
+		       fill ? "rg" : "RG");
+  } else {
+    appearBuf->appendf("{0:.2f} {1:c}\n",
+		       color[0],
+		       fill ? 'g' : 'G');
+  }
+}
+
+// Draw the variable text or caption for a field.
+void Annot::drawText(GString *text, GString *da, GfxFontDict *fontDict,
+		     GBool multiline, int comb, int quadding,
+		     GBool txField, GBool forceZapfDingbats) {
+  GList *daToks;
+  GString *tok;
+  GfxFont *font;
+  double fontSize, fontSize2, border, x, xPrev, y, w, w2, wMax;
+  int tfPos, tmPos, i, j, k, c;
+
+  //~ if there is no MK entry, this should use the existing content stream,
+  //~ and only replace the marked content portion of it
+  //~ (this is only relevant for Tx fields)
+
+  // parse the default appearance string
+  tfPos = tmPos = -1;
+  if (da) {
+    daToks = new GList();
+    i = 0;
+    while (i < da->getLength()) {
+      while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) {
+	++i;
+      }
+      if (i < da->getLength()) {
+	for (j = i + 1;
+	     j < da->getLength() && !Lexer::isSpace(da->getChar(j));
+	     ++j) ;
+	daToks->append(new GString(da, i, j - i));
+	i = j;
+      }
+    }
+    for (i = 2; i < daToks->getLength(); ++i) {
+      if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) {
+	tfPos = i - 2;
+      } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) {
+	tmPos = i - 6;
+      }
+    }
+  } else {
+    daToks = NULL;
+  }
+
+  // force ZapfDingbats
+  //~ this should create the font if needed (?)
+  if (forceZapfDingbats) {
+    if (tfPos >= 0) {
+      tok = (GString *)daToks->get(tfPos);
+      if (tok->cmp("/ZaDb")) {
+	tok->clear();
+	tok->append("/ZaDb");
+      }
+    }
+  }
+
+  // get the font and font size
+  font = NULL;
+  fontSize = 0;
+  if (tfPos >= 0) {
+    tok = (GString *)daToks->get(tfPos);
+    if (tok->getLength() >= 1 && tok->getChar(0) == '/') {
+      if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) {
+	error(-1, "Unknown font in field's DA string");
+      }
+    } else {
+      error(-1, "Invalid font name in 'Tf' operator in field's DA string");
+    }
+    tok = (GString *)daToks->get(tfPos + 1);
+    fontSize = atof(tok->getCString());
+  } else {
+    error(-1, "Missing 'Tf' operator in field's DA string");
+  }
+
+  // get the border width
+  border = borderStyle->getWidth();
+
+  // setup
+  if (txField) {
+    appearBuf->append("/Tx BMC\n");
+  }
+  appearBuf->append("q\n");
+  appearBuf->append("BT\n");
+
+  // multi-line text
+  if (multiline) {
+    // note: the comb flag is ignored in multiline mode
+
+    wMax = xMax - xMin - 2 * border - 4;
+
+    // compute font autosize
+    if (fontSize == 0) {
+      for (fontSize = 20; fontSize > 1; --fontSize) {
+	y = yMax - yMin;
+	w2 = 0;
+	i = 0;
+	while (i < text->getLength()) {
+	  getNextLine(text, i, font, fontSize, wMax, &j, &w, &k);
+	  if (w > w2) {
+	    w2 = w;
+	  }
+	  i = k;
+	  y -= fontSize;
+	}
+	// approximate the descender for the last line
+	if (y >= 0.33 * fontSize) {
+	  break;
+	}
+      }
+      if (tfPos >= 0) {
+	tok = (GString *)daToks->get(tfPos + 1);
+	tok->clear();
+	tok->appendf("{0:.2f}", fontSize);
+      }
+    }
+
+    // starting y coordinate
+    // (note: each line of text starts with a Td operator that moves
+    // down a line)
+    y = yMax - yMin;
+
+    // set the font matrix
+    if (tmPos >= 0) {
+      tok = (GString *)daToks->get(tmPos + 4);
+      tok->clear();
+      tok->append('0');
+      tok = (GString *)daToks->get(tmPos + 5);
+      tok->clear();
+      tok->appendf("{0:.2f}", y);
+    }
+
+    // write the DA string
+    if (daToks) {
+      for (i = 0; i < daToks->getLength(); ++i) {
+	appearBuf->append((GString *)daToks->get(i))->append(' ');
+      }
+    }
+
+    // write the font matrix (if not part of the DA string)
+    if (tmPos < 0) {
+      appearBuf->appendf("1 0 0 1 0 {0:.2f} Tm\n", y);
+    }
+
+    // write a series of lines of text
+    i = 0;
+    xPrev = 0;
+    while (i < text->getLength()) {
+
+      getNextLine(text, i, font, fontSize, wMax, &j, &w, &k);
+
+      // compute text start position
+      switch (quadding) {
+      case fieldQuadLeft:
+      default:
+	x = border + 2;
+	break;
+      case fieldQuadCenter:
+	x = (xMax - xMin - w) / 2;
+	break;
+      case fieldQuadRight:
+	x = xMax - xMin - border - 2 - w;
+	break;
+      }
+
+      // draw the line
+      appearBuf->appendf("{0:.2f} {1:.2f} Td\n", x - xPrev, -fontSize);
+      appearBuf->append('(');
+      for (; i < j; ++i) {
+	c = text->getChar(i) & 0xff;
+	if (c == '(' || c == ')' || c == '\\') {
+	  appearBuf->append('\\');
+	  appearBuf->append(c);
+	} else if (c < 0x20 || c >= 0x80) {
+	  appearBuf->appendf("\\{0:03o}", c);
+	} else {
+	  appearBuf->append(c);
+	}
+      }
+      appearBuf->append(") Tj\n");
+
+      // next line
+      i = k;
+      xPrev = x;
+    }
+
+  // single-line text
+  } else {
+    //~ replace newlines with spaces? - what does Acrobat do?
+
+    // comb formatting
+    if (comb > 0) {
+
+      // compute comb spacing
+      w = (xMax - xMin - 2 * border) / comb;
+
+      // compute font autosize
+      if (fontSize == 0) {
+	fontSize = yMax - yMin - 2 * border;
+	if (w < fontSize) {
+	  fontSize = w;
+	}
+	fontSize = floor(fontSize);
+	if (tfPos >= 0) {
+	  tok = (GString *)daToks->get(tfPos + 1);
+	  tok->clear();
+	  tok->appendf("{0:.2f}", fontSize);
+	}
+      }
+
+      // compute text start position
+      switch (quadding) {
+      case fieldQuadLeft:
+      default:
+	x = border + 2;
+	break;
+      case fieldQuadCenter:
+	x = border + 2 + 0.5 * (comb - text->getLength()) * w;
+	break;
+      case fieldQuadRight:
+	x = border + 2 + (comb - text->getLength()) * w;
+	break;
+      }
+      y = 0.5 * (yMax - yMin) - 0.4 * fontSize;
+
+      // set the font matrix
+      if (tmPos >= 0) {
+	tok = (GString *)daToks->get(tmPos + 4);
+	tok->clear();
+	tok->appendf("{0:.2f}", x);
+	tok = (GString *)daToks->get(tmPos + 5);
+	tok->clear();
+	tok->appendf("{0:.2f}", y);
+      }
+
+      // write the DA string
+      if (daToks) {
+	for (i = 0; i < daToks->getLength(); ++i) {
+	  appearBuf->append((GString *)daToks->get(i))->append(' ');
+	}
+      }
+
+      // write the font matrix (if not part of the DA string)
+      if (tmPos < 0) {
+	appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y);
+      }
+
+      // write the text string
+      //~ this should center (instead of left-justify) each character within
+      //~     its comb cell
+      for (i = 0; i < text->getLength(); ++i) {
+	if (i > 0) {
+	  appearBuf->appendf("{0:.2f} 0 Td\n", w);
+	}
+	appearBuf->append('(');
+	c = text->getChar(i) & 0xff;
+	if (c == '(' || c == ')' || c == '\\') {
+	  appearBuf->append('\\');
+	  appearBuf->append(c);
+	} else if (c < 0x20 || c >= 0x80) {
+	  appearBuf->appendf("{0:.2f} 0 Td\n", w);
+	} else {
+	  appearBuf->append(c);
+	}
+	appearBuf->append(") Tj\n");
+      }
+
+    // regular (non-comb) formatting
+    } else {
+
+      // compute string width
+      if (font && !font->isCIDFont()) {
+	w = 0;
+	for (i = 0; i < text->getLength(); ++i) {
+	  w += ((Gfx8BitFont *)font)->getWidth(text->getChar(i));
+	}
+      } else {
+	// otherwise, make a crude estimate
+	w = text->getLength() * 0.5;
+      }
+
+      // compute font autosize
+      if (fontSize == 0) {
+	fontSize = yMax - yMin - 2 * border;
+	fontSize2 = (xMax - xMin - 4 - 2 * border) / w;
+	if (fontSize2 < fontSize) {
+	  fontSize = fontSize2;
+	}
+	fontSize = floor(fontSize);
+	if (tfPos >= 0) {
+	  tok = (GString *)daToks->get(tfPos + 1);
+	  tok->clear();
+	  tok->appendf("{0:.2f}", fontSize);
+	}
+      }
+
+      // compute text start position
+      w *= fontSize;
+      switch (quadding) {
+      case fieldQuadLeft:
+      default:
+	x = border + 2;
+	break;
+      case fieldQuadCenter:
+	x = (xMax - xMin - w) / 2;
+	break;
+      case fieldQuadRight:
+	x = xMax - xMin - border - 2 - w;
+	break;
+      }
+      y = 0.5 * (yMax - yMin) - 0.4 * fontSize;
+
+      // set the font matrix
+      if (tmPos >= 0) {
+	tok = (GString *)daToks->get(tmPos + 4);
+	tok->clear();
+	tok->appendf("{0:.2f}", x);
+	tok = (GString *)daToks->get(tmPos + 5);
+	tok->clear();
+	tok->appendf("{0:.2f}", y);
+      }
+
+      // write the DA string
+      if (daToks) {
+	for (i = 0; i < daToks->getLength(); ++i) {
+	  appearBuf->append((GString *)daToks->get(i))->append(' ');
+	}
+      }
+
+      // write the font matrix (if not part of the DA string)
+      if (tmPos < 0) {
+	appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y);
+      }
+
+      // write the text string
+      appearBuf->append('(');
+      for (i = 0; i < text->getLength(); ++i) {
+	c = text->getChar(i) & 0xff;
+	if (c == '(' || c == ')' || c == '\\') {
+	  appearBuf->append('\\');
+	  appearBuf->append(c);
+	} else if (c < 0x20 || c >= 0x80) {
+	  appearBuf->appendf("\\{0:03o}", c);
+	} else {
+	  appearBuf->append(c);
+	}
+      }
+      appearBuf->append(") Tj\n");
+    }
+  }
+
+  // cleanup
+  appearBuf->append("ET\n");
+  appearBuf->append("Q\n");
+  if (txField) {
+    appearBuf->append("EMC\n");
+  }
+
+  if (daToks) {
+    deleteGList(daToks, GString);
+  }
+}
+
+// Draw the variable text or caption for a field.
+void Annot::drawListBox(GString **text, GBool *selection,
+			int nOptions, int topIdx,
+			GString *da, GfxFontDict *fontDict, GBool quadding) {
+  GList *daToks;
+  GString *tok;
+  GfxFont *font;
+  double fontSize, fontSize2, border, x, y, w, wMax;
+  int tfPos, tmPos, i, j, c;
+
+  //~ if there is no MK entry, this should use the existing content stream,
+  //~ and only replace the marked content portion of it
+  //~ (this is only relevant for Tx fields)
+
+  // parse the default appearance string
+  tfPos = tmPos = -1;
+  if (da) {
+    daToks = new GList();
+    i = 0;
+    while (i < da->getLength()) {
+      while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) {
+	++i;
+      }
+      if (i < da->getLength()) {
+	for (j = i + 1;
+	     j < da->getLength() && !Lexer::isSpace(da->getChar(j));
+	     ++j) ;
+	daToks->append(new GString(da, i, j - i));
+	i = j;
+      }
+    }
+    for (i = 2; i < daToks->getLength(); ++i) {
+      if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) {
+	tfPos = i - 2;
+      } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) {
+	tmPos = i - 6;
+      }
+    }
+  } else {
+    daToks = NULL;
+  }
+
+  // get the font and font size
+  font = NULL;
+  fontSize = 0;
+  if (tfPos >= 0) {
+    tok = (GString *)daToks->get(tfPos);
+    if (tok->getLength() >= 1 && tok->getChar(0) == '/') {
+      if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) {
+	error(-1, "Unknown font in field's DA string");
+      }
+    } else {
+      error(-1, "Invalid font name in 'Tf' operator in field's DA string");
+    }
+    tok = (GString *)daToks->get(tfPos + 1);
+    fontSize = atof(tok->getCString());
+  } else {
+    error(-1, "Missing 'Tf' operator in field's DA string");
+  }
+
+  // get the border width
+  border = borderStyle->getWidth();
+
+  // compute font autosize
+  if (fontSize == 0) {
+    wMax = 0;
+    for (i = 0; i < nOptions; ++i) {
+      if (font && !font->isCIDFont()) {
+	w = 0;
+	for (j = 0; j < text[i]->getLength(); ++j) {
+	  w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j));
+	}
+      } else {
+	// otherwise, make a crude estimate
+	w = text[i]->getLength() * 0.5;
+      }
+      if (w > wMax) {
+	wMax = w;
+      }
+    }
+    fontSize = yMax - yMin - 2 * border;
+    fontSize2 = (xMax - xMin - 4 - 2 * border) / wMax;
+    if (fontSize2 < fontSize) {
+      fontSize = fontSize2;
+    }
+    fontSize = floor(fontSize);
+    if (tfPos >= 0) {
+      tok = (GString *)daToks->get(tfPos + 1);
+      tok->clear();
+      tok->appendf("{0:.2f}", fontSize);
+    }
+  }
+
+  // draw the text
+  y = yMax - yMin - 1.1 * fontSize;
+  for (i = topIdx; i < nOptions; ++i) {
+
+    // setup
+    appearBuf->append("q\n");
+
+    // draw the background if selected
+    if (selection[i]) {
+      appearBuf->append("0 g f\n");
+      appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re f\n",
+	      border,
+	      y - 0.2 * fontSize,
+	      xMax - xMin - 2 * border,
+	      1.1 * fontSize);
+    }
+
+    // setup
+    appearBuf->append("BT\n");
+
+    // compute string width
+    if (font && !font->isCIDFont()) {
+      w = 0;
+      for (j = 0; j < text[i]->getLength(); ++j) {
+	w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j));
+      }
+    } else {
+      // otherwise, make a crude estimate
+      w = text[i]->getLength() * 0.5;
+    }
+
+    // compute text start position
+    w *= fontSize;
+    switch (quadding) {
+    case fieldQuadLeft:
+    default:
+      x = border + 2;
+      break;
+    case fieldQuadCenter:
+      x = (xMax - xMin - w) / 2;
+      break;
+    case fieldQuadRight:
+      x = xMax - xMin - border - 2 - w;
+      break;
+    }
+
+    // set the font matrix
+    if (tmPos >= 0) {
+      tok = (GString *)daToks->get(tmPos + 4);
+      tok->clear();
+      tok->appendf("{0:.2f}", x);
+      tok = (GString *)daToks->get(tmPos + 5);
+      tok->clear();
+      tok->appendf("{0:.2f}", y);
+    }
+
+    // write the DA string
+    if (daToks) {
+      for (j = 0; j < daToks->getLength(); ++j) {
+	appearBuf->append((GString *)daToks->get(j))->append(' ');
+      }
+    }
+
+    // write the font matrix (if not part of the DA string)
+    if (tmPos < 0) {
+      appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y);
+    }
+
+    // change the text color if selected
+    if (selection[i]) {
+      appearBuf->append("1 g\n");
+    }
+
+    // write the text string
+    appearBuf->append('(');
+    for (j = 0; j < text[i]->getLength(); ++j) {
+      c = text[i]->getChar(j) & 0xff;
+      if (c == '(' || c == ')' || c == '\\') {
+	appearBuf->append('\\');
+	appearBuf->append(c);
+      } else if (c < 0x20 || c >= 0x80) {
+	appearBuf->appendf("\\{0:03o}", c);
+      } else {
+	appearBuf->append(c);
+      }
+    }
+    appearBuf->append(") Tj\n");
+
+    // cleanup
+    appearBuf->append("ET\n");
+    appearBuf->append("Q\n");
+
+    // next line
+    y -= 1.1 * fontSize;
+  }
+
+  if (daToks) {
+    deleteGList(daToks, GString);
+  }
+}
+
+// Figure out how much text will fit on the next line.  Returns:
+// *end = one past the last character to be included
+// *width = width of the characters start .. end-1
+// *next = index of first character on the following line
+void Annot::getNextLine(GString *text, int start,
+			GfxFont *font, double fontSize, double wMax,
+			int *end, double *width, int *next) {
+  double w, dw;
+  int j, k, c;
+
+  // figure out how much text will fit on the line
+  //~ what does Adobe do with tabs?
+  w = 0;
+  for (j = start; j < text->getLength() && w <= wMax; ++j) {
+    c = text->getChar(j) & 0xff;
+    if (c == 0x0a || c == 0x0d) {
+      break;
+    }
+    if (font && !font->isCIDFont()) {
+      dw = ((Gfx8BitFont *)font)->getWidth(c) * fontSize;
+    } else {
+      // otherwise, make a crude estimate
+      dw = 0.5 * fontSize;
+    }
+    w += dw;
+  }
+  if (w > wMax) {
+    for (k = j; k > start && text->getChar(k-1) != ' '; --k) ;
+    for (; k > start && text->getChar(k-1) == ' '; --k) ;
+    if (k > start) {
+      j = k;
+    }
+    if (j == start) {
+      // handle the pathological case where the first character is
+      // too wide to fit on the line all by itself
+      j = start + 1;
+    }
+  }
+  *end = j;
+
+  // compute the width
+  w = 0;
+  for (k = start; k < j; ++k) {
+    if (font && !font->isCIDFont()) {
+      dw = ((Gfx8BitFont *)font)->getWidth(text->getChar(k)) * fontSize;
+    } else {
+      // otherwise, make a crude estimate
+      dw = 0.5 * fontSize;
+    }
+    w += dw;
+  }
+  *width = w;
+
+  // next line
+  while (j < text->getLength() && text->getChar(j) == ' ') {
+    ++j;
+  }
+  if (j < text->getLength() && text->getChar(j) == 0x0d) {
+    ++j;
+  }
+  if (j < text->getLength() && text->getChar(j) == 0x0a) {
+    ++j;
+  }
+  *next = j;
+}
+
+// Draw an (approximate) circle of radius <r> centered at (<cx>, <cy>).
+// If <fill> is true, the circle is filled; otherwise it is stroked.
+void Annot::drawCircle(double cx, double cy, double r, GBool fill) {
+  appearBuf->appendf("{0:.2f} {1:.2f} m\n",
+		     cx + r, cy);
+  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+		     cx + r, cy + bezierCircle * r,
+		     cx + bezierCircle * r, cy + r,
+		     cx, cy + r);
+  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+		     cx - bezierCircle * r, cy + r,
+		     cx - r, cy + bezierCircle * r,
+		     cx - r, cy);
+  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+		     cx - r, cy - bezierCircle * r,
+		     cx - bezierCircle * r, cy - r,
+		     cx, cy - r);
+  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+		     cx + bezierCircle * r, cy - r,
+		     cx + r, cy - bezierCircle * r,
+		     cx + r, cy);
+  appearBuf->append(fill ? "f\n" : "s\n");
+}
+
+// Draw the top-left half of an (approximate) circle of radius <r>
+// centered at (<cx>, <cy>).
+void Annot::drawCircleTopLeft(double cx, double cy, double r) {
+  double r2;
+
+  r2 = r / sqrt(2.0);
+  appearBuf->appendf("{0:.2f} {1:.2f} m\n",
+		     cx + r2, cy + r2);
+  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+		     cx + (1 - bezierCircle) * r2,
+		     cy + (1 + bezierCircle) * r2,
+		     cx - (1 - bezierCircle) * r2,
+		     cy + (1 + bezierCircle) * r2,
+		     cx - r2,
+		     cy + r2);
+  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+		     cx - (1 + bezierCircle) * r2,
+		     cy + (1 - bezierCircle) * r2,
+		     cx - (1 + bezierCircle) * r2,
+		     cy - (1 - bezierCircle) * r2,
+		     cx - r2,
+		     cy - r2);
+  appearBuf->append("S\n");
+}
+
+// Draw the bottom-right half of an (approximate) circle of radius <r>
+// centered at (<cx>, <cy>).
+void Annot::drawCircleBottomRight(double cx, double cy, double r) {
+  double r2;
+
+  r2 = r / sqrt(2.0);
+  appearBuf->appendf("{0:.2f} {1:.2f} m\n",
+		     cx - r2, cy - r2);
+  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+		     cx - (1 - bezierCircle) * r2,
+		     cy - (1 + bezierCircle) * r2,
+		     cx + (1 - bezierCircle) * r2,
+		     cy - (1 + bezierCircle) * r2,
+		     cx + r2,
+		     cy - r2);
+  appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n",
+		     cx + (1 + bezierCircle) * r2,
+		     cy - (1 - bezierCircle) * r2,
+		     cx + (1 + bezierCircle) * r2,
+		     cy + (1 - bezierCircle) * r2,
+		     cx + r2,
+		     cy + r2);
+  appearBuf->append("S\n");
+}
+
+// Look up an inheritable field dictionary entry.
+Object *Annot::fieldLookup(Dict *field, char *key, Object *obj) {
+  Dict *dict;
+  Object parent;
+
+  dict = field;
+  if (!dict->lookup(key, obj)->isNull()) {
+    return obj;
+  }
+  obj->free();
+  if (dict->lookup("Parent", &parent)->isDict()) {
+    fieldLookup(parent.getDict(), key, obj);
+  } else {
+    obj->initNull();
+  }
+  parent.free();
+  return obj;
+}
+
+void Annot::draw(Gfx *gfx, GBool printing) {
+  Object obj;
+  GBool isLink;
+
+  // check the flags
+  if ((flags & annotFlagHidden) ||
+      (printing && !(flags & annotFlagPrint)) ||
+      (!printing && (flags & annotFlagNoView))) {
+    return;
+  }
+
+  // draw the appearance stream
+  isLink = type && !type->cmp("Link");
+  appearance.fetch(xref, &obj);
+  gfx->drawAnnot(&obj, isLink ? borderStyle : (AnnotBorderStyle *)NULL,
+		 xMin, yMin, xMax, yMax);
+  obj.free();
+}
+
+//------------------------------------------------------------------------
+// Annots
+//------------------------------------------------------------------------
+
+Annots::Annots(XRef *xref, Catalog *catalog, Object *annotsObj) {
+  Dict *acroForm;
+  Annot *annot;
+  Object obj1;
+  Ref ref;
+  int size;
+  int i;
+
+  annots = NULL;
+  size = 0;
+  nAnnots = 0;
+
+  acroForm = catalog->getAcroForm()->isDict() ?
+               catalog->getAcroForm()->getDict() : NULL;
+  if (annotsObj->isArray()) {
+    for (i = 0; i < annotsObj->arrayGetLength(); ++i) {
+      if (annotsObj->arrayGetNF(i, &obj1)->isRef()) {
+	ref = obj1.getRef();
+	obj1.free();
+	annotsObj->arrayGet(i, &obj1);
+      } else {
+	ref.num = ref.gen = -1;
+      }
+      if (obj1.isDict()) {
+	annot = new Annot(xref, acroForm, obj1.getDict(), &ref);
+	if (annot->isOk()) {
+	  if (nAnnots >= size) {
+	    size += 16;
+	    annots = (Annot **)greallocn(annots, size, sizeof(Annot *));
+	  }
+	  annots[nAnnots++] = annot;
+	} else {
+	  delete annot;
+	}
+      }
+      obj1.free();
+    }
+  }
+}
+
+Annots::~Annots() {
+  int i;
+
+  for (i = 0; i < nAnnots; ++i) {
+    delete annots[i];
+  }
+  gfree(annots);
+}
+
+void Annots::generateAppearances(Dict *acroForm) {
+  Object obj1, obj2;
+  Ref ref;
+  int i;
+
+  if (acroForm->lookup("Fields", &obj1)->isArray()) {
+    for (i = 0; i < obj1.arrayGetLength(); ++i) {
+      if (obj1.arrayGetNF(i, &obj2)->isRef()) {
+	ref = obj2.getRef();
+	obj2.free();
+	obj1.arrayGet(i, &obj2);
+      } else {
+	ref.num = ref.gen = -1;
+      }
+      if (obj2.isDict()) {
+	scanFieldAppearances(obj2.getDict(), &ref, NULL, acroForm);
+      }
+      obj2.free();
+    }
+  }
+  obj1.free();
+}
+
+void Annots::scanFieldAppearances(Dict *node, Ref *ref, Dict *parent,
+				  Dict *acroForm) {
+  Annot *annot;
+  Object obj1, obj2;
+  Ref ref2;
+  int i;
+
+  // non-terminal node: scan the children
+  if (node->lookup("Kids", &obj1)->isArray()) {
+    for (i = 0; i < obj1.arrayGetLength(); ++i) {
+      if (obj1.arrayGetNF(i, &obj2)->isRef()) {
+	ref2 = obj2.getRef();
+	obj2.free();
+	obj1.arrayGet(i, &obj2);
+      } else {
+	ref2.num = ref2.gen = -1;
+      }
+      if (obj2.isDict()) {
+	scanFieldAppearances(obj2.getDict(), &ref2, node, acroForm);
+      }
+      obj2.free();
+    }
+    obj1.free();
+    return;
+  }
+  obj1.free();
+
+  // terminal node: this is either a combined annot/field dict, or an
+  // annot dict whose parent is a field
+  if ((annot = findAnnot(ref))) {
+    node->lookupNF("Parent", &obj1);
+    if (!parent || !obj1.isNull()) {
+      annot->generateFieldAppearance(node, node, acroForm);
+    } else {
+      annot->generateFieldAppearance(parent, node, acroForm);
+    }
+    obj1.free();
+  }
+}
+
+Annot *Annots::findAnnot(Ref *ref) {
+  int i;
+
+  for (i = 0; i < nAnnots; ++i) {
+    if (annots[i]->match(ref)) {
+      return annots[i];
+    }
+  }
+  return NULL;
+}
diff --git a/xpdf/Annot.h b/xpdf/Annot.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Annot.h
@@ -0,0 +1,142 @@
+//========================================================================
+//
+// Annot.h
+//
+// Copyright 2000-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef ANNOT_H
+#define ANNOT_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+class XRef;
+class Catalog;
+class Gfx;
+class GfxFontDict;
+
+//------------------------------------------------------------------------
+// AnnotBorderStyle
+//------------------------------------------------------------------------
+
+enum AnnotBorderType {
+  annotBorderSolid,
+  annotBorderDashed,
+  annotBorderBeveled,
+  annotBorderInset,
+  annotBorderUnderlined
+};
+
+class AnnotBorderStyle {
+public:
+
+  AnnotBorderStyle(AnnotBorderType typeA, double widthA,
+		   double *dashA, int dashLengthA,
+		   double rA, double gA, double bA);
+  ~AnnotBorderStyle();
+
+  AnnotBorderType getType() { return type; }
+  double getWidth() { return width; }
+  void getDash(double **dashA, int *dashLengthA)
+    { *dashA = dash; *dashLengthA = dashLength; }
+  void getColor(double *rA, double *gA, double *bA)
+    { *rA = r; *gA = g; *bA = b; }
+
+private:
+
+  AnnotBorderType type;
+  double width;
+  double *dash;
+  int dashLength;
+  double r, g, b;
+};
+
+//------------------------------------------------------------------------
+// Annot
+//------------------------------------------------------------------------
+
+class Annot {
+public:
+
+  Annot(XRef *xrefA, Dict *acroForm, Dict *dict, Ref *refA);
+  ~Annot();
+  GBool isOk() { return ok; }
+
+  void draw(Gfx *gfx, GBool printing);
+
+  // Get appearance object.
+  Object *getAppearance(Object *obj) { return appearance.fetch(xref, obj); }
+
+  AnnotBorderStyle *getBorderStyle() { return borderStyle; }
+
+  GBool match(Ref *refA)
+    { return ref.num == refA->num && ref.gen == refA->gen; }
+
+  void generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm);
+
+private:
+ 
+  void setColor(Array *a, GBool fill, int adjust);
+  void drawText(GString *text, GString *da, GfxFontDict *fontDict,
+		GBool multiline, int comb, int quadding,
+		GBool txField, GBool forceZapfDingbats);
+  void drawListBox(GString **text, GBool *selection,
+		   int nOptions, int topIdx,
+		   GString *da, GfxFontDict *fontDict, GBool quadding);
+  void getNextLine(GString *text, int start,
+		   GfxFont *font, double fontSize, double wMax,
+		   int *end, double *width, int *next);
+  void drawCircle(double cx, double cy, double r, GBool fill);
+  void drawCircleTopLeft(double cx, double cy, double r);
+  void drawCircleBottomRight(double cx, double cy, double r);
+  Object *fieldLookup(Dict *field, char *key, Object *obj);
+
+  XRef *xref;			// the xref table for this PDF file
+  Ref ref;			// object ref identifying this annotation
+  GString *type;		// annotation type
+  Object appearance;		// a reference to the Form XObject stream
+				//   for the normal appearance
+  GString *appearBuf;
+  double xMin, yMin,		// annotation rectangle
+         xMax, yMax;
+  Guint flags;
+  AnnotBorderStyle *borderStyle;
+  GBool ok;
+};
+
+//------------------------------------------------------------------------
+// Annots
+//------------------------------------------------------------------------
+
+class Annots {
+public:
+
+  // Build a list of Annot objects.
+  Annots(XRef *xref, Catalog *catalog, Object *annotsObj);
+
+  ~Annots();
+
+  // Iterate through list of annotations.
+  int getNumAnnots() { return nAnnots; }
+  Annot *getAnnot(int i) { return annots[i]; }
+
+  // (Re)generate the appearance streams for all annotations belonging
+  // to a form field.
+  void generateAppearances(Dict *acroForm);
+
+private:
+
+  void scanFieldAppearances(Dict *node, Ref *ref, Dict *parent,
+			    Dict *acroForm);
+  Annot *findAnnot(Ref *ref);
+
+  Annot **annots;
+  int nAnnots;
+};
+
+#endif
diff --git a/xpdf/Array.cc b/xpdf/Array.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Array.cc
@@ -0,0 +1,73 @@
+//========================================================================
+//
+// Array.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <stddef.h>
+#include "gmem.h"
+#include "Object.h"
+#include "Array.h"
+
+//------------------------------------------------------------------------
+// Array
+//------------------------------------------------------------------------
+
+Array::Array(XRef *xrefA) {
+  xref = xrefA;
+  elems = NULL;
+  size = length = 0;
+  ref = 1;
+}
+
+Array::~Array() {
+  int i;
+
+  for (i = 0; i < length; ++i)
+    elems[i].free();
+  gfree(elems);
+}
+
+void Array::add(Object *elem) {
+  if (length == size) {
+    if (length == 0) {
+      size = 8;
+    } else {
+      size *= 2;
+    }
+    elems = (Object *)greallocn(elems, size, sizeof(Object));
+  }
+  elems[length] = *elem;
+  ++length;
+}
+
+Object *Array::get(int i, Object *obj) {
+  if (i < 0 || i >= length) {
+#ifdef DEBUG_MEM
+    abort();
+#else
+    return obj->initNull();
+#endif
+  }
+  return elems[i].fetch(xref, obj);
+}
+
+Object *Array::getNF(int i, Object *obj) {
+  if (i < 0 || i >= length) {
+#ifdef DEBUG_MEM
+    abort();
+#else
+    return obj->initNull();
+#endif
+  }
+  return elems[i].copy(obj);
+}
diff --git a/xpdf/Array.h b/xpdf/Array.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Array.h
@@ -0,0 +1,58 @@
+//========================================================================
+//
+// Array.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef ARRAY_H
+#define ARRAY_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "Object.h"
+
+class XRef;
+
+//------------------------------------------------------------------------
+// Array
+//------------------------------------------------------------------------
+
+class Array {
+public:
+
+  // Constructor.
+  Array(XRef *xrefA);
+
+  // Destructor.
+  ~Array();
+
+  // Reference counting.
+  int incRef() { return ++ref; }
+  int decRef() { return --ref; }
+
+  // Get number of elements.
+  int getLength() { return length; }
+
+  // Add an element.
+  void add(Object *elem);
+
+  // Accessors.
+  Object *get(int i, Object *obj);
+  Object *getNF(int i, Object *obj);
+
+private:
+
+  XRef *xref;			// the xref table for this PDF file
+  Object *elems;		// array of elements
+  int size;			// size of <elems> array
+  int length;			// number of elements in array
+  int ref;			// reference count
+};
+
+#endif
diff --git a/xpdf/BuiltinFont.cc b/xpdf/BuiltinFont.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/BuiltinFont.cc
@@ -0,0 +1,65 @@
+//========================================================================
+//
+// BuiltinFont.cc
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "gmem.h"
+#include "FontEncodingTables.h"
+#include "BuiltinFont.h"
+
+//------------------------------------------------------------------------
+
+BuiltinFontWidths::BuiltinFontWidths(BuiltinFontWidth *widths, int sizeA) {
+  int i, h;
+
+  size = sizeA;
+  tab = (BuiltinFontWidth **)gmallocn(size, sizeof(BuiltinFontWidth *));
+  for (i = 0; i < size; ++i) {
+    tab[i] = NULL;
+  }
+  for (i = 0; i < sizeA; ++i) {
+    h = hash(widths[i].name);
+    widths[i].next = tab[h];
+    tab[h] = &widths[i];
+  }
+}
+
+BuiltinFontWidths::~BuiltinFontWidths() {
+  gfree(tab);
+}
+
+GBool BuiltinFontWidths::getWidth(char *name, Gushort *width) {
+  int h;
+  BuiltinFontWidth *p;
+
+  h = hash(name);
+  for (p = tab[h]; p; p = p->next) {
+    if (!strcmp(p->name, name)) {
+      *width = p->width;
+      return gTrue;
+    }
+  }
+  return gFalse;
+}
+
+int BuiltinFontWidths::hash(char *name) {
+  char *p;
+  unsigned int h;
+
+  h = 0;
+  for (p = name; *p; ++p) {
+    h = 17 * h + (int)(*p & 0xff);
+  }
+  return (int)(h % size);
+}
diff --git a/xpdf/BuiltinFont.h b/xpdf/BuiltinFont.h
new file mode 100644
--- /dev/null
+++ b/xpdf/BuiltinFont.h
@@ -0,0 +1,57 @@
+//========================================================================
+//
+// BuiltinFont.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef BUILTINFONT_H
+#define BUILTINFONT_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+
+struct BuiltinFont;
+class BuiltinFontWidths;
+
+//------------------------------------------------------------------------
+
+struct BuiltinFont {
+  char *name;
+  char **defaultBaseEnc;
+  short ascent;
+  short descent;
+  short bbox[4];
+  BuiltinFontWidths *widths;
+};
+
+//------------------------------------------------------------------------
+
+struct BuiltinFontWidth {
+  char *name;
+  Gushort width;
+  BuiltinFontWidth *next;
+};
+
+class BuiltinFontWidths {
+public:
+
+  BuiltinFontWidths(BuiltinFontWidth *widths, int sizeA);
+  ~BuiltinFontWidths();
+  GBool getWidth(char *name, Gushort *width);
+
+private:
+
+  int hash(char *name);
+
+  BuiltinFontWidth **tab;
+  int size;
+};
+
+#endif
diff --git a/xpdf/BuiltinFontTables.cc b/xpdf/BuiltinFontTables.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/BuiltinFontTables.cc
@@ -0,0 +1,4284 @@
+//========================================================================
+//
+// BuiltinFontTables.cc
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+#include <stdlib.h>
+#include "FontEncodingTables.h"
+#include "BuiltinFontTables.h"
+
+static BuiltinFontWidth courierWidthsTab[] = {
+  { "Ntilde",                            600, NULL },
+  { "rcaron",                            600, NULL },
+  { "kcommaaccent",                      600, NULL },
+  { "Ncommaaccent",                      600, NULL },
+  { "Zacute",                            600, NULL },
+  { "comma",                             600, NULL },
+  { "cedilla",                           600, NULL },
+  { "plusminus",                         600, NULL },
+  { "circumflex",                        600, NULL },
+  { "dotaccent",                         600, NULL },
+  { "edotaccent",                        600, NULL },
+  { "asciitilde",                        600, NULL },
+  { "colon",                             600, NULL },
+  { "onehalf",                           600, NULL },
+  { "dollar",                            600, NULL },
+  { "Lcaron",                            600, NULL },
+  { "ntilde",                            600, NULL },
+  { "Aogonek",                           600, NULL },
+  { "ncommaaccent",                      600, NULL },
+  { "minus",                             600, NULL },
+  { "Iogonek",                           600, NULL },
+  { "zacute",                            600, NULL },
+  { "yen",                               600, NULL },
+  { "space",                             600, NULL },
+  { "Omacron",                           600, NULL },
+  { "questiondown",                      600, NULL },
+  { "emdash",                            600, NULL },
+  { "Agrave",                            600, NULL },
+  { "three",                             600, NULL },
+  { "numbersign",                        600, NULL },
+  { "lcaron",                            600, NULL },
+  { "A",                                 600, NULL },
+  { "B",                                 600, NULL },
+  { "C",                                 600, NULL },
+  { "aogonek",                           600, NULL },
+  { "D",                                 600, NULL },
+  { "E",                                 600, NULL },
+  { "onequarter",                        600, NULL },
+  { "F",                                 600, NULL },
+  { "G",                                 600, NULL },
+  { "H",                                 600, NULL },
+  { "I",                                 600, NULL },
+  { "J",                                 600, NULL },
+  { "K",                                 600, NULL },
+  { "iogonek",                           600, NULL },
+  { "L",                                 600, NULL },
+  { "backslash",                         600, NULL },
+  { "periodcentered",                    600, NULL },
+  { "M",                                 600, NULL },
+  { "N",                                 600, NULL },
+  { "omacron",                           600, NULL },
+  { "Tcommaaccent",                      600, NULL },
+  { "O",                                 600, NULL },
+  { "P",                                 600, NULL },
+  { "Q",                                 600, NULL },
+  { "Uhungarumlaut",                     600, NULL },
+  { "R",                                 600, NULL },
+  { "Aacute",                            600, NULL },
+  { "caron",                             600, NULL },
+  { "S",                                 600, NULL },
+  { "T",                                 600, NULL },
+  { "U",                                 600, NULL },
+  { "agrave",                            600, NULL },
+  { "V",                                 600, NULL },
+  { "W",                                 600, NULL },
+  { "equal",                             600, NULL },
+  { "question",                          600, NULL },
+  { "X",                                 600, NULL },
+  { "Y",                                 600, NULL },
+  { "Z",                                 600, NULL },
+  { "four",                              600, NULL },
+  { "a",                                 600, NULL },
+  { "Gcommaaccent",                      600, NULL },
+  { "b",                                 600, NULL },
+  { "c",                                 600, NULL },
+  { "d",                                 600, NULL },
+  { "e",                                 600, NULL },
+  { "f",                                 600, NULL },
+  { "g",                                 600, NULL },
+  { "bullet",                            600, NULL },
+  { "h",                                 600, NULL },
+  { "i",                                 600, NULL },
+  { "Oslash",                            600, NULL },
+  { "dagger",                            600, NULL },
+  { "j",                                 600, NULL },
+  { "k",                                 600, NULL },
+  { "l",                                 600, NULL },
+  { "m",                                 600, NULL },
+  { "n",                                 600, NULL },
+  { "tcommaaccent",                      600, NULL },
+  { "o",                                 600, NULL },
+  { "ordfeminine",                       600, NULL },
+  { "ring",                              600, NULL },
+  { "p",                                 600, NULL },
+  { "q",                                 600, NULL },
+  { "uhungarumlaut",                     600, NULL },
+  { "r",                                 600, NULL },
+  { "twosuperior",                       600, NULL },
+  { "aacute",                            600, NULL },
+  { "s",                                 600, NULL },
+  { "OE",                                600, NULL },
+  { "t",                                 600, NULL },
+  { "divide",                            600, NULL },
+  { "u",                                 600, NULL },
+  { "Ccaron",                            600, NULL },
+  { "v",                                 600, NULL },
+  { "w",                                 600, NULL },
+  { "x",                                 600, NULL },
+  { "y",                                 600, NULL },
+  { "z",                                 600, NULL },
+  { "Gbreve",                            600, NULL },
+  { "commaaccent",                       600, NULL },
+  { "hungarumlaut",                      600, NULL },
+  { "Idotaccent",                        600, NULL },
+  { "Nacute",                            600, NULL },
+  { "quotedbl",                          600, NULL },
+  { "gcommaaccent",                      600, NULL },
+  { "mu",                                600, NULL },
+  { "greaterequal",                      600, NULL },
+  { "Scaron",                            600, NULL },
+  { "Lslash",                            600, NULL },
+  { "semicolon",                         600, NULL },
+  { "oslash",                            600, NULL },
+  { "lessequal",                         600, NULL },
+  { "lozenge",                           600, NULL },
+  { "parenright",                        600, NULL },
+  { "ccaron",                            600, NULL },
+  { "Ecircumflex",                       600, NULL },
+  { "gbreve",                            600, NULL },
+  { "trademark",                         600, NULL },
+  { "daggerdbl",                         600, NULL },
+  { "nacute",                            600, NULL },
+  { "macron",                            600, NULL },
+  { "Otilde",                            600, NULL },
+  { "Emacron",                           600, NULL },
+  { "ellipsis",                          600, NULL },
+  { "scaron",                            600, NULL },
+  { "AE",                                600, NULL },
+  { "Ucircumflex",                       600, NULL },
+  { "lslash",                            600, NULL },
+  { "quotedblleft",                      600, NULL },
+  { "hyphen",                            600, NULL },
+  { "guilsinglright",                    600, NULL },
+  { "quotesingle",                       600, NULL },
+  { "eight",                             600, NULL },
+  { "exclamdown",                        600, NULL },
+  { "endash",                            600, NULL },
+  { "oe",                                600, NULL },
+  { "Abreve",                            600, NULL },
+  { "Umacron",                           600, NULL },
+  { "ecircumflex",                       600, NULL },
+  { "Adieresis",                         600, NULL },
+  { "copyright",                         600, NULL },
+  { "Egrave",                            600, NULL },
+  { "slash",                             600, NULL },
+  { "Edieresis",                         600, NULL },
+  { "otilde",                            600, NULL },
+  { "Idieresis",                         600, NULL },
+  { "parenleft",                         600, NULL },
+  { "one",                               600, NULL },
+  { "emacron",                           600, NULL },
+  { "Odieresis",                         600, NULL },
+  { "ucircumflex",                       600, NULL },
+  { "bracketleft",                       600, NULL },
+  { "Ugrave",                            600, NULL },
+  { "quoteright",                        600, NULL },
+  { "Udieresis",                         600, NULL },
+  { "perthousand",                       600, NULL },
+  { "Ydieresis",                         600, NULL },
+  { "umacron",                           600, NULL },
+  { "abreve",                            600, NULL },
+  { "Eacute",                            600, NULL },
+  { "adieresis",                         600, NULL },
+  { "egrave",                            600, NULL },
+  { "edieresis",                         600, NULL },
+  { "idieresis",                         600, NULL },
+  { "Eth",                               600, NULL },
+  { "ae",                                600, NULL },
+  { "asterisk",                          600, NULL },
+  { "odieresis",                         600, NULL },
+  { "Uacute",                            600, NULL },
+  { "ugrave",                            600, NULL },
+  { "five",                              600, NULL },
+  { "nine",                              600, NULL },
+  { "udieresis",                         600, NULL },
+  { "Zcaron",                            600, NULL },
+  { "Scommaaccent",                      600, NULL },
+  { "threequarters",                     600, NULL },
+  { "guillemotright",                    600, NULL },
+  { "Ccedilla",                          600, NULL },
+  { "ydieresis",                         600, NULL },
+  { "tilde",                             600, NULL },
+  { "at",                                600, NULL },
+  { "eacute",                            600, NULL },
+  { "underscore",                        600, NULL },
+  { "Euro",                              600, NULL },
+  { "Dcroat",                            600, NULL },
+  { "zero",                              600, NULL },
+  { "multiply",                          600, NULL },
+  { "eth",                               600, NULL },
+  { "Scedilla",                          600, NULL },
+  { "Racute",                            600, NULL },
+  { "Ograve",                            600, NULL },
+  { "partialdiff",                       600, NULL },
+  { "uacute",                            600, NULL },
+  { "braceleft",                         600, NULL },
+  { "Thorn",                             600, NULL },
+  { "zcaron",                            600, NULL },
+  { "scommaaccent",                      600, NULL },
+  { "ccedilla",                          600, NULL },
+  { "Dcaron",                            600, NULL },
+  { "dcroat",                            600, NULL },
+  { "scedilla",                          600, NULL },
+  { "Oacute",                            600, NULL },
+  { "Ocircumflex",                       600, NULL },
+  { "ogonek",                            600, NULL },
+  { "ograve",                            600, NULL },
+  { "racute",                            600, NULL },
+  { "Tcaron",                            600, NULL },
+  { "Eogonek",                           600, NULL },
+  { "thorn",                             600, NULL },
+  { "degree",                            600, NULL },
+  { "registered",                        600, NULL },
+  { "radical",                           600, NULL },
+  { "Aring",                             600, NULL },
+  { "percent",                           600, NULL },
+  { "six",                               600, NULL },
+  { "paragraph",                         600, NULL },
+  { "dcaron",                            600, NULL },
+  { "Uogonek",                           600, NULL },
+  { "two",                               600, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            600, NULL },
+  { "Lacute",                            600, NULL },
+  { "ocircumflex",                       600, NULL },
+  { "oacute",                            600, NULL },
+  { "Uring",                             600, NULL },
+  { "Lcommaaccent",                      600, NULL },
+  { "tcaron",                            600, NULL },
+  { "eogonek",                           600, NULL },
+  { "Delta",                             600, NULL },
+  { "Ohungarumlaut",                     600, NULL },
+  { "asciicircum",                       600, NULL },
+  { "aring",                             600, NULL },
+  { "grave",                             600, NULL },
+  { "uogonek",                           600, NULL },
+  { "bracketright",                      600, NULL },
+  { "ampersand",                         600, NULL },
+  { "Iacute",                            600, NULL },
+  { "lacute",                            600, NULL },
+  { "igrave",                            600, NULL },
+  { "Ncaron",                            600, NULL },
+  { "plus",                              600, NULL },
+  { "uring",                             600, NULL },
+  { "quotesinglbase",                    600, NULL },
+  { "lcommaaccent",                      600, NULL },
+  { "Yacute",                            600, NULL },
+  { "ohungarumlaut",                     600, NULL },
+  { "threesuperior",                     600, NULL },
+  { "acute",                             600, NULL },
+  { "section",                           600, NULL },
+  { "dieresis",                          600, NULL },
+  { "quotedblbase",                      600, NULL },
+  { "iacute",                            600, NULL },
+  { "ncaron",                            600, NULL },
+  { "florin",                            600, NULL },
+  { "yacute",                            600, NULL },
+  { "Rcommaaccent",                      600, NULL },
+  { "fi",                                600, NULL },
+  { "fl",                                600, NULL },
+  { "Acircumflex",                       600, NULL },
+  { "Cacute",                            600, NULL },
+  { "Icircumflex",                       600, NULL },
+  { "guillemotleft",                     600, NULL },
+  { "germandbls",                        600, NULL },
+  { "seven",                             600, NULL },
+  { "Amacron",                           600, NULL },
+  { "Sacute",                            600, NULL },
+  { "ordmasculine",                      600, NULL },
+  { "dotlessi",                          600, NULL },
+  { "sterling",                          600, NULL },
+  { "notequal",                          600, NULL },
+  { "Imacron",                           600, NULL },
+  { "rcommaaccent",                      600, NULL },
+  { "Zdotaccent",                        600, NULL },
+  { "acircumflex",                       600, NULL },
+  { "cacute",                            600, NULL },
+  { "Ecaron",                            600, NULL },
+  { "braceright",                        600, NULL },
+  { "icircumflex",                       600, NULL },
+  { "quotedblright",                     600, NULL },
+  { "amacron",                           600, NULL },
+  { "sacute",                            600, NULL },
+  { "imacron",                           600, NULL },
+  { "cent",                              600, NULL },
+  { "currency",                          600, NULL },
+  { "logicalnot",                        600, NULL },
+  { "zdotaccent",                        600, NULL },
+  { "Atilde",                            600, NULL },
+  { "breve",                             600, NULL },
+  { "bar",                               600, NULL },
+  { "fraction",                          600, NULL },
+  { "less",                              600, NULL },
+  { "ecaron",                            600, NULL },
+  { "guilsinglleft",                     600, NULL },
+  { "exclam",                            600, NULL },
+  { "period",                            600, NULL },
+  { "Rcaron",                            600, NULL },
+  { "Kcommaaccent",                      600, NULL },
+  { "greater",                           600, NULL },
+  { "atilde",                            600, NULL },
+  { "brokenbar",                         600, NULL },
+  { "quoteleft",                         600, NULL },
+  { "Edotaccent",                        600, NULL },
+  { "onesuperior",                       600, NULL }
+};
+
+static BuiltinFontWidth courierBoldWidthsTab[] = {
+  { "Ntilde",                            600, NULL },
+  { "rcaron",                            600, NULL },
+  { "kcommaaccent",                      600, NULL },
+  { "Ncommaaccent",                      600, NULL },
+  { "Zacute",                            600, NULL },
+  { "comma",                             600, NULL },
+  { "cedilla",                           600, NULL },
+  { "plusminus",                         600, NULL },
+  { "circumflex",                        600, NULL },
+  { "dotaccent",                         600, NULL },
+  { "edotaccent",                        600, NULL },
+  { "asciitilde",                        600, NULL },
+  { "colon",                             600, NULL },
+  { "onehalf",                           600, NULL },
+  { "dollar",                            600, NULL },
+  { "Lcaron",                            600, NULL },
+  { "ntilde",                            600, NULL },
+  { "Aogonek",                           600, NULL },
+  { "ncommaaccent",                      600, NULL },
+  { "minus",                             600, NULL },
+  { "Iogonek",                           600, NULL },
+  { "zacute",                            600, NULL },
+  { "yen",                               600, NULL },
+  { "space",                             600, NULL },
+  { "Omacron",                           600, NULL },
+  { "questiondown",                      600, NULL },
+  { "emdash",                            600, NULL },
+  { "Agrave",                            600, NULL },
+  { "three",                             600, NULL },
+  { "numbersign",                        600, NULL },
+  { "lcaron",                            600, NULL },
+  { "A",                                 600, NULL },
+  { "B",                                 600, NULL },
+  { "C",                                 600, NULL },
+  { "aogonek",                           600, NULL },
+  { "D",                                 600, NULL },
+  { "E",                                 600, NULL },
+  { "onequarter",                        600, NULL },
+  { "F",                                 600, NULL },
+  { "G",                                 600, NULL },
+  { "H",                                 600, NULL },
+  { "I",                                 600, NULL },
+  { "J",                                 600, NULL },
+  { "K",                                 600, NULL },
+  { "iogonek",                           600, NULL },
+  { "backslash",                         600, NULL },
+  { "L",                                 600, NULL },
+  { "periodcentered",                    600, NULL },
+  { "M",                                 600, NULL },
+  { "N",                                 600, NULL },
+  { "omacron",                           600, NULL },
+  { "Tcommaaccent",                      600, NULL },
+  { "O",                                 600, NULL },
+  { "P",                                 600, NULL },
+  { "Q",                                 600, NULL },
+  { "Uhungarumlaut",                     600, NULL },
+  { "R",                                 600, NULL },
+  { "Aacute",                            600, NULL },
+  { "caron",                             600, NULL },
+  { "S",                                 600, NULL },
+  { "T",                                 600, NULL },
+  { "U",                                 600, NULL },
+  { "agrave",                            600, NULL },
+  { "V",                                 600, NULL },
+  { "W",                                 600, NULL },
+  { "X",                                 600, NULL },
+  { "question",                          600, NULL },
+  { "equal",                             600, NULL },
+  { "Y",                                 600, NULL },
+  { "Z",                                 600, NULL },
+  { "four",                              600, NULL },
+  { "a",                                 600, NULL },
+  { "Gcommaaccent",                      600, NULL },
+  { "b",                                 600, NULL },
+  { "c",                                 600, NULL },
+  { "d",                                 600, NULL },
+  { "e",                                 600, NULL },
+  { "f",                                 600, NULL },
+  { "g",                                 600, NULL },
+  { "bullet",                            600, NULL },
+  { "h",                                 600, NULL },
+  { "i",                                 600, NULL },
+  { "Oslash",                            600, NULL },
+  { "dagger",                            600, NULL },
+  { "j",                                 600, NULL },
+  { "k",                                 600, NULL },
+  { "l",                                 600, NULL },
+  { "m",                                 600, NULL },
+  { "n",                                 600, NULL },
+  { "tcommaaccent",                      600, NULL },
+  { "o",                                 600, NULL },
+  { "ordfeminine",                       600, NULL },
+  { "ring",                              600, NULL },
+  { "p",                                 600, NULL },
+  { "q",                                 600, NULL },
+  { "uhungarumlaut",                     600, NULL },
+  { "r",                                 600, NULL },
+  { "twosuperior",                       600, NULL },
+  { "aacute",                            600, NULL },
+  { "s",                                 600, NULL },
+  { "OE",                                600, NULL },
+  { "t",                                 600, NULL },
+  { "divide",                            600, NULL },
+  { "u",                                 600, NULL },
+  { "Ccaron",                            600, NULL },
+  { "v",                                 600, NULL },
+  { "w",                                 600, NULL },
+  { "x",                                 600, NULL },
+  { "y",                                 600, NULL },
+  { "z",                                 600, NULL },
+  { "Gbreve",                            600, NULL },
+  { "commaaccent",                       600, NULL },
+  { "hungarumlaut",                      600, NULL },
+  { "Idotaccent",                        600, NULL },
+  { "Nacute",                            600, NULL },
+  { "quotedbl",                          600, NULL },
+  { "gcommaaccent",                      600, NULL },
+  { "mu",                                600, NULL },
+  { "greaterequal",                      600, NULL },
+  { "Scaron",                            600, NULL },
+  { "Lslash",                            600, NULL },
+  { "semicolon",                         600, NULL },
+  { "oslash",                            600, NULL },
+  { "lessequal",                         600, NULL },
+  { "lozenge",                           600, NULL },
+  { "parenright",                        600, NULL },
+  { "ccaron",                            600, NULL },
+  { "Ecircumflex",                       600, NULL },
+  { "gbreve",                            600, NULL },
+  { "trademark",                         600, NULL },
+  { "daggerdbl",                         600, NULL },
+  { "nacute",                            600, NULL },
+  { "macron",                            600, NULL },
+  { "Otilde",                            600, NULL },
+  { "Emacron",                           600, NULL },
+  { "ellipsis",                          600, NULL },
+  { "scaron",                            600, NULL },
+  { "AE",                                600, NULL },
+  { "Ucircumflex",                       600, NULL },
+  { "lslash",                            600, NULL },
+  { "quotedblleft",                      600, NULL },
+  { "guilsinglright",                    600, NULL },
+  { "hyphen",                            600, NULL },
+  { "quotesingle",                       600, NULL },
+  { "eight",                             600, NULL },
+  { "exclamdown",                        600, NULL },
+  { "endash",                            600, NULL },
+  { "oe",                                600, NULL },
+  { "Abreve",                            600, NULL },
+  { "Umacron",                           600, NULL },
+  { "ecircumflex",                       600, NULL },
+  { "Adieresis",                         600, NULL },
+  { "copyright",                         600, NULL },
+  { "Egrave",                            600, NULL },
+  { "slash",                             600, NULL },
+  { "Edieresis",                         600, NULL },
+  { "otilde",                            600, NULL },
+  { "Idieresis",                         600, NULL },
+  { "parenleft",                         600, NULL },
+  { "one",                               600, NULL },
+  { "emacron",                           600, NULL },
+  { "Odieresis",                         600, NULL },
+  { "ucircumflex",                       600, NULL },
+  { "bracketleft",                       600, NULL },
+  { "Ugrave",                            600, NULL },
+  { "quoteright",                        600, NULL },
+  { "Udieresis",                         600, NULL },
+  { "perthousand",                       600, NULL },
+  { "Ydieresis",                         600, NULL },
+  { "umacron",                           600, NULL },
+  { "abreve",                            600, NULL },
+  { "Eacute",                            600, NULL },
+  { "adieresis",                         600, NULL },
+  { "egrave",                            600, NULL },
+  { "edieresis",                         600, NULL },
+  { "idieresis",                         600, NULL },
+  { "Eth",                               600, NULL },
+  { "ae",                                600, NULL },
+  { "asterisk",                          600, NULL },
+  { "odieresis",                         600, NULL },
+  { "Uacute",                            600, NULL },
+  { "ugrave",                            600, NULL },
+  { "nine",                              600, NULL },
+  { "five",                              600, NULL },
+  { "udieresis",                         600, NULL },
+  { "Zcaron",                            600, NULL },
+  { "Scommaaccent",                      600, NULL },
+  { "threequarters",                     600, NULL },
+  { "guillemotright",                    600, NULL },
+  { "Ccedilla",                          600, NULL },
+  { "ydieresis",                         600, NULL },
+  { "tilde",                             600, NULL },
+  { "at",                                600, NULL },
+  { "eacute",                            600, NULL },
+  { "underscore",                        600, NULL },
+  { "Euro",                              600, NULL },
+  { "Dcroat",                            600, NULL },
+  { "multiply",                          600, NULL },
+  { "zero",                              600, NULL },
+  { "eth",                               600, NULL },
+  { "Scedilla",                          600, NULL },
+  { "Ograve",                            600, NULL },
+  { "Racute",                            600, NULL },
+  { "partialdiff",                       600, NULL },
+  { "uacute",                            600, NULL },
+  { "braceleft",                         600, NULL },
+  { "Thorn",                             600, NULL },
+  { "zcaron",                            600, NULL },
+  { "scommaaccent",                      600, NULL },
+  { "ccedilla",                          600, NULL },
+  { "Dcaron",                            600, NULL },
+  { "dcroat",                            600, NULL },
+  { "Ocircumflex",                       600, NULL },
+  { "Oacute",                            600, NULL },
+  { "scedilla",                          600, NULL },
+  { "ogonek",                            600, NULL },
+  { "ograve",                            600, NULL },
+  { "racute",                            600, NULL },
+  { "Tcaron",                            600, NULL },
+  { "Eogonek",                           600, NULL },
+  { "thorn",                             600, NULL },
+  { "degree",                            600, NULL },
+  { "registered",                        600, NULL },
+  { "radical",                           600, NULL },
+  { "Aring",                             600, NULL },
+  { "percent",                           600, NULL },
+  { "six",                               600, NULL },
+  { "paragraph",                         600, NULL },
+  { "dcaron",                            600, NULL },
+  { "Uogonek",                           600, NULL },
+  { "two",                               600, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            600, NULL },
+  { "Lacute",                            600, NULL },
+  { "ocircumflex",                       600, NULL },
+  { "oacute",                            600, NULL },
+  { "Uring",                             600, NULL },
+  { "Lcommaaccent",                      600, NULL },
+  { "tcaron",                            600, NULL },
+  { "eogonek",                           600, NULL },
+  { "Delta",                             600, NULL },
+  { "Ohungarumlaut",                     600, NULL },
+  { "asciicircum",                       600, NULL },
+  { "aring",                             600, NULL },
+  { "grave",                             600, NULL },
+  { "uogonek",                           600, NULL },
+  { "bracketright",                      600, NULL },
+  { "Iacute",                            600, NULL },
+  { "ampersand",                         600, NULL },
+  { "igrave",                            600, NULL },
+  { "lacute",                            600, NULL },
+  { "Ncaron",                            600, NULL },
+  { "plus",                              600, NULL },
+  { "uring",                             600, NULL },
+  { "quotesinglbase",                    600, NULL },
+  { "lcommaaccent",                      600, NULL },
+  { "Yacute",                            600, NULL },
+  { "ohungarumlaut",                     600, NULL },
+  { "threesuperior",                     600, NULL },
+  { "acute",                             600, NULL },
+  { "section",                           600, NULL },
+  { "dieresis",                          600, NULL },
+  { "iacute",                            600, NULL },
+  { "quotedblbase",                      600, NULL },
+  { "ncaron",                            600, NULL },
+  { "florin",                            600, NULL },
+  { "yacute",                            600, NULL },
+  { "Rcommaaccent",                      600, NULL },
+  { "fi",                                600, NULL },
+  { "fl",                                600, NULL },
+  { "Acircumflex",                       600, NULL },
+  { "Cacute",                            600, NULL },
+  { "Icircumflex",                       600, NULL },
+  { "guillemotleft",                     600, NULL },
+  { "germandbls",                        600, NULL },
+  { "Amacron",                           600, NULL },
+  { "seven",                             600, NULL },
+  { "Sacute",                            600, NULL },
+  { "ordmasculine",                      600, NULL },
+  { "dotlessi",                          600, NULL },
+  { "sterling",                          600, NULL },
+  { "notequal",                          600, NULL },
+  { "Imacron",                           600, NULL },
+  { "rcommaaccent",                      600, NULL },
+  { "Zdotaccent",                        600, NULL },
+  { "acircumflex",                       600, NULL },
+  { "cacute",                            600, NULL },
+  { "Ecaron",                            600, NULL },
+  { "icircumflex",                       600, NULL },
+  { "braceright",                        600, NULL },
+  { "quotedblright",                     600, NULL },
+  { "amacron",                           600, NULL },
+  { "sacute",                            600, NULL },
+  { "imacron",                           600, NULL },
+  { "cent",                              600, NULL },
+  { "currency",                          600, NULL },
+  { "logicalnot",                        600, NULL },
+  { "zdotaccent",                        600, NULL },
+  { "Atilde",                            600, NULL },
+  { "breve",                             600, NULL },
+  { "bar",                               600, NULL },
+  { "fraction",                          600, NULL },
+  { "less",                              600, NULL },
+  { "ecaron",                            600, NULL },
+  { "guilsinglleft",                     600, NULL },
+  { "exclam",                            600, NULL },
+  { "period",                            600, NULL },
+  { "Rcaron",                            600, NULL },
+  { "Kcommaaccent",                      600, NULL },
+  { "greater",                           600, NULL },
+  { "atilde",                            600, NULL },
+  { "brokenbar",                         600, NULL },
+  { "quoteleft",                         600, NULL },
+  { "Edotaccent",                        600, NULL },
+  { "onesuperior",                       600, NULL }
+};
+
+static BuiltinFontWidth courierBoldObliqueWidthsTab[] = {
+  { "Ntilde",                            600, NULL },
+  { "rcaron",                            600, NULL },
+  { "kcommaaccent",                      600, NULL },
+  { "Ncommaaccent",                      600, NULL },
+  { "Zacute",                            600, NULL },
+  { "comma",                             600, NULL },
+  { "cedilla",                           600, NULL },
+  { "plusminus",                         600, NULL },
+  { "circumflex",                        600, NULL },
+  { "dotaccent",                         600, NULL },
+  { "edotaccent",                        600, NULL },
+  { "asciitilde",                        600, NULL },
+  { "colon",                             600, NULL },
+  { "onehalf",                           600, NULL },
+  { "dollar",                            600, NULL },
+  { "Lcaron",                            600, NULL },
+  { "ntilde",                            600, NULL },
+  { "Aogonek",                           600, NULL },
+  { "ncommaaccent",                      600, NULL },
+  { "minus",                             600, NULL },
+  { "Iogonek",                           600, NULL },
+  { "zacute",                            600, NULL },
+  { "yen",                               600, NULL },
+  { "space",                             600, NULL },
+  { "Omacron",                           600, NULL },
+  { "questiondown",                      600, NULL },
+  { "emdash",                            600, NULL },
+  { "Agrave",                            600, NULL },
+  { "three",                             600, NULL },
+  { "numbersign",                        600, NULL },
+  { "lcaron",                            600, NULL },
+  { "A",                                 600, NULL },
+  { "B",                                 600, NULL },
+  { "C",                                 600, NULL },
+  { "aogonek",                           600, NULL },
+  { "D",                                 600, NULL },
+  { "E",                                 600, NULL },
+  { "onequarter",                        600, NULL },
+  { "F",                                 600, NULL },
+  { "G",                                 600, NULL },
+  { "H",                                 600, NULL },
+  { "I",                                 600, NULL },
+  { "J",                                 600, NULL },
+  { "K",                                 600, NULL },
+  { "iogonek",                           600, NULL },
+  { "backslash",                         600, NULL },
+  { "L",                                 600, NULL },
+  { "periodcentered",                    600, NULL },
+  { "M",                                 600, NULL },
+  { "N",                                 600, NULL },
+  { "omacron",                           600, NULL },
+  { "Tcommaaccent",                      600, NULL },
+  { "O",                                 600, NULL },
+  { "P",                                 600, NULL },
+  { "Q",                                 600, NULL },
+  { "Uhungarumlaut",                     600, NULL },
+  { "R",                                 600, NULL },
+  { "Aacute",                            600, NULL },
+  { "caron",                             600, NULL },
+  { "S",                                 600, NULL },
+  { "T",                                 600, NULL },
+  { "U",                                 600, NULL },
+  { "agrave",                            600, NULL },
+  { "V",                                 600, NULL },
+  { "W",                                 600, NULL },
+  { "X",                                 600, NULL },
+  { "question",                          600, NULL },
+  { "equal",                             600, NULL },
+  { "Y",                                 600, NULL },
+  { "Z",                                 600, NULL },
+  { "four",                              600, NULL },
+  { "a",                                 600, NULL },
+  { "Gcommaaccent",                      600, NULL },
+  { "b",                                 600, NULL },
+  { "c",                                 600, NULL },
+  { "d",                                 600, NULL },
+  { "e",                                 600, NULL },
+  { "f",                                 600, NULL },
+  { "g",                                 600, NULL },
+  { "bullet",                            600, NULL },
+  { "h",                                 600, NULL },
+  { "i",                                 600, NULL },
+  { "Oslash",                            600, NULL },
+  { "dagger",                            600, NULL },
+  { "j",                                 600, NULL },
+  { "k",                                 600, NULL },
+  { "l",                                 600, NULL },
+  { "m",                                 600, NULL },
+  { "n",                                 600, NULL },
+  { "tcommaaccent",                      600, NULL },
+  { "o",                                 600, NULL },
+  { "ordfeminine",                       600, NULL },
+  { "ring",                              600, NULL },
+  { "p",                                 600, NULL },
+  { "q",                                 600, NULL },
+  { "uhungarumlaut",                     600, NULL },
+  { "r",                                 600, NULL },
+  { "twosuperior",                       600, NULL },
+  { "aacute",                            600, NULL },
+  { "s",                                 600, NULL },
+  { "OE",                                600, NULL },
+  { "t",                                 600, NULL },
+  { "divide",                            600, NULL },
+  { "u",                                 600, NULL },
+  { "Ccaron",                            600, NULL },
+  { "v",                                 600, NULL },
+  { "w",                                 600, NULL },
+  { "x",                                 600, NULL },
+  { "y",                                 600, NULL },
+  { "z",                                 600, NULL },
+  { "Gbreve",                            600, NULL },
+  { "commaaccent",                       600, NULL },
+  { "hungarumlaut",                      600, NULL },
+  { "Idotaccent",                        600, NULL },
+  { "Nacute",                            600, NULL },
+  { "quotedbl",                          600, NULL },
+  { "gcommaaccent",                      600, NULL },
+  { "mu",                                600, NULL },
+  { "greaterequal",                      600, NULL },
+  { "Scaron",                            600, NULL },
+  { "Lslash",                            600, NULL },
+  { "semicolon",                         600, NULL },
+  { "oslash",                            600, NULL },
+  { "lessequal",                         600, NULL },
+  { "lozenge",                           600, NULL },
+  { "parenright",                        600, NULL },
+  { "ccaron",                            600, NULL },
+  { "Ecircumflex",                       600, NULL },
+  { "gbreve",                            600, NULL },
+  { "trademark",                         600, NULL },
+  { "daggerdbl",                         600, NULL },
+  { "nacute",                            600, NULL },
+  { "macron",                            600, NULL },
+  { "Otilde",                            600, NULL },
+  { "Emacron",                           600, NULL },
+  { "ellipsis",                          600, NULL },
+  { "scaron",                            600, NULL },
+  { "AE",                                600, NULL },
+  { "Ucircumflex",                       600, NULL },
+  { "lslash",                            600, NULL },
+  { "quotedblleft",                      600, NULL },
+  { "guilsinglright",                    600, NULL },
+  { "hyphen",                            600, NULL },
+  { "quotesingle",                       600, NULL },
+  { "eight",                             600, NULL },
+  { "exclamdown",                        600, NULL },
+  { "endash",                            600, NULL },
+  { "oe",                                600, NULL },
+  { "Abreve",                            600, NULL },
+  { "Umacron",                           600, NULL },
+  { "ecircumflex",                       600, NULL },
+  { "Adieresis",                         600, NULL },
+  { "copyright",                         600, NULL },
+  { "Egrave",                            600, NULL },
+  { "slash",                             600, NULL },
+  { "Edieresis",                         600, NULL },
+  { "otilde",                            600, NULL },
+  { "Idieresis",                         600, NULL },
+  { "parenleft",                         600, NULL },
+  { "one",                               600, NULL },
+  { "emacron",                           600, NULL },
+  { "Odieresis",                         600, NULL },
+  { "ucircumflex",                       600, NULL },
+  { "bracketleft",                       600, NULL },
+  { "Ugrave",                            600, NULL },
+  { "quoteright",                        600, NULL },
+  { "Udieresis",                         600, NULL },
+  { "perthousand",                       600, NULL },
+  { "Ydieresis",                         600, NULL },
+  { "umacron",                           600, NULL },
+  { "abreve",                            600, NULL },
+  { "Eacute",                            600, NULL },
+  { "adieresis",                         600, NULL },
+  { "egrave",                            600, NULL },
+  { "edieresis",                         600, NULL },
+  { "idieresis",                         600, NULL },
+  { "Eth",                               600, NULL },
+  { "ae",                                600, NULL },
+  { "asterisk",                          600, NULL },
+  { "odieresis",                         600, NULL },
+  { "Uacute",                            600, NULL },
+  { "ugrave",                            600, NULL },
+  { "nine",                              600, NULL },
+  { "five",                              600, NULL },
+  { "udieresis",                         600, NULL },
+  { "Zcaron",                            600, NULL },
+  { "Scommaaccent",                      600, NULL },
+  { "threequarters",                     600, NULL },
+  { "guillemotright",                    600, NULL },
+  { "Ccedilla",                          600, NULL },
+  { "ydieresis",                         600, NULL },
+  { "tilde",                             600, NULL },
+  { "at",                                600, NULL },
+  { "eacute",                            600, NULL },
+  { "underscore",                        600, NULL },
+  { "Euro",                              600, NULL },
+  { "Dcroat",                            600, NULL },
+  { "multiply",                          600, NULL },
+  { "zero",                              600, NULL },
+  { "eth",                               600, NULL },
+  { "Scedilla",                          600, NULL },
+  { "Ograve",                            600, NULL },
+  { "Racute",                            600, NULL },
+  { "partialdiff",                       600, NULL },
+  { "uacute",                            600, NULL },
+  { "braceleft",                         600, NULL },
+  { "Thorn",                             600, NULL },
+  { "zcaron",                            600, NULL },
+  { "scommaaccent",                      600, NULL },
+  { "ccedilla",                          600, NULL },
+  { "Dcaron",                            600, NULL },
+  { "dcroat",                            600, NULL },
+  { "Ocircumflex",                       600, NULL },
+  { "Oacute",                            600, NULL },
+  { "scedilla",                          600, NULL },
+  { "ogonek",                            600, NULL },
+  { "ograve",                            600, NULL },
+  { "racute",                            600, NULL },
+  { "Tcaron",                            600, NULL },
+  { "Eogonek",                           600, NULL },
+  { "thorn",                             600, NULL },
+  { "degree",                            600, NULL },
+  { "registered",                        600, NULL },
+  { "radical",                           600, NULL },
+  { "Aring",                             600, NULL },
+  { "percent",                           600, NULL },
+  { "six",                               600, NULL },
+  { "paragraph",                         600, NULL },
+  { "dcaron",                            600, NULL },
+  { "Uogonek",                           600, NULL },
+  { "two",                               600, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            600, NULL },
+  { "Lacute",                            600, NULL },
+  { "ocircumflex",                       600, NULL },
+  { "oacute",                            600, NULL },
+  { "Uring",                             600, NULL },
+  { "Lcommaaccent",                      600, NULL },
+  { "tcaron",                            600, NULL },
+  { "eogonek",                           600, NULL },
+  { "Delta",                             600, NULL },
+  { "Ohungarumlaut",                     600, NULL },
+  { "asciicircum",                       600, NULL },
+  { "aring",                             600, NULL },
+  { "grave",                             600, NULL },
+  { "uogonek",                           600, NULL },
+  { "bracketright",                      600, NULL },
+  { "Iacute",                            600, NULL },
+  { "ampersand",                         600, NULL },
+  { "igrave",                            600, NULL },
+  { "lacute",                            600, NULL },
+  { "Ncaron",                            600, NULL },
+  { "plus",                              600, NULL },
+  { "uring",                             600, NULL },
+  { "quotesinglbase",                    600, NULL },
+  { "lcommaaccent",                      600, NULL },
+  { "Yacute",                            600, NULL },
+  { "ohungarumlaut",                     600, NULL },
+  { "threesuperior",                     600, NULL },
+  { "acute",                             600, NULL },
+  { "section",                           600, NULL },
+  { "dieresis",                          600, NULL },
+  { "iacute",                            600, NULL },
+  { "quotedblbase",                      600, NULL },
+  { "ncaron",                            600, NULL },
+  { "florin",                            600, NULL },
+  { "yacute",                            600, NULL },
+  { "Rcommaaccent",                      600, NULL },
+  { "fi",                                600, NULL },
+  { "fl",                                600, NULL },
+  { "Acircumflex",                       600, NULL },
+  { "Cacute",                            600, NULL },
+  { "Icircumflex",                       600, NULL },
+  { "guillemotleft",                     600, NULL },
+  { "germandbls",                        600, NULL },
+  { "Amacron",                           600, NULL },
+  { "seven",                             600, NULL },
+  { "Sacute",                            600, NULL },
+  { "ordmasculine",                      600, NULL },
+  { "dotlessi",                          600, NULL },
+  { "sterling",                          600, NULL },
+  { "notequal",                          600, NULL },
+  { "Imacron",                           600, NULL },
+  { "rcommaaccent",                      600, NULL },
+  { "Zdotaccent",                        600, NULL },
+  { "acircumflex",                       600, NULL },
+  { "cacute",                            600, NULL },
+  { "Ecaron",                            600, NULL },
+  { "icircumflex",                       600, NULL },
+  { "braceright",                        600, NULL },
+  { "quotedblright",                     600, NULL },
+  { "amacron",                           600, NULL },
+  { "sacute",                            600, NULL },
+  { "imacron",                           600, NULL },
+  { "cent",                              600, NULL },
+  { "currency",                          600, NULL },
+  { "logicalnot",                        600, NULL },
+  { "zdotaccent",                        600, NULL },
+  { "Atilde",                            600, NULL },
+  { "breve",                             600, NULL },
+  { "bar",                               600, NULL },
+  { "fraction",                          600, NULL },
+  { "less",                              600, NULL },
+  { "ecaron",                            600, NULL },
+  { "guilsinglleft",                     600, NULL },
+  { "exclam",                            600, NULL },
+  { "period",                            600, NULL },
+  { "Rcaron",                            600, NULL },
+  { "Kcommaaccent",                      600, NULL },
+  { "greater",                           600, NULL },
+  { "atilde",                            600, NULL },
+  { "brokenbar",                         600, NULL },
+  { "quoteleft",                         600, NULL },
+  { "Edotaccent",                        600, NULL },
+  { "onesuperior",                       600, NULL }
+};
+
+static BuiltinFontWidth courierObliqueWidthsTab[] = {
+  { "Ntilde",                            600, NULL },
+  { "rcaron",                            600, NULL },
+  { "kcommaaccent",                      600, NULL },
+  { "Ncommaaccent",                      600, NULL },
+  { "Zacute",                            600, NULL },
+  { "comma",                             600, NULL },
+  { "cedilla",                           600, NULL },
+  { "plusminus",                         600, NULL },
+  { "circumflex",                        600, NULL },
+  { "dotaccent",                         600, NULL },
+  { "edotaccent",                        600, NULL },
+  { "asciitilde",                        600, NULL },
+  { "colon",                             600, NULL },
+  { "onehalf",                           600, NULL },
+  { "dollar",                            600, NULL },
+  { "Lcaron",                            600, NULL },
+  { "ntilde",                            600, NULL },
+  { "Aogonek",                           600, NULL },
+  { "ncommaaccent",                      600, NULL },
+  { "minus",                             600, NULL },
+  { "Iogonek",                           600, NULL },
+  { "zacute",                            600, NULL },
+  { "yen",                               600, NULL },
+  { "space",                             600, NULL },
+  { "Omacron",                           600, NULL },
+  { "questiondown",                      600, NULL },
+  { "emdash",                            600, NULL },
+  { "Agrave",                            600, NULL },
+  { "three",                             600, NULL },
+  { "numbersign",                        600, NULL },
+  { "lcaron",                            600, NULL },
+  { "A",                                 600, NULL },
+  { "B",                                 600, NULL },
+  { "C",                                 600, NULL },
+  { "aogonek",                           600, NULL },
+  { "D",                                 600, NULL },
+  { "E",                                 600, NULL },
+  { "onequarter",                        600, NULL },
+  { "F",                                 600, NULL },
+  { "G",                                 600, NULL },
+  { "H",                                 600, NULL },
+  { "I",                                 600, NULL },
+  { "J",                                 600, NULL },
+  { "K",                                 600, NULL },
+  { "iogonek",                           600, NULL },
+  { "backslash",                         600, NULL },
+  { "L",                                 600, NULL },
+  { "periodcentered",                    600, NULL },
+  { "M",                                 600, NULL },
+  { "N",                                 600, NULL },
+  { "omacron",                           600, NULL },
+  { "Tcommaaccent",                      600, NULL },
+  { "O",                                 600, NULL },
+  { "P",                                 600, NULL },
+  { "Q",                                 600, NULL },
+  { "Uhungarumlaut",                     600, NULL },
+  { "R",                                 600, NULL },
+  { "Aacute",                            600, NULL },
+  { "caron",                             600, NULL },
+  { "S",                                 600, NULL },
+  { "T",                                 600, NULL },
+  { "U",                                 600, NULL },
+  { "agrave",                            600, NULL },
+  { "V",                                 600, NULL },
+  { "W",                                 600, NULL },
+  { "X",                                 600, NULL },
+  { "question",                          600, NULL },
+  { "equal",                             600, NULL },
+  { "Y",                                 600, NULL },
+  { "Z",                                 600, NULL },
+  { "four",                              600, NULL },
+  { "a",                                 600, NULL },
+  { "Gcommaaccent",                      600, NULL },
+  { "b",                                 600, NULL },
+  { "c",                                 600, NULL },
+  { "d",                                 600, NULL },
+  { "e",                                 600, NULL },
+  { "f",                                 600, NULL },
+  { "g",                                 600, NULL },
+  { "bullet",                            600, NULL },
+  { "h",                                 600, NULL },
+  { "i",                                 600, NULL },
+  { "Oslash",                            600, NULL },
+  { "dagger",                            600, NULL },
+  { "j",                                 600, NULL },
+  { "k",                                 600, NULL },
+  { "l",                                 600, NULL },
+  { "m",                                 600, NULL },
+  { "n",                                 600, NULL },
+  { "tcommaaccent",                      600, NULL },
+  { "o",                                 600, NULL },
+  { "ordfeminine",                       600, NULL },
+  { "ring",                              600, NULL },
+  { "p",                                 600, NULL },
+  { "q",                                 600, NULL },
+  { "uhungarumlaut",                     600, NULL },
+  { "r",                                 600, NULL },
+  { "twosuperior",                       600, NULL },
+  { "aacute",                            600, NULL },
+  { "s",                                 600, NULL },
+  { "OE",                                600, NULL },
+  { "t",                                 600, NULL },
+  { "divide",                            600, NULL },
+  { "u",                                 600, NULL },
+  { "Ccaron",                            600, NULL },
+  { "v",                                 600, NULL },
+  { "w",                                 600, NULL },
+  { "x",                                 600, NULL },
+  { "y",                                 600, NULL },
+  { "z",                                 600, NULL },
+  { "Gbreve",                            600, NULL },
+  { "commaaccent",                       600, NULL },
+  { "hungarumlaut",                      600, NULL },
+  { "Idotaccent",                        600, NULL },
+  { "Nacute",                            600, NULL },
+  { "quotedbl",                          600, NULL },
+  { "gcommaaccent",                      600, NULL },
+  { "mu",                                600, NULL },
+  { "greaterequal",                      600, NULL },
+  { "Scaron",                            600, NULL },
+  { "Lslash",                            600, NULL },
+  { "semicolon",                         600, NULL },
+  { "oslash",                            600, NULL },
+  { "lessequal",                         600, NULL },
+  { "lozenge",                           600, NULL },
+  { "parenright",                        600, NULL },
+  { "ccaron",                            600, NULL },
+  { "Ecircumflex",                       600, NULL },
+  { "gbreve",                            600, NULL },
+  { "trademark",                         600, NULL },
+  { "daggerdbl",                         600, NULL },
+  { "nacute",                            600, NULL },
+  { "macron",                            600, NULL },
+  { "Otilde",                            600, NULL },
+  { "Emacron",                           600, NULL },
+  { "ellipsis",                          600, NULL },
+  { "scaron",                            600, NULL },
+  { "AE",                                600, NULL },
+  { "Ucircumflex",                       600, NULL },
+  { "lslash",                            600, NULL },
+  { "quotedblleft",                      600, NULL },
+  { "guilsinglright",                    600, NULL },
+  { "hyphen",                            600, NULL },
+  { "quotesingle",                       600, NULL },
+  { "eight",                             600, NULL },
+  { "exclamdown",                        600, NULL },
+  { "endash",                            600, NULL },
+  { "oe",                                600, NULL },
+  { "Abreve",                            600, NULL },
+  { "Umacron",                           600, NULL },
+  { "ecircumflex",                       600, NULL },
+  { "Adieresis",                         600, NULL },
+  { "copyright",                         600, NULL },
+  { "Egrave",                            600, NULL },
+  { "slash",                             600, NULL },
+  { "Edieresis",                         600, NULL },
+  { "otilde",                            600, NULL },
+  { "Idieresis",                         600, NULL },
+  { "parenleft",                         600, NULL },
+  { "one",                               600, NULL },
+  { "emacron",                           600, NULL },
+  { "Odieresis",                         600, NULL },
+  { "ucircumflex",                       600, NULL },
+  { "bracketleft",                       600, NULL },
+  { "Ugrave",                            600, NULL },
+  { "quoteright",                        600, NULL },
+  { "Udieresis",                         600, NULL },
+  { "perthousand",                       600, NULL },
+  { "Ydieresis",                         600, NULL },
+  { "umacron",                           600, NULL },
+  { "abreve",                            600, NULL },
+  { "Eacute",                            600, NULL },
+  { "adieresis",                         600, NULL },
+  { "egrave",                            600, NULL },
+  { "edieresis",                         600, NULL },
+  { "idieresis",                         600, NULL },
+  { "Eth",                               600, NULL },
+  { "ae",                                600, NULL },
+  { "asterisk",                          600, NULL },
+  { "odieresis",                         600, NULL },
+  { "Uacute",                            600, NULL },
+  { "ugrave",                            600, NULL },
+  { "nine",                              600, NULL },
+  { "five",                              600, NULL },
+  { "udieresis",                         600, NULL },
+  { "Zcaron",                            600, NULL },
+  { "Scommaaccent",                      600, NULL },
+  { "threequarters",                     600, NULL },
+  { "guillemotright",                    600, NULL },
+  { "Ccedilla",                          600, NULL },
+  { "ydieresis",                         600, NULL },
+  { "tilde",                             600, NULL },
+  { "at",                                600, NULL },
+  { "eacute",                            600, NULL },
+  { "underscore",                        600, NULL },
+  { "Euro",                              600, NULL },
+  { "Dcroat",                            600, NULL },
+  { "multiply",                          600, NULL },
+  { "zero",                              600, NULL },
+  { "eth",                               600, NULL },
+  { "Scedilla",                          600, NULL },
+  { "Ograve",                            600, NULL },
+  { "Racute",                            600, NULL },
+  { "partialdiff",                       600, NULL },
+  { "uacute",                            600, NULL },
+  { "braceleft",                         600, NULL },
+  { "Thorn",                             600, NULL },
+  { "zcaron",                            600, NULL },
+  { "scommaaccent",                      600, NULL },
+  { "ccedilla",                          600, NULL },
+  { "Dcaron",                            600, NULL },
+  { "dcroat",                            600, NULL },
+  { "Ocircumflex",                       600, NULL },
+  { "Oacute",                            600, NULL },
+  { "scedilla",                          600, NULL },
+  { "ogonek",                            600, NULL },
+  { "ograve",                            600, NULL },
+  { "racute",                            600, NULL },
+  { "Tcaron",                            600, NULL },
+  { "Eogonek",                           600, NULL },
+  { "thorn",                             600, NULL },
+  { "degree",                            600, NULL },
+  { "registered",                        600, NULL },
+  { "radical",                           600, NULL },
+  { "Aring",                             600, NULL },
+  { "percent",                           600, NULL },
+  { "six",                               600, NULL },
+  { "paragraph",                         600, NULL },
+  { "dcaron",                            600, NULL },
+  { "Uogonek",                           600, NULL },
+  { "two",                               600, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            600, NULL },
+  { "Lacute",                            600, NULL },
+  { "ocircumflex",                       600, NULL },
+  { "oacute",                            600, NULL },
+  { "Uring",                             600, NULL },
+  { "Lcommaaccent",                      600, NULL },
+  { "tcaron",                            600, NULL },
+  { "eogonek",                           600, NULL },
+  { "Delta",                             600, NULL },
+  { "Ohungarumlaut",                     600, NULL },
+  { "asciicircum",                       600, NULL },
+  { "aring",                             600, NULL },
+  { "grave",                             600, NULL },
+  { "uogonek",                           600, NULL },
+  { "bracketright",                      600, NULL },
+  { "Iacute",                            600, NULL },
+  { "ampersand",                         600, NULL },
+  { "igrave",                            600, NULL },
+  { "lacute",                            600, NULL },
+  { "Ncaron",                            600, NULL },
+  { "plus",                              600, NULL },
+  { "uring",                             600, NULL },
+  { "quotesinglbase",                    600, NULL },
+  { "lcommaaccent",                      600, NULL },
+  { "Yacute",                            600, NULL },
+  { "ohungarumlaut",                     600, NULL },
+  { "threesuperior",                     600, NULL },
+  { "acute",                             600, NULL },
+  { "section",                           600, NULL },
+  { "dieresis",                          600, NULL },
+  { "iacute",                            600, NULL },
+  { "quotedblbase",                      600, NULL },
+  { "ncaron",                            600, NULL },
+  { "florin",                            600, NULL },
+  { "yacute",                            600, NULL },
+  { "Rcommaaccent",                      600, NULL },
+  { "fi",                                600, NULL },
+  { "fl",                                600, NULL },
+  { "Acircumflex",                       600, NULL },
+  { "Cacute",                            600, NULL },
+  { "Icircumflex",                       600, NULL },
+  { "guillemotleft",                     600, NULL },
+  { "germandbls",                        600, NULL },
+  { "Amacron",                           600, NULL },
+  { "seven",                             600, NULL },
+  { "Sacute",                            600, NULL },
+  { "ordmasculine",                      600, NULL },
+  { "dotlessi",                          600, NULL },
+  { "sterling",                          600, NULL },
+  { "notequal",                          600, NULL },
+  { "Imacron",                           600, NULL },
+  { "rcommaaccent",                      600, NULL },
+  { "Zdotaccent",                        600, NULL },
+  { "acircumflex",                       600, NULL },
+  { "cacute",                            600, NULL },
+  { "Ecaron",                            600, NULL },
+  { "icircumflex",                       600, NULL },
+  { "braceright",                        600, NULL },
+  { "quotedblright",                     600, NULL },
+  { "amacron",                           600, NULL },
+  { "sacute",                            600, NULL },
+  { "imacron",                           600, NULL },
+  { "cent",                              600, NULL },
+  { "currency",                          600, NULL },
+  { "logicalnot",                        600, NULL },
+  { "zdotaccent",                        600, NULL },
+  { "Atilde",                            600, NULL },
+  { "breve",                             600, NULL },
+  { "bar",                               600, NULL },
+  { "fraction",                          600, NULL },
+  { "less",                              600, NULL },
+  { "ecaron",                            600, NULL },
+  { "guilsinglleft",                     600, NULL },
+  { "exclam",                            600, NULL },
+  { "period",                            600, NULL },
+  { "Rcaron",                            600, NULL },
+  { "Kcommaaccent",                      600, NULL },
+  { "greater",                           600, NULL },
+  { "atilde",                            600, NULL },
+  { "brokenbar",                         600, NULL },
+  { "quoteleft",                         600, NULL },
+  { "Edotaccent",                        600, NULL },
+  { "onesuperior",                       600, NULL }
+};
+
+static BuiltinFontWidth helveticaWidthsTab[] = {
+  { "Ntilde",                            722, NULL },
+  { "rcaron",                            333, NULL },
+  { "kcommaaccent",                      500, NULL },
+  { "Ncommaaccent",                      722, NULL },
+  { "Zacute",                            611, NULL },
+  { "comma",                             278, NULL },
+  { "cedilla",                           333, NULL },
+  { "plusminus",                         584, NULL },
+  { "circumflex",                        333, NULL },
+  { "dotaccent",                         333, NULL },
+  { "edotaccent",                        556, NULL },
+  { "asciitilde",                        584, NULL },
+  { "colon",                             278, NULL },
+  { "onehalf",                           834, NULL },
+  { "dollar",                            556, NULL },
+  { "Lcaron",                            556, NULL },
+  { "ntilde",                            556, NULL },
+  { "Aogonek",                           667, NULL },
+  { "ncommaaccent",                      556, NULL },
+  { "minus",                             584, NULL },
+  { "Iogonek",                           278, NULL },
+  { "zacute",                            500, NULL },
+  { "yen",                               556, NULL },
+  { "space",                             278, NULL },
+  { "Omacron",                           778, NULL },
+  { "questiondown",                      611, NULL },
+  { "emdash",                           1000, NULL },
+  { "Agrave",                            667, NULL },
+  { "three",                             556, NULL },
+  { "numbersign",                        556, NULL },
+  { "lcaron",                            299, NULL },
+  { "A",                                 667, NULL },
+  { "B",                                 667, NULL },
+  { "C",                                 722, NULL },
+  { "aogonek",                           556, NULL },
+  { "D",                                 722, NULL },
+  { "E",                                 667, NULL },
+  { "onequarter",                        834, NULL },
+  { "F",                                 611, NULL },
+  { "G",                                 778, NULL },
+  { "H",                                 722, NULL },
+  { "I",                                 278, NULL },
+  { "J",                                 500, NULL },
+  { "K",                                 667, NULL },
+  { "iogonek",                           222, NULL },
+  { "backslash",                         278, NULL },
+  { "L",                                 556, NULL },
+  { "periodcentered",                    278, NULL },
+  { "M",                                 833, NULL },
+  { "N",                                 722, NULL },
+  { "omacron",                           556, NULL },
+  { "Tcommaaccent",                      611, NULL },
+  { "O",                                 778, NULL },
+  { "P",                                 667, NULL },
+  { "Q",                                 778, NULL },
+  { "Uhungarumlaut",                     722, NULL },
+  { "R",                                 722, NULL },
+  { "Aacute",                            667, NULL },
+  { "caron",                             333, NULL },
+  { "S",                                 667, NULL },
+  { "T",                                 611, NULL },
+  { "U",                                 722, NULL },
+  { "agrave",                            556, NULL },
+  { "V",                                 667, NULL },
+  { "W",                                 944, NULL },
+  { "X",                                 667, NULL },
+  { "question",                          556, NULL },
+  { "equal",                             584, NULL },
+  { "Y",                                 667, NULL },
+  { "Z",                                 611, NULL },
+  { "four",                              556, NULL },
+  { "a",                                 556, NULL },
+  { "Gcommaaccent",                      778, NULL },
+  { "b",                                 556, NULL },
+  { "c",                                 500, NULL },
+  { "d",                                 556, NULL },
+  { "e",                                 556, NULL },
+  { "f",                                 278, NULL },
+  { "g",                                 556, NULL },
+  { "bullet",                            350, NULL },
+  { "h",                                 556, NULL },
+  { "i",                                 222, NULL },
+  { "Oslash",                            778, NULL },
+  { "dagger",                            556, NULL },
+  { "j",                                 222, NULL },
+  { "k",                                 500, NULL },
+  { "l",                                 222, NULL },
+  { "m",                                 833, NULL },
+  { "n",                                 556, NULL },
+  { "tcommaaccent",                      278, NULL },
+  { "o",                                 556, NULL },
+  { "ordfeminine",                       370, NULL },
+  { "ring",                              333, NULL },
+  { "p",                                 556, NULL },
+  { "q",                                 556, NULL },
+  { "uhungarumlaut",                     556, NULL },
+  { "r",                                 333, NULL },
+  { "twosuperior",                       333, NULL },
+  { "aacute",                            556, NULL },
+  { "s",                                 500, NULL },
+  { "OE",                               1000, NULL },
+  { "t",                                 278, NULL },
+  { "divide",                            584, NULL },
+  { "u",                                 556, NULL },
+  { "Ccaron",                            722, NULL },
+  { "v",                                 500, NULL },
+  { "w",                                 722, NULL },
+  { "x",                                 500, NULL },
+  { "y",                                 500, NULL },
+  { "z",                                 500, NULL },
+  { "Gbreve",                            778, NULL },
+  { "commaaccent",                       250, NULL },
+  { "hungarumlaut",                      333, NULL },
+  { "Idotaccent",                        278, NULL },
+  { "Nacute",                            722, NULL },
+  { "quotedbl",                          355, NULL },
+  { "gcommaaccent",                      556, NULL },
+  { "mu",                                556, NULL },
+  { "greaterequal",                      549, NULL },
+  { "Scaron",                            667, NULL },
+  { "Lslash",                            556, NULL },
+  { "semicolon",                         278, NULL },
+  { "oslash",                            611, NULL },
+  { "lessequal",                         549, NULL },
+  { "lozenge",                           471, NULL },
+  { "parenright",                        333, NULL },
+  { "ccaron",                            500, NULL },
+  { "Ecircumflex",                       667, NULL },
+  { "gbreve",                            556, NULL },
+  { "trademark",                        1000, NULL },
+  { "daggerdbl",                         556, NULL },
+  { "nacute",                            556, NULL },
+  { "macron",                            333, NULL },
+  { "Otilde",                            778, NULL },
+  { "Emacron",                           667, NULL },
+  { "ellipsis",                         1000, NULL },
+  { "scaron",                            500, NULL },
+  { "AE",                               1000, NULL },
+  { "Ucircumflex",                       722, NULL },
+  { "lslash",                            222, NULL },
+  { "quotedblleft",                      333, NULL },
+  { "guilsinglright",                    333, NULL },
+  { "hyphen",                            333, NULL },
+  { "quotesingle",                       191, NULL },
+  { "eight",                             556, NULL },
+  { "exclamdown",                        333, NULL },
+  { "endash",                            556, NULL },
+  { "oe",                                944, NULL },
+  { "Abreve",                            667, NULL },
+  { "Umacron",                           722, NULL },
+  { "ecircumflex",                       556, NULL },
+  { "Adieresis",                         667, NULL },
+  { "copyright",                         737, NULL },
+  { "Egrave",                            667, NULL },
+  { "slash",                             278, NULL },
+  { "Edieresis",                         667, NULL },
+  { "otilde",                            556, NULL },
+  { "Idieresis",                         278, NULL },
+  { "parenleft",                         333, NULL },
+  { "one",                               556, NULL },
+  { "emacron",                           556, NULL },
+  { "Odieresis",                         778, NULL },
+  { "ucircumflex",                       556, NULL },
+  { "bracketleft",                       278, NULL },
+  { "Ugrave",                            722, NULL },
+  { "quoteright",                        222, NULL },
+  { "Udieresis",                         722, NULL },
+  { "perthousand",                      1000, NULL },
+  { "Ydieresis",                         667, NULL },
+  { "umacron",                           556, NULL },
+  { "abreve",                            556, NULL },
+  { "Eacute",                            667, NULL },
+  { "adieresis",                         556, NULL },
+  { "egrave",                            556, NULL },
+  { "edieresis",                         556, NULL },
+  { "idieresis",                         278, NULL },
+  { "Eth",                               722, NULL },
+  { "ae",                                889, NULL },
+  { "asterisk",                          389, NULL },
+  { "odieresis",                         556, NULL },
+  { "Uacute",                            722, NULL },
+  { "ugrave",                            556, NULL },
+  { "nine",                              556, NULL },
+  { "five",                              556, NULL },
+  { "udieresis",                         556, NULL },
+  { "Zcaron",                            611, NULL },
+  { "Scommaaccent",                      667, NULL },
+  { "threequarters",                     834, NULL },
+  { "guillemotright",                    556, NULL },
+  { "Ccedilla",                          722, NULL },
+  { "ydieresis",                         500, NULL },
+  { "tilde",                             333, NULL },
+  { "at",                               1015, NULL },
+  { "eacute",                            556, NULL },
+  { "underscore",                        556, NULL },
+  { "Euro",                              556, NULL },
+  { "Dcroat",                            722, NULL },
+  { "multiply",                          584, NULL },
+  { "zero",                              556, NULL },
+  { "eth",                               556, NULL },
+  { "Scedilla",                          667, NULL },
+  { "Ograve",                            778, NULL },
+  { "Racute",                            722, NULL },
+  { "partialdiff",                       476, NULL },
+  { "uacute",                            556, NULL },
+  { "braceleft",                         334, NULL },
+  { "Thorn",                             667, NULL },
+  { "zcaron",                            500, NULL },
+  { "scommaaccent",                      500, NULL },
+  { "ccedilla",                          500, NULL },
+  { "Dcaron",                            722, NULL },
+  { "dcroat",                            556, NULL },
+  { "Ocircumflex",                       778, NULL },
+  { "Oacute",                            778, NULL },
+  { "scedilla",                          500, NULL },
+  { "ogonek",                            333, NULL },
+  { "ograve",                            556, NULL },
+  { "racute",                            333, NULL },
+  { "Tcaron",                            611, NULL },
+  { "Eogonek",                           667, NULL },
+  { "thorn",                             556, NULL },
+  { "degree",                            400, NULL },
+  { "registered",                        737, NULL },
+  { "radical",                           453, NULL },
+  { "Aring",                             667, NULL },
+  { "percent",                           889, NULL },
+  { "six",                               556, NULL },
+  { "paragraph",                         537, NULL },
+  { "dcaron",                            643, NULL },
+  { "Uogonek",                           722, NULL },
+  { "two",                               556, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            278, NULL },
+  { "Lacute",                            556, NULL },
+  { "ocircumflex",                       556, NULL },
+  { "oacute",                            556, NULL },
+  { "Uring",                             722, NULL },
+  { "Lcommaaccent",                      556, NULL },
+  { "tcaron",                            317, NULL },
+  { "eogonek",                           556, NULL },
+  { "Delta",                             612, NULL },
+  { "Ohungarumlaut",                     778, NULL },
+  { "asciicircum",                       469, NULL },
+  { "aring",                             556, NULL },
+  { "grave",                             333, NULL },
+  { "uogonek",                           556, NULL },
+  { "bracketright",                      278, NULL },
+  { "Iacute",                            278, NULL },
+  { "ampersand",                         667, NULL },
+  { "igrave",                            278, NULL },
+  { "lacute",                            222, NULL },
+  { "Ncaron",                            722, NULL },
+  { "plus",                              584, NULL },
+  { "uring",                             556, NULL },
+  { "quotesinglbase",                    222, NULL },
+  { "lcommaaccent",                      222, NULL },
+  { "Yacute",                            667, NULL },
+  { "ohungarumlaut",                     556, NULL },
+  { "threesuperior",                     333, NULL },
+  { "acute",                             333, NULL },
+  { "section",                           556, NULL },
+  { "dieresis",                          333, NULL },
+  { "iacute",                            278, NULL },
+  { "quotedblbase",                      333, NULL },
+  { "ncaron",                            556, NULL },
+  { "florin",                            556, NULL },
+  { "yacute",                            500, NULL },
+  { "Rcommaaccent",                      722, NULL },
+  { "fi",                                500, NULL },
+  { "fl",                                500, NULL },
+  { "Acircumflex",                       667, NULL },
+  { "Cacute",                            722, NULL },
+  { "Icircumflex",                       278, NULL },
+  { "guillemotleft",                     556, NULL },
+  { "germandbls",                        611, NULL },
+  { "Amacron",                           667, NULL },
+  { "seven",                             556, NULL },
+  { "Sacute",                            667, NULL },
+  { "ordmasculine",                      365, NULL },
+  { "dotlessi",                          278, NULL },
+  { "sterling",                          556, NULL },
+  { "notequal",                          549, NULL },
+  { "Imacron",                           278, NULL },
+  { "rcommaaccent",                      333, NULL },
+  { "Zdotaccent",                        611, NULL },
+  { "acircumflex",                       556, NULL },
+  { "cacute",                            500, NULL },
+  { "Ecaron",                            667, NULL },
+  { "icircumflex",                       278, NULL },
+  { "braceright",                        334, NULL },
+  { "quotedblright",                     333, NULL },
+  { "amacron",                           556, NULL },
+  { "sacute",                            500, NULL },
+  { "imacron",                           278, NULL },
+  { "cent",                              556, NULL },
+  { "currency",                          556, NULL },
+  { "logicalnot",                        584, NULL },
+  { "zdotaccent",                        500, NULL },
+  { "Atilde",                            667, NULL },
+  { "breve",                             333, NULL },
+  { "bar",                               260, NULL },
+  { "fraction",                          167, NULL },
+  { "less",                              584, NULL },
+  { "ecaron",                            556, NULL },
+  { "guilsinglleft",                     333, NULL },
+  { "exclam",                            278, NULL },
+  { "period",                            278, NULL },
+  { "Rcaron",                            722, NULL },
+  { "Kcommaaccent",                      667, NULL },
+  { "greater",                           584, NULL },
+  { "atilde",                            556, NULL },
+  { "brokenbar",                         260, NULL },
+  { "quoteleft",                         222, NULL },
+  { "Edotaccent",                        667, NULL },
+  { "onesuperior",                       333, NULL }
+};
+
+static BuiltinFontWidth helveticaBoldWidthsTab[] = {
+  { "Ntilde",                            722, NULL },
+  { "rcaron",                            389, NULL },
+  { "kcommaaccent",                      556, NULL },
+  { "Ncommaaccent",                      722, NULL },
+  { "Zacute",                            611, NULL },
+  { "comma",                             278, NULL },
+  { "cedilla",                           333, NULL },
+  { "plusminus",                         584, NULL },
+  { "circumflex",                        333, NULL },
+  { "dotaccent",                         333, NULL },
+  { "edotaccent",                        556, NULL },
+  { "asciitilde",                        584, NULL },
+  { "colon",                             333, NULL },
+  { "onehalf",                           834, NULL },
+  { "dollar",                            556, NULL },
+  { "Lcaron",                            611, NULL },
+  { "ntilde",                            611, NULL },
+  { "Aogonek",                           722, NULL },
+  { "ncommaaccent",                      611, NULL },
+  { "minus",                             584, NULL },
+  { "Iogonek",                           278, NULL },
+  { "zacute",                            500, NULL },
+  { "yen",                               556, NULL },
+  { "space",                             278, NULL },
+  { "Omacron",                           778, NULL },
+  { "questiondown",                      611, NULL },
+  { "emdash",                           1000, NULL },
+  { "Agrave",                            722, NULL },
+  { "three",                             556, NULL },
+  { "numbersign",                        556, NULL },
+  { "lcaron",                            400, NULL },
+  { "A",                                 722, NULL },
+  { "B",                                 722, NULL },
+  { "C",                                 722, NULL },
+  { "aogonek",                           556, NULL },
+  { "D",                                 722, NULL },
+  { "E",                                 667, NULL },
+  { "onequarter",                        834, NULL },
+  { "F",                                 611, NULL },
+  { "G",                                 778, NULL },
+  { "H",                                 722, NULL },
+  { "I",                                 278, NULL },
+  { "J",                                 556, NULL },
+  { "K",                                 722, NULL },
+  { "iogonek",                           278, NULL },
+  { "backslash",                         278, NULL },
+  { "L",                                 611, NULL },
+  { "periodcentered",                    278, NULL },
+  { "M",                                 833, NULL },
+  { "N",                                 722, NULL },
+  { "omacron",                           611, NULL },
+  { "Tcommaaccent",                      611, NULL },
+  { "O",                                 778, NULL },
+  { "P",                                 667, NULL },
+  { "Q",                                 778, NULL },
+  { "Uhungarumlaut",                     722, NULL },
+  { "R",                                 722, NULL },
+  { "Aacute",                            722, NULL },
+  { "caron",                             333, NULL },
+  { "S",                                 667, NULL },
+  { "T",                                 611, NULL },
+  { "U",                                 722, NULL },
+  { "agrave",                            556, NULL },
+  { "V",                                 667, NULL },
+  { "W",                                 944, NULL },
+  { "X",                                 667, NULL },
+  { "question",                          611, NULL },
+  { "equal",                             584, NULL },
+  { "Y",                                 667, NULL },
+  { "Z",                                 611, NULL },
+  { "four",                              556, NULL },
+  { "a",                                 556, NULL },
+  { "Gcommaaccent",                      778, NULL },
+  { "b",                                 611, NULL },
+  { "c",                                 556, NULL },
+  { "d",                                 611, NULL },
+  { "e",                                 556, NULL },
+  { "f",                                 333, NULL },
+  { "g",                                 611, NULL },
+  { "bullet",                            350, NULL },
+  { "h",                                 611, NULL },
+  { "i",                                 278, NULL },
+  { "Oslash",                            778, NULL },
+  { "dagger",                            556, NULL },
+  { "j",                                 278, NULL },
+  { "k",                                 556, NULL },
+  { "l",                                 278, NULL },
+  { "m",                                 889, NULL },
+  { "n",                                 611, NULL },
+  { "tcommaaccent",                      333, NULL },
+  { "o",                                 611, NULL },
+  { "ordfeminine",                       370, NULL },
+  { "ring",                              333, NULL },
+  { "p",                                 611, NULL },
+  { "q",                                 611, NULL },
+  { "uhungarumlaut",                     611, NULL },
+  { "r",                                 389, NULL },
+  { "twosuperior",                       333, NULL },
+  { "aacute",                            556, NULL },
+  { "s",                                 556, NULL },
+  { "OE",                               1000, NULL },
+  { "t",                                 333, NULL },
+  { "divide",                            584, NULL },
+  { "u",                                 611, NULL },
+  { "Ccaron",                            722, NULL },
+  { "v",                                 556, NULL },
+  { "w",                                 778, NULL },
+  { "x",                                 556, NULL },
+  { "y",                                 556, NULL },
+  { "z",                                 500, NULL },
+  { "Gbreve",                            778, NULL },
+  { "commaaccent",                       250, NULL },
+  { "hungarumlaut",                      333, NULL },
+  { "Idotaccent",                        278, NULL },
+  { "Nacute",                            722, NULL },
+  { "quotedbl",                          474, NULL },
+  { "gcommaaccent",                      611, NULL },
+  { "mu",                                611, NULL },
+  { "greaterequal",                      549, NULL },
+  { "Scaron",                            667, NULL },
+  { "Lslash",                            611, NULL },
+  { "semicolon",                         333, NULL },
+  { "oslash",                            611, NULL },
+  { "lessequal",                         549, NULL },
+  { "lozenge",                           494, NULL },
+  { "parenright",                        333, NULL },
+  { "ccaron",                            556, NULL },
+  { "Ecircumflex",                       667, NULL },
+  { "gbreve",                            611, NULL },
+  { "trademark",                        1000, NULL },
+  { "daggerdbl",                         556, NULL },
+  { "nacute",                            611, NULL },
+  { "macron",                            333, NULL },
+  { "Otilde",                            778, NULL },
+  { "Emacron",                           667, NULL },
+  { "ellipsis",                         1000, NULL },
+  { "scaron",                            556, NULL },
+  { "AE",                               1000, NULL },
+  { "Ucircumflex",                       722, NULL },
+  { "lslash",                            278, NULL },
+  { "quotedblleft",                      500, NULL },
+  { "guilsinglright",                    333, NULL },
+  { "hyphen",                            333, NULL },
+  { "quotesingle",                       238, NULL },
+  { "eight",                             556, NULL },
+  { "exclamdown",                        333, NULL },
+  { "endash",                            556, NULL },
+  { "oe",                                944, NULL },
+  { "Abreve",                            722, NULL },
+  { "Umacron",                           722, NULL },
+  { "ecircumflex",                       556, NULL },
+  { "Adieresis",                         722, NULL },
+  { "copyright",                         737, NULL },
+  { "Egrave",                            667, NULL },
+  { "slash",                             278, NULL },
+  { "Edieresis",                         667, NULL },
+  { "otilde",                            611, NULL },
+  { "Idieresis",                         278, NULL },
+  { "parenleft",                         333, NULL },
+  { "one",                               556, NULL },
+  { "emacron",                           556, NULL },
+  { "Odieresis",                         778, NULL },
+  { "ucircumflex",                       611, NULL },
+  { "bracketleft",                       333, NULL },
+  { "Ugrave",                            722, NULL },
+  { "quoteright",                        278, NULL },
+  { "Udieresis",                         722, NULL },
+  { "perthousand",                      1000, NULL },
+  { "Ydieresis",                         667, NULL },
+  { "umacron",                           611, NULL },
+  { "abreve",                            556, NULL },
+  { "Eacute",                            667, NULL },
+  { "adieresis",                         556, NULL },
+  { "egrave",                            556, NULL },
+  { "edieresis",                         556, NULL },
+  { "idieresis",                         278, NULL },
+  { "Eth",                               722, NULL },
+  { "ae",                                889, NULL },
+  { "asterisk",                          389, NULL },
+  { "odieresis",                         611, NULL },
+  { "Uacute",                            722, NULL },
+  { "ugrave",                            611, NULL },
+  { "nine",                              556, NULL },
+  { "five",                              556, NULL },
+  { "udieresis",                         611, NULL },
+  { "Zcaron",                            611, NULL },
+  { "Scommaaccent",                      667, NULL },
+  { "threequarters",                     834, NULL },
+  { "guillemotright",                    556, NULL },
+  { "Ccedilla",                          722, NULL },
+  { "ydieresis",                         556, NULL },
+  { "tilde",                             333, NULL },
+  { "dbldaggerumlaut",                   556, NULL },
+  { "at",                                975, NULL },
+  { "eacute",                            556, NULL },
+  { "underscore",                        556, NULL },
+  { "Euro",                              556, NULL },
+  { "Dcroat",                            722, NULL },
+  { "multiply",                          584, NULL },
+  { "zero",                              556, NULL },
+  { "eth",                               611, NULL },
+  { "Scedilla",                          667, NULL },
+  { "Ograve",                            778, NULL },
+  { "Racute",                            722, NULL },
+  { "partialdiff",                       494, NULL },
+  { "uacute",                            611, NULL },
+  { "braceleft",                         389, NULL },
+  { "Thorn",                             667, NULL },
+  { "zcaron",                            500, NULL },
+  { "scommaaccent",                      556, NULL },
+  { "ccedilla",                          556, NULL },
+  { "Dcaron",                            722, NULL },
+  { "dcroat",                            611, NULL },
+  { "Ocircumflex",                       778, NULL },
+  { "Oacute",                            778, NULL },
+  { "scedilla",                          556, NULL },
+  { "ogonek",                            333, NULL },
+  { "ograve",                            611, NULL },
+  { "racute",                            389, NULL },
+  { "Tcaron",                            611, NULL },
+  { "Eogonek",                           667, NULL },
+  { "thorn",                             611, NULL },
+  { "degree",                            400, NULL },
+  { "registered",                        737, NULL },
+  { "radical",                           549, NULL },
+  { "Aring",                             722, NULL },
+  { "percent",                           889, NULL },
+  { "six",                               556, NULL },
+  { "paragraph",                         556, NULL },
+  { "dcaron",                            743, NULL },
+  { "Uogonek",                           722, NULL },
+  { "two",                               556, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            278, NULL },
+  { "Lacute",                            611, NULL },
+  { "ocircumflex",                       611, NULL },
+  { "oacute",                            611, NULL },
+  { "Uring",                             722, NULL },
+  { "Lcommaaccent",                      611, NULL },
+  { "tcaron",                            389, NULL },
+  { "eogonek",                           556, NULL },
+  { "Delta",                             612, NULL },
+  { "Ohungarumlaut",                     778, NULL },
+  { "asciicircum",                       584, NULL },
+  { "aring",                             556, NULL },
+  { "grave",                             333, NULL },
+  { "uogonek",                           611, NULL },
+  { "bracketright",                      333, NULL },
+  { "Iacute",                            278, NULL },
+  { "ampersand",                         722, NULL },
+  { "igrave",                            278, NULL },
+  { "lacute",                            278, NULL },
+  { "Ncaron",                            722, NULL },
+  { "plus",                              584, NULL },
+  { "uring",                             611, NULL },
+  { "quotesinglbase",                    278, NULL },
+  { "lcommaaccent",                      278, NULL },
+  { "Yacute",                            667, NULL },
+  { "ohungarumlaut",                     611, NULL },
+  { "threesuperior",                     333, NULL },
+  { "acute",                             333, NULL },
+  { "section",                           556, NULL },
+  { "dieresis",                          333, NULL },
+  { "iacute",                            278, NULL },
+  { "quotedblbase",                      500, NULL },
+  { "ncaron",                            611, NULL },
+  { "florin",                            556, NULL },
+  { "yacute",                            556, NULL },
+  { "Rcommaaccent",                      722, NULL },
+  { "fi",                                611, NULL },
+  { "fl",                                611, NULL },
+  { "Acircumflex",                       722, NULL },
+  { "Cacute",                            722, NULL },
+  { "Icircumflex",                       278, NULL },
+  { "guillemotleft",                     556, NULL },
+  { "germandbls",                        611, NULL },
+  { "Amacron",                           722, NULL },
+  { "seven",                             556, NULL },
+  { "Sacute",                            667, NULL },
+  { "ordmasculine",                      365, NULL },
+  { "dotlessi",                          278, NULL },
+  { "sterling",                          556, NULL },
+  { "notequal",                          549, NULL },
+  { "Imacron",                           278, NULL },
+  { "rcommaaccent",                      389, NULL },
+  { "Zdotaccent",                        611, NULL },
+  { "acircumflex",                       556, NULL },
+  { "cacute",                            556, NULL },
+  { "Ecaron",                            667, NULL },
+  { "icircumflex",                       278, NULL },
+  { "braceright",                        389, NULL },
+  { "quotedblright",                     500, NULL },
+  { "amacron",                           556, NULL },
+  { "sacute",                            556, NULL },
+  { "imacron",                           278, NULL },
+  { "cent",                              556, NULL },
+  { "currency",                          556, NULL },
+  { "logicalnot",                        584, NULL },
+  { "zdotaccent",                        500, NULL },
+  { "Atilde",                            722, NULL },
+  { "breve",                             333, NULL },
+  { "bar",                               280, NULL },
+  { "fraction",                          167, NULL },
+  { "less",                              584, NULL },
+  { "ecaron",                            556, NULL },
+  { "guilsinglleft",                     333, NULL },
+  { "exclam",                            333, NULL },
+  { "period",                            278, NULL },
+  { "Rcaron",                            722, NULL },
+  { "Kcommaaccent",                      722, NULL },
+  { "greater",                           584, NULL },
+  { "atilde",                            556, NULL },
+  { "brokenbar",                         280, NULL },
+  { "quoteleft",                         278, NULL },
+  { "Edotaccent",                        667, NULL },
+  { "onesuperior",                       333, NULL }
+};
+
+static BuiltinFontWidth helveticaBoldObliqueWidthsTab[] = {
+  { "Ntilde",                            722, NULL },
+  { "rcaron",                            389, NULL },
+  { "kcommaaccent",                      556, NULL },
+  { "Ncommaaccent",                      722, NULL },
+  { "Zacute",                            611, NULL },
+  { "comma",                             278, NULL },
+  { "cedilla",                           333, NULL },
+  { "plusminus",                         584, NULL },
+  { "circumflex",                        333, NULL },
+  { "dotaccent",                         333, NULL },
+  { "edotaccent",                        556, NULL },
+  { "asciitilde",                        584, NULL },
+  { "colon",                             333, NULL },
+  { "onehalf",                           834, NULL },
+  { "dollar",                            556, NULL },
+  { "Lcaron",                            611, NULL },
+  { "ntilde",                            611, NULL },
+  { "Aogonek",                           722, NULL },
+  { "ncommaaccent",                      611, NULL },
+  { "minus",                             584, NULL },
+  { "Iogonek",                           278, NULL },
+  { "zacute",                            500, NULL },
+  { "yen",                               556, NULL },
+  { "space",                             278, NULL },
+  { "Omacron",                           778, NULL },
+  { "questiondown",                      611, NULL },
+  { "emdash",                           1000, NULL },
+  { "Agrave",                            722, NULL },
+  { "three",                             556, NULL },
+  { "numbersign",                        556, NULL },
+  { "lcaron",                            400, NULL },
+  { "A",                                 722, NULL },
+  { "B",                                 722, NULL },
+  { "C",                                 722, NULL },
+  { "aogonek",                           556, NULL },
+  { "D",                                 722, NULL },
+  { "E",                                 667, NULL },
+  { "onequarter",                        834, NULL },
+  { "F",                                 611, NULL },
+  { "G",                                 778, NULL },
+  { "H",                                 722, NULL },
+  { "I",                                 278, NULL },
+  { "J",                                 556, NULL },
+  { "K",                                 722, NULL },
+  { "iogonek",                           278, NULL },
+  { "backslash",                         278, NULL },
+  { "L",                                 611, NULL },
+  { "periodcentered",                    278, NULL },
+  { "M",                                 833, NULL },
+  { "N",                                 722, NULL },
+  { "omacron",                           611, NULL },
+  { "Tcommaaccent",                      611, NULL },
+  { "O",                                 778, NULL },
+  { "P",                                 667, NULL },
+  { "Q",                                 778, NULL },
+  { "Uhungarumlaut",                     722, NULL },
+  { "R",                                 722, NULL },
+  { "Aacute",                            722, NULL },
+  { "caron",                             333, NULL },
+  { "S",                                 667, NULL },
+  { "T",                                 611, NULL },
+  { "U",                                 722, NULL },
+  { "agrave",                            556, NULL },
+  { "V",                                 667, NULL },
+  { "W",                                 944, NULL },
+  { "X",                                 667, NULL },
+  { "question",                          611, NULL },
+  { "equal",                             584, NULL },
+  { "Y",                                 667, NULL },
+  { "Z",                                 611, NULL },
+  { "four",                              556, NULL },
+  { "a",                                 556, NULL },
+  { "Gcommaaccent",                      778, NULL },
+  { "b",                                 611, NULL },
+  { "c",                                 556, NULL },
+  { "d",                                 611, NULL },
+  { "e",                                 556, NULL },
+  { "f",                                 333, NULL },
+  { "g",                                 611, NULL },
+  { "bullet",                            350, NULL },
+  { "h",                                 611, NULL },
+  { "i",                                 278, NULL },
+  { "Oslash",                            778, NULL },
+  { "dagger",                            556, NULL },
+  { "j",                                 278, NULL },
+  { "k",                                 556, NULL },
+  { "l",                                 278, NULL },
+  { "m",                                 889, NULL },
+  { "n",                                 611, NULL },
+  { "tcommaaccent",                      333, NULL },
+  { "o",                                 611, NULL },
+  { "ordfeminine",                       370, NULL },
+  { "ring",                              333, NULL },
+  { "p",                                 611, NULL },
+  { "q",                                 611, NULL },
+  { "uhungarumlaut",                     611, NULL },
+  { "r",                                 389, NULL },
+  { "twosuperior",                       333, NULL },
+  { "aacute",                            556, NULL },
+  { "s",                                 556, NULL },
+  { "OE",                               1000, NULL },
+  { "t",                                 333, NULL },
+  { "divide",                            584, NULL },
+  { "u",                                 611, NULL },
+  { "Ccaron",                            722, NULL },
+  { "v",                                 556, NULL },
+  { "w",                                 778, NULL },
+  { "x",                                 556, NULL },
+  { "y",                                 556, NULL },
+  { "z",                                 500, NULL },
+  { "Gbreve",                            778, NULL },
+  { "commaaccent",                       250, NULL },
+  { "hungarumlaut",                      333, NULL },
+  { "Idotaccent",                        278, NULL },
+  { "Nacute",                            722, NULL },
+  { "quotedbl",                          474, NULL },
+  { "gcommaaccent",                      611, NULL },
+  { "mu",                                611, NULL },
+  { "greaterequal",                      549, NULL },
+  { "Scaron",                            667, NULL },
+  { "Lslash",                            611, NULL },
+  { "semicolon",                         333, NULL },
+  { "oslash",                            611, NULL },
+  { "lessequal",                         549, NULL },
+  { "lozenge",                           494, NULL },
+  { "parenright",                        333, NULL },
+  { "ccaron",                            556, NULL },
+  { "Ecircumflex",                       667, NULL },
+  { "gbreve",                            611, NULL },
+  { "trademark",                        1000, NULL },
+  { "daggerdbl",                         556, NULL },
+  { "nacute",                            611, NULL },
+  { "macron",                            333, NULL },
+  { "Otilde",                            778, NULL },
+  { "Emacron",                           667, NULL },
+  { "ellipsis",                         1000, NULL },
+  { "scaron",                            556, NULL },
+  { "AE",                               1000, NULL },
+  { "Ucircumflex",                       722, NULL },
+  { "lslash",                            278, NULL },
+  { "quotedblleft",                      500, NULL },
+  { "guilsinglright",                    333, NULL },
+  { "hyphen",                            333, NULL },
+  { "quotesingle",                       238, NULL },
+  { "eight",                             556, NULL },
+  { "exclamdown",                        333, NULL },
+  { "endash",                            556, NULL },
+  { "oe",                                944, NULL },
+  { "Abreve",                            722, NULL },
+  { "Umacron",                           722, NULL },
+  { "ecircumflex",                       556, NULL },
+  { "Adieresis",                         722, NULL },
+  { "copyright",                         737, NULL },
+  { "Egrave",                            667, NULL },
+  { "slash",                             278, NULL },
+  { "Edieresis",                         667, NULL },
+  { "otilde",                            611, NULL },
+  { "Idieresis",                         278, NULL },
+  { "parenleft",                         333, NULL },
+  { "one",                               556, NULL },
+  { "emacron",                           556, NULL },
+  { "Odieresis",                         778, NULL },
+  { "ucircumflex",                       611, NULL },
+  { "bracketleft",                       333, NULL },
+  { "Ugrave",                            722, NULL },
+  { "quoteright",                        278, NULL },
+  { "Udieresis",                         722, NULL },
+  { "perthousand",                      1000, NULL },
+  { "Ydieresis",                         667, NULL },
+  { "umacron",                           611, NULL },
+  { "abreve",                            556, NULL },
+  { "Eacute",                            667, NULL },
+  { "adieresis",                         556, NULL },
+  { "egrave",                            556, NULL },
+  { "edieresis",                         556, NULL },
+  { "idieresis",                         278, NULL },
+  { "Eth",                               722, NULL },
+  { "ae",                                889, NULL },
+  { "asterisk",                          389, NULL },
+  { "odieresis",                         611, NULL },
+  { "Uacute",                            722, NULL },
+  { "ugrave",                            611, NULL },
+  { "nine",                              556, NULL },
+  { "five",                              556, NULL },
+  { "udieresis",                         611, NULL },
+  { "Zcaron",                            611, NULL },
+  { "Scommaaccent",                      667, NULL },
+  { "threequarters",                     834, NULL },
+  { "guillemotright",                    556, NULL },
+  { "Ccedilla",                          722, NULL },
+  { "ydieresis",                         556, NULL },
+  { "tilde",                             333, NULL },
+  { "at",                                975, NULL },
+  { "eacute",                            556, NULL },
+  { "underscore",                        556, NULL },
+  { "Euro",                              556, NULL },
+  { "Dcroat",                            722, NULL },
+  { "multiply",                          584, NULL },
+  { "zero",                              556, NULL },
+  { "eth",                               611, NULL },
+  { "Scedilla",                          667, NULL },
+  { "Ograve",                            778, NULL },
+  { "Racute",                            722, NULL },
+  { "partialdiff",                       494, NULL },
+  { "uacute",                            611, NULL },
+  { "braceleft",                         389, NULL },
+  { "Thorn",                             667, NULL },
+  { "zcaron",                            500, NULL },
+  { "scommaaccent",                      556, NULL },
+  { "ccedilla",                          556, NULL },
+  { "Dcaron",                            722, NULL },
+  { "dcroat",                            611, NULL },
+  { "Ocircumflex",                       778, NULL },
+  { "Oacute",                            778, NULL },
+  { "scedilla",                          556, NULL },
+  { "ogonek",                            333, NULL },
+  { "ograve",                            611, NULL },
+  { "racute",                            389, NULL },
+  { "Tcaron",                            611, NULL },
+  { "Eogonek",                           667, NULL },
+  { "thorn",                             611, NULL },
+  { "degree",                            400, NULL },
+  { "registered",                        737, NULL },
+  { "radical",                           549, NULL },
+  { "Aring",                             722, NULL },
+  { "percent",                           889, NULL },
+  { "six",                               556, NULL },
+  { "paragraph",                         556, NULL },
+  { "dcaron",                            743, NULL },
+  { "Uogonek",                           722, NULL },
+  { "two",                               556, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            278, NULL },
+  { "Lacute",                            611, NULL },
+  { "ocircumflex",                       611, NULL },
+  { "oacute",                            611, NULL },
+  { "Uring",                             722, NULL },
+  { "Lcommaaccent",                      611, NULL },
+  { "tcaron",                            389, NULL },
+  { "eogonek",                           556, NULL },
+  { "Delta",                             612, NULL },
+  { "Ohungarumlaut",                     778, NULL },
+  { "asciicircum",                       584, NULL },
+  { "aring",                             556, NULL },
+  { "grave",                             333, NULL },
+  { "uogonek",                           611, NULL },
+  { "bracketright",                      333, NULL },
+  { "Iacute",                            278, NULL },
+  { "ampersand",                         722, NULL },
+  { "igrave",                            278, NULL },
+  { "lacute",                            278, NULL },
+  { "Ncaron",                            722, NULL },
+  { "plus",                              584, NULL },
+  { "uring",                             611, NULL },
+  { "quotesinglbase",                    278, NULL },
+  { "lcommaaccent",                      278, NULL },
+  { "Yacute",                            667, NULL },
+  { "ohungarumlaut",                     611, NULL },
+  { "threesuperior",                     333, NULL },
+  { "acute",                             333, NULL },
+  { "section",                           556, NULL },
+  { "dieresis",                          333, NULL },
+  { "iacute",                            278, NULL },
+  { "quotedblbase",                      500, NULL },
+  { "ncaron",                            611, NULL },
+  { "florin",                            556, NULL },
+  { "yacute",                            556, NULL },
+  { "Rcommaaccent",                      722, NULL },
+  { "fi",                                611, NULL },
+  { "fl",                                611, NULL },
+  { "Acircumflex",                       722, NULL },
+  { "Cacute",                            722, NULL },
+  { "Icircumflex",                       278, NULL },
+  { "guillemotleft",                     556, NULL },
+  { "germandbls",                        611, NULL },
+  { "Amacron",                           722, NULL },
+  { "seven",                             556, NULL },
+  { "Sacute",                            667, NULL },
+  { "ordmasculine",                      365, NULL },
+  { "dotlessi",                          278, NULL },
+  { "sterling",                          556, NULL },
+  { "notequal",                          549, NULL },
+  { "Imacron",                           278, NULL },
+  { "rcommaaccent",                      389, NULL },
+  { "Zdotaccent",                        611, NULL },
+  { "acircumflex",                       556, NULL },
+  { "cacute",                            556, NULL },
+  { "Ecaron",                            667, NULL },
+  { "icircumflex",                       278, NULL },
+  { "braceright",                        389, NULL },
+  { "quotedblright",                     500, NULL },
+  { "amacron",                           556, NULL },
+  { "sacute",                            556, NULL },
+  { "imacron",                           278, NULL },
+  { "cent",                              556, NULL },
+  { "currency",                          556, NULL },
+  { "logicalnot",                        584, NULL },
+  { "zdotaccent",                        500, NULL },
+  { "Atilde",                            722, NULL },
+  { "breve",                             333, NULL },
+  { "bar",                               280, NULL },
+  { "fraction",                          167, NULL },
+  { "less",                              584, NULL },
+  { "ecaron",                            556, NULL },
+  { "guilsinglleft",                     333, NULL },
+  { "exclam",                            333, NULL },
+  { "period",                            278, NULL },
+  { "Rcaron",                            722, NULL },
+  { "Kcommaaccent",                      722, NULL },
+  { "greater",                           584, NULL },
+  { "atilde",                            556, NULL },
+  { "brokenbar",                         280, NULL },
+  { "quoteleft",                         278, NULL },
+  { "Edotaccent",                        667, NULL },
+  { "onesuperior",                       333, NULL }
+};
+
+static BuiltinFontWidth helveticaObliqueWidthsTab[] = {
+  { "Ntilde",                            722, NULL },
+  { "rcaron",                            333, NULL },
+  { "kcommaaccent",                      500, NULL },
+  { "Ncommaaccent",                      722, NULL },
+  { "Zacute",                            611, NULL },
+  { "comma",                             278, NULL },
+  { "cedilla",                           333, NULL },
+  { "plusminus",                         584, NULL },
+  { "circumflex",                        333, NULL },
+  { "dotaccent",                         333, NULL },
+  { "edotaccent",                        556, NULL },
+  { "asciitilde",                        584, NULL },
+  { "colon",                             278, NULL },
+  { "onehalf",                           834, NULL },
+  { "dollar",                            556, NULL },
+  { "Lcaron",                            556, NULL },
+  { "ntilde",                            556, NULL },
+  { "Aogonek",                           667, NULL },
+  { "ncommaaccent",                      556, NULL },
+  { "minus",                             584, NULL },
+  { "Iogonek",                           278, NULL },
+  { "zacute",                            500, NULL },
+  { "yen",                               556, NULL },
+  { "space",                             278, NULL },
+  { "Omacron",                           778, NULL },
+  { "questiondown",                      611, NULL },
+  { "emdash",                           1000, NULL },
+  { "Agrave",                            667, NULL },
+  { "three",                             556, NULL },
+  { "numbersign",                        556, NULL },
+  { "lcaron",                            299, NULL },
+  { "A",                                 667, NULL },
+  { "B",                                 667, NULL },
+  { "C",                                 722, NULL },
+  { "aogonek",                           556, NULL },
+  { "D",                                 722, NULL },
+  { "E",                                 667, NULL },
+  { "onequarter",                        834, NULL },
+  { "F",                                 611, NULL },
+  { "G",                                 778, NULL },
+  { "H",                                 722, NULL },
+  { "I",                                 278, NULL },
+  { "J",                                 500, NULL },
+  { "K",                                 667, NULL },
+  { "iogonek",                           222, NULL },
+  { "backslash",                         278, NULL },
+  { "L",                                 556, NULL },
+  { "periodcentered",                    278, NULL },
+  { "M",                                 833, NULL },
+  { "N",                                 722, NULL },
+  { "omacron",                           556, NULL },
+  { "Tcommaaccent",                      611, NULL },
+  { "O",                                 778, NULL },
+  { "P",                                 667, NULL },
+  { "Q",                                 778, NULL },
+  { "Uhungarumlaut",                     722, NULL },
+  { "R",                                 722, NULL },
+  { "Aacute",                            667, NULL },
+  { "caron",                             333, NULL },
+  { "S",                                 667, NULL },
+  { "T",                                 611, NULL },
+  { "U",                                 722, NULL },
+  { "agrave",                            556, NULL },
+  { "V",                                 667, NULL },
+  { "W",                                 944, NULL },
+  { "X",                                 667, NULL },
+  { "question",                          556, NULL },
+  { "equal",                             584, NULL },
+  { "Y",                                 667, NULL },
+  { "Z",                                 611, NULL },
+  { "four",                              556, NULL },
+  { "a",                                 556, NULL },
+  { "Gcommaaccent",                      778, NULL },
+  { "b",                                 556, NULL },
+  { "c",                                 500, NULL },
+  { "d",                                 556, NULL },
+  { "e",                                 556, NULL },
+  { "f",                                 278, NULL },
+  { "g",                                 556, NULL },
+  { "bullet",                            350, NULL },
+  { "h",                                 556, NULL },
+  { "i",                                 222, NULL },
+  { "Oslash",                            778, NULL },
+  { "dagger",                            556, NULL },
+  { "j",                                 222, NULL },
+  { "k",                                 500, NULL },
+  { "l",                                 222, NULL },
+  { "m",                                 833, NULL },
+  { "n",                                 556, NULL },
+  { "tcommaaccent",                      278, NULL },
+  { "o",                                 556, NULL },
+  { "ordfeminine",                       370, NULL },
+  { "ring",                              333, NULL },
+  { "p",                                 556, NULL },
+  { "q",                                 556, NULL },
+  { "uhungarumlaut",                     556, NULL },
+  { "r",                                 333, NULL },
+  { "twosuperior",                       333, NULL },
+  { "aacute",                            556, NULL },
+  { "s",                                 500, NULL },
+  { "OE",                               1000, NULL },
+  { "t",                                 278, NULL },
+  { "divide",                            584, NULL },
+  { "u",                                 556, NULL },
+  { "Ccaron",                            722, NULL },
+  { "v",                                 500, NULL },
+  { "w",                                 722, NULL },
+  { "x",                                 500, NULL },
+  { "y",                                 500, NULL },
+  { "z",                                 500, NULL },
+  { "Gbreve",                            778, NULL },
+  { "commaaccent",                       250, NULL },
+  { "hungarumlaut",                      333, NULL },
+  { "Idotaccent",                        278, NULL },
+  { "Nacute",                            722, NULL },
+  { "quotedbl",                          355, NULL },
+  { "gcommaaccent",                      556, NULL },
+  { "mu",                                556, NULL },
+  { "greaterequal",                      549, NULL },
+  { "Scaron",                            667, NULL },
+  { "Lslash",                            556, NULL },
+  { "semicolon",                         278, NULL },
+  { "oslash",                            611, NULL },
+  { "lessequal",                         549, NULL },
+  { "lozenge",                           471, NULL },
+  { "parenright",                        333, NULL },
+  { "ccaron",                            500, NULL },
+  { "Ecircumflex",                       667, NULL },
+  { "gbreve",                            556, NULL },
+  { "trademark",                        1000, NULL },
+  { "daggerdbl",                         556, NULL },
+  { "nacute",                            556, NULL },
+  { "macron",                            333, NULL },
+  { "Otilde",                            778, NULL },
+  { "Emacron",                           667, NULL },
+  { "ellipsis",                         1000, NULL },
+  { "scaron",                            500, NULL },
+  { "AE",                               1000, NULL },
+  { "Ucircumflex",                       722, NULL },
+  { "lslash",                            222, NULL },
+  { "quotedblleft",                      333, NULL },
+  { "guilsinglright",                    333, NULL },
+  { "hyphen",                            333, NULL },
+  { "quotesingle",                       191, NULL },
+  { "eight",                             556, NULL },
+  { "exclamdown",                        333, NULL },
+  { "endash",                            556, NULL },
+  { "oe",                                944, NULL },
+  { "Abreve",                            667, NULL },
+  { "Umacron",                           722, NULL },
+  { "ecircumflex",                       556, NULL },
+  { "Adieresis",                         667, NULL },
+  { "copyright",                         737, NULL },
+  { "Egrave",                            667, NULL },
+  { "slash",                             278, NULL },
+  { "Edieresis",                         667, NULL },
+  { "otilde",                            556, NULL },
+  { "Idieresis",                         278, NULL },
+  { "parenleft",                         333, NULL },
+  { "one",                               556, NULL },
+  { "emacron",                           556, NULL },
+  { "Odieresis",                         778, NULL },
+  { "ucircumflex",                       556, NULL },
+  { "bracketleft",                       278, NULL },
+  { "Ugrave",                            722, NULL },
+  { "quoteright",                        222, NULL },
+  { "Udieresis",                         722, NULL },
+  { "perthousand",                      1000, NULL },
+  { "Ydieresis",                         667, NULL },
+  { "umacron",                           556, NULL },
+  { "abreve",                            556, NULL },
+  { "Eacute",                            667, NULL },
+  { "adieresis",                         556, NULL },
+  { "egrave",                            556, NULL },
+  { "edieresis",                         556, NULL },
+  { "idieresis",                         278, NULL },
+  { "Eth",                               722, NULL },
+  { "ae",                                889, NULL },
+  { "asterisk",                          389, NULL },
+  { "odieresis",                         556, NULL },
+  { "Uacute",                            722, NULL },
+  { "ugrave",                            556, NULL },
+  { "nine",                              556, NULL },
+  { "five",                              556, NULL },
+  { "udieresis",                         556, NULL },
+  { "Zcaron",                            611, NULL },
+  { "Scommaaccent",                      667, NULL },
+  { "threequarters",                     834, NULL },
+  { "guillemotright",                    556, NULL },
+  { "Ccedilla",                          722, NULL },
+  { "ydieresis",                         500, NULL },
+  { "tilde",                             333, NULL },
+  { "at",                               1015, NULL },
+  { "eacute",                            556, NULL },
+  { "underscore",                        556, NULL },
+  { "Euro",                              556, NULL },
+  { "Dcroat",                            722, NULL },
+  { "multiply",                          584, NULL },
+  { "zero",                              556, NULL },
+  { "eth",                               556, NULL },
+  { "Scedilla",                          667, NULL },
+  { "Ograve",                            778, NULL },
+  { "Racute",                            722, NULL },
+  { "partialdiff",                       476, NULL },
+  { "uacute",                            556, NULL },
+  { "braceleft",                         334, NULL },
+  { "Thorn",                             667, NULL },
+  { "zcaron",                            500, NULL },
+  { "scommaaccent",                      500, NULL },
+  { "ccedilla",                          500, NULL },
+  { "Dcaron",                            722, NULL },
+  { "dcroat",                            556, NULL },
+  { "Ocircumflex",                       778, NULL },
+  { "Oacute",                            778, NULL },
+  { "scedilla",                          500, NULL },
+  { "ogonek",                            333, NULL },
+  { "ograve",                            556, NULL },
+  { "racute",                            333, NULL },
+  { "Tcaron",                            611, NULL },
+  { "Eogonek",                           667, NULL },
+  { "thorn",                             556, NULL },
+  { "degree",                            400, NULL },
+  { "registered",                        737, NULL },
+  { "radical",                           453, NULL },
+  { "Aring",                             667, NULL },
+  { "percent",                           889, NULL },
+  { "six",                               556, NULL },
+  { "paragraph",                         537, NULL },
+  { "dcaron",                            643, NULL },
+  { "Uogonek",                           722, NULL },
+  { "two",                               556, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            278, NULL },
+  { "Lacute",                            556, NULL },
+  { "ocircumflex",                       556, NULL },
+  { "oacute",                            556, NULL },
+  { "Uring",                             722, NULL },
+  { "Lcommaaccent",                      556, NULL },
+  { "tcaron",                            317, NULL },
+  { "eogonek",                           556, NULL },
+  { "Delta",                             612, NULL },
+  { "Ohungarumlaut",                     778, NULL },
+  { "asciicircum",                       469, NULL },
+  { "aring",                             556, NULL },
+  { "grave",                             333, NULL },
+  { "uogonek",                           556, NULL },
+  { "bracketright",                      278, NULL },
+  { "Iacute",                            278, NULL },
+  { "ampersand",                         667, NULL },
+  { "igrave",                            278, NULL },
+  { "lacute",                            222, NULL },
+  { "Ncaron",                            722, NULL },
+  { "plus",                              584, NULL },
+  { "uring",                             556, NULL },
+  { "quotesinglbase",                    222, NULL },
+  { "lcommaaccent",                      222, NULL },
+  { "Yacute",                            667, NULL },
+  { "ohungarumlaut",                     556, NULL },
+  { "threesuperior",                     333, NULL },
+  { "acute",                             333, NULL },
+  { "section",                           556, NULL },
+  { "dieresis",                          333, NULL },
+  { "iacute",                            278, NULL },
+  { "quotedblbase",                      333, NULL },
+  { "ncaron",                            556, NULL },
+  { "florin",                            556, NULL },
+  { "yacute",                            500, NULL },
+  { "Rcommaaccent",                      722, NULL },
+  { "fi",                                500, NULL },
+  { "fl",                                500, NULL },
+  { "Acircumflex",                       667, NULL },
+  { "Cacute",                            722, NULL },
+  { "Icircumflex",                       278, NULL },
+  { "guillemotleft",                     556, NULL },
+  { "germandbls",                        611, NULL },
+  { "Amacron",                           667, NULL },
+  { "seven",                             556, NULL },
+  { "Sacute",                            667, NULL },
+  { "ordmasculine",                      365, NULL },
+  { "dotlessi",                          278, NULL },
+  { "sterling",                          556, NULL },
+  { "notequal",                          549, NULL },
+  { "Imacron",                           278, NULL },
+  { "rcommaaccent",                      333, NULL },
+  { "Zdotaccent",                        611, NULL },
+  { "acircumflex",                       556, NULL },
+  { "cacute",                            500, NULL },
+  { "Ecaron",                            667, NULL },
+  { "icircumflex",                       278, NULL },
+  { "braceright",                        334, NULL },
+  { "quotedblright",                     333, NULL },
+  { "amacron",                           556, NULL },
+  { "sacute",                            500, NULL },
+  { "imacron",                           278, NULL },
+  { "cent",                              556, NULL },
+  { "currency",                          556, NULL },
+  { "logicalnot",                        584, NULL },
+  { "zdotaccent",                        500, NULL },
+  { "Atilde",                            667, NULL },
+  { "breve",                             333, NULL },
+  { "bar",                               260, NULL },
+  { "fraction",                          167, NULL },
+  { "less",                              584, NULL },
+  { "ecaron",                            556, NULL },
+  { "guilsinglleft",                     333, NULL },
+  { "exclam",                            278, NULL },
+  { "period",                            278, NULL },
+  { "Rcaron",                            722, NULL },
+  { "Kcommaaccent",                      667, NULL },
+  { "greater",                           584, NULL },
+  { "atilde",                            556, NULL },
+  { "brokenbar",                         260, NULL },
+  { "quoteleft",                         222, NULL },
+  { "Edotaccent",                        667, NULL },
+  { "onesuperior",                       333, NULL }
+};
+
+static BuiltinFontWidth symbolWidthsTab[] = {
+  { "bracketleftex",                     384, NULL },
+  { "alpha",                             631, NULL },
+  { "union",                             768, NULL },
+  { "infinity",                          713, NULL },
+  { "comma",                             250, NULL },
+  { "copyrightsans",                     790, NULL },
+  { "plusminus",                         549, NULL },
+  { "arrowup",                           603, NULL },
+  { "apple",                             790, NULL },
+  { "parenleftbt",                       384, NULL },
+  { "notelement",                        713, NULL },
+  { "colon",                             278, NULL },
+  { "beta",                              549, NULL },
+  { "braceleftbt",                       494, NULL },
+  { "Lambda",                            686, NULL },
+  { "Phi",                               763, NULL },
+  { "minus",                             549, NULL },
+  { "space",                             250, NULL },
+  { "Sigma",                             592, NULL },
+  { "approxequal",                       549, NULL },
+  { "minute",                            247, NULL },
+  { "circleplus",                        768, NULL },
+  { "Omicron",                           722, NULL },
+  { "three",                             500, NULL },
+  { "numbersign",                        500, NULL },
+  { "lambda",                            549, NULL },
+  { "phi",                               521, NULL },
+  { "aleph",                             823, NULL },
+  { "Tau",                               611, NULL },
+  { "spade",                             753, NULL },
+  { "logicaland",                        603, NULL },
+  { "sigma",                             603, NULL },
+  { "propersuperset",                    713, NULL },
+  { "omicron",                           549, NULL },
+  { "question",                          444, NULL },
+  { "equal",                             549, NULL },
+  { "Epsilon",                           611, NULL },
+  { "emptyset",                          823, NULL },
+  { "diamond",                           753, NULL },
+  { "four",                              500, NULL },
+  { "Mu",                                889, NULL },
+  { "parenlefttp",                       384, NULL },
+  { "club",                              753, NULL },
+  { "bullet",                            460, NULL },
+  { "Omega",                             768, NULL },
+  { "tau",                               439, NULL },
+  { "Upsilon",                           690, NULL },
+  { "bracelefttp",                       494, NULL },
+  { "heart",                             753, NULL },
+  { "divide",                            549, NULL },
+  { "epsilon",                           439, NULL },
+  { "logicalor",                         603, NULL },
+  { "parenleftex",                       384, NULL },
+  { "greaterequal",                      549, NULL },
+  { "mu",                                576, NULL },
+  { "Nu",                                722, NULL },
+  { "therefore",                         863, NULL },
+  { "notsubset",                         713, NULL },
+  { "omega",                             686, NULL },
+  { "semicolon",                         278, NULL },
+  { "element",                           713, NULL },
+  { "upsilon",                           576, NULL },
+  { "existential",                       549, NULL },
+  { "integralbt",                        686, NULL },
+  { "lessequal",                         549, NULL },
+  { "phi1",                              603, NULL },
+  { "lozenge",                           494, NULL },
+  { "trademarkserif",                    890, NULL },
+  { "parenright",                        333, NULL },
+  { "reflexsuperset",                    713, NULL },
+  { "sigma1",                            439, NULL },
+  { "nu",                                521, NULL },
+  { "Gamma",                             603, NULL },
+  { "angleright",                        329, NULL },
+  { "ellipsis",                         1000, NULL },
+  { "Rho",                               556, NULL },
+  { "parenrightbt",                      384, NULL },
+  { "radicalex",                         500, NULL },
+  { "eight",                             500, NULL },
+  { "angleleft",                         329, NULL },
+  { "arrowdbldown",                      603, NULL },
+  { "congruent",                         549, NULL },
+  { "Theta",                             741, NULL },
+  { "intersection",                      768, NULL },
+  { "Pi",                                768, NULL },
+  { "slash",                             278, NULL },
+  { "registerserif",                     790, NULL },
+  { "parenleft",                         333, NULL },
+  { "one",                               500, NULL },
+  { "gamma",                             411, NULL },
+  { "bracketleft",                       333, NULL },
+  { "rho",                               549, NULL },
+  { "circlemultiply",                    768, NULL },
+  { "Chi",                               722, NULL },
+  { "theta",                             521, NULL },
+  { "pi",                                549, NULL },
+  { "integraltp",                        686, NULL },
+  { "Eta",                               722, NULL },
+  { "product",                           823, NULL },
+  { "nine",                              500, NULL },
+  { "five",                              500, NULL },
+  { "propersubset",                      713, NULL },
+  { "bracketrightbt",                    384, NULL },
+  { "trademarksans",                     786, NULL },
+  { "dotmath",                           250, NULL },
+  { "integralex",                        686, NULL },
+  { "chi",                               549, NULL },
+  { "parenrighttp",                      384, NULL },
+  { "eta",                               603, NULL },
+  { "underscore",                        500, NULL },
+  { "Euro",                              750, NULL },
+  { "multiply",                          549, NULL },
+  { "zero",                              500, NULL },
+  { "partialdiff",                       494, NULL },
+  { "angle",                             768, NULL },
+  { "arrowdblleft",                      987, NULL },
+  { "braceleft",                         480, NULL },
+  { "parenrightex",                      384, NULL },
+  { "Rfraktur",                          795, NULL },
+  { "Zeta",                              611, NULL },
+  { "braceex",                           494, NULL },
+  { "arrowdblup",                        603, NULL },
+  { "arrowdown",                         603, NULL },
+  { "Ifraktur",                          686, NULL },
+  { "degree",                            400, NULL },
+  { "Iota",                              333, NULL },
+  { "perpendicular",                     658, NULL },
+  { "radical",                           549, NULL },
+  { "asteriskmath",                      500, NULL },
+  { "percent",                           833, NULL },
+  { "zeta",                              494, NULL },
+  { "six",                               500, NULL },
+  { "two",                               500, NULL },
+  { "weierstrass",                       987, NULL },
+  { "summation",                         713, NULL },
+  { "bracketrighttp",                    384, NULL },
+  { "carriagereturn",                    658, NULL },
+  { "suchthat",                          439, NULL },
+  { "arrowvertex",                       603, NULL },
+  { "Delta",                             612, NULL },
+  { "iota",                              329, NULL },
+  { "arrowhorizex",                     1000, NULL },
+  { "bracketrightex",                    384, NULL },
+  { "bracketright",                      333, NULL },
+  { "ampersand",                         778, NULL },
+  { "plus",                              549, NULL },
+  { "proportional",                      713, NULL },
+  { "delta",                             494, NULL },
+  { "copyrightserif",                    790, NULL },
+  { "bracerightmid",                     494, NULL },
+  { "arrowleft",                         987, NULL },
+  { "second",                            411, NULL },
+  { "arrowdblboth",                     1042, NULL },
+  { "florin",                            500, NULL },
+  { "Psi",                               795, NULL },
+  { "bracerightbt",                      494, NULL },
+  { "bracketleftbt",                     384, NULL },
+  { "seven",                             500, NULL },
+  { "braceleftmid",                      494, NULL },
+  { "notequal",                          549, NULL },
+  { "psi",                               686, NULL },
+  { "equivalence",                       549, NULL },
+  { "universal",                         713, NULL },
+  { "arrowdblright",                     987, NULL },
+  { "braceright",                        480, NULL },
+  { "reflexsubset",                      713, NULL },
+  { "Xi",                                645, NULL },
+  { "theta1",                            631, NULL },
+  { "logicalnot",                        713, NULL },
+  { "Kappa",                             722, NULL },
+  { "similar",                           549, NULL },
+  { "bar",                               200, NULL },
+  { "fraction",                          167, NULL },
+  { "less",                              549, NULL },
+  { "registersans",                      790, NULL },
+  { "omega1",                            713, NULL },
+  { "exclam",                            333, NULL },
+  { "Upsilon1",                          620, NULL },
+  { "bracerighttp",                      494, NULL },
+  { "xi",                                493, NULL },
+  { "period",                            250, NULL },
+  { "Alpha",                             722, NULL },
+  { "arrowright",                        987, NULL },
+  { "greater",                           549, NULL },
+  { "bracketlefttp",                     384, NULL },
+  { "kappa",                             549, NULL },
+  { "gradient",                          713, NULL },
+  { "integral",                          274, NULL },
+  { "arrowboth",                        1042, NULL },
+  { "Beta",                              667, NULL }
+};
+
+static BuiltinFontWidth timesBoldWidthsTab[] = {
+  { "Ntilde",                            722, NULL },
+  { "rcaron",                            444, NULL },
+  { "kcommaaccent",                      556, NULL },
+  { "Ncommaaccent",                      722, NULL },
+  { "Zacute",                            667, NULL },
+  { "comma",                             250, NULL },
+  { "cedilla",                           333, NULL },
+  { "plusminus",                         570, NULL },
+  { "circumflex",                        333, NULL },
+  { "dotaccent",                         333, NULL },
+  { "edotaccent",                        444, NULL },
+  { "asciitilde",                        520, NULL },
+  { "colon",                             333, NULL },
+  { "onehalf",                           750, NULL },
+  { "dollar",                            500, NULL },
+  { "Lcaron",                            667, NULL },
+  { "ntilde",                            556, NULL },
+  { "Aogonek",                           722, NULL },
+  { "ncommaaccent",                      556, NULL },
+  { "minus",                             570, NULL },
+  { "Iogonek",                           389, NULL },
+  { "zacute",                            444, NULL },
+  { "yen",                               500, NULL },
+  { "space",                             250, NULL },
+  { "Omacron",                           778, NULL },
+  { "questiondown",                      500, NULL },
+  { "emdash",                           1000, NULL },
+  { "Agrave",                            722, NULL },
+  { "three",                             500, NULL },
+  { "numbersign",                        500, NULL },
+  { "lcaron",                            394, NULL },
+  { "A",                                 722, NULL },
+  { "B",                                 667, NULL },
+  { "C",                                 722, NULL },
+  { "aogonek",                           500, NULL },
+  { "D",                                 722, NULL },
+  { "E",                                 667, NULL },
+  { "onequarter",                        750, NULL },
+  { "F",                                 611, NULL },
+  { "G",                                 778, NULL },
+  { "H",                                 778, NULL },
+  { "I",                                 389, NULL },
+  { "J",                                 500, NULL },
+  { "K",                                 778, NULL },
+  { "iogonek",                           278, NULL },
+  { "backslash",                         278, NULL },
+  { "L",                                 667, NULL },
+  { "periodcentered",                    250, NULL },
+  { "M",                                 944, NULL },
+  { "N",                                 722, NULL },
+  { "omacron",                           500, NULL },
+  { "Tcommaaccent",                      667, NULL },
+  { "O",                                 778, NULL },
+  { "P",                                 611, NULL },
+  { "Q",                                 778, NULL },
+  { "Uhungarumlaut",                     722, NULL },
+  { "R",                                 722, NULL },
+  { "Aacute",                            722, NULL },
+  { "caron",                             333, NULL },
+  { "S",                                 556, NULL },
+  { "T",                                 667, NULL },
+  { "U",                                 722, NULL },
+  { "agrave",                            500, NULL },
+  { "V",                                 722, NULL },
+  { "W",                                1000, NULL },
+  { "X",                                 722, NULL },
+  { "question",                          500, NULL },
+  { "equal",                             570, NULL },
+  { "Y",                                 722, NULL },
+  { "Z",                                 667, NULL },
+  { "four",                              500, NULL },
+  { "a",                                 500, NULL },
+  { "Gcommaaccent",                      778, NULL },
+  { "b",                                 556, NULL },
+  { "c",                                 444, NULL },
+  { "d",                                 556, NULL },
+  { "e",                                 444, NULL },
+  { "f",                                 333, NULL },
+  { "g",                                 500, NULL },
+  { "bullet",                            350, NULL },
+  { "h",                                 556, NULL },
+  { "i",                                 278, NULL },
+  { "Oslash",                            778, NULL },
+  { "dagger",                            500, NULL },
+  { "j",                                 333, NULL },
+  { "k",                                 556, NULL },
+  { "l",                                 278, NULL },
+  { "m",                                 833, NULL },
+  { "n",                                 556, NULL },
+  { "tcommaaccent",                      333, NULL },
+  { "o",                                 500, NULL },
+  { "ordfeminine",                       300, NULL },
+  { "ring",                              333, NULL },
+  { "p",                                 556, NULL },
+  { "q",                                 556, NULL },
+  { "uhungarumlaut",                     556, NULL },
+  { "r",                                 444, NULL },
+  { "twosuperior",                       300, NULL },
+  { "aacute",                            500, NULL },
+  { "s",                                 389, NULL },
+  { "OE",                               1000, NULL },
+  { "t",                                 333, NULL },
+  { "divide",                            570, NULL },
+  { "u",                                 556, NULL },
+  { "Ccaron",                            722, NULL },
+  { "v",                                 500, NULL },
+  { "w",                                 722, NULL },
+  { "x",                                 500, NULL },
+  { "y",                                 500, NULL },
+  { "z",                                 444, NULL },
+  { "Gbreve",                            778, NULL },
+  { "commaaccent",                       250, NULL },
+  { "hungarumlaut",                      333, NULL },
+  { "Idotaccent",                        389, NULL },
+  { "Nacute",                            722, NULL },
+  { "quotedbl",                          555, NULL },
+  { "gcommaaccent",                      500, NULL },
+  { "mu",                                556, NULL },
+  { "greaterequal",                      549, NULL },
+  { "Scaron",                            556, NULL },
+  { "Lslash",                            667, NULL },
+  { "semicolon",                         333, NULL },
+  { "oslash",                            500, NULL },
+  { "lessequal",                         549, NULL },
+  { "lozenge",                           494, NULL },
+  { "parenright",                        333, NULL },
+  { "ccaron",                            444, NULL },
+  { "Ecircumflex",                       667, NULL },
+  { "gbreve",                            500, NULL },
+  { "trademark",                        1000, NULL },
+  { "daggerdbl",                         500, NULL },
+  { "nacute",                            556, NULL },
+  { "macron",                            333, NULL },
+  { "Otilde",                            778, NULL },
+  { "Emacron",                           667, NULL },
+  { "ellipsis",                         1000, NULL },
+  { "scaron",                            389, NULL },
+  { "AE",                               1000, NULL },
+  { "Ucircumflex",                       722, NULL },
+  { "lslash",                            278, NULL },
+  { "quotedblleft",                      500, NULL },
+  { "guilsinglright",                    333, NULL },
+  { "hyphen",                            333, NULL },
+  { "quotesingle",                       278, NULL },
+  { "eight",                             500, NULL },
+  { "exclamdown",                        333, NULL },
+  { "endash",                            500, NULL },
+  { "oe",                                722, NULL },
+  { "Abreve",                            722, NULL },
+  { "Umacron",                           722, NULL },
+  { "ecircumflex",                       444, NULL },
+  { "Adieresis",                         722, NULL },
+  { "copyright",                         747, NULL },
+  { "Egrave",                            667, NULL },
+  { "slash",                             278, NULL },
+  { "Edieresis",                         667, NULL },
+  { "otilde",                            500, NULL },
+  { "Idieresis",                         389, NULL },
+  { "parenleft",                         333, NULL },
+  { "one",                               500, NULL },
+  { "emacron",                           444, NULL },
+  { "Odieresis",                         778, NULL },
+  { "ucircumflex",                       556, NULL },
+  { "bracketleft",                       333, NULL },
+  { "Ugrave",                            722, NULL },
+  { "quoteright",                        333, NULL },
+  { "Udieresis",                         722, NULL },
+  { "perthousand",                      1000, NULL },
+  { "Ydieresis",                         722, NULL },
+  { "umacron",                           556, NULL },
+  { "abreve",                            500, NULL },
+  { "Eacute",                            667, NULL },
+  { "adieresis",                         500, NULL },
+  { "egrave",                            444, NULL },
+  { "edieresis",                         444, NULL },
+  { "idieresis",                         278, NULL },
+  { "Eth",                               722, NULL },
+  { "ae",                                722, NULL },
+  { "asterisk",                          500, NULL },
+  { "odieresis",                         500, NULL },
+  { "Uacute",                            722, NULL },
+  { "ugrave",                            556, NULL },
+  { "nine",                              500, NULL },
+  { "five",                              500, NULL },
+  { "udieresis",                         556, NULL },
+  { "Zcaron",                            667, NULL },
+  { "Scommaaccent",                      556, NULL },
+  { "threequarters",                     750, NULL },
+  { "guillemotright",                    500, NULL },
+  { "Ccedilla",                          722, NULL },
+  { "ydieresis",                         500, NULL },
+  { "tilde",                             333, NULL },
+  { "at",                                930, NULL },
+  { "eacute",                            444, NULL },
+  { "underscore",                        500, NULL },
+  { "Euro",                              500, NULL },
+  { "Dcroat",                            722, NULL },
+  { "multiply",                          570, NULL },
+  { "zero",                              500, NULL },
+  { "eth",                               500, NULL },
+  { "Scedilla",                          556, NULL },
+  { "Ograve",                            778, NULL },
+  { "Racute",                            722, NULL },
+  { "partialdiff",                       494, NULL },
+  { "uacute",                            556, NULL },
+  { "braceleft",                         394, NULL },
+  { "Thorn",                             611, NULL },
+  { "zcaron",                            444, NULL },
+  { "scommaaccent",                      389, NULL },
+  { "ccedilla",                          444, NULL },
+  { "Dcaron",                            722, NULL },
+  { "dcroat",                            556, NULL },
+  { "Ocircumflex",                       778, NULL },
+  { "Oacute",                            778, NULL },
+  { "scedilla",                          389, NULL },
+  { "ogonek",                            333, NULL },
+  { "ograve",                            500, NULL },
+  { "racute",                            444, NULL },
+  { "Tcaron",                            667, NULL },
+  { "Eogonek",                           667, NULL },
+  { "thorn",                             556, NULL },
+  { "degree",                            400, NULL },
+  { "registered",                        747, NULL },
+  { "radical",                           549, NULL },
+  { "Aring",                             722, NULL },
+  { "percent",                          1000, NULL },
+  { "six",                               500, NULL },
+  { "paragraph",                         540, NULL },
+  { "dcaron",                            672, NULL },
+  { "Uogonek",                           722, NULL },
+  { "two",                               500, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            389, NULL },
+  { "Lacute",                            667, NULL },
+  { "ocircumflex",                       500, NULL },
+  { "oacute",                            500, NULL },
+  { "Uring",                             722, NULL },
+  { "Lcommaaccent",                      667, NULL },
+  { "tcaron",                            416, NULL },
+  { "eogonek",                           444, NULL },
+  { "Delta",                             612, NULL },
+  { "Ohungarumlaut",                     778, NULL },
+  { "asciicircum",                       581, NULL },
+  { "aring",                             500, NULL },
+  { "grave",                             333, NULL },
+  { "uogonek",                           556, NULL },
+  { "bracketright",                      333, NULL },
+  { "Iacute",                            389, NULL },
+  { "ampersand",                         833, NULL },
+  { "igrave",                            278, NULL },
+  { "lacute",                            278, NULL },
+  { "Ncaron",                            722, NULL },
+  { "plus",                              570, NULL },
+  { "uring",                             556, NULL },
+  { "quotesinglbase",                    333, NULL },
+  { "lcommaaccent",                      278, NULL },
+  { "Yacute",                            722, NULL },
+  { "ohungarumlaut",                     500, NULL },
+  { "threesuperior",                     300, NULL },
+  { "acute",                             333, NULL },
+  { "section",                           500, NULL },
+  { "dieresis",                          333, NULL },
+  { "iacute",                            278, NULL },
+  { "quotedblbase",                      500, NULL },
+  { "ncaron",                            556, NULL },
+  { "florin",                            500, NULL },
+  { "yacute",                            500, NULL },
+  { "Rcommaaccent",                      722, NULL },
+  { "fi",                                556, NULL },
+  { "fl",                                556, NULL },
+  { "Acircumflex",                       722, NULL },
+  { "Cacute",                            722, NULL },
+  { "Icircumflex",                       389, NULL },
+  { "guillemotleft",                     500, NULL },
+  { "germandbls",                        556, NULL },
+  { "Amacron",                           722, NULL },
+  { "seven",                             500, NULL },
+  { "Sacute",                            556, NULL },
+  { "ordmasculine",                      330, NULL },
+  { "dotlessi",                          278, NULL },
+  { "sterling",                          500, NULL },
+  { "notequal",                          549, NULL },
+  { "Imacron",                           389, NULL },
+  { "rcommaaccent",                      444, NULL },
+  { "Zdotaccent",                        667, NULL },
+  { "acircumflex",                       500, NULL },
+  { "cacute",                            444, NULL },
+  { "Ecaron",                            667, NULL },
+  { "icircumflex",                       278, NULL },
+  { "braceright",                        394, NULL },
+  { "quotedblright",                     500, NULL },
+  { "amacron",                           500, NULL },
+  { "sacute",                            389, NULL },
+  { "imacron",                           278, NULL },
+  { "cent",                              500, NULL },
+  { "currency",                          500, NULL },
+  { "logicalnot",                        570, NULL },
+  { "zdotaccent",                        444, NULL },
+  { "Atilde",                            722, NULL },
+  { "breve",                             333, NULL },
+  { "bar",                               220, NULL },
+  { "fraction",                          167, NULL },
+  { "less",                              570, NULL },
+  { "ecaron",                            444, NULL },
+  { "guilsinglleft",                     333, NULL },
+  { "exclam",                            333, NULL },
+  { "period",                            250, NULL },
+  { "Rcaron",                            722, NULL },
+  { "Kcommaaccent",                      778, NULL },
+  { "greater",                           570, NULL },
+  { "atilde",                            500, NULL },
+  { "brokenbar",                         220, NULL },
+  { "quoteleft",                         333, NULL },
+  { "Edotaccent",                        667, NULL },
+  { "onesuperior",                       300, NULL }
+};
+
+static BuiltinFontWidth timesBoldItalicWidthsTab[] = {
+  { "Ntilde",                            722, NULL },
+  { "rcaron",                            389, NULL },
+  { "kcommaaccent",                      500, NULL },
+  { "Ncommaaccent",                      722, NULL },
+  { "Zacute",                            611, NULL },
+  { "comma",                             250, NULL },
+  { "cedilla",                           333, NULL },
+  { "plusminus",                         570, NULL },
+  { "circumflex",                        333, NULL },
+  { "dotaccent",                         333, NULL },
+  { "edotaccent",                        444, NULL },
+  { "asciitilde",                        570, NULL },
+  { "colon",                             333, NULL },
+  { "onehalf",                           750, NULL },
+  { "dollar",                            500, NULL },
+  { "Lcaron",                            611, NULL },
+  { "ntilde",                            556, NULL },
+  { "Aogonek",                           667, NULL },
+  { "ncommaaccent",                      556, NULL },
+  { "minus",                             606, NULL },
+  { "Iogonek",                           389, NULL },
+  { "zacute",                            389, NULL },
+  { "yen",                               500, NULL },
+  { "space",                             250, NULL },
+  { "Omacron",                           722, NULL },
+  { "questiondown",                      500, NULL },
+  { "emdash",                           1000, NULL },
+  { "Agrave",                            667, NULL },
+  { "three",                             500, NULL },
+  { "numbersign",                        500, NULL },
+  { "lcaron",                            382, NULL },
+  { "A",                                 667, NULL },
+  { "B",                                 667, NULL },
+  { "C",                                 667, NULL },
+  { "aogonek",                           500, NULL },
+  { "D",                                 722, NULL },
+  { "E",                                 667, NULL },
+  { "onequarter",                        750, NULL },
+  { "F",                                 667, NULL },
+  { "G",                                 722, NULL },
+  { "H",                                 778, NULL },
+  { "I",                                 389, NULL },
+  { "J",                                 500, NULL },
+  { "K",                                 667, NULL },
+  { "iogonek",                           278, NULL },
+  { "backslash",                         278, NULL },
+  { "L",                                 611, NULL },
+  { "periodcentered",                    250, NULL },
+  { "M",                                 889, NULL },
+  { "N",                                 722, NULL },
+  { "omacron",                           500, NULL },
+  { "Tcommaaccent",                      611, NULL },
+  { "O",                                 722, NULL },
+  { "P",                                 611, NULL },
+  { "Q",                                 722, NULL },
+  { "Uhungarumlaut",                     722, NULL },
+  { "R",                                 667, NULL },
+  { "Aacute",                            667, NULL },
+  { "caron",                             333, NULL },
+  { "S",                                 556, NULL },
+  { "T",                                 611, NULL },
+  { "U",                                 722, NULL },
+  { "agrave",                            500, NULL },
+  { "V",                                 667, NULL },
+  { "W",                                 889, NULL },
+  { "X",                                 667, NULL },
+  { "question",                          500, NULL },
+  { "equal",                             570, NULL },
+  { "Y",                                 611, NULL },
+  { "Z",                                 611, NULL },
+  { "four",                              500, NULL },
+  { "a",                                 500, NULL },
+  { "Gcommaaccent",                      722, NULL },
+  { "b",                                 500, NULL },
+  { "c",                                 444, NULL },
+  { "d",                                 500, NULL },
+  { "e",                                 444, NULL },
+  { "f",                                 333, NULL },
+  { "g",                                 500, NULL },
+  { "bullet",                            350, NULL },
+  { "h",                                 556, NULL },
+  { "i",                                 278, NULL },
+  { "Oslash",                            722, NULL },
+  { "dagger",                            500, NULL },
+  { "j",                                 278, NULL },
+  { "k",                                 500, NULL },
+  { "l",                                 278, NULL },
+  { "m",                                 778, NULL },
+  { "n",                                 556, NULL },
+  { "tcommaaccent",                      278, NULL },
+  { "o",                                 500, NULL },
+  { "ordfeminine",                       266, NULL },
+  { "ring",                              333, NULL },
+  { "p",                                 500, NULL },
+  { "q",                                 500, NULL },
+  { "uhungarumlaut",                     556, NULL },
+  { "r",                                 389, NULL },
+  { "twosuperior",                       300, NULL },
+  { "aacute",                            500, NULL },
+  { "s",                                 389, NULL },
+  { "OE",                                944, NULL },
+  { "t",                                 278, NULL },
+  { "divide",                            570, NULL },
+  { "u",                                 556, NULL },
+  { "Ccaron",                            667, NULL },
+  { "v",                                 444, NULL },
+  { "w",                                 667, NULL },
+  { "x",                                 500, NULL },
+  { "y",                                 444, NULL },
+  { "z",                                 389, NULL },
+  { "Gbreve",                            722, NULL },
+  { "commaaccent",                       250, NULL },
+  { "hungarumlaut",                      333, NULL },
+  { "Idotaccent",                        389, NULL },
+  { "Nacute",                            722, NULL },
+  { "quotedbl",                          555, NULL },
+  { "gcommaaccent",                      500, NULL },
+  { "mu",                                576, NULL },
+  { "greaterequal",                      549, NULL },
+  { "Scaron",                            556, NULL },
+  { "Lslash",                            611, NULL },
+  { "semicolon",                         333, NULL },
+  { "oslash",                            500, NULL },
+  { "lessequal",                         549, NULL },
+  { "lozenge",                           494, NULL },
+  { "parenright",                        333, NULL },
+  { "ccaron",                            444, NULL },
+  { "Ecircumflex",                       667, NULL },
+  { "gbreve",                            500, NULL },
+  { "trademark",                        1000, NULL },
+  { "daggerdbl",                         500, NULL },
+  { "nacute",                            556, NULL },
+  { "macron",                            333, NULL },
+  { "Otilde",                            722, NULL },
+  { "Emacron",                           667, NULL },
+  { "ellipsis",                         1000, NULL },
+  { "scaron",                            389, NULL },
+  { "AE",                                944, NULL },
+  { "Ucircumflex",                       722, NULL },
+  { "lslash",                            278, NULL },
+  { "quotedblleft",                      500, NULL },
+  { "guilsinglright",                    333, NULL },
+  { "hyphen",                            333, NULL },
+  { "quotesingle",                       278, NULL },
+  { "eight",                             500, NULL },
+  { "exclamdown",                        389, NULL },
+  { "endash",                            500, NULL },
+  { "oe",                                722, NULL },
+  { "Abreve",                            667, NULL },
+  { "Umacron",                           722, NULL },
+  { "ecircumflex",                       444, NULL },
+  { "Adieresis",                         667, NULL },
+  { "copyright",                         747, NULL },
+  { "Egrave",                            667, NULL },
+  { "slash",                             278, NULL },
+  { "Edieresis",                         667, NULL },
+  { "otilde",                            500, NULL },
+  { "Idieresis",                         389, NULL },
+  { "parenleft",                         333, NULL },
+  { "one",                               500, NULL },
+  { "emacron",                           444, NULL },
+  { "Odieresis",                         722, NULL },
+  { "ucircumflex",                       556, NULL },
+  { "bracketleft",                       333, NULL },
+  { "Ugrave",                            722, NULL },
+  { "quoteright",                        333, NULL },
+  { "Udieresis",                         722, NULL },
+  { "perthousand",                      1000, NULL },
+  { "Ydieresis",                         611, NULL },
+  { "umacron",                           556, NULL },
+  { "abreve",                            500, NULL },
+  { "Eacute",                            667, NULL },
+  { "adieresis",                         500, NULL },
+  { "egrave",                            444, NULL },
+  { "edieresis",                         444, NULL },
+  { "idieresis",                         278, NULL },
+  { "Eth",                               722, NULL },
+  { "ae",                                722, NULL },
+  { "asterisk",                          500, NULL },
+  { "odieresis",                         500, NULL },
+  { "Uacute",                            722, NULL },
+  { "ugrave",                            556, NULL },
+  { "nine",                              500, NULL },
+  { "five",                              500, NULL },
+  { "udieresis",                         556, NULL },
+  { "Zcaron",                            611, NULL },
+  { "Scommaaccent",                      556, NULL },
+  { "threequarters",                     750, NULL },
+  { "guillemotright",                    500, NULL },
+  { "Ccedilla",                          667, NULL },
+  { "ydieresis",                         444, NULL },
+  { "tilde",                             333, NULL },
+  { "at",                                832, NULL },
+  { "eacute",                            444, NULL },
+  { "underscore",                        500, NULL },
+  { "Euro",                              500, NULL },
+  { "Dcroat",                            722, NULL },
+  { "multiply",                          570, NULL },
+  { "zero",                              500, NULL },
+  { "eth",                               500, NULL },
+  { "Scedilla",                          556, NULL },
+  { "Ograve",                            722, NULL },
+  { "Racute",                            667, NULL },
+  { "partialdiff",                       494, NULL },
+  { "uacute",                            556, NULL },
+  { "braceleft",                         348, NULL },
+  { "Thorn",                             611, NULL },
+  { "zcaron",                            389, NULL },
+  { "scommaaccent",                      389, NULL },
+  { "ccedilla",                          444, NULL },
+  { "Dcaron",                            722, NULL },
+  { "dcroat",                            500, NULL },
+  { "Ocircumflex",                       722, NULL },
+  { "Oacute",                            722, NULL },
+  { "scedilla",                          389, NULL },
+  { "ogonek",                            333, NULL },
+  { "ograve",                            500, NULL },
+  { "racute",                            389, NULL },
+  { "Tcaron",                            611, NULL },
+  { "Eogonek",                           667, NULL },
+  { "thorn",                             500, NULL },
+  { "degree",                            400, NULL },
+  { "registered",                        747, NULL },
+  { "radical",                           549, NULL },
+  { "Aring",                             667, NULL },
+  { "percent",                           833, NULL },
+  { "six",                               500, NULL },
+  { "paragraph",                         500, NULL },
+  { "dcaron",                            608, NULL },
+  { "Uogonek",                           722, NULL },
+  { "two",                               500, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            389, NULL },
+  { "Lacute",                            611, NULL },
+  { "ocircumflex",                       500, NULL },
+  { "oacute",                            500, NULL },
+  { "Uring",                             722, NULL },
+  { "Lcommaaccent",                      611, NULL },
+  { "tcaron",                            366, NULL },
+  { "eogonek",                           444, NULL },
+  { "Delta",                             612, NULL },
+  { "Ohungarumlaut",                     722, NULL },
+  { "asciicircum",                       570, NULL },
+  { "aring",                             500, NULL },
+  { "grave",                             333, NULL },
+  { "uogonek",                           556, NULL },
+  { "bracketright",                      333, NULL },
+  { "Iacute",                            389, NULL },
+  { "ampersand",                         778, NULL },
+  { "igrave",                            278, NULL },
+  { "lacute",                            278, NULL },
+  { "Ncaron",                            722, NULL },
+  { "plus",                              570, NULL },
+  { "uring",                             556, NULL },
+  { "quotesinglbase",                    333, NULL },
+  { "lcommaaccent",                      278, NULL },
+  { "Yacute",                            611, NULL },
+  { "ohungarumlaut",                     500, NULL },
+  { "threesuperior",                     300, NULL },
+  { "acute",                             333, NULL },
+  { "section",                           500, NULL },
+  { "dieresis",                          333, NULL },
+  { "iacute",                            278, NULL },
+  { "quotedblbase",                      500, NULL },
+  { "ncaron",                            556, NULL },
+  { "florin",                            500, NULL },
+  { "yacute",                            444, NULL },
+  { "Rcommaaccent",                      667, NULL },
+  { "fi",                                556, NULL },
+  { "fl",                                556, NULL },
+  { "Acircumflex",                       667, NULL },
+  { "Cacute",                            667, NULL },
+  { "Icircumflex",                       389, NULL },
+  { "guillemotleft",                     500, NULL },
+  { "germandbls",                        500, NULL },
+  { "Amacron",                           667, NULL },
+  { "seven",                             500, NULL },
+  { "Sacute",                            556, NULL },
+  { "ordmasculine",                      300, NULL },
+  { "dotlessi",                          278, NULL },
+  { "sterling",                          500, NULL },
+  { "notequal",                          549, NULL },
+  { "Imacron",                           389, NULL },
+  { "rcommaaccent",                      389, NULL },
+  { "Zdotaccent",                        611, NULL },
+  { "acircumflex",                       500, NULL },
+  { "cacute",                            444, NULL },
+  { "Ecaron",                            667, NULL },
+  { "icircumflex",                       278, NULL },
+  { "braceright",                        348, NULL },
+  { "quotedblright",                     500, NULL },
+  { "amacron",                           500, NULL },
+  { "sacute",                            389, NULL },
+  { "imacron",                           278, NULL },
+  { "cent",                              500, NULL },
+  { "currency",                          500, NULL },
+  { "logicalnot",                        606, NULL },
+  { "zdotaccent",                        389, NULL },
+  { "Atilde",                            667, NULL },
+  { "breve",                             333, NULL },
+  { "bar",                               220, NULL },
+  { "fraction",                          167, NULL },
+  { "less",                              570, NULL },
+  { "ecaron",                            444, NULL },
+  { "guilsinglleft",                     333, NULL },
+  { "exclam",                            389, NULL },
+  { "period",                            250, NULL },
+  { "Rcaron",                            667, NULL },
+  { "Kcommaaccent",                      667, NULL },
+  { "greater",                           570, NULL },
+  { "atilde",                            500, NULL },
+  { "brokenbar",                         220, NULL },
+  { "quoteleft",                         333, NULL },
+  { "Edotaccent",                        667, NULL },
+  { "onesuperior",                       300, NULL }
+};
+
+static BuiltinFontWidth timesItalicWidthsTab[] = {
+  { "Ntilde",                            667, NULL },
+  { "rcaron",                            389, NULL },
+  { "kcommaaccent",                      444, NULL },
+  { "Ncommaaccent",                      667, NULL },
+  { "Zacute",                            556, NULL },
+  { "comma",                             250, NULL },
+  { "cedilla",                           333, NULL },
+  { "plusminus",                         675, NULL },
+  { "circumflex",                        333, NULL },
+  { "dotaccent",                         333, NULL },
+  { "edotaccent",                        444, NULL },
+  { "asciitilde",                        541, NULL },
+  { "colon",                             333, NULL },
+  { "onehalf",                           750, NULL },
+  { "dollar",                            500, NULL },
+  { "Lcaron",                            611, NULL },
+  { "ntilde",                            500, NULL },
+  { "Aogonek",                           611, NULL },
+  { "ncommaaccent",                      500, NULL },
+  { "minus",                             675, NULL },
+  { "Iogonek",                           333, NULL },
+  { "zacute",                            389, NULL },
+  { "yen",                               500, NULL },
+  { "space",                             250, NULL },
+  { "Omacron",                           722, NULL },
+  { "questiondown",                      500, NULL },
+  { "emdash",                            889, NULL },
+  { "Agrave",                            611, NULL },
+  { "three",                             500, NULL },
+  { "numbersign",                        500, NULL },
+  { "lcaron",                            300, NULL },
+  { "A",                                 611, NULL },
+  { "B",                                 611, NULL },
+  { "C",                                 667, NULL },
+  { "aogonek",                           500, NULL },
+  { "D",                                 722, NULL },
+  { "E",                                 611, NULL },
+  { "onequarter",                        750, NULL },
+  { "F",                                 611, NULL },
+  { "G",                                 722, NULL },
+  { "H",                                 722, NULL },
+  { "I",                                 333, NULL },
+  { "J",                                 444, NULL },
+  { "K",                                 667, NULL },
+  { "iogonek",                           278, NULL },
+  { "backslash",                         278, NULL },
+  { "L",                                 556, NULL },
+  { "periodcentered",                    250, NULL },
+  { "M",                                 833, NULL },
+  { "N",                                 667, NULL },
+  { "omacron",                           500, NULL },
+  { "Tcommaaccent",                      556, NULL },
+  { "O",                                 722, NULL },
+  { "P",                                 611, NULL },
+  { "Q",                                 722, NULL },
+  { "Uhungarumlaut",                     722, NULL },
+  { "R",                                 611, NULL },
+  { "Aacute",                            611, NULL },
+  { "caron",                             333, NULL },
+  { "S",                                 500, NULL },
+  { "T",                                 556, NULL },
+  { "U",                                 722, NULL },
+  { "agrave",                            500, NULL },
+  { "V",                                 611, NULL },
+  { "W",                                 833, NULL },
+  { "X",                                 611, NULL },
+  { "question",                          500, NULL },
+  { "equal",                             675, NULL },
+  { "Y",                                 556, NULL },
+  { "Z",                                 556, NULL },
+  { "four",                              500, NULL },
+  { "a",                                 500, NULL },
+  { "Gcommaaccent",                      722, NULL },
+  { "b",                                 500, NULL },
+  { "c",                                 444, NULL },
+  { "d",                                 500, NULL },
+  { "e",                                 444, NULL },
+  { "f",                                 278, NULL },
+  { "g",                                 500, NULL },
+  { "bullet",                            350, NULL },
+  { "h",                                 500, NULL },
+  { "i",                                 278, NULL },
+  { "Oslash",                            722, NULL },
+  { "dagger",                            500, NULL },
+  { "j",                                 278, NULL },
+  { "k",                                 444, NULL },
+  { "l",                                 278, NULL },
+  { "m",                                 722, NULL },
+  { "n",                                 500, NULL },
+  { "tcommaaccent",                      278, NULL },
+  { "o",                                 500, NULL },
+  { "ordfeminine",                       276, NULL },
+  { "ring",                              333, NULL },
+  { "p",                                 500, NULL },
+  { "q",                                 500, NULL },
+  { "uhungarumlaut",                     500, NULL },
+  { "r",                                 389, NULL },
+  { "twosuperior",                       300, NULL },
+  { "aacute",                            500, NULL },
+  { "s",                                 389, NULL },
+  { "OE",                                944, NULL },
+  { "t",                                 278, NULL },
+  { "divide",                            675, NULL },
+  { "u",                                 500, NULL },
+  { "Ccaron",                            667, NULL },
+  { "v",                                 444, NULL },
+  { "w",                                 667, NULL },
+  { "x",                                 444, NULL },
+  { "y",                                 444, NULL },
+  { "z",                                 389, NULL },
+  { "Gbreve",                            722, NULL },
+  { "commaaccent",                       250, NULL },
+  { "hungarumlaut",                      333, NULL },
+  { "Idotaccent",                        333, NULL },
+  { "Nacute",                            667, NULL },
+  { "quotedbl",                          420, NULL },
+  { "gcommaaccent",                      500, NULL },
+  { "mu",                                500, NULL },
+  { "greaterequal",                      549, NULL },
+  { "Scaron",                            500, NULL },
+  { "Lslash",                            556, NULL },
+  { "semicolon",                         333, NULL },
+  { "oslash",                            500, NULL },
+  { "lessequal",                         549, NULL },
+  { "lozenge",                           471, NULL },
+  { "parenright",                        333, NULL },
+  { "ccaron",                            444, NULL },
+  { "Ecircumflex",                       611, NULL },
+  { "gbreve",                            500, NULL },
+  { "trademark",                         980, NULL },
+  { "daggerdbl",                         500, NULL },
+  { "nacute",                            500, NULL },
+  { "macron",                            333, NULL },
+  { "Otilde",                            722, NULL },
+  { "Emacron",                           611, NULL },
+  { "ellipsis",                          889, NULL },
+  { "scaron",                            389, NULL },
+  { "AE",                                889, NULL },
+  { "Ucircumflex",                       722, NULL },
+  { "lslash",                            278, NULL },
+  { "quotedblleft",                      556, NULL },
+  { "guilsinglright",                    333, NULL },
+  { "hyphen",                            333, NULL },
+  { "quotesingle",                       214, NULL },
+  { "eight",                             500, NULL },
+  { "exclamdown",                        389, NULL },
+  { "endash",                            500, NULL },
+  { "oe",                                667, NULL },
+  { "Abreve",                            611, NULL },
+  { "Umacron",                           722, NULL },
+  { "ecircumflex",                       444, NULL },
+  { "Adieresis",                         611, NULL },
+  { "copyright",                         760, NULL },
+  { "Egrave",                            611, NULL },
+  { "slash",                             278, NULL },
+  { "Edieresis",                         611, NULL },
+  { "otilde",                            500, NULL },
+  { "Idieresis",                         333, NULL },
+  { "parenleft",                         333, NULL },
+  { "one",                               500, NULL },
+  { "emacron",                           444, NULL },
+  { "Odieresis",                         722, NULL },
+  { "ucircumflex",                       500, NULL },
+  { "bracketleft",                       389, NULL },
+  { "Ugrave",                            722, NULL },
+  { "quoteright",                        333, NULL },
+  { "Udieresis",                         722, NULL },
+  { "perthousand",                      1000, NULL },
+  { "Ydieresis",                         556, NULL },
+  { "umacron",                           500, NULL },
+  { "abreve",                            500, NULL },
+  { "Eacute",                            611, NULL },
+  { "adieresis",                         500, NULL },
+  { "egrave",                            444, NULL },
+  { "edieresis",                         444, NULL },
+  { "idieresis",                         278, NULL },
+  { "Eth",                               722, NULL },
+  { "ae",                                667, NULL },
+  { "asterisk",                          500, NULL },
+  { "odieresis",                         500, NULL },
+  { "Uacute",                            722, NULL },
+  { "ugrave",                            500, NULL },
+  { "nine",                              500, NULL },
+  { "five",                              500, NULL },
+  { "udieresis",                         500, NULL },
+  { "Zcaron",                            556, NULL },
+  { "Scommaaccent",                      500, NULL },
+  { "threequarters",                     750, NULL },
+  { "guillemotright",                    500, NULL },
+  { "Ccedilla",                          667, NULL },
+  { "ydieresis",                         444, NULL },
+  { "tilde",                             333, NULL },
+  { "at",                                920, NULL },
+  { "eacute",                            444, NULL },
+  { "underscore",                        500, NULL },
+  { "Euro",                              500, NULL },
+  { "Dcroat",                            722, NULL },
+  { "multiply",                          675, NULL },
+  { "zero",                              500, NULL },
+  { "eth",                               500, NULL },
+  { "Scedilla",                          500, NULL },
+  { "Ograve",                            722, NULL },
+  { "Racute",                            611, NULL },
+  { "partialdiff",                       476, NULL },
+  { "uacute",                            500, NULL },
+  { "braceleft",                         400, NULL },
+  { "Thorn",                             611, NULL },
+  { "zcaron",                            389, NULL },
+  { "scommaaccent",                      389, NULL },
+  { "ccedilla",                          444, NULL },
+  { "Dcaron",                            722, NULL },
+  { "dcroat",                            500, NULL },
+  { "Ocircumflex",                       722, NULL },
+  { "Oacute",                            722, NULL },
+  { "scedilla",                          389, NULL },
+  { "ogonek",                            333, NULL },
+  { "ograve",                            500, NULL },
+  { "racute",                            389, NULL },
+  { "Tcaron",                            556, NULL },
+  { "Eogonek",                           611, NULL },
+  { "thorn",                             500, NULL },
+  { "degree",                            400, NULL },
+  { "registered",                        760, NULL },
+  { "radical",                           453, NULL },
+  { "Aring",                             611, NULL },
+  { "percent",                           833, NULL },
+  { "six",                               500, NULL },
+  { "paragraph",                         523, NULL },
+  { "dcaron",                            544, NULL },
+  { "Uogonek",                           722, NULL },
+  { "two",                               500, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            333, NULL },
+  { "Lacute",                            556, NULL },
+  { "ocircumflex",                       500, NULL },
+  { "oacute",                            500, NULL },
+  { "Uring",                             722, NULL },
+  { "Lcommaaccent",                      556, NULL },
+  { "tcaron",                            300, NULL },
+  { "eogonek",                           444, NULL },
+  { "Delta",                             612, NULL },
+  { "Ohungarumlaut",                     722, NULL },
+  { "asciicircum",                       422, NULL },
+  { "aring",                             500, NULL },
+  { "grave",                             333, NULL },
+  { "uogonek",                           500, NULL },
+  { "bracketright",                      389, NULL },
+  { "Iacute",                            333, NULL },
+  { "ampersand",                         778, NULL },
+  { "igrave",                            278, NULL },
+  { "lacute",                            278, NULL },
+  { "Ncaron",                            667, NULL },
+  { "plus",                              675, NULL },
+  { "uring",                             500, NULL },
+  { "quotesinglbase",                    333, NULL },
+  { "lcommaaccent",                      278, NULL },
+  { "Yacute",                            556, NULL },
+  { "ohungarumlaut",                     500, NULL },
+  { "threesuperior",                     300, NULL },
+  { "acute",                             333, NULL },
+  { "section",                           500, NULL },
+  { "dieresis",                          333, NULL },
+  { "iacute",                            278, NULL },
+  { "quotedblbase",                      556, NULL },
+  { "ncaron",                            500, NULL },
+  { "florin",                            500, NULL },
+  { "yacute",                            444, NULL },
+  { "Rcommaaccent",                      611, NULL },
+  { "fi",                                500, NULL },
+  { "fl",                                500, NULL },
+  { "Acircumflex",                       611, NULL },
+  { "Cacute",                            667, NULL },
+  { "Icircumflex",                       333, NULL },
+  { "guillemotleft",                     500, NULL },
+  { "germandbls",                        500, NULL },
+  { "Amacron",                           611, NULL },
+  { "seven",                             500, NULL },
+  { "Sacute",                            500, NULL },
+  { "ordmasculine",                      310, NULL },
+  { "dotlessi",                          278, NULL },
+  { "sterling",                          500, NULL },
+  { "notequal",                          549, NULL },
+  { "Imacron",                           333, NULL },
+  { "rcommaaccent",                      389, NULL },
+  { "Zdotaccent",                        556, NULL },
+  { "acircumflex",                       500, NULL },
+  { "cacute",                            444, NULL },
+  { "Ecaron",                            611, NULL },
+  { "icircumflex",                       278, NULL },
+  { "braceright",                        400, NULL },
+  { "quotedblright",                     556, NULL },
+  { "amacron",                           500, NULL },
+  { "sacute",                            389, NULL },
+  { "imacron",                           278, NULL },
+  { "cent",                              500, NULL },
+  { "currency",                          500, NULL },
+  { "logicalnot",                        675, NULL },
+  { "zdotaccent",                        389, NULL },
+  { "Atilde",                            611, NULL },
+  { "breve",                             333, NULL },
+  { "bar",                               275, NULL },
+  { "fraction",                          167, NULL },
+  { "less",                              675, NULL },
+  { "ecaron",                            444, NULL },
+  { "guilsinglleft",                     333, NULL },
+  { "exclam",                            333, NULL },
+  { "period",                            250, NULL },
+  { "Rcaron",                            611, NULL },
+  { "Kcommaaccent",                      667, NULL },
+  { "greater",                           675, NULL },
+  { "atilde",                            500, NULL },
+  { "brokenbar",                         275, NULL },
+  { "quoteleft",                         333, NULL },
+  { "Edotaccent",                        611, NULL },
+  { "onesuperior",                       300, NULL }
+};
+
+static BuiltinFontWidth timesRomanWidthsTab[] = {
+  { "Ntilde",                            722, NULL },
+  { "rcaron",                            333, NULL },
+  { "kcommaaccent",                      500, NULL },
+  { "Ncommaaccent",                      722, NULL },
+  { "Zacute",                            611, NULL },
+  { "comma",                             250, NULL },
+  { "cedilla",                           333, NULL },
+  { "plusminus",                         564, NULL },
+  { "circumflex",                        333, NULL },
+  { "dotaccent",                         333, NULL },
+  { "edotaccent",                        444, NULL },
+  { "asciitilde",                        541, NULL },
+  { "colon",                             278, NULL },
+  { "onehalf",                           750, NULL },
+  { "dollar",                            500, NULL },
+  { "Lcaron",                            611, NULL },
+  { "ntilde",                            500, NULL },
+  { "Aogonek",                           722, NULL },
+  { "ncommaaccent",                      500, NULL },
+  { "minus",                             564, NULL },
+  { "Iogonek",                           333, NULL },
+  { "zacute",                            444, NULL },
+  { "yen",                               500, NULL },
+  { "space",                             250, NULL },
+  { "Omacron",                           722, NULL },
+  { "questiondown",                      444, NULL },
+  { "emdash",                           1000, NULL },
+  { "Agrave",                            722, NULL },
+  { "three",                             500, NULL },
+  { "numbersign",                        500, NULL },
+  { "lcaron",                            344, NULL },
+  { "A",                                 722, NULL },
+  { "B",                                 667, NULL },
+  { "C",                                 667, NULL },
+  { "aogonek",                           444, NULL },
+  { "D",                                 722, NULL },
+  { "E",                                 611, NULL },
+  { "onequarter",                        750, NULL },
+  { "F",                                 556, NULL },
+  { "G",                                 722, NULL },
+  { "H",                                 722, NULL },
+  { "I",                                 333, NULL },
+  { "J",                                 389, NULL },
+  { "K",                                 722, NULL },
+  { "iogonek",                           278, NULL },
+  { "backslash",                         278, NULL },
+  { "L",                                 611, NULL },
+  { "periodcentered",                    250, NULL },
+  { "M",                                 889, NULL },
+  { "N",                                 722, NULL },
+  { "omacron",                           500, NULL },
+  { "Tcommaaccent",                      611, NULL },
+  { "O",                                 722, NULL },
+  { "P",                                 556, NULL },
+  { "Q",                                 722, NULL },
+  { "Uhungarumlaut",                     722, NULL },
+  { "R",                                 667, NULL },
+  { "Aacute",                            722, NULL },
+  { "caron",                             333, NULL },
+  { "S",                                 556, NULL },
+  { "T",                                 611, NULL },
+  { "U",                                 722, NULL },
+  { "agrave",                            444, NULL },
+  { "V",                                 722, NULL },
+  { "W",                                 944, NULL },
+  { "X",                                 722, NULL },
+  { "question",                          444, NULL },
+  { "equal",                             564, NULL },
+  { "Y",                                 722, NULL },
+  { "Z",                                 611, NULL },
+  { "four",                              500, NULL },
+  { "a",                                 444, NULL },
+  { "Gcommaaccent",                      722, NULL },
+  { "b",                                 500, NULL },
+  { "c",                                 444, NULL },
+  { "d",                                 500, NULL },
+  { "e",                                 444, NULL },
+  { "f",                                 333, NULL },
+  { "g",                                 500, NULL },
+  { "bullet",                            350, NULL },
+  { "h",                                 500, NULL },
+  { "i",                                 278, NULL },
+  { "Oslash",                            722, NULL },
+  { "dagger",                            500, NULL },
+  { "j",                                 278, NULL },
+  { "k",                                 500, NULL },
+  { "l",                                 278, NULL },
+  { "m",                                 778, NULL },
+  { "n",                                 500, NULL },
+  { "tcommaaccent",                      278, NULL },
+  { "o",                                 500, NULL },
+  { "ordfeminine",                       276, NULL },
+  { "ring",                              333, NULL },
+  { "p",                                 500, NULL },
+  { "q",                                 500, NULL },
+  { "uhungarumlaut",                     500, NULL },
+  { "r",                                 333, NULL },
+  { "twosuperior",                       300, NULL },
+  { "aacute",                            444, NULL },
+  { "s",                                 389, NULL },
+  { "OE",                                889, NULL },
+  { "t",                                 278, NULL },
+  { "divide",                            564, NULL },
+  { "u",                                 500, NULL },
+  { "Ccaron",                            667, NULL },
+  { "v",                                 500, NULL },
+  { "w",                                 722, NULL },
+  { "x",                                 500, NULL },
+  { "y",                                 500, NULL },
+  { "z",                                 444, NULL },
+  { "Gbreve",                            722, NULL },
+  { "commaaccent",                       250, NULL },
+  { "hungarumlaut",                      333, NULL },
+  { "Idotaccent",                        333, NULL },
+  { "Nacute",                            722, NULL },
+  { "quotedbl",                          408, NULL },
+  { "gcommaaccent",                      500, NULL },
+  { "mu",                                500, NULL },
+  { "greaterequal",                      549, NULL },
+  { "Scaron",                            556, NULL },
+  { "Lslash",                            611, NULL },
+  { "semicolon",                         278, NULL },
+  { "oslash",                            500, NULL },
+  { "lessequal",                         549, NULL },
+  { "lozenge",                           471, NULL },
+  { "parenright",                        333, NULL },
+  { "ccaron",                            444, NULL },
+  { "Ecircumflex",                       611, NULL },
+  { "gbreve",                            500, NULL },
+  { "trademark",                         980, NULL },
+  { "daggerdbl",                         500, NULL },
+  { "nacute",                            500, NULL },
+  { "macron",                            333, NULL },
+  { "Otilde",                            722, NULL },
+  { "Emacron",                           611, NULL },
+  { "ellipsis",                         1000, NULL },
+  { "scaron",                            389, NULL },
+  { "AE",                                889, NULL },
+  { "Ucircumflex",                       722, NULL },
+  { "lslash",                            278, NULL },
+  { "quotedblleft",                      444, NULL },
+  { "guilsinglright",                    333, NULL },
+  { "hyphen",                            333, NULL },
+  { "quotesingle",                       180, NULL },
+  { "eight",                             500, NULL },
+  { "exclamdown",                        333, NULL },
+  { "endash",                            500, NULL },
+  { "oe",                                722, NULL },
+  { "Abreve",                            722, NULL },
+  { "Umacron",                           722, NULL },
+  { "ecircumflex",                       444, NULL },
+  { "Adieresis",                         722, NULL },
+  { "copyright",                         760, NULL },
+  { "Egrave",                            611, NULL },
+  { "slash",                             278, NULL },
+  { "Edieresis",                         611, NULL },
+  { "otilde",                            500, NULL },
+  { "Idieresis",                         333, NULL },
+  { "parenleft",                         333, NULL },
+  { "one",                               500, NULL },
+  { "emacron",                           444, NULL },
+  { "Odieresis",                         722, NULL },
+  { "ucircumflex",                       500, NULL },
+  { "bracketleft",                       333, NULL },
+  { "Ugrave",                            722, NULL },
+  { "quoteright",                        333, NULL },
+  { "Udieresis",                         722, NULL },
+  { "perthousand",                      1000, NULL },
+  { "Ydieresis",                         722, NULL },
+  { "umacron",                           500, NULL },
+  { "abreve",                            444, NULL },
+  { "Eacute",                            611, NULL },
+  { "adieresis",                         444, NULL },
+  { "egrave",                            444, NULL },
+  { "edieresis",                         444, NULL },
+  { "idieresis",                         278, NULL },
+  { "Eth",                               722, NULL },
+  { "ae",                                667, NULL },
+  { "asterisk",                          500, NULL },
+  { "odieresis",                         500, NULL },
+  { "Uacute",                            722, NULL },
+  { "ugrave",                            500, NULL },
+  { "nine",                              500, NULL },
+  { "five",                              500, NULL },
+  { "udieresis",                         500, NULL },
+  { "Zcaron",                            611, NULL },
+  { "Scommaaccent",                      556, NULL },
+  { "threequarters",                     750, NULL },
+  { "guillemotright",                    500, NULL },
+  { "Ccedilla",                          667, NULL },
+  { "ydieresis",                         500, NULL },
+  { "tilde",                             333, NULL },
+  { "at",                                921, NULL },
+  { "eacute",                            444, NULL },
+  { "underscore",                        500, NULL },
+  { "Euro",                              500, NULL },
+  { "Dcroat",                            722, NULL },
+  { "multiply",                          564, NULL },
+  { "zero",                              500, NULL },
+  { "eth",                               500, NULL },
+  { "Scedilla",                          556, NULL },
+  { "Ograve",                            722, NULL },
+  { "Racute",                            667, NULL },
+  { "partialdiff",                       476, NULL },
+  { "uacute",                            500, NULL },
+  { "braceleft",                         480, NULL },
+  { "Thorn",                             556, NULL },
+  { "zcaron",                            444, NULL },
+  { "scommaaccent",                      389, NULL },
+  { "ccedilla",                          444, NULL },
+  { "Dcaron",                            722, NULL },
+  { "dcroat",                            500, NULL },
+  { "Ocircumflex",                       722, NULL },
+  { "Oacute",                            722, NULL },
+  { "scedilla",                          389, NULL },
+  { "ogonek",                            333, NULL },
+  { "ograve",                            500, NULL },
+  { "racute",                            333, NULL },
+  { "Tcaron",                            611, NULL },
+  { "Eogonek",                           611, NULL },
+  { "thorn",                             500, NULL },
+  { "degree",                            400, NULL },
+  { "registered",                        760, NULL },
+  { "radical",                           453, NULL },
+  { "Aring",                             722, NULL },
+  { "percent",                           833, NULL },
+  { "six",                               500, NULL },
+  { "paragraph",                         453, NULL },
+  { "dcaron",                            588, NULL },
+  { "Uogonek",                           722, NULL },
+  { "two",                               500, NULL },
+  { "summation",                         600, NULL },
+  { "Igrave",                            333, NULL },
+  { "Lacute",                            611, NULL },
+  { "ocircumflex",                       500, NULL },
+  { "oacute",                            500, NULL },
+  { "Uring",                             722, NULL },
+  { "Lcommaaccent",                      611, NULL },
+  { "tcaron",                            326, NULL },
+  { "eogonek",                           444, NULL },
+  { "Delta",                             612, NULL },
+  { "Ohungarumlaut",                     722, NULL },
+  { "asciicircum",                       469, NULL },
+  { "aring",                             444, NULL },
+  { "grave",                             333, NULL },
+  { "uogonek",                           500, NULL },
+  { "bracketright",                      333, NULL },
+  { "Iacute",                            333, NULL },
+  { "ampersand",                         778, NULL },
+  { "igrave",                            278, NULL },
+  { "lacute",                            278, NULL },
+  { "Ncaron",                            722, NULL },
+  { "plus",                              564, NULL },
+  { "uring",                             500, NULL },
+  { "quotesinglbase",                    333, NULL },
+  { "lcommaaccent",                      278, NULL },
+  { "Yacute",                            722, NULL },
+  { "ohungarumlaut",                     500, NULL },
+  { "threesuperior",                     300, NULL },
+  { "acute",                             333, NULL },
+  { "section",                           500, NULL },
+  { "dieresis",                          333, NULL },
+  { "iacute",                            278, NULL },
+  { "quotedblbase",                      444, NULL },
+  { "ncaron",                            500, NULL },
+  { "florin",                            500, NULL },
+  { "yacute",                            500, NULL },
+  { "Rcommaaccent",                      667, NULL },
+  { "fi",                                556, NULL },
+  { "fl",                                556, NULL },
+  { "Acircumflex",                       722, NULL },
+  { "Cacute",                            667, NULL },
+  { "Icircumflex",                       333, NULL },
+  { "guillemotleft",                     500, NULL },
+  { "germandbls",                        500, NULL },
+  { "Amacron",                           722, NULL },
+  { "seven",                             500, NULL },
+  { "Sacute",                            556, NULL },
+  { "ordmasculine",                      310, NULL },
+  { "dotlessi",                          278, NULL },
+  { "sterling",                          500, NULL },
+  { "notequal",                          549, NULL },
+  { "Imacron",                           333, NULL },
+  { "rcommaaccent",                      333, NULL },
+  { "Zdotaccent",                        611, NULL },
+  { "acircumflex",                       444, NULL },
+  { "cacute",                            444, NULL },
+  { "Ecaron",                            611, NULL },
+  { "icircumflex",                       278, NULL },
+  { "braceright",                        480, NULL },
+  { "quotedblright",                     444, NULL },
+  { "amacron",                           444, NULL },
+  { "sacute",                            389, NULL },
+  { "imacron",                           278, NULL },
+  { "cent",                              500, NULL },
+  { "currency",                          500, NULL },
+  { "logicalnot",                        564, NULL },
+  { "zdotaccent",                        444, NULL },
+  { "Atilde",                            722, NULL },
+  { "breve",                             333, NULL },
+  { "bar",                               200, NULL },
+  { "fraction",                          167, NULL },
+  { "less",                              564, NULL },
+  { "ecaron",                            444, NULL },
+  { "guilsinglleft",                     333, NULL },
+  { "exclam",                            333, NULL },
+  { "period",                            250, NULL },
+  { "Rcaron",                            667, NULL },
+  { "Kcommaaccent",                      722, NULL },
+  { "greater",                           564, NULL },
+  { "atilde",                            444, NULL },
+  { "brokenbar",                         200, NULL },
+  { "quoteleft",                         333, NULL },
+  { "Edotaccent",                        611, NULL },
+  { "onesuperior",                       300, NULL }
+};
+
+static BuiltinFontWidth zapfDingbatsWidthsTab[] = {
+  { "a81",                               438, NULL },
+  { "a82",                               138, NULL },
+  { "a83",                               277, NULL },
+  { "a84",                               415, NULL },
+  { "a85",                               509, NULL },
+  { "a86",                               410, NULL },
+  { "a87",                               234, NULL },
+  { "a88",                               234, NULL },
+  { "a89",                               390, NULL },
+  { "a140",                              788, NULL },
+  { "a141",                              788, NULL },
+  { "a142",                              788, NULL },
+  { "a143",                              788, NULL },
+  { "a144",                              788, NULL },
+  { "a145",                              788, NULL },
+  { "a146",                              788, NULL },
+  { "a147",                              788, NULL },
+  { "a148",                              788, NULL },
+  { "a149",                              788, NULL },
+  { "a90",                               390, NULL },
+  { "a91",                               276, NULL },
+  { "a92",                               276, NULL },
+  { "space",                             278, NULL },
+  { "a93",                               317, NULL },
+  { "a94",                               317, NULL },
+  { "a95",                               334, NULL },
+  { "a96",                               334, NULL },
+  { "a97",                               392, NULL },
+  { "a98",                               392, NULL },
+  { "a99",                               668, NULL },
+  { "a150",                              788, NULL },
+  { "a151",                              788, NULL },
+  { "a152",                              788, NULL },
+  { "a153",                              788, NULL },
+  { "a154",                              788, NULL },
+  { "a155",                              788, NULL },
+  { "a156",                              788, NULL },
+  { "a157",                              788, NULL },
+  { "a158",                              788, NULL },
+  { "a159",                              788, NULL },
+  { "a160",                              894, NULL },
+  { "a161",                              838, NULL },
+  { "a162",                              924, NULL },
+  { "a163",                             1016, NULL },
+  { "a164",                              458, NULL },
+  { "a165",                              924, NULL },
+  { "a166",                              918, NULL },
+  { "a167",                              927, NULL },
+  { "a168",                              928, NULL },
+  { "a169",                              928, NULL },
+  { "a170",                              834, NULL },
+  { "a171",                              873, NULL },
+  { "a172",                              828, NULL },
+  { "a173",                              924, NULL },
+  { "a174",                              917, NULL },
+  { "a175",                              930, NULL },
+  { "a176",                              931, NULL },
+  { "a177",                              463, NULL },
+  { "a178",                              883, NULL },
+  { "a179",                              836, NULL },
+  { "a180",                              867, NULL },
+  { "a181",                              696, NULL },
+  { "a182",                              874, NULL },
+  { "a183",                              760, NULL },
+  { "a184",                              946, NULL },
+  { "a185",                              865, NULL },
+  { "a186",                              967, NULL },
+  { "a187",                              831, NULL },
+  { "a188",                              873, NULL },
+  { "a189",                              927, NULL },
+  { "a1",                                974, NULL },
+  { "a2",                                961, NULL },
+  { "a3",                                980, NULL },
+  { "a4",                                719, NULL },
+  { "a5",                                789, NULL },
+  { "a6",                                494, NULL },
+  { "a7",                                552, NULL },
+  { "a8",                                537, NULL },
+  { "a9",                                577, NULL },
+  { "a190",                              970, NULL },
+  { "a191",                              918, NULL },
+  { "a192",                              748, NULL },
+  { "a193",                              836, NULL },
+  { "a194",                              771, NULL },
+  { "a195",                              888, NULL },
+  { "a196",                              748, NULL },
+  { "a197",                              771, NULL },
+  { "a198",                              888, NULL },
+  { "a199",                              867, NULL },
+  { "a10",                               692, NULL },
+  { "a11",                               960, NULL },
+  { "a12",                               939, NULL },
+  { "a13",                               549, NULL },
+  { "a14",                               855, NULL },
+  { "a15",                               911, NULL },
+  { "a16",                               933, NULL },
+  { "a17",                               945, NULL },
+  { "a18",                               974, NULL },
+  { "a19",                               755, NULL },
+  { "a20",                               846, NULL },
+  { "a21",                               762, NULL },
+  { "a22",                               761, NULL },
+  { "a23",                               571, NULL },
+  { "a24",                               677, NULL },
+  { "a25",                               763, NULL },
+  { "a26",                               760, NULL },
+  { "a27",                               759, NULL },
+  { "a28",                               754, NULL },
+  { "a29",                               786, NULL },
+  { "a30",                               788, NULL },
+  { "a31",                               788, NULL },
+  { "a32",                               790, NULL },
+  { "a33",                               793, NULL },
+  { "a34",                               794, NULL },
+  { "a35",                               816, NULL },
+  { "a36",                               823, NULL },
+  { "a37",                               789, NULL },
+  { "a38",                               841, NULL },
+  { "a39",                               823, NULL },
+  { "a40",                               833, NULL },
+  { "a41",                               816, NULL },
+  { "a42",                               831, NULL },
+  { "a43",                               923, NULL },
+  { "a44",                               744, NULL },
+  { "a45",                               723, NULL },
+  { "a46",                               749, NULL },
+  { "a47",                               790, NULL },
+  { "a48",                               792, NULL },
+  { "a49",                               695, NULL },
+  { "a100",                              668, NULL },
+  { "a101",                              732, NULL },
+  { "a102",                              544, NULL },
+  { "a103",                              544, NULL },
+  { "a104",                              910, NULL },
+  { "a105",                              911, NULL },
+  { "a106",                              667, NULL },
+  { "a107",                              760, NULL },
+  { "a108",                              760, NULL },
+  { "a109",                              626, NULL },
+  { "a50",                               776, NULL },
+  { "a51",                               768, NULL },
+  { "a52",                               792, NULL },
+  { "a53",                               759, NULL },
+  { "a54",                               707, NULL },
+  { "a55",                               708, NULL },
+  { "a56",                               682, NULL },
+  { "a57",                               701, NULL },
+  { "a58",                               826, NULL },
+  { "a59",                               815, NULL },
+  { "a110",                              694, NULL },
+  { "a111",                              595, NULL },
+  { "a112",                              776, NULL },
+  { "a117",                              690, NULL },
+  { "a118",                              791, NULL },
+  { "a119",                              790, NULL },
+  { "a60",                               789, NULL },
+  { "a61",                               789, NULL },
+  { "a62",                               707, NULL },
+  { "a63",                               687, NULL },
+  { "a64",                               696, NULL },
+  { "a65",                               689, NULL },
+  { "a66",                               786, NULL },
+  { "a67",                               787, NULL },
+  { "a68",                               713, NULL },
+  { "a69",                               791, NULL },
+  { "a200",                              696, NULL },
+  { "a201",                              874, NULL },
+  { "a120",                              788, NULL },
+  { "a121",                              788, NULL },
+  { "a202",                              974, NULL },
+  { "a122",                              788, NULL },
+  { "a203",                              762, NULL },
+  { "a123",                              788, NULL },
+  { "a204",                              759, NULL },
+  { "a124",                              788, NULL },
+  { "a205",                              509, NULL },
+  { "a125",                              788, NULL },
+  { "a206",                              410, NULL },
+  { "a126",                              788, NULL },
+  { "a127",                              788, NULL },
+  { "a128",                              788, NULL },
+  { "a129",                              788, NULL },
+  { "a70",                               785, NULL },
+  { "a71",                               791, NULL },
+  { "a72",                               873, NULL },
+  { "a73",                               761, NULL },
+  { "a74",                               762, NULL },
+  { "a75",                               759, NULL },
+  { "a76",                               892, NULL },
+  { "a77",                               892, NULL },
+  { "a78",                               788, NULL },
+  { "a79",                               784, NULL },
+  { "a130",                              788, NULL },
+  { "a131",                              788, NULL },
+  { "a132",                              788, NULL },
+  { "a133",                              788, NULL },
+  { "a134",                              788, NULL },
+  { "a135",                              788, NULL },
+  { "a136",                              788, NULL },
+  { "a137",                              788, NULL },
+  { "a138",                              788, NULL },
+  { "a139",                              788, NULL }
+};
+
+BuiltinFont builtinFonts[] = {
+  { "Courier",               standardEncoding,            629, -157, { -23, -250,  715,  805}, NULL },
+  { "Courier-Bold",          standardEncoding,            629, -157, {-113, -250,  749,  801}, NULL },
+  { "Courier-BoldOblique",   standardEncoding,            629, -157, { -57, -250,  869,  801}, NULL },
+  { "Courier-Oblique",       standardEncoding,            629, -157, { -27, -250,  849,  805}, NULL },
+  { "Helvetica",             standardEncoding,            718, -207, {-166, -225, 1000,  931}, NULL },
+  { "Helvetica-Bold",        standardEncoding,            718, -207, {-170, -228, 1003,  962}, NULL },
+  { "Helvetica-BoldOblique", standardEncoding,            718, -207, {-174, -228, 1114,  962}, NULL },
+  { "Helvetica-Oblique",     standardEncoding,            718, -207, {-170, -225, 1116,  931}, NULL },
+  { "Symbol",                symbolEncoding,             1010, -293, {-180, -293, 1090, 1010}, NULL },
+  { "Times-Bold",            standardEncoding,            683, -217, {-168, -218, 1000,  935}, NULL },
+  { "Times-BoldItalic",      standardEncoding,            683, -217, {-200, -218,  996,  921}, NULL },
+  { "Times-Italic",          standardEncoding,            683, -217, {-169, -217, 1010,  883}, NULL },
+  { "Times-Roman",           standardEncoding,            683, -217, {-168, -218, 1000,  898}, NULL },
+  { "ZapfDingbats",          zapfDingbatsEncoding,        820, -143, {  -1, -143,  981,  820}, NULL }
+};
+
+BuiltinFont *builtinFontSubst[] = {
+  &builtinFonts[0],
+  &builtinFonts[3],
+  &builtinFonts[1],
+  &builtinFonts[2],
+  &builtinFonts[4],
+  &builtinFonts[7],
+  &builtinFonts[5],
+  &builtinFonts[6],
+  &builtinFonts[12],
+  &builtinFonts[11],
+  &builtinFonts[9],
+  &builtinFonts[10]
+};
+
+void initBuiltinFontTables() {
+  builtinFonts[0].widths = new BuiltinFontWidths(courierWidthsTab, 315);
+  builtinFonts[1].widths = new BuiltinFontWidths(courierBoldWidthsTab, 315);
+  builtinFonts[2].widths = new BuiltinFontWidths(courierBoldObliqueWidthsTab, 315);
+  builtinFonts[3].widths = new BuiltinFontWidths(courierObliqueWidthsTab, 315);
+  builtinFonts[4].widths = new BuiltinFontWidths(helveticaWidthsTab, 315);
+  builtinFonts[5].widths = new BuiltinFontWidths(helveticaBoldWidthsTab, 316);
+  builtinFonts[6].widths = new BuiltinFontWidths(helveticaBoldObliqueWidthsTab, 315);
+  builtinFonts[7].widths = new BuiltinFontWidths(helveticaObliqueWidthsTab, 315);
+  builtinFonts[8].widths = new BuiltinFontWidths(symbolWidthsTab, 190);
+  builtinFonts[9].widths = new BuiltinFontWidths(timesBoldWidthsTab, 315);
+  builtinFonts[10].widths = new BuiltinFontWidths(timesBoldItalicWidthsTab, 315);
+  builtinFonts[11].widths = new BuiltinFontWidths(timesItalicWidthsTab, 315);
+  builtinFonts[12].widths = new BuiltinFontWidths(timesRomanWidthsTab, 315);
+  builtinFonts[13].widths = new BuiltinFontWidths(zapfDingbatsWidthsTab, 202);
+}
+
+void freeBuiltinFontTables() {
+  int i;
+
+  for (i = 0; i < 14; ++i) {
+    delete builtinFonts[i].widths;
+  }
+}
diff --git a/xpdf/BuiltinFontTables.h b/xpdf/BuiltinFontTables.h
new file mode 100644
--- /dev/null
+++ b/xpdf/BuiltinFontTables.h
@@ -0,0 +1,23 @@
+//========================================================================
+//
+// BuiltinFontTables.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef BUILTINFONTTABLES_H
+#define BUILTINFONTTABLES_H
+
+#include "BuiltinFont.h"
+
+#define nBuiltinFonts      14
+#define nBuiltinFontSubsts 12
+
+extern BuiltinFont builtinFonts[nBuiltinFonts];
+extern BuiltinFont *builtinFontSubst[nBuiltinFontSubsts];
+
+extern void initBuiltinFontTables();
+extern void freeBuiltinFontTables();
+
+#endif
diff --git a/xpdf/CMap.cc b/xpdf/CMap.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/CMap.cc
@@ -0,0 +1,408 @@
+//========================================================================
+//
+// CMap.cc
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "gmem.h"
+#include "gfile.h"
+#include "GString.h"
+#include "Error.h"
+#include "GlobalParams.h"
+#include "PSTokenizer.h"
+#include "CMap.h"
+
+//------------------------------------------------------------------------
+
+struct CMapVectorEntry {
+  GBool isVector;
+  union {
+    CMapVectorEntry *vector;
+    CID cid;
+  };
+};
+
+//------------------------------------------------------------------------
+
+static int getCharFromFile(void *data) {
+  return fgetc((FILE *)data);
+}
+
+//------------------------------------------------------------------------
+
+CMap *CMap::parse(CMapCache *cache, GString *collectionA,
+		  GString *cMapNameA) {
+  FILE *f;
+  CMap *cmap;
+  PSTokenizer *pst;
+  char tok1[256], tok2[256], tok3[256];
+  int n1, n2, n3;
+  Guint start, end, code;
+
+  if (!(f = globalParams->findCMapFile(collectionA, cMapNameA))) {
+
+    // Check for an identity CMap.
+    if (!cMapNameA->cmp("Identity") || !cMapNameA->cmp("Identity-H")) {
+      return new CMap(collectionA->copy(), cMapNameA->copy(), 0);
+    }
+    if (!cMapNameA->cmp("Identity-V")) {
+      return new CMap(collectionA->copy(), cMapNameA->copy(), 1);
+    }
+
+    error(-1, "Couldn't find '%s' CMap file for '%s' collection",
+	  cMapNameA->getCString(), collectionA->getCString());
+    return NULL;
+  }
+
+  cmap = new CMap(collectionA->copy(), cMapNameA->copy());
+
+  pst = new PSTokenizer(&getCharFromFile, f);
+  pst->getToken(tok1, sizeof(tok1), &n1);
+  while (pst->getToken(tok2, sizeof(tok2), &n2)) {
+    if (!strcmp(tok2, "usecmap")) {
+      if (tok1[0] == '/') {
+	cmap->useCMap(cache, tok1 + 1);
+      }
+      pst->getToken(tok1, sizeof(tok1), &n1);
+    } else if (!strcmp(tok1, "/WMode")) {
+      cmap->wMode = atoi(tok2);
+      pst->getToken(tok1, sizeof(tok1), &n1);
+    } else if (!strcmp(tok2, "begincodespacerange")) {
+      while (pst->getToken(tok1, sizeof(tok1), &n1)) {
+	if (!strcmp(tok1, "endcodespacerange")) {
+	  break;
+	}
+	if (!pst->getToken(tok2, sizeof(tok2), &n2) ||
+	    !strcmp(tok2, "endcodespacerange")) {
+	  error(-1, "Illegal entry in codespacerange block in CMap");
+	  break;
+	}
+	if (tok1[0] == '<' && tok2[0] == '<' &&
+	    n1 == n2 && n1 >= 4 && (n1 & 1) == 0) {
+	  tok1[n1 - 1] = tok2[n1 - 1] = '\0';
+	  sscanf(tok1 + 1, "%x", &start);
+	  sscanf(tok2 + 1, "%x", &end);
+	  n1 = (n1 - 2) / 2;
+	  cmap->addCodeSpace(cmap->vector, start, end, n1);
+	}
+      }
+      pst->getToken(tok1, sizeof(tok1), &n1);
+    } else if (!strcmp(tok2, "begincidchar")) {
+      while (pst->getToken(tok1, sizeof(tok1), &n1)) {
+	if (!strcmp(tok1, "endcidchar")) {
+	  break;
+	}
+	if (!pst->getToken(tok2, sizeof(tok2), &n2) ||
+	    !strcmp(tok2, "endcidchar")) {
+	  error(-1, "Illegal entry in cidchar block in CMap");
+	  break;
+	}
+	if (!(tok1[0] == '<' && tok1[n1 - 1] == '>' &&
+	      n1 >= 4 && (n1 & 1) == 0)) {
+	  error(-1, "Illegal entry in cidchar block in CMap");
+	  continue;
+	}
+	tok1[n1 - 1] = '\0';
+	if (sscanf(tok1 + 1, "%x", &code) != 1) {
+	  error(-1, "Illegal entry in cidchar block in CMap");
+	  continue;
+	}
+	n1 = (n1 - 2) / 2;
+	cmap->addCIDs(code, code, n1, (CID)atoi(tok2));
+      }
+      pst->getToken(tok1, sizeof(tok1), &n1);
+    } else if (!strcmp(tok2, "begincidrange")) {
+      while (pst->getToken(tok1, sizeof(tok1), &n1)) {
+	if (!strcmp(tok1, "endcidrange")) {
+	  break;
+	}
+	if (!pst->getToken(tok2, sizeof(tok2), &n2) ||
+	    !strcmp(tok2, "endcidrange") ||
+	    !pst->getToken(tok3, sizeof(tok3), &n3) ||
+	    !strcmp(tok3, "endcidrange")) {
+	  error(-1, "Illegal entry in cidrange block in CMap");
+	  break;
+	}
+	if (tok1[0] == '<' && tok2[0] == '<' &&
+	    n1 == n2 && n1 >= 4 && (n1 & 1) == 0) {
+	  tok1[n1 - 1] = tok2[n1 - 1] = '\0';
+	  sscanf(tok1 + 1, "%x", &start);
+	  sscanf(tok2 + 1, "%x", &end);
+	  n1 = (n1 - 2) / 2;
+	  cmap->addCIDs(start, end, n1, (CID)atoi(tok3));
+	}
+      }
+      pst->getToken(tok1, sizeof(tok1), &n1);
+    } else {
+      strcpy(tok1, tok2);
+    }
+  }
+  delete pst;
+
+  fclose(f);
+
+  return cmap;
+}
+
+CMap::CMap(GString *collectionA, GString *cMapNameA) {
+  int i;
+
+  collection = collectionA;
+  cMapName = cMapNameA;
+  wMode = 0;
+  vector = (CMapVectorEntry *)gmallocn(256, sizeof(CMapVectorEntry));
+  for (i = 0; i < 256; ++i) {
+    vector[i].isVector = gFalse;
+    vector[i].cid = 0;
+  }
+  refCnt = 1;
+#if MULTITHREADED
+  gInitMutex(&mutex);
+#endif
+}
+
+CMap::CMap(GString *collectionA, GString *cMapNameA, int wModeA) {
+  collection = collectionA;
+  cMapName = cMapNameA;
+  wMode = wModeA;
+  vector = NULL;
+  refCnt = 1;
+#if MULTITHREADED
+  gInitMutex(&mutex);
+#endif
+}
+
+void CMap::useCMap(CMapCache *cache, char *useName) {
+  GString *useNameStr;
+  CMap *subCMap;
+
+  useNameStr = new GString(useName);
+  subCMap = cache->getCMap(collection, useNameStr);
+  delete useNameStr;
+  if (!subCMap) {
+    return;
+  }
+  copyVector(vector, subCMap->vector);
+  subCMap->decRefCnt();
+}
+
+void CMap::copyVector(CMapVectorEntry *dest, CMapVectorEntry *src) {
+  int i, j;
+
+  for (i = 0; i < 256; ++i) {
+    if (src[i].isVector) {
+      if (!dest[i].isVector) {
+	dest[i].isVector = gTrue;
+	dest[i].vector =
+	  (CMapVectorEntry *)gmallocn(256, sizeof(CMapVectorEntry));
+	for (j = 0; j < 256; ++j) {
+	  dest[i].vector[j].isVector = gFalse;
+	  dest[i].vector[j].cid = 0;
+	}
+      }
+      copyVector(dest[i].vector, src[i].vector);
+    } else {
+      if (dest[i].isVector) {
+	error(-1, "Collision in usecmap");
+      } else {
+	dest[i].cid = src[i].cid;
+      }
+    }
+  }
+}
+
+void CMap::addCodeSpace(CMapVectorEntry *vec, Guint start, Guint end,
+			Guint nBytes) {
+  Guint start2, end2;
+  int startByte, endByte, i, j;
+
+  if (nBytes > 1) {
+    startByte = (start >> (8 * (nBytes - 1))) & 0xff;
+    endByte = (end >> (8 * (nBytes - 1))) & 0xff;
+    start2 = start & ((1 << (8 * (nBytes - 1))) - 1);
+    end2 = end & ((1 << (8 * (nBytes - 1))) - 1);
+    for (i = startByte; i <= endByte; ++i) {
+      if (!vec[i].isVector) {
+	vec[i].isVector = gTrue;
+	vec[i].vector =
+	  (CMapVectorEntry *)gmallocn(256, sizeof(CMapVectorEntry));
+	for (j = 0; j < 256; ++j) {
+	  vec[i].vector[j].isVector = gFalse;
+	  vec[i].vector[j].cid = 0;
+	}
+      }
+      addCodeSpace(vec[i].vector, start2, end2, nBytes - 1);
+    }
+  }
+}
+
+void CMap::addCIDs(Guint start, Guint end, Guint nBytes, CID firstCID) {
+  CMapVectorEntry *vec;
+  CID cid;
+  int byte;
+  Guint i;
+
+  vec = vector;
+  for (i = nBytes - 1; i >= 1; --i) {
+    byte = (start >> (8 * i)) & 0xff;
+    if (!vec[byte].isVector) {
+      error(-1, "Invalid CID (%0*x - %0*x) in CMap",
+	    2*nBytes, start, 2*nBytes, end);
+      return;
+    }
+    vec = vec[byte].vector;
+  }
+  cid = firstCID;
+  for (byte = (int)(start & 0xff); byte <= (int)(end & 0xff); ++byte) {
+    if (vec[byte].isVector) {
+      error(-1, "Invalid CID (%0*x - %0*x) in CMap",
+	    2*nBytes, start, 2*nBytes, end);
+    } else {
+      vec[byte].cid = cid;
+    }
+    ++cid;
+  }
+}
+
+CMap::~CMap() {
+  delete collection;
+  delete cMapName;
+  if (vector) {
+    freeCMapVector(vector);
+  }
+#if MULTITHREADED
+  gDestroyMutex(&mutex);
+#endif
+}
+
+void CMap::freeCMapVector(CMapVectorEntry *vec) {
+  int i;
+
+  for (i = 0; i < 256; ++i) {
+    if (vec[i].isVector) {
+      freeCMapVector(vec[i].vector);
+    }
+  }
+  gfree(vec);
+}
+
+void CMap::incRefCnt() {
+#if MULTITHREADED
+  gLockMutex(&mutex);
+#endif
+  ++refCnt;
+#if MULTITHREADED
+  gUnlockMutex(&mutex);
+#endif
+}
+
+void CMap::decRefCnt() {
+  GBool done;
+
+#if MULTITHREADED
+  gLockMutex(&mutex);
+#endif
+  done = --refCnt == 0;
+#if MULTITHREADED
+  gUnlockMutex(&mutex);
+#endif
+  if (done) {
+    delete this;
+  }
+}
+
+GBool CMap::match(GString *collectionA, GString *cMapNameA) {
+  return !collection->cmp(collectionA) && !cMapName->cmp(cMapNameA);
+}
+
+CID CMap::getCID(char *s, int len, int *nUsed) {
+  CMapVectorEntry *vec;
+  int n, i;
+
+  if (!(vec = vector)) {
+    // identity CMap
+    *nUsed = 2;
+    if (len < 2) {
+      return 0;
+    }
+    return ((s[0] & 0xff) << 8) + (s[1] & 0xff);
+  }
+  n = 0;
+  while (1) {
+    if (n >= len) {
+      *nUsed = n;
+      return 0;
+    }
+    i = s[n++] & 0xff;
+    if (!vec[i].isVector) {
+      *nUsed = n;
+      return vec[i].cid;
+    }
+    vec = vec[i].vector;
+  }
+}
+
+//------------------------------------------------------------------------
+
+CMapCache::CMapCache() {
+  int i;
+
+  for (i = 0; i < cMapCacheSize; ++i) {
+    cache[i] = NULL;
+  }
+}
+
+CMapCache::~CMapCache() {
+  int i;
+
+  for (i = 0; i < cMapCacheSize; ++i) {
+    if (cache[i]) {
+      cache[i]->decRefCnt();
+    }
+  }
+}
+
+CMap *CMapCache::getCMap(GString *collection, GString *cMapName) {
+  CMap *cmap;
+  int i, j;
+
+  if (cache[0] && cache[0]->match(collection, cMapName)) {
+    cache[0]->incRefCnt();
+    return cache[0];
+  }
+  for (i = 1; i < cMapCacheSize; ++i) {
+    if (cache[i] && cache[i]->match(collection, cMapName)) {
+      cmap = cache[i];
+      for (j = i; j >= 1; --j) {
+	cache[j] = cache[j - 1];
+      }
+      cache[0] = cmap;
+      cmap->incRefCnt();
+      return cmap;
+    }
+  }
+  if ((cmap = CMap::parse(this, collection, cMapName))) {
+    if (cache[cMapCacheSize - 1]) {
+      cache[cMapCacheSize - 1]->decRefCnt();
+    }
+    for (j = cMapCacheSize - 1; j >= 1; --j) {
+      cache[j] = cache[j - 1];
+    }
+    cache[0] = cmap;
+    cmap->incRefCnt();
+    return cmap;
+  }
+  return NULL;
+}
diff --git a/xpdf/CMap.h b/xpdf/CMap.h
new file mode 100644
--- /dev/null
+++ b/xpdf/CMap.h
@@ -0,0 +1,102 @@
+//========================================================================
+//
+// CMap.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef CMAP_H
+#define CMAP_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "CharTypes.h"
+
+#if MULTITHREADED
+#include "GMutex.h"
+#endif
+
+class GString;
+struct CMapVectorEntry;
+class CMapCache;
+
+//------------------------------------------------------------------------
+
+class CMap {
+public:
+
+  // Create the CMap specified by <collection> and <cMapName>.  Sets
+  // the initial reference count to 1.  Returns NULL on failure.
+  static CMap *parse(CMapCache *cache, GString *collectionA,
+		     GString *cMapNameA);
+
+  ~CMap();
+
+  void incRefCnt();
+  void decRefCnt();
+
+  // Return collection name (<registry>-<ordering>).
+  GString *getCollection() { return collection; }
+
+  // Return true if this CMap matches the specified <collectionA>, and
+  // <cMapNameA>.
+  GBool match(GString *collectionA, GString *cMapNameA);
+
+  // Return the CID corresponding to the character code starting at
+  // <s>, which contains <len> bytes.  Sets *<nUsed> to the number of
+  // bytes used by the char code.
+  CID getCID(char *s, int len, int *nUsed);
+
+  // Return the writing mode (0=horizontal, 1=vertical).
+  int getWMode() { return wMode; }
+
+private:
+
+  CMap(GString *collectionA, GString *cMapNameA);
+  CMap(GString *collectionA, GString *cMapNameA, int wModeA);
+  void useCMap(CMapCache *cache, char *useName);
+  void copyVector(CMapVectorEntry *dest, CMapVectorEntry *src);
+  void addCodeSpace(CMapVectorEntry *vec, Guint start, Guint end,
+		    Guint nBytes);
+  void addCIDs(Guint start, Guint end, Guint nBytes, CID firstCID);
+  void freeCMapVector(CMapVectorEntry *vec);
+
+  GString *collection;
+  GString *cMapName;
+  int wMode;			// writing mode (0=horizontal, 1=vertical)
+  CMapVectorEntry *vector;	// vector for first byte (NULL for
+				//   identity CMap)
+  int refCnt;
+#if MULTITHREADED
+  GMutex mutex;
+#endif
+};
+
+//------------------------------------------------------------------------
+
+#define cMapCacheSize 4
+
+class CMapCache {
+public:
+
+  CMapCache();
+  ~CMapCache();
+
+  // Get the <cMapName> CMap for the specified character collection.
+  // Increments its reference count; there will be one reference for
+  // the cache plus one for the caller of this function.  Returns NULL
+  // on failure.
+  CMap *getCMap(GString *collection, GString *cMapName);
+
+private:
+
+  CMap *cache[cMapCacheSize];
+};
+
+#endif
diff --git a/xpdf/Catalog.cc b/xpdf/Catalog.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Catalog.cc
@@ -0,0 +1,374 @@
+//========================================================================
+//
+// Catalog.cc
+//
+// Copyright 1996-2007 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stddef.h>
+#include "gmem.h"
+#include "Object.h"
+#include "XRef.h"
+#include "Array.h"
+#include "Dict.h"
+#include "Page.h"
+#include "Error.h"
+#include "Link.h"
+#include "Catalog.h"
+
+//------------------------------------------------------------------------
+// Catalog
+//------------------------------------------------------------------------
+
+Catalog::Catalog(XRef *xrefA) {
+  Object catDict, pagesDict, pagesDictRef;
+  Object obj, obj2;
+  char *alreadyRead;
+  int numPages0;
+  int i;
+
+  ok = gTrue;
+  xref = xrefA;
+  pages = NULL;
+  pageRefs = NULL;
+  numPages = pagesSize = 0;
+  baseURI = NULL;
+
+  xref->getCatalog(&catDict);
+  if (!catDict.isDict()) {
+    error(-1, "Catalog object is wrong type (%s)", catDict.getTypeName());
+    goto err1;
+  }
+
+  // read page tree
+  catDict.dictLookup("Pages", &pagesDict);
+  // This should really be isDict("Pages"), but I've seen at least one
+  // PDF file where the /Type entry is missing.
+  if (!pagesDict.isDict()) {
+    error(-1, "Top-level pages object is wrong type (%s)",
+	  pagesDict.getTypeName());
+    goto err2;
+  }
+  pagesDict.dictLookup("Count", &obj);
+  // some PDF files actually use real numbers here ("/Count 9.0")
+  if (!obj.isNum()) {
+    error(-1, "Page count in top-level pages object is wrong type (%s)",
+	  obj.getTypeName());
+    goto err3;
+  }
+  pagesSize = numPages0 = (int)obj.getNum();
+  obj.free();
+  pages = (Page **)gmallocn(pagesSize, sizeof(Page *));
+  pageRefs = (Ref *)gmallocn(pagesSize, sizeof(Ref));
+  for (i = 0; i < pagesSize; ++i) {
+    pages[i] = NULL;
+    pageRefs[i].num = -1;
+    pageRefs[i].gen = -1;
+  }
+  alreadyRead = (char *)gmalloc(xref->getNumObjects());
+  memset(alreadyRead, 0, xref->getNumObjects());
+  if (catDict.dictLookupNF("Pages", &pagesDictRef)->isRef() &&
+      pagesDictRef.getRefNum() >= 0 &&
+      pagesDictRef.getRefNum() < xref->getNumObjects()) {
+    alreadyRead[pagesDictRef.getRefNum()] = 1;
+  }
+  pagesDictRef.free();
+  numPages = readPageTree(pagesDict.getDict(), NULL, 0, alreadyRead);
+  gfree(alreadyRead);
+  if (numPages != numPages0) {
+    error(-1, "Page count in top-level pages object is incorrect");
+  }
+  pagesDict.free();
+
+  // read named destination dictionary
+  catDict.dictLookup("Dests", &dests);
+
+  // read root of named destination tree
+  if (catDict.dictLookup("Names", &obj)->isDict())
+    obj.dictLookup("Dests", &nameTree);
+  else
+    nameTree.initNull();
+  obj.free();
+
+  // read base URI
+  if (catDict.dictLookup("URI", &obj)->isDict()) {
+    if (obj.dictLookup("Base", &obj2)->isString()) {
+      baseURI = obj2.getString()->copy();
+    }
+    obj2.free();
+  }
+  obj.free();
+
+  // get the metadata stream
+  catDict.dictLookup("Metadata", &metadata);
+
+  // get the structure tree root
+  catDict.dictLookup("StructTreeRoot", &structTreeRoot);
+
+  // get the outline dictionary
+  catDict.dictLookup("Outlines", &outline);
+
+  // get the AcroForm dictionary
+  catDict.dictLookup("AcroForm", &acroForm);
+
+  catDict.free();
+  return;
+
+ err3:
+  obj.free();
+ err2:
+  pagesDict.free();
+ err1:
+  catDict.free();
+  dests.initNull();
+  nameTree.initNull();
+  ok = gFalse;
+}
+
+Catalog::~Catalog() {
+  int i;
+
+  if (pages) {
+    for (i = 0; i < pagesSize; ++i) {
+      if (pages[i]) {
+	delete pages[i];
+      }
+    }
+    gfree(pages);
+    gfree(pageRefs);
+  }
+  dests.free();
+  nameTree.free();
+  if (baseURI) {
+    delete baseURI;
+  }
+  metadata.free();
+  structTreeRoot.free();
+  outline.free();
+  acroForm.free();
+}
+
+GString *Catalog::readMetadata() {
+  GString *s;
+  Dict *dict;
+  Object obj;
+  int c;
+
+  if (!metadata.isStream()) {
+    return NULL;
+  }
+  dict = metadata.streamGetDict();
+  if (!dict->lookup("Subtype", &obj)->isName("XML")) {
+    error(-1, "Unknown Metadata type: '%s'",
+	  obj.isName() ? obj.getName() : "???");
+  }
+  obj.free();
+  s = new GString();
+  metadata.streamReset();
+  while ((c = metadata.streamGetChar()) != EOF) {
+    s->append(c);
+  }
+  metadata.streamClose();
+  return s;
+}
+
+int Catalog::readPageTree(Dict *pagesDict, PageAttrs *attrs, int start,
+			  char *alreadyRead) {
+  Object kids;
+  Object kid;
+  Object kidRef;
+  PageAttrs *attrs1, *attrs2;
+  Page *page;
+  int i, j;
+
+  attrs1 = new PageAttrs(attrs, pagesDict);
+  pagesDict->lookup("Kids", &kids);
+  if (!kids.isArray()) {
+    error(-1, "Kids object (page %d) is wrong type (%s)",
+	  start+1, kids.getTypeName());
+    goto err1;
+  }
+  for (i = 0; i < kids.arrayGetLength(); ++i) {
+    kids.arrayGetNF(i, &kidRef);
+    if (kidRef.isRef() &&
+	kidRef.getRefNum() >= 0 &&
+	kidRef.getRefNum() < xref->getNumObjects()) {
+      if (alreadyRead[kidRef.getRefNum()]) {
+	error(-1, "Loop in Pages tree");
+	kidRef.free();
+	continue;
+      }
+      alreadyRead[kidRef.getRefNum()] = 1;
+    }
+    kids.arrayGet(i, &kid);
+    if (kid.isDict("Page")) {
+      attrs2 = new PageAttrs(attrs1, kid.getDict());
+      page = new Page(xref, start+1, kid.getDict(), attrs2);
+      if (!page->isOk()) {
+	++start;
+	goto err3;
+      }
+      if (start >= pagesSize) {
+	pagesSize += 32;
+	pages = (Page **)greallocn(pages, pagesSize, sizeof(Page *));
+	pageRefs = (Ref *)greallocn(pageRefs, pagesSize, sizeof(Ref));
+	for (j = pagesSize - 32; j < pagesSize; ++j) {
+	  pages[j] = NULL;
+	  pageRefs[j].num = -1;
+	  pageRefs[j].gen = -1;
+	}
+      }
+      pages[start] = page;
+      if (kidRef.isRef()) {
+	pageRefs[start].num = kidRef.getRefNum();
+	pageRefs[start].gen = kidRef.getRefGen();
+      }
+      ++start;
+    // This should really be isDict("Pages"), but I've seen at least one
+    // PDF file where the /Type entry is missing.
+    } else if (kid.isDict()) {
+      if ((start = readPageTree(kid.getDict(), attrs1, start, alreadyRead))
+	  < 0)
+	goto err2;
+    } else {
+      error(-1, "Kid object (page %d) is wrong type (%s)",
+	    start+1, kid.getTypeName());
+    }
+    kid.free();
+    kidRef.free();
+  }
+  delete attrs1;
+  kids.free();
+  return start;
+
+ err3:
+  delete page;
+ err2:
+  kid.free();
+ err1:
+  kids.free();
+  delete attrs1;
+  ok = gFalse;
+  return -1;
+}
+
+int Catalog::findPage(int num, int gen) {
+  int i;
+
+  for (i = 0; i < numPages; ++i) {
+    if (pageRefs[i].num == num && pageRefs[i].gen == gen)
+      return i + 1;
+  }
+  return 0;
+}
+
+LinkDest *Catalog::findDest(GString *name) {
+  LinkDest *dest;
+  Object obj1, obj2;
+  GBool found;
+
+  // try named destination dictionary then name tree
+  found = gFalse;
+  if (dests.isDict()) {
+    if (!dests.dictLookup(name->getCString(), &obj1)->isNull())
+      found = gTrue;
+    else
+      obj1.free();
+  }
+  if (!found && nameTree.isDict()) {
+    if (!findDestInTree(&nameTree, name, &obj1)->isNull())
+      found = gTrue;
+    else
+      obj1.free();
+  }
+  if (!found)
+    return NULL;
+
+  // construct LinkDest
+  dest = NULL;
+  if (obj1.isArray()) {
+    dest = new LinkDest(obj1.getArray());
+  } else if (obj1.isDict()) {
+    if (obj1.dictLookup("D", &obj2)->isArray())
+      dest = new LinkDest(obj2.getArray());
+    else
+      error(-1, "Bad named destination value");
+    obj2.free();
+  } else {
+    error(-1, "Bad named destination value");
+  }
+  obj1.free();
+  if (dest && !dest->isOk()) {
+    delete dest;
+    dest = NULL;
+  }
+
+  return dest;
+}
+
+Object *Catalog::findDestInTree(Object *tree, GString *name, Object *obj) {
+  Object names, name1;
+  Object kids, kid, limits, low, high;
+  GBool done, found;
+  int cmp, i;
+
+  // leaf node
+  if (tree->dictLookup("Names", &names)->isArray()) {
+    done = found = gFalse;
+    for (i = 0; !done && i < names.arrayGetLength(); i += 2) {
+      if (names.arrayGet(i, &name1)->isString()) {
+	cmp = name->cmp(name1.getString());
+	if (cmp == 0) {
+	  names.arrayGet(i+1, obj);
+	  found = gTrue;
+	  done = gTrue;
+	} else if (cmp < 0) {
+	  done = gTrue;
+	}
+      }
+      name1.free();
+    }
+    names.free();
+    if (!found)
+      obj->initNull();
+    return obj;
+  }
+  names.free();
+
+  // root or intermediate node
+  done = gFalse;
+  if (tree->dictLookup("Kids", &kids)->isArray()) {
+    for (i = 0; !done && i < kids.arrayGetLength(); ++i) {
+      if (kids.arrayGet(i, &kid)->isDict()) {
+	if (kid.dictLookup("Limits", &limits)->isArray()) {
+	  if (limits.arrayGet(0, &low)->isString() &&
+	      name->cmp(low.getString()) >= 0) {
+	    if (limits.arrayGet(1, &high)->isString() &&
+		name->cmp(high.getString()) <= 0) {
+	      findDestInTree(&kid, name, obj);
+	      done = gTrue;
+	    }
+	    high.free();
+	  }
+	  low.free();
+	}
+	limits.free();
+      }
+      kid.free();
+    }
+  }
+  kids.free();
+
+  // name was outside of ranges of all kids
+  if (!done)
+    obj->initNull();
+
+  return obj;
+}
diff --git a/xpdf/Catalog.h b/xpdf/Catalog.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Catalog.h
@@ -0,0 +1,97 @@
+//========================================================================
+//
+// Catalog.h
+//
+// Copyright 1996-2007 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef CATALOG_H
+#define CATALOG_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+class XRef;
+class Object;
+class Page;
+class PageAttrs;
+struct Ref;
+class LinkDest;
+
+//------------------------------------------------------------------------
+// Catalog
+//------------------------------------------------------------------------
+
+class Catalog {
+public:
+
+  // Constructor.
+  Catalog(XRef *xrefA);
+
+  // Destructor.
+  ~Catalog();
+
+  // Is catalog valid?
+  GBool isOk() { return ok; }
+
+  // Get number of pages.
+  int getNumPages() { return numPages; }
+
+  // Get a page.
+  Page *getPage(int i) { return pages[i-1]; }
+
+  // Get the reference for a page object.
+  Ref *getPageRef(int i) { return &pageRefs[i-1]; }
+
+  // Return base URI, or NULL if none.
+  GString *getBaseURI() { return baseURI; }
+
+  // Return the contents of the metadata stream, or NULL if there is
+  // no metadata.
+  GString *readMetadata();
+
+  // Return the structure tree root object.
+  Object *getStructTreeRoot() { return &structTreeRoot; }
+
+  // Find a page, given its object ID.  Returns page number, or 0 if
+  // not found.
+  int findPage(int num, int gen);
+
+  // Find a named destination.  Returns the link destination, or
+  // NULL if <name> is not a destination.
+  LinkDest *findDest(GString *name);
+
+  Object *getDests() { return &dests; }
+
+  Object *getNameTree() { return &nameTree; }
+
+  Object *getOutline() { return &outline; }
+
+  Object *getAcroForm() { return &acroForm; }
+
+private:
+
+  XRef *xref;			// the xref table for this PDF file
+  Page **pages;			// array of pages
+  Ref *pageRefs;		// object ID for each page
+  int numPages;			// number of pages
+  int pagesSize;		// size of pages array
+  Object dests;			// named destination dictionary
+  Object nameTree;		// name tree
+  GString *baseURI;		// base URI for URI-type links
+  Object metadata;		// metadata stream
+  Object structTreeRoot;	// structure tree root dictionary
+  Object outline;		// outline dictionary
+  Object acroForm;		// AcroForm dictionary
+  GBool ok;			// true if catalog is valid
+
+  int readPageTree(Dict *pages, PageAttrs *attrs, int start,
+		   char *alreadyRead);
+  Object *findDestInTree(Object *tree, GString *name, Object *obj);
+};
+
+#endif
diff --git a/xpdf/CharCodeToUnicode.cc b/xpdf/CharCodeToUnicode.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/CharCodeToUnicode.cc
@@ -0,0 +1,540 @@
+//========================================================================
+//
+// CharCodeToUnicode.cc
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "gmem.h"
+#include "gfile.h"
+#include "GString.h"
+#include "Error.h"
+#include "GlobalParams.h"
+#include "PSTokenizer.h"
+#include "CharCodeToUnicode.h"
+
+//------------------------------------------------------------------------
+
+#define maxUnicodeString 8
+
+struct CharCodeToUnicodeString {
+  CharCode c;
+  Unicode u[maxUnicodeString];
+  int len;
+};
+
+//------------------------------------------------------------------------
+
+static int getCharFromString(void *data) {
+  char *p;
+  int c;
+
+  p = *(char **)data;
+  if (*p) {
+    c = *p++;
+    *(char **)data = p;
+  } else {
+    c = EOF;
+  }
+  return c;
+}
+
+static int getCharFromFile(void *data) {
+  return fgetc((FILE *)data);
+}
+
+//------------------------------------------------------------------------
+
+CharCodeToUnicode *CharCodeToUnicode::parseCIDToUnicode(GString *fileName,
+							GString *collection) {
+  FILE *f;
+  Unicode *mapA;
+  CharCode size, mapLenA;
+  char buf[64];
+  Unicode u;
+  CharCodeToUnicode *ctu;
+
+  if (!(f = fopen(fileName->getCString(), "r"))) {
+    error(-1, "Couldn't open cidToUnicode file '%s'",
+	  fileName->getCString());
+    return NULL;
+  }
+
+  size = 32768;
+  mapA = (Unicode *)gmallocn(size, sizeof(Unicode));
+  mapLenA = 0;
+
+  while (getLine(buf, sizeof(buf), f)) {
+    if (mapLenA == size) {
+      size *= 2;
+      mapA = (Unicode *)greallocn(mapA, size, sizeof(Unicode));
+    }
+    if (sscanf(buf, "%x", &u) == 1) {
+      mapA[mapLenA] = u;
+    } else {
+      error(-1, "Bad line (%d) in cidToUnicode file '%s'",
+	    (int)(mapLenA + 1), fileName->getCString());
+      mapA[mapLenA] = 0;
+    }
+    ++mapLenA;
+  }
+  fclose(f);
+
+  ctu = new CharCodeToUnicode(collection->copy(), mapA, mapLenA, gTrue,
+			      NULL, 0, 0);
+  gfree(mapA);
+  return ctu;
+}
+
+CharCodeToUnicode *CharCodeToUnicode::parseUnicodeToUnicode(
+						    GString *fileName) {
+  FILE *f;
+  Unicode *mapA;
+  CharCodeToUnicodeString *sMapA;
+  CharCode size, oldSize, len, sMapSizeA, sMapLenA;
+  char buf[256];
+  char *tok;
+  Unicode u0;
+  Unicode uBuf[maxUnicodeString];
+  CharCodeToUnicode *ctu;
+  int line, n, i;
+
+  if (!(f = fopen(fileName->getCString(), "r"))) {
+    error(-1, "Couldn't open unicodeToUnicode file '%s'",
+	  fileName->getCString());
+    return NULL;
+  }
+
+  size = 4096;
+  mapA = (Unicode *)gmallocn(size, sizeof(Unicode));
+  memset(mapA, 0, size * sizeof(Unicode));
+  len = 0;
+  sMapA = NULL;
+  sMapSizeA = sMapLenA = 0;
+
+  line = 0;
+  while (getLine(buf, sizeof(buf), f)) {
+    ++line;
+    if (!(tok = strtok(buf, " \t\r\n")) ||
+	sscanf(tok, "%x", &u0) != 1) {
+      error(-1, "Bad line (%d) in unicodeToUnicode file '%s'",
+	    line, fileName->getCString());
+      continue;
+    }
+    n = 0;
+    while (n < maxUnicodeString) {
+      if (!(tok = strtok(NULL, " \t\r\n"))) {
+	break;
+      }
+      if (sscanf(tok, "%x", &uBuf[n]) != 1) {
+	error(-1, "Bad line (%d) in unicodeToUnicode file '%s'",
+	      line, fileName->getCString());
+	break;
+      }
+      ++n;
+    }
+    if (n < 1) {
+      error(-1, "Bad line (%d) in unicodeToUnicode file '%s'",
+	    line, fileName->getCString());
+      continue;
+    }
+    if (u0 >= size) {
+      oldSize = size;
+      while (u0 >= size) {
+	size *= 2;
+      }
+      mapA = (Unicode *)greallocn(mapA, size, sizeof(Unicode));
+      memset(mapA + oldSize, 0, (size - oldSize) * sizeof(Unicode));
+    }
+    if (n == 1) {
+      mapA[u0] = uBuf[0];
+    } else {
+      mapA[u0] = 0;
+      if (sMapLenA == sMapSizeA) {
+	sMapSizeA += 16;
+	sMapA = (CharCodeToUnicodeString *)
+	          greallocn(sMapA, sMapSizeA, sizeof(CharCodeToUnicodeString));
+      }
+      sMapA[sMapLenA].c = u0;
+      for (i = 0; i < n; ++i) {
+	sMapA[sMapLenA].u[i] = uBuf[i];
+      }
+      sMapA[sMapLenA].len = n;
+      ++sMapLenA;
+    }
+    if (u0 >= len) {
+      len = u0 + 1;
+    }
+  }
+  fclose(f);
+
+  ctu = new CharCodeToUnicode(fileName->copy(), mapA, len, gTrue,
+			      sMapA, sMapLenA, sMapSizeA);
+  gfree(mapA);
+  return ctu;
+}
+
+CharCodeToUnicode *CharCodeToUnicode::make8BitToUnicode(Unicode *toUnicode) {
+  return new CharCodeToUnicode(NULL, toUnicode, 256, gTrue, NULL, 0, 0);
+}
+
+CharCodeToUnicode *CharCodeToUnicode::parseCMap(GString *buf, int nBits) {
+  CharCodeToUnicode *ctu;
+  char *p;
+
+  ctu = new CharCodeToUnicode(NULL);
+  p = buf->getCString();
+  ctu->parseCMap1(&getCharFromString, &p, nBits);
+  return ctu;
+}
+
+void CharCodeToUnicode::mergeCMap(GString *buf, int nBits) {
+  char *p;
+
+  p = buf->getCString();
+  parseCMap1(&getCharFromString, &p, nBits);
+}
+
+void CharCodeToUnicode::parseCMap1(int (*getCharFunc)(void *), void *data,
+				   int nBits) {
+  PSTokenizer *pst;
+  char tok1[256], tok2[256], tok3[256];
+  int nDigits, n1, n2, n3;
+  CharCode i;
+  CharCode code1, code2;
+  GString *name;
+  FILE *f;
+
+  nDigits = nBits / 4;
+  pst = new PSTokenizer(getCharFunc, data);
+  pst->getToken(tok1, sizeof(tok1), &n1);
+  while (pst->getToken(tok2, sizeof(tok2), &n2)) {
+    if (!strcmp(tok2, "usecmap")) {
+      if (tok1[0] == '/') {
+	name = new GString(tok1 + 1);
+	if ((f = globalParams->findToUnicodeFile(name))) {
+	  parseCMap1(&getCharFromFile, f, nBits);
+	  fclose(f);
+	} else {
+	  error(-1, "Couldn't find ToUnicode CMap file for '%s'",
+		name->getCString());
+	}
+	delete name;
+      }
+      pst->getToken(tok1, sizeof(tok1), &n1);
+    } else if (!strcmp(tok2, "beginbfchar")) {
+      while (pst->getToken(tok1, sizeof(tok1), &n1)) {
+	if (!strcmp(tok1, "endbfchar")) {
+	  break;
+	}
+	if (!pst->getToken(tok2, sizeof(tok2), &n2) ||
+	    !strcmp(tok2, "endbfchar")) {
+	  error(-1, "Illegal entry in bfchar block in ToUnicode CMap");
+	  break;
+	}
+	if (!(n1 == 2 + nDigits && tok1[0] == '<' && tok1[n1 - 1] == '>' &&
+	      tok2[0] == '<' && tok2[n2 - 1] == '>')) {
+	  error(-1, "Illegal entry in bfchar block in ToUnicode CMap");
+	  continue;
+	}
+	tok1[n1 - 1] = tok2[n2 - 1] = '\0';
+	if (sscanf(tok1 + 1, "%x", &code1) != 1) {
+	  error(-1, "Illegal entry in bfchar block in ToUnicode CMap");
+	  continue;
+	}
+	addMapping(code1, tok2 + 1, n2 - 2, 0);
+      }
+      pst->getToken(tok1, sizeof(tok1), &n1);
+    } else if (!strcmp(tok2, "beginbfrange")) {
+      while (pst->getToken(tok1, sizeof(tok1), &n1)) {
+	if (!strcmp(tok1, "endbfrange")) {
+	  break;
+	}
+	if (!pst->getToken(tok2, sizeof(tok2), &n2) ||
+	    !strcmp(tok2, "endbfrange") ||
+	    !pst->getToken(tok3, sizeof(tok3), &n3) ||
+	    !strcmp(tok3, "endbfrange")) {
+	  error(-1, "Illegal entry in bfrange block in ToUnicode CMap");
+	  break;
+	}
+	if (!(n1 == 2 + nDigits && tok1[0] == '<' && tok1[n1 - 1] == '>' &&
+	      n2 == 2 + nDigits && tok2[0] == '<' && tok2[n2 - 1] == '>')) {
+	  error(-1, "Illegal entry in bfrange block in ToUnicode CMap");
+	  continue;
+	}
+	tok1[n1 - 1] = tok2[n2 - 1] = '\0';
+	if (sscanf(tok1 + 1, "%x", &code1) != 1 ||
+	    sscanf(tok2 + 1, "%x", &code2) != 1) {
+	  error(-1, "Illegal entry in bfrange block in ToUnicode CMap");
+	  continue;
+	}
+	if (!strcmp(tok3, "[")) {
+	  i = 0;
+	  while (pst->getToken(tok1, sizeof(tok1), &n1) &&
+		 code1 + i <= code2) {
+	    if (!strcmp(tok1, "]")) {
+	      break;
+	    }
+	    if (tok1[0] == '<' && tok1[n1 - 1] == '>') {
+	      tok1[n1 - 1] = '\0';
+	      addMapping(code1 + i, tok1 + 1, n1 - 2, 0);
+	    } else {
+	      error(-1, "Illegal entry in bfrange block in ToUnicode CMap");
+	    }
+	    ++i;
+	  }
+	} else if (tok3[0] == '<' && tok3[n3 - 1] == '>') {
+	  tok3[n3 - 1] = '\0';
+	  for (i = 0; code1 <= code2; ++code1, ++i) {
+	    addMapping(code1, tok3 + 1, n3 - 2, i);
+	  }
+
+	} else {
+	  error(-1, "Illegal entry in bfrange block in ToUnicode CMap");
+	}
+      }
+      pst->getToken(tok1, sizeof(tok1), &n1);
+    } else {
+      strcpy(tok1, tok2);
+    }
+  }
+  delete pst;
+}
+
+void CharCodeToUnicode::addMapping(CharCode code, char *uStr, int n,
+				   int offset) {
+  CharCode oldLen, i;
+  Unicode u;
+  char uHex[5];
+  int j;
+
+  if (code >= mapLen) {
+    oldLen = mapLen;
+    mapLen = (code + 256) & ~255;
+    map = (Unicode *)greallocn(map, mapLen, sizeof(Unicode));
+    for (i = oldLen; i < mapLen; ++i) {
+      map[i] = 0;
+    }
+  }
+  if (n <= 4) {
+    if (sscanf(uStr, "%x", &u) != 1) {
+      error(-1, "Illegal entry in ToUnicode CMap");
+      return;
+    }
+    map[code] = u + offset;
+  } else {
+    if (sMapLen >= sMapSize) {
+      sMapSize = sMapSize + 16;
+      sMap = (CharCodeToUnicodeString *)
+	       greallocn(sMap, sMapSize, sizeof(CharCodeToUnicodeString));
+    }
+    map[code] = 0;
+    sMap[sMapLen].c = code;
+    sMap[sMapLen].len = n / 4;
+    for (j = 0; j < sMap[sMapLen].len && j < maxUnicodeString; ++j) {
+      strncpy(uHex, uStr + j*4, 4);
+      uHex[4] = '\0';
+      if (sscanf(uHex, "%x", &sMap[sMapLen].u[j]) != 1) {
+	error(-1, "Illegal entry in ToUnicode CMap");
+      }
+    }
+    sMap[sMapLen].u[sMap[sMapLen].len - 1] += offset;
+    ++sMapLen;
+  }
+}
+
+CharCodeToUnicode::CharCodeToUnicode(GString *tagA) {
+  CharCode i;
+
+  tag = tagA;
+  mapLen = 256;
+  map = (Unicode *)gmallocn(mapLen, sizeof(Unicode));
+  for (i = 0; i < mapLen; ++i) {
+    map[i] = 0;
+  }
+  sMap = NULL;
+  sMapLen = sMapSize = 0;
+  refCnt = 1;
+#if MULTITHREADED
+  gInitMutex(&mutex);
+#endif
+}
+
+CharCodeToUnicode::CharCodeToUnicode(GString *tagA, Unicode *mapA,
+				     CharCode mapLenA, GBool copyMap,
+				     CharCodeToUnicodeString *sMapA,
+				     int sMapLenA, int sMapSizeA) {
+  tag = tagA;
+  mapLen = mapLenA;
+  if (copyMap) {
+    map = (Unicode *)gmallocn(mapLen, sizeof(Unicode));
+    memcpy(map, mapA, mapLen * sizeof(Unicode));
+  } else {
+    map = mapA;
+  }
+  sMap = sMapA;
+  sMapLen = sMapLenA;
+  sMapSize = sMapSizeA;
+  refCnt = 1;
+#if MULTITHREADED
+  gInitMutex(&mutex);
+#endif
+}
+
+CharCodeToUnicode::~CharCodeToUnicode() {
+  if (tag) {
+    delete tag;
+  }
+  gfree(map);
+  if (sMap) {
+    gfree(sMap);
+  }
+#if MULTITHREADED
+  gDestroyMutex(&mutex);
+#endif
+}
+
+void CharCodeToUnicode::incRefCnt() {
+#if MULTITHREADED
+  gLockMutex(&mutex);
+#endif
+  ++refCnt;
+#if MULTITHREADED
+  gUnlockMutex(&mutex);
+#endif
+}
+
+void CharCodeToUnicode::decRefCnt() {
+  GBool done;
+
+#if MULTITHREADED
+  gLockMutex(&mutex);
+#endif
+  done = --refCnt == 0;
+#if MULTITHREADED
+  gUnlockMutex(&mutex);
+#endif
+  if (done) {
+    delete this;
+  }
+}
+
+GBool CharCodeToUnicode::match(GString *tagA) {
+  return tag && !tag->cmp(tagA);
+}
+
+void CharCodeToUnicode::setMapping(CharCode c, Unicode *u, int len) {
+  int i, j;
+
+  if (len == 1) {
+    map[c] = u[0];
+  } else {
+    for (i = 0; i < sMapLen; ++i) {
+      if (sMap[i].c == c) {
+	break;
+      }
+    }
+    if (i == sMapLen) {
+      if (sMapLen == sMapSize) {
+	sMapSize += 8;
+	sMap = (CharCodeToUnicodeString *)
+	         greallocn(sMap, sMapSize, sizeof(CharCodeToUnicodeString));
+      }
+      ++sMapLen;
+    }
+    map[c] = 0;
+    sMap[i].c = c;
+    sMap[i].len = len;
+    for (j = 0; j < len && j < maxUnicodeString; ++j) {
+      sMap[i].u[j] = u[j];
+    }
+  }
+}
+
+int CharCodeToUnicode::mapToUnicode(CharCode c, Unicode *u, int size) {
+  int i, j;
+
+  if (c >= mapLen) {
+    return 0;
+  }
+  if (map[c]) {
+    u[0] = map[c];
+    return 1;
+  }
+  for (i = 0; i < sMapLen; ++i) {
+    if (sMap[i].c == c) {
+      for (j = 0; j < sMap[i].len && j < size; ++j) {
+	u[j] = sMap[i].u[j];
+      }
+      return j;
+    }
+  }
+  return 0;
+}
+
+//------------------------------------------------------------------------
+
+CharCodeToUnicodeCache::CharCodeToUnicodeCache(int sizeA) {
+  int i;
+
+  size = sizeA;
+  cache = (CharCodeToUnicode **)gmallocn(size, sizeof(CharCodeToUnicode *));
+  for (i = 0; i < size; ++i) {
+    cache[i] = NULL;
+  }
+}
+
+CharCodeToUnicodeCache::~CharCodeToUnicodeCache() {
+  int i;
+
+  for (i = 0; i < size; ++i) {
+    if (cache[i]) {
+      cache[i]->decRefCnt();
+    }
+  }
+  gfree(cache);
+}
+
+CharCodeToUnicode *CharCodeToUnicodeCache::getCharCodeToUnicode(GString *tag) {
+  CharCodeToUnicode *ctu;
+  int i, j;
+
+  if (cache[0] && cache[0]->match(tag)) {
+    cache[0]->incRefCnt();
+    return cache[0];
+  }
+  for (i = 1; i < size; ++i) {
+    if (cache[i] && cache[i]->match(tag)) {
+      ctu = cache[i];
+      for (j = i; j >= 1; --j) {
+	cache[j] = cache[j - 1];
+      }
+      cache[0] = ctu;
+      ctu->incRefCnt();
+      return ctu;
+    }
+  }
+  return NULL;
+}
+
+void CharCodeToUnicodeCache::add(CharCodeToUnicode *ctu) {
+  int i;
+
+  if (cache[size - 1]) {
+    cache[size - 1]->decRefCnt();
+  }
+  for (i = size - 1; i >= 1; --i) {
+    cache[i] = cache[i - 1];
+  }
+  cache[0] = ctu;
+  ctu->incRefCnt();
+}
diff --git a/xpdf/CharCodeToUnicode.h b/xpdf/CharCodeToUnicode.h
new file mode 100644
--- /dev/null
+++ b/xpdf/CharCodeToUnicode.h
@@ -0,0 +1,117 @@
+//========================================================================
+//
+// CharCodeToUnicode.h
+//
+// Mapping from character codes to Unicode.
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef CHARCODETOUNICODE_H
+#define CHARCODETOUNICODE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "CharTypes.h"
+
+#if MULTITHREADED
+#include "GMutex.h"
+#endif
+
+struct CharCodeToUnicodeString;
+
+//------------------------------------------------------------------------
+
+class CharCodeToUnicode {
+public:
+
+  // Read the CID-to-Unicode mapping for <collection> from the file
+  // specified by <fileName>.  Sets the initial reference count to 1.
+  // Returns NULL on failure.
+  static CharCodeToUnicode *parseCIDToUnicode(GString *fileName,
+					      GString *collection);
+
+  // Create a Unicode-to-Unicode mapping from the file specified by
+  // <fileName>.  Sets the initial reference count to 1.  Returns NULL
+  // on failure.
+  static CharCodeToUnicode *parseUnicodeToUnicode(GString *fileName);
+
+  // Create the CharCode-to-Unicode mapping for an 8-bit font.
+  // <toUnicode> is an array of 256 Unicode indexes.  Sets the initial
+  // reference count to 1.
+  static CharCodeToUnicode *make8BitToUnicode(Unicode *toUnicode);
+
+  // Parse a ToUnicode CMap for an 8- or 16-bit font.
+  static CharCodeToUnicode *parseCMap(GString *buf, int nBits);
+
+  // Parse a ToUnicode CMap for an 8- or 16-bit font, merging it into
+  // <this>.
+  void mergeCMap(GString *buf, int nBits);
+
+  ~CharCodeToUnicode();
+
+  void incRefCnt();
+  void decRefCnt();
+
+  // Return true if this mapping matches the specified <tagA>.
+  GBool match(GString *tagA);
+
+  // Set the mapping for <c>.
+  void setMapping(CharCode c, Unicode *u, int len);
+
+  // Map a CharCode to Unicode.
+  int mapToUnicode(CharCode c, Unicode *u, int size);
+
+  // Return the mapping's length, i.e., one more than the max char
+  // code supported by the mapping.
+  CharCode getLength() { return mapLen; }
+
+private:
+
+  void parseCMap1(int (*getCharFunc)(void *), void *data, int nBits);
+  void addMapping(CharCode code, char *uStr, int n, int offset);
+  CharCodeToUnicode(GString *tagA);
+  CharCodeToUnicode(GString *tagA, Unicode *mapA,
+		    CharCode mapLenA, GBool copyMap,
+		    CharCodeToUnicodeString *sMapA,
+		    int sMapLenA, int sMapSizeA);
+
+  GString *tag;
+  Unicode *map;
+  CharCode mapLen;
+  CharCodeToUnicodeString *sMap;
+  int sMapLen, sMapSize;
+  int refCnt;
+#if MULTITHREADED
+  GMutex mutex;
+#endif
+};
+
+//------------------------------------------------------------------------
+
+class CharCodeToUnicodeCache {
+public:
+
+  CharCodeToUnicodeCache(int sizeA);
+  ~CharCodeToUnicodeCache();
+
+  // Get the CharCodeToUnicode object for <tag>.  Increments its
+  // reference count; there will be one reference for the cache plus
+  // one for the caller of this function.  Returns NULL on failure.
+  CharCodeToUnicode *getCharCodeToUnicode(GString *tag);
+
+  // Insert <ctu> into the cache, in the most-recently-used position.
+  void add(CharCodeToUnicode *ctu);
+
+private:
+
+  CharCodeToUnicode **cache;
+  int size;
+};
+
+#endif
diff --git a/xpdf/CharTypes.h b/xpdf/CharTypes.h
new file mode 100644
--- /dev/null
+++ b/xpdf/CharTypes.h
@@ -0,0 +1,24 @@
+//========================================================================
+//
+// CharTypes.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef CHARTYPES_H
+#define CHARTYPES_H
+
+// Unicode character.
+typedef unsigned int Unicode;
+
+// Character ID for CID character collections.
+typedef unsigned int CID;
+
+// This is large enough to hold any of the following:
+// - 8-bit char code
+// - 16-bit CID
+// - Unicode
+typedef unsigned int CharCode;
+
+#endif
diff --git a/xpdf/CompactFontTables.h b/xpdf/CompactFontTables.h
new file mode 100644
--- /dev/null
+++ b/xpdf/CompactFontTables.h
@@ -0,0 +1,464 @@
+//========================================================================
+//
+// CompactFontTables.h
+//
+// Copyright 1999-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef COMPACTFONTINFO_H
+#define COMPACTFONTINFO_H
+
+static char *type1CStdStrings[391] = {
+  ".notdef",
+  "space",
+  "exclam",
+  "quotedbl",
+  "numbersign",
+  "dollar",
+  "percent",
+  "ampersand",
+  "quoteright",
+  "parenleft",
+  "parenright",
+  "asterisk",
+  "plus",
+  "comma",
+  "hyphen",
+  "period",
+  "slash",
+  "zero",
+  "one",
+  "two",
+  "three",
+  "four",
+  "five",
+  "six",
+  "seven",
+  "eight",
+  "nine",
+  "colon",
+  "semicolon",
+  "less",
+  "equal",
+  "greater",
+  "question",
+  "at",
+  "A",
+  "B",
+  "C",
+  "D",
+  "E",
+  "F",
+  "G",
+  "H",
+  "I",
+  "J",
+  "K",
+  "L",
+  "M",
+  "N",
+  "O",
+  "P",
+  "Q",
+  "R",
+  "S",
+  "T",
+  "U",
+  "V",
+  "W",
+  "X",
+  "Y",
+  "Z",
+  "bracketleft",
+  "backslash",
+  "bracketright",
+  "asciicircum",
+  "underscore",
+  "quoteleft",
+  "a",
+  "b",
+  "c",
+  "d",
+  "e",
+  "f",
+  "g",
+  "h",
+  "i",
+  "j",
+  "k",
+  "l",
+  "m",
+  "n",
+  "o",
+  "p",
+  "q",
+  "r",
+  "s",
+  "t",
+  "u",
+  "v",
+  "w",
+  "x",
+  "y",
+  "z",
+  "braceleft",
+  "bar",
+  "braceright",
+  "asciitilde",
+  "exclamdown",
+  "cent",
+  "sterling",
+  "fraction",
+  "yen",
+  "florin",
+  "section",
+  "currency",
+  "quotesingle",
+  "quotedblleft",
+  "guillemotleft",
+  "guilsinglleft",
+  "guilsinglright",
+  "fi",
+  "fl",
+  "endash",
+  "dagger",
+  "daggerdbl",
+  "periodcentered",
+  "paragraph",
+  "bullet",
+  "quotesinglbase",
+  "quotedblbase",
+  "quotedblright",
+  "guillemotright",
+  "ellipsis",
+  "perthousand",
+  "questiondown",
+  "grave",
+  "acute",
+  "circumflex",
+  "tilde",
+  "macron",
+  "breve",
+  "dotaccent",
+  "dieresis",
+  "ring",
+  "cedilla",
+  "hungarumlaut",
+  "ogonek",
+  "caron",
+  "emdash",
+  "AE",
+  "ordfeminine",
+  "Lslash",
+  "Oslash",
+  "OE",
+  "ordmasculine",
+  "ae",
+  "dotlessi",
+  "lslash",
+  "oslash",
+  "oe",
+  "germandbls",
+  "onesuperior",
+  "logicalnot",
+  "mu",
+  "trademark",
+  "Eth",
+  "onehalf",
+  "plusminus",
+  "Thorn",
+  "onequarter",
+  "divide",
+  "brokenbar",
+  "degree",
+  "thorn",
+  "threequarters",
+  "twosuperior",
+  "registered",
+  "minus",
+  "eth",
+  "multiply",
+  "threesuperior",
+  "copyright",
+  "Aacute",
+  "Acircumflex",
+  "Adieresis",
+  "Agrave",
+  "Aring",
+  "Atilde",
+  "Ccedilla",
+  "Eacute",
+  "Ecircumflex",
+  "Edieresis",
+  "Egrave",
+  "Iacute",
+  "Icircumflex",
+  "Idieresis",
+  "Igrave",
+  "Ntilde",
+  "Oacute",
+  "Ocircumflex",
+  "Odieresis",
+  "Ograve",
+  "Otilde",
+  "Scaron",
+  "Uacute",
+  "Ucircumflex",
+  "Udieresis",
+  "Ugrave",
+  "Yacute",
+  "Ydieresis",
+  "Zcaron",
+  "aacute",
+  "acircumflex",
+  "adieresis",
+  "agrave",
+  "aring",
+  "atilde",
+  "ccedilla",
+  "eacute",
+  "ecircumflex",
+  "edieresis",
+  "egrave",
+  "iacute",
+  "icircumflex",
+  "idieresis",
+  "igrave",
+  "ntilde",
+  "oacute",
+  "ocircumflex",
+  "odieresis",
+  "ograve",
+  "otilde",
+  "scaron",
+  "uacute",
+  "ucircumflex",
+  "udieresis",
+  "ugrave",
+  "yacute",
+  "ydieresis",
+  "zcaron",
+  "exclamsmall",
+  "Hungarumlautsmall",
+  "dollaroldstyle",
+  "dollarsuperior",
+  "ampersandsmall",
+  "Acutesmall",
+  "parenleftsuperior",
+  "parenrightsuperior",
+  "twodotenleader",
+  "onedotenleader",
+  "zerooldstyle",
+  "oneoldstyle",
+  "twooldstyle",
+  "threeoldstyle",
+  "fouroldstyle",
+  "fiveoldstyle",
+  "sixoldstyle",
+  "sevenoldstyle",
+  "eightoldstyle",
+  "nineoldstyle",
+  "commasuperior",
+  "threequartersemdash",
+  "periodsuperior",
+  "questionsmall",
+  "asuperior",
+  "bsuperior",
+  "centsuperior",
+  "dsuperior",
+  "esuperior",
+  "isuperior",
+  "lsuperior",
+  "msuperior",
+  "nsuperior",
+  "osuperior",
+  "rsuperior",
+  "ssuperior",
+  "tsuperior",
+  "ff",
+  "ffi",
+  "ffl",
+  "parenleftinferior",
+  "parenrightinferior",
+  "Circumflexsmall",
+  "hyphensuperior",
+  "Gravesmall",
+  "Asmall",
+  "Bsmall",
+  "Csmall",
+  "Dsmall",
+  "Esmall",
+  "Fsmall",
+  "Gsmall",
+  "Hsmall",
+  "Ismall",
+  "Jsmall",
+  "Ksmall",
+  "Lsmall",
+  "Msmall",
+  "Nsmall",
+  "Osmall",
+  "Psmall",
+  "Qsmall",
+  "Rsmall",
+  "Ssmall",
+  "Tsmall",
+  "Usmall",
+  "Vsmall",
+  "Wsmall",
+  "Xsmall",
+  "Ysmall",
+  "Zsmall",
+  "colonmonetary",
+  "onefitted",
+  "rupiah",
+  "Tildesmall",
+  "exclamdownsmall",
+  "centoldstyle",
+  "Lslashsmall",
+  "Scaronsmall",
+  "Zcaronsmall",
+  "Dieresissmall",
+  "Brevesmall",
+  "Caronsmall",
+  "Dotaccentsmall",
+  "Macronsmall",
+  "figuredash",
+  "hypheninferior",
+  "Ogoneksmall",
+  "Ringsmall",
+  "Cedillasmall",
+  "questiondownsmall",
+  "oneeighth",
+  "threeeighths",
+  "fiveeighths",
+  "seveneighths",
+  "onethird",
+  "twothirds",
+  "zerosuperior",
+  "foursuperior",
+  "fivesuperior",
+  "sixsuperior",
+  "sevensuperior",
+  "eightsuperior",
+  "ninesuperior",
+  "zeroinferior",
+  "oneinferior",
+  "twoinferior",
+  "threeinferior",
+  "fourinferior",
+  "fiveinferior",
+  "sixinferior",
+  "seveninferior",
+  "eightinferior",
+  "nineinferior",
+  "centinferior",
+  "dollarinferior",
+  "periodinferior",
+  "commainferior",
+  "Agravesmall",
+  "Aacutesmall",
+  "Acircumflexsmall",
+  "Atildesmall",
+  "Adieresissmall",
+  "Aringsmall",
+  "AEsmall",
+  "Ccedillasmall",
+  "Egravesmall",
+  "Eacutesmall",
+  "Ecircumflexsmall",
+  "Edieresissmall",
+  "Igravesmall",
+  "Iacutesmall",
+  "Icircumflexsmall",
+  "Idieresissmall",
+  "Ethsmall",
+  "Ntildesmall",
+  "Ogravesmall",
+  "Oacutesmall",
+  "Ocircumflexsmall",
+  "Otildesmall",
+  "Odieresissmall",
+  "OEsmall",
+  "Oslashsmall",
+  "Ugravesmall",
+  "Uacutesmall",
+  "Ucircumflexsmall",
+  "Udieresissmall",
+  "Yacutesmall",
+  "Thornsmall",
+  "Ydieresissmall",
+  "001.000",
+  "001.001",
+  "001.002",
+  "001.003",
+  "Black",
+  "Bold",
+  "Book",
+  "Light",
+  "Medium",
+  "Regular",
+  "Roman",
+  "Semibold"
+};
+
+static Gushort type1CISOAdobeCharset[229] = {
+    0,   1,   2,   3,   4,   5,   6,   7,   8,   9,
+   10,  11,  12,  13,  14,  15,  16,  17,  18,  19,
+   20,  21,  22,  23,  24,  25,  26,  27,  28,  29,
+   30,  31,  32,  33,  34,  35,  36,  37,  38,  39,
+   40,  41,  42,  43,  44,  45,  46,  47,  48,  49,
+   50,  51,  52,  53,  54,  55,  56,  57,  58,  59,
+   60,  61,  62,  63,  64,  65,  66,  67,  68,  69,
+   70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
+   80,  81,  82,  83,  84,  85,  86,  87,  88,  89,
+   90,  91,  92,  93,  94,  95,  96,  97,  98,  99,
+  100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+  110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+  120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
+  130, 131, 132, 133, 134, 135, 136, 137, 138, 139,
+  140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+  150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+  160, 161, 162, 163, 164, 165, 166, 167, 168, 169,
+  170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+  180, 181, 182, 183, 184, 185, 186, 187, 188, 189,
+  190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
+  200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+  210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
+  220, 221, 222, 223, 224, 225, 226, 227, 228
+};
+
+static Gushort type1CExpertCharset[166] = {
+    0,   1, 229, 230, 231, 232, 233, 234, 235, 236,
+  237, 238,  13,  14,  15,  99, 239, 240, 241, 242,
+  243, 244, 245, 246, 247, 248,  27,  28, 249, 250,
+  251, 252, 253, 254, 255, 256, 257, 258, 259, 260,
+  261, 262, 263, 264, 265, 266, 109, 110, 267, 268,
+  269, 270, 271, 272, 273, 274, 275, 276, 277, 278,
+  279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
+  289, 290, 291, 292, 293, 294, 295, 296, 297, 298,
+  299, 300, 301, 302, 303, 304, 305, 306, 307, 308,
+  309, 310, 311, 312, 313, 314, 315, 316, 317, 318,
+  158, 155, 163, 319, 320, 321, 322, 323, 324, 325,
+  326, 150, 164, 169, 327, 328, 329, 330, 331, 332,
+  333, 334, 335, 336, 337, 338, 339, 340, 341, 342,
+  343, 344, 345, 346, 347, 348, 349, 350, 351, 352,
+  353, 354, 355, 356, 357, 358, 359, 360, 361, 362,
+  363, 364, 365, 366, 367, 368, 369, 370, 371, 372,
+  373, 374, 375, 376, 377, 378
+};
+
+static Gushort type1CExpertSubsetCharset[87] = {
+    0,   1, 231, 232, 235, 236, 237, 238,  13,  14,
+   15,  99, 239, 240, 241, 242, 243, 244, 245, 246,
+  247, 248,  27,  28, 249, 250, 251, 253, 254, 255,
+  256, 257, 258, 259, 260, 261, 262, 263, 264, 265,
+  266, 109, 110, 267, 268, 269, 270, 272, 300, 301,
+  302, 305, 314, 315, 158, 155, 163, 320, 321, 322,
+  323, 324, 325, 326, 150, 164, 169, 327, 328, 329,
+  330, 331, 332, 333, 334, 335, 336, 337, 338, 339,
+  340, 341, 342, 343, 344, 345, 346
+};
+
+#endif
diff --git a/xpdf/CoreOutputDev.cc b/xpdf/CoreOutputDev.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/CoreOutputDev.cc
@@ -0,0 +1,61 @@
+//========================================================================
+//
+// CoreOutputDev.cc
+//
+// Copyright 2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include "Object.h"
+#include "TextOutputDev.h"
+#include "CoreOutputDev.h"
+
+//------------------------------------------------------------------------
+// CoreOutputDev
+//------------------------------------------------------------------------
+
+CoreOutputDev::CoreOutputDev(SplashColorMode colorModeA, int bitmapRowPadA,
+			     GBool reverseVideoA, SplashColorPtr paperColorA,
+			     GBool incrementalUpdateA,
+			     CoreOutRedrawCbk redrawCbkA,
+			     void *redrawCbkDataA):
+  SplashOutputDev(colorModeA, bitmapRowPadA, reverseVideoA, paperColorA)
+{
+  incrementalUpdate = incrementalUpdateA;
+  redrawCbk = redrawCbkA;
+  redrawCbkData = redrawCbkDataA;
+}
+
+CoreOutputDev::~CoreOutputDev() {
+}
+
+void CoreOutputDev::endPage() {
+  SplashOutputDev::endPage();
+  if (!incrementalUpdate) {
+    (*redrawCbk)(redrawCbkData, 0, 0, getBitmapWidth(), getBitmapHeight(),
+		 gTrue);
+  }
+}
+
+void CoreOutputDev::dump() {
+  int x0, y0, x1, y1;
+
+  if (incrementalUpdate) {
+    getModRegion(&x0, &y0, &x1, &y1);
+    clearModRegion();
+    if (x1 >= x0 && y1 >= y0) {
+      (*redrawCbk)(redrawCbkData, x0, y0, x1, y1, gFalse);
+    }
+  }
+}
+
+void CoreOutputDev::clear() {
+  startDoc(NULL);
+  startPage(0, NULL);
+}
diff --git a/xpdf/CoreOutputDev.h b/xpdf/CoreOutputDev.h
new file mode 100644
--- /dev/null
+++ b/xpdf/CoreOutputDev.h
@@ -0,0 +1,61 @@
+//========================================================================
+//
+// CoreOutputDev.h
+//
+// Copyright 2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef COREOUTPUTDEV_H
+#define COREOUTPUTDEV_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashTypes.h"
+#include "SplashOutputDev.h"
+
+class TextPage;
+
+//------------------------------------------------------------------------
+
+typedef void (*CoreOutRedrawCbk)(void *data, int x0, int y0, int x1, int y1,
+				 GBool composited);
+
+//------------------------------------------------------------------------
+// CoreOutputDev
+//------------------------------------------------------------------------
+
+class CoreOutputDev: public SplashOutputDev {
+public:
+
+  CoreOutputDev(SplashColorMode colorModeA, int bitmapRowPadA,
+		GBool reverseVideoA, SplashColorPtr paperColorA,
+		GBool incrementalUpdateA,
+		CoreOutRedrawCbk redrawCbkA,
+		void *redrawCbkDataA);
+
+  virtual ~CoreOutputDev();
+
+  //----- initialization and control
+
+  // End a page.
+  virtual void endPage();
+
+  // Dump page contents to display.
+  virtual void dump();
+
+  //----- special access
+
+  // Clear out the document (used when displaying an empty window).
+  void clear();
+
+private:
+
+  GBool incrementalUpdate;      // incrementally update the display?
+  CoreOutRedrawCbk redrawCbk;
+  void *redrawCbkData;
+};
+
+#endif
diff --git a/xpdf/Decrypt.cc b/xpdf/Decrypt.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Decrypt.cc
@@ -0,0 +1,776 @@
+//========================================================================
+//
+// Decrypt.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <string.h>
+#include "gmem.h"
+#include "Decrypt.h"
+
+static void rc4InitKey(Guchar *key, int keyLen, Guchar *state);
+static Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c);
+static void aesKeyExpansion(DecryptAESState *s,
+			    Guchar *objKey, int objKeyLen);
+static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last);
+static void md5(Guchar *msg, int msgLen, Guchar *digest);
+
+static Guchar passwordPad[32] = {
+  0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41,
+  0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, 
+  0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 
+  0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a
+};
+
+//------------------------------------------------------------------------
+// Decrypt
+//------------------------------------------------------------------------
+
+GBool Decrypt::makeFileKey(int encVersion, int encRevision, int keyLength,
+			   GString *ownerKey, GString *userKey,
+			   int permissions, GString *fileID,
+			   GString *ownerPassword, GString *userPassword,
+			   Guchar *fileKey, GBool encryptMetadata,
+			   GBool *ownerPasswordOk) {
+  Guchar test[32], test2[32];
+  GString *userPassword2;
+  Guchar fState[256];
+  Guchar tmpKey[16];
+  Guchar fx, fy;
+  int len, i, j;
+
+  // try using the supplied owner password to generate the user password
+  *ownerPasswordOk = gFalse;
+  if (ownerPassword) {
+    len = ownerPassword->getLength();
+    if (len < 32) {
+      memcpy(test, ownerPassword->getCString(), len);
+      memcpy(test + len, passwordPad, 32 - len);
+    } else {
+      memcpy(test, ownerPassword->getCString(), 32);
+    }
+    md5(test, 32, test);
+    if (encRevision == 3) {
+      for (i = 0; i < 50; ++i) {
+	md5(test, 16, test);
+      }
+    }
+    if (encRevision == 2) {
+      rc4InitKey(test, keyLength, fState);
+      fx = fy = 0;
+      for (i = 0; i < 32; ++i) {
+	test2[i] = rc4DecryptByte(fState, &fx, &fy, ownerKey->getChar(i));
+      }
+    } else {
+      memcpy(test2, ownerKey->getCString(), 32);
+      for (i = 19; i >= 0; --i) {
+	for (j = 0; j < keyLength; ++j) {
+	  tmpKey[j] = test[j] ^ i;
+	}
+	rc4InitKey(tmpKey, keyLength, fState);
+	fx = fy = 0;
+	for (j = 0; j < 32; ++j) {
+	  test2[j] = rc4DecryptByte(fState, &fx, &fy, test2[j]);
+	}
+      }
+    }
+    userPassword2 = new GString((char *)test2, 32);
+    if (makeFileKey2(encVersion, encRevision, keyLength, ownerKey, userKey,
+		     permissions, fileID, userPassword2, fileKey,
+		     encryptMetadata)) {
+      *ownerPasswordOk = gTrue;
+      delete userPassword2;
+      return gTrue;
+    }
+    delete userPassword2;
+  }
+
+  // try using the supplied user password
+  return makeFileKey2(encVersion, encRevision, keyLength, ownerKey, userKey,
+		      permissions, fileID, userPassword, fileKey,
+		      encryptMetadata);
+}
+
+GBool Decrypt::makeFileKey2(int encVersion, int encRevision, int keyLength,
+			    GString *ownerKey, GString *userKey,
+			    int permissions, GString *fileID,
+			    GString *userPassword, Guchar *fileKey,
+			    GBool encryptMetadata) {
+  Guchar *buf;
+  Guchar test[32];
+  Guchar fState[256];
+  Guchar tmpKey[16];
+  Guchar fx, fy;
+  int len, i, j;
+  GBool ok;
+
+  // generate file key
+  buf = (Guchar *)gmalloc(72 + fileID->getLength());
+  if (userPassword) {
+    len = userPassword->getLength();
+    if (len < 32) {
+      memcpy(buf, userPassword->getCString(), len);
+      memcpy(buf + len, passwordPad, 32 - len);
+    } else {
+      memcpy(buf, userPassword->getCString(), 32);
+    }
+  } else {
+    memcpy(buf, passwordPad, 32);
+  }
+  memcpy(buf + 32, ownerKey->getCString(), 32);
+  buf[64] = permissions & 0xff;
+  buf[65] = (permissions >> 8) & 0xff;
+  buf[66] = (permissions >> 16) & 0xff;
+  buf[67] = (permissions >> 24) & 0xff;
+  memcpy(buf + 68, fileID->getCString(), fileID->getLength());
+  len = 68 + fileID->getLength();
+  if (!encryptMetadata) {
+    buf[len++] = 0xff;
+    buf[len++] = 0xff;
+    buf[len++] = 0xff;
+    buf[len++] = 0xff;
+  }
+  md5(buf, len, fileKey);
+  if (encRevision == 3) {
+    for (i = 0; i < 50; ++i) {
+      md5(fileKey, keyLength, fileKey);
+    }
+  }
+
+  // test user password
+  if (encRevision == 2) {
+    rc4InitKey(fileKey, keyLength, fState);
+    fx = fy = 0;
+    for (i = 0; i < 32; ++i) {
+      test[i] = rc4DecryptByte(fState, &fx, &fy, userKey->getChar(i));
+    }
+    ok = memcmp(test, passwordPad, 32) == 0;
+  } else if (encRevision == 3) {
+    memcpy(test, userKey->getCString(), 32);
+    for (i = 19; i >= 0; --i) {
+      for (j = 0; j < keyLength; ++j) {
+	tmpKey[j] = fileKey[j] ^ i;
+      }
+      rc4InitKey(tmpKey, keyLength, fState);
+      fx = fy = 0;
+      for (j = 0; j < 32; ++j) {
+	test[j] = rc4DecryptByte(fState, &fx, &fy, test[j]);
+      }
+    }
+    memcpy(buf, passwordPad, 32);
+    memcpy(buf + 32, fileID->getCString(), fileID->getLength());
+    md5(buf, 32 + fileID->getLength(), buf);
+    ok = memcmp(test, buf, 16) == 0;
+  } else {
+    ok = gFalse;
+  }
+
+  gfree(buf);
+  return ok;
+}
+
+//------------------------------------------------------------------------
+// DecryptStream
+//------------------------------------------------------------------------
+
+DecryptStream::DecryptStream(Stream *strA, Guchar *fileKey,
+			     CryptAlgorithm algoA, int keyLength,
+			     int objNum, int objGen):
+  FilterStream(strA)
+{
+  int n, i;
+
+  algo = algoA;
+
+  // construct object key
+  for (i = 0; i < keyLength; ++i) {
+    objKey[i] = fileKey[i];
+  }
+  objKey[keyLength] = objNum & 0xff;
+  objKey[keyLength + 1] = (objNum >> 8) & 0xff;
+  objKey[keyLength + 2] = (objNum >> 16) & 0xff;
+  objKey[keyLength + 3] = objGen & 0xff;
+  objKey[keyLength + 4] = (objGen >> 8) & 0xff;
+  if (algo == cryptAES) {
+    objKey[keyLength + 5] = 0x73; // 's'
+    objKey[keyLength + 6] = 0x41; // 'A'
+    objKey[keyLength + 7] = 0x6c; // 'l'
+    objKey[keyLength + 8] = 0x54; // 'T'
+    n = keyLength + 9;
+  } else {
+    n = keyLength + 5;
+  }
+  md5(objKey, n, objKey);
+  if ((objKeyLength = keyLength + 5) > 16) {
+    objKeyLength = 16;
+  }
+}
+
+DecryptStream::~DecryptStream() {
+  delete str;
+}
+
+void DecryptStream::reset() {
+  int i;
+
+  str->reset();
+  switch (algo) {
+  case cryptRC4:
+    state.rc4.x = state.rc4.y = 0;
+    rc4InitKey(objKey, objKeyLength, state.rc4.state);
+    state.rc4.buf = EOF;
+    break;
+  case cryptAES:
+    aesKeyExpansion(&state.aes, objKey, objKeyLength);
+    for (i = 0; i < 16; ++i) {
+      state.aes.cbc[i] = str->getChar();
+    }
+    state.aes.bufIdx = 16;
+    break;
+  }
+}
+
+int DecryptStream::getChar() {
+  Guchar in[16];
+  int c, i;
+
+  c = EOF; // make gcc happy
+  switch (algo) {
+  case cryptRC4:
+    if (state.rc4.buf == EOF) {
+      c = str->getChar();
+      if (c != EOF) {
+	state.rc4.buf = rc4DecryptByte(state.rc4.state, &state.rc4.x,
+				       &state.rc4.y, (Guchar)c);
+      }
+    }
+    c = state.rc4.buf;
+    state.rc4.buf = EOF;
+    break;
+  case cryptAES:
+    if (state.aes.bufIdx == 16) {
+      for (i = 0; i < 16; ++i) {
+	if ((c = str->getChar()) == EOF) {
+	  return EOF;
+	}
+	in[i] = (Guchar)c;
+      }
+      aesDecryptBlock(&state.aes, in, str->lookChar() == EOF);
+    }
+    if (state.aes.bufIdx == 16) {
+      c = EOF;
+    } else {
+      c = state.aes.buf[state.aes.bufIdx++];
+    }
+    break;
+  }
+  return c;
+}
+
+int DecryptStream::lookChar() {
+  Guchar in[16];
+  int c, i;
+
+  c = EOF; // make gcc happy
+  switch (algo) {
+  case cryptRC4:
+    if (state.rc4.buf == EOF) {
+      c = str->getChar();
+      if (c != EOF) {
+	state.rc4.buf = rc4DecryptByte(state.rc4.state, &state.rc4.x,
+				       &state.rc4.y, (Guchar)c);
+      }
+    }
+    c = state.rc4.buf;
+    break;
+  case cryptAES:
+    if (state.aes.bufIdx == 16) {
+      for (i = 0; i < 16; ++i) {
+	if ((c = str->getChar()) == EOF) {
+	  return EOF;
+	}
+	in[i] = c;
+      }
+      aesDecryptBlock(&state.aes, in, str->lookChar() == EOF);
+    }
+    if (state.aes.bufIdx == 16) {
+      c = EOF;
+    } else {
+      c = state.aes.buf[state.aes.bufIdx];
+    }
+    break;
+  }
+  return c;
+}
+
+GBool DecryptStream::isBinary(GBool last) {
+  return str->isBinary(last);
+}
+
+//------------------------------------------------------------------------
+// RC4-compatible decryption
+//------------------------------------------------------------------------
+
+static void rc4InitKey(Guchar *key, int keyLen, Guchar *state) {
+  Guchar index1, index2;
+  Guchar t;
+  int i;
+
+  for (i = 0; i < 256; ++i)
+    state[i] = i;
+  index1 = index2 = 0;
+  for (i = 0; i < 256; ++i) {
+    index2 = (key[index1] + state[i] + index2) % 256;
+    t = state[i];
+    state[i] = state[index2];
+    state[index2] = t;
+    index1 = (index1 + 1) % keyLen;
+  }
+}
+
+static Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c) {
+  Guchar x1, y1, tx, ty;
+
+  x1 = *x = (*x + 1) % 256;
+  y1 = *y = (state[*x] + *y) % 256;
+  tx = state[x1];
+  ty = state[y1];
+  state[x1] = ty;
+  state[y1] = tx;
+  return c ^ state[(tx + ty) % 256];
+}
+
+//------------------------------------------------------------------------
+// AES decryption
+//------------------------------------------------------------------------
+
+static Guchar sbox[256] = {
+  0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
+  0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
+  0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
+  0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
+  0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
+  0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
+  0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
+  0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
+  0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
+  0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
+  0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
+  0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
+  0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
+  0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
+  0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
+  0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
+};
+
+static Guchar invSbox[256] = {
+  0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
+  0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
+  0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
+  0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
+  0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
+  0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
+  0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
+  0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
+  0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
+  0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
+  0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
+  0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
+  0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
+  0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
+  0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
+  0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
+};
+
+static Guint rcon[11] = {
+  0x00000000, // unused
+  0x01000000,
+  0x02000000,
+  0x04000000,
+  0x08000000,
+  0x10000000,
+  0x20000000,
+  0x40000000,
+  0x80000000,
+  0x1b000000,
+  0x36000000
+};
+
+static inline Guint subWord(Guint x) {
+  return (sbox[x >> 24] << 24)
+         | (sbox[(x >> 16) & 0xff] << 16)
+         | (sbox[(x >> 8) & 0xff] << 8)
+         | sbox[x & 0xff];
+}
+
+static inline Guint rotWord(Guint x) {
+  return ((x << 8) & 0xffffffff) | (x >> 24);
+}
+
+static inline void invSubBytes(Guchar *state) {
+  int i;
+
+  for (i = 0; i < 16; ++i) {
+    state[i] = invSbox[state[i]];
+  }
+}
+
+static inline void invShiftRows(Guchar *state) {
+  Guchar t;
+
+  t = state[7];
+  state[7] = state[6];
+  state[6] = state[5];
+  state[5] = state[4];
+  state[4] = t;
+
+  t = state[8];
+  state[8] = state[10];
+  state[10] = t;
+  t = state[9];
+  state[9] = state[11];
+  state[11] = t;
+
+  t = state[12];
+  state[12] = state[13];
+  state[13] = state[14];
+  state[14] = state[15];
+  state[15] = t;
+}
+
+// {09} \cdot s
+static inline Guchar mul09(Guchar s) {
+  Guchar s2, s4, s8;
+
+  s2 = (s & 0x80) ? ((s << 1) ^ 0x1b) : (s << 1);
+  s4 = (s2 & 0x80) ? ((s2 << 1) ^ 0x1b) : (s2 << 1);
+  s8 = (s4 & 0x80) ? ((s4 << 1) ^ 0x1b) : (s4 << 1);
+  return s ^ s8;
+}
+
+// {0b} \cdot s
+static inline Guchar mul0b(Guchar s) {
+  Guchar s2, s4, s8;
+
+  s2 = (s & 0x80) ? ((s << 1) ^ 0x1b) : (s << 1);
+  s4 = (s2 & 0x80) ? ((s2 << 1) ^ 0x1b) : (s2 << 1);
+  s8 = (s4 & 0x80) ? ((s4 << 1) ^ 0x1b) : (s4 << 1);
+  return s ^ s2 ^ s8;
+}
+
+// {0d} \cdot s
+static inline Guchar mul0d(Guchar s) {
+  Guchar s2, s4, s8;
+
+  s2 = (s & 0x80) ? ((s << 1) ^ 0x1b) : (s << 1);
+  s4 = (s2 & 0x80) ? ((s2 << 1) ^ 0x1b) : (s2 << 1);
+  s8 = (s4 & 0x80) ? ((s4 << 1) ^ 0x1b) : (s4 << 1);
+  return s ^ s4 ^ s8;
+}
+
+// {0e} \cdot s
+static inline Guchar mul0e(Guchar s) {
+  Guchar s2, s4, s8;
+
+  s2 = (s & 0x80) ? ((s << 1) ^ 0x1b) : (s << 1);
+  s4 = (s2 & 0x80) ? ((s2 << 1) ^ 0x1b) : (s2 << 1);
+  s8 = (s4 & 0x80) ? ((s4 << 1) ^ 0x1b) : (s4 << 1);
+  return s2 ^ s4 ^ s8;
+}
+
+static inline void invMixColumns(Guchar *state) {
+  int c;
+  Guchar s0, s1, s2, s3;
+
+  for (c = 0; c < 4; ++c) {
+    s0 = state[c];
+    s1 = state[4+c];
+    s2 = state[8+c];
+    s3 = state[12+c];
+    state[c] =    mul0e(s0) ^ mul0b(s1) ^ mul0d(s2) ^ mul09(s3);
+    state[4+c] =  mul09(s0) ^ mul0e(s1) ^ mul0b(s2) ^ mul0d(s3);
+    state[8+c] =  mul0d(s0) ^ mul09(s1) ^ mul0e(s2) ^ mul0b(s3);
+    state[12+c] = mul0b(s0) ^ mul0d(s1) ^ mul09(s2) ^ mul0e(s3);
+  }
+}
+
+static inline void invMixColumnsW(Guint *w) {
+  int c;
+  Guchar s0, s1, s2, s3;
+
+  for (c = 0; c < 4; ++c) {
+    s0 = w[c] >> 24;
+    s1 = w[c] >> 16;
+    s2 = w[c] >> 8;
+    s3 = w[c];
+    w[c] = ((mul0e(s0) ^ mul0b(s1) ^ mul0d(s2) ^ mul09(s3)) << 24)
+           | ((mul09(s0) ^ mul0e(s1) ^ mul0b(s2) ^ mul0d(s3)) << 16)
+           | ((mul0d(s0) ^ mul09(s1) ^ mul0e(s2) ^ mul0b(s3)) << 8)
+           | (mul0b(s0) ^ mul0d(s1) ^ mul09(s2) ^ mul0e(s3));
+  }
+}
+
+static inline void addRoundKey(Guchar *state, Guint *w) {
+  int c;
+
+  for (c = 0; c < 4; ++c) {
+    state[c] ^= w[c] >> 24;
+    state[4+c] ^= w[c] >> 16;
+    state[8+c] ^= w[c] >> 8;
+    state[12+c] ^= w[c];
+  }
+}
+
+static void aesKeyExpansion(DecryptAESState *s,
+			    Guchar *objKey, int objKeyLen) {
+  Guint temp;
+  int i, round;
+
+  //~ this assumes objKeyLen == 16
+
+  for (i = 0; i < 4; ++i) {
+    s->w[i] = (objKey[4*i] << 24) + (objKey[4*i+1] << 16) +
+              (objKey[4*i+2] << 8) + objKey[4*i+3];
+  }
+  for (i = 4; i < 44; ++i) {
+    temp = s->w[i-1];
+    if (!(i & 3)) {
+      temp = subWord(rotWord(temp)) ^ rcon[i/4];
+    }
+    s->w[i] = s->w[i-4] ^ temp;
+  }
+  for (round = 1; round <= 9; ++round) {
+    invMixColumnsW(&s->w[round * 4]);
+  }
+}
+
+static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last) {
+  int c, round, n, i;
+
+  // initial state
+  for (c = 0; c < 4; ++c) {
+    s->state[c] = in[4*c];
+    s->state[4+c] = in[4*c+1];
+    s->state[8+c] = in[4*c+2];
+    s->state[12+c] = in[4*c+3];
+  }
+
+  // round 0
+  addRoundKey(s->state, &s->w[10 * 4]);
+
+  // rounds 1-9
+  for (round = 9; round >= 1; --round) {
+    invSubBytes(s->state);
+    invShiftRows(s->state);
+    invMixColumns(s->state);
+    addRoundKey(s->state, &s->w[round * 4]);
+  }
+
+  // round 10
+  invSubBytes(s->state);
+  invShiftRows(s->state);
+  addRoundKey(s->state, &s->w[0]);
+
+  // CBC
+  for (c = 0; c < 4; ++c) {
+    s->buf[4*c] = s->state[c] ^ s->cbc[4*c];
+    s->buf[4*c+1] = s->state[4+c] ^ s->cbc[4*c+1];
+    s->buf[4*c+2] = s->state[8+c] ^ s->cbc[4*c+2];
+    s->buf[4*c+3] = s->state[12+c] ^ s->cbc[4*c+3];
+  }
+
+  // save the input block for the next CBC
+  for (i = 0; i < 16; ++i) {
+    s->cbc[i] = in[i];
+  }
+
+  // remove padding
+  s->bufIdx = 0;
+  if (last) {
+    n = s->buf[15];
+    for (i = 15; i >= n; --i) {
+      s->buf[i] = s->buf[i-n];
+    }
+    s->bufIdx = n;
+  }
+}
+
+//------------------------------------------------------------------------
+// MD5 message digest
+//------------------------------------------------------------------------
+
+// this works around a bug in older Sun compilers
+static inline Gulong rotateLeft(Gulong x, int r) {
+  x &= 0xffffffff;
+  return ((x << r) | (x >> (32 - r))) & 0xffffffff;
+}
+
+static inline Gulong md5Round1(Gulong a, Gulong b, Gulong c, Gulong d,
+			       Gulong Xk,  Gulong s, Gulong Ti) {
+  return b + rotateLeft((a + ((b & c) | (~b & d)) + Xk + Ti), s);
+}
+
+static inline Gulong md5Round2(Gulong a, Gulong b, Gulong c, Gulong d,
+			       Gulong Xk,  Gulong s, Gulong Ti) {
+  return b + rotateLeft((a + ((b & d) | (c & ~d)) + Xk + Ti), s);
+}
+
+static inline Gulong md5Round3(Gulong a, Gulong b, Gulong c, Gulong d,
+			       Gulong Xk,  Gulong s, Gulong Ti) {
+  return b + rotateLeft((a + (b ^ c ^ d) + Xk + Ti), s);
+}
+
+static inline Gulong md5Round4(Gulong a, Gulong b, Gulong c, Gulong d,
+			       Gulong Xk,  Gulong s, Gulong Ti) {
+  return b + rotateLeft((a + (c ^ (b | ~d)) + Xk + Ti), s);
+}
+
+static void md5(Guchar *msg, int msgLen, Guchar *digest) {
+  Gulong x[16];
+  Gulong a, b, c, d, aa, bb, cc, dd;
+  int n64;
+  int i, j, k;
+
+  // compute number of 64-byte blocks
+  // (length + pad byte (0x80) + 8 bytes for length)
+  n64 = (msgLen + 1 + 8 + 63) / 64;
+
+  // initialize a, b, c, d
+  a = 0x67452301;
+  b = 0xefcdab89;
+  c = 0x98badcfe;
+  d = 0x10325476;
+
+  // loop through blocks
+  k = 0;
+  for (i = 0; i < n64; ++i) {
+
+    // grab a 64-byte block
+    for (j = 0; j < 16 && k < msgLen - 3; ++j, k += 4)
+      x[j] = (((((msg[k+3] << 8) + msg[k+2]) << 8) + msg[k+1]) << 8) + msg[k];
+    if (i == n64 - 1) {
+      if (k == msgLen - 3)
+	x[j] = 0x80000000 + (((msg[k+2] << 8) + msg[k+1]) << 8) + msg[k];
+      else if (k == msgLen - 2)
+	x[j] = 0x800000 + (msg[k+1] << 8) + msg[k];
+      else if (k == msgLen - 1)
+	x[j] = 0x8000 + msg[k];
+      else
+	x[j] = 0x80;
+      ++j;
+      while (j < 16)
+	x[j++] = 0;
+      x[14] = msgLen << 3;
+    }
+
+    // save a, b, c, d
+    aa = a;
+    bb = b;
+    cc = c;
+    dd = d;
+
+    // round 1
+    a = md5Round1(a, b, c, d, x[0],   7, 0xd76aa478);
+    d = md5Round1(d, a, b, c, x[1],  12, 0xe8c7b756);
+    c = md5Round1(c, d, a, b, x[2],  17, 0x242070db);
+    b = md5Round1(b, c, d, a, x[3],  22, 0xc1bdceee);
+    a = md5Round1(a, b, c, d, x[4],   7, 0xf57c0faf);
+    d = md5Round1(d, a, b, c, x[5],  12, 0x4787c62a);
+    c = md5Round1(c, d, a, b, x[6],  17, 0xa8304613);
+    b = md5Round1(b, c, d, a, x[7],  22, 0xfd469501);
+    a = md5Round1(a, b, c, d, x[8],   7, 0x698098d8);
+    d = md5Round1(d, a, b, c, x[9],  12, 0x8b44f7af);
+    c = md5Round1(c, d, a, b, x[10], 17, 0xffff5bb1);
+    b = md5Round1(b, c, d, a, x[11], 22, 0x895cd7be);
+    a = md5Round1(a, b, c, d, x[12],  7, 0x6b901122);
+    d = md5Round1(d, a, b, c, x[13], 12, 0xfd987193);
+    c = md5Round1(c, d, a, b, x[14], 17, 0xa679438e);
+    b = md5Round1(b, c, d, a, x[15], 22, 0x49b40821);
+
+    // round 2
+    a = md5Round2(a, b, c, d, x[1],   5, 0xf61e2562);
+    d = md5Round2(d, a, b, c, x[6],   9, 0xc040b340);
+    c = md5Round2(c, d, a, b, x[11], 14, 0x265e5a51);
+    b = md5Round2(b, c, d, a, x[0],  20, 0xe9b6c7aa);
+    a = md5Round2(a, b, c, d, x[5],   5, 0xd62f105d);
+    d = md5Round2(d, a, b, c, x[10],  9, 0x02441453);
+    c = md5Round2(c, d, a, b, x[15], 14, 0xd8a1e681);
+    b = md5Round2(b, c, d, a, x[4],  20, 0xe7d3fbc8);
+    a = md5Round2(a, b, c, d, x[9],   5, 0x21e1cde6);
+    d = md5Round2(d, a, b, c, x[14],  9, 0xc33707d6);
+    c = md5Round2(c, d, a, b, x[3],  14, 0xf4d50d87);
+    b = md5Round2(b, c, d, a, x[8],  20, 0x455a14ed);
+    a = md5Round2(a, b, c, d, x[13],  5, 0xa9e3e905);
+    d = md5Round2(d, a, b, c, x[2],   9, 0xfcefa3f8);
+    c = md5Round2(c, d, a, b, x[7],  14, 0x676f02d9);
+    b = md5Round2(b, c, d, a, x[12], 20, 0x8d2a4c8a);
+
+    // round 3
+    a = md5Round3(a, b, c, d, x[5],   4, 0xfffa3942);
+    d = md5Round3(d, a, b, c, x[8],  11, 0x8771f681);
+    c = md5Round3(c, d, a, b, x[11], 16, 0x6d9d6122);
+    b = md5Round3(b, c, d, a, x[14], 23, 0xfde5380c);
+    a = md5Round3(a, b, c, d, x[1],   4, 0xa4beea44);
+    d = md5Round3(d, a, b, c, x[4],  11, 0x4bdecfa9);
+    c = md5Round3(c, d, a, b, x[7],  16, 0xf6bb4b60);
+    b = md5Round3(b, c, d, a, x[10], 23, 0xbebfbc70);
+    a = md5Round3(a, b, c, d, x[13],  4, 0x289b7ec6);
+    d = md5Round3(d, a, b, c, x[0],  11, 0xeaa127fa);
+    c = md5Round3(c, d, a, b, x[3],  16, 0xd4ef3085);
+    b = md5Round3(b, c, d, a, x[6],  23, 0x04881d05);
+    a = md5Round3(a, b, c, d, x[9],   4, 0xd9d4d039);
+    d = md5Round3(d, a, b, c, x[12], 11, 0xe6db99e5);
+    c = md5Round3(c, d, a, b, x[15], 16, 0x1fa27cf8);
+    b = md5Round3(b, c, d, a, x[2],  23, 0xc4ac5665);
+
+    // round 4
+    a = md5Round4(a, b, c, d, x[0],   6, 0xf4292244);
+    d = md5Round4(d, a, b, c, x[7],  10, 0x432aff97);
+    c = md5Round4(c, d, a, b, x[14], 15, 0xab9423a7);
+    b = md5Round4(b, c, d, a, x[5],  21, 0xfc93a039);
+    a = md5Round4(a, b, c, d, x[12],  6, 0x655b59c3);
+    d = md5Round4(d, a, b, c, x[3],  10, 0x8f0ccc92);
+    c = md5Round4(c, d, a, b, x[10], 15, 0xffeff47d);
+    b = md5Round4(b, c, d, a, x[1],  21, 0x85845dd1);
+    a = md5Round4(a, b, c, d, x[8],   6, 0x6fa87e4f);
+    d = md5Round4(d, a, b, c, x[15], 10, 0xfe2ce6e0);
+    c = md5Round4(c, d, a, b, x[6],  15, 0xa3014314);
+    b = md5Round4(b, c, d, a, x[13], 21, 0x4e0811a1);
+    a = md5Round4(a, b, c, d, x[4],   6, 0xf7537e82);
+    d = md5Round4(d, a, b, c, x[11], 10, 0xbd3af235);
+    c = md5Round4(c, d, a, b, x[2],  15, 0x2ad7d2bb);
+    b = md5Round4(b, c, d, a, x[9],  21, 0xeb86d391);
+
+    // increment a, b, c, d
+    a += aa;
+    b += bb;
+    c += cc;
+    d += dd;
+  }
+
+  // break digest into bytes
+  digest[0] = (Guchar)(a & 0xff);
+  digest[1] = (Guchar)((a >>= 8) & 0xff);
+  digest[2] = (Guchar)((a >>= 8) & 0xff);
+  digest[3] = (Guchar)((a >>= 8) & 0xff);
+  digest[4] = (Guchar)(b & 0xff);
+  digest[5] = (Guchar)((b >>= 8) & 0xff);
+  digest[6] = (Guchar)((b >>= 8) & 0xff);
+  digest[7] = (Guchar)((b >>= 8) & 0xff);
+  digest[8] = (Guchar)(c & 0xff);
+  digest[9] = (Guchar)((c >>= 8) & 0xff);
+  digest[10] = (Guchar)((c >>= 8) & 0xff);
+  digest[11] = (Guchar)((c >>= 8) & 0xff);
+  digest[12] = (Guchar)(d & 0xff);
+  digest[13] = (Guchar)((d >>= 8) & 0xff);
+  digest[14] = (Guchar)((d >>= 8) & 0xff);
+  digest[15] = (Guchar)((d >>= 8) & 0xff);
+}
diff --git a/xpdf/Decrypt.h b/xpdf/Decrypt.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Decrypt.h
@@ -0,0 +1,95 @@
+//========================================================================
+//
+// Decrypt.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef DECRYPT_H
+#define DECRYPT_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "GString.h"
+#include "Object.h"
+#include "Stream.h"
+
+//------------------------------------------------------------------------
+// Decrypt
+//------------------------------------------------------------------------
+
+class Decrypt {
+public:
+
+  // Generate a file key.  The <fileKey> buffer must have space for at
+  // least 16 bytes.  Checks <ownerPassword> and then <userPassword>
+  // and returns true if either is correct.  Sets <ownerPasswordOk> if
+  // the owner password was correct.  Either or both of the passwords
+  // may be NULL, which is treated as an empty string.
+  static GBool makeFileKey(int encVersion, int encRevision, int keyLength,
+			   GString *ownerKey, GString *userKey,
+			   int permissions, GString *fileID,
+			   GString *ownerPassword, GString *userPassword,
+			   Guchar *fileKey, GBool encryptMetadata,
+			   GBool *ownerPasswordOk);
+
+private:
+
+  static GBool makeFileKey2(int encVersion, int encRevision, int keyLength,
+			    GString *ownerKey, GString *userKey,
+			    int permissions, GString *fileID,
+			    GString *userPassword, Guchar *fileKey,
+			    GBool encryptMetadata);
+};
+
+//------------------------------------------------------------------------
+// DecryptStream
+//------------------------------------------------------------------------
+
+struct DecryptRC4State {
+  Guchar state[256];
+  Guchar x, y;
+  int buf;
+};
+
+struct DecryptAESState {
+  Guint w[44];
+  Guchar state[16];
+  Guchar cbc[16];
+  Guchar buf[16];
+  int bufIdx;
+};
+
+class DecryptStream: public FilterStream {
+public:
+
+  DecryptStream(Stream *strA, Guchar *fileKey,
+		CryptAlgorithm algoA, int keyLength,
+		int objNum, int objGen);
+  virtual ~DecryptStream();
+  virtual StreamKind getKind() { return strWeird; }
+  virtual void reset();
+  virtual int getChar();
+  virtual int lookChar();
+  virtual GBool isBinary(GBool last);
+  virtual Stream *getUndecodedStream() { return this; }
+
+private:
+
+  CryptAlgorithm algo;
+  int objKeyLength;
+  Guchar objKey[16 + 9];
+
+  union {
+    DecryptRC4State rc4;
+    DecryptAESState aes;
+  } state;
+};
+
+#endif
diff --git a/xpdf/Dict.cc b/xpdf/Dict.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Dict.cc
@@ -0,0 +1,95 @@
+//========================================================================
+//
+// Dict.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stddef.h>
+#include <string.h>
+#include "gmem.h"
+#include "Object.h"
+#include "XRef.h"
+#include "Dict.h"
+
+//------------------------------------------------------------------------
+// Dict
+//------------------------------------------------------------------------
+
+Dict::Dict(XRef *xrefA) {
+  xref = xrefA;
+  entries = NULL;
+  size = length = 0;
+  ref = 1;
+}
+
+Dict::~Dict() {
+  int i;
+
+  for (i = 0; i < length; ++i) {
+    gfree(entries[i].key);
+    entries[i].val.free();
+  }
+  gfree(entries);
+}
+
+void Dict::add(char *key, Object *val) {
+  if (length == size) {
+    if (length == 0) {
+      size = 8;
+    } else {
+      size *= 2;
+    }
+    entries = (DictEntry *)greallocn(entries, size, sizeof(DictEntry));
+  }
+  entries[length].key = key;
+  entries[length].val = *val;
+  ++length;
+}
+
+inline DictEntry *Dict::find(char *key) {
+  int i;
+
+  for (i = 0; i < length; ++i) {
+    if (!strcmp(key, entries[i].key))
+      return &entries[i];
+  }
+  return NULL;
+}
+
+GBool Dict::is(char *type) {
+  DictEntry *e;
+
+  return (e = find("Type")) && e->val.isName(type);
+}
+
+Object *Dict::lookup(char *key, Object *obj) {
+  DictEntry *e;
+
+  return (e = find(key)) ? e->val.fetch(xref, obj) : obj->initNull();
+}
+
+Object *Dict::lookupNF(char *key, Object *obj) {
+  DictEntry *e;
+
+  return (e = find(key)) ? e->val.copy(obj) : obj->initNull();
+}
+
+char *Dict::getKey(int i) {
+  return entries[i].key;
+}
+
+Object *Dict::getVal(int i, Object *obj) {
+  return entries[i].val.fetch(xref, obj);
+}
+
+Object *Dict::getValNF(int i, Object *obj) {
+  return entries[i].val.copy(obj);
+}
diff --git a/xpdf/Dict.h b/xpdf/Dict.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Dict.h
@@ -0,0 +1,77 @@
+//========================================================================
+//
+// Dict.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef DICT_H
+#define DICT_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "Object.h"
+
+//------------------------------------------------------------------------
+// Dict
+//------------------------------------------------------------------------
+
+struct DictEntry {
+  char *key;
+  Object val;
+};
+
+class Dict {
+public:
+
+  // Constructor.
+  Dict(XRef *xrefA);
+
+  // Destructor.
+  ~Dict();
+
+  // Reference counting.
+  int incRef() { return ++ref; }
+  int decRef() { return --ref; }
+
+  // Get number of entries.
+  int getLength() { return length; }
+
+  // Add an entry.  NB: does not copy key.
+  void add(char *key, Object *val);
+
+  // Check if dictionary is of specified type.
+  GBool is(char *type);
+
+  // Look up an entry and return the value.  Returns a null object
+  // if <key> is not in the dictionary.
+  Object *lookup(char *key, Object *obj);
+  Object *lookupNF(char *key, Object *obj);
+
+  // Iterative accessors.
+  char *getKey(int i);
+  Object *getVal(int i, Object *obj);
+  Object *getValNF(int i, Object *obj);
+
+  // Set the xref pointer.  This is only used in one special case: the
+  // trailer dictionary, which is read before the xref table is
+  // parsed.
+  void setXRef(XRef *xrefA) { xref = xrefA; }
+
+private:
+
+  XRef *xref;			// the xref table for this PDF file
+  DictEntry *entries;		// array of entries
+  int size;			// size of <entries> array
+  int length;			// number of entries in dictionary
+  int ref;			// reference count
+
+  DictEntry *find(char *key);
+};
+
+#endif
diff --git a/xpdf/Error.cc b/xpdf/Error.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Error.cc
@@ -0,0 +1,38 @@
+//========================================================================
+//
+// Error.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include "GlobalParams.h"
+#include "Error.h"
+
+void CDECL error(int pos, char *msg, ...) {
+  va_list args;
+
+  // NB: this can be called before the globalParams object is created
+  if (globalParams && globalParams->getErrQuiet()) {
+    return;
+  }
+  if (pos >= 0) {
+    fprintf(stderr, "Error (%d): ", pos);
+  } else {
+    fprintf(stderr, "Error: ");
+  }
+  va_start(args, msg);
+  vfprintf(stderr, msg, args);
+  va_end(args);
+  fprintf(stderr, "\n");
+  fflush(stderr);
+}
diff --git a/xpdf/Error.h b/xpdf/Error.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Error.h
@@ -0,0 +1,23 @@
+//========================================================================
+//
+// Error.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef ERROR_H
+#define ERROR_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <stdio.h>
+#include "config.h"
+
+extern void CDECL error(int pos, char *msg, ...);
+
+#endif
diff --git a/xpdf/ErrorCodes.h b/xpdf/ErrorCodes.h
new file mode 100644
--- /dev/null
+++ b/xpdf/ErrorCodes.h
@@ -0,0 +1,36 @@
+//========================================================================
+//
+// ErrorCodes.h
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef ERRORCODES_H
+#define ERRORCODES_H
+
+#define errNone             0	// no error
+
+#define errOpenFile         1	// couldn't open the PDF file
+
+#define errBadCatalog       2	// couldn't read the page catalog
+
+#define errDamaged          3	// PDF file was damaged and couldn't be
+				// repaired
+
+#define errEncrypted        4	// file was encrypted and password was
+				// incorrect or not supplied
+
+#define errHighlightFile    5	// nonexistent or invalid highlight file
+
+#define errBadPrinter       6   // invalid printer
+
+#define errPrinting         7   // error during printing
+
+#define errPermission       8	// PDF file doesn't allow that operation
+
+#define errBadPageNum       9	// invalid page number
+
+#define errFileIO          10   // file I/O error
+
+#endif
diff --git a/xpdf/FixedPoint.cc b/xpdf/FixedPoint.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/FixedPoint.cc
@@ -0,0 +1,118 @@
+//========================================================================
+//
+// FixedPoint.cc
+//
+// Fixed point type, with C++ operators.
+//
+// Copyright 2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#if USE_FIXEDPOINT
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include "FixedPoint.h"
+
+#define ln2 ((FixedPoint)0.69314718)
+
+FixedPoint FixedPoint::sqrt(FixedPoint x) {
+  FixedPoint y0, y1, z;
+
+  if (x.val <= 0) {
+    y1.val = 0;
+  } else {
+    y1.val = x.val == 1 ? 2 : x.val >> 1;
+    do {
+      y0.val = y1.val;
+      z = x / y0;
+      y1.val = (y0.val + z.val) >> 1;
+    } while (::abs(y0.val - y1.val) > 1);
+  }
+  return y1;
+}
+
+FixedPoint FixedPoint::pow(FixedPoint x, FixedPoint y) {
+  FixedPoint t, t2, lnx0, lnx, z0, z;
+  int d, n, i;
+
+  if (y.val <= 0) {
+    z.val = 0;
+  } else {
+    // y * ln(x)
+    t = (x - 1) / (x + 1);
+    t2 = t * t;
+    d = 1;
+    lnx = 0;
+    do {
+      lnx0 = lnx;
+      lnx += t / d;
+      t *= t2;
+      d += 2;
+    } while (::abs(lnx.val - lnx0.val) > 2);
+    lnx.val <<= 1;
+    t = y * lnx;
+    // exp(y * ln(x))
+    n = floor(t / ln2);
+    t -= ln2 * n;
+    t2 = t;
+    d = 1;
+    i = 1;
+    z = 1;
+    do {
+      z0 = z;
+      z += t2 / d;
+      t2 *= t;
+      ++i;
+      d *= i;
+    } while (::abs(z.val - z0.val) > 2 && d < (1 << fixptShift));
+    if (n >= 0) {
+      z.val <<= n;
+    } else if (n < 0) {
+      z.val >>= -n;
+    }
+  }
+  return z;
+}
+
+int FixedPoint::mul(int x, int y) {
+#if 1 //~tmp
+  return ((FixPtInt64)x * y) >> fixptShift;
+#else
+  int ah0, ah, bh, al, bl;
+  ah0 = x & fixptMaskH;
+  ah = x >> fixptShift;
+  al = x - ah0;
+  bh = y >> fixptShift;
+  bl = y - (bh << fixptShift);
+  return ah0 * bh + ah * bl + al * bh + ((al * bl) >> fixptShift);
+#endif
+}
+
+int FixedPoint::div(int x, int y) {
+#if 1 //~tmp
+  return ((FixPtInt64)x << fixptShift) / y;
+#else
+#endif
+}
+
+GBool FixedPoint::divCheck(FixedPoint x, FixedPoint y, FixedPoint *result) {
+#if 1 //~tmp
+  FixPtInt64 z;
+
+  z = ((FixPtInt64)x.val << fixptShift) / y.val;
+  if ((z == 0 && x != 0) ||
+      z >= ((FixPtInt64)1 << 31) || z < -((FixPtInt64)1 << 31)) {
+    return gFalse;
+  }
+  result->val = z;
+  return gTrue;
+#else
+#endif
+}
+
+#endif // USE_FIXEDPOINT
diff --git a/xpdf/FixedPoint.h b/xpdf/FixedPoint.h
new file mode 100644
--- /dev/null
+++ b/xpdf/FixedPoint.h
@@ -0,0 +1,155 @@
+//========================================================================
+//
+// FixedPoint.h
+//
+// Fixed point type, with C++ operators.
+//
+// Copyright 2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef FIXEDPOINT_H
+#define FIXEDPOINT_H
+
+#include <aconf.h>
+
+#if USE_FIXEDPOINT
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "gtypes.h"
+
+#define fixptShift 16
+#define fixptMaskL ((1 << fixptShift) - 1)
+#define fixptMaskH (~fixptMaskL)
+
+typedef long long FixPtInt64;
+
+class FixedPoint {
+public:
+
+  FixedPoint() { val = 0; }
+  FixedPoint(const FixedPoint &x) { val = x.val; }
+  FixedPoint(double x) { val = (int)(x * (1 << fixptShift) + 0.5); }
+  FixedPoint(int x) { val = x << fixptShift; }
+  FixedPoint(long x) { val = x << fixptShift; }
+
+  operator float()
+    { return (float) val * ((float)1 / (float)(1 << fixptShift)); }
+  operator double()
+    { return (double) val * (1.0 / (double)(1 << fixptShift)); }
+  operator int()
+    { return val >> fixptShift; }
+
+  int getRaw() { return val; }
+
+  FixedPoint operator =(FixedPoint x) { val = x.val; return *this; }
+
+  int operator ==(FixedPoint x) { return val == x.val; }
+  int operator ==(double x) { return *this == (FixedPoint)x; }
+  int operator ==(int x) { return *this == (FixedPoint)x; }
+  int operator ==(long x) { return *this == (FixedPoint)x; }
+
+  int operator !=(FixedPoint x) { return val != x.val; }
+  int operator !=(double x) { return *this != (FixedPoint)x; }
+  int operator !=(int x) { return *this != (FixedPoint)x; }
+  int operator !=(long x) { return *this != (FixedPoint)x; }
+
+  int operator <(FixedPoint x) { return val < x.val; }
+  int operator <(double x) { return *this < (FixedPoint)x; }
+  int operator <(int x) { return *this < (FixedPoint)x; }
+  int operator <(long x) { return *this < (FixedPoint)x; }
+
+  int operator <=(FixedPoint x) { return val <= x.val; }
+  int operator <=(double x) { return *this <= (FixedPoint)x; }
+  int operator <=(int x) { return *this <= (FixedPoint)x; }
+  int operator <=(long x) { return *this <= (FixedPoint)x; }
+
+  int operator >(FixedPoint x) { return val > x.val; }
+  int operator >(double x) { return *this > (FixedPoint)x; }
+  int operator >(int x) { return *this > (FixedPoint)x; }
+  int operator >(long x) { return *this > (FixedPoint)x; }
+
+  int operator >=(FixedPoint x) { return val >= x.val; }
+  int operator >=(double x) { return *this >= (FixedPoint)x; }
+  int operator >=(int x) { return *this >= (FixedPoint)x; }
+  int operator >=(long x) { return *this >= (FixedPoint)x; }
+
+  FixedPoint operator -() { return make(-val); }
+
+  FixedPoint operator +(FixedPoint x) { return make(val + x.val); }
+  FixedPoint operator +(double x) { return *this + (FixedPoint)x; }
+  FixedPoint operator +(int x) { return *this + (FixedPoint)x; }
+  FixedPoint operator +(long x) { return *this + (FixedPoint)x; }
+
+  FixedPoint operator +=(FixedPoint x) { val = val + x.val; return *this; }
+  FixedPoint operator +=(double x) { return *this += (FixedPoint)x; }
+  FixedPoint operator +=(int x) { return *this += (FixedPoint)x; }
+  FixedPoint operator +=(long x) { return *this += (FixedPoint)x; }
+
+  FixedPoint operator -(FixedPoint x) { return make(val - x.val); }
+  FixedPoint operator -(double x) { return *this - (FixedPoint)x; }
+  FixedPoint operator -(int x) { return *this - (FixedPoint)x; }
+  FixedPoint operator -(long x) { return *this - (FixedPoint)x; }
+
+  FixedPoint operator -=(FixedPoint x) { val = val - x.val; return *this; }
+  FixedPoint operator -=(double x) { return *this -= (FixedPoint)x; }
+  FixedPoint operator -=(int x) { return *this -= (FixedPoint)x; }
+  FixedPoint operator -=(long x) { return *this -= (FixedPoint)x; }
+
+  FixedPoint operator *(FixedPoint x) { return make(mul(val, x.val)); }
+  FixedPoint operator *(double x) { return *this * (FixedPoint)x; }
+  FixedPoint operator *(int x) { return *this * (FixedPoint)x; }
+  FixedPoint operator *(long x) { return *this * (FixedPoint)x; }
+
+  FixedPoint operator *=(FixedPoint x) { val = mul(val, x.val); return *this; }
+  FixedPoint operator *=(double x) { return *this *= (FixedPoint)x; }
+  FixedPoint operator *=(int x) { return *this *= (FixedPoint)x; }
+  FixedPoint operator *=(long x) { return *this *= (FixedPoint)x; }
+
+  FixedPoint operator /(FixedPoint x) { return make(div(val, x.val)); }
+  FixedPoint operator /(double x) { return *this / (FixedPoint)x; }
+  FixedPoint operator /(int x) { return *this / (FixedPoint)x; }
+  FixedPoint operator /(long x) { return *this / (FixedPoint)x; }
+
+  FixedPoint operator /=(FixedPoint x) { val = div(val, x.val); return *this; }
+  FixedPoint operator /=(double x) { return *this /= (FixedPoint)x; }
+  FixedPoint operator /=(int x) { return *this /= (FixedPoint)x; }
+  FixedPoint operator /=(long x) { return *this /= (FixedPoint)x; }
+
+  static FixedPoint abs(FixedPoint x) { return make(::abs(x.val)); }
+
+  static int floor(FixedPoint x) { return x.val >> fixptShift; }
+
+  static int ceil(FixedPoint x)
+    { return (x.val & fixptMaskL) ? ((x.val >> fixptShift) + 1)
+	                          : (x.val >> fixptShift); }
+
+  static int round(FixedPoint x)
+    { return (x.val + (1 << (fixptShift - 1))) >> fixptShift; }
+
+  static FixedPoint sqrt(FixedPoint x);
+
+  static FixedPoint pow(FixedPoint x, FixedPoint y);
+
+  // Compute *result = x/y; return false if there is an underflow or
+  // overflow.
+  static GBool divCheck(FixedPoint x, FixedPoint y, FixedPoint *result);
+
+private:
+
+  static FixedPoint make(int valA) { FixedPoint x; x.val = valA; return x; }
+
+  static int mul(int x, int y);
+  static int div(int x, int y);
+
+  int val;			// 16.16 fixed point
+};
+
+#endif // USE_FIXEDPOINT
+
+#endif
diff --git a/xpdf/FoFiBase.cc b/xpdf/FoFiBase.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/FoFiBase.cc
@@ -0,0 +1,156 @@
+//========================================================================
+//
+// FoFiBase.cc
+//
+// Copyright 1999-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include "gmem.h"
+#include "FoFiBase.h"
+
+//------------------------------------------------------------------------
+// FoFiBase
+//------------------------------------------------------------------------
+
+FoFiBase::FoFiBase(char *fileA, int lenA, GBool freeFileDataA) {
+  fileData = file = (Guchar *)fileA;
+  len = lenA;
+  freeFileData = freeFileDataA;
+}
+
+FoFiBase::~FoFiBase() {
+  if (freeFileData) {
+    gfree(fileData);
+  }
+}
+
+char *FoFiBase::readFile(char *fileName, int *fileLen) {
+  FILE *f;
+  char *buf;
+  int n;
+
+  if (!(f = fopen(fileName, "rb"))) {
+    return NULL;
+  }
+  fseek(f, 0, SEEK_END);
+  n = (int)ftell(f);
+  fseek(f, 0, SEEK_SET);
+  buf = (char *)gmalloc(n);
+  if ((int)fread(buf, 1, n, f) != n) {
+    gfree(buf);
+    fclose(f);
+    return NULL;
+  }
+  fclose(f);
+  *fileLen = n;
+  return buf;
+}
+
+int FoFiBase::getS8(int pos, GBool *ok) {
+  int x;
+
+  if (pos < 0 || pos >= len) {
+    *ok = gFalse;
+    return 0;
+  }
+  x = file[pos];
+  if (x & 0x80) {
+    x |= ~0xff;
+  }
+  return x;
+}
+
+int FoFiBase::getU8(int pos, GBool *ok) {
+  if (pos < 0 || pos >= len) {
+    *ok = gFalse;
+    return 0;
+  }
+  return file[pos];
+}
+
+int FoFiBase::getS16BE(int pos, GBool *ok) {
+  int x;
+
+  if (pos < 0 || pos+1 >= len) {
+    *ok = gFalse;
+    return 0;
+  }
+  x = file[pos];
+  x = (x << 8) + file[pos+1];
+  if (x & 0x8000) {
+    x |= ~0xffff;
+  }
+  return x;
+}
+
+int FoFiBase::getU16BE(int pos, GBool *ok) {
+  int x;
+
+  if (pos < 0 || pos+1 >= len) {
+    *ok = gFalse;
+    return 0;
+  }
+  x = file[pos];
+  x = (x << 8) + file[pos+1];
+  return x;
+}
+
+int FoFiBase::getS32BE(int pos, GBool *ok) {
+  int x;
+
+  if (pos < 0 || pos+3 >= len) {
+    *ok = gFalse;
+    return 0;
+  }
+  x = file[pos];
+  x = (x << 8) + file[pos+1];
+  x = (x << 8) + file[pos+2];
+  x = (x << 8) + file[pos+3];
+  if (x & 0x80000000) {
+    x |= ~0xffffffff;
+  }
+  return x;
+}
+
+Guint FoFiBase::getU32BE(int pos, GBool *ok) {
+  Guint x;
+
+  if (pos < 0 || pos+3 >= len) {
+    *ok = gFalse;
+    return 0;
+  }
+  x = file[pos];
+  x = (x << 8) + file[pos+1];
+  x = (x << 8) + file[pos+2];
+  x = (x << 8) + file[pos+3];
+  return x;
+}
+
+Guint FoFiBase::getUVarBE(int pos, int size, GBool *ok) {
+  Guint x;
+  int i;
+
+  if (pos < 0 || pos + size > len) {
+    *ok = gFalse;
+    return 0;
+  }
+  x = 0;
+  for (i = 0; i < size; ++i) {
+    x = (x << 8) + file[pos + i];
+  }
+  return x;
+}
+
+GBool FoFiBase::checkRegion(int pos, int size) {
+  return pos >= 0 &&
+         pos + size >= pos &&
+         pos + size <= len;
+}
diff --git a/xpdf/FoFiBase.h b/xpdf/FoFiBase.h
new file mode 100644
--- /dev/null
+++ b/xpdf/FoFiBase.h
@@ -0,0 +1,57 @@
+//========================================================================
+//
+// FoFiBase.h
+//
+// Copyright 1999-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef FOFIBASE_H
+#define FOFIBASE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+
+//------------------------------------------------------------------------
+
+typedef void (*FoFiOutputFunc)(void *stream, char *data, int len);
+
+//------------------------------------------------------------------------
+// FoFiBase
+//------------------------------------------------------------------------
+
+class FoFiBase {
+public:
+
+  virtual ~FoFiBase();
+
+protected:
+
+  FoFiBase(char *fileA, int lenA, GBool freeFileDataA);
+  static char *readFile(char *fileName, int *fileLen);
+
+  // S = signed / U = unsigned
+  // 8/16/32/Var = word length, in bytes
+  // BE = big endian
+  int getS8(int pos, GBool *ok);
+  int getU8(int pos, GBool *ok);
+  int getS16BE(int pos, GBool *ok);
+  int getU16BE(int pos, GBool *ok);
+  int getS32BE(int pos, GBool *ok);
+  Guint getU32BE(int pos, GBool *ok);
+  Guint getUVarBE(int pos, int size, GBool *ok);
+
+  GBool checkRegion(int pos, int size);
+
+  Guchar *fileData;
+  Guchar *file;
+  int len;
+  GBool freeFileData;
+};
+
+#endif
diff --git a/xpdf/FoFiEncodings.cc b/xpdf/FoFiEncodings.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/FoFiEncodings.cc
@@ -0,0 +1,994 @@
+//========================================================================
+//
+// FoFiEncodings.cc
+//
+// Copyright 1999-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include "FoFiEncodings.h"
+
+//------------------------------------------------------------------------
+// Type 1 and 1C font data
+//------------------------------------------------------------------------
+
+char *fofiType1StandardEncoding[256] = {
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "space",
+  "exclam",
+  "quotedbl",
+  "numbersign",
+  "dollar",
+  "percent",
+  "ampersand",
+  "quoteright",
+  "parenleft",
+  "parenright",
+  "asterisk",
+  "plus",
+  "comma",
+  "hyphen",
+  "period",
+  "slash",
+  "zero",
+  "one",
+  "two",
+  "three",
+  "four",
+  "five",
+  "six",
+  "seven",
+  "eight",
+  "nine",
+  "colon",
+  "semicolon",
+  "less",
+  "equal",
+  "greater",
+  "question",
+  "at",
+  "A",
+  "B",
+  "C",
+  "D",
+  "E",
+  "F",
+  "G",
+  "H",
+  "I",
+  "J",
+  "K",
+  "L",
+  "M",
+  "N",
+  "O",
+  "P",
+  "Q",
+  "R",
+  "S",
+  "T",
+  "U",
+  "V",
+  "W",
+  "X",
+  "Y",
+  "Z",
+  "bracketleft",
+  "backslash",
+  "bracketright",
+  "asciicircum",
+  "underscore",
+  "quoteleft",
+  "a",
+  "b",
+  "c",
+  "d",
+  "e",
+  "f",
+  "g",
+  "h",
+  "i",
+  "j",
+  "k",
+  "l",
+  "m",
+  "n",
+  "o",
+  "p",
+  "q",
+  "r",
+  "s",
+  "t",
+  "u",
+  "v",
+  "w",
+  "x",
+  "y",
+  "z",
+  "braceleft",
+  "bar",
+  "braceright",
+  "asciitilde",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "exclamdown",
+  "cent",
+  "sterling",
+  "fraction",
+  "yen",
+  "florin",
+  "section",
+  "currency",
+  "quotesingle",
+  "quotedblleft",
+  "guillemotleft",
+  "guilsinglleft",
+  "guilsinglright",
+  "fi",
+  "fl",
+  NULL,
+  "endash",
+  "dagger",
+  "daggerdbl",
+  "periodcentered",
+  NULL,
+  "paragraph",
+  "bullet",
+  "quotesinglbase",
+  "quotedblbase",
+  "quotedblright",
+  "guillemotright",
+  "ellipsis",
+  "perthousand",
+  NULL,
+  "questiondown",
+  NULL,
+  "grave",
+  "acute",
+  "circumflex",
+  "tilde",
+  "macron",
+  "breve",
+  "dotaccent",
+  "dieresis",
+  NULL,
+  "ring",
+  "cedilla",
+  NULL,
+  "hungarumlaut",
+  "ogonek",
+  "caron",
+  "emdash",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "AE",
+  NULL,
+  "ordfeminine",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "Lslash",
+  "Oslash",
+  "OE",
+  "ordmasculine",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "ae",
+  NULL,
+  NULL,
+  NULL,
+  "dotlessi",
+  NULL,
+  NULL,
+  "lslash",
+  "oslash",
+  "oe",
+  "germandbls",
+  NULL,
+  NULL,
+  NULL,
+  NULL
+};
+
+char *fofiType1ExpertEncoding[256] = {
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "space",
+  "exclamsmall",
+  "Hungarumlautsmall",
+  NULL,
+  "dollaroldstyle",
+  "dollarsuperior",
+  "ampersandsmall",
+  "Acutesmall",
+  "parenleftsuperior",
+  "parenrightsuperior",
+  "twodotenleader",
+  "onedotenleader",
+  "comma",
+  "hyphen",
+  "period",
+  "fraction",
+  "zerooldstyle",
+  "oneoldstyle",
+  "twooldstyle",
+  "threeoldstyle",
+  "fouroldstyle",
+  "fiveoldstyle",
+  "sixoldstyle",
+  "sevenoldstyle",
+  "eightoldstyle",
+  "nineoldstyle",
+  "colon",
+  "semicolon",
+  "commasuperior",
+  "threequartersemdash",
+  "periodsuperior",
+  "questionsmall",
+  NULL,
+  "asuperior",
+  "bsuperior",
+  "centsuperior",
+  "dsuperior",
+  "esuperior",
+  NULL,
+  NULL,
+  NULL,
+  "isuperior",
+  NULL,
+  NULL,
+  "lsuperior",
+  "msuperior",
+  "nsuperior",
+  "osuperior",
+  NULL,
+  NULL,
+  "rsuperior",
+  "ssuperior",
+  "tsuperior",
+  NULL,
+  "ff",
+  "fi",
+  "fl",
+  "ffi",
+  "ffl",
+  "parenleftinferior",
+  NULL,
+  "parenrightinferior",
+  "Circumflexsmall",
+  "hyphensuperior",
+  "Gravesmall",
+  "Asmall",
+  "Bsmall",
+  "Csmall",
+  "Dsmall",
+  "Esmall",
+  "Fsmall",
+  "Gsmall",
+  "Hsmall",
+  "Ismall",
+  "Jsmall",
+  "Ksmall",
+  "Lsmall",
+  "Msmall",
+  "Nsmall",
+  "Osmall",
+  "Psmall",
+  "Qsmall",
+  "Rsmall",
+  "Ssmall",
+  "Tsmall",
+  "Usmall",
+  "Vsmall",
+  "Wsmall",
+  "Xsmall",
+  "Ysmall",
+  "Zsmall",
+  "colonmonetary",
+  "onefitted",
+  "rupiah",
+  "Tildesmall",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "exclamdownsmall",
+  "centoldstyle",
+  "Lslashsmall",
+  NULL,
+  NULL,
+  "Scaronsmall",
+  "Zcaronsmall",
+  "Dieresissmall",
+  "Brevesmall",
+  "Caronsmall",
+  NULL,
+  "Dotaccentsmall",
+  NULL,
+  NULL,
+  "Macronsmall",
+  NULL,
+  NULL,
+  "figuredash",
+  "hypheninferior",
+  NULL,
+  NULL,
+  "Ogoneksmall",
+  "Ringsmall",
+  "Cedillasmall",
+  NULL,
+  NULL,
+  NULL,
+  "onequarter",
+  "onehalf",
+  "threequarters",
+  "questiondownsmall",
+  "oneeighth",
+  "threeeighths",
+  "fiveeighths",
+  "seveneighths",
+  "onethird",
+  "twothirds",
+  NULL,
+  NULL,
+  "zerosuperior",
+  "onesuperior",
+  "twosuperior",
+  "threesuperior",
+  "foursuperior",
+  "fivesuperior",
+  "sixsuperior",
+  "sevensuperior",
+  "eightsuperior",
+  "ninesuperior",
+  "zeroinferior",
+  "oneinferior",
+  "twoinferior",
+  "threeinferior",
+  "fourinferior",
+  "fiveinferior",
+  "sixinferior",
+  "seveninferior",
+  "eightinferior",
+  "nineinferior",
+  "centinferior",
+  "dollarinferior",
+  "periodinferior",
+  "commainferior",
+  "Agravesmall",
+  "Aacutesmall",
+  "Acircumflexsmall",
+  "Atildesmall",
+  "Adieresissmall",
+  "Aringsmall",
+  "AEsmall",
+  "Ccedillasmall",
+  "Egravesmall",
+  "Eacutesmall",
+  "Ecircumflexsmall",
+  "Edieresissmall",
+  "Igravesmall",
+  "Iacutesmall",
+  "Icircumflexsmall",
+  "Idieresissmall",
+  "Ethsmall",
+  "Ntildesmall",
+  "Ogravesmall",
+  "Oacutesmall",
+  "Ocircumflexsmall",
+  "Otildesmall",
+  "Odieresissmall",
+  "OEsmall",
+  "Oslashsmall",
+  "Ugravesmall",
+  "Uacutesmall",
+  "Ucircumflexsmall",
+  "Udieresissmall",
+  "Yacutesmall",
+  "Thornsmall",
+  "Ydieresissmall"
+};
+
+//------------------------------------------------------------------------
+// Type 1C font data
+//------------------------------------------------------------------------
+
+char *fofiType1CStdStrings[391] = {
+  ".notdef",
+  "space",
+  "exclam",
+  "quotedbl",
+  "numbersign",
+  "dollar",
+  "percent",
+  "ampersand",
+  "quoteright",
+  "parenleft",
+  "parenright",
+  "asterisk",
+  "plus",
+  "comma",
+  "hyphen",
+  "period",
+  "slash",
+  "zero",
+  "one",
+  "two",
+  "three",
+  "four",
+  "five",
+  "six",
+  "seven",
+  "eight",
+  "nine",
+  "colon",
+  "semicolon",
+  "less",
+  "equal",
+  "greater",
+  "question",
+  "at",
+  "A",
+  "B",
+  "C",
+  "D",
+  "E",
+  "F",
+  "G",
+  "H",
+  "I",
+  "J",
+  "K",
+  "L",
+  "M",
+  "N",
+  "O",
+  "P",
+  "Q",
+  "R",
+  "S",
+  "T",
+  "U",
+  "V",
+  "W",
+  "X",
+  "Y",
+  "Z",
+  "bracketleft",
+  "backslash",
+  "bracketright",
+  "asciicircum",
+  "underscore",
+  "quoteleft",
+  "a",
+  "b",
+  "c",
+  "d",
+  "e",
+  "f",
+  "g",
+  "h",
+  "i",
+  "j",
+  "k",
+  "l",
+  "m",
+  "n",
+  "o",
+  "p",
+  "q",
+  "r",
+  "s",
+  "t",
+  "u",
+  "v",
+  "w",
+  "x",
+  "y",
+  "z",
+  "braceleft",
+  "bar",
+  "braceright",
+  "asciitilde",
+  "exclamdown",
+  "cent",
+  "sterling",
+  "fraction",
+  "yen",
+  "florin",
+  "section",
+  "currency",
+  "quotesingle",
+  "quotedblleft",
+  "guillemotleft",
+  "guilsinglleft",
+  "guilsinglright",
+  "fi",
+  "fl",
+  "endash",
+  "dagger",
+  "daggerdbl",
+  "periodcentered",
+  "paragraph",
+  "bullet",
+  "quotesinglbase",
+  "quotedblbase",
+  "quotedblright",
+  "guillemotright",
+  "ellipsis",
+  "perthousand",
+  "questiondown",
+  "grave",
+  "acute",
+  "circumflex",
+  "tilde",
+  "macron",
+  "breve",
+  "dotaccent",
+  "dieresis",
+  "ring",
+  "cedilla",
+  "hungarumlaut",
+  "ogonek",
+  "caron",
+  "emdash",
+  "AE",
+  "ordfeminine",
+  "Lslash",
+  "Oslash",
+  "OE",
+  "ordmasculine",
+  "ae",
+  "dotlessi",
+  "lslash",
+  "oslash",
+  "oe",
+  "germandbls",
+  "onesuperior",
+  "logicalnot",
+  "mu",
+  "trademark",
+  "Eth",
+  "onehalf",
+  "plusminus",
+  "Thorn",
+  "onequarter",
+  "divide",
+  "brokenbar",
+  "degree",
+  "thorn",
+  "threequarters",
+  "twosuperior",
+  "registered",
+  "minus",
+  "eth",
+  "multiply",
+  "threesuperior",
+  "copyright",
+  "Aacute",
+  "Acircumflex",
+  "Adieresis",
+  "Agrave",
+  "Aring",
+  "Atilde",
+  "Ccedilla",
+  "Eacute",
+  "Ecircumflex",
+  "Edieresis",
+  "Egrave",
+  "Iacute",
+  "Icircumflex",
+  "Idieresis",
+  "Igrave",
+  "Ntilde",
+  "Oacute",
+  "Ocircumflex",
+  "Odieresis",
+  "Ograve",
+  "Otilde",
+  "Scaron",
+  "Uacute",
+  "Ucircumflex",
+  "Udieresis",
+  "Ugrave",
+  "Yacute",
+  "Ydieresis",
+  "Zcaron",
+  "aacute",
+  "acircumflex",
+  "adieresis",
+  "agrave",
+  "aring",
+  "atilde",
+  "ccedilla",
+  "eacute",
+  "ecircumflex",
+  "edieresis",
+  "egrave",
+  "iacute",
+  "icircumflex",
+  "idieresis",
+  "igrave",
+  "ntilde",
+  "oacute",
+  "ocircumflex",
+  "odieresis",
+  "ograve",
+  "otilde",
+  "scaron",
+  "uacute",
+  "ucircumflex",
+  "udieresis",
+  "ugrave",
+  "yacute",
+  "ydieresis",
+  "zcaron",
+  "exclamsmall",
+  "Hungarumlautsmall",
+  "dollaroldstyle",
+  "dollarsuperior",
+  "ampersandsmall",
+  "Acutesmall",
+  "parenleftsuperior",
+  "parenrightsuperior",
+  "twodotenleader",
+  "onedotenleader",
+  "zerooldstyle",
+  "oneoldstyle",
+  "twooldstyle",
+  "threeoldstyle",
+  "fouroldstyle",
+  "fiveoldstyle",
+  "sixoldstyle",
+  "sevenoldstyle",
+  "eightoldstyle",
+  "nineoldstyle",
+  "commasuperior",
+  "threequartersemdash",
+  "periodsuperior",
+  "questionsmall",
+  "asuperior",
+  "bsuperior",
+  "centsuperior",
+  "dsuperior",
+  "esuperior",
+  "isuperior",
+  "lsuperior",
+  "msuperior",
+  "nsuperior",
+  "osuperior",
+  "rsuperior",
+  "ssuperior",
+  "tsuperior",
+  "ff",
+  "ffi",
+  "ffl",
+  "parenleftinferior",
+  "parenrightinferior",
+  "Circumflexsmall",
+  "hyphensuperior",
+  "Gravesmall",
+  "Asmall",
+  "Bsmall",
+  "Csmall",
+  "Dsmall",
+  "Esmall",
+  "Fsmall",
+  "Gsmall",
+  "Hsmall",
+  "Ismall",
+  "Jsmall",
+  "Ksmall",
+  "Lsmall",
+  "Msmall",
+  "Nsmall",
+  "Osmall",
+  "Psmall",
+  "Qsmall",
+  "Rsmall",
+  "Ssmall",
+  "Tsmall",
+  "Usmall",
+  "Vsmall",
+  "Wsmall",
+  "Xsmall",
+  "Ysmall",
+  "Zsmall",
+  "colonmonetary",
+  "onefitted",
+  "rupiah",
+  "Tildesmall",
+  "exclamdownsmall",
+  "centoldstyle",
+  "Lslashsmall",
+  "Scaronsmall",
+  "Zcaronsmall",
+  "Dieresissmall",
+  "Brevesmall",
+  "Caronsmall",
+  "Dotaccentsmall",
+  "Macronsmall",
+  "figuredash",
+  "hypheninferior",
+  "Ogoneksmall",
+  "Ringsmall",
+  "Cedillasmall",
+  "questiondownsmall",
+  "oneeighth",
+  "threeeighths",
+  "fiveeighths",
+  "seveneighths",
+  "onethird",
+  "twothirds",
+  "zerosuperior",
+  "foursuperior",
+  "fivesuperior",
+  "sixsuperior",
+  "sevensuperior",
+  "eightsuperior",
+  "ninesuperior",
+  "zeroinferior",
+  "oneinferior",
+  "twoinferior",
+  "threeinferior",
+  "fourinferior",
+  "fiveinferior",
+  "sixinferior",
+  "seveninferior",
+  "eightinferior",
+  "nineinferior",
+  "centinferior",
+  "dollarinferior",
+  "periodinferior",
+  "commainferior",
+  "Agravesmall",
+  "Aacutesmall",
+  "Acircumflexsmall",
+  "Atildesmall",
+  "Adieresissmall",
+  "Aringsmall",
+  "AEsmall",
+  "Ccedillasmall",
+  "Egravesmall",
+  "Eacutesmall",
+  "Ecircumflexsmall",
+  "Edieresissmall",
+  "Igravesmall",
+  "Iacutesmall",
+  "Icircumflexsmall",
+  "Idieresissmall",
+  "Ethsmall",
+  "Ntildesmall",
+  "Ogravesmall",
+  "Oacutesmall",
+  "Ocircumflexsmall",
+  "Otildesmall",
+  "Odieresissmall",
+  "OEsmall",
+  "Oslashsmall",
+  "Ugravesmall",
+  "Uacutesmall",
+  "Ucircumflexsmall",
+  "Udieresissmall",
+  "Yacutesmall",
+  "Thornsmall",
+  "Ydieresissmall",
+  "001.000",
+  "001.001",
+  "001.002",
+  "001.003",
+  "Black",
+  "Bold",
+  "Book",
+  "Light",
+  "Medium",
+  "Regular",
+  "Roman",
+  "Semibold"
+};
+
+Gushort fofiType1CISOAdobeCharset[229] = {
+    0,   1,   2,   3,   4,   5,   6,   7,   8,   9,
+   10,  11,  12,  13,  14,  15,  16,  17,  18,  19,
+   20,  21,  22,  23,  24,  25,  26,  27,  28,  29,
+   30,  31,  32,  33,  34,  35,  36,  37,  38,  39,
+   40,  41,  42,  43,  44,  45,  46,  47,  48,  49,
+   50,  51,  52,  53,  54,  55,  56,  57,  58,  59,
+   60,  61,  62,  63,  64,  65,  66,  67,  68,  69,
+   70,  71,  72,  73,  74,  75,  76,  77,  78,  79,
+   80,  81,  82,  83,  84,  85,  86,  87,  88,  89,
+   90,  91,  92,  93,  94,  95,  96,  97,  98,  99,
+  100, 101, 102, 103, 104, 105, 106, 107, 108, 109,
+  110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
+  120, 121, 122, 123, 124, 125, 126, 127, 128, 129,
+  130, 131, 132, 133, 134, 135, 136, 137, 138, 139,
+  140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
+  150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+  160, 161, 162, 163, 164, 165, 166, 167, 168, 169,
+  170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+  180, 181, 182, 183, 184, 185, 186, 187, 188, 189,
+  190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
+  200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+  210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
+  220, 221, 222, 223, 224, 225, 226, 227, 228
+};
+
+Gushort fofiType1CExpertCharset[166] = {
+    0,   1, 229, 230, 231, 232, 233, 234, 235, 236,
+  237, 238,  13,  14,  15,  99, 239, 240, 241, 242,
+  243, 244, 245, 246, 247, 248,  27,  28, 249, 250,
+  251, 252, 253, 254, 255, 256, 257, 258, 259, 260,
+  261, 262, 263, 264, 265, 266, 109, 110, 267, 268,
+  269, 270, 271, 272, 273, 274, 275, 276, 277, 278,
+  279, 280, 281, 282, 283, 284, 285, 286, 287, 288,
+  289, 290, 291, 292, 293, 294, 295, 296, 297, 298,
+  299, 300, 301, 302, 303, 304, 305, 306, 307, 308,
+  309, 310, 311, 312, 313, 314, 315, 316, 317, 318,
+  158, 155, 163, 319, 320, 321, 322, 323, 324, 325,
+  326, 150, 164, 169, 327, 328, 329, 330, 331, 332,
+  333, 334, 335, 336, 337, 338, 339, 340, 341, 342,
+  343, 344, 345, 346, 347, 348, 349, 350, 351, 352,
+  353, 354, 355, 356, 357, 358, 359, 360, 361, 362,
+  363, 364, 365, 366, 367, 368, 369, 370, 371, 372,
+  373, 374, 375, 376, 377, 378
+};
+
+Gushort fofiType1CExpertSubsetCharset[87] = {
+    0,   1, 231, 232, 235, 236, 237, 238,  13,  14,
+   15,  99, 239, 240, 241, 242, 243, 244, 245, 246,
+  247, 248,  27,  28, 249, 250, 251, 253, 254, 255,
+  256, 257, 258, 259, 260, 261, 262, 263, 264, 265,
+  266, 109, 110, 267, 268, 269, 270, 272, 300, 301,
+  302, 305, 314, 315, 158, 155, 163, 320, 321, 322,
+  323, 324, 325, 326, 150, 164, 169, 327, 328, 329,
+  330, 331, 332, 333, 334, 335, 336, 337, 338, 339,
+  340, 341, 342, 343, 344, 345, 346
+};
diff --git a/xpdf/FoFiEncodings.h b/xpdf/FoFiEncodings.h
new file mode 100644
--- /dev/null
+++ b/xpdf/FoFiEncodings.h
@@ -0,0 +1,36 @@
+//========================================================================
+//
+// FoFiEncodings.h
+//
+// Copyright 1999-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef FOFIENCODINGS_H
+#define FOFIENCODINGS_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+
+//------------------------------------------------------------------------
+// Type 1 and 1C font data
+//------------------------------------------------------------------------
+
+extern char *fofiType1StandardEncoding[256];
+extern char *fofiType1ExpertEncoding[256];
+
+//------------------------------------------------------------------------
+// Type 1C font data
+//------------------------------------------------------------------------
+
+extern char *fofiType1CStdStrings[391];
+extern Gushort fofiType1CISOAdobeCharset[229];
+extern Gushort fofiType1CExpertCharset[166];
+extern Gushort fofiType1CExpertSubsetCharset[87];
+
+#endif
diff --git a/xpdf/FoFiTrueType.cc b/xpdf/FoFiTrueType.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/FoFiTrueType.cc
@@ -0,0 +1,2027 @@
+//========================================================================
+//
+// FoFiTrueType.cc
+//
+// Copyright 1999-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "gtypes.h"
+#include "gmem.h"
+#include "GString.h"
+#include "GHash.h"
+#include "FoFiType1C.h"
+#include "FoFiTrueType.h"
+
+//
+// Terminology
+// -----------
+//
+// character code = number used as an element of a text string
+//
+// character name = glyph name = name for a particular glyph within a
+//                  font
+//
+// glyph index = GID = position (within some internal table in the font)
+//               where the instructions to draw a particular glyph are
+//               stored
+//
+// Type 1 fonts
+// ------------
+//
+// Type 1 fonts contain:
+//
+// Encoding: array of glyph names, maps char codes to glyph names
+//
+//           Encoding[charCode] = charName
+//
+// CharStrings: dictionary of instructions, keyed by character names,
+//              maps character name to glyph data
+//
+//              CharStrings[charName] = glyphData
+//
+// TrueType fonts
+// --------------
+//
+// TrueType fonts contain:
+//
+// 'cmap' table: mapping from character code to glyph index; there may
+//               be multiple cmaps in a TrueType font
+//
+//               cmap[charCode] = gid
+//
+// 'post' table: mapping from glyph index to glyph name
+//
+//               post[gid] = glyphName
+//
+// Type 42 fonts
+// -------------
+//
+// Type 42 fonts contain:
+//
+// Encoding: array of glyph names, maps char codes to glyph names
+//
+//           Encoding[charCode] = charName
+//
+// CharStrings: dictionary of glyph indexes, keyed by character names,
+//              maps character name to glyph index
+//
+//              CharStrings[charName] = gid
+//
+
+//------------------------------------------------------------------------
+
+#define ttcfTag 0x74746366
+
+//------------------------------------------------------------------------
+
+struct TrueTypeTable {
+  Guint tag;
+  Guint checksum;
+  int offset;
+  int origOffset;
+  int len;
+};
+
+struct TrueTypeCmap {
+  int platform;
+  int encoding;
+  int offset;
+  int len;
+  int fmt;
+};
+
+struct TrueTypeLoca {
+  int idx;
+  int origOffset;
+  int newOffset;
+  int len;
+};
+
+#define cmapTag 0x636d6170
+#define glyfTag 0x676c7966
+#define headTag 0x68656164
+#define hheaTag 0x68686561
+#define hmtxTag 0x686d7478
+#define locaTag 0x6c6f6361
+#define nameTag 0x6e616d65
+#define os2Tag  0x4f532f32
+#define postTag 0x706f7374
+
+static int cmpTrueTypeLocaOffset(const void *p1, const void *p2) {
+  TrueTypeLoca *loca1 = (TrueTypeLoca *)p1;
+  TrueTypeLoca *loca2 = (TrueTypeLoca *)p2;
+
+  if (loca1->origOffset == loca2->origOffset) {
+    return loca1->idx - loca2->idx;
+  }
+  return loca1->origOffset - loca2->origOffset;
+}
+
+static int cmpTrueTypeLocaIdx(const void *p1, const void *p2) {
+  TrueTypeLoca *loca1 = (TrueTypeLoca *)p1;
+  TrueTypeLoca *loca2 = (TrueTypeLoca *)p2;
+
+  return loca1->idx - loca2->idx;
+}
+
+static int cmpTrueTypeTableTag(const void *p1, const void *p2) {
+  TrueTypeTable *tab1 = (TrueTypeTable *)p1;
+  TrueTypeTable *tab2 = (TrueTypeTable *)p2;
+
+  return (int)tab1->tag - (int)tab2->tag;
+}
+
+//------------------------------------------------------------------------
+
+struct T42Table {
+  char *tag;			// 4-byte tag
+  GBool required;		// required by the TrueType spec?
+};
+
+// TrueType tables to be embedded in Type 42 fonts.
+// NB: the table names must be in alphabetical order here.
+#define nT42Tables 11
+static T42Table t42Tables[nT42Tables] = {
+  { "cvt ", gTrue  },
+  { "fpgm", gTrue  },
+  { "glyf", gTrue  },
+  { "head", gTrue  },
+  { "hhea", gTrue  },
+  { "hmtx", gTrue  },
+  { "loca", gTrue  },
+  { "maxp", gTrue  },
+  { "prep", gTrue  },
+  { "vhea", gFalse },
+  { "vmtx", gFalse }
+};
+#define t42HeadTable  3
+#define t42LocaTable  6
+#define t42GlyfTable  2
+#define t42VheaTable  9
+#define t42VmtxTable 10
+
+//------------------------------------------------------------------------
+
+// Glyph names in some arbitrary standard order that Apple uses for
+// their TrueType fonts.
+static char *macGlyphNames[258] = {
+  ".notdef",        "null",           "CR",             "space",
+  "exclam",         "quotedbl",       "numbersign",     "dollar",
+  "percent",        "ampersand",      "quotesingle",    "parenleft",
+  "parenright",     "asterisk",       "plus",           "comma",
+  "hyphen",         "period",         "slash",          "zero",
+  "one",            "two",            "three",          "four",
+  "five",           "six",            "seven",          "eight",
+  "nine",           "colon",          "semicolon",      "less",
+  "equal",          "greater",        "question",       "at",
+  "A",              "B",              "C",              "D",
+  "E",              "F",              "G",              "H",
+  "I",              "J",              "K",              "L",
+  "M",              "N",              "O",              "P",
+  "Q",              "R",              "S",              "T",
+  "U",              "V",              "W",              "X",
+  "Y",              "Z",              "bracketleft",    "backslash",
+  "bracketright",   "asciicircum",    "underscore",     "grave",
+  "a",              "b",              "c",              "d",
+  "e",              "f",              "g",              "h",
+  "i",              "j",              "k",              "l",
+  "m",              "n",              "o",              "p",
+  "q",              "r",              "s",              "t",
+  "u",              "v",              "w",              "x",
+  "y",              "z",              "braceleft",      "bar",
+  "braceright",     "asciitilde",     "Adieresis",      "Aring",
+  "Ccedilla",       "Eacute",         "Ntilde",         "Odieresis",
+  "Udieresis",      "aacute",         "agrave",         "acircumflex",
+  "adieresis",      "atilde",         "aring",          "ccedilla",
+  "eacute",         "egrave",         "ecircumflex",    "edieresis",
+  "iacute",         "igrave",         "icircumflex",    "idieresis",
+  "ntilde",         "oacute",         "ograve",         "ocircumflex",
+  "odieresis",      "otilde",         "uacute",         "ugrave",
+  "ucircumflex",    "udieresis",      "dagger",         "degree",
+  "cent",           "sterling",       "section",        "bullet",
+  "paragraph",      "germandbls",     "registered",     "copyright",
+  "trademark",      "acute",          "dieresis",       "notequal",
+  "AE",             "Oslash",         "infinity",       "plusminus",
+  "lessequal",      "greaterequal",   "yen",            "mu1",
+  "partialdiff",    "summation",      "product",        "pi",
+  "integral",       "ordfeminine",    "ordmasculine",   "Ohm",
+  "ae",             "oslash",         "questiondown",   "exclamdown",
+  "logicalnot",     "radical",        "florin",         "approxequal",
+  "increment",      "guillemotleft",  "guillemotright", "ellipsis",
+  "nbspace",        "Agrave",         "Atilde",         "Otilde",
+  "OE",             "oe",             "endash",         "emdash",
+  "quotedblleft",   "quotedblright",  "quoteleft",      "quoteright",
+  "divide",         "lozenge",        "ydieresis",      "Ydieresis",
+  "fraction",       "currency",       "guilsinglleft",  "guilsinglright",
+  "fi",             "fl",             "daggerdbl",      "periodcentered",
+  "quotesinglbase", "quotedblbase",   "perthousand",    "Acircumflex",
+  "Ecircumflex",    "Aacute",         "Edieresis",      "Egrave",
+  "Iacute",         "Icircumflex",    "Idieresis",      "Igrave",
+  "Oacute",         "Ocircumflex",    "applelogo",      "Ograve",
+  "Uacute",         "Ucircumflex",    "Ugrave",         "dotlessi",
+  "circumflex",     "tilde",          "overscore",      "breve",
+  "dotaccent",      "ring",           "cedilla",        "hungarumlaut",
+  "ogonek",         "caron",          "Lslash",         "lslash",
+  "Scaron",         "scaron",         "Zcaron",         "zcaron",
+  "brokenbar",      "Eth",            "eth",            "Yacute",
+  "yacute",         "Thorn",          "thorn",          "minus",
+  "multiply",       "onesuperior",    "twosuperior",    "threesuperior",
+  "onehalf",        "onequarter",     "threequarters",  "franc",
+  "Gbreve",         "gbreve",         "Idot",           "Scedilla",
+  "scedilla",       "Cacute",         "cacute",         "Ccaron",
+  "ccaron",         "dmacron"
+};
+
+//------------------------------------------------------------------------
+// FoFiTrueType
+//------------------------------------------------------------------------
+
+FoFiTrueType *FoFiTrueType::make(char *fileA, int lenA) {
+  FoFiTrueType *ff;
+
+  ff = new FoFiTrueType(fileA, lenA, gFalse);
+  if (!ff->parsedOk) {
+    delete ff;
+    return NULL;
+  }
+  return ff;
+}
+
+FoFiTrueType *FoFiTrueType::load(char *fileName) {
+  FoFiTrueType *ff;
+  char *fileA;
+  int lenA;
+
+  if (!(fileA = FoFiBase::readFile(fileName, &lenA))) {
+    return NULL;
+  }
+  ff = new FoFiTrueType(fileA, lenA, gTrue);
+  if (!ff->parsedOk) {
+    delete ff;
+    return NULL;
+  }
+  return ff;
+}
+
+FoFiTrueType::FoFiTrueType(char *fileA, int lenA, GBool freeFileDataA):
+  FoFiBase(fileA, lenA, freeFileDataA)
+{
+  tables = NULL;
+  nTables = 0;
+  cmaps = NULL;
+  nCmaps = 0;
+  nameToGID = NULL;
+  parsedOk = gFalse;
+
+  parse();
+}
+
+FoFiTrueType::~FoFiTrueType() {
+  gfree(tables);
+  gfree(cmaps);
+  if (nameToGID) {
+    delete nameToGID;
+  }
+}
+
+int FoFiTrueType::getNumCmaps() {
+  return nCmaps;
+}
+
+int FoFiTrueType::getCmapPlatform(int i) {
+  return cmaps[i].platform;
+}
+
+int FoFiTrueType::getCmapEncoding(int i) {
+  return cmaps[i].encoding;
+}
+
+int FoFiTrueType::findCmap(int platform, int encoding) {
+  int i;
+
+  for (i = 0; i < nCmaps; ++i) {
+    if (cmaps[i].platform == platform && cmaps[i].encoding == encoding) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+Gushort FoFiTrueType::mapCodeToGID(int i, int c) {
+  Gushort gid;
+  int segCnt, segEnd, segStart, segDelta, segOffset;
+  int cmapFirst, cmapLen;
+  int pos, a, b, m;
+  GBool ok;
+
+  if (i < 0 || i >= nCmaps) {
+    return 0;
+  }
+  ok = gTrue;
+  pos = cmaps[i].offset;
+  switch (cmaps[i].fmt) {
+  case 0:
+    if (c < 0 || c >= cmaps[i].len - 6) {
+      return 0;
+    }
+    gid = getU8(cmaps[i].offset + 6 + c, &ok);
+    break;
+  case 4:
+    segCnt = getU16BE(pos + 6, &ok) / 2;
+    a = -1;
+    b = segCnt - 1;
+    segEnd = getU16BE(pos + 14 + 2*b, &ok);
+    if (c > segEnd) {
+      // malformed font -- the TrueType spec requires the last segEnd
+      // to be 0xffff
+      return 0;
+    }
+    // invariant: seg[a].end < code <= seg[b].end
+    while (b - a > 1 && ok) {
+      m = (a + b) / 2;
+      segEnd = getU16BE(pos + 14 + 2*m, &ok);
+      if (segEnd < c) {
+	a = m;
+      } else {
+	b = m;
+      }
+    }
+    segStart = getU16BE(pos + 16 + 2*segCnt + 2*b, &ok);
+    segDelta = getU16BE(pos + 16 + 4*segCnt + 2*b, &ok);
+    segOffset = getU16BE(pos + 16 + 6*segCnt + 2*b, &ok);
+    if (c < segStart) {
+      return 0;
+    }
+    if (segOffset == 0) {
+      gid = (c + segDelta) & 0xffff;
+    } else {
+      gid = getU16BE(pos + 16 + 6*segCnt + 2*b +
+		       segOffset + 2 * (c - segStart), &ok);
+      if (gid != 0) {
+	gid = (gid + segDelta) & 0xffff;
+      }
+    }
+    break;
+  case 6:
+    cmapFirst = getU16BE(pos + 6, &ok);
+    cmapLen = getU16BE(pos + 8, &ok);
+    if (c < cmapFirst || c >= cmapFirst + cmapLen) {
+      return 0;
+    }
+    gid = getU16BE(pos + 10 + 2 * (c - cmapFirst), &ok);
+    break;
+  default:
+    return 0;
+  }
+  if (!ok) {
+    return 0;
+  }
+  return gid;
+}
+
+int FoFiTrueType::mapNameToGID(char *name) {
+  if (!nameToGID) {
+    return 0;
+  }
+  return nameToGID->lookupInt(name);
+}
+
+Gushort *FoFiTrueType::getCIDToGIDMap(int *nCIDs) {
+  FoFiType1C *ff;
+  Gushort *map;
+  int i;
+
+  *nCIDs = 0;
+  if (!openTypeCFF) {
+    return NULL;
+  }
+  i = seekTable("CFF ");
+  if (!checkRegion(tables[i].offset, tables[i].len)) {
+    return NULL;
+  }
+  if (!(ff = FoFiType1C::make((char *)file + tables[i].offset,
+			      tables[i].len))) {
+    return NULL;
+  }
+  map = ff->getCIDToGIDMap(nCIDs);
+  delete ff;
+  return map;
+}
+
+int FoFiTrueType::getEmbeddingRights() {
+  int i, fsType;
+  GBool ok;
+
+  if ((i = seekTable("OS/2")) < 0) {
+    return 4;
+  }
+  ok = gTrue;
+  fsType = getU16BE(tables[i].offset + 8, &ok);
+  if (!ok) {
+    return 4;
+  }
+  if (fsType & 0x0008) {
+    return 2;
+  }
+  if (fsType & 0x0004) {
+    return 1;
+  }
+  if (fsType & 0x0002) {
+    return 0;
+  }
+  return 3;
+}
+
+void FoFiTrueType::convertToType42(char *psName, char **encoding,
+				   Gushort *codeToGID,
+				   FoFiOutputFunc outputFunc,
+				   void *outputStream) {
+  GString *buf;
+  GBool ok;
+
+  if (openTypeCFF) {
+    return;
+  }
+
+  // write the header
+  ok = gTrue;
+  buf = GString::format("%!PS-TrueTypeFont-{0:2g}\n",
+			(double)getS32BE(0, &ok) / 65536.0);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+
+  // begin the font dictionary
+  (*outputFunc)(outputStream, "10 dict begin\n", 14);
+  (*outputFunc)(outputStream, "/FontName /", 11);
+  (*outputFunc)(outputStream, psName, strlen(psName));
+  (*outputFunc)(outputStream, " def\n", 5);
+  (*outputFunc)(outputStream, "/FontType 42 def\n", 17);
+  (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
+  buf = GString::format("/FontBBox [{0:d} {1:d} {2:d} {3:d}] def\n",
+			bbox[0], bbox[1], bbox[2], bbox[3]);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  (*outputFunc)(outputStream, "/PaintType 0 def\n", 17);
+
+  // write the guts of the dictionary
+  cvtEncoding(encoding, outputFunc, outputStream);
+  cvtCharStrings(encoding, codeToGID, outputFunc, outputStream);
+  cvtSfnts(outputFunc, outputStream, NULL, gFalse);
+
+  // end the dictionary and define the font
+  (*outputFunc)(outputStream, "FontName currentdict end definefont pop\n", 40);
+}
+
+void FoFiTrueType::convertToType1(char *psName, char **newEncoding,
+				  GBool ascii, FoFiOutputFunc outputFunc,
+				  void *outputStream) {
+  FoFiType1C *ff;
+  int i;
+
+  if (!openTypeCFF) {
+    return;
+  }
+  i = seekTable("CFF ");
+  if (!checkRegion(tables[i].offset, tables[i].len)) {
+    return;
+  }
+  if (!(ff = FoFiType1C::make((char *)file + tables[i].offset,
+			      tables[i].len))) {
+    return;
+  }
+  ff->convertToType1(psName, newEncoding, ascii, outputFunc, outputStream);
+  delete ff;
+}
+
+void FoFiTrueType::convertToCIDType2(char *psName,
+				     Gushort *cidMap, int nCIDs,
+				     GBool needVerticalMetrics,
+				     FoFiOutputFunc outputFunc,
+				     void *outputStream) {
+  GString *buf;
+  Gushort cid;
+  GBool ok;
+  int i, j, k;
+
+  if (openTypeCFF) {
+    return;
+  }
+
+  // write the header
+  ok = gTrue;
+  buf = GString::format("%!PS-TrueTypeFont-{0:2g}\n",
+			(double)getS32BE(0, &ok) / 65536.0);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+
+  // begin the font dictionary
+  (*outputFunc)(outputStream, "20 dict begin\n", 14);
+  (*outputFunc)(outputStream, "/CIDFontName /", 14);
+  (*outputFunc)(outputStream, psName, strlen(psName));
+  (*outputFunc)(outputStream, " def\n", 5);
+  (*outputFunc)(outputStream, "/CIDFontType 2 def\n", 19);
+  (*outputFunc)(outputStream, "/FontType 42 def\n", 17);
+  (*outputFunc)(outputStream, "/CIDSystemInfo 3 dict dup begin\n", 32);
+  (*outputFunc)(outputStream, "  /Registry (Adobe) def\n", 24);
+  (*outputFunc)(outputStream, "  /Ordering (Identity) def\n", 27);
+  (*outputFunc)(outputStream, "  /Supplement 0 def\n", 20);
+  (*outputFunc)(outputStream, "  end def\n", 10);
+  (*outputFunc)(outputStream, "/GDBytes 2 def\n", 15);
+  if (cidMap) {
+    buf = GString::format("/CIDCount {0:d} def\n", nCIDs);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+    if (nCIDs > 32767) {
+      (*outputFunc)(outputStream, "/CIDMap [", 9);
+      for (i = 0; i < nCIDs; i += 32768 - 16) {
+	(*outputFunc)(outputStream, "<\n", 2);
+	for (j = 0; j < 32768 - 16 && i+j < nCIDs; j += 16) {
+	  (*outputFunc)(outputStream, "  ", 2);
+	  for (k = 0; k < 16 && i+j+k < nCIDs; ++k) {
+	    cid = cidMap[i+j+k];
+	    buf = GString::format("{0:02x}{1:02x}",
+				  (cid >> 8) & 0xff, cid & 0xff);
+	    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	    delete buf;
+	  }
+	  (*outputFunc)(outputStream, "\n", 1);
+	}
+	(*outputFunc)(outputStream, "  >", 3);
+      }
+      (*outputFunc)(outputStream, "\n", 1);
+      (*outputFunc)(outputStream, "] def\n", 6);
+    } else {
+      (*outputFunc)(outputStream, "/CIDMap <\n", 10);
+      for (i = 0; i < nCIDs; i += 16) {
+	(*outputFunc)(outputStream, "  ", 2);
+	for (j = 0; j < 16 && i+j < nCIDs; ++j) {
+	  cid = cidMap[i+j];
+	  buf = GString::format("{0:02x}{1:02x}",
+				(cid >> 8) & 0xff, cid & 0xff);
+	  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	  delete buf;
+	}
+	(*outputFunc)(outputStream, "\n", 1);
+      }
+      (*outputFunc)(outputStream, "> def\n", 6);
+    }
+  } else {
+    // direct mapping - just fill the string(s) with s[i]=i
+    buf = GString::format("/CIDCount {0:d} def\n", nGlyphs);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+    if (nGlyphs > 32767) {
+      (*outputFunc)(outputStream, "/CIDMap [\n", 10);
+      for (i = 0; i < nGlyphs; i += 32767) {
+	j = nGlyphs - i < 32767 ? nGlyphs - i : 32767;
+	buf = GString::format("  {0:d} string 0 1 {1:d} {{\n", 2 * j, j - 1);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+	buf = GString::format("    2 copy dup 2 mul exch {0:d} add -8 bitshift put\n",
+			      i);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+	buf = GString::format("    1 index exch dup 2 mul 1 add exch {0:d} add"
+			      " 255 and put\n", i);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+	(*outputFunc)(outputStream, "  } for\n", 8);
+      }
+      (*outputFunc)(outputStream, "] def\n", 6);
+    } else {
+      buf = GString::format("/CIDMap {0:d} string\n", 2 * nGlyphs);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+      buf = GString::format("  0 1 {0:d} {{\n", nGlyphs - 1);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+      (*outputFunc)(outputStream,
+		    "    2 copy dup 2 mul exch -8 bitshift put\n", 42);
+      (*outputFunc)(outputStream,
+		    "    1 index exch dup 2 mul 1 add exch 255 and put\n", 50);
+      (*outputFunc)(outputStream, "  } for\n", 8);
+      (*outputFunc)(outputStream, "def\n", 4);
+    }
+  }
+  (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
+  buf = GString::format("/FontBBox [{0:d} {1:d} {2:d} {3:d}] def\n",
+			bbox[0], bbox[1], bbox[2], bbox[3]);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  (*outputFunc)(outputStream, "/PaintType 0 def\n", 17);
+  (*outputFunc)(outputStream, "/Encoding [] readonly def\n", 26);
+  (*outputFunc)(outputStream, "/CharStrings 1 dict dup begin\n", 30);
+  (*outputFunc)(outputStream, "  /.notdef 0 def\n", 17);
+  (*outputFunc)(outputStream, "  end readonly def\n", 19);
+
+  // write the guts of the dictionary
+  cvtSfnts(outputFunc, outputStream, NULL, needVerticalMetrics);
+
+  // end the dictionary and define the font
+  (*outputFunc)(outputStream,
+		"CIDFontName currentdict end /CIDFont defineresource pop\n",
+		56);
+}
+
+void FoFiTrueType::convertToCIDType0(char *psName,
+				     FoFiOutputFunc outputFunc,
+				     void *outputStream) {
+  FoFiType1C *ff;
+  int i;
+
+  if (!openTypeCFF) {
+    return;
+  }
+  i = seekTable("CFF ");
+  if (!checkRegion(tables[i].offset, tables[i].len)) {
+    return;
+  }
+  if (!(ff = FoFiType1C::make((char *)file + tables[i].offset,
+			      tables[i].len))) {
+    return;
+  }
+  ff->convertToCIDType0(psName, outputFunc, outputStream);
+  delete ff;
+}
+
+void FoFiTrueType::convertToType0(char *psName, Gushort *cidMap, int nCIDs,
+				  GBool needVerticalMetrics,
+				  FoFiOutputFunc outputFunc,
+				  void *outputStream) {
+  GString *buf;
+  GString *sfntsName;
+  int n, i, j;
+
+  if (openTypeCFF) {
+    return;
+  }
+
+  // write the Type 42 sfnts array
+  sfntsName = (new GString(psName))->append("_sfnts");
+  cvtSfnts(outputFunc, outputStream, sfntsName, needVerticalMetrics);
+  delete sfntsName;
+
+  // write the descendant Type 42 fonts
+  n = cidMap ? nCIDs : nGlyphs;
+  for (i = 0; i < n; i += 256) {
+    (*outputFunc)(outputStream, "10 dict begin\n", 14);
+    (*outputFunc)(outputStream, "/FontName /", 11);
+    (*outputFunc)(outputStream, psName, strlen(psName));
+    buf = GString::format("_{0:02x} def\n", i >> 8);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+    (*outputFunc)(outputStream, "/FontType 42 def\n", 17);
+    (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
+    buf = GString::format("/FontBBox [{0:d} {1:d} {2:d} {3:d}] def\n",
+			  bbox[0], bbox[1], bbox[2], bbox[3]);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+    (*outputFunc)(outputStream, "/PaintType 0 def\n", 17);
+    (*outputFunc)(outputStream, "/sfnts ", 7);
+    (*outputFunc)(outputStream, psName, strlen(psName));
+    (*outputFunc)(outputStream, "_sfnts def\n", 11);
+    (*outputFunc)(outputStream, "/Encoding 256 array\n", 20);
+    for (j = 0; j < 256 && i+j < n; ++j) {
+      buf = GString::format("dup {0:d} /c{1:02x} put\n", j, j);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    (*outputFunc)(outputStream, "readonly def\n", 13);
+    (*outputFunc)(outputStream, "/CharStrings 257 dict dup begin\n", 32);
+    (*outputFunc)(outputStream, "/.notdef 0 def\n", 15);
+    for (j = 0; j < 256 && i+j < n; ++j) {
+      buf = GString::format("/c{0:02x} {1:d} def\n",
+			    j, cidMap ? cidMap[i+j] : i+j);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    (*outputFunc)(outputStream, "end readonly def\n", 17);
+    (*outputFunc)(outputStream,
+		  "FontName currentdict end definefont pop\n", 40);
+  }
+
+  // write the Type 0 parent font
+  (*outputFunc)(outputStream, "16 dict begin\n", 14);
+  (*outputFunc)(outputStream, "/FontName /", 11);
+  (*outputFunc)(outputStream, psName, strlen(psName));
+  (*outputFunc)(outputStream, " def\n", 5);
+  (*outputFunc)(outputStream, "/FontType 0 def\n", 16);
+  (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
+  (*outputFunc)(outputStream, "/FMapType 2 def\n", 16);
+  (*outputFunc)(outputStream, "/Encoding [\n", 12);
+  for (i = 0; i < n; i += 256) {
+    buf = GString::format("{0:d}\n", i >> 8);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+  }
+  (*outputFunc)(outputStream, "] def\n", 6);
+  (*outputFunc)(outputStream, "/FDepVector [\n", 14);
+  for (i = 0; i < n; i += 256) {
+    (*outputFunc)(outputStream, "/", 1);
+    (*outputFunc)(outputStream, psName, strlen(psName));
+    buf = GString::format("_{0:02x} findfont\n", i >> 8);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+  }
+  (*outputFunc)(outputStream, "] def\n", 6);
+  (*outputFunc)(outputStream, "FontName currentdict end definefont pop\n", 40);
+}
+
+void FoFiTrueType::convertToType0(char *psName,
+				  FoFiOutputFunc outputFunc,
+				  void *outputStream) {
+  FoFiType1C *ff;
+  int i;
+
+  if (!openTypeCFF) {
+    return;
+  }
+  i = seekTable("CFF ");
+  if (!checkRegion(tables[i].offset, tables[i].len)) {
+    return;
+  }
+  if (!(ff = FoFiType1C::make((char *)file + tables[i].offset,
+			      tables[i].len))) {
+    return;
+  }
+  ff->convertToType0(psName, outputFunc, outputStream);
+  delete ff;
+}
+
+void FoFiTrueType::writeTTF(FoFiOutputFunc outputFunc,
+			    void *outputStream, char *name,
+			    Gushort *codeToGID) {
+  // this substitute cmap table maps char codes 0000-ffff directly to
+  // glyphs 0000-ffff
+  static char cmapTab[36] = {
+    0, 0,			// table version number
+    0, 1,			// number of encoding tables
+    0, 1,			// platform ID
+    0, 0,			// encoding ID
+    0, 0, 0, 12,		// offset of subtable
+    0, 4,			// subtable format
+    0, 24,			// subtable length
+    0, 0,			// subtable version
+    0, 2,			// segment count * 2
+    0, 2,			// 2 * 2 ^ floor(log2(segCount))
+    0, 0,			// floor(log2(segCount))
+    0, 0,			// 2*segCount - 2*2^floor(log2(segCount))
+    (char)0xff, (char)0xff,	// endCount[0]
+    0, 0,			// reserved
+    0, 0,			// startCount[0]
+    0, 0,			// idDelta[0]
+    0, 0			// pad to a mulitple of four bytes
+  };
+  static char nameTab[8] = {
+    0, 0,			// format
+    0, 0,			// number of name records
+    0, 6,			// offset to start of string storage
+    0, 0			// pad to multiple of four bytes
+  };
+  static char postTab[32] = {
+    0, 1, 0, 0,			// format
+    0, 0, 0, 0,			// italic angle
+    0, 0,			// underline position
+    0, 0,			// underline thickness
+    0, 0, 0, 0,			// fixed pitch
+    0, 0, 0, 0,			// min Type 42 memory
+    0, 0, 0, 0,			// max Type 42 memory
+    0, 0, 0, 0,			// min Type 1 memory
+    0, 0, 0, 0			// max Type 1 memory
+  };
+  static char os2Tab[86] = {
+    0, 1,			// version
+    0, 1,			// xAvgCharWidth
+    0, 0,			// usWeightClass
+    0, 0,			// usWidthClass
+    0, 0,			// fsType
+    0, 0,			// ySubscriptXSize
+    0, 0,			// ySubscriptYSize
+    0, 0,			// ySubscriptXOffset
+    0, 0,			// ySubscriptYOffset
+    0, 0,			// ySuperscriptXSize
+    0, 0,			// ySuperscriptYSize
+    0, 0,			// ySuperscriptXOffset
+    0, 0,			// ySuperscriptYOffset
+    0, 0,			// yStrikeoutSize
+    0, 0,			// yStrikeoutPosition
+    0, 0,			// sFamilyClass
+    0, 0, 0, 0, 0,		// panose
+    0, 0, 0, 0, 0,
+    0, 0, 0, 0,			// ulUnicodeRange1
+    0, 0, 0, 0,			// ulUnicodeRange2
+    0, 0, 0, 0,			// ulUnicodeRange3
+    0, 0, 0, 0,			// ulUnicodeRange4
+    0, 0, 0, 0,			// achVendID
+    0, 0,			// fsSelection
+    0, 0,			// usFirstCharIndex
+    0, 0,			// usLastCharIndex
+    0, 0,			// sTypoAscender
+    0, 0,			// sTypoDescender
+    0, 0,			// sTypoLineGap
+    0, 0,			// usWinAscent
+    0, 0,			// usWinDescent
+    0, 0, 0, 0,			// ulCodePageRange1
+    0, 0, 0, 0			// ulCodePageRange2
+  };
+  GBool missingCmap, missingName, missingPost, missingOS2;
+  GBool unsortedLoca, badCmapLen, abbrevHMTX;
+  int nZeroLengthTables;
+  int nHMetrics, advWidth, lsb;
+  TrueTypeLoca *locaTable;
+  TrueTypeTable *newTables;
+  char *newNameTab, *newCmapTab, *newHHEATab, *newHMTXTab;
+  int nNewTables, cmapIdx, cmapLen, glyfLen, newNameLen, newCmapLen, next;
+  int newHHEALen, newHMTXLen;
+  Guint locaChecksum, glyfChecksum, fileChecksum;
+  char *tableDir;
+  char locaBuf[4], checksumBuf[4];
+  GBool ok;
+  Guint t;
+  int pos, i, j, k, n;
+
+  if (openTypeCFF) {
+    return;
+  }
+
+  // check for missing tables
+  // (Note: if the OS/2 table is missing, the Microsoft PCL5 driver
+  // will embed a PCL TrueType font with the pitch field set to zero,
+  // which apparently causes divide-by-zero errors.  As far as I can
+  // tell, the only important field in the OS/2 table is
+  // xAvgCharWidth.)
+  missingCmap = (cmapIdx = seekTable("cmap")) < 0;
+  missingName = seekTable("name") < 0;
+  missingPost = seekTable("post") < 0;
+  missingOS2 = seekTable("OS/2") < 0;
+
+  // read the loca table, check to see if it's sorted
+  locaTable = (TrueTypeLoca *)gmallocn(nGlyphs + 1, sizeof(TrueTypeLoca));
+  unsortedLoca = gFalse;
+  i = seekTable("loca");
+  pos = tables[i].offset;
+  ok = gTrue;
+  for (i = 0; i <= nGlyphs; ++i) {
+    if (locaFmt) {
+      locaTable[i].origOffset = (int)getU32BE(pos + i*4, &ok);
+    } else {
+      locaTable[i].origOffset = 2 * getU16BE(pos + i*2, &ok);
+    }
+    if (i > 0 && locaTable[i].origOffset < locaTable[i-1].origOffset) {
+      unsortedLoca = gTrue;
+    }
+    // glyph descriptions must be at least 12 bytes long (nContours,
+    // xMin, yMin, xMax, yMax, instructionLength - two bytes each);
+    // invalid glyph descriptions (even if they're never used) make
+    // Windows choke, so we work around that problem here (ideally,
+    // this would parse the glyph descriptions in the glyf table and
+    // remove any that were invalid, but this quick test is a decent
+    // start)
+    if (i > 0 &&
+	locaTable[i].origOffset - locaTable[i-1].origOffset > 0 &&
+	locaTable[i].origOffset - locaTable[i-1].origOffset < 12) {
+      locaTable[i-1].origOffset = locaTable[i].origOffset;
+      unsortedLoca = gTrue;
+    }
+    locaTable[i].idx = i;
+  }
+
+  // check for zero-length tables
+  nZeroLengthTables = 0;
+  for (i = 0; i < nTables; ++i) {
+    if (tables[i].len == 0) {
+      ++nZeroLengthTables;
+    }
+  }
+
+  // check for an incorrect cmap table length
+  badCmapLen = gFalse;
+  cmapLen = 0; // make gcc happy
+  if (!missingCmap) {
+    cmapLen = cmaps[0].offset + cmaps[0].len;
+    for (i = 1; i < nCmaps; ++i) {
+      if (cmaps[i].offset + cmaps[i].len > cmapLen) {
+	cmapLen = cmaps[i].offset + cmaps[i].len;
+      }
+    }
+    cmapLen -= tables[cmapIdx].offset;
+    if (cmapLen > tables[cmapIdx].len) {
+      badCmapLen = gTrue;
+    }
+  }
+
+  // check for an abbreviated hmtx table (this is completely legal,
+  // but confuses the Microsoft PCL5 printer driver, which generates
+  // embedded fonts with the pitch field set to zero)
+  i = seekTable("hhea");
+  nHMetrics = getU16BE(tables[i].offset + 34, &ok);
+  abbrevHMTX = nHMetrics < nGlyphs;
+
+  // if nothing is broken, just write the TTF file as is
+  if (!missingCmap && !missingName && !missingPost && !missingOS2 &&
+      !unsortedLoca && !badCmapLen && !abbrevHMTX && nZeroLengthTables == 0 &&
+      !name && !codeToGID) {
+    (*outputFunc)(outputStream, (char *)file, len);
+    goto done1;
+  }
+
+  // sort the 'loca' table: some (non-compliant) fonts have
+  // out-of-order loca tables; in order to correctly handle the case
+  // where (compliant) fonts have empty entries in the middle of the
+  // table, cmpTrueTypeLocaOffset uses offset as its primary sort key,
+  // and idx as its secondary key (ensuring that adjacent entries with
+  // the same pos value remain in the same order)
+  glyfLen = 0; // make gcc happy
+  if (unsortedLoca) {
+    qsort(locaTable, nGlyphs + 1, sizeof(TrueTypeLoca),
+	  &cmpTrueTypeLocaOffset);
+    for (i = 0; i < nGlyphs; ++i) {
+      locaTable[i].len = locaTable[i+1].origOffset - locaTable[i].origOffset;
+    }
+    locaTable[nGlyphs].len = 0;
+    qsort(locaTable, nGlyphs + 1, sizeof(TrueTypeLoca),
+	  &cmpTrueTypeLocaIdx);
+    pos = 0;
+    for (i = 0; i <= nGlyphs; ++i) {
+      locaTable[i].newOffset = pos;
+      pos += locaTable[i].len;
+      if (pos & 3) {
+	pos += 4 - (pos & 3);
+      }
+    }
+    glyfLen = pos;
+  }
+
+  // compute checksums for the loca and glyf tables
+  locaChecksum = glyfChecksum = 0;
+  if (unsortedLoca) {
+    if (locaFmt) {
+      for (j = 0; j <= nGlyphs; ++j) {
+	locaChecksum += locaTable[j].newOffset;
+      }
+    } else {
+      for (j = 0; j <= nGlyphs; j += 2) {
+	locaChecksum += locaTable[j].newOffset << 16;
+	if (j + 1 <= nGlyphs) {
+	  locaChecksum += locaTable[j+1].newOffset;
+	}
+      }
+    }
+    pos = tables[seekTable("glyf")].offset;
+    for (j = 0; j < nGlyphs; ++j) {
+      n = locaTable[j].len;
+      if (n > 0) {
+	k = locaTable[j].origOffset;
+	if (checkRegion(pos + k, n)) {
+	  glyfChecksum += computeTableChecksum(file + pos + k, n);
+	}
+      }
+    }
+  }
+
+  // construct the new name table
+  if (name) {
+    n = strlen(name);
+    newNameLen = (6 + 4*12 + 2 * (3*n + 7) + 3) & ~3;
+    newNameTab = (char *)gmalloc(newNameLen);
+    memset(newNameTab, 0, newNameLen);
+    newNameTab[0] = 0;		// format selector
+    newNameTab[1] = 0;
+    newNameTab[2] = 0;		// number of name records
+    newNameTab[3] = 4;
+    newNameTab[4] = 0;		// offset to start of string storage
+    newNameTab[5] = 6 + 4*12;
+    next = 0;
+    for (i = 0; i < 4; ++i) {
+      newNameTab[6 + i*12 + 0] = 0;	// platform ID = Microsoft
+      newNameTab[6 + i*12 + 1] = 3;
+      newNameTab[6 + i*12 + 2] = 0;	// encoding ID = Unicode
+      newNameTab[6 + i*12 + 3] = 1;
+      newNameTab[6 + i*12 + 4] = 0x04;	// language ID = American English
+      newNameTab[6 + i*12 + 5] = 0x09;
+      newNameTab[6 + i*12 + 6] = 0;	// name ID
+      newNameTab[6 + i*12 + 7] = i + 1;
+      newNameTab[6 + i*12 + 8] = i+1 == 2 ? 0 : ((2*n) >> 8); // string length
+      newNameTab[6 + i*12 + 9] = i+1 == 2 ? 14 : ((2*n) & 0xff);
+      newNameTab[6 + i*12 + 10] = next >> 8;		    // string offset
+      newNameTab[6 + i*12 + 11] = next & 0xff;
+      if (i+1 == 2) {
+	memcpy(newNameTab + 6 + 4*12 + next, "\0R\0e\0g\0u\0l\0a\0r", 14);
+	next += 14;
+      } else {
+	for (j = 0; j < n; ++j) {
+	  newNameTab[6 + 4*12 + next + 2*j] = 0;
+	  newNameTab[6 + 4*12 + next + 2*j + 1] = name[j];
+	}
+	next += 2*n;
+      }
+    }
+  } else {
+    newNameLen = 0;
+    newNameTab = NULL;
+  }
+
+  // construct the new cmap table
+  if (codeToGID) {
+    newCmapLen = 44 + 256 * 2;
+    newCmapTab = (char *)gmalloc(newCmapLen);
+    newCmapTab[0] = 0;		// table version number = 0
+    newCmapTab[1] = 0;
+    newCmapTab[2] = 0;		// number of encoding tables = 1
+    newCmapTab[3] = 1;
+    newCmapTab[4] = 0;		// platform ID = Microsoft
+    newCmapTab[5] = 3;
+    newCmapTab[6] = 0;		// encoding ID = Unicode
+    newCmapTab[7] = 1;
+    newCmapTab[8] = 0;		// offset of subtable
+    newCmapTab[9] = 0;
+    newCmapTab[10] = 0;
+    newCmapTab[11] = 12;
+    newCmapTab[12] = 0;		// subtable format = 4
+    newCmapTab[13] = 4;
+    newCmapTab[14] = 0x02;	// subtable length
+    newCmapTab[15] = 0x20;
+    newCmapTab[16] = 0;		// subtable version = 0
+    newCmapTab[17] = 0;
+    newCmapTab[18] = 0;		// segment count * 2
+    newCmapTab[19] = 4;
+    newCmapTab[20] = 0;		// 2 * 2 ^ floor(log2(segCount))
+    newCmapTab[21] = 4;
+    newCmapTab[22] = 0;		// floor(log2(segCount))
+    newCmapTab[23] = 1;
+    newCmapTab[24] = 0;		// 2*segCount - 2*2^floor(log2(segCount))
+    newCmapTab[25] = 0;
+    newCmapTab[26] = 0x00;	// endCount[0]
+    newCmapTab[27] = (char)0xff;
+    newCmapTab[28] = (char)0xff; // endCount[1]
+    newCmapTab[29] = (char)0xff;
+    newCmapTab[30] = 0;		// reserved
+    newCmapTab[31] = 0;
+    newCmapTab[32] = 0x00;	// startCount[0]
+    newCmapTab[33] = 0x00;
+    newCmapTab[34] = (char)0xff; // startCount[1]
+    newCmapTab[35] = (char)0xff;
+    newCmapTab[36] = 0;		// idDelta[0]
+    newCmapTab[37] = 0;
+    newCmapTab[38] = 0;		// idDelta[1]
+    newCmapTab[39] = 1;
+    newCmapTab[40] = 0;		// idRangeOffset[0]
+    newCmapTab[41] = 4;
+    newCmapTab[42] = 0;		// idRangeOffset[1]
+    newCmapTab[43] = 0;
+    for (i = 0; i < 256; ++i) {
+      newCmapTab[44 + 2*i] = codeToGID[i] >> 8;
+      newCmapTab[44 + 2*i + 1] = codeToGID[i] & 0xff;
+    }
+  } else {
+    newCmapLen = 0;
+    newCmapTab = NULL;
+  }
+
+  // generate the new hmtx table and the updated hhea table
+  if (abbrevHMTX) {
+    i = seekTable("hhea");
+    pos = tables[i].offset;
+    newHHEALen = 36;
+    newHHEATab = (char *)gmalloc(newHHEALen);
+    for (i = 0; i < newHHEALen; ++i) {
+      newHHEATab[i] = getU8(pos++, &ok);
+    }
+    newHHEATab[34] = nGlyphs >> 8;
+    newHHEATab[35] = nGlyphs & 0xff;
+    i = seekTable("hmtx");
+    pos = tables[i].offset;
+    newHMTXLen = 4 * nGlyphs;
+    newHMTXTab = (char *)gmalloc(newHMTXLen);
+    advWidth = 0;
+    for (i = 0; i < nHMetrics; ++i) {
+      advWidth = getU16BE(pos, &ok);
+      lsb = getU16BE(pos + 2, &ok);
+      pos += 4;
+      newHMTXTab[4*i    ] = advWidth >> 8;
+      newHMTXTab[4*i + 1] = advWidth & 0xff;
+      newHMTXTab[4*i + 2] = lsb >> 8;
+      newHMTXTab[4*i + 3] = lsb & 0xff;
+    }
+    for (; i < nGlyphs; ++i) {
+      lsb = getU16BE(pos, &ok);
+      pos += 2;
+      newHMTXTab[4*i    ] = advWidth >> 8;
+      newHMTXTab[4*i + 1] = advWidth & 0xff;
+      newHMTXTab[4*i + 2] = lsb >> 8;
+      newHMTXTab[4*i + 3] = lsb & 0xff;
+    }
+  } else {
+    newHHEATab = newHMTXTab = NULL;
+    newHHEALen = newHMTXLen = 0; // make gcc happy
+  }
+
+  // construct the new table directory:
+  // - keep all original tables with non-zero length
+  // - fix the cmap table's length, if necessary
+  // - add missing tables
+  // - sort the table by tag
+  // - compute new table positions, including 4-byte alignment
+  // - (re)compute table checksums
+  nNewTables = nTables - nZeroLengthTables +
+               (missingCmap ? 1 : 0) + (missingName ? 1 : 0) +
+               (missingPost ? 1 : 0) + (missingOS2 ? 1 : 0);
+  newTables = (TrueTypeTable *)gmallocn(nNewTables, sizeof(TrueTypeTable));
+  j = 0;
+  for (i = 0; i < nTables; ++i) {
+    if (tables[i].len > 0) {
+      newTables[j] = tables[i];
+      newTables[j].origOffset = tables[i].offset;
+      if (checkRegion(tables[i].offset, newTables[i].len)) {
+	newTables[j].checksum =
+	    computeTableChecksum(file + tables[i].offset, tables[i].len);
+	if (tables[i].tag == headTag) {
+	  // don't include the file checksum
+	  newTables[j].checksum -= getU32BE(tables[i].offset + 8, &ok);
+	}
+      }
+      if (newTables[j].tag == cmapTag && codeToGID) {
+	newTables[j].len = newCmapLen;
+	newTables[j].checksum = computeTableChecksum((Guchar *)newCmapTab,
+						     newCmapLen);
+      } else if (newTables[j].tag == cmapTag && badCmapLen) {
+	newTables[j].len = cmapLen;
+      } else if (newTables[j].tag == locaTag && unsortedLoca) {
+	newTables[j].len = (nGlyphs + 1) * (locaFmt ? 4 : 2);
+	newTables[j].checksum = locaChecksum;
+      } else if (newTables[j].tag == glyfTag && unsortedLoca) {
+	newTables[j].len = glyfLen;
+	newTables[j].checksum = glyfChecksum;
+      } else if (newTables[j].tag == nameTag && name) {
+	newTables[j].len = newNameLen;
+	newTables[j].checksum = computeTableChecksum((Guchar *)newNameTab,
+						     newNameLen);
+      } else if (newTables[j].tag == hheaTag && abbrevHMTX) {
+	newTables[j].len = newHHEALen;
+	newTables[j].checksum = computeTableChecksum((Guchar *)newHHEATab,
+						     newHHEALen);
+      } else if (newTables[j].tag == hmtxTag && abbrevHMTX) {
+	newTables[j].len = newHMTXLen;
+	newTables[j].checksum = computeTableChecksum((Guchar *)newHMTXTab,
+						     newHMTXLen);
+      }
+      ++j;
+    }
+  }
+  if (missingCmap) {
+    newTables[j].tag = cmapTag;
+    if (codeToGID) {
+      newTables[j].checksum = computeTableChecksum((Guchar *)newCmapTab,
+						   newCmapLen);
+      newTables[j].len = newCmapLen;
+    } else {
+      newTables[j].checksum = computeTableChecksum((Guchar *)cmapTab,
+						   sizeof(cmapTab));
+      newTables[j].len = sizeof(cmapTab);
+    }
+    ++j;
+  }
+  if (missingName) {
+    newTables[j].tag = nameTag;
+    if (name) {
+      newTables[j].checksum = computeTableChecksum((Guchar *)newNameTab,
+						   newNameLen);
+      newTables[j].len = newNameLen;
+    } else {
+      newTables[j].checksum = computeTableChecksum((Guchar *)nameTab,
+						   sizeof(nameTab));
+      newTables[j].len = sizeof(nameTab);
+    }
+    ++j;
+  }
+  if (missingPost) {
+    newTables[j].tag = postTag;
+    newTables[j].checksum = computeTableChecksum((Guchar *)postTab,
+						 sizeof(postTab));
+    newTables[j].len = sizeof(postTab);
+    ++j;
+  }
+  if (missingOS2) {
+    newTables[j].tag = os2Tag;
+    newTables[j].checksum = computeTableChecksum((Guchar *)os2Tab,
+						 sizeof(os2Tab));
+    newTables[j].len = sizeof(os2Tab);
+    ++j;
+  }
+  qsort(newTables, nNewTables, sizeof(TrueTypeTable),
+	&cmpTrueTypeTableTag);
+  pos = 12 + nNewTables * 16;
+  for (i = 0; i < nNewTables; ++i) {
+    newTables[i].offset = pos;
+    pos += newTables[i].len;
+    if (pos & 3) {
+      pos += 4 - (pos & 3);
+    }
+  }
+
+  // write the table directory
+  tableDir = (char *)gmalloc(12 + nNewTables * 16);
+  tableDir[0] = 0x00;					// sfnt version
+  tableDir[1] = 0x01;
+  tableDir[2] = 0x00;
+  tableDir[3] = 0x00;
+  tableDir[4] = (char)((nNewTables >> 8) & 0xff);	// numTables
+  tableDir[5] = (char)(nNewTables & 0xff);
+  for (i = -1, t = (Guint)nNewTables; t; ++i, t >>= 1) ;
+  t = 1 << (4 + i);
+  tableDir[6] = (char)((t >> 8) & 0xff);		// searchRange
+  tableDir[7] = (char)(t & 0xff);
+  tableDir[8] = (char)((i >> 8) & 0xff);		// entrySelector
+  tableDir[9] = (char)(i & 0xff);
+  t = nNewTables * 16 - t;
+  tableDir[10] = (char)((t >> 8) & 0xff);		// rangeShift
+  tableDir[11] = (char)(t & 0xff);
+  pos = 12;
+  for (i = 0; i < nNewTables; ++i) {
+    tableDir[pos   ] = (char)(newTables[i].tag >> 24);
+    tableDir[pos+ 1] = (char)(newTables[i].tag >> 16);
+    tableDir[pos+ 2] = (char)(newTables[i].tag >>  8);
+    tableDir[pos+ 3] = (char) newTables[i].tag;
+    tableDir[pos+ 4] = (char)(newTables[i].checksum >> 24);
+    tableDir[pos+ 5] = (char)(newTables[i].checksum >> 16);
+    tableDir[pos+ 6] = (char)(newTables[i].checksum >>  8);
+    tableDir[pos+ 7] = (char) newTables[i].checksum;
+    tableDir[pos+ 8] = (char)(newTables[i].offset >> 24);
+    tableDir[pos+ 9] = (char)(newTables[i].offset >> 16);
+    tableDir[pos+10] = (char)(newTables[i].offset >>  8);
+    tableDir[pos+11] = (char) newTables[i].offset;
+    tableDir[pos+12] = (char)(newTables[i].len >> 24);
+    tableDir[pos+13] = (char)(newTables[i].len >> 16);
+    tableDir[pos+14] = (char)(newTables[i].len >>  8);
+    tableDir[pos+15] = (char) newTables[i].len;
+    pos += 16;
+  }
+  (*outputFunc)(outputStream, tableDir, 12 + nNewTables * 16);
+
+  // compute the file checksum
+  fileChecksum = computeTableChecksum((Guchar *)tableDir,
+				      12 + nNewTables * 16);
+  for (i = 0; i < nNewTables; ++i) {
+    fileChecksum += newTables[i].checksum;
+  }
+  fileChecksum = 0xb1b0afba - fileChecksum;
+
+  // write the tables
+  for (i = 0; i < nNewTables; ++i) {
+    if (newTables[i].tag == headTag) {
+      if (checkRegion(newTables[i].origOffset, newTables[i].len)) {
+	(*outputFunc)(outputStream, (char *)file + newTables[i].origOffset, 8);
+	checksumBuf[0] = fileChecksum >> 24;
+	checksumBuf[1] = fileChecksum >> 16;
+	checksumBuf[2] = fileChecksum >> 8;
+	checksumBuf[3] = fileChecksum;
+	(*outputFunc)(outputStream, checksumBuf, 4);
+	(*outputFunc)(outputStream,
+		      (char *)file + newTables[i].origOffset + 12,
+		      newTables[i].len - 12);
+      } else {
+	for (j = 0; j < newTables[i].len; ++j) {
+	  (*outputFunc)(outputStream, "\0", 1);
+	}
+      }
+    } else if (newTables[i].tag == cmapTag && codeToGID) {
+      (*outputFunc)(outputStream, newCmapTab, newTables[i].len);
+    } else if (newTables[i].tag == cmapTag && missingCmap) {
+      (*outputFunc)(outputStream, cmapTab, newTables[i].len);
+    } else if (newTables[i].tag == nameTag && name) {
+      (*outputFunc)(outputStream, newNameTab, newTables[i].len);
+    } else if (newTables[i].tag == nameTag && missingName) {
+      (*outputFunc)(outputStream, nameTab, newTables[i].len);
+    } else if (newTables[i].tag == postTag && missingPost) {
+      (*outputFunc)(outputStream, postTab, newTables[i].len);
+    } else if (newTables[i].tag == os2Tag && missingOS2) {
+      (*outputFunc)(outputStream, os2Tab, newTables[i].len);
+    } else if (newTables[i].tag == hheaTag && abbrevHMTX) {
+      (*outputFunc)(outputStream, newHHEATab, newTables[i].len);
+    } else if (newTables[i].tag == hmtxTag && abbrevHMTX) {
+      (*outputFunc)(outputStream, newHMTXTab, newTables[i].len);
+    } else if (newTables[i].tag == locaTag && unsortedLoca) {
+      for (j = 0; j <= nGlyphs; ++j) {
+	if (locaFmt) {
+	  locaBuf[0] = (char)(locaTable[j].newOffset >> 24);
+	  locaBuf[1] = (char)(locaTable[j].newOffset >> 16);
+	  locaBuf[2] = (char)(locaTable[j].newOffset >>  8);
+	  locaBuf[3] = (char) locaTable[j].newOffset;
+	  (*outputFunc)(outputStream, locaBuf, 4);
+	} else {
+	  locaBuf[0] = (char)(locaTable[j].newOffset >> 9);
+	  locaBuf[1] = (char)(locaTable[j].newOffset >> 1);
+	  (*outputFunc)(outputStream, locaBuf, 2);
+	}
+      }
+    } else if (newTables[i].tag == glyfTag && unsortedLoca) {
+      pos = tables[seekTable("glyf")].offset;
+      for (j = 0; j < nGlyphs; ++j) {
+	n = locaTable[j].len;
+	if (n > 0) {
+	  k = locaTable[j].origOffset;
+	  if (checkRegion(pos + k, n)) {
+	    (*outputFunc)(outputStream, (char *)file + pos + k, n);
+	  } else {
+	    for (k = 0; k < n; ++k) {
+	      (*outputFunc)(outputStream, "\0", 1);
+	    }
+	  }
+	  if ((k = locaTable[j].len & 3)) {
+	    (*outputFunc)(outputStream, "\0\0\0\0", 4 - k);
+	  }
+	}
+      }
+    } else {
+      if (checkRegion(newTables[i].origOffset, newTables[i].len)) {
+	(*outputFunc)(outputStream, (char *)file + newTables[i].origOffset,
+		      newTables[i].len);
+      } else {
+	for (j = 0; j < newTables[i].len; ++j) {
+	  (*outputFunc)(outputStream, "\0", 1);
+	}
+      }
+    }
+    if (newTables[i].len & 3) {
+      (*outputFunc)(outputStream, "\0\0\0", 4 - (newTables[i].len & 3));
+    }
+  }
+
+  gfree(newHMTXTab);
+  gfree(newHHEATab);
+  gfree(newCmapTab);
+  gfree(newNameTab);
+  gfree(tableDir);
+  gfree(newTables);
+ done1:
+  gfree(locaTable);
+}
+
+void FoFiTrueType::cvtEncoding(char **encoding,
+			       FoFiOutputFunc outputFunc,
+			       void *outputStream) {
+  char *name;
+  GString *buf;
+  int i;
+
+  (*outputFunc)(outputStream, "/Encoding 256 array\n", 20);
+  if (encoding) {
+    for (i = 0; i < 256; ++i) {
+      if (!(name = encoding[i])) {
+	name = ".notdef";
+      }
+      buf = GString::format("dup {0:d} /", i);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+      (*outputFunc)(outputStream, name, strlen(name));
+      (*outputFunc)(outputStream, " put\n", 5);
+    }
+  } else {
+    for (i = 0; i < 256; ++i) {
+      buf = GString::format("dup {0:d} /c{1:02x} put\n", i, i);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+  }
+  (*outputFunc)(outputStream, "readonly def\n", 13);
+}
+
+void FoFiTrueType::cvtCharStrings(char **encoding,
+				  Gushort *codeToGID,
+				  FoFiOutputFunc outputFunc,
+				  void *outputStream) {
+  char *name;
+  GString *buf;
+  char buf2[16];
+  int i, k;
+
+  // always define '.notdef'
+  (*outputFunc)(outputStream, "/CharStrings 256 dict dup begin\n", 32);
+  (*outputFunc)(outputStream, "/.notdef 0 def\n", 15);
+
+  // if there's no 'cmap' table, punt
+  if (nCmaps == 0) {
+    goto err;
+  }
+
+  // map char name to glyph index:
+  // 1. use encoding to map name to char code
+  // 2. use codeToGID to map char code to glyph index
+  // N.B. We do this in reverse order because font subsets can have
+  //      weird encodings that use the same character name twice, and
+  //      the first definition is probably the one we want.
+  k = 0; // make gcc happy
+  for (i = 255; i >= 0; --i) {
+    if (encoding) {
+      name = encoding[i];
+    } else {
+      sprintf(buf2, "c%02x", i);
+      name = buf2;
+    }
+    if (name && strcmp(name, ".notdef")) {
+      k = codeToGID[i];
+      // note: Distiller (maybe Adobe's PS interpreter in general)
+      // doesn't like TrueType fonts that have CharStrings entries
+      // which point to nonexistent glyphs, hence the (k < nGlyphs)
+      // test
+      if (k > 0 && k < nGlyphs) {
+	(*outputFunc)(outputStream, "/", 1);
+	(*outputFunc)(outputStream, name, strlen(name));
+	buf = GString::format(" {0:d} def\n", k);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+      }
+    }
+  }
+
+ err:
+  (*outputFunc)(outputStream, "end readonly def\n", 17);
+}
+
+void FoFiTrueType::cvtSfnts(FoFiOutputFunc outputFunc,
+			    void *outputStream, GString *name,
+			    GBool needVerticalMetrics) {
+  Guchar headData[54];
+  TrueTypeLoca *locaTable;
+  Guchar *locaData;
+  TrueTypeTable newTables[nT42Tables];
+  Guchar tableDir[12 + nT42Tables*16];
+  GBool ok;
+  Guint checksum;
+  int nNewTables;
+  int length, pos, glyfPos, i, j, k;
+  Guchar vheaTab[36] = {
+    0, 1, 0, 0,			// table version number
+    0, 0,			// ascent
+    0, 0,			// descent
+    0, 0,			// reserved
+    0, 0,			// max advance height
+    0, 0,			// min top side bearing
+    0, 0,			// min bottom side bearing
+    0, 0,			// y max extent
+    0, 0,			// caret slope rise
+    0, 1,			// caret slope run
+    0, 0,			// caret offset
+    0, 0,			// reserved
+    0, 0,			// reserved
+    0, 0,			// reserved
+    0, 0,			// reserved
+    0, 0,			// metric data format
+    0, 1			// number of advance heights in vmtx table
+  };
+  Guchar *vmtxTab;
+  GBool needVhea, needVmtx;
+  int advance;
+
+  // construct the 'head' table, zero out the font checksum
+  i = seekTable("head");
+  pos = tables[i].offset;
+  if (!checkRegion(pos, 54)) {
+    return;
+  }
+  memcpy(headData, file + pos, 54);
+  headData[8] = headData[9] = headData[10] = headData[11] = (Guchar)0;
+
+  // read the original 'loca' table, pad entries out to 4 bytes, and
+  // sort it into proper order -- some (non-compliant) fonts have
+  // out-of-order loca tables; in order to correctly handle the case
+  // where (compliant) fonts have empty entries in the middle of the
+  // table, cmpTrueTypeLocaPos uses offset as its primary sort key,
+  // and idx as its secondary key (ensuring that adjacent entries with
+  // the same pos value remain in the same order)
+  locaTable = (TrueTypeLoca *)gmallocn(nGlyphs + 1, sizeof(TrueTypeLoca));
+  i = seekTable("loca");
+  pos = tables[i].offset;
+  ok = gTrue;
+  for (i = 0; i <= nGlyphs; ++i) {
+    locaTable[i].idx = i;
+    if (locaFmt) {
+      locaTable[i].origOffset = (int)getU32BE(pos + i*4, &ok);
+    } else {
+      locaTable[i].origOffset = 2 * getU16BE(pos + i*2, &ok);
+    }
+  }
+  qsort(locaTable, nGlyphs + 1, sizeof(TrueTypeLoca),
+	&cmpTrueTypeLocaOffset);
+  for (i = 0; i < nGlyphs; ++i) {
+    locaTable[i].len = locaTable[i+1].origOffset - locaTable[i].origOffset;
+  }
+  locaTable[nGlyphs].len = 0;
+  qsort(locaTable, nGlyphs + 1, sizeof(TrueTypeLoca),
+	&cmpTrueTypeLocaIdx);
+  pos = 0;
+  for (i = 0; i <= nGlyphs; ++i) {
+    locaTable[i].newOffset = pos;
+    pos += locaTable[i].len;
+    if (pos & 3) {
+      pos += 4 - (pos & 3);
+    }
+  }
+
+  // construct the new 'loca' table
+  locaData = (Guchar *)gmallocn(nGlyphs + 1, (locaFmt ? 4 : 2));
+  for (i = 0; i <= nGlyphs; ++i) {
+    pos = locaTable[i].newOffset;
+    if (locaFmt) {
+      locaData[4*i  ] = (Guchar)(pos >> 24);
+      locaData[4*i+1] = (Guchar)(pos >> 16);
+      locaData[4*i+2] = (Guchar)(pos >>  8);
+      locaData[4*i+3] = (Guchar) pos;
+    } else {
+      locaData[2*i  ] = (Guchar)(pos >> 9);
+      locaData[2*i+1] = (Guchar)(pos >> 1);
+    }
+  }
+
+  // count the number of tables
+  nNewTables = 0;
+  for (i = 0; i < nT42Tables; ++i) {
+    if (t42Tables[i].required ||
+	seekTable(t42Tables[i].tag) >= 0) {
+      ++nNewTables;
+    }
+  }
+  vmtxTab = NULL; // make gcc happy
+  advance = 0; // make gcc happy
+  if (needVerticalMetrics) {
+    needVhea = seekTable("vhea") < 0;
+    needVmtx = seekTable("vmtx") < 0;
+    if (needVhea || needVmtx) {
+      i = seekTable("head");
+      advance = getU16BE(tables[i].offset + 18, &ok); // units per em
+      if (needVhea) {
+	++nNewTables;
+      }
+      if (needVmtx) {
+	++nNewTables;
+      }
+    }
+  }
+
+  // construct the new table headers, including table checksums
+  // (pad each table out to a multiple of 4 bytes)
+  pos = 12 + nNewTables*16;
+  k = 0;
+  for (i = 0; i < nT42Tables; ++i) {
+    length = -1;
+    checksum = 0; // make gcc happy
+    if (i == t42HeadTable) {
+      length = 54;
+      checksum = computeTableChecksum(headData, 54);
+    } else if (i == t42LocaTable) {
+      length = (nGlyphs + 1) * (locaFmt ? 4 : 2);
+      checksum = computeTableChecksum(locaData, length);
+    } else if (i == t42GlyfTable) {
+      length = 0;
+      checksum = 0;
+      glyfPos = tables[seekTable("glyf")].offset;
+      for (j = 0; j < nGlyphs; ++j) {
+	length += locaTable[j].len;
+	if (length & 3) {
+	  length += 4 - (length & 3);
+	}
+	if (checkRegion(glyfPos + locaTable[j].origOffset, locaTable[j].len)) {
+	  checksum +=
+	      computeTableChecksum(file + glyfPos + locaTable[j].origOffset,
+				   locaTable[j].len);
+	}
+      }
+    } else {
+      if ((j = seekTable(t42Tables[i].tag)) >= 0) {
+	length = tables[j].len;
+	if (checkRegion(tables[j].offset, length)) {
+	  checksum = computeTableChecksum(file + tables[j].offset, length);
+	}
+      } else if (needVerticalMetrics && i == t42VheaTable) {
+	vheaTab[10] = advance / 256;    // max advance height
+	vheaTab[11] = advance % 256;
+	length = sizeof(vheaTab);
+	checksum = computeTableChecksum(vheaTab, length);
+      } else if (needVerticalMetrics && i == t42VmtxTable) {
+	length = 4 + (nGlyphs - 1) * 4;
+	vmtxTab = (Guchar *)gmalloc(length);
+	vmtxTab[0] = advance / 256;
+	vmtxTab[1] = advance % 256;
+	for (j = 2; j < length; j += 2) {
+	  vmtxTab[j] = 0;
+	  vmtxTab[j+1] = 0;
+	}
+	checksum = computeTableChecksum(vmtxTab, length);
+      } else if (t42Tables[i].required) {
+	//~ error(-1, "Embedded TrueType font is missing a required table ('%s')",
+	//~       t42Tables[i].tag);
+	length = 0;
+	checksum = 0;
+      }
+    }
+    if (length >= 0) {
+      newTables[k].tag = ((t42Tables[i].tag[0] & 0xff) << 24) |
+	                 ((t42Tables[i].tag[1] & 0xff) << 16) |
+	                 ((t42Tables[i].tag[2] & 0xff) <<  8) |
+	                  (t42Tables[i].tag[3] & 0xff);
+      newTables[k].checksum = checksum;
+      newTables[k].offset = pos;
+      newTables[k].len = length;
+      pos += length;
+      if (pos & 3) {
+	pos += 4 - (length & 3);
+      }
+      ++k;
+    }
+  }
+
+  // construct the table directory
+  tableDir[0] = 0x00;		// sfnt version
+  tableDir[1] = 0x01;
+  tableDir[2] = 0x00;
+  tableDir[3] = 0x00;
+  tableDir[4] = 0;		// numTables
+  tableDir[5] = nNewTables;
+  tableDir[6] = 0;		// searchRange
+  tableDir[7] = (Guchar)128;
+  tableDir[8] = 0;		// entrySelector
+  tableDir[9] = 3;
+  tableDir[10] = 0;		// rangeShift
+  tableDir[11] = (Guchar)(16 * nNewTables - 128);
+  pos = 12;
+  for (i = 0; i < nNewTables; ++i) {
+    tableDir[pos   ] = (Guchar)(newTables[i].tag >> 24);
+    tableDir[pos+ 1] = (Guchar)(newTables[i].tag >> 16);
+    tableDir[pos+ 2] = (Guchar)(newTables[i].tag >>  8);
+    tableDir[pos+ 3] = (Guchar) newTables[i].tag;
+    tableDir[pos+ 4] = (Guchar)(newTables[i].checksum >> 24);
+    tableDir[pos+ 5] = (Guchar)(newTables[i].checksum >> 16);
+    tableDir[pos+ 6] = (Guchar)(newTables[i].checksum >>  8);
+    tableDir[pos+ 7] = (Guchar) newTables[i].checksum;
+    tableDir[pos+ 8] = (Guchar)(newTables[i].offset >> 24);
+    tableDir[pos+ 9] = (Guchar)(newTables[i].offset >> 16);
+    tableDir[pos+10] = (Guchar)(newTables[i].offset >>  8);
+    tableDir[pos+11] = (Guchar) newTables[i].offset;
+    tableDir[pos+12] = (Guchar)(newTables[i].len >> 24);
+    tableDir[pos+13] = (Guchar)(newTables[i].len >> 16);
+    tableDir[pos+14] = (Guchar)(newTables[i].len >>  8);
+    tableDir[pos+15] = (Guchar) newTables[i].len;
+    pos += 16;
+  }
+
+  // compute the font checksum and store it in the head table
+  checksum = computeTableChecksum(tableDir, 12 + nNewTables*16);
+  for (i = 0; i < nNewTables; ++i) {
+    checksum += newTables[i].checksum;
+  }
+  checksum = 0xb1b0afba - checksum; // because the TrueType spec says so
+  headData[ 8] = (Guchar)(checksum >> 24);
+  headData[ 9] = (Guchar)(checksum >> 16);
+  headData[10] = (Guchar)(checksum >>  8);
+  headData[11] = (Guchar) checksum;
+
+  // start the sfnts array
+  if (name) {
+    (*outputFunc)(outputStream, "/", 1);
+    (*outputFunc)(outputStream, name->getCString(), name->getLength());
+    (*outputFunc)(outputStream, " [\n", 3);
+  } else {
+    (*outputFunc)(outputStream, "/sfnts [\n", 9);
+  }
+
+  // write the table directory
+  dumpString(tableDir, 12 + nNewTables*16, outputFunc, outputStream);
+
+  // write the tables
+  for (i = 0; i < nNewTables; ++i) {
+    if (i == t42HeadTable) {
+      dumpString(headData, 54, outputFunc, outputStream);
+    } else if (i == t42LocaTable) {
+      length = (nGlyphs + 1) * (locaFmt ? 4 : 2);
+      dumpString(locaData, length, outputFunc, outputStream);
+    } else if (i == t42GlyfTable) {
+      glyfPos = tables[seekTable("glyf")].offset;
+      for (j = 0; j < nGlyphs; ++j) {
+	if (locaTable[j].len > 0 &&
+	    checkRegion(glyfPos + locaTable[j].origOffset, locaTable[j].len)) {
+	  dumpString(file + glyfPos + locaTable[j].origOffset,
+		     locaTable[j].len, outputFunc, outputStream);
+	}
+      }
+    } else {
+      // length == 0 means the table is missing and the error was
+      // already reported during the construction of the table
+      // headers
+      if ((length = newTables[i].len) > 0) {
+	if ((j = seekTable(t42Tables[i].tag)) >= 0 &&
+	    checkRegion(tables[j].offset, tables[j].len)) {
+	  dumpString(file + tables[j].offset, tables[j].len,
+		     outputFunc, outputStream);
+	} else if (needVerticalMetrics && i == t42VheaTable) {
+	  dumpString(vheaTab, length, outputFunc, outputStream);
+	} else if (needVerticalMetrics && i == t42VmtxTable) {
+	  dumpString(vmtxTab, length, outputFunc, outputStream);
+	  gfree(vmtxTab);
+	}
+      }
+    }
+  }
+
+  // end the sfnts array
+  (*outputFunc)(outputStream, "] def\n", 6);
+
+  gfree(locaData);
+  gfree(locaTable);
+}
+
+void FoFiTrueType::dumpString(Guchar *s, int length,
+			      FoFiOutputFunc outputFunc,
+			      void *outputStream) {
+  GString *buf;
+  int pad, i, j;
+
+  (*outputFunc)(outputStream, "<", 1);
+  for (i = 0; i < length; i += 32) {
+    for (j = 0; j < 32 && i+j < length; ++j) {
+      buf = GString::format("{0:02x}", s[i+j] & 0xff);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    if (i % (65536 - 32) == 65536 - 64) {
+      (*outputFunc)(outputStream, ">\n<", 3);
+    } else if (i+32 < length) {
+      (*outputFunc)(outputStream, "\n", 1);
+    }
+  }
+  if (length & 3) {
+    pad = 4 - (length & 3);
+    for (i = 0; i < pad; ++i) {
+      (*outputFunc)(outputStream, "00", 2);
+    }
+  }
+  // add an extra zero byte because the Adobe Type 42 spec says so
+  (*outputFunc)(outputStream, "00>\n", 4);
+}
+
+Guint FoFiTrueType::computeTableChecksum(Guchar *data, int length) {
+  Guint checksum, word;
+  int i;
+
+  checksum = 0;
+  for (i = 0; i+3 < length; i += 4) {
+    word = ((data[i  ] & 0xff) << 24) +
+           ((data[i+1] & 0xff) << 16) +
+           ((data[i+2] & 0xff) <<  8) +
+            (data[i+3] & 0xff);
+    checksum += word;
+  }
+  if (length & 3) {
+    word = 0;
+    i = length & ~3;
+    switch (length & 3) {
+    case 3:
+      word |= (data[i+2] & 0xff) <<  8;
+    case 2:
+      word |= (data[i+1] & 0xff) << 16;
+    case 1:
+      word |= (data[i  ] & 0xff) << 24;
+      break;
+    }
+    checksum += word;
+  }
+  return checksum;
+}
+
+void FoFiTrueType::parse() {
+  Guint topTag;
+  int pos, ver, i, j;
+
+  parsedOk = gTrue;
+
+  // look for a collection (TTC)
+  topTag = getU32BE(0, &parsedOk);
+  if (!parsedOk) {
+    return;
+  }
+  if (topTag == ttcfTag) {
+    pos = getU32BE(12, &parsedOk);
+    if (!parsedOk) {
+      return;
+    }
+  } else {
+    pos = 0;
+  }
+
+  // check the sfnt version
+  ver = getU32BE(pos, &parsedOk);
+  if (!parsedOk) {
+    return;
+  }
+  openTypeCFF = ver == 0x4f54544f; // 'OTTO'
+
+  // read the table directory
+  nTables = getU16BE(pos + 4, &parsedOk);
+  if (!parsedOk) {
+    return;
+  }
+  tables = (TrueTypeTable *)gmallocn(nTables, sizeof(TrueTypeTable));
+  pos += 12;
+  for (i = 0; i < nTables; ++i) {
+    tables[i].tag = getU32BE(pos, &parsedOk);
+    tables[i].checksum = getU32BE(pos + 4, &parsedOk);
+    tables[i].offset = (int)getU32BE(pos + 8, &parsedOk);
+    tables[i].len = (int)getU32BE(pos + 12, &parsedOk);
+    if (tables[i].offset + tables[i].len < tables[i].offset ||
+	tables[i].offset + tables[i].len > len) {
+      parsedOk = gFalse;
+    }
+    pos += 16;
+  }
+  if (!parsedOk) {
+    return;
+  }
+
+  // check for tables that are required by both the TrueType spec and
+  // the Type 42 spec
+  if (seekTable("head") < 0 ||
+      seekTable("hhea") < 0 ||
+      seekTable("maxp") < 0 ||
+      seekTable("hmtx") < 0 ||
+      (!openTypeCFF && seekTable("loca") < 0) ||
+      (!openTypeCFF && seekTable("glyf") < 0) ||
+      (openTypeCFF && seekTable("CFF ") < 0)) {
+    parsedOk = gFalse;
+    return;
+  }
+
+  // read the cmaps
+  if ((i = seekTable("cmap")) >= 0) {
+    pos = tables[i].offset + 2;
+    nCmaps = getU16BE(pos, &parsedOk);
+    pos += 2;
+    if (!parsedOk) {
+      return;
+    }
+    cmaps = (TrueTypeCmap *)gmallocn(nCmaps, sizeof(TrueTypeCmap));
+    for (j = 0; j < nCmaps; ++j) {
+      cmaps[j].platform = getU16BE(pos, &parsedOk);
+      cmaps[j].encoding = getU16BE(pos + 2, &parsedOk);
+      cmaps[j].offset = tables[i].offset + getU32BE(pos + 4, &parsedOk);
+      pos += 8;
+      cmaps[j].fmt = getU16BE(cmaps[j].offset, &parsedOk);
+      cmaps[j].len = getU16BE(cmaps[j].offset + 2, &parsedOk);
+    }
+    if (!parsedOk) {
+      return;
+    }
+  } else {
+    nCmaps = 0;
+  }
+
+  // get the number of glyphs from the maxp table
+  i = seekTable("maxp");
+  nGlyphs = getU16BE(tables[i].offset + 4, &parsedOk);
+  if (!parsedOk) {
+    return;
+  }
+
+  // get the bbox and loca table format from the head table
+  i = seekTable("head");
+  bbox[0] = getS16BE(tables[i].offset + 36, &parsedOk);
+  bbox[1] = getS16BE(tables[i].offset + 38, &parsedOk);
+  bbox[2] = getS16BE(tables[i].offset + 40, &parsedOk);
+  bbox[3] = getS16BE(tables[i].offset + 42, &parsedOk);
+  locaFmt = getS16BE(tables[i].offset + 50, &parsedOk);
+  if (!parsedOk) {
+    return;
+  }
+
+  // make sure the loca table is sane (correct length and entries are
+  // in bounds)
+  if (!openTypeCFF) {
+    i = seekTable("loca");
+    if (tables[i].len < 0) {
+      parsedOk = gFalse;
+      return;
+    }
+    if (tables[i].len < (nGlyphs + 1) * (locaFmt ? 4 : 2)) {
+      nGlyphs = tables[i].len / (locaFmt ? 4 : 2) - 1;
+    }
+    for (j = 0; j <= nGlyphs; ++j) {
+      if (locaFmt) {
+	pos = (int)getU32BE(tables[i].offset + j*4, &parsedOk);
+      } else {
+	pos = getU16BE(tables[i].offset + j*2, &parsedOk);
+      }
+      if (pos < 0 || pos > len) {
+	parsedOk = gFalse;
+      }
+    }
+    if (!parsedOk) {
+      return;
+    }
+  }
+
+  // read the post table
+  readPostTable();
+}
+
+void FoFiTrueType::readPostTable() {
+  GString *name;
+  int tablePos, postFmt, stringIdx, stringPos;
+  GBool ok;
+  int i, j, n, m;
+
+  ok = gTrue;
+  if ((i = seekTable("post")) < 0) {
+    return;
+  }
+  tablePos = tables[i].offset;
+  postFmt = getU32BE(tablePos, &ok);
+  if (!ok) {
+    goto err;
+  }
+  if (postFmt == 0x00010000) {
+    nameToGID = new GHash(gTrue);
+    for (i = 0; i < 258; ++i) {
+      nameToGID->add(new GString(macGlyphNames[i]), i);
+    }
+  } else if (postFmt == 0x00020000) {
+    nameToGID = new GHash(gTrue);
+    n = getU16BE(tablePos + 32, &ok);
+    if (!ok) {
+      goto err;
+    }
+    if (n > nGlyphs) {
+      n = nGlyphs;
+    }
+    stringIdx = 0;
+    stringPos = tablePos + 34 + 2*n;
+    for (i = 0; i < n; ++i) {
+      j = getU16BE(tablePos + 34 + 2*i, &ok);
+      if (j < 258) {
+	nameToGID->removeInt(macGlyphNames[j]);
+	nameToGID->add(new GString(macGlyphNames[j]), i);
+      } else {
+	j -= 258;
+	if (j != stringIdx) {
+	  for (stringIdx = 0, stringPos = tablePos + 34 + 2*n;
+	       stringIdx < j;
+	       ++stringIdx, stringPos += 1 + getU8(stringPos, &ok)) ;
+	  if (!ok) {
+	    goto err;
+	  }
+	}
+	m = getU8(stringPos, &ok);
+	if (!ok || !checkRegion(stringPos + 1, m)) {
+	  goto err;
+	}
+	name = new GString((char *)&file[stringPos + 1], m);
+	nameToGID->removeInt(name);
+	nameToGID->add(name, i);
+	++stringIdx;
+	stringPos += 1 + m;
+      }
+    }
+  } else if (postFmt == 0x00028000) {
+    nameToGID = new GHash(gTrue);
+    for (i = 0; i < nGlyphs; ++i) {
+      j = getU8(tablePos + 32 + i, &ok);
+      if (!ok) {
+	goto err;
+      }
+      if (j < 258) {
+	nameToGID->removeInt(macGlyphNames[j]);
+	nameToGID->add(new GString(macGlyphNames[j]), i);
+      }
+    }
+  }
+
+  return;
+
+ err:
+  if (nameToGID) {
+    delete nameToGID;
+    nameToGID = NULL;
+  }
+}
+
+int FoFiTrueType::seekTable(char *tag) {
+  Guint tagI;
+  int i;
+
+  tagI = ((tag[0] & 0xff) << 24) |
+         ((tag[1] & 0xff) << 16) |
+         ((tag[2] & 0xff) << 8) |
+          (tag[3] & 0xff);
+  for (i = 0; i < nTables; ++i) {
+    if (tables[i].tag == tagI) {
+      return i;
+    }
+  }
+  return -1;
+}
diff --git a/xpdf/FoFiTrueType.h b/xpdf/FoFiTrueType.h
new file mode 100644
--- /dev/null
+++ b/xpdf/FoFiTrueType.h
@@ -0,0 +1,174 @@
+//========================================================================
+//
+// FoFiTrueType.h
+//
+// Copyright 1999-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef FOFITRUETYPE_H
+#define FOFITRUETYPE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "FoFiBase.h"
+
+class GString;
+class GHash;
+struct TrueTypeTable;
+struct TrueTypeCmap;
+
+//------------------------------------------------------------------------
+// FoFiTrueType
+//------------------------------------------------------------------------
+
+class FoFiTrueType: public FoFiBase {
+public:
+
+  // Create a FoFiTrueType object from a memory buffer.
+  static FoFiTrueType *make(char *fileA, int lenA);
+
+  // Create a FoFiTrueType object from a file on disk.
+  static FoFiTrueType *load(char *fileName);
+
+  virtual ~FoFiTrueType();
+
+  // Returns true if this an OpenType font containing CFF data, false
+  // if it's a TrueType font (or OpenType font with TrueType data).
+  GBool isOpenTypeCFF() { return openTypeCFF; }
+
+  // Return the number of cmaps defined by this font.
+  int getNumCmaps();
+
+  // Return the platform ID of the <i>th cmap.
+  int getCmapPlatform(int i);
+
+  // Return the encoding ID of the <i>th cmap.
+  int getCmapEncoding(int i);
+
+  // Return the index of the cmap for <platform>, <encoding>.  Returns
+  // -1 if there is no corresponding cmap.
+  int findCmap(int platform, int encoding);
+
+  // Return the GID corresponding to <c> according to the <i>th cmap.
+  Gushort mapCodeToGID(int i, int c);
+
+  // Returns the GID corresponding to <name> according to the post
+  // table.  Returns 0 if there is no mapping for <name> or if the
+  // font does not have a post table.
+  int mapNameToGID(char *name);
+
+  // Return the mapping from CIDs to GIDs, and return the number of
+  // CIDs in *<nCIDs>.  This is only useful for CID fonts.  (Only
+  // useful for OpenType CFF fonts.)
+  Gushort *getCIDToGIDMap(int *nCIDs);
+
+  // Returns the least restrictive embedding licensing right (as
+  // defined by the TrueType spec):
+  // * 4: OS/2 table is missing or invalid
+  // * 3: installable embedding
+  // * 2: editable embedding
+  // * 1: preview & print embedding
+  // * 0: restricted license embedding
+  int getEmbeddingRights();
+
+  // Convert to a Type 42 font, suitable for embedding in a PostScript
+  // file.  <psName> will be used as the PostScript font name (so we
+  // don't need to depend on the 'name' table in the font).  The
+  // <encoding> array specifies the mapping from char codes to names.
+  // If <encoding> is NULL, the encoding is unknown or undefined.  The
+  // <codeToGID> array specifies the mapping from char codes to GIDs.
+  // (Not useful for OpenType CFF fonts.)
+  void convertToType42(char *psName, char **encoding,
+		       Gushort *codeToGID,
+		       FoFiOutputFunc outputFunc, void *outputStream);
+
+  // Convert to a Type 1 font, suitable for embedding in a PostScript
+  // file.  This is only useful with 8-bit fonts.  If <newEncoding> is
+  // not NULL, it will be used in place of the encoding in the Type 1C
+  // font.  If <ascii> is true the eexec section will be hex-encoded,
+  // otherwise it will be left as binary data.  If <psName> is
+  // non-NULL, it will be used as the PostScript font name.  (Only
+  // useful for OpenType CFF fonts.)
+  void convertToType1(char *psName, char **newEncoding, GBool ascii,
+		      FoFiOutputFunc outputFunc, void *outputStream);
+
+  // Convert to a Type 2 CIDFont, suitable for embedding in a
+  // PostScript file.  <psName> will be used as the PostScript font
+  // name (so we don't need to depend on the 'name' table in the
+  // font).  The <cidMap> array maps CIDs to GIDs; it has <nCIDs>
+  // entries.  (Not useful for OpenType CFF fonts.)
+  void convertToCIDType2(char *psName, Gushort *cidMap, int nCIDs,
+			 GBool needVerticalMetrics,
+			 FoFiOutputFunc outputFunc, void *outputStream);
+
+  // Convert to a Type 0 CIDFont, suitable for embedding in a
+  // PostScript file.  <psName> will be used as the PostScript font
+  // name.  (Only useful for OpenType CFF fonts.)
+  void convertToCIDType0(char *psName,
+			 FoFiOutputFunc outputFunc, void *outputStream);
+
+  // Convert to a Type 0 (but non-CID) composite font, suitable for
+  // embedding in a PostScript file.  <psName> will be used as the
+  // PostScript font name (so we don't need to depend on the 'name'
+  // table in the font).  The <cidMap> array maps CIDs to GIDs; it has
+  // <nCIDs> entries.  (Not useful for OpenType CFF fonts.)
+  void convertToType0(char *psName, Gushort *cidMap, int nCIDs,
+		      GBool needVerticalMetrics,
+		      FoFiOutputFunc outputFunc, void *outputStream);
+
+  // Convert to a Type 0 (but non-CID) composite font, suitable for
+  // embedding in a PostScript file.  <psName> will be used as the
+  // PostScript font name.  (Only useful for OpenType CFF fonts.)
+  void convertToType0(char *psName,
+		      FoFiOutputFunc outputFunc, void *outputStream);
+
+  // Write a clean TTF file, filling in missing tables and correcting
+  // various other errors.  If <name> is non-NULL, the font is renamed
+  // to <name>.  If <codeToGID> is non-NULL, the font is re-encoded,
+  // using a Windows Unicode cmap.  If <name> is NULL and the font is
+  // complete and correct, it will be written unmodified.  (Not useful
+  // for OpenType CFF fonts.)
+  void writeTTF(FoFiOutputFunc outputFunc, void *outputStream,
+		char *name = NULL, Gushort *codeToGID = NULL);
+
+private:
+
+  FoFiTrueType(char *fileA, int lenA, GBool freeFileDataA);
+  void cvtEncoding(char **encoding,
+		   FoFiOutputFunc outputFunc,
+		   void *outputStream);
+  void cvtCharStrings(char **encoding,
+		      Gushort *codeToGID,
+		      FoFiOutputFunc outputFunc,
+		      void *outputStream);
+  void cvtSfnts(FoFiOutputFunc outputFunc,
+		void *outputStream, GString *name,
+		GBool needVerticalMetrics);
+  void dumpString(Guchar *s, int length,
+		  FoFiOutputFunc outputFunc,
+		  void *outputStream);
+  Guint computeTableChecksum(Guchar *data, int length);
+  void parse();
+  void readPostTable();
+  int seekTable(char *tag);
+
+  TrueTypeTable *tables;
+  int nTables;
+  TrueTypeCmap *cmaps;
+  int nCmaps;
+  int nGlyphs;
+  int locaFmt;
+  int bbox[4];
+  GHash *nameToGID;
+  GBool openTypeCFF;
+
+  GBool parsedOk;
+};
+
+#endif
diff --git a/xpdf/FoFiType1.cc b/xpdf/FoFiType1.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/FoFiType1.cc
@@ -0,0 +1,252 @@
+//========================================================================
+//
+// FoFiType1.cc
+//
+// Copyright 1999-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "gmem.h"
+#include "FoFiEncodings.h"
+#include "FoFiType1.h"
+
+//------------------------------------------------------------------------
+// FoFiType1
+//------------------------------------------------------------------------
+
+FoFiType1 *FoFiType1::make(char *fileA, int lenA) {
+  return new FoFiType1(fileA, lenA, gFalse);
+}
+
+FoFiType1 *FoFiType1::load(char *fileName) {
+  char *fileA;
+  int lenA;
+
+  if (!(fileA = FoFiBase::readFile(fileName, &lenA))) {
+    return NULL;
+  }
+  return new FoFiType1(fileA, lenA, gTrue);
+}
+
+FoFiType1::FoFiType1(char *fileA, int lenA, GBool freeFileDataA):
+  FoFiBase(fileA, lenA, freeFileDataA)
+{
+  name = NULL;
+  encoding = NULL;
+  parsed = gFalse;
+}
+
+FoFiType1::~FoFiType1() {
+  int i;
+
+  if (name) {
+    gfree(name);
+  }
+  if (encoding && encoding != fofiType1StandardEncoding) {
+    for (i = 0; i < 256; ++i) {
+      gfree(encoding[i]);
+    }
+    gfree(encoding);
+  }
+}
+
+char *FoFiType1::getName() {
+  if (!parsed) {
+    parse();
+  }
+  return name;
+}
+
+char **FoFiType1::getEncoding() {
+  if (!parsed) {
+    parse();
+  }
+  return encoding;
+}
+
+void FoFiType1::writeEncoded(char **newEncoding,
+			     FoFiOutputFunc outputFunc, void *outputStream) {
+  char buf[512];
+  char *line, *line2, *p;
+  int i;
+
+  // copy everything up to the encoding
+  for (line = (char *)file;
+       line && strncmp(line, "/Encoding", 9);
+       line = getNextLine(line)) ;
+  if (!line) {
+    // no encoding - just copy the whole font file
+    (*outputFunc)(outputStream, (char *)file, len);
+    return;
+  }
+  (*outputFunc)(outputStream, (char *)file, line - (char *)file);
+
+  // write the new encoding
+  (*outputFunc)(outputStream, "/Encoding 256 array\n", 20);
+  (*outputFunc)(outputStream,
+		"0 1 255 {1 index exch /.notdef put} for\n", 40);
+  for (i = 0; i < 256; ++i) {
+    if (newEncoding[i]) {
+      sprintf(buf, "dup %d /%s put\n", i, newEncoding[i]);
+      (*outputFunc)(outputStream, buf, strlen(buf));
+    }
+  }
+  (*outputFunc)(outputStream, "readonly def\n", 13);
+  
+  // find the end of the encoding data
+  //~ this ought to parse PostScript tokens
+  if (!strncmp(line, "/Encoding StandardEncoding def", 30)) {
+    line = getNextLine(line);
+  } else {
+    // skip "/Encoding" + one whitespace char,
+    // then look for 'def' preceded by PostScript whitespace
+    p = line + 10;
+    line = NULL;
+    for (; p < (char *)file + len; ++p) {
+      if ((*p == ' ' || *p == '\t' || *p == '\x0a' ||
+	   *p == '\x0d' || *p == '\x0c' || *p == '\0') &&
+	  p + 4 <= (char *)file + len &&
+	  !strncmp(p + 1, "def", 3)) {
+	line = p + 4;
+	break;
+      }
+    }
+  }
+
+  // some fonts have two /Encoding entries in their dictionary, so we
+  // check for a second one here
+  if (line) {
+    for (line2 = line, i = 0;
+	 i < 20 && line2 && strncmp(line2, "/Encoding", 9);
+	 line2 = getNextLine(line2), ++i) ;
+    if (i < 20 && line2) {
+      (*outputFunc)(outputStream, line, line2 - line);
+      if (!strncmp(line2, "/Encoding StandardEncoding def", 30)) {
+	line = getNextLine(line2);
+      } else {
+	// skip "/Encoding" + one whitespace char,
+	// then look for 'def' preceded by PostScript whitespace
+	p = line2 + 10;
+	line = NULL;
+	for (; p < (char *)file + len; ++p) {
+	  if ((*p == ' ' || *p == '\t' || *p == '\x0a' ||
+	       *p == '\x0d' || *p == '\x0c' || *p == '\0') &&
+	      p + 4 <= (char *)file + len &&
+	      !strncmp(p + 1, "def", 3)) {
+	    line = p + 4;
+	    break;
+	  }
+	}
+      }
+    }
+
+    // copy everything after the encoding
+    if (line) {
+      (*outputFunc)(outputStream, line, ((char *)file + len) - line);
+    }
+  }
+}
+
+char *FoFiType1::getNextLine(char *line) {
+  while (line < (char *)file + len && *line != '\x0a' && *line != '\x0d') {
+    ++line;
+  }
+  if (line < (char *)file + len && *line == '\x0d') {
+    ++line;
+  }
+  if (line < (char *)file + len && *line == '\x0a') {
+    ++line;
+  }
+  if (line >= (char *)file + len) {
+    return NULL;
+  }
+  return line;
+}
+
+void FoFiType1::parse() {
+  char *line, *line1, *p, *p2;
+  char buf[256];
+  char c;
+  int n, code, i, j;
+
+  for (i = 1, line = (char *)file;
+       i <= 100 && line && (!name || !encoding);
+       ++i) {
+
+    // get font name
+    if (!name && !strncmp(line, "/FontName", 9)) {
+      strncpy(buf, line, 255);
+      buf[255] = '\0';
+      if ((p = strchr(buf+9, '/')) &&
+	  (p = strtok(p+1, " \t\n\r"))) {
+	name = copyString(p);
+      }
+      line = getNextLine(line);
+
+    // get encoding
+    } else if (!encoding &&
+	       !strncmp(line, "/Encoding StandardEncoding def", 30)) {
+      encoding = fofiType1StandardEncoding;
+    } else if (!encoding &&
+	       !strncmp(line, "/Encoding 256 array", 19)) {
+      encoding = (char **)gmallocn(256, sizeof(char *));
+      for (j = 0; j < 256; ++j) {
+	encoding[j] = NULL;
+      }
+      for (j = 0, line = getNextLine(line);
+	   j < 300 && line && (line1 = getNextLine(line));
+	   ++j, line = line1) {
+	if ((n = line1 - line) > 255) {
+	  n = 255;
+	}
+	strncpy(buf, line, n);
+	buf[n] = '\0';
+	for (p = buf; *p == ' ' || *p == '\t'; ++p) ;
+	if (!strncmp(p, "dup", 3)) {
+	  for (p += 3; *p == ' ' || *p == '\t'; ++p) ;
+	  for (p2 = p; *p2 >= '0' && *p2 <= '9'; ++p2) ;
+	  if (*p2) {
+	    c = *p2;
+	    *p2 = '\0';
+	    code = atoi(p);
+	    *p2 = c;
+	    if (code == 8 && *p2 == '#') {
+	      code = 0;
+	      for (++p2; *p2 >= '0' && *p2 <= '7'; ++p2) {
+		code = code * 8 + (*p2 - '0');
+	      }
+	    }
+	    if (code < 256) {
+	      for (p = p2; *p == ' ' || *p == '\t'; ++p) ;
+	      if (*p == '/') {
+		++p;
+		for (p2 = p; *p2 && *p2 != ' ' && *p2 != '\t'; ++p2) ;
+		*p2 = '\0';
+		encoding[code] = copyString(p);
+	      }
+	    }
+	  }
+	} else {
+	  if (strtok(buf, " \t") &&
+	      (p = strtok(NULL, " \t\n\r")) && !strcmp(p, "def")) {
+	    break;
+	  }
+	}
+      }
+      //~ check for getinterval/putinterval junk
+
+    } else {
+      line = getNextLine(line);
+    }
+  }
+
+  parsed = gTrue;
+}
diff --git a/xpdf/FoFiType1.h b/xpdf/FoFiType1.h
new file mode 100644
--- /dev/null
+++ b/xpdf/FoFiType1.h
@@ -0,0 +1,59 @@
+//========================================================================
+//
+// FoFiType1.h
+//
+// Copyright 1999-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef FOFITYPE1_H
+#define FOFITYPE1_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "FoFiBase.h"
+
+//------------------------------------------------------------------------
+// FoFiType1
+//------------------------------------------------------------------------
+
+class FoFiType1: public FoFiBase {
+public:
+
+  // Create a FoFiType1 object from a memory buffer.
+  static FoFiType1 *make(char *fileA, int lenA);
+
+  // Create a FoFiType1 object from a file on disk.
+  static FoFiType1 *load(char *fileName);
+
+  virtual ~FoFiType1();
+
+  // Return the font name.
+  char *getName();
+
+  // Return the encoding, as an array of 256 names (any of which may
+  // be NULL).
+  char **getEncoding();
+
+  // Write a version of the Type 1 font file with a new encoding.
+  void writeEncoded(char **newEncoding,
+		    FoFiOutputFunc outputFunc, void *outputStream);
+
+private:
+
+  FoFiType1(char *fileA, int lenA, GBool freeFileDataA);
+
+  char *getNextLine(char *line);
+  void parse();
+
+  char *name;
+  char **encoding;
+  GBool parsed;
+};
+
+#endif
diff --git a/xpdf/FoFiType1C.cc b/xpdf/FoFiType1C.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/FoFiType1C.cc
@@ -0,0 +1,2603 @@
+//========================================================================
+//
+// FoFiType1C.cc
+//
+// Copyright 1999-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include "gmem.h"
+#include "GString.h"
+#include "FoFiEncodings.h"
+#include "FoFiType1C.h"
+
+//------------------------------------------------------------------------
+
+static char hexChars[17] = "0123456789ABCDEF";
+
+//------------------------------------------------------------------------
+// FoFiType1C
+//------------------------------------------------------------------------
+
+FoFiType1C *FoFiType1C::make(char *fileA, int lenA) {
+  FoFiType1C *ff;
+
+  ff = new FoFiType1C(fileA, lenA, gFalse);
+  if (!ff->parse()) {
+    delete ff;
+    return NULL;
+  }
+  return ff;
+}
+
+FoFiType1C *FoFiType1C::load(char *fileName) {
+  FoFiType1C *ff;
+  char *fileA;
+  int lenA;
+
+  if (!(fileA = FoFiBase::readFile(fileName, &lenA))) {
+    return NULL;
+  }
+  ff = new FoFiType1C(fileA, lenA, gTrue);
+  if (!ff->parse()) {
+    delete ff;
+    return NULL;
+  }
+  return ff;
+}
+
+FoFiType1C::FoFiType1C(char *fileA, int lenA, GBool freeFileDataA):
+  FoFiBase(fileA, lenA, freeFileDataA)
+{
+  name = NULL;
+  encoding = NULL;
+  privateDicts = NULL;
+  fdSelect = NULL;
+  charset = NULL;
+}
+
+FoFiType1C::~FoFiType1C() {
+  int i;
+
+  if (name) {
+    delete name;
+  }
+  if (encoding &&
+      encoding != fofiType1StandardEncoding &&
+      encoding != fofiType1ExpertEncoding) {
+    for (i = 0; i < 256; ++i) {
+      gfree(encoding[i]);
+    }
+    gfree(encoding);
+  }
+  if (privateDicts) {
+    gfree(privateDicts);
+  }
+  if (fdSelect) {
+    gfree(fdSelect);
+  }
+  if (charset &&
+      charset != fofiType1CISOAdobeCharset &&
+      charset != fofiType1CExpertCharset &&
+      charset != fofiType1CExpertSubsetCharset) {
+    gfree(charset);
+  }
+}
+
+char *FoFiType1C::getName() {
+  return name ? name->getCString() : (char *)NULL;
+}
+
+char **FoFiType1C::getEncoding() {
+  return encoding;
+}
+
+Gushort *FoFiType1C::getCIDToGIDMap(int *nCIDs) {
+  Gushort *map;
+  int n, i;
+
+  // a CID font's top dict has ROS as the first operator
+  if (topDict.firstOp != 0x0c1e) {
+    *nCIDs = 0;
+    return NULL;
+  }
+
+  // in a CID font, the charset data is the GID-to-CID mapping, so all
+  // we have to do is reverse it
+  n = 0;
+  for (i = 0; i < nGlyphs; ++i) {
+    if (charset[i] > n) {
+      n = charset[i];
+    }
+  }
+  ++n;
+  map = (Gushort *)gmallocn(n, sizeof(Gushort));
+  memset(map, 0, n * sizeof(Gushort));
+  for (i = 0; i < nGlyphs; ++i) {
+    map[charset[i]] = i;
+  }
+  *nCIDs = n;
+  return map;
+}
+
+void FoFiType1C::convertToType1(char *psName, char **newEncoding, GBool ascii,
+				FoFiOutputFunc outputFunc,
+				void *outputStream) {
+  int psNameLen;
+  Type1CEexecBuf eb;
+  Type1CIndex subrIdx;
+  Type1CIndexVal val;
+  GString *buf;
+  char buf2[256];
+  char **enc;
+  GBool ok;
+  int i;
+
+  if (psName) {
+    psNameLen = strlen(psName);
+  } else {
+    psName = name->getCString();
+    psNameLen = name->getLength();
+  }
+
+  // write header and font dictionary, up to encoding
+  ok = gTrue;
+  (*outputFunc)(outputStream, "%!FontType1-1.0: ", 17);
+  (*outputFunc)(outputStream, psName, psNameLen);
+  if (topDict.versionSID != 0) {
+    getString(topDict.versionSID, buf2, &ok);
+    (*outputFunc)(outputStream, buf2, strlen(buf2));
+  }
+  (*outputFunc)(outputStream, "\n", 1);
+  // the dictionary needs room for 12 entries: the following 9, plus
+  // Private and CharStrings (in the eexec section) and FID (which is
+  // added by definefont)
+  (*outputFunc)(outputStream, "12 dict begin\n", 14);
+  (*outputFunc)(outputStream, "/FontInfo 10 dict dup begin\n", 28);
+  if (topDict.versionSID != 0) {
+    (*outputFunc)(outputStream, "/version (", 10);
+    (*outputFunc)(outputStream, buf2, strlen(buf2));
+    (*outputFunc)(outputStream, ") readonly def\n", 15);
+  }
+  if (topDict.noticeSID != 0) {
+    getString(topDict.noticeSID, buf2, &ok);
+    (*outputFunc)(outputStream, "/Notice (", 9);
+    (*outputFunc)(outputStream, buf2, strlen(buf2));
+    (*outputFunc)(outputStream, ") readonly def\n", 15);
+  }
+  if (topDict.copyrightSID != 0) {
+    getString(topDict.copyrightSID, buf2, &ok);
+    (*outputFunc)(outputStream, "/Copyright (", 12);
+    (*outputFunc)(outputStream, buf2, strlen(buf2));
+    (*outputFunc)(outputStream, ") readonly def\n", 15);
+  }
+  if (topDict.fullNameSID != 0) {
+    getString(topDict.fullNameSID, buf2, &ok);
+    (*outputFunc)(outputStream, "/FullName (", 11);
+    (*outputFunc)(outputStream, buf2, strlen(buf2));
+    (*outputFunc)(outputStream, ") readonly def\n", 15);
+  }
+  if (topDict.familyNameSID != 0) {
+    getString(topDict.familyNameSID, buf2, &ok);
+    (*outputFunc)(outputStream, "/FamilyName (", 13);
+    (*outputFunc)(outputStream, buf2, strlen(buf2));
+    (*outputFunc)(outputStream, ") readonly def\n", 15);
+  }
+  if (topDict.weightSID != 0) {
+    getString(topDict.weightSID, buf2, &ok);
+    (*outputFunc)(outputStream, "/Weight (", 9);
+    (*outputFunc)(outputStream, buf2, strlen(buf2));
+    (*outputFunc)(outputStream, ") readonly def\n", 15);
+  }
+  if (topDict.isFixedPitch) {
+    (*outputFunc)(outputStream, "/isFixedPitch true def\n", 23);
+  } else {
+    (*outputFunc)(outputStream, "/isFixedPitch false def\n", 24);
+  }
+  buf = GString::format("/ItalicAngle {0:.4g} def\n", topDict.italicAngle);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  buf = GString::format("/UnderlinePosition {0:.4g} def\n",
+			topDict.underlinePosition);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  buf = GString::format("/UnderlineThickness {0:.4g} def\n",
+			topDict.underlineThickness);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  (*outputFunc)(outputStream, "end readonly def\n", 17);
+  (*outputFunc)(outputStream, "/FontName /", 11);
+  (*outputFunc)(outputStream, psName, psNameLen);
+  (*outputFunc)(outputStream, " def\n", 5);
+  buf = GString::format("/PaintType {0:d} def\n", topDict.paintType);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  (*outputFunc)(outputStream, "/FontType 1 def\n", 16);
+  buf = GString::format("/FontMatrix [{0:.8g} {1:.8g} {2:.8g} {3:.8g} {4:.8g} {5:.8g}] readonly def\n",
+			topDict.fontMatrix[0], topDict.fontMatrix[1],
+			topDict.fontMatrix[2], topDict.fontMatrix[3],
+			topDict.fontMatrix[4], topDict.fontMatrix[5]);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  buf = GString::format("/FontBBox [{0:.4g} {1:.4g} {2:.4g} {3:.4g}] readonly def\n",
+			topDict.fontBBox[0], topDict.fontBBox[1],
+			topDict.fontBBox[2], topDict.fontBBox[3]);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  buf = GString::format("/StrokeWidth {0:.4g} def\n", topDict.strokeWidth);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  if (topDict.uniqueID != 0) {
+    buf = GString::format("/UniqueID {0:d} def\n", topDict.uniqueID);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+  }
+
+  // write the encoding
+  (*outputFunc)(outputStream, "/Encoding ", 10);
+  if (!newEncoding && encoding == fofiType1StandardEncoding) {
+    (*outputFunc)(outputStream, "StandardEncoding def\n", 21);
+  } else {
+    (*outputFunc)(outputStream, "256 array\n", 10);
+    (*outputFunc)(outputStream,
+		  "0 1 255 {1 index exch /.notdef put} for\n", 40);
+    enc = newEncoding ? newEncoding : encoding;
+    for (i = 0; i < 256; ++i) {
+      if (enc[i]) {
+	buf = GString::format("dup {0:d} /{1:s} put\n", i, enc[i]);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+      }
+    }
+    (*outputFunc)(outputStream, "readonly def\n", 13);
+  }
+  (*outputFunc)(outputStream, "currentdict end\n", 16);
+
+  // start the binary section
+  (*outputFunc)(outputStream, "currentfile eexec\n", 18);
+  eb.outputFunc = outputFunc;
+  eb.outputStream = outputStream;
+  eb.ascii = ascii;
+  eb.r1 = 55665;
+  eb.line = 0;
+
+  // write the private dictionary
+  eexecWrite(&eb, "\x83\xca\x73\xd5");
+  eexecWrite(&eb, "dup /Private 32 dict dup begin\n");
+  eexecWrite(&eb, "/RD {string currentfile exch readstring pop}"
+	     " executeonly def\n");
+  eexecWrite(&eb, "/ND {noaccess def} executeonly def\n");
+  eexecWrite(&eb, "/NP {noaccess put} executeonly def\n");
+  eexecWrite(&eb, "/MinFeature {16 16} def\n");
+  eexecWrite(&eb, "/password 5839 def\n");
+  if (privateDicts[0].nBlueValues) {
+    eexecWrite(&eb, "/BlueValues [");
+    for (i = 0; i < privateDicts[0].nBlueValues; ++i) {
+      buf = GString::format("{0:s}{1:d}",
+			    i > 0 ? " " : "", privateDicts[0].blueValues[i]);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    eexecWrite(&eb, "] def\n");
+  }
+  if (privateDicts[0].nOtherBlues) {
+    eexecWrite(&eb, "/OtherBlues [");
+    for (i = 0; i < privateDicts[0].nOtherBlues; ++i) {
+      buf = GString::format("{0:s}{1:d}",
+			    i > 0 ? " " : "", privateDicts[0].otherBlues[i]);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    eexecWrite(&eb, "] def\n");
+  }
+  if (privateDicts[0].nFamilyBlues) {
+    eexecWrite(&eb, "/FamilyBlues [");
+    for (i = 0; i < privateDicts[0].nFamilyBlues; ++i) {
+      buf = GString::format("{0:s}{1:d}",
+			    i > 0 ? " " : "", privateDicts[0].familyBlues[i]);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    eexecWrite(&eb, "] def\n");
+  }
+  if (privateDicts[0].nFamilyOtherBlues) {
+    eexecWrite(&eb, "/FamilyOtherBlues [");
+    for (i = 0; i < privateDicts[0].nFamilyOtherBlues; ++i) {
+      buf = GString::format("{0:s}{1:d}", i > 0 ? " " : "",
+			    privateDicts[0].familyOtherBlues[i]);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    eexecWrite(&eb, "] def\n");
+  }
+  if (privateDicts[0].blueScale != 0.039625) {
+    buf = GString::format("/BlueScale {0:.4g} def\n",
+			  privateDicts[0].blueScale);
+    eexecWrite(&eb, buf->getCString());
+    delete buf;
+  }
+  if (privateDicts[0].blueShift != 7) {
+    buf = GString::format("/BlueShift {0:d} def\n", privateDicts[0].blueShift);
+    eexecWrite(&eb, buf->getCString());
+    delete buf;
+  }
+  if (privateDicts[0].blueFuzz != 1) {
+    buf = GString::format("/BlueFuzz {0:d} def\n", privateDicts[0].blueFuzz);
+    eexecWrite(&eb, buf->getCString());
+    delete buf;
+  }
+  if (privateDicts[0].hasStdHW) {
+    buf = GString::format("/StdHW [{0:.4g}] def\n", privateDicts[0].stdHW);
+    eexecWrite(&eb, buf->getCString());
+    delete buf;
+  }
+  if (privateDicts[0].hasStdVW) {
+    buf = GString::format("/StdVW [{0:.4g}] def\n", privateDicts[0].stdVW);
+    eexecWrite(&eb, buf->getCString());
+    delete buf;
+  }
+  if (privateDicts[0].nStemSnapH) {
+    eexecWrite(&eb, "/StemSnapH [");
+    for (i = 0; i < privateDicts[0].nStemSnapH; ++i) {
+      buf = GString::format("{0:s}{1:.4g}",
+			    i > 0 ? " " : "", privateDicts[0].stemSnapH[i]);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    eexecWrite(&eb, "] def\n");
+  }
+  if (privateDicts[0].nStemSnapV) {
+    eexecWrite(&eb, "/StemSnapV [");
+    for (i = 0; i < privateDicts[0].nStemSnapV; ++i) {
+      buf = GString::format("{0:s}{1:.4g}",
+			    i > 0 ? " " : "", privateDicts[0].stemSnapV[i]);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    eexecWrite(&eb, "] def\n");
+  }
+  if (privateDicts[0].hasForceBold) {
+    buf = GString::format("/ForceBold {0:s} def\n",
+			  privateDicts[0].forceBold ? "true" : "false");
+    eexecWrite(&eb, buf->getCString());
+    delete buf;
+  }
+  if (privateDicts[0].forceBoldThreshold != 0) {
+    buf = GString::format("/ForceBoldThreshold {0:.4g} def\n",
+			  privateDicts[0].forceBoldThreshold);
+    eexecWrite(&eb, buf->getCString());
+    delete buf;
+  }
+  if (privateDicts[0].languageGroup != 0) {
+    buf = GString::format("/LanguageGroup {0:d} def\n",
+			  privateDicts[0].languageGroup);
+    eexecWrite(&eb, buf->getCString());
+    delete buf;
+  }
+  if (privateDicts[0].expansionFactor != 0.06) {
+    buf = GString::format("/ExpansionFactor {0:.4g} def\n",
+			  privateDicts[0].expansionFactor);
+    eexecWrite(&eb, buf->getCString());
+    delete buf;
+  }
+
+  // set up subroutines
+  ok = gTrue;
+  getIndex(privateDicts[0].subrsOffset, &subrIdx, &ok);
+  if (!ok) {
+    subrIdx.pos = -1;
+  }
+
+  // write the CharStrings
+  buf = GString::format("2 index /CharStrings {0:d} dict dup begin\n",
+			nGlyphs);
+  eexecWrite(&eb, buf->getCString());
+  delete buf;
+  for (i = 0; i < nGlyphs; ++i) {
+    ok = gTrue;
+    getIndexVal(&charStringsIdx, i, &val, &ok);
+    if (ok) {
+      getString(charset[i], buf2, &ok);
+      if (ok) {
+	eexecCvtGlyph(&eb, buf2, val.pos, val.len, &subrIdx, &privateDicts[0]);
+      }
+    }
+  }
+  eexecWrite(&eb, "end\n");
+  eexecWrite(&eb, "end\n");
+  eexecWrite(&eb, "readonly put\n");
+  eexecWrite(&eb, "noaccess put\n");
+  eexecWrite(&eb, "dup /FontName get exch definefont pop\n");
+  eexecWrite(&eb, "mark currentfile closefile\n");
+
+  // trailer
+  if (ascii && eb.line > 0) {
+    (*outputFunc)(outputStream, "\n", 1);
+  }
+  for (i = 0; i < 8; ++i) {
+    (*outputFunc)(outputStream, "0000000000000000000000000000000000000000000000000000000000000000\n", 65);
+  }
+  (*outputFunc)(outputStream, "cleartomark\n", 12);
+}
+
+void FoFiType1C::convertToCIDType0(char *psName,
+				   FoFiOutputFunc outputFunc,
+				   void *outputStream) {
+  int *cidMap;
+  GString *charStrings;
+  int *charStringOffsets;
+  Type1CIndex subrIdx;
+  Type1CIndexVal val;
+  int nCIDs, gdBytes;
+  GString *buf;
+  char buf2[256];
+  GBool ok;
+  int gid, offset, n, i, j, k;
+
+  // compute the CID count and build the CID-to-GID mapping
+  nCIDs = 0;
+  for (i = 0; i < nGlyphs; ++i) {
+    if (charset[i] >= nCIDs) {
+      nCIDs = charset[i] + 1;
+    }
+  }
+  cidMap = (int *)gmallocn(nCIDs, sizeof(int));
+  for (i = 0; i < nCIDs; ++i) {
+    cidMap[i] = -1;
+  }
+  for (i = 0; i < nGlyphs; ++i) {
+    cidMap[charset[i]] = i;
+  }
+
+  // build the charstrings
+  charStrings = new GString();
+  charStringOffsets = (int *)gmallocn(nCIDs + 1, sizeof(int));
+  for (i = 0; i < nCIDs; ++i) {
+    charStringOffsets[i] = charStrings->getLength();
+    if ((gid = cidMap[i]) >= 0) {
+      ok = gTrue;
+      getIndexVal(&charStringsIdx, gid, &val, &ok);
+      if (ok) {
+	getIndex(privateDicts[fdSelect[gid]].subrsOffset, &subrIdx, &ok);
+	if (!ok) {
+	  subrIdx.pos = -1;
+	}
+	cvtGlyph(val.pos, val.len, charStrings,
+		 &subrIdx, &privateDicts[fdSelect[gid]], gTrue);
+      }
+    }
+  }
+  charStringOffsets[nCIDs] = charStrings->getLength();
+
+  // compute gdBytes = number of bytes needed for charstring offsets
+  // (offset size needs to account for the charstring offset table,
+  // with a worst case of five bytes per entry, plus the charstrings
+  // themselves)
+  i = (nCIDs + 1) * 5 + charStrings->getLength();
+  if (i < 0x100) {
+    gdBytes = 1;
+  } else if (i < 0x10000) {
+    gdBytes = 2;
+  } else if (i < 0x1000000) {
+    gdBytes = 3;
+  } else {
+    gdBytes = 4;
+  }
+
+  // begin the font dictionary
+  (*outputFunc)(outputStream, "/CIDInit /ProcSet findresource begin\n", 37);
+  (*outputFunc)(outputStream, "20 dict begin\n", 14);
+  (*outputFunc)(outputStream, "/CIDFontName /", 14);
+  (*outputFunc)(outputStream, psName, strlen(psName));
+  (*outputFunc)(outputStream, " def\n", 5);
+  (*outputFunc)(outputStream, "/CIDFontType 0 def\n", 19);
+  (*outputFunc)(outputStream, "/CIDSystemInfo 3 dict dup begin\n", 32);
+  if (topDict.registrySID > 0 && topDict.orderingSID > 0) {
+    ok = gTrue;
+    getString(topDict.registrySID, buf2, &ok);
+    if (ok) {
+      (*outputFunc)(outputStream, "  /Registry (", 13);
+      (*outputFunc)(outputStream, buf2, strlen(buf2));
+      (*outputFunc)(outputStream, ") def\n", 6);
+    }
+    ok = gTrue;
+    getString(topDict.orderingSID, buf2, &ok);
+    if (ok) {
+      (*outputFunc)(outputStream, "  /Ordering (", 13);
+      (*outputFunc)(outputStream, buf2, strlen(buf2));
+      (*outputFunc)(outputStream, ") def\n", 6);
+    }
+  } else {
+    (*outputFunc)(outputStream, "  /Registry (Adobe) def\n", 24);
+    (*outputFunc)(outputStream, "  /Ordering (Identity) def\n", 27);
+  }
+  buf = GString::format("  /Supplement {0:d} def\n", topDict.supplement);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  (*outputFunc)(outputStream, "end def\n", 8);
+  if (topDict.hasFontMatrix) {
+    buf = GString::format("/FontMatrix [{0:.8g} {1:.8g} {2:.8g} {3:.8g} {4:.8g} {5:.8g}] def\n",
+			  topDict.fontMatrix[0], topDict.fontMatrix[1],
+			  topDict.fontMatrix[2], topDict.fontMatrix[3],
+			  topDict.fontMatrix[4], topDict.fontMatrix[5]);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+  } else if (privateDicts[0].hasFontMatrix) {
+    (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
+  } else {
+    (*outputFunc)(outputStream,
+		  "/FontMatrix [0.001 0 0 0.001 0 0] def\n", 38);
+  }
+  buf = GString::format("/FontBBox [{0:.4g} {1:.4g} {2:.4g} {3:.4g}] def\n",
+			topDict.fontBBox[0], topDict.fontBBox[1],
+			topDict.fontBBox[2], topDict.fontBBox[3]);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  (*outputFunc)(outputStream, "/FontInfo 1 dict dup begin\n", 27);
+  (*outputFunc)(outputStream, "  /FSType 8 def\n", 16);
+  (*outputFunc)(outputStream, "end def\n", 8);
+
+  // CIDFont-specific entries
+  buf = GString::format("/CIDCount {0:d} def\n", nCIDs);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  (*outputFunc)(outputStream, "/FDBytes 1 def\n", 15);
+  buf = GString::format("/GDBytes {0:d} def\n", gdBytes);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  (*outputFunc)(outputStream, "/CIDMapOffset 0 def\n", 20);
+  if (topDict.paintType != 0) {
+    buf = GString::format("/PaintType {0:d} def\n", topDict.paintType);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+    buf = GString::format("/StrokeWidth {0:.4g} def\n", topDict.strokeWidth);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+  }
+
+  // FDArray entry
+  buf = GString::format("/FDArray {0:d} array\n", nFDs);
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+  for (i = 0; i < nFDs; ++i) {
+    buf = GString::format("dup {0:d} 10 dict begin\n", i);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+    (*outputFunc)(outputStream, "/FontType 1 def\n", 16);
+    if (privateDicts[i].hasFontMatrix) {
+      buf = GString::format("/FontMatrix [{0:.8g} {1:.8g} {2:.8g} {3:.8g} {4:.8g} {5:.8g}] def\n",
+			    privateDicts[i].fontMatrix[0],
+			    privateDicts[i].fontMatrix[1],
+			    privateDicts[i].fontMatrix[2],
+			    privateDicts[i].fontMatrix[3],
+			    privateDicts[i].fontMatrix[4],
+			    privateDicts[i].fontMatrix[5]);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    } else {
+      (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
+    }
+    buf = GString::format("/PaintType {0:d} def\n", topDict.paintType);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+    (*outputFunc)(outputStream, "/Private 32 dict begin\n", 23);
+    if (privateDicts[i].nBlueValues) {
+      (*outputFunc)(outputStream, "/BlueValues [", 13);
+      for (j = 0; j < privateDicts[i].nBlueValues; ++j) {
+	buf = GString::format("{0:s}{1:d}",
+			      j > 0 ? " " : "", privateDicts[i].blueValues[j]);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+      }
+      (*outputFunc)(outputStream, "] def\n", 6);
+    }
+    if (privateDicts[i].nOtherBlues) {
+      (*outputFunc)(outputStream, "/OtherBlues [", 13);
+      for (j = 0; j < privateDicts[i].nOtherBlues; ++j) {
+	buf = GString::format("{0:s}{1:d}",
+			      j > 0 ? " " : "", privateDicts[i].otherBlues[j]);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+      }
+      (*outputFunc)(outputStream, "] def\n", 6);
+    }
+    if (privateDicts[i].nFamilyBlues) {
+      (*outputFunc)(outputStream, "/FamilyBlues [", 14);
+      for (j = 0; j < privateDicts[i].nFamilyBlues; ++j) {
+	buf = GString::format("{0:s}{1:d}",
+			      j > 0 ? " " : "",
+			      privateDicts[i].familyBlues[j]);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+      }
+      (*outputFunc)(outputStream, "] def\n", 6);
+    }
+    if (privateDicts[i].nFamilyOtherBlues) {
+      (*outputFunc)(outputStream, "/FamilyOtherBlues [", 19);
+      for (j = 0; j < privateDicts[i].nFamilyOtherBlues; ++j) {
+	buf = GString::format("{0:s}{1:d}", j > 0 ? " " : "",
+			      privateDicts[i].familyOtherBlues[j]);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+      }
+      (*outputFunc)(outputStream, "] def\n", 6);
+    }
+    if (privateDicts[i].blueScale != 0.039625) {
+      buf = GString::format("/BlueScale {0:.4g} def\n",
+			    privateDicts[i].blueScale);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    if (privateDicts[i].blueShift != 7) {
+      buf = GString::format("/BlueShift {0:d} def\n",
+			    privateDicts[i].blueShift);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    if (privateDicts[i].blueFuzz != 1) {
+      buf = GString::format("/BlueFuzz {0:d} def\n", privateDicts[i].blueFuzz);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    if (privateDicts[i].hasStdHW) {
+      buf = GString::format("/StdHW [{0:.4g}] def\n", privateDicts[i].stdHW);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    if (privateDicts[i].hasStdVW) {
+      buf = GString::format("/StdVW [{0:.4g}] def\n", privateDicts[i].stdVW);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    if (privateDicts[i].nStemSnapH) {
+      (*outputFunc)(outputStream, "/StemSnapH [", 12);
+      for (j = 0; j < privateDicts[i].nStemSnapH; ++j) {
+	buf = GString::format("{0:s}{1:.4g}",
+			      j > 0 ? " " : "", privateDicts[i].stemSnapH[j]);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+      }
+      (*outputFunc)(outputStream, "] def\n", 6);
+    }
+    if (privateDicts[i].nStemSnapV) {
+      (*outputFunc)(outputStream, "/StemSnapV [", 12);
+      for (j = 0; j < privateDicts[i].nStemSnapV; ++j) {
+	buf = GString::format("{0:s}{1:.4g}",
+			      j > 0 ? " " : "", privateDicts[i].stemSnapV[j]);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+      }
+      (*outputFunc)(outputStream, "] def\n", 6);
+    }
+    if (privateDicts[i].hasForceBold) {
+      buf = GString::format("/ForceBold {0:s} def\n",
+			    privateDicts[i].forceBold ? "true" : "false");
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    if (privateDicts[i].forceBoldThreshold != 0) {
+      buf = GString::format("/ForceBoldThreshold {0:.4g} def\n",
+			    privateDicts[i].forceBoldThreshold);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    if (privateDicts[i].languageGroup != 0) {
+      buf = GString::format("/LanguageGroup {0:d} def\n",
+			    privateDicts[i].languageGroup);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    if (privateDicts[i].expansionFactor != 0.06) {
+      buf = GString::format("/ExpansionFactor {0:.4g} def\n",
+			    privateDicts[i].expansionFactor);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    (*outputFunc)(outputStream, "currentdict end def\n", 20);
+    (*outputFunc)(outputStream, "currentdict end put\n", 20);
+  }
+  (*outputFunc)(outputStream, "def\n", 4);
+
+  // start the binary section
+  offset = (nCIDs + 1) * (1 + gdBytes);
+  buf = GString::format("(Hex) {0:d} StartData\n",
+			offset + charStrings->getLength());
+  (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+  delete buf;
+
+  // write the charstring offset (CIDMap) table
+  for (i = 0; i <= nCIDs; i += 6) {
+    for (j = 0; j < 6 && i+j <= nCIDs; ++j) {
+      if (i+j < nCIDs && cidMap[i+j] >= 0) {
+	buf2[0] = (char)fdSelect[cidMap[i+j]];
+      } else {
+	buf2[0] = (char)0;
+      }
+      n = offset + charStringOffsets[i+j];
+      for (k = gdBytes; k >= 1; --k) {
+	buf2[k] = (char)(n & 0xff);
+	n >>= 8;
+      }
+      for (k = 0; k <= gdBytes; ++k) {
+	buf = GString::format("{0:02x}", buf2[k] & 0xff);
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+      }
+    }
+    (*outputFunc)(outputStream, "\n", 1);
+  }
+
+  // write the charstring data
+  n = charStrings->getLength();
+  for (i = 0; i < n; i += 32) {
+    for (j = 0; j < 32 && i+j < n; ++j) {
+      buf = GString::format("{0:02x}", charStrings->getChar(i+j) & 0xff);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    if (i + 32 >= n) {
+      (*outputFunc)(outputStream, ">", 1);
+    }
+    (*outputFunc)(outputStream, "\n", 1);
+  }
+
+  gfree(charStringOffsets);
+  delete charStrings;
+  gfree(cidMap);
+}
+
+void FoFiType1C::convertToType0(char *psName,
+				FoFiOutputFunc outputFunc,
+				void *outputStream) {
+  int *cidMap;
+  Type1CIndex subrIdx;
+  Type1CIndexVal val;
+  int nCIDs;
+  GString *buf;
+  Type1CEexecBuf eb;
+  GBool ok;
+  int fd, i, j, k;
+
+  // compute the CID count and build the CID-to-GID mapping
+  nCIDs = 0;
+  for (i = 0; i < nGlyphs; ++i) {
+    if (charset[i] >= nCIDs) {
+      nCIDs = charset[i] + 1;
+    }
+  }
+  cidMap = (int *)gmallocn(nCIDs, sizeof(int));
+  for (i = 0; i < nCIDs; ++i) {
+    cidMap[i] = -1;
+  }
+  for (i = 0; i < nGlyphs; ++i) {
+    cidMap[charset[i]] = i;
+  }
+
+  // write the descendant Type 1 fonts
+  for (i = 0; i < nCIDs; i += 256) {
+
+    //~ this assumes that all CIDs in this block have the same FD --
+    //~ to handle multiple FDs correctly, need to somehow divide the
+    //~ font up by FD; as a kludge we ignore CID 0, which is .notdef
+    fd = 0;
+    for (j = i==0 ? 1 : 0; j < 256 && i+j < nCIDs; ++j) {
+      if (cidMap[i+j] >= 0) {
+	fd = fdSelect[cidMap[i+j]];
+	break;
+      }
+    }
+
+    // font dictionary (unencrypted section)
+    (*outputFunc)(outputStream, "16 dict begin\n", 14);
+    (*outputFunc)(outputStream, "/FontName /", 11);
+    (*outputFunc)(outputStream, psName, strlen(psName));
+    buf = GString::format("_{0:02x} def\n", i >> 8);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+    (*outputFunc)(outputStream, "/FontType 1 def\n", 16);
+    if (privateDicts[fd].hasFontMatrix) {
+      buf = GString::format("/FontMatrix [{0:.8g} {1:.8g} {2:.8g} {3:.8g} {4:.8g} {5:.8g}] def\n",
+			    privateDicts[fd].fontMatrix[0],
+			    privateDicts[fd].fontMatrix[1],
+			    privateDicts[fd].fontMatrix[2],
+			    privateDicts[fd].fontMatrix[3],
+			    privateDicts[fd].fontMatrix[4],
+			    privateDicts[fd].fontMatrix[5]);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    } else if (topDict.hasFontMatrix) {
+      (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
+    } else {
+      (*outputFunc)(outputStream,
+		    "/FontMatrix [0.001 0 0 0.001 0 0] def\n", 38);
+    }
+    buf = GString::format("/FontBBox [{0:.4g} {1:.4g} {2:.4g} {3:.4g}] def\n",
+			  topDict.fontBBox[0], topDict.fontBBox[1],
+			  topDict.fontBBox[2], topDict.fontBBox[3]);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+    buf = GString::format("/PaintType {0:d} def\n", topDict.paintType);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+    if (topDict.paintType != 0) {
+      buf = GString::format("/StrokeWidth {0:.4g} def\n", topDict.strokeWidth);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    (*outputFunc)(outputStream, "/Encoding 256 array\n", 20);
+    for (j = 0; j < 256 && i+j < nCIDs; ++j) {
+      buf = GString::format("dup {0:d} /c{1:02x} put\n", j, j);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    if (j < 256) {
+      buf = GString::format("{0:d} 1 255 {{ 1 index exch /.notdef put }} for\n",
+			    j);
+      (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+      delete buf;
+    }
+    (*outputFunc)(outputStream, "readonly def\n", 13);
+    (*outputFunc)(outputStream, "currentdict end\n", 16);
+
+    // start the binary section
+    (*outputFunc)(outputStream, "currentfile eexec\n", 18);
+    eb.outputFunc = outputFunc;
+    eb.outputStream = outputStream;
+    eb.ascii = gTrue;
+    eb.r1 = 55665;
+    eb.line = 0;
+
+    // start the private dictionary
+    eexecWrite(&eb, "\x83\xca\x73\xd5");
+    eexecWrite(&eb, "dup /Private 32 dict dup begin\n");
+    eexecWrite(&eb, "/RD {string currentfile exch readstring pop}"
+	       " executeonly def\n");
+    eexecWrite(&eb, "/ND {noaccess def} executeonly def\n");
+    eexecWrite(&eb, "/NP {noaccess put} executeonly def\n");
+    eexecWrite(&eb, "/MinFeature {16 16} def\n");
+    eexecWrite(&eb, "/password 5839 def\n");
+    if (privateDicts[fd].nBlueValues) {
+      eexecWrite(&eb, "/BlueValues [");
+      for (k = 0; k < privateDicts[fd].nBlueValues; ++k) {
+	buf = GString::format("{0:s}{1:d}",
+			      k > 0 ? " " : "",
+			      privateDicts[fd].blueValues[k]);
+	eexecWrite(&eb, buf->getCString());
+	delete buf;
+      }
+      eexecWrite(&eb, "] def\n");
+    }
+    if (privateDicts[fd].nOtherBlues) {
+      eexecWrite(&eb, "/OtherBlues [");
+      for (k = 0; k < privateDicts[fd].nOtherBlues; ++k) {
+	buf = GString::format("{0:s}{1:d}",
+			      k > 0 ? " " : "",
+			      privateDicts[fd].otherBlues[k]);
+	eexecWrite(&eb, buf->getCString());
+	delete buf;
+      }
+      eexecWrite(&eb, "] def\n");
+    }
+    if (privateDicts[fd].nFamilyBlues) {
+      eexecWrite(&eb, "/FamilyBlues [");
+      for (k = 0; k < privateDicts[fd].nFamilyBlues; ++k) {
+	buf = GString::format("{0:s}{1:d}", k > 0 ? " " : "",
+			      privateDicts[fd].familyBlues[k]);
+	eexecWrite(&eb, buf->getCString());
+	delete buf;
+      }
+      eexecWrite(&eb, "] def\n");
+    }
+    if (privateDicts[fd].nFamilyOtherBlues) {
+      eexecWrite(&eb, "/FamilyOtherBlues [");
+      for (k = 0; k < privateDicts[fd].nFamilyOtherBlues; ++k) {
+	buf = GString::format("{0:s}{1:d}", k > 0 ? " " : "",
+			      privateDicts[fd].familyOtherBlues[k]);
+	eexecWrite(&eb, buf->getCString());
+	delete buf;
+      }
+      eexecWrite(&eb, "] def\n");
+    }
+    if (privateDicts[fd].blueScale != 0.039625) {
+      buf = GString::format("/BlueScale {0:.4g} def\n",
+			    privateDicts[fd].blueScale);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    if (privateDicts[fd].blueShift != 7) {
+      buf = GString::format("/BlueShift {0:d} def\n",
+			    privateDicts[fd].blueShift);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    if (privateDicts[fd].blueFuzz != 1) {
+      buf = GString::format("/BlueFuzz {0:d} def\n",
+			    privateDicts[fd].blueFuzz);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    if (privateDicts[fd].hasStdHW) {
+      buf = GString::format("/StdHW [{0:.4g}] def\n", privateDicts[fd].stdHW);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    if (privateDicts[fd].hasStdVW) {
+      buf = GString::format("/StdVW [{0:.4g}] def\n", privateDicts[fd].stdVW);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    if (privateDicts[fd].nStemSnapH) {
+      eexecWrite(&eb, "/StemSnapH [");
+      for (k = 0; k < privateDicts[fd].nStemSnapH; ++k) {
+	buf = GString::format("{0:s}{1:.4g}",
+			      k > 0 ? " " : "", privateDicts[fd].stemSnapH[k]);
+	eexecWrite(&eb, buf->getCString());
+	delete buf;
+      }
+      eexecWrite(&eb, "] def\n");
+    }
+    if (privateDicts[fd].nStemSnapV) {
+      eexecWrite(&eb, "/StemSnapV [");
+      for (k = 0; k < privateDicts[fd].nStemSnapV; ++k) {
+	buf = GString::format("{0:s}{1:.4g}",
+			      k > 0 ? " " : "", privateDicts[fd].stemSnapV[k]);
+	eexecWrite(&eb, buf->getCString());
+	delete buf;
+      }
+      eexecWrite(&eb, "] def\n");
+    }
+    if (privateDicts[fd].hasForceBold) {
+      buf = GString::format("/ForceBold {0:s} def\n",
+			    privateDicts[fd].forceBold ? "true" : "false");
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    if (privateDicts[fd].forceBoldThreshold != 0) {
+      buf = GString::format("/ForceBoldThreshold {0:.4g} def\n",
+			    privateDicts[fd].forceBoldThreshold);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    if (privateDicts[fd].languageGroup != 0) {
+      buf = GString::format("/LanguageGroup {0:d} def\n",
+			    privateDicts[fd].languageGroup);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+    if (privateDicts[fd].expansionFactor != 0.06) {
+      buf = GString::format("/ExpansionFactor {0:.4g} def\n",
+			    privateDicts[fd].expansionFactor);
+      eexecWrite(&eb, buf->getCString());
+      delete buf;
+    }
+
+    // set up the subroutines
+    ok = gTrue;
+    getIndex(privateDicts[fd].subrsOffset, &subrIdx, &ok);
+    if (!ok) {
+      subrIdx.pos = -1;
+    }
+
+    // start the CharStrings
+    eexecWrite(&eb, "2 index /CharStrings 256 dict dup begin\n");
+
+    // write the .notdef CharString
+    ok = gTrue;
+    getIndexVal(&charStringsIdx, 0, &val, &ok);
+    if (ok) {
+      eexecCvtGlyph(&eb, ".notdef", val.pos, val.len,
+		    &subrIdx, &privateDicts[fd]);
+    }
+
+    // write the CharStrings
+    for (j = 0; j < 256 && i+j < nCIDs; ++j) {
+      if (cidMap[i+j] >= 0) {
+	ok = gTrue;
+	getIndexVal(&charStringsIdx, cidMap[i+j], &val, &ok);
+	if (ok) {
+	  buf = GString::format("c{0:02x}", j);
+	  eexecCvtGlyph(&eb, buf->getCString(), val.pos, val.len,
+			&subrIdx, &privateDicts[fd]);
+	  delete buf;
+	}
+      }
+    }
+    eexecWrite(&eb, "end\n");
+    eexecWrite(&eb, "end\n");
+    eexecWrite(&eb, "readonly put\n");
+    eexecWrite(&eb, "noaccess put\n");
+    eexecWrite(&eb, "dup /FontName get exch definefont pop\n");
+    eexecWrite(&eb, "mark currentfile closefile\n");
+
+    // trailer
+    if (eb.line > 0) {
+      (*outputFunc)(outputStream, "\n", 1);
+    }
+    for (j = 0; j < 8; ++j) {
+      (*outputFunc)(outputStream, "0000000000000000000000000000000000000000000000000000000000000000\n", 65);
+    }
+    (*outputFunc)(outputStream, "cleartomark\n", 12);
+  }
+
+  // write the Type 0 parent font
+  (*outputFunc)(outputStream, "16 dict begin\n", 14);
+  (*outputFunc)(outputStream, "/FontName /", 11);
+  (*outputFunc)(outputStream, psName, strlen(psName));
+  (*outputFunc)(outputStream, " def\n", 5);
+  (*outputFunc)(outputStream, "/FontType 0 def\n", 16);
+  if (topDict.hasFontMatrix) {
+    buf = GString::format("/FontMatrix [{0:.8g} {1:.8g} {2:.8g} {3:.8g} {4:.8g} {5:.8g}] def\n",
+			  topDict.fontMatrix[0], topDict.fontMatrix[1],
+			  topDict.fontMatrix[2], topDict.fontMatrix[3],
+			  topDict.fontMatrix[4], topDict.fontMatrix[5]);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+  } else {
+    (*outputFunc)(outputStream, "/FontMatrix [1 0 0 1 0 0] def\n", 30);
+  }
+  (*outputFunc)(outputStream, "/FMapType 2 def\n", 16);
+  (*outputFunc)(outputStream, "/Encoding [\n", 12);
+  for (i = 0; i < nCIDs; i += 256) {
+    buf = GString::format("{0:d}\n", i >> 8);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+  }
+  (*outputFunc)(outputStream, "] def\n", 6);
+  (*outputFunc)(outputStream, "/FDepVector [\n", 14);
+  for (i = 0; i < nCIDs; i += 256) {
+    (*outputFunc)(outputStream, "/", 1);
+    (*outputFunc)(outputStream, psName, strlen(psName));
+    buf = GString::format("_{0:02x} findfont\n", i >> 8);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+  }
+  (*outputFunc)(outputStream, "] def\n", 6);
+  (*outputFunc)(outputStream, "FontName currentdict end definefont pop\n", 40);
+
+  gfree(cidMap);
+}
+
+void FoFiType1C::eexecCvtGlyph(Type1CEexecBuf *eb, char *glyphName,
+			       int offset, int nBytes,
+			       Type1CIndex *subrIdx,
+			       Type1CPrivateDict *pDict) {
+  GString *buf;
+  GString *charBuf;
+
+  // generate the charstring
+  charBuf = new GString();
+  cvtGlyph(offset, nBytes, charBuf, subrIdx, pDict, gTrue);
+
+  buf = GString::format("/{0:s} {1:d} RD ", glyphName, charBuf->getLength());
+  eexecWrite(eb, buf->getCString());
+  delete buf;
+  eexecWriteCharstring(eb, (Guchar *)charBuf->getCString(),
+		       charBuf->getLength());
+  eexecWrite(eb, " ND\n");
+
+  delete charBuf;
+}
+
+void FoFiType1C::cvtGlyph(int offset, int nBytes, GString *charBuf,
+			  Type1CIndex *subrIdx, Type1CPrivateDict *pDict,
+			  GBool top) {
+  Type1CIndexVal val;
+  GBool ok, dFP;
+  double d, dx, dy;
+  Gushort r2;
+  Guchar byte;
+  int pos, subrBias, start, i, k;
+
+  start = charBuf->getLength();
+  if (top) {
+    charBuf->append((char)73);
+    charBuf->append((char)58);
+    charBuf->append((char)147);
+    charBuf->append((char)134);
+    nOps = 0;
+    nHints = 0;
+    firstOp = gTrue;
+    openPath = gFalse;
+  }
+
+  pos = offset;
+  while (pos < offset + nBytes) {
+    ok = gTrue;
+    pos = getOp(pos, gTrue, &ok);
+    if (!ok) {
+      break;
+    }
+    if (!ops[nOps - 1].isNum) {
+      --nOps; // drop the operator
+      switch (ops[nOps].op) {
+      case 0x0001:		// hstem
+	if (firstOp) {
+	  cvtGlyphWidth(nOps & 1, charBuf, pDict);
+	  firstOp = gFalse;
+	}
+	if (nOps & 1) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 hstem", nOps);
+	}
+	d = 0;
+	dFP = gFalse;
+	for (k = 0; k < nOps; k += 2) {
+	  // convert Type 2 edge hints (-20 or -21) to Type 1 ghost hints
+	  if (ops[k+1].num < 0) {
+	    d += ops[k].num + ops[k+1].num;
+	    dFP |= ops[k].isFP | ops[k+1].isFP;
+	    cvtNum(d, dFP, charBuf);
+	    cvtNum(-ops[k+1].num, ops[k+1].isFP, charBuf);
+	  } else {
+	    d += ops[k].num;
+	    dFP |= ops[k].isFP;
+	    cvtNum(d, dFP, charBuf);
+	    cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	    d += ops[k+1].num;
+	    dFP |= ops[k+1].isFP;
+	  }
+	  charBuf->append((char)1);
+	}
+	nHints += nOps / 2;
+	nOps = 0;
+	break;
+      case 0x0003:		// vstem
+	if (firstOp) {
+	  cvtGlyphWidth(nOps & 1, charBuf, pDict);
+	  firstOp = gFalse;
+	}
+	if (nOps & 1) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 vstem", nOps);
+	}
+	d = 0;
+	dFP = gFalse;
+	for (k = 0; k < nOps; k += 2) {
+	  // convert Type 2 edge hints (-20 or -21) to Type 1 ghost hints
+	  if (ops[k+1].num < 0) {
+	    d += ops[k].num + ops[k+1].num;
+	    dFP |= ops[k].isFP | ops[k+1].isFP;
+	    cvtNum(d, dFP, charBuf);
+	    cvtNum(-ops[k+1].num, ops[k+1].isFP, charBuf);
+	  } else {
+	    d += ops[k].num;
+	    dFP |= ops[k].isFP;
+	    cvtNum(d, dFP, charBuf);
+	    cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	    d += ops[k+1].num;
+	    dFP |= ops[k+1].isFP;
+	  }
+	  charBuf->append((char)3);
+	}
+	nHints += nOps / 2;
+	nOps = 0;
+	break;
+      case 0x0004:		// vmoveto
+	if (firstOp) {
+	  cvtGlyphWidth(nOps == 2, charBuf, pDict);
+	  firstOp = gFalse;
+	}
+	if (openPath) {
+	  charBuf->append((char)9);
+	  openPath = gFalse;
+	}
+	if (nOps != 1) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 vmoveto", nOps);
+	}
+	cvtNum(ops[0].num, ops[0].isFP, charBuf);
+	charBuf->append((char)4);
+	nOps = 0;
+	break;
+      case 0x0005:		// rlineto
+	if (nOps < 2 || nOps % 2 != 0) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 rlineto", nOps);
+	}
+	for (k = 0; k < nOps; k += 2) {
+	  cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	  cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	  charBuf->append((char)5);
+	}
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x0006:		// hlineto
+	if (nOps < 1) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 hlineto", nOps);
+	}
+	for (k = 0; k < nOps; ++k) {
+	  cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	  charBuf->append((char)((k & 1) ? 7 : 6));
+	}
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x0007:		// vlineto
+	if (nOps < 1) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 vlineto", nOps);
+	}
+	for (k = 0; k < nOps; ++k) {
+	  cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	  charBuf->append((char)((k & 1) ? 6 : 7));
+	}
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x0008:		// rrcurveto
+	if (nOps < 6 || nOps % 6 != 0) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 rrcurveto", nOps);
+	}
+	for (k = 0; k < nOps; k += 6) {
+	  cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	  cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	  cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	  cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	  cvtNum(ops[k+4].num, ops[k+4].isFP, charBuf);
+	  cvtNum(ops[k+5].num, ops[k+5].isFP, charBuf);
+	  charBuf->append((char)8);
+	}
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x000a:		// callsubr
+	if (nOps >= 1) {
+	  subrBias = (subrIdx->len < 1240)
+	               ? 107 : (subrIdx->len < 33900) ? 1131 : 32768;
+	  k = subrBias + (int)ops[nOps - 1].num;
+	  --nOps;
+	  ok = gTrue;
+	  getIndexVal(subrIdx, k, &val, &ok);
+	  if (ok) {
+	    cvtGlyph(val.pos, val.len, charBuf, subrIdx, pDict, gFalse);
+	  }
+	} else {
+	  //~ error(-1, "Too few args to Type 2 callsubr");
+	}
+	// don't clear the stack
+	break;
+      case 0x000b:		// return
+	// don't clear the stack
+	break;
+      case 0x000e:		// endchar / seac
+	if (firstOp) {
+	  cvtGlyphWidth(nOps == 1 || nOps == 5, charBuf, pDict);
+	  firstOp = gFalse;
+	}
+	if (openPath) {
+	  charBuf->append((char)9);
+	  openPath = gFalse;
+	}
+	if (nOps == 4) {
+	  cvtNum(0, gFalse, charBuf);
+	  cvtNum(ops[0].num, ops[0].isFP, charBuf);
+	  cvtNum(ops[1].num, ops[1].isFP, charBuf);
+	  cvtNum(ops[2].num, ops[2].isFP, charBuf);
+	  cvtNum(ops[3].num, ops[3].isFP, charBuf);
+	  charBuf->append((char)12)->append((char)6);
+	} else if (nOps == 0) {
+	  charBuf->append((char)14);
+	} else {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 endchar", nOps);
+	}
+	nOps = 0;
+	break;
+      case 0x000f:		// (obsolete)
+	// this op is ignored, but we need the glyph width
+	if (firstOp) {
+	  cvtGlyphWidth(nOps > 0, charBuf, pDict);
+	  firstOp = gFalse;
+	}
+	nOps = 0;
+	break;
+      case 0x0010:		// blend
+	//~ error(-1, "Unimplemented Type 2 charstring op: %d", file[i]);
+	nOps = 0;
+	break;
+      case 0x0012:		// hstemhm
+	// ignored
+	if (firstOp) {
+	  cvtGlyphWidth(nOps & 1, charBuf, pDict);
+	  firstOp = gFalse;
+	}
+	if (nOps & 1) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 hstemhm", nOps);
+	}
+	nHints += nOps / 2;
+	nOps = 0;
+	break;
+      case 0x0013:		// hintmask
+	// ignored
+	if (firstOp) {
+	  cvtGlyphWidth(nOps & 1, charBuf, pDict);
+	  firstOp = gFalse;
+	}
+	if (nOps > 0) {
+	  if (nOps & 1) {
+	    //~ error(-1, "Wrong number of args (%d) to Type 2 hintmask/vstemhm",
+	    //~       nOps);
+	  }
+	  nHints += nOps / 2;
+	}
+	pos += (nHints + 7) >> 3;
+	nOps = 0;
+	break;
+      case 0x0014:		// cntrmask
+	// ignored
+	if (firstOp) {
+	  cvtGlyphWidth(nOps & 1, charBuf, pDict);
+	  firstOp = gFalse;
+	}
+	if (nOps > 0) {
+	  if (nOps & 1) {
+	    //~ error(-1, "Wrong number of args (%d) to Type 2 cntrmask/vstemhm",
+	    //~       nOps);
+	  }
+	  nHints += nOps / 2;
+	}
+	pos += (nHints + 7) >> 3;
+	nOps = 0;
+	break;
+      case 0x0015:		// rmoveto
+	if (firstOp) {
+	  cvtGlyphWidth(nOps == 3, charBuf, pDict);
+	  firstOp = gFalse;
+	}
+	if (openPath) {
+	  charBuf->append((char)9);
+	  openPath = gFalse;
+	}
+	if (nOps != 2) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 rmoveto", nOps);
+	}
+	cvtNum(ops[0].num, ops[0].isFP, charBuf);
+	cvtNum(ops[1].num, ops[1].isFP, charBuf);
+	charBuf->append((char)21);
+	nOps = 0;
+	break;
+      case 0x0016:		// hmoveto
+	if (firstOp) {
+	  cvtGlyphWidth(nOps == 2, charBuf, pDict);
+	  firstOp = gFalse;
+	}
+	if (openPath) {
+	  charBuf->append((char)9);
+	  openPath = gFalse;
+	}
+	if (nOps != 1) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 hmoveto", nOps);
+	}
+	cvtNum(ops[0].num, ops[0].isFP, charBuf);
+	charBuf->append((char)22);
+	nOps = 0;
+	break;
+      case 0x0017:		// vstemhm
+	// ignored
+	if (firstOp) {
+	  cvtGlyphWidth(nOps & 1, charBuf, pDict);
+	  firstOp = gFalse;
+	}
+	if (nOps & 1) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 vstemhm", nOps);
+	}
+	nHints += nOps / 2;
+	nOps = 0;
+	break;
+      case 0x0018:		// rcurveline
+	if (nOps < 8 || (nOps - 2) % 6 != 0) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 rcurveline", nOps);
+	}
+	for (k = 0; k < nOps - 2; k += 6) {
+	  cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	  cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	  cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	  cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	  cvtNum(ops[k+4].num, ops[k+4].isFP, charBuf);
+	  cvtNum(ops[k+5].num, ops[k+5].isFP, charBuf);
+	  charBuf->append((char)8);
+	}
+	cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	cvtNum(ops[k+1].num, ops[k].isFP, charBuf);
+	charBuf->append((char)5);
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x0019:		// rlinecurve
+	if (nOps < 8 || (nOps - 6) % 2 != 0) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 rlinecurve", nOps);
+	}
+	for (k = 0; k < nOps - 6; k += 2) {
+	  cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	  cvtNum(ops[k+1].num, ops[k].isFP, charBuf);
+	  charBuf->append((char)5);
+	}
+	cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	cvtNum(ops[k+4].num, ops[k+4].isFP, charBuf);
+	cvtNum(ops[k+5].num, ops[k+5].isFP, charBuf);
+	charBuf->append((char)8);
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x001a:		// vvcurveto
+	if (nOps < 4 || !(nOps % 4 == 0 || (nOps-1) % 4 == 0)) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 vvcurveto", nOps);
+	}
+	if (nOps % 2 == 1) {
+	  cvtNum(ops[0].num, ops[0].isFP, charBuf);
+	  cvtNum(ops[1].num, ops[1].isFP, charBuf);
+	  cvtNum(ops[2].num, ops[2].isFP, charBuf);
+	  cvtNum(ops[3].num, ops[3].isFP, charBuf);
+	  cvtNum(0, gFalse, charBuf);
+	  cvtNum(ops[4].num, ops[4].isFP, charBuf);
+	  charBuf->append((char)8);
+	  k = 5;
+	} else {
+	  k = 0;
+	}
+	for (; k < nOps; k += 4) {
+	  cvtNum(0, gFalse, charBuf);
+	  cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	  cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	  cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	  cvtNum(0, gFalse, charBuf);
+	  cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	  charBuf->append((char)8);
+	}
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x001b:		// hhcurveto
+	if (nOps < 4 || !(nOps % 4 == 0 || (nOps-1) % 4 == 0)) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 hhcurveto", nOps);
+	}
+	if (nOps % 2 == 1) {
+	  cvtNum(ops[1].num, ops[1].isFP, charBuf);
+	  cvtNum(ops[0].num, ops[0].isFP, charBuf);
+	  cvtNum(ops[2].num, ops[2].isFP, charBuf);
+	  cvtNum(ops[3].num, ops[3].isFP, charBuf);
+	  cvtNum(ops[4].num, ops[4].isFP, charBuf);
+	  cvtNum(0, gFalse, charBuf);
+	  charBuf->append((char)8);
+	  k = 5;
+	} else {
+	  k = 0;
+	}
+	for (; k < nOps; k += 4) {
+	  cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	  cvtNum(0, gFalse, charBuf);
+	  cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	  cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	  cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	  cvtNum(0, gFalse, charBuf);
+	  charBuf->append((char)8);
+	}
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x001d:		// callgsubr
+	if (nOps >= 1) {
+	  k = gsubrBias + (int)ops[nOps - 1].num;
+	  --nOps;
+	  ok = gTrue;
+	  getIndexVal(&gsubrIdx, k, &val, &ok);
+	  if (ok) {
+	    cvtGlyph(val.pos, val.len, charBuf, subrIdx, pDict, gFalse);
+	  }
+	} else {
+	  //~ error(-1, "Too few args to Type 2 callgsubr");
+	}
+	// don't clear the stack
+	break;
+      case 0x001e:		// vhcurveto
+	if (nOps < 4 || !(nOps % 4 == 0 || (nOps-1) % 4 == 0)) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 vhcurveto", nOps);
+	}
+	for (k = 0; k < nOps && k != nOps-5; k += 4) {
+	  if (k % 8 == 0) {
+	    cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	    cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	    cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	    cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	    charBuf->append((char)30);
+	  } else {
+	    cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	    cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	    cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	    cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	    charBuf->append((char)31);
+	  }
+	}
+	if (k == nOps-5) {
+	  if (k % 8 == 0) {
+	    cvtNum(0, gFalse, charBuf);
+	    cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	    cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	    cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	    cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	    cvtNum(ops[k+4].num, ops[k+4].isFP, charBuf);
+	  } else {
+	    cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	    cvtNum(0, gFalse, charBuf);
+	    cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	    cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	    cvtNum(ops[k+4].num, ops[k+4].isFP, charBuf);
+	    cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	  }
+	  charBuf->append((char)8);
+	}
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x001f:		// hvcurveto
+	if (nOps < 4 || !(nOps % 4 == 0 || (nOps-1) % 4 == 0)) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 hvcurveto", nOps);
+	}
+	for (k = 0; k < nOps && k != nOps-5; k += 4) {
+	  if (k % 8 == 0) {
+	    cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	    cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	    cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	    cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	    charBuf->append((char)31);
+	  } else {
+	    cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	    cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	    cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	    cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	    charBuf->append((char)30);
+	  }
+	}
+	if (k == nOps-5) {
+	  if (k % 8 == 0) {
+	    cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	    cvtNum(0, gFalse, charBuf);
+	    cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	    cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	    cvtNum(ops[k+4].num, ops[k+4].isFP, charBuf);
+	    cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	  } else {
+	    cvtNum(0, gFalse, charBuf);
+	    cvtNum(ops[k].num, ops[k].isFP, charBuf);
+	    cvtNum(ops[k+1].num, ops[k+1].isFP, charBuf);
+	    cvtNum(ops[k+2].num, ops[k+2].isFP, charBuf);
+	    cvtNum(ops[k+3].num, ops[k+3].isFP, charBuf);
+	    cvtNum(ops[k+4].num, ops[k+4].isFP, charBuf);
+	  }
+	  charBuf->append((char)8);
+	}
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x0c00:		// dotsection (should be Type 1 only?)
+	// ignored
+	nOps = 0;
+	break;
+      case 0x0c03:		// and
+      case 0x0c04:		// or
+      case 0x0c05:		// not
+      case 0x0c08:		// store
+      case 0x0c09:		// abs
+      case 0x0c0a:		// add
+      case 0x0c0b:		// sub
+      case 0x0c0c:		// div
+      case 0x0c0d:		// load
+      case 0x0c0e:		// neg
+      case 0x0c0f:		// eq
+      case 0x0c12:		// drop
+      case 0x0c14:		// put
+      case 0x0c15:		// get
+      case 0x0c16:		// ifelse
+      case 0x0c17:		// random
+      case 0x0c18:		// mul
+      case 0x0c1a:		// sqrt
+      case 0x0c1b:		// dup
+      case 0x0c1c:		// exch
+      case 0x0c1d:		// index
+      case 0x0c1e:		// roll
+	//~ error(-1, "Unimplemented Type 2 charstring op: 12.%d", file[i+1]);
+	nOps = 0;
+	break;
+      case 0x0c22:		// hflex
+	if (nOps != 7) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 hflex", nOps);
+	}
+	cvtNum(ops[0].num, ops[0].isFP, charBuf);
+	cvtNum(0, gFalse, charBuf);
+	cvtNum(ops[1].num, ops[1].isFP, charBuf);
+	cvtNum(ops[2].num, ops[2].isFP, charBuf);
+	cvtNum(ops[3].num, ops[3].isFP, charBuf);
+	cvtNum(0, gFalse, charBuf);
+	charBuf->append((char)8);
+	cvtNum(ops[4].num, ops[4].isFP, charBuf);
+	cvtNum(0, gFalse, charBuf);
+	cvtNum(ops[5].num, ops[5].isFP, charBuf);
+	cvtNum(-ops[2].num, ops[2].isFP, charBuf);
+	cvtNum(ops[6].num, ops[6].isFP, charBuf);
+	cvtNum(0, gFalse, charBuf);
+	charBuf->append((char)8);
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x0c23:		// flex
+	if (nOps != 13) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 flex", nOps);
+	}
+	cvtNum(ops[0].num, ops[0].isFP, charBuf);
+	cvtNum(ops[1].num, ops[1].isFP, charBuf);
+	cvtNum(ops[2].num, ops[2].isFP, charBuf);
+	cvtNum(ops[3].num, ops[3].isFP, charBuf);
+	cvtNum(ops[4].num, ops[4].isFP, charBuf);
+	cvtNum(ops[5].num, ops[5].isFP, charBuf);
+	charBuf->append((char)8);
+	cvtNum(ops[6].num, ops[6].isFP, charBuf);
+	cvtNum(ops[7].num, ops[7].isFP, charBuf);
+	cvtNum(ops[8].num, ops[8].isFP, charBuf);
+	cvtNum(ops[9].num, ops[9].isFP, charBuf);
+	cvtNum(ops[10].num, ops[10].isFP, charBuf);
+	cvtNum(ops[11].num, ops[11].isFP, charBuf);
+	charBuf->append((char)8);
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x0c24:		// hflex1
+	if (nOps != 9) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 hflex1", nOps);
+	}
+	cvtNum(ops[0].num, ops[0].isFP, charBuf);
+	cvtNum(ops[1].num, ops[1].isFP, charBuf);
+	cvtNum(ops[2].num, ops[2].isFP, charBuf);
+	cvtNum(ops[3].num, ops[3].isFP, charBuf);
+	cvtNum(ops[4].num, ops[4].isFP, charBuf);
+	cvtNum(0, gFalse, charBuf);
+	charBuf->append((char)8);
+	cvtNum(ops[5].num, ops[5].isFP, charBuf);
+	cvtNum(0, gFalse, charBuf);
+	cvtNum(ops[6].num, ops[6].isFP, charBuf);
+	cvtNum(ops[7].num, ops[7].isFP, charBuf);
+	cvtNum(ops[8].num, ops[8].isFP, charBuf);
+	cvtNum(-(ops[1].num + ops[3].num + ops[7].num),
+	       ops[1].isFP | ops[3].isFP | ops[7].isFP, charBuf);
+	charBuf->append((char)8);
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      case 0x0c25:		// flex1
+	if (nOps != 11) {
+	  //~ error(-1, "Wrong number of args (%d) to Type 2 flex1", nOps);
+	}
+	cvtNum(ops[0].num, ops[0].isFP, charBuf);
+	cvtNum(ops[1].num, ops[1].isFP, charBuf);
+	cvtNum(ops[2].num, ops[2].isFP, charBuf);
+	cvtNum(ops[3].num, ops[3].isFP, charBuf);
+	cvtNum(ops[4].num, ops[4].isFP, charBuf);
+	cvtNum(ops[5].num, ops[5].isFP, charBuf);
+	charBuf->append((char)8);
+	cvtNum(ops[6].num, ops[6].isFP, charBuf);
+	cvtNum(ops[7].num, ops[7].isFP, charBuf);
+	cvtNum(ops[8].num, ops[8].isFP, charBuf);
+	cvtNum(ops[9].num, ops[9].isFP, charBuf);
+	dx = ops[0].num + ops[2].num + ops[4].num + ops[6].num + ops[8].num;
+	dy = ops[1].num + ops[3].num + ops[5].num + ops[7].num + ops[9].num;
+	if (fabs(dx) > fabs(dy)) {
+	  cvtNum(ops[10].num, ops[10].isFP, charBuf);
+	  cvtNum(-dy, ops[1].isFP | ops[3].isFP | ops[5].isFP |
+		      ops[7].isFP | ops[9].isFP, charBuf);
+	} else {
+	  cvtNum(-dx, ops[0].isFP | ops[2].isFP | ops[4].isFP |
+		      ops[6].isFP | ops[8].isFP, charBuf);
+	  cvtNum(ops[10].num, ops[10].isFP, charBuf);
+	}
+	charBuf->append((char)8);
+	nOps = 0;
+	openPath = gTrue;
+	break;
+      default:
+	//~ error(-1, "Illegal Type 2 charstring op: %04x",
+	//~       ops[nOps].op);
+	nOps = 0;
+	break;
+      }
+    }
+  }
+
+  // charstring encryption
+  if (top) {
+    r2 = 4330;
+    for (i = start; i < charBuf->getLength(); ++i) {
+      byte = charBuf->getChar(i) ^ (r2 >> 8);
+      charBuf->setChar(i, byte);
+      r2 = (byte + r2) * 52845 + 22719;
+    }
+  }
+}
+
+void FoFiType1C::cvtGlyphWidth(GBool useOp, GString *charBuf,
+			       Type1CPrivateDict *pDict) {
+  double w;
+  GBool wFP;
+  int i;
+
+  if (useOp) {
+    w = pDict->nominalWidthX + ops[0].num;
+    wFP = pDict->nominalWidthXFP | ops[0].isFP;
+    for (i = 1; i < nOps; ++i) {
+      ops[i-1] = ops[i];
+    }
+    --nOps;
+  } else {
+    w = pDict->defaultWidthX;
+    wFP = pDict->defaultWidthXFP;
+  }
+  cvtNum(0, gFalse, charBuf);
+  cvtNum(w, wFP, charBuf);
+  charBuf->append((char)13);
+}
+
+void FoFiType1C::cvtNum(double x, GBool isFP, GString *charBuf) {
+  Guchar buf[12];
+  int y, n;
+
+  n = 0;
+  if (isFP) {
+    if (x >= -32768 && x < 32768) {
+      y = (int)(x * 256.0);
+      buf[0] = 255;
+      buf[1] = (Guchar)(y >> 24);
+      buf[2] = (Guchar)(y >> 16);
+      buf[3] = (Guchar)(y >> 8);
+      buf[4] = (Guchar)y;
+      buf[5] = 255;
+      buf[6] = 0;
+      buf[7] = 0;
+      buf[8] = 1;
+      buf[9] = 0;
+      buf[10] = 12;
+      buf[11] = 12;
+      n = 12;
+    } else {
+      //~ error(-1, "Type 2 fixed point constant out of range");
+    }
+  } else {
+    y = (int)x;
+    if (y >= -107 && y <= 107) {
+      buf[0] = (Guchar)(y + 139);
+      n = 1;
+    } else if (y > 107 && y <= 1131) {
+      y -= 108;
+      buf[0] = (Guchar)((y >> 8) + 247);
+      buf[1] = (Guchar)(y & 0xff);
+      n = 2;
+    } else if (y < -107 && y >= -1131) {
+      y = -y - 108;
+      buf[0] = (Guchar)((y >> 8) + 251);
+      buf[1] = (Guchar)(y & 0xff);
+      n = 2;
+    } else {
+      buf[0] = 255;
+      buf[1] = (Guchar)(y >> 24);
+      buf[2] = (Guchar)(y >> 16);
+      buf[3] = (Guchar)(y >> 8);
+      buf[4] = (Guchar)y;
+      n = 5;
+    }
+  }
+  charBuf->append((char *)buf, n);
+}
+
+void FoFiType1C::eexecWrite(Type1CEexecBuf *eb, char *s) {
+  Guchar *p;
+  Guchar x;
+
+  for (p = (Guchar *)s; *p; ++p) {
+    x = *p ^ (eb->r1 >> 8);
+    eb->r1 = (x + eb->r1) * 52845 + 22719;
+    if (eb->ascii) {
+      (*eb->outputFunc)(eb->outputStream, &hexChars[x >> 4], 1);
+      (*eb->outputFunc)(eb->outputStream, &hexChars[x & 0x0f], 1);
+      eb->line += 2;
+      if (eb->line == 64) {
+	(*eb->outputFunc)(eb->outputStream, "\n", 1);
+	eb->line = 0;
+      }
+    } else {
+      (*eb->outputFunc)(eb->outputStream, (char *)&x, 1);
+    }
+  }
+}
+
+void FoFiType1C::eexecWriteCharstring(Type1CEexecBuf *eb,
+				      Guchar *s, int n) {
+  Guchar x;
+  int i;
+
+  // eexec encryption
+  for (i = 0; i < n; ++i) {
+    x = s[i] ^ (eb->r1 >> 8);
+    eb->r1 = (x + eb->r1) * 52845 + 22719;
+    if (eb->ascii) {
+      (*eb->outputFunc)(eb->outputStream, &hexChars[x >> 4], 1);
+      (*eb->outputFunc)(eb->outputStream, &hexChars[x & 0x0f], 1);
+      eb->line += 2;
+      if (eb->line == 64) {
+	(*eb->outputFunc)(eb->outputStream, "\n", 1);
+	eb->line = 0;
+      }
+    } else {
+      (*eb->outputFunc)(eb->outputStream, (char *)&x, 1);
+    }
+  }
+}
+
+GBool FoFiType1C::parse() {
+  Type1CIndex fdIdx;
+  Type1CIndexVal val;
+  int i;
+
+  parsedOk = gTrue;
+
+  // some tools embed Type 1C fonts with an extra whitespace char at
+  // the beginning
+  if (len > 0 && file[0] != '\x01') {
+    ++file;
+    --len;
+  }
+
+  // find the indexes
+  getIndex(getU8(2, &parsedOk), &nameIdx, &parsedOk);
+  getIndex(nameIdx.endPos, &topDictIdx, &parsedOk);
+  getIndex(topDictIdx.endPos, &stringIdx, &parsedOk);
+  getIndex(stringIdx.endPos, &gsubrIdx, &parsedOk);
+  if (!parsedOk) {
+    return gFalse;
+  }
+  gsubrBias = (gsubrIdx.len < 1240) ? 107
+                                    : (gsubrIdx.len < 33900) ? 1131 : 32768;
+
+  // read the first font name
+  getIndexVal(&nameIdx, 0, &val, &parsedOk);
+  if (!parsedOk) {
+    return gFalse;
+  }
+  name = new GString((char *)&file[val.pos], val.len);
+
+  // read the top dict for the first font
+  readTopDict();
+
+  // for CID fonts: read the FDArray dicts and private dicts
+  if (topDict.firstOp == 0x0c1e) {
+    if (topDict.fdArrayOffset == 0) {
+      nFDs = 1;
+      privateDicts = (Type1CPrivateDict *)gmalloc(sizeof(Type1CPrivateDict));
+      readPrivateDict(0, 0, &privateDicts[0]);
+    } else {
+      getIndex(topDict.fdArrayOffset, &fdIdx, &parsedOk);
+      if (!parsedOk) {
+	return gFalse;
+      }
+      nFDs = fdIdx.len;
+      privateDicts = (Type1CPrivateDict *)
+	                 gmallocn(nFDs, sizeof(Type1CPrivateDict));
+      for (i = 0; i < nFDs; ++i) {
+	getIndexVal(&fdIdx, i, &val, &parsedOk);
+	if (!parsedOk) {
+	  return gFalse;
+	}
+	readFD(val.pos, val.len, &privateDicts[i]);
+      }
+    }
+
+  // for 8-bit fonts: read the private dict
+  } else {
+    privateDicts = (Type1CPrivateDict *)gmalloc(sizeof(Type1CPrivateDict));
+    readPrivateDict(topDict.privateOffset, topDict.privateSize,
+		    &privateDicts[0]);
+  }
+
+  // check for parse errors in the private dict(s)
+  if (!parsedOk) {
+    return gFalse;
+  }
+
+  // get the charstrings index
+  if (topDict.charStringsOffset <= 0) {
+    parsedOk = gFalse;
+    return gFalse;
+  }
+  getIndex(topDict.charStringsOffset, &charStringsIdx, &parsedOk);
+  if (!parsedOk) {
+    return gFalse;
+  }
+  nGlyphs = charStringsIdx.len;
+
+  // for CID fonts: read the FDSelect table
+  if (topDict.firstOp == 0x0c1e) {
+    readFDSelect();
+    if (!parsedOk) {
+      return gFalse;
+    }
+  }
+
+  // read the charset
+  if (!readCharset()) {
+    parsedOk = gFalse;
+    return gFalse;
+  }
+
+  // for 8-bit fonts: build the encoding
+  if (topDict.firstOp != 0x0c14 && topDict.firstOp != 0x0c1e) {
+    buildEncoding();
+    if (!parsedOk) {
+      return gFalse;
+    }
+  }
+
+  return parsedOk;
+}
+
+void FoFiType1C::readTopDict() {
+  Type1CIndexVal topDictPtr;
+  int pos;
+
+  topDict.firstOp = -1;
+  topDict.versionSID = 0;
+  topDict.noticeSID = 0;
+  topDict.copyrightSID = 0;
+  topDict.fullNameSID = 0;
+  topDict.familyNameSID = 0;
+  topDict.weightSID = 0;
+  topDict.isFixedPitch = 0;
+  topDict.italicAngle = 0;
+  topDict.underlinePosition = -100;
+  topDict.underlineThickness = 50;
+  topDict.paintType = 0;
+  topDict.charstringType = 2;
+  topDict.fontMatrix[0] = 0.001;
+  topDict.fontMatrix[1] = 0;
+  topDict.fontMatrix[2] = 0;
+  topDict.fontMatrix[3] = 0.001;
+  topDict.fontMatrix[4] = 0;
+  topDict.fontMatrix[5] = 0;
+  topDict.hasFontMatrix = gFalse;
+  topDict.uniqueID = 0;
+  topDict.fontBBox[0] = 0;
+  topDict.fontBBox[1] = 0;
+  topDict.fontBBox[2] = 0;
+  topDict.fontBBox[3] = 0;
+  topDict.strokeWidth = 0;
+  topDict.charsetOffset = 0;
+  topDict.encodingOffset = 0;
+  topDict.charStringsOffset = 0;
+  topDict.privateSize = 0;
+  topDict.privateOffset = 0;
+  topDict.registrySID = 0;
+  topDict.orderingSID = 0;
+  topDict.supplement = 0;
+  topDict.fdArrayOffset = 0;
+  topDict.fdSelectOffset = 0;
+
+  getIndexVal(&topDictIdx, 0, &topDictPtr, &parsedOk);
+  pos = topDictPtr.pos;
+  nOps = 0;
+  while (pos < topDictPtr.pos + topDictPtr.len) {
+    pos = getOp(pos, gFalse, &parsedOk);
+    if (!parsedOk) {
+      break;
+    }
+    if (!ops[nOps - 1].isNum) {
+      --nOps; // drop the operator
+      if (topDict.firstOp < 0) {
+	topDict.firstOp = ops[nOps].op;
+      }
+      switch (ops[nOps].op) {
+      case 0x0000: topDict.versionSID = (int)ops[0].num; break;
+      case 0x0001: topDict.noticeSID = (int)ops[0].num; break;
+      case 0x0c00: topDict.copyrightSID = (int)ops[0].num; break;
+      case 0x0002: topDict.fullNameSID = (int)ops[0].num; break;
+      case 0x0003: topDict.familyNameSID = (int)ops[0].num; break;
+      case 0x0004: topDict.weightSID = (int)ops[0].num; break;
+      case 0x0c01: topDict.isFixedPitch = (int)ops[0].num; break;
+      case 0x0c02: topDict.italicAngle = ops[0].num; break;
+      case 0x0c03: topDict.underlinePosition = ops[0].num; break;
+      case 0x0c04: topDict.underlineThickness = ops[0].num; break;
+      case 0x0c05: topDict.paintType = (int)ops[0].num; break;
+      case 0x0c06: topDict.charstringType = (int)ops[0].num; break;
+      case 0x0c07: topDict.fontMatrix[0] = ops[0].num;
+	           topDict.fontMatrix[1] = ops[1].num;
+	           topDict.fontMatrix[2] = ops[2].num;
+	           topDict.fontMatrix[3] = ops[3].num;
+	           topDict.fontMatrix[4] = ops[4].num;
+	           topDict.fontMatrix[5] = ops[5].num;
+		   topDict.hasFontMatrix = gTrue; break;
+      case 0x000d: topDict.uniqueID = (int)ops[0].num; break;
+      case 0x0005: topDict.fontBBox[0] = ops[0].num;
+	           topDict.fontBBox[1] = ops[1].num;
+	           topDict.fontBBox[2] = ops[2].num;
+	           topDict.fontBBox[3] = ops[3].num; break;
+      case 0x0c08: topDict.strokeWidth = ops[0].num; break;
+      case 0x000f: topDict.charsetOffset = (int)ops[0].num; break;
+      case 0x0010: topDict.encodingOffset = (int)ops[0].num; break;
+      case 0x0011: topDict.charStringsOffset = (int)ops[0].num; break;
+      case 0x0012: topDict.privateSize = (int)ops[0].num;
+	           topDict.privateOffset = (int)ops[1].num; break;
+      case 0x0c1e: topDict.registrySID = (int)ops[0].num;
+	           topDict.orderingSID = (int)ops[1].num;
+		   topDict.supplement = (int)ops[2].num; break;
+      case 0x0c24: topDict.fdArrayOffset = (int)ops[0].num; break;
+      case 0x0c25: topDict.fdSelectOffset = (int)ops[0].num; break;
+      }
+      nOps = 0;
+    }
+  }
+}
+
+// Read a CID font dict (FD) - this pulls out the private dict
+// pointer, and reads the private dict.  It also pulls the FontMatrix
+// (if any) out of the FD.
+void FoFiType1C::readFD(int offset, int length, Type1CPrivateDict *pDict) {
+  int pos, pSize, pOffset;
+  double fontMatrix[6];
+  GBool hasFontMatrix;
+
+  hasFontMatrix = gFalse;
+  pSize = pOffset = 0;
+  pos = offset;
+  nOps = 0;
+  while (pos < offset + length) {
+    pos = getOp(pos, gFalse, &parsedOk);
+    if (!parsedOk) {
+      return;
+    }
+    if (!ops[nOps - 1].isNum) {
+      if (ops[nOps - 1].op == 0x0012) {
+	if (nOps < 3) {
+	  parsedOk = gFalse;
+	  return;
+	}
+	pSize = (int)ops[0].num;
+	pOffset = (int)ops[1].num;
+	break;
+      } else if (ops[nOps - 1].op == 0x0c07) {
+	fontMatrix[0] = ops[0].num;
+	fontMatrix[1] = ops[1].num;
+	fontMatrix[2] = ops[2].num;
+	fontMatrix[3] = ops[3].num;
+	fontMatrix[4] = ops[4].num;
+	fontMatrix[5] = ops[5].num;
+	hasFontMatrix = gTrue;
+      }
+      nOps = 0;
+    }
+  }
+  readPrivateDict(pOffset, pSize, pDict);
+  if (hasFontMatrix) {
+    pDict->fontMatrix[0] = fontMatrix[0];
+    pDict->fontMatrix[1] = fontMatrix[1];
+    pDict->fontMatrix[2] = fontMatrix[2];
+    pDict->fontMatrix[3] = fontMatrix[3];
+    pDict->fontMatrix[4] = fontMatrix[4];
+    pDict->fontMatrix[5] = fontMatrix[5];
+    pDict->hasFontMatrix = gTrue;
+  }
+}
+
+void FoFiType1C::readPrivateDict(int offset, int length,
+				 Type1CPrivateDict *pDict) {
+  int pos;
+
+  pDict->hasFontMatrix = gFalse;
+  pDict->nBlueValues = 0;
+  pDict->nOtherBlues = 0;
+  pDict->nFamilyBlues = 0;
+  pDict->nFamilyOtherBlues = 0;
+  pDict->blueScale = 0.039625;
+  pDict->blueShift = 7;
+  pDict->blueFuzz = 1;
+  pDict->hasStdHW = gFalse;
+  pDict->hasStdVW = gFalse;
+  pDict->nStemSnapH = 0;
+  pDict->nStemSnapV = 0;
+  pDict->hasForceBold = gFalse;
+  pDict->forceBoldThreshold = 0;
+  pDict->languageGroup = 0;
+  pDict->expansionFactor = 0.06;
+  pDict->initialRandomSeed = 0;
+  pDict->subrsOffset = 0;
+  pDict->defaultWidthX = 0;
+  pDict->defaultWidthXFP = gFalse;
+  pDict->nominalWidthX = 0;
+  pDict->nominalWidthXFP = gFalse;
+
+  // no dictionary
+  if (offset == 0 || length == 0) {
+    return;
+  }
+
+  pos = offset;
+  nOps = 0;
+  while (pos < offset + length) {
+    pos = getOp(pos, gFalse, &parsedOk);
+    if (!parsedOk) {
+      break;
+    }
+    if (!ops[nOps - 1].isNum) {
+      --nOps; // drop the operator
+      switch (ops[nOps].op) {
+      case 0x0006:
+	pDict->nBlueValues = getDeltaIntArray(pDict->blueValues,
+					      type1CMaxBlueValues);
+	break;
+      case 0x0007:
+	pDict->nOtherBlues = getDeltaIntArray(pDict->otherBlues,
+					      type1CMaxOtherBlues);
+	break;
+      case 0x0008:
+	pDict->nFamilyBlues = getDeltaIntArray(pDict->familyBlues,
+					       type1CMaxBlueValues);
+	break;
+      case 0x0009:
+	pDict->nFamilyOtherBlues = getDeltaIntArray(pDict->familyOtherBlues,
+						    type1CMaxOtherBlues);
+	break;
+      case 0x0c09:
+	pDict->blueScale = ops[0].num;
+	break;
+      case 0x0c0a:
+	pDict->blueShift = (int)ops[0].num;
+	break;
+      case 0x0c0b:
+	pDict->blueFuzz = (int)ops[0].num;
+	break;
+      case 0x000a:
+	pDict->stdHW = ops[0].num;
+	pDict->hasStdHW = gTrue;
+	break;
+      case 0x000b:
+	pDict->stdVW = ops[0].num;
+	pDict->hasStdVW = gTrue;
+	break;
+      case 0x0c0c:
+	pDict->nStemSnapH = getDeltaFPArray(pDict->stemSnapH,
+					    type1CMaxStemSnap);
+	break;
+      case 0x0c0d:
+	pDict->nStemSnapV = getDeltaFPArray(pDict->stemSnapV,
+					    type1CMaxStemSnap);
+	break;
+      case 0x0c0e:
+	pDict->forceBold = ops[0].num != 0;
+	pDict->hasForceBold = gTrue;
+	break;
+      case 0x0c0f:
+	pDict->forceBoldThreshold = ops[0].num;
+	break;
+      case 0x0c11:
+	pDict->languageGroup = (int)ops[0].num;
+	break;
+      case 0x0c12:
+	pDict->expansionFactor = ops[0].num;
+	break;
+      case 0x0c13:
+	pDict->initialRandomSeed = (int)ops[0].num;
+	break;
+      case 0x0013:
+	pDict->subrsOffset = offset + (int)ops[0].num;
+	break;
+      case 0x0014:
+	pDict->defaultWidthX = ops[0].num;
+	pDict->defaultWidthXFP = ops[0].isFP;
+	break;
+      case 0x0015:
+	pDict->nominalWidthX = ops[0].num;
+	pDict->nominalWidthXFP = ops[0].isFP;
+	break;
+      }
+      nOps = 0;
+    }
+  }
+}
+
+void FoFiType1C::readFDSelect() {
+  int fdSelectFmt, pos, nRanges, gid0, gid1, fd, i, j;
+
+  fdSelect = (Guchar *)gmalloc(nGlyphs);
+  if (topDict.fdSelectOffset == 0) {
+    for (i = 0; i < nGlyphs; ++i) {
+      fdSelect[i] = 0;
+    }
+  } else {
+    pos = topDict.fdSelectOffset;
+    fdSelectFmt = getU8(pos++, &parsedOk);
+    if (!parsedOk) {
+      return;
+    }
+    if (fdSelectFmt == 0) {
+      if (!checkRegion(pos, nGlyphs)) {
+	parsedOk = gFalse;
+	return;
+      }
+      memcpy(fdSelect, file + pos, nGlyphs);
+    } else if (fdSelectFmt == 3) {
+      nRanges = getU16BE(pos, &parsedOk);
+      pos += 2;
+      gid0 = getU16BE(pos, &parsedOk);
+      pos += 2;
+      for (i = 1; i <= nRanges; ++i) {
+	fd = getU8(pos++, &parsedOk);
+	gid1 = getU16BE(pos, &parsedOk);
+	if (!parsedOk) {
+	  return;
+	}
+	pos += 2;
+	if (gid0 > gid1 || gid1 > nGlyphs) {
+	  //~ error(-1, "Bad FDSelect table in CID font");
+	  parsedOk = gFalse;
+	  return;
+	}
+	for (j = gid0; j < gid1; ++j) {
+	  fdSelect[j] = fd;
+	}
+	gid0 = gid1;
+      }
+    } else {
+      //~ error(-1, "Unknown FDSelect table format in CID font");
+      for (i = 0; i < nGlyphs; ++i) {
+	fdSelect[i] = 0;
+      }
+    }
+  }
+}
+
+void FoFiType1C::buildEncoding() {
+  char buf[256];
+  int nCodes, nRanges, encFormat;
+  int pos, c, sid, nLeft, nSups, i, j;
+
+  if (topDict.encodingOffset == 0) {
+    encoding = fofiType1StandardEncoding;
+
+  } else if (topDict.encodingOffset == 1) {
+    encoding = fofiType1ExpertEncoding;
+
+  } else {
+    encoding = (char **)gmallocn(256, sizeof(char *));
+    for (i = 0; i < 256; ++i) {
+      encoding[i] = NULL;
+    }
+    pos = topDict.encodingOffset;
+    encFormat = getU8(pos++, &parsedOk);
+    if (!parsedOk) {
+      return;
+    }
+    if ((encFormat & 0x7f) == 0) {
+      nCodes = 1 + getU8(pos++, &parsedOk);
+      if (!parsedOk) {
+	return;
+      }
+      if (nCodes > nGlyphs) {
+	nCodes = nGlyphs;
+      }
+      for (i = 1; i < nCodes; ++i) {
+	c = getU8(pos++, &parsedOk);
+	if (!parsedOk) {
+	  return;
+	}
+	if (encoding[c]) {
+	  gfree(encoding[c]);
+	}
+	encoding[c] = copyString(getString(charset[i], buf, &parsedOk));
+      }
+    } else if ((encFormat & 0x7f) == 1) {
+      nRanges = getU8(pos++, &parsedOk);
+      if (!parsedOk) {
+	return;
+      }
+      nCodes = 1;
+      for (i = 0; i < nRanges; ++i) {
+	c = getU8(pos++, &parsedOk);
+	nLeft = getU8(pos++, &parsedOk);
+	if (!parsedOk) {
+	  return;
+	}
+	for (j = 0; j <= nLeft && nCodes < nGlyphs; ++j) {
+	  if (c < 256) {
+	    if (encoding[c]) {
+	      gfree(encoding[c]);
+	    }
+	    encoding[c] = copyString(getString(charset[nCodes], buf,
+					       &parsedOk));
+	  }
+	  ++nCodes;
+	  ++c;
+	}
+      }
+    }
+    if (encFormat & 0x80) {
+      nSups = getU8(pos++, &parsedOk);
+      if (!parsedOk) {
+	return;
+      }
+      for (i = 0; i < nSups; ++i) {
+	c = getU8(pos++, &parsedOk);;
+	if (!parsedOk) {
+	  return;;
+	}
+	sid = getU16BE(pos, &parsedOk);
+	pos += 2;
+	if (!parsedOk) {
+	  return;
+	}
+	if (encoding[c]) {
+	  gfree(encoding[c]);
+	}
+	encoding[c] = copyString(getString(sid, buf, &parsedOk));
+      }
+    }
+  }
+}
+
+GBool FoFiType1C::readCharset() {
+  int charsetFormat, c, pos;
+  int nLeft, i, j;
+
+  if (topDict.charsetOffset == 0) {
+    charset = fofiType1CISOAdobeCharset;
+  } else if (topDict.charsetOffset == 1) {
+    charset = fofiType1CExpertCharset;
+  } else if (topDict.charsetOffset == 2) {
+    charset = fofiType1CExpertSubsetCharset;
+  } else {
+    charset = (Gushort *)gmallocn(nGlyphs, sizeof(Gushort));
+    for (i = 0; i < nGlyphs; ++i) {
+      charset[i] = 0;
+    }
+    pos = topDict.charsetOffset;
+    charsetFormat = getU8(pos++, &parsedOk);
+    if (charsetFormat == 0) {
+      for (i = 1; i < nGlyphs; ++i) {
+	charset[i] = (Gushort)getU16BE(pos, &parsedOk);
+	pos += 2;
+	if (!parsedOk) {
+	  break;
+	}
+      }
+    } else if (charsetFormat == 1) {
+      i = 1;
+      while (i < nGlyphs) {
+	c = getU16BE(pos, &parsedOk);
+	pos += 2;
+	nLeft = getU8(pos++, &parsedOk);
+	if (!parsedOk) {
+	  break;
+	}
+	for (j = 0; j <= nLeft && i < nGlyphs; ++j) {
+	  charset[i++] = (Gushort)c++;
+	}
+      }
+    } else if (charsetFormat == 2) {
+      i = 1;
+      while (i < nGlyphs) {
+	c = getU16BE(pos, &parsedOk);
+	pos += 2;
+	nLeft = getU16BE(pos, &parsedOk);
+	pos += 2;
+	if (!parsedOk) {
+	  break;
+	}
+	for (j = 0; j <= nLeft && i < nGlyphs; ++j) {
+	  charset[i++] = (Gushort)c++;
+	}
+      }
+    }
+    if (!parsedOk) {
+      gfree(charset);
+      charset = NULL;
+      return gFalse;
+    }
+  }
+  return gTrue;
+}
+
+int FoFiType1C::getOp(int pos, GBool charstring, GBool *ok) {
+  static char nybChars[16] = "0123456789.ee -";
+  Type1COp op;
+  char buf[65];
+  int b0, b1, nyb0, nyb1, x, i;
+
+  b0 = getU8(pos++, ok);
+  op.isNum = gTrue;
+  op.isFP = gFalse;
+
+  if (b0 == 28) {
+    x = getU8(pos++, ok);
+    x = (x << 8) | getU8(pos++, ok);
+    if (x & 0x8000) {
+      x |= ~0xffff;
+    }
+    op.num = x;
+
+  } else if (!charstring && b0 == 29) {
+    x = getU8(pos++, ok);
+    x = (x << 8) | getU8(pos++, ok);
+    x = (x << 8) | getU8(pos++, ok);
+    x = (x << 8) | getU8(pos++, ok);
+    if (x & 0x80000000) {
+      x |= ~0xffffffff;
+    }
+    op.num = x;
+
+  } else if (!charstring && b0 == 30) {
+    i = 0;
+    do {
+      b1 = getU8(pos++, ok);
+      nyb0 = b1 >> 4;
+      nyb1 = b1 & 0x0f;
+      if (nyb0 == 0xf) {
+	break;
+      }
+      buf[i++] = nybChars[nyb0];
+      if (i == 64) {
+	break;
+      }
+      if (nyb0 == 0xc) {
+	buf[i++] = '-';
+      }
+      if (i == 64) {
+	break;
+      }
+      if (nyb1 == 0xf) {
+	break;
+      }
+      buf[i++] = nybChars[nyb1];
+      if (i == 64) {
+	break;
+      }
+      if (nyb1 == 0xc) {
+	buf[i++] = '-';
+      }
+    } while (i < 64);
+    buf[i] = '\0';
+    op.num = atof(buf);
+    op.isFP = gTrue;
+
+  } else if (b0 >= 32 && b0 <= 246) {
+    op.num = b0 - 139;
+
+  } else if (b0 >= 247 && b0 <= 250) {
+    op.num = ((b0 - 247) << 8) + getU8(pos++, ok) + 108;
+
+  } else if (b0 >= 251 && b0 <= 254) {
+    op.num = -((b0 - 251) << 8) - getU8(pos++, ok) - 108;
+
+  } else if (charstring && b0 == 255) {
+    x = getU8(pos++, ok);
+    x = (x << 8) | getU8(pos++, ok);
+    x = (x << 8) | getU8(pos++, ok);
+    x = (x << 8) | getU8(pos++, ok);
+    if (x & 0x80000000) {
+      x |= ~0xffffffff;
+    }
+    op.num = (double)x / 65536.0;
+    op.isFP = gTrue;
+
+  } else if (b0 == 12) {
+    op.isNum = gFalse;
+    op.op = 0x0c00 + getU8(pos++, ok);
+
+  } else {
+    op.isNum = gFalse;
+    op.op = b0;
+  }
+
+  if (nOps < 49) {
+    ops[nOps++] = op;
+  }
+
+  return pos;
+}
+
+// Convert the delta-encoded ops array to an array of ints.
+int FoFiType1C::getDeltaIntArray(int *arr, int maxLen) {
+  int x;
+  int n, i;
+
+  if ((n = nOps) > maxLen) {
+    n = maxLen;
+  }
+  x = 0;
+  for (i = 0; i < n; ++i) {
+    x += (int)ops[i].num;
+    arr[i] = x;
+  }
+  return n;
+}
+
+// Convert the delta-encoded ops array to an array of doubles.
+int FoFiType1C::getDeltaFPArray(double *arr, int maxLen) {
+  double x;
+  int n, i;
+
+  if ((n = nOps) > maxLen) {
+    n = maxLen;
+  }
+  x = 0;
+  for (i = 0; i < n; ++i) {
+    x += ops[i].num;
+    arr[i] = x;
+  }
+  return n;
+}
+
+void FoFiType1C::getIndex(int pos, Type1CIndex *idx, GBool *ok) {
+  idx->pos = pos;
+  idx->len = getU16BE(pos, ok);
+  if (idx->len == 0) {
+    // empty indexes are legal and contain just the length field
+    idx->offSize = 0;
+    idx->startPos = idx->endPos = pos + 2;
+  } else {
+    idx->offSize = getU8(pos + 2, ok);
+    if (idx->offSize < 1 || idx->offSize > 4) {
+      *ok = gFalse;
+    }
+    idx->startPos = pos + 3 + (idx->len + 1) * idx->offSize - 1;
+    if (idx->startPos < 0 || idx->startPos >= len) {
+      *ok = gFalse;
+    }
+    idx->endPos = idx->startPos + getUVarBE(pos + 3 + idx->len * idx->offSize,
+					    idx->offSize, ok);
+    if (idx->endPos < idx->startPos || idx->endPos > len) {
+      *ok = gFalse;
+    }
+  }
+}
+
+void FoFiType1C::getIndexVal(Type1CIndex *idx, int i,
+			     Type1CIndexVal *val, GBool *ok) {
+  int pos0, pos1;
+
+  if (i < 0 || i >= idx->len) {
+    *ok = gFalse;
+    return;
+  }
+  pos0 = idx->startPos + getUVarBE(idx->pos + 3 + i * idx->offSize,
+				   idx->offSize, ok);
+  pos1 = idx->startPos + getUVarBE(idx->pos + 3 + (i + 1) * idx->offSize,
+				   idx->offSize, ok);
+  if (pos0 < idx->startPos || pos0 > idx->endPos ||
+      pos1 <= idx->startPos || pos1 > idx->endPos ||
+      pos1 < pos0) {
+    *ok = gFalse;
+  }
+  val->pos = pos0;
+  val->len = pos1 - pos0;
+}
+
+char *FoFiType1C::getString(int sid, char *buf, GBool *ok) {
+  Type1CIndexVal val;
+  int n;
+
+  if (sid < 391) {
+    strcpy(buf, fofiType1CStdStrings[sid]);
+  } else {
+    sid -= 391;
+    getIndexVal(&stringIdx, sid, &val, ok);
+    if (*ok) {
+      if ((n = val.len) > 255) {
+	n = 255;
+      }
+      strncpy(buf, (char *)&file[val.pos], n);
+      buf[n] = '\0';
+    } else {
+      buf[0] = '\0';
+    }
+  }
+  return buf;
+}
diff --git a/xpdf/FoFiType1C.h b/xpdf/FoFiType1C.h
new file mode 100644
--- /dev/null
+++ b/xpdf/FoFiType1C.h
@@ -0,0 +1,233 @@
+//========================================================================
+//
+// FoFiType1C.h
+//
+// Copyright 1999-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef FOFITYPE1C_H
+#define FOFITYPE1C_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "FoFiBase.h"
+
+class GString;
+
+//------------------------------------------------------------------------
+
+struct Type1CIndex {
+  int pos;			// absolute position in file
+  int len;			// length (number of entries)
+  int offSize;			// offset size
+  int startPos;			// position of start of index data - 1
+  int endPos;			// position one byte past end of the index
+};
+
+struct Type1CIndexVal {
+  int pos;			// absolute position in file
+  int len;			// length, in bytes
+};
+
+struct Type1CTopDict {
+  int firstOp;
+
+  int versionSID;
+  int noticeSID;
+  int copyrightSID;
+  int fullNameSID;
+  int familyNameSID;
+  int weightSID;
+  int isFixedPitch;
+  double italicAngle;
+  double underlinePosition;
+  double underlineThickness;
+  int paintType;
+  int charstringType;
+  double fontMatrix[6];
+  GBool hasFontMatrix;		// CID fonts are allowed to put their
+				//   FontMatrix in the FD instead of the
+				//   top dict
+  int uniqueID;
+  double fontBBox[4];
+  double strokeWidth;
+  int charsetOffset;
+  int encodingOffset;
+  int charStringsOffset;
+  int privateSize;
+  int privateOffset;
+
+  // CIDFont entries
+  int registrySID;
+  int orderingSID;
+  int supplement;
+  int fdArrayOffset;
+  int fdSelectOffset;
+};
+
+#define type1CMaxBlueValues 14
+#define type1CMaxOtherBlues 10
+#define type1CMaxStemSnap   12
+
+struct Type1CPrivateDict {
+  double fontMatrix[6];
+  GBool hasFontMatrix;
+  int blueValues[type1CMaxBlueValues];
+  int nBlueValues;
+  int otherBlues[type1CMaxOtherBlues];
+  int nOtherBlues;
+  int familyBlues[type1CMaxBlueValues];
+  int nFamilyBlues;
+  int familyOtherBlues[type1CMaxOtherBlues];
+  int nFamilyOtherBlues;
+  double blueScale;
+  int blueShift;
+  int blueFuzz;
+  double stdHW;
+  GBool hasStdHW;
+  double stdVW;
+  GBool hasStdVW;
+  double stemSnapH[type1CMaxStemSnap];
+  int nStemSnapH;
+  double stemSnapV[type1CMaxStemSnap];
+  int nStemSnapV;
+  GBool forceBold;
+  GBool hasForceBold;
+  double forceBoldThreshold;
+  int languageGroup;
+  double expansionFactor;
+  int initialRandomSeed;
+  int subrsOffset;
+  double defaultWidthX;
+  GBool defaultWidthXFP;
+  double nominalWidthX;
+  GBool nominalWidthXFP;
+};
+
+struct Type1COp {
+  GBool isNum;			// true -> number, false -> operator
+  GBool isFP;			// true -> floating point number, false -> int
+  union {
+    double num;			// if num is true
+    int op;			// if num is false
+  };
+};
+
+struct Type1CEexecBuf {
+  FoFiOutputFunc outputFunc;
+  void *outputStream;
+  GBool ascii;			// ASCII encoding?
+  Gushort r1;			// eexec encryption key
+  int line;			// number of eexec chars left on current line
+};
+
+//------------------------------------------------------------------------
+// FoFiType1C
+//------------------------------------------------------------------------
+
+class FoFiType1C: public FoFiBase {
+public:
+
+  // Create a FoFiType1C object from a memory buffer.
+  static FoFiType1C *make(char *fileA, int lenA);
+
+  // Create a FoFiType1C object from a file on disk.
+  static FoFiType1C *load(char *fileName);
+
+  virtual ~FoFiType1C();
+
+  // Return the font name.
+  char *getName();
+
+  // Return the encoding, as an array of 256 names (any of which may
+  // be NULL).  This is only useful with 8-bit fonts.
+  char **getEncoding();
+
+  // Return the mapping from CIDs to GIDs, and return the number of
+  // CIDs in *<nCIDs>.  This is only useful for CID fonts.
+  Gushort *getCIDToGIDMap(int *nCIDs);
+
+  // Convert to a Type 1 font, suitable for embedding in a PostScript
+  // file.  This is only useful with 8-bit fonts.  If <newEncoding> is
+  // not NULL, it will be used in place of the encoding in the Type 1C
+  // font.  If <ascii> is true the eexec section will be hex-encoded,
+  // otherwise it will be left as binary data.  If <psName> is non-NULL,
+  // it will be used as the PostScript font name.
+  void convertToType1(char *psName, char **newEncoding, GBool ascii,
+		      FoFiOutputFunc outputFunc, void *outputStream);
+
+  // Convert to a Type 0 CIDFont, suitable for embedding in a
+  // PostScript file.  <psName> will be used as the PostScript font
+  // name.
+  void convertToCIDType0(char *psName,
+			 FoFiOutputFunc outputFunc, void *outputStream);
+
+  // Convert to a Type 0 (but non-CID) composite font, suitable for
+  // embedding in a PostScript file.  <psName> will be used as the
+  // PostScript font name.
+  void convertToType0(char *psName,
+		      FoFiOutputFunc outputFunc, void *outputStream);
+
+private:
+
+  FoFiType1C(char *fileA, int lenA, GBool freeFileDataA);
+  void eexecCvtGlyph(Type1CEexecBuf *eb, char *glyphName,
+		     int offset, int nBytes,
+		     Type1CIndex *subrIdx,
+		     Type1CPrivateDict *pDict);
+  void cvtGlyph(int offset, int nBytes, GString *charBuf,
+		Type1CIndex *subrIdx, Type1CPrivateDict *pDict,
+		GBool top);
+  void cvtGlyphWidth(GBool useOp, GString *charBuf,
+		     Type1CPrivateDict *pDict);
+  void cvtNum(double x, GBool isFP, GString *charBuf);
+  void eexecWrite(Type1CEexecBuf *eb, char *s);
+  void eexecWriteCharstring(Type1CEexecBuf *eb, Guchar *s, int n);
+  GBool parse();
+  void readTopDict();
+  void readFD(int offset, int length, Type1CPrivateDict *pDict);
+  void readPrivateDict(int offset, int length, Type1CPrivateDict *pDict);
+  void readFDSelect();
+  void buildEncoding();
+  GBool readCharset();
+  int getOp(int pos, GBool charstring, GBool *ok);
+  int getDeltaIntArray(int *arr, int maxLen);
+  int getDeltaFPArray(double *arr, int maxLen);
+  void getIndex(int pos, Type1CIndex *idx, GBool *ok);
+  void getIndexVal(Type1CIndex *idx, int i, Type1CIndexVal *val, GBool *ok);
+  char *getString(int sid, char *buf, GBool *ok);
+
+  GString *name;
+  char **encoding;
+
+  Type1CIndex nameIdx;
+  Type1CIndex topDictIdx;
+  Type1CIndex stringIdx;
+  Type1CIndex gsubrIdx;
+  Type1CIndex charStringsIdx;
+
+  Type1CTopDict topDict;
+  Type1CPrivateDict *privateDicts;
+
+  int nGlyphs;
+  int nFDs;
+  Guchar *fdSelect;
+  Gushort *charset;
+  int gsubrBias;
+
+  GBool parsedOk;
+
+  Type1COp ops[49];		// operands and operator
+  int nOps;			// number of operands
+  int nHints;			// number of hints for the current glyph
+  GBool firstOp;		// true if we haven't hit the first op yet
+  GBool openPath;		// true if there is an unclosed path
+};
+
+#endif
diff --git a/xpdf/FontEncodingTables.cc b/xpdf/FontEncodingTables.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/FontEncodingTables.cc
@@ -0,0 +1,1824 @@
+//========================================================================
+//
+// FontEncodingTables.cc
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+#include <stdlib.h>
+#include "FontEncodingTables.h"
+
+char *macRomanEncoding[256] = {
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "space",
+  "exclam",
+  "quotedbl",
+  "numbersign",
+  "dollar",
+  "percent",
+  "ampersand",
+  "quotesingle",
+  "parenleft",
+  "parenright",
+  "asterisk",
+  "plus",
+  "comma",
+  "hyphen",
+  "period",
+  "slash",
+  "zero",
+  "one",
+  "two",
+  "three",
+  "four",
+  "five",
+  "six",
+  "seven",
+  "eight",
+  "nine",
+  "colon",
+  "semicolon",
+  "less",
+  "equal",
+  "greater",
+  "question",
+  "at",
+  "A",
+  "B",
+  "C",
+  "D",
+  "E",
+  "F",
+  "G",
+  "H",
+  "I",
+  "J",
+  "K",
+  "L",
+  "M",
+  "N",
+  "O",
+  "P",
+  "Q",
+  "R",
+  "S",
+  "T",
+  "U",
+  "V",
+  "W",
+  "X",
+  "Y",
+  "Z",
+  "bracketleft",
+  "backslash",
+  "bracketright",
+  "asciicircum",
+  "underscore",
+  "grave",
+  "a",
+  "b",
+  "c",
+  "d",
+  "e",
+  "f",
+  "g",
+  "h",
+  "i",
+  "j",
+  "k",
+  "l",
+  "m",
+  "n",
+  "o",
+  "p",
+  "q",
+  "r",
+  "s",
+  "t",
+  "u",
+  "v",
+  "w",
+  "x",
+  "y",
+  "z",
+  "braceleft",
+  "bar",
+  "braceright",
+  "asciitilde",
+  NULL,
+  "Adieresis",
+  "Aring",
+  "Ccedilla",
+  "Eacute",
+  "Ntilde",
+  "Odieresis",
+  "Udieresis",
+  "aacute",
+  "agrave",
+  "acircumflex",
+  "adieresis",
+  "atilde",
+  "aring",
+  "ccedilla",
+  "eacute",
+  "egrave",
+  "ecircumflex",
+  "edieresis",
+  "iacute",
+  "igrave",
+  "icircumflex",
+  "idieresis",
+  "ntilde",
+  "oacute",
+  "ograve",
+  "ocircumflex",
+  "odieresis",
+  "otilde",
+  "uacute",
+  "ugrave",
+  "ucircumflex",
+  "udieresis",
+  "dagger",
+  "degree",
+  "cent",
+  "sterling",
+  "section",
+  "bullet",
+  "paragraph",
+  "germandbls",
+  "registered",
+  "copyright",
+  "trademark",
+  "acute",
+  "dieresis",
+  "notequal",
+  "AE",
+  "Oslash",
+  "infinity",
+  "plusminus",
+  "lessequal",
+  "greaterequal",
+  "yen",
+  "mu",
+  "partialdiff",
+  "summation",
+  "product",
+  "pi",
+  "integral",
+  "ordfeminine",
+  "ordmasculine",
+  "Omega",
+  "ae",
+  "oslash",
+  "questiondown",
+  "exclamdown",
+  "logicalnot",
+  "radical",
+  "florin",
+  "approxequal",
+  "Delta",
+  "guillemotleft",
+  "guillemotright",
+  "ellipsis",
+  "space",
+  "Agrave",
+  "Atilde",
+  "Otilde",
+  "OE",
+  "oe",
+  "endash",
+  "emdash",
+  "quotedblleft",
+  "quotedblright",
+  "quoteleft",
+  "quoteright",
+  "divide",
+  "lozenge",
+  "ydieresis",
+  "Ydieresis",
+  "fraction",
+  "currency",
+  "guilsinglleft",
+  "guilsinglright",
+  "fi",
+  "fl",
+  "daggerdbl",
+  "periodcentered",
+  "quotesinglbase",
+  "quotedblbase",
+  "perthousand",
+  "Acircumflex",
+  "Ecircumflex",
+  "Aacute",
+  "Edieresis",
+  "Egrave",
+  "Iacute",
+  "Icircumflex",
+  "Idieresis",
+  "Igrave",
+  "Oacute",
+  "Ocircumflex",
+  "apple",
+  "Ograve",
+  "Uacute",
+  "Ucircumflex",
+  "Ugrave",
+  "dotlessi",
+  "circumflex",
+  "tilde",
+  "macron",
+  "breve",
+  "dotaccent",
+  "ring",
+  "cedilla",
+  "hungarumlaut",
+  "ogonek",
+  "caron"
+};
+
+char *macExpertEncoding[256] = {
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "space",
+  "exclamsmall",
+  "Hungarumlautsmall",
+  "centoldstyle",
+  "dollaroldstyle",
+  "dollarsuperior",
+  "ampersandsmall",
+  "Acutesmall",
+  "parenleftsuperior",
+  "parenrightsuperior",
+  "twodotenleader",
+  "onedotenleader",
+  "comma",
+  "hyphen",
+  "period",
+  "fraction",
+  "zerooldstyle",
+  "oneoldstyle",
+  "twooldstyle",
+  "threeoldstyle",
+  "fouroldstyle",
+  "fiveoldstyle",
+  "sixoldstyle",
+  "sevenoldstyle",
+  "eightoldstyle",
+  "nineoldstyle",
+  "colon",
+  "semicolon",
+  NULL,
+  "threequartersemdash",
+  NULL,
+  "questionsmall",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "Ethsmall",
+  NULL,
+  NULL,
+  "onequarter",
+  "onehalf",
+  "threequarters",
+  "oneeighth",
+  "threeeighths",
+  "fiveeighths",
+  "seveneighths",
+  "onethird",
+  "twothirds",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "ff",
+  "fi",
+  "fl",
+  "ffi",
+  "ffl",
+  "parenleftinferior",
+  NULL,
+  "parenrightinferior",
+  "Circumflexsmall",
+  "hypheninferior",
+  "Gravesmall",
+  "Asmall",
+  "Bsmall",
+  "Csmall",
+  "Dsmall",
+  "Esmall",
+  "Fsmall",
+  "Gsmall",
+  "Hsmall",
+  "Ismall",
+  "Jsmall",
+  "Ksmall",
+  "Lsmall",
+  "Msmall",
+  "Nsmall",
+  "Osmall",
+  "Psmall",
+  "Qsmall",
+  "Rsmall",
+  "Ssmall",
+  "Tsmall",
+  "Usmall",
+  "Vsmall",
+  "Wsmall",
+  "Xsmall",
+  "Ysmall",
+  "Zsmall",
+  "colonmonetary",
+  "onefitted",
+  "rupiah",
+  "Tildesmall",
+  NULL,
+  NULL,
+  "asuperior",
+  "centsuperior",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "Aacutesmall",
+  "Agravesmall",
+  "Acircumflexsmall",
+  "Adieresissmall",
+  "Atildesmall",
+  "Aringsmall",
+  "Ccedillasmall",
+  "Eacutesmall",
+  "Egravesmall",
+  "Ecircumflexsmall",
+  "Edieresissmall",
+  "Iacutesmall",
+  "Igravesmall",
+  "Icircumflexsmall",
+  "Idieresissmall",
+  "Ntildesmall",
+  "Oacutesmall",
+  "Ogravesmall",
+  "Ocircumflexsmall",
+  "Odieresissmall",
+  "Otildesmall",
+  "Uacutesmall",
+  "Ugravesmall",
+  "Ucircumflexsmall",
+  "Udieresissmall",
+  NULL,
+  "eightsuperior",
+  "fourinferior",
+  "threeinferior",
+  "sixinferior",
+  "eightinferior",
+  "seveninferior",
+  "Scaronsmall",
+  NULL,
+  "centinferior",
+  "twoinferior",
+  NULL,
+  "Dieresissmall",
+  NULL,
+  "Caronsmall",
+  "osuperior",
+  "fiveinferior",
+  NULL,
+  "commainferior",
+  "periodinferior",
+  "Yacutesmall",
+  NULL,
+  "dollarinferior",
+  NULL,
+  NULL,
+  "Thornsmall",
+  NULL,
+  "nineinferior",
+  "zeroinferior",
+  "Zcaronsmall",
+  "AEsmall",
+  "Oslashsmall",
+  "questiondownsmall",
+  "oneinferior",
+  "Lslashsmall",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "Cedillasmall",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "OEsmall",
+  "figuredash",
+  "hyphensuperior",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "exclamdownsmall",
+  NULL,
+  "Ydieresissmall",
+  NULL,
+  "onesuperior",
+  "twosuperior",
+  "threesuperior",
+  "foursuperior",
+  "fivesuperior",
+  "sixsuperior",
+  "sevensuperior",
+  "ninesuperior",
+  "zerosuperior",
+  NULL,
+  "esuperior",
+  "rsuperior",
+  "tsuperior",
+  NULL,
+  NULL,
+  "isuperior",
+  "ssuperior",
+  "dsuperior",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "lsuperior",
+  "Ogoneksmall",
+  "Brevesmall",
+  "Macronsmall",
+  "bsuperior",
+  "nsuperior",
+  "msuperior",
+  "commasuperior",
+  "periodsuperior",
+  "Dotaccentsmall",
+  "Ringsmall",
+  NULL,
+  NULL,
+  NULL,
+  NULL
+};
+
+char *winAnsiEncoding[256] = {
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "space",
+  "exclam",
+  "quotedbl",
+  "numbersign",
+  "dollar",
+  "percent",
+  "ampersand",
+  "quotesingle",
+  "parenleft",
+  "parenright",
+  "asterisk",
+  "plus",
+  "comma",
+  "hyphen",
+  "period",
+  "slash",
+  "zero",
+  "one",
+  "two",
+  "three",
+  "four",
+  "five",
+  "six",
+  "seven",
+  "eight",
+  "nine",
+  "colon",
+  "semicolon",
+  "less",
+  "equal",
+  "greater",
+  "question",
+  "at",
+  "A",
+  "B",
+  "C",
+  "D",
+  "E",
+  "F",
+  "G",
+  "H",
+  "I",
+  "J",
+  "K",
+  "L",
+  "M",
+  "N",
+  "O",
+  "P",
+  "Q",
+  "R",
+  "S",
+  "T",
+  "U",
+  "V",
+  "W",
+  "X",
+  "Y",
+  "Z",
+  "bracketleft",
+  "backslash",
+  "bracketright",
+  "asciicircum",
+  "underscore",
+  "grave",
+  "a",
+  "b",
+  "c",
+  "d",
+  "e",
+  "f",
+  "g",
+  "h",
+  "i",
+  "j",
+  "k",
+  "l",
+  "m",
+  "n",
+  "o",
+  "p",
+  "q",
+  "r",
+  "s",
+  "t",
+  "u",
+  "v",
+  "w",
+  "x",
+  "y",
+  "z",
+  "braceleft",
+  "bar",
+  "braceright",
+  "asciitilde",
+  "bullet",
+  "Euro",
+  "bullet",
+  "quotesinglbase",
+  "florin",
+  "quotedblbase",
+  "ellipsis",
+  "dagger",
+  "daggerdbl",
+  "circumflex",
+  "perthousand",
+  "Scaron",
+  "guilsinglleft",
+  "OE",
+  "bullet",
+  "Zcaron",
+  "bullet",
+  "bullet",
+  "quoteleft",
+  "quoteright",
+  "quotedblleft",
+  "quotedblright",
+  "bullet",
+  "endash",
+  "emdash",
+  "tilde",
+  "trademark",
+  "scaron",
+  "guilsinglright",
+  "oe",
+  "bullet",
+  "zcaron",
+  "Ydieresis",
+  "space",
+  "exclamdown",
+  "cent",
+  "sterling",
+  "currency",
+  "yen",
+  "brokenbar",
+  "section",
+  "dieresis",
+  "copyright",
+  "ordfeminine",
+  "guillemotleft",
+  "logicalnot",
+  "hyphen",
+  "registered",
+  "macron",
+  "degree",
+  "plusminus",
+  "twosuperior",
+  "threesuperior",
+  "acute",
+  "mu",
+  "paragraph",
+  "periodcentered",
+  "cedilla",
+  "onesuperior",
+  "ordmasculine",
+  "guillemotright",
+  "onequarter",
+  "onehalf",
+  "threequarters",
+  "questiondown",
+  "Agrave",
+  "Aacute",
+  "Acircumflex",
+  "Atilde",
+  "Adieresis",
+  "Aring",
+  "AE",
+  "Ccedilla",
+  "Egrave",
+  "Eacute",
+  "Ecircumflex",
+  "Edieresis",
+  "Igrave",
+  "Iacute",
+  "Icircumflex",
+  "Idieresis",
+  "Eth",
+  "Ntilde",
+  "Ograve",
+  "Oacute",
+  "Ocircumflex",
+  "Otilde",
+  "Odieresis",
+  "multiply",
+  "Oslash",
+  "Ugrave",
+  "Uacute",
+  "Ucircumflex",
+  "Udieresis",
+  "Yacute",
+  "Thorn",
+  "germandbls",
+  "agrave",
+  "aacute",
+  "acircumflex",
+  "atilde",
+  "adieresis",
+  "aring",
+  "ae",
+  "ccedilla",
+  "egrave",
+  "eacute",
+  "ecircumflex",
+  "edieresis",
+  "igrave",
+  "iacute",
+  "icircumflex",
+  "idieresis",
+  "eth",
+  "ntilde",
+  "ograve",
+  "oacute",
+  "ocircumflex",
+  "otilde",
+  "odieresis",
+  "divide",
+  "oslash",
+  "ugrave",
+  "uacute",
+  "ucircumflex",
+  "udieresis",
+  "yacute",
+  "thorn",
+  "ydieresis"
+};
+
+char *standardEncoding[256] = {
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "space",
+  "exclam",
+  "quotedbl",
+  "numbersign",
+  "dollar",
+  "percent",
+  "ampersand",
+  "quoteright",
+  "parenleft",
+  "parenright",
+  "asterisk",
+  "plus",
+  "comma",
+  "hyphen",
+  "period",
+  "slash",
+  "zero",
+  "one",
+  "two",
+  "three",
+  "four",
+  "five",
+  "six",
+  "seven",
+  "eight",
+  "nine",
+  "colon",
+  "semicolon",
+  "less",
+  "equal",
+  "greater",
+  "question",
+  "at",
+  "A",
+  "B",
+  "C",
+  "D",
+  "E",
+  "F",
+  "G",
+  "H",
+  "I",
+  "J",
+  "K",
+  "L",
+  "M",
+  "N",
+  "O",
+  "P",
+  "Q",
+  "R",
+  "S",
+  "T",
+  "U",
+  "V",
+  "W",
+  "X",
+  "Y",
+  "Z",
+  "bracketleft",
+  "backslash",
+  "bracketright",
+  "asciicircum",
+  "underscore",
+  "quoteleft",
+  "a",
+  "b",
+  "c",
+  "d",
+  "e",
+  "f",
+  "g",
+  "h",
+  "i",
+  "j",
+  "k",
+  "l",
+  "m",
+  "n",
+  "o",
+  "p",
+  "q",
+  "r",
+  "s",
+  "t",
+  "u",
+  "v",
+  "w",
+  "x",
+  "y",
+  "z",
+  "braceleft",
+  "bar",
+  "braceright",
+  "asciitilde",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "exclamdown",
+  "cent",
+  "sterling",
+  "fraction",
+  "yen",
+  "florin",
+  "section",
+  "currency",
+  "quotesingle",
+  "quotedblleft",
+  "guillemotleft",
+  "guilsinglleft",
+  "guilsinglright",
+  "fi",
+  "fl",
+  NULL,
+  "endash",
+  "dagger",
+  "daggerdbl",
+  "periodcentered",
+  NULL,
+  "paragraph",
+  "bullet",
+  "quotesinglbase",
+  "quotedblbase",
+  "quotedblright",
+  "guillemotright",
+  "ellipsis",
+  "perthousand",
+  NULL,
+  "questiondown",
+  NULL,
+  "grave",
+  "acute",
+  "circumflex",
+  "tilde",
+  "macron",
+  "breve",
+  "dotaccent",
+  "dieresis",
+  NULL,
+  "ring",
+  "cedilla",
+  NULL,
+  "hungarumlaut",
+  "ogonek",
+  "caron",
+  "emdash",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "AE",
+  NULL,
+  "ordfeminine",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "Lslash",
+  "Oslash",
+  "OE",
+  "ordmasculine",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "ae",
+  NULL,
+  NULL,
+  NULL,
+  "dotlessi",
+  NULL,
+  NULL,
+  "lslash",
+  "oslash",
+  "oe",
+  "germandbls",
+  NULL,
+  NULL,
+  NULL,
+  NULL
+};
+
+char *expertEncoding[256] = {
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "space",
+  "exclamsmall",
+  "Hungarumlautsmall",
+  NULL,
+  "dollaroldstyle",
+  "dollarsuperior",
+  "ampersandsmall",
+  "Acutesmall",
+  "parenleftsuperior",
+  "parenrightsuperior",
+  "twodotenleader",
+  "onedotenleader",
+  "comma",
+  "hyphen",
+  "period",
+  "fraction",
+  "zerooldstyle",
+  "oneoldstyle",
+  "twooldstyle",
+  "threeoldstyle",
+  "fouroldstyle",
+  "fiveoldstyle",
+  "sixoldstyle",
+  "sevenoldstyle",
+  "eightoldstyle",
+  "nineoldstyle",
+  "colon",
+  "semicolon",
+  "commasuperior",
+  "threequartersemdash",
+  "periodsuperior",
+  "questionsmall",
+  NULL,
+  "asuperior",
+  "bsuperior",
+  "centsuperior",
+  "dsuperior",
+  "esuperior",
+  NULL,
+  NULL,
+  NULL,
+  "isuperior",
+  NULL,
+  NULL,
+  "lsuperior",
+  "msuperior",
+  "nsuperior",
+  "osuperior",
+  NULL,
+  NULL,
+  "rsuperior",
+  "ssuperior",
+  "tsuperior",
+  NULL,
+  "ff",
+  "fi",
+  "fl",
+  "ffi",
+  "ffl",
+  "parenleftinferior",
+  NULL,
+  "parenrightinferior",
+  "Circumflexsmall",
+  "hyphensuperior",
+  "Gravesmall",
+  "Asmall",
+  "Bsmall",
+  "Csmall",
+  "Dsmall",
+  "Esmall",
+  "Fsmall",
+  "Gsmall",
+  "Hsmall",
+  "Ismall",
+  "Jsmall",
+  "Ksmall",
+  "Lsmall",
+  "Msmall",
+  "Nsmall",
+  "Osmall",
+  "Psmall",
+  "Qsmall",
+  "Rsmall",
+  "Ssmall",
+  "Tsmall",
+  "Usmall",
+  "Vsmall",
+  "Wsmall",
+  "Xsmall",
+  "Ysmall",
+  "Zsmall",
+  "colonmonetary",
+  "onefitted",
+  "rupiah",
+  "Tildesmall",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "exclamdownsmall",
+  "centoldstyle",
+  "Lslashsmall",
+  NULL,
+  NULL,
+  "Scaronsmall",
+  "Zcaronsmall",
+  "Dieresissmall",
+  "Brevesmall",
+  "Caronsmall",
+  NULL,
+  "Dotaccentsmall",
+  NULL,
+  NULL,
+  "Macronsmall",
+  NULL,
+  NULL,
+  "figuredash",
+  "hypheninferior",
+  NULL,
+  NULL,
+  "Ogoneksmall",
+  "Ringsmall",
+  "Cedillasmall",
+  NULL,
+  NULL,
+  NULL,
+  "onequarter",
+  "onehalf",
+  "threequarters",
+  "questiondownsmall",
+  "oneeighth",
+  "threeeighths",
+  "fiveeighths",
+  "seveneighths",
+  "onethird",
+  "twothirds",
+  NULL,
+  NULL,
+  "zerosuperior",
+  "onesuperior",
+  "twosuperior",
+  "threesuperior",
+  "foursuperior",
+  "fivesuperior",
+  "sixsuperior",
+  "sevensuperior",
+  "eightsuperior",
+  "ninesuperior",
+  "zeroinferior",
+  "oneinferior",
+  "twoinferior",
+  "threeinferior",
+  "fourinferior",
+  "fiveinferior",
+  "sixinferior",
+  "seveninferior",
+  "eightinferior",
+  "nineinferior",
+  "centinferior",
+  "dollarinferior",
+  "periodinferior",
+  "commainferior",
+  "Agravesmall",
+  "Aacutesmall",
+  "Acircumflexsmall",
+  "Atildesmall",
+  "Adieresissmall",
+  "Aringsmall",
+  "AEsmall",
+  "Ccedillasmall",
+  "Egravesmall",
+  "Eacutesmall",
+  "Ecircumflexsmall",
+  "Edieresissmall",
+  "Igravesmall",
+  "Iacutesmall",
+  "Icircumflexsmall",
+  "Idieresissmall",
+  "Ethsmall",
+  "Ntildesmall",
+  "Ogravesmall",
+  "Oacutesmall",
+  "Ocircumflexsmall",
+  "Otildesmall",
+  "Odieresissmall",
+  "OEsmall",
+  "Oslashsmall",
+  "Ugravesmall",
+  "Uacutesmall",
+  "Ucircumflexsmall",
+  "Udieresissmall",
+  "Yacutesmall",
+  "Thornsmall",
+  "Ydieresissmall"
+};
+
+char *symbolEncoding[256] = {
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "space",
+  "exclam",
+  "universal",
+  "numbersign",
+  "existential",
+  "percent",
+  "ampersand",
+  "suchthat",
+  "parenleft",
+  "parenright",
+  "asteriskmath",
+  "plus",
+  "comma",
+  "minus",
+  "period",
+  "slash",
+  "zero",
+  "one",
+  "two",
+  "three",
+  "four",
+  "five",
+  "six",
+  "seven",
+  "eight",
+  "nine",
+  "colon",
+  "semicolon",
+  "less",
+  "equal",
+  "greater",
+  "question",
+  "congruent",
+  "Alpha",
+  "Beta",
+  "Chi",
+  "Delta",
+  "Epsilon",
+  "Phi",
+  "Gamma",
+  "Eta",
+  "Iota",
+  "theta1",
+  "Kappa",
+  "Lambda",
+  "Mu",
+  "Nu",
+  "Omicron",
+  "Pi",
+  "Theta",
+  "Rho",
+  "Sigma",
+  "Tau",
+  "Upsilon",
+  "sigma1",
+  "Omega",
+  "Xi",
+  "Psi",
+  "Zeta",
+  "bracketleft",
+  "therefore",
+  "bracketright",
+  "perpendicular",
+  "underscore",
+  "radicalex",
+  "alpha",
+  "beta",
+  "chi",
+  "delta",
+  "epsilon",
+  "phi",
+  "gamma",
+  "eta",
+  "iota",
+  "phi1",
+  "kappa",
+  "lambda",
+  "mu",
+  "nu",
+  "omicron",
+  "pi",
+  "theta",
+  "rho",
+  "sigma",
+  "tau",
+  "upsilon",
+  "omega1",
+  "omega",
+  "xi",
+  "psi",
+  "zeta",
+  "braceleft",
+  "bar",
+  "braceright",
+  "similar",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "Upsilon1",
+  "minute",
+  "lessequal",
+  "fraction",
+  "infinity",
+  "florin",
+  "club",
+  "diamond",
+  "heart",
+  "spade",
+  "arrowboth",
+  "arrowleft",
+  "arrowup",
+  "arrowright",
+  "arrowdown",
+  "degree",
+  "plusminus",
+  "second",
+  "greaterequal",
+  "multiply",
+  "proportional",
+  "partialdiff",
+  "bullet",
+  "divide",
+  "notequal",
+  "equivalence",
+  "approxequal",
+  "ellipsis",
+  "arrowvertex",
+  "arrowhorizex",
+  "carriagereturn",
+  "aleph",
+  "Ifraktur",
+  "Rfraktur",
+  "weierstrass",
+  "circlemultiply",
+  "circleplus",
+  "emptyset",
+  "intersection",
+  "union",
+  "propersuperset",
+  "reflexsuperset",
+  "notsubset",
+  "propersubset",
+  "reflexsubset",
+  "element",
+  "notelement",
+  "angle",
+  "gradient",
+  "registerserif",
+  "copyrightserif",
+  "trademarkserif",
+  "product",
+  "radical",
+  "dotmath",
+  "logicalnot",
+  "logicaland",
+  "logicalor",
+  "arrowdblboth",
+  "arrowdblleft",
+  "arrowdblup",
+  "arrowdblright",
+  "arrowdbldown",
+  "lozenge",
+  "angleleft",
+  "registersans",
+  "copyrightsans",
+  "trademarksans",
+  "summation",
+  "parenlefttp",
+  "parenleftex",
+  "parenleftbt",
+  "bracketlefttp",
+  "bracketleftex",
+  "bracketleftbt",
+  "bracelefttp",
+  "braceleftmid",
+  "braceleftbt",
+  "braceex",
+  NULL,
+  "angleright",
+  "integral",
+  "integraltp",
+  "integralex",
+  "integralbt",
+  "parenrighttp",
+  "parenrightex",
+  "parenrightbt",
+  "bracketrighttp",
+  "bracketrightex",
+  "bracketrightbt",
+  "bracerighttp",
+  "bracerightmid",
+  "bracerightbt",
+  NULL
+};
+
+char *zapfDingbatsEncoding[256] = {
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "space",
+  "a1",
+  "a2",
+  "a202",
+  "a3",
+  "a4",
+  "a5",
+  "a119",
+  "a118",
+  "a117",
+  "a11",
+  "a12",
+  "a13",
+  "a14",
+  "a15",
+  "a16",
+  "a105",
+  "a17",
+  "a18",
+  "a19",
+  "a20",
+  "a21",
+  "a22",
+  "a23",
+  "a24",
+  "a25",
+  "a26",
+  "a27",
+  "a28",
+  "a6",
+  "a7",
+  "a8",
+  "a9",
+  "a10",
+  "a29",
+  "a30",
+  "a31",
+  "a32",
+  "a33",
+  "a34",
+  "a35",
+  "a36",
+  "a37",
+  "a38",
+  "a39",
+  "a40",
+  "a41",
+  "a42",
+  "a43",
+  "a44",
+  "a45",
+  "a46",
+  "a47",
+  "a48",
+  "a49",
+  "a50",
+  "a51",
+  "a52",
+  "a53",
+  "a54",
+  "a55",
+  "a56",
+  "a57",
+  "a58",
+  "a59",
+  "a60",
+  "a61",
+  "a62",
+  "a63",
+  "a64",
+  "a65",
+  "a66",
+  "a67",
+  "a68",
+  "a69",
+  "a70",
+  "a71",
+  "a72",
+  "a73",
+  "a74",
+  "a203",
+  "a75",
+  "a204",
+  "a76",
+  "a77",
+  "a78",
+  "a79",
+  "a81",
+  "a82",
+  "a83",
+  "a84",
+  "a97",
+  "a98",
+  "a99",
+  "a100",
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  "a101",
+  "a102",
+  "a103",
+  "a104",
+  "a106",
+  "a107",
+  "a108",
+  "a112",
+  "a111",
+  "a110",
+  "a109",
+  "a120",
+  "a121",
+  "a122",
+  "a123",
+  "a124",
+  "a125",
+  "a126",
+  "a127",
+  "a128",
+  "a129",
+  "a130",
+  "a131",
+  "a132",
+  "a133",
+  "a134",
+  "a135",
+  "a136",
+  "a137",
+  "a138",
+  "a139",
+  "a140",
+  "a141",
+  "a142",
+  "a143",
+  "a144",
+  "a145",
+  "a146",
+  "a147",
+  "a148",
+  "a149",
+  "a150",
+  "a151",
+  "a152",
+  "a153",
+  "a154",
+  "a155",
+  "a156",
+  "a157",
+  "a158",
+  "a159",
+  "a160",
+  "a161",
+  "a163",
+  "a164",
+  "a196",
+  "a165",
+  "a192",
+  "a166",
+  "a167",
+  "a168",
+  "a169",
+  "a170",
+  "a171",
+  "a172",
+  "a173",
+  "a162",
+  "a174",
+  "a175",
+  "a176",
+  "a177",
+  "a178",
+  "a179",
+  "a193",
+  "a180",
+  "a199",
+  "a181",
+  "a200",
+  "a182",
+  NULL,
+  "a201",
+  "a183",
+  "a184",
+  "a197",
+  "a185",
+  "a194",
+  "a198",
+  "a186",
+  "a195",
+  "a187",
+  "a188",
+  "a189",
+  "a190",
+  "a191",
+  NULL
+};
diff --git a/xpdf/FontEncodingTables.h b/xpdf/FontEncodingTables.h
new file mode 100644
--- /dev/null
+++ b/xpdf/FontEncodingTables.h
@@ -0,0 +1,20 @@
+//========================================================================
+//
+// FontEncodingTables.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef FONTENCODINGTABLES_H
+#define FONTENCODINGTABLES_H
+
+extern char *macRomanEncoding[];
+extern char *macExpertEncoding[];
+extern char *winAnsiEncoding[];
+extern char *standardEncoding[];
+extern char *expertEncoding[];
+extern char *symbolEncoding[];
+extern char *zapfDingbatsEncoding[];
+
+#endif
diff --git a/xpdf/Function.cc b/xpdf/Function.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Function.cc
@@ -0,0 +1,1573 @@
+//========================================================================
+//
+// Function.cc
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include "gmem.h"
+#include "Object.h"
+#include "Dict.h"
+#include "Stream.h"
+#include "Error.h"
+#include "Function.h"
+
+//------------------------------------------------------------------------
+// Function
+//------------------------------------------------------------------------
+
+Function::Function() {
+}
+
+Function::~Function() {
+}
+
+Function *Function::parse(Object *funcObj) {
+  Function *func;
+  Dict *dict;
+  int funcType;
+  Object obj1;
+
+  if (funcObj->isStream()) {
+    dict = funcObj->streamGetDict();
+  } else if (funcObj->isDict()) {
+    dict = funcObj->getDict();
+  } else if (funcObj->isName("Identity")) {
+    return new IdentityFunction();
+  } else {
+    error(-1, "Expected function dictionary or stream");
+    return NULL;
+  }
+
+  if (!dict->lookup("FunctionType", &obj1)->isInt()) {
+    error(-1, "Function type is missing or wrong type");
+    obj1.free();
+    return NULL;
+  }
+  funcType = obj1.getInt();
+  obj1.free();
+
+  if (funcType == 0) {
+    func = new SampledFunction(funcObj, dict);
+  } else if (funcType == 2) {
+    func = new ExponentialFunction(funcObj, dict);
+  } else if (funcType == 3) {
+    func = new StitchingFunction(funcObj, dict);
+  } else if (funcType == 4) {
+    func = new PostScriptFunction(funcObj, dict);
+  } else {
+    error(-1, "Unimplemented function type (%d)", funcType);
+    return NULL;
+  }
+  if (!func->isOk()) {
+    delete func;
+    return NULL;
+  }
+
+  return func;
+}
+
+GBool Function::init(Dict *dict) {
+  Object obj1, obj2;
+  int i;
+
+  //----- Domain
+  if (!dict->lookup("Domain", &obj1)->isArray()) {
+    error(-1, "Function is missing domain");
+    goto err2;
+  }
+  m = obj1.arrayGetLength() / 2;
+  if (m > funcMaxInputs) {
+    error(-1, "Functions with more than %d inputs are unsupported",
+	  funcMaxInputs);
+    goto err2;
+  }
+  for (i = 0; i < m; ++i) {
+    obj1.arrayGet(2*i, &obj2);
+    if (!obj2.isNum()) {
+      error(-1, "Illegal value in function domain array");
+      goto err1;
+    }
+    domain[i][0] = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(2*i+1, &obj2);
+    if (!obj2.isNum()) {
+      error(-1, "Illegal value in function domain array");
+      goto err1;
+    }
+    domain[i][1] = obj2.getNum();
+    obj2.free();
+  }
+  obj1.free();
+
+  //----- Range
+  hasRange = gFalse;
+  n = 0;
+  if (dict->lookup("Range", &obj1)->isArray()) {
+    hasRange = gTrue;
+    n = obj1.arrayGetLength() / 2;
+    if (n > funcMaxOutputs) {
+      error(-1, "Functions with more than %d outputs are unsupported",
+	    funcMaxOutputs);
+      goto err2;
+    }
+    for (i = 0; i < n; ++i) {
+      obj1.arrayGet(2*i, &obj2);
+      if (!obj2.isNum()) {
+	error(-1, "Illegal value in function range array");
+	goto err1;
+      }
+      range[i][0] = obj2.getNum();
+      obj2.free();
+      obj1.arrayGet(2*i+1, &obj2);
+      if (!obj2.isNum()) {
+	error(-1, "Illegal value in function range array");
+	goto err1;
+      }
+      range[i][1] = obj2.getNum();
+      obj2.free();
+    }
+  }
+  obj1.free();
+
+  return gTrue;
+
+ err1:
+  obj2.free();
+ err2:
+  obj1.free();
+  return gFalse;
+}
+
+//------------------------------------------------------------------------
+// IdentityFunction
+//------------------------------------------------------------------------
+
+IdentityFunction::IdentityFunction() {
+  int i;
+
+  // fill these in with arbitrary values just in case they get used
+  // somewhere
+  m = funcMaxInputs;
+  n = funcMaxOutputs;
+  for (i = 0; i < funcMaxInputs; ++i) {
+    domain[i][0] = 0;
+    domain[i][1] = 1;
+  }
+  hasRange = gFalse;
+}
+
+IdentityFunction::~IdentityFunction() {
+}
+
+void IdentityFunction::transform(double *in, double *out) {
+  int i;
+
+  for (i = 0; i < funcMaxOutputs; ++i) {
+    out[i] = in[i];
+  }
+}
+
+//------------------------------------------------------------------------
+// SampledFunction
+//------------------------------------------------------------------------
+
+SampledFunction::SampledFunction(Object *funcObj, Dict *dict) {
+  Stream *str;
+  int sampleBits;
+  double sampleMul;
+  Object obj1, obj2;
+  Guint buf, bitMask;
+  int bits;
+  Guint s;
+  int i;
+
+  samples = NULL;
+  sBuf = NULL;
+  ok = gFalse;
+
+  //----- initialize the generic stuff
+  if (!init(dict)) {
+    goto err1;
+  }
+  if (!hasRange) {
+    error(-1, "Type 0 function is missing range");
+    goto err1;
+  }
+  if (m > sampledFuncMaxInputs) {
+    error(-1, "Sampled functions with more than %d inputs are unsupported",
+	  sampledFuncMaxInputs);
+    goto err1;
+  }
+
+  //----- buffer
+  sBuf = (double *)gmallocn(1 << m, sizeof(double));
+
+  //----- get the stream
+  if (!funcObj->isStream()) {
+    error(-1, "Type 0 function isn't a stream");
+    goto err1;
+  }
+  str = funcObj->getStream();
+
+  //----- Size
+  if (!dict->lookup("Size", &obj1)->isArray() ||
+      obj1.arrayGetLength() != m) {
+    error(-1, "Function has missing or invalid size array");
+    goto err2;
+  }
+  for (i = 0; i < m; ++i) {
+    obj1.arrayGet(i, &obj2);
+    if (!obj2.isInt()) {
+      error(-1, "Illegal value in function size array");
+      goto err3;
+    }
+    sampleSize[i] = obj2.getInt();
+    obj2.free();
+  }
+  obj1.free();
+  idxMul[0] = n;
+  for (i = 1; i < m; ++i) {
+    idxMul[i] = idxMul[i-1] * sampleSize[i-1];
+  }
+
+  //----- BitsPerSample
+  if (!dict->lookup("BitsPerSample", &obj1)->isInt()) {
+    error(-1, "Function has missing or invalid BitsPerSample");
+    goto err2;
+  }
+  sampleBits = obj1.getInt();
+  sampleMul = 1.0 / (pow(2.0, (double)sampleBits) - 1);
+  obj1.free();
+
+  //----- Encode
+  if (dict->lookup("Encode", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 2*m) {
+    for (i = 0; i < m; ++i) {
+      obj1.arrayGet(2*i, &obj2);
+      if (!obj2.isNum()) {
+	error(-1, "Illegal value in function encode array");
+	goto err3;
+      }
+      encode[i][0] = obj2.getNum();
+      obj2.free();
+      obj1.arrayGet(2*i+1, &obj2);
+      if (!obj2.isNum()) {
+	error(-1, "Illegal value in function encode array");
+	goto err3;
+      }
+      encode[i][1] = obj2.getNum();
+      obj2.free();
+    }
+  } else {
+    for (i = 0; i < m; ++i) {
+      encode[i][0] = 0;
+      encode[i][1] = sampleSize[i] - 1;
+    }
+  }
+  obj1.free();
+  for (i = 0; i < m; ++i) {
+    inputMul[i] = (encode[i][1] - encode[i][0]) /
+                  (domain[i][1] - domain[i][0]);
+  }
+
+  //----- Decode
+  if (dict->lookup("Decode", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 2*n) {
+    for (i = 0; i < n; ++i) {
+      obj1.arrayGet(2*i, &obj2);
+      if (!obj2.isNum()) {
+	error(-1, "Illegal value in function decode array");
+	goto err3;
+      }
+      decode[i][0] = obj2.getNum();
+      obj2.free();
+      obj1.arrayGet(2*i+1, &obj2);
+      if (!obj2.isNum()) {
+	error(-1, "Illegal value in function decode array");
+	goto err3;
+      }
+      decode[i][1] = obj2.getNum();
+      obj2.free();
+    }
+  } else {
+    for (i = 0; i < n; ++i) {
+      decode[i][0] = range[i][0];
+      decode[i][1] = range[i][1];
+    }
+  }
+  obj1.free();
+
+  //----- samples
+  nSamples = n;
+  for (i = 0; i < m; ++i)
+    nSamples *= sampleSize[i];
+  samples = (double *)gmallocn(nSamples, sizeof(double));
+  buf = 0;
+  bits = 0;
+  bitMask = (1 << sampleBits) - 1;
+  str->reset();
+  for (i = 0; i < nSamples; ++i) {
+    if (sampleBits == 8) {
+      s = str->getChar();
+    } else if (sampleBits == 16) {
+      s = str->getChar();
+      s = (s << 8) + str->getChar();
+    } else if (sampleBits == 32) {
+      s = str->getChar();
+      s = (s << 8) + str->getChar();
+      s = (s << 8) + str->getChar();
+      s = (s << 8) + str->getChar();
+    } else {
+      while (bits < sampleBits) {
+	buf = (buf << 8) | (str->getChar() & 0xff);
+	bits += 8;
+      }
+      s = (buf >> (bits - sampleBits)) & bitMask;
+      bits -= sampleBits;
+    }
+    samples[i] = (double)s * sampleMul;
+  }
+  str->close();
+
+  ok = gTrue;
+  return;
+
+ err3:
+  obj2.free();
+ err2:
+  obj1.free();
+ err1:
+  return;
+}
+
+SampledFunction::~SampledFunction() {
+  if (samples) {
+    gfree(samples);
+  }
+  if (sBuf) {
+    gfree(sBuf);
+  }
+}
+
+SampledFunction::SampledFunction(SampledFunction *func) {
+  memcpy(this, func, sizeof(SampledFunction));
+  samples = (double *)gmallocn(nSamples, sizeof(double));
+  memcpy(samples, func->samples, nSamples * sizeof(double));
+  sBuf = (double *)gmallocn(1 << m, sizeof(double));
+}
+
+void SampledFunction::transform(double *in, double *out) {
+  double x;
+  int e[funcMaxInputs][2];
+  double efrac0[funcMaxInputs];
+  double efrac1[funcMaxInputs];
+  int i, j, k, idx, t;
+
+  // map input values into sample array
+  for (i = 0; i < m; ++i) {
+    x = (in[i] - domain[i][0]) * inputMul[i] + encode[i][0];
+    if (x < 0) {
+      x = 0;
+    } else if (x > sampleSize[i] - 1) {
+      x = sampleSize[i] - 1;
+    }
+    e[i][0] = (int)x;
+    if ((e[i][1] = e[i][0] + 1) >= sampleSize[i]) {
+      // this happens if in[i] = domain[i][1]
+      e[i][1] = e[i][0];
+    }
+    efrac1[i] = x - e[i][0];
+    efrac0[i] = 1 - efrac1[i];
+  }
+
+  // for each output, do m-linear interpolation
+  for (i = 0; i < n; ++i) {
+
+    // pull 2^m values out of the sample array
+    for (j = 0; j < (1<<m); ++j) {
+      idx = i;
+      for (k = 0, t = j; k < m; ++k, t >>= 1) {
+	idx += idxMul[k] * (e[k][t & 1]);
+      }
+      sBuf[j] = samples[idx];
+    }
+
+    // do m sets of interpolations
+    for (j = 0, t = (1<<m); j < m; ++j, t >>= 1) {
+      for (k = 0; k < t; k += 2) {
+	sBuf[k >> 1] = efrac0[j] * sBuf[k] + efrac1[j] * sBuf[k+1];
+      }
+    }
+
+    // map output value to range
+    out[i] = sBuf[0] * (decode[i][1] - decode[i][0]) + decode[i][0];
+    if (out[i] < range[i][0]) {
+      out[i] = range[i][0];
+    } else if (out[i] > range[i][1]) {
+      out[i] = range[i][1];
+    }
+  }
+}
+
+//------------------------------------------------------------------------
+// ExponentialFunction
+//------------------------------------------------------------------------
+
+ExponentialFunction::ExponentialFunction(Object *funcObj, Dict *dict) {
+  Object obj1, obj2;
+  int i;
+
+  ok = gFalse;
+
+  //----- initialize the generic stuff
+  if (!init(dict)) {
+    goto err1;
+  }
+  if (m != 1) {
+    error(-1, "Exponential function with more than one input");
+    goto err1;
+  }
+
+  //----- C0
+  if (dict->lookup("C0", &obj1)->isArray()) {
+    if (hasRange && obj1.arrayGetLength() != n) {
+      error(-1, "Function's C0 array is wrong length");
+      goto err2;
+    }
+    n = obj1.arrayGetLength();
+    for (i = 0; i < n; ++i) {
+      obj1.arrayGet(i, &obj2);
+      if (!obj2.isNum()) {
+	error(-1, "Illegal value in function C0 array");
+	goto err3;
+      }
+      c0[i] = obj2.getNum();
+      obj2.free();
+    }
+  } else {
+    if (hasRange && n != 1) {
+      error(-1, "Function's C0 array is wrong length");
+      goto err2;
+    }
+    n = 1;
+    c0[0] = 0;
+  }
+  obj1.free();
+
+  //----- C1
+  if (dict->lookup("C1", &obj1)->isArray()) {
+    if (obj1.arrayGetLength() != n) {
+      error(-1, "Function's C1 array is wrong length");
+      goto err2;
+    }
+    for (i = 0; i < n; ++i) {
+      obj1.arrayGet(i, &obj2);
+      if (!obj2.isNum()) {
+	error(-1, "Illegal value in function C1 array");
+	goto err3;
+      }
+      c1[i] = obj2.getNum();
+      obj2.free();
+    }
+  } else {
+    if (n != 1) {
+      error(-1, "Function's C1 array is wrong length");
+      goto err2;
+    }
+    c1[0] = 1;
+  }
+  obj1.free();
+
+  //----- N (exponent)
+  if (!dict->lookup("N", &obj1)->isNum()) {
+    error(-1, "Function has missing or invalid N");
+    goto err2;
+  }
+  e = obj1.getNum();
+  obj1.free();
+
+  ok = gTrue;
+  return;
+
+ err3:
+  obj2.free();
+ err2:
+  obj1.free();
+ err1:
+  return;
+}
+
+ExponentialFunction::~ExponentialFunction() {
+}
+
+ExponentialFunction::ExponentialFunction(ExponentialFunction *func) {
+  memcpy(this, func, sizeof(ExponentialFunction));
+}
+
+void ExponentialFunction::transform(double *in, double *out) {
+  double x;
+  int i;
+
+  if (in[0] < domain[0][0]) {
+    x = domain[0][0];
+  } else if (in[0] > domain[0][1]) {
+    x = domain[0][1];
+  } else {
+    x = in[0];
+  }
+  for (i = 0; i < n; ++i) {
+    out[i] = c0[i] + pow(x, e) * (c1[i] - c0[i]);
+    if (hasRange) {
+      if (out[i] < range[i][0]) {
+	out[i] = range[i][0];
+      } else if (out[i] > range[i][1]) {
+	out[i] = range[i][1];
+      }
+    }
+  }
+  return;
+}
+
+//------------------------------------------------------------------------
+// StitchingFunction
+//------------------------------------------------------------------------
+
+StitchingFunction::StitchingFunction(Object *funcObj, Dict *dict) {
+  Object obj1, obj2;
+  int i;
+
+  ok = gFalse;
+  funcs = NULL;
+  bounds = NULL;
+  encode = NULL;
+  scale = NULL;
+
+  //----- initialize the generic stuff
+  if (!init(dict)) {
+    goto err1;
+  }
+  if (m != 1) {
+    error(-1, "Stitching function with more than one input");
+    goto err1;
+  }
+
+  //----- Functions
+  if (!dict->lookup("Functions", &obj1)->isArray()) {
+    error(-1, "Missing 'Functions' entry in stitching function");
+    goto err1;
+  }
+  k = obj1.arrayGetLength();
+  funcs = (Function **)gmallocn(k, sizeof(Function *));
+  bounds = (double *)gmallocn(k + 1, sizeof(double));
+  encode = (double *)gmallocn(2 * k, sizeof(double));
+  scale = (double *)gmallocn(k, sizeof(double));
+  for (i = 0; i < k; ++i) {
+    funcs[i] = NULL;
+  }
+  for (i = 0; i < k; ++i) {
+    if (!(funcs[i] = Function::parse(obj1.arrayGet(i, &obj2)))) {
+      goto err2;
+    }
+    if (i > 0 && (funcs[i]->getInputSize() != 1 ||
+		  funcs[i]->getOutputSize() != funcs[0]->getOutputSize())) {
+      error(-1, "Incompatible subfunctions in stitching function");
+      goto err2;
+    }
+    obj2.free();
+  }
+  obj1.free();
+
+  //----- Bounds
+  if (!dict->lookup("Bounds", &obj1)->isArray() ||
+      obj1.arrayGetLength() != k - 1) {
+    error(-1, "Missing or invalid 'Bounds' entry in stitching function");
+    goto err1;
+  }
+  bounds[0] = domain[0][0];
+  for (i = 1; i < k; ++i) {
+    if (!obj1.arrayGet(i - 1, &obj2)->isNum()) {
+      error(-1, "Invalid type in 'Bounds' array in stitching function");
+      goto err2;
+    }
+    bounds[i] = obj2.getNum();
+    obj2.free();
+  }
+  bounds[k] = domain[0][1];
+  obj1.free();
+
+  //----- Encode
+  if (!dict->lookup("Encode", &obj1)->isArray() ||
+      obj1.arrayGetLength() != 2 * k) {
+    error(-1, "Missing or invalid 'Encode' entry in stitching function");
+    goto err1;
+  }
+  for (i = 0; i < 2 * k; ++i) {
+    if (!obj1.arrayGet(i, &obj2)->isNum()) {
+      error(-1, "Invalid type in 'Encode' array in stitching function");
+      goto err2;
+    }
+    encode[i] = obj2.getNum();
+    obj2.free();
+  }
+  obj1.free();
+
+  //----- pre-compute the scale factors
+  for (i = 0; i < k; ++i) {
+    if (bounds[i] == bounds[i+1]) {
+      // avoid a divide-by-zero -- in this situation, function i will
+      // never be used anyway
+      scale[i] = 0;
+    } else {
+      scale[i] = (encode[2*i+1] - encode[2*i]) / (bounds[i+1] - bounds[i]);
+    }
+  }
+
+  ok = gTrue;
+  return;
+
+ err2:
+  obj2.free();
+ err1:
+  obj1.free();
+}
+
+StitchingFunction::StitchingFunction(StitchingFunction *func) {
+  int i;
+
+  k = func->k;
+  funcs = (Function **)gmallocn(k, sizeof(Function *));
+  for (i = 0; i < k; ++i) {
+    funcs[i] = func->funcs[i]->copy();
+  }
+  bounds = (double *)gmallocn(k + 1, sizeof(double));
+  memcpy(bounds, func->bounds, (k + 1) * sizeof(double));
+  encode = (double *)gmallocn(2 * k, sizeof(double));
+  memcpy(encode, func->encode, 2 * k * sizeof(double));
+  scale = (double *)gmallocn(k, sizeof(double));
+  memcpy(scale, func->scale, k * sizeof(double));
+  ok = gTrue;
+}
+
+StitchingFunction::~StitchingFunction() {
+  int i;
+
+  if (funcs) {
+    for (i = 0; i < k; ++i) {
+      if (funcs[i]) {
+	delete funcs[i];
+      }
+    }
+  }
+  gfree(funcs);
+  gfree(bounds);
+  gfree(encode);
+  gfree(scale);
+}
+
+void StitchingFunction::transform(double *in, double *out) {
+  double x;
+  int i;
+
+  if (in[0] < domain[0][0]) {
+    x = domain[0][0];
+  } else if (in[0] > domain[0][1]) {
+    x = domain[0][1];
+  } else {
+    x = in[0];
+  }
+  for (i = 0; i < k - 1; ++i) {
+    if (x < bounds[i+1]) {
+      break;
+    }
+  }
+  x = encode[2*i] + (x - bounds[i]) * scale[i];
+  funcs[i]->transform(&x, out);
+}
+
+//------------------------------------------------------------------------
+// PostScriptFunction
+//------------------------------------------------------------------------
+
+enum PSOp {
+  psOpAbs,
+  psOpAdd,
+  psOpAnd,
+  psOpAtan,
+  psOpBitshift,
+  psOpCeiling,
+  psOpCopy,
+  psOpCos,
+  psOpCvi,
+  psOpCvr,
+  psOpDiv,
+  psOpDup,
+  psOpEq,
+  psOpExch,
+  psOpExp,
+  psOpFalse,
+  psOpFloor,
+  psOpGe,
+  psOpGt,
+  psOpIdiv,
+  psOpIndex,
+  psOpLe,
+  psOpLn,
+  psOpLog,
+  psOpLt,
+  psOpMod,
+  psOpMul,
+  psOpNe,
+  psOpNeg,
+  psOpNot,
+  psOpOr,
+  psOpPop,
+  psOpRoll,
+  psOpRound,
+  psOpSin,
+  psOpSqrt,
+  psOpSub,
+  psOpTrue,
+  psOpTruncate,
+  psOpXor,
+  psOpIf,
+  psOpIfelse,
+  psOpReturn
+};
+
+// Note: 'if' and 'ifelse' are parsed separately.
+// The rest are listed here in alphabetical order.
+// The index in this table is equivalent to the entry in PSOp.
+char *psOpNames[] = {
+  "abs",
+  "add",
+  "and",
+  "atan",
+  "bitshift",
+  "ceiling",
+  "copy",
+  "cos",
+  "cvi",
+  "cvr",
+  "div",
+  "dup",
+  "eq",
+  "exch",
+  "exp",
+  "false",
+  "floor",
+  "ge",
+  "gt",
+  "idiv",
+  "index",
+  "le",
+  "ln",
+  "log",
+  "lt",
+  "mod",
+  "mul",
+  "ne",
+  "neg",
+  "not",
+  "or",
+  "pop",
+  "roll",
+  "round",
+  "sin",
+  "sqrt",
+  "sub",
+  "true",
+  "truncate",
+  "xor"
+};
+
+#define nPSOps (sizeof(psOpNames) / sizeof(char *))
+
+enum PSObjectType {
+  psBool,
+  psInt,
+  psReal,
+  psOperator,
+  psBlock
+};
+
+// In the code array, 'if'/'ifelse' operators take up three slots
+// plus space for the code in the subclause(s).
+//
+//         +---------------------------------+
+//         | psOperator: psOpIf / psOpIfelse |
+//         +---------------------------------+
+//         | psBlock: ptr=<A>                |
+//         +---------------------------------+
+//         | psBlock: ptr=<B>                |
+//         +---------------------------------+
+//         | if clause                       |
+//         | ...                             |
+//         | psOperator: psOpReturn          |
+//         +---------------------------------+
+//     <A> | else clause                     |
+//         | ...                             |
+//         | psOperator: psOpReturn          |
+//         +---------------------------------+
+//     <B> | ...                             |
+//
+// For 'if', pointer <A> is present in the code stream but unused.
+
+struct PSObject {
+  PSObjectType type;
+  union {
+    GBool booln;		// boolean (stack only)
+    int intg;			// integer (stack and code)
+    double real;		// real (stack and code)
+    PSOp op;			// operator (code only)
+    int blk;			// if/ifelse block pointer (code only)
+  };
+};
+
+#define psStackSize 100
+
+class PSStack {
+public:
+
+  PSStack() { sp = psStackSize; }
+  void pushBool(GBool booln);
+  void pushInt(int intg);
+  void pushReal(double real);
+  GBool popBool();
+  int popInt();
+  double popNum();
+  GBool empty() { return sp == psStackSize; }
+  GBool topIsInt() { return sp < psStackSize && stack[sp].type == psInt; }
+  GBool topTwoAreInts()
+    { return sp < psStackSize - 1 &&
+	     stack[sp].type == psInt &&
+             stack[sp+1].type == psInt; }
+  GBool topIsReal() { return sp < psStackSize && stack[sp].type == psReal; }
+  GBool topTwoAreNums()
+    { return sp < psStackSize - 1 &&
+	     (stack[sp].type == psInt || stack[sp].type == psReal) &&
+	     (stack[sp+1].type == psInt || stack[sp+1].type == psReal); }
+  void copy(int n);
+  void roll(int n, int j);
+  void index(int i);
+  void pop();
+
+private:
+
+  GBool checkOverflow(int n = 1);
+  GBool checkUnderflow();
+  GBool checkType(PSObjectType t1, PSObjectType t2);
+
+  PSObject stack[psStackSize];
+  int sp;
+};
+
+GBool PSStack::checkOverflow(int n) {
+  if (sp - n < 0) {
+    error(-1, "Stack overflow in PostScript function");
+    return gFalse;
+  }
+  return gTrue;
+}
+
+GBool PSStack::checkUnderflow() {
+  if (sp == psStackSize) {
+    error(-1, "Stack underflow in PostScript function");
+    return gFalse;
+  }
+  return gTrue;
+}
+
+GBool PSStack::checkType(PSObjectType t1, PSObjectType t2) {
+  if (stack[sp].type != t1 && stack[sp].type != t2) {
+    error(-1, "Type mismatch in PostScript function");
+    return gFalse;
+  }
+  return gTrue;
+}
+
+void PSStack::pushBool(GBool booln) {
+  if (checkOverflow()) {
+    stack[--sp].type = psBool;
+    stack[sp].booln = booln;
+  }
+}
+
+void PSStack::pushInt(int intg) {
+  if (checkOverflow()) {
+    stack[--sp].type = psInt;
+    stack[sp].intg = intg;
+  }
+}
+
+void PSStack::pushReal(double real) {
+  if (checkOverflow()) {
+    stack[--sp].type = psReal;
+    stack[sp].real = real;
+  }
+}
+
+GBool PSStack::popBool() {
+  if (checkUnderflow() && checkType(psBool, psBool)) {
+    return stack[sp++].booln;
+  }
+  return gFalse;
+}
+
+int PSStack::popInt() {
+  if (checkUnderflow() && checkType(psInt, psInt)) {
+    return stack[sp++].intg;
+  }
+  return 0;
+}
+
+double PSStack::popNum() {
+  double ret;
+
+  if (checkUnderflow() && checkType(psInt, psReal)) {
+    ret = (stack[sp].type == psInt) ? (double)stack[sp].intg : stack[sp].real;
+    ++sp;
+    return ret;
+  }
+  return 0;
+}
+
+void PSStack::copy(int n) {
+  int i;
+
+  if (sp + n > psStackSize) {
+    error(-1, "Stack underflow in PostScript function");
+    return;
+  }
+  if (!checkOverflow(n)) {
+    return;
+  }
+  for (i = sp + n - 1; i >= sp; --i) {
+    stack[i - n] = stack[i];
+  }
+  sp -= n;
+}
+
+void PSStack::roll(int n, int j) {
+  PSObject obj;
+  int i, k;
+
+  if (j >= 0) {
+    j %= n;
+  } else {
+    j = -j % n;
+    if (j != 0) {
+      j = n - j;
+    }
+  }
+  if (n <= 0 || j == 0) {
+    return;
+  }
+  for (i = 0; i < j; ++i) {
+    obj = stack[sp];
+    for (k = sp; k < sp + n - 1; ++k) {
+      stack[k] = stack[k+1];
+    }
+    stack[sp + n - 1] = obj;
+  }
+}
+
+void PSStack::index(int i) {
+  if (!checkOverflow()) {
+    return;
+  }
+  --sp;
+  stack[sp] = stack[sp + 1 + i];
+}
+
+void PSStack::pop() {
+  if (!checkUnderflow()) {
+    return;
+  }
+  ++sp;
+}
+
+PostScriptFunction::PostScriptFunction(Object *funcObj, Dict *dict) {
+  Stream *str;
+  int codePtr;
+  GString *tok;
+
+  code = NULL;
+  codeSize = 0;
+  ok = gFalse;
+
+  //----- initialize the generic stuff
+  if (!init(dict)) {
+    goto err1;
+  }
+  if (!hasRange) {
+    error(-1, "Type 4 function is missing range");
+    goto err1;
+  }
+
+  //----- get the stream
+  if (!funcObj->isStream()) {
+    error(-1, "Type 4 function isn't a stream");
+    goto err1;
+  }
+  str = funcObj->getStream();
+
+  //----- parse the function
+  codeString = new GString();
+  str->reset();
+  if (!(tok = getToken(str)) || tok->cmp("{")) {
+    error(-1, "Expected '{' at start of PostScript function");
+    if (tok) {
+      delete tok;
+    }
+    goto err1;
+  }
+  delete tok;
+  codePtr = 0;
+  if (!parseCode(str, &codePtr)) {
+    goto err2;
+  }
+  str->close();
+
+  ok = gTrue;
+
+ err2:
+  str->close();
+ err1:
+  return;
+}
+
+PostScriptFunction::PostScriptFunction(PostScriptFunction *func) {
+  memcpy(this, func, sizeof(PostScriptFunction));
+  code = (PSObject *)gmallocn(codeSize, sizeof(PSObject));
+  memcpy(code, func->code, codeSize * sizeof(PSObject));
+  codeString = func->codeString->copy();
+}
+
+PostScriptFunction::~PostScriptFunction() {
+  gfree(code);
+  delete codeString;
+}
+
+void PostScriptFunction::transform(double *in, double *out) {
+  PSStack *stack;
+  int i;
+
+  stack = new PSStack();
+  for (i = 0; i < m; ++i) {
+    //~ may need to check for integers here
+    stack->pushReal(in[i]);
+  }
+  exec(stack, 0);
+  for (i = n - 1; i >= 0; --i) {
+    out[i] = stack->popNum();
+    if (out[i] < range[i][0]) {
+      out[i] = range[i][0];
+    } else if (out[i] > range[i][1]) {
+      out[i] = range[i][1];
+    }
+  }
+  // if (!stack->empty()) {
+  //   error(-1, "Extra values on stack at end of PostScript function");
+  // }
+  delete stack;
+}
+
+GBool PostScriptFunction::parseCode(Stream *str, int *codePtr) {
+  GString *tok;
+  char *p;
+  GBool isReal;
+  int opPtr, elsePtr;
+  int a, b, mid, cmp;
+
+  while (1) {
+    if (!(tok = getToken(str))) {
+      error(-1, "Unexpected end of PostScript function stream");
+      return gFalse;
+    }
+    p = tok->getCString();
+    if (isdigit(*p) || *p == '.' || *p == '-') {
+      isReal = gFalse;
+      for (++p; *p; ++p) {
+	if (*p == '.') {
+	  isReal = gTrue;
+	  break;
+	}
+      }
+      resizeCode(*codePtr);
+      if (isReal) {
+	code[*codePtr].type = psReal;
+	code[*codePtr].real = atof(tok->getCString());
+      } else {
+	code[*codePtr].type = psInt;
+	code[*codePtr].intg = atoi(tok->getCString());
+      }
+      ++*codePtr;
+      delete tok;
+    } else if (!tok->cmp("{")) {
+      delete tok;
+      opPtr = *codePtr;
+      *codePtr += 3;
+      resizeCode(opPtr + 2);
+      if (!parseCode(str, codePtr)) {
+	return gFalse;
+      }
+      if (!(tok = getToken(str))) {
+	error(-1, "Unexpected end of PostScript function stream");
+	return gFalse;
+      }
+      if (!tok->cmp("{")) {
+	elsePtr = *codePtr;
+	if (!parseCode(str, codePtr)) {
+	  return gFalse;
+	}
+	delete tok;
+	if (!(tok = getToken(str))) {
+	  error(-1, "Unexpected end of PostScript function stream");
+	  return gFalse;
+	}
+      } else {
+	elsePtr = -1;
+      }
+      if (!tok->cmp("if")) {
+	if (elsePtr >= 0) {
+	  error(-1, "Got 'if' operator with two blocks in PostScript function");
+	  return gFalse;
+	}
+	code[opPtr].type = psOperator;
+	code[opPtr].op = psOpIf;
+	code[opPtr+2].type = psBlock;
+	code[opPtr+2].blk = *codePtr;
+      } else if (!tok->cmp("ifelse")) {
+	if (elsePtr < 0) {
+	  error(-1, "Got 'ifelse' operator with one blocks in PostScript function");
+	  return gFalse;
+	}
+	code[opPtr].type = psOperator;
+	code[opPtr].op = psOpIfelse;
+	code[opPtr+1].type = psBlock;
+	code[opPtr+1].blk = elsePtr;
+	code[opPtr+2].type = psBlock;
+	code[opPtr+2].blk = *codePtr;
+      } else {
+	error(-1, "Expected if/ifelse operator in PostScript function");
+	delete tok;
+	return gFalse;
+      }
+      delete tok;
+    } else if (!tok->cmp("}")) {
+      delete tok;
+      resizeCode(*codePtr);
+      code[*codePtr].type = psOperator;
+      code[*codePtr].op = psOpReturn;
+      ++*codePtr;
+      break;
+    } else {
+      a = -1;
+      b = nPSOps;
+      // invariant: psOpNames[a] < tok < psOpNames[b]
+      while (b - a > 1) {
+	mid = (a + b) / 2;
+	cmp = tok->cmp(psOpNames[mid]);
+	if (cmp > 0) {
+	  a = mid;
+	} else if (cmp < 0) {
+	  b = mid;
+	} else {
+	  a = b = mid;
+	}
+      }
+      if (cmp != 0) {
+	error(-1, "Unknown operator '%s' in PostScript function",
+	      tok->getCString());
+	delete tok;
+	return gFalse;
+      }
+      delete tok;
+      resizeCode(*codePtr);
+      code[*codePtr].type = psOperator;
+      code[*codePtr].op = (PSOp)a;
+      ++*codePtr;
+    }
+  }
+  return gTrue;
+}
+
+GString *PostScriptFunction::getToken(Stream *str) {
+  GString *s;
+  int c;
+  GBool comment;
+
+  s = new GString();
+  comment = gFalse;
+  while (1) {
+    if ((c = str->getChar()) == EOF) {
+      break;
+    }
+    codeString->append(c);
+    if (comment) {
+      if (c == '\x0a' || c == '\x0d') {
+	comment = gFalse;
+      }
+    } else if (c == '%') {
+      comment = gTrue;
+    } else if (!isspace(c)) {
+      break;
+    }
+  }
+  if (c == '{' || c == '}') {
+    s->append((char)c);
+  } else if (isdigit(c) || c == '.' || c == '-') {
+    while (1) {
+      s->append((char)c);
+      c = str->lookChar();
+      if (c == EOF || !(isdigit(c) || c == '.' || c == '-')) {
+	break;
+      }
+      str->getChar();
+      codeString->append(c);
+    }
+  } else {
+    while (1) {
+      s->append((char)c);
+      c = str->lookChar();
+      if (c == EOF || !isalnum(c)) {
+	break;
+      }
+      str->getChar();
+      codeString->append(c);
+    }
+  }
+  return s;
+}
+
+void PostScriptFunction::resizeCode(int newSize) {
+  if (newSize >= codeSize) {
+    codeSize += 64;
+    code = (PSObject *)greallocn(code, codeSize, sizeof(PSObject));
+  }
+}
+
+void PostScriptFunction::exec(PSStack *stack, int codePtr) {
+  int i1, i2;
+  double r1, r2;
+  GBool b1, b2;
+
+  while (1) {
+    switch (code[codePtr].type) {
+    case psInt:
+      stack->pushInt(code[codePtr++].intg);
+      break;
+    case psReal:
+      stack->pushReal(code[codePtr++].real);
+      break;
+    case psOperator:
+      switch (code[codePtr++].op) {
+      case psOpAbs:
+	if (stack->topIsInt()) {
+	  stack->pushInt(abs(stack->popInt()));
+	} else {
+	  stack->pushReal(fabs(stack->popNum()));
+	}
+	break;
+      case psOpAdd:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  stack->pushInt(i1 + i2);
+	} else {
+	  r2 = stack->popNum();
+	  r1 = stack->popNum();
+	  stack->pushReal(r1 + r2);
+	}
+	break;
+      case psOpAnd:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  stack->pushInt(i1 & i2);
+	} else {
+	  b2 = stack->popBool();
+	  b1 = stack->popBool();
+	  stack->pushBool(b1 && b2);
+	}
+	break;
+      case psOpAtan:
+	r2 = stack->popNum();
+	r1 = stack->popNum();
+	stack->pushReal(atan2(r1, r2));
+	break;
+      case psOpBitshift:
+	i2 = stack->popInt();
+	i1 = stack->popInt();
+	if (i2 > 0) {
+	  stack->pushInt(i1 << i2);
+	} else if (i2 < 0) {
+	  stack->pushInt((int)((Guint)i1 >> i2));
+	} else {
+	  stack->pushInt(i1);
+	}
+	break;
+      case psOpCeiling:
+	if (!stack->topIsInt()) {
+	  stack->pushReal(ceil(stack->popNum()));
+	}
+	break;
+      case psOpCopy:
+	stack->copy(stack->popInt());
+	break;
+      case psOpCos:
+	stack->pushReal(cos(stack->popNum()));
+	break;
+      case psOpCvi:
+	if (!stack->topIsInt()) {
+	  stack->pushInt((int)stack->popNum());
+	}
+	break;
+      case psOpCvr:
+	if (!stack->topIsReal()) {
+	  stack->pushReal(stack->popNum());
+	}
+	break;
+      case psOpDiv:
+	r2 = stack->popNum();
+	r1 = stack->popNum();
+	stack->pushReal(r1 / r2);
+	break;
+      case psOpDup:
+	stack->copy(1);
+	break;
+      case psOpEq:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  stack->pushBool(i1 == i2);
+	} else if (stack->topTwoAreNums()) {
+	  r2 = stack->popNum();
+	  r1 = stack->popNum();
+	  stack->pushBool(r1 == r2);
+	} else {
+	  b2 = stack->popBool();
+	  b1 = stack->popBool();
+	  stack->pushBool(b1 == b2);
+	}
+	break;
+      case psOpExch:
+	stack->roll(2, 1);
+	break;
+      case psOpExp:
+	r2 = stack->popNum();
+	r1 = stack->popNum();
+	stack->pushReal(pow(r1, r2));
+	break;
+      case psOpFalse:
+	stack->pushBool(gFalse);
+	break;
+      case psOpFloor:
+	if (!stack->topIsInt()) {
+	  stack->pushReal(floor(stack->popNum()));
+	}
+	break;
+      case psOpGe:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  stack->pushBool(i1 >= i2);
+	} else {
+	  r2 = stack->popNum();
+	  r1 = stack->popNum();
+	  stack->pushBool(r1 >= r2);
+	}
+	break;
+      case psOpGt:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  stack->pushBool(i1 > i2);
+	} else {
+	  r2 = stack->popNum();
+	  r1 = stack->popNum();
+	  stack->pushBool(r1 > r2);
+	}
+	break;
+      case psOpIdiv:
+	i2 = stack->popInt();
+	i1 = stack->popInt();
+	stack->pushInt(i1 / i2);
+	break;
+      case psOpIndex:
+	stack->index(stack->popInt());
+	break;
+      case psOpLe:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  stack->pushBool(i1 <= i2);
+	} else {
+	  r2 = stack->popNum();
+	  r1 = stack->popNum();
+	  stack->pushBool(r1 <= r2);
+	}
+	break;
+      case psOpLn:
+	stack->pushReal(log(stack->popNum()));
+	break;
+      case psOpLog:
+	stack->pushReal(log10(stack->popNum()));
+	break;
+      case psOpLt:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  stack->pushBool(i1 < i2);
+	} else {
+	  r2 = stack->popNum();
+	  r1 = stack->popNum();
+	  stack->pushBool(r1 < r2);
+	}
+	break;
+      case psOpMod:
+	i2 = stack->popInt();
+	i1 = stack->popInt();
+	stack->pushInt(i1 % i2);
+	break;
+      case psOpMul:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  //~ should check for out-of-range, and push a real instead
+	  stack->pushInt(i1 * i2);
+	} else {
+	  r2 = stack->popNum();
+	  r1 = stack->popNum();
+	  stack->pushReal(r1 * r2);
+	}
+	break;
+      case psOpNe:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  stack->pushBool(i1 != i2);
+	} else if (stack->topTwoAreNums()) {
+	  r2 = stack->popNum();
+	  r1 = stack->popNum();
+	  stack->pushBool(r1 != r2);
+	} else {
+	  b2 = stack->popBool();
+	  b1 = stack->popBool();
+	  stack->pushBool(b1 != b2);
+	}
+	break;
+      case psOpNeg:
+	if (stack->topIsInt()) {
+	  stack->pushInt(-stack->popInt());
+	} else {
+	  stack->pushReal(-stack->popNum());
+	}
+	break;
+      case psOpNot:
+	if (stack->topIsInt()) {
+	  stack->pushInt(~stack->popInt());
+	} else {
+	  stack->pushBool(!stack->popBool());
+	}
+	break;
+      case psOpOr:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  stack->pushInt(i1 | i2);
+	} else {
+	  b2 = stack->popBool();
+	  b1 = stack->popBool();
+	  stack->pushBool(b1 || b2);
+	}
+	break;
+      case psOpPop:
+	stack->pop();
+	break;
+      case psOpRoll:
+	i2 = stack->popInt();
+	i1 = stack->popInt();
+	stack->roll(i1, i2);
+	break;
+      case psOpRound:
+	if (!stack->topIsInt()) {
+	  r1 = stack->popNum();
+	  stack->pushReal((r1 >= 0) ? floor(r1 + 0.5) : ceil(r1 - 0.5));
+	}
+	break;
+      case psOpSin:
+	stack->pushReal(sin(stack->popNum()));
+	break;
+      case psOpSqrt:
+	stack->pushReal(sqrt(stack->popNum()));
+	break;
+      case psOpSub:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  stack->pushInt(i1 - i2);
+	} else {
+	  r2 = stack->popNum();
+	  r1 = stack->popNum();
+	  stack->pushReal(r1 - r2);
+	}
+	break;
+      case psOpTrue:
+	stack->pushBool(gTrue);
+	break;
+      case psOpTruncate:
+	if (!stack->topIsInt()) {
+	  r1 = stack->popNum();
+	  stack->pushReal((r1 >= 0) ? floor(r1) : ceil(r1));
+	}
+	break;
+      case psOpXor:
+	if (stack->topTwoAreInts()) {
+	  i2 = stack->popInt();
+	  i1 = stack->popInt();
+	  stack->pushInt(i1 ^ i2);
+	} else {
+	  b2 = stack->popBool();
+	  b1 = stack->popBool();
+	  stack->pushBool(b1 ^ b2);
+	}
+	break;
+      case psOpIf:
+	b1 = stack->popBool();
+	if (b1) {
+	  exec(stack, codePtr + 2);
+	}
+	codePtr = code[codePtr + 1].blk;
+	break;
+      case psOpIfelse:
+	b1 = stack->popBool();
+	if (b1) {
+	  exec(stack, codePtr + 2);
+	} else {
+	  exec(stack, code[codePtr].blk);
+	}
+	codePtr = code[codePtr + 1].blk;
+	break;
+      case psOpReturn:
+	return;
+      }
+      break;
+    default:
+      error(-1, "Internal: bad object in PostScript function code");
+      break;
+    }
+  }
+}
diff --git a/xpdf/Function.h b/xpdf/Function.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Function.h
@@ -0,0 +1,229 @@
+//========================================================================
+//
+// Function.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef FUNCTION_H
+#define FUNCTION_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "Object.h"
+
+class Dict;
+class Stream;
+struct PSObject;
+class PSStack;
+
+//------------------------------------------------------------------------
+// Function
+//------------------------------------------------------------------------
+
+#define funcMaxInputs        32
+#define funcMaxOutputs       32
+#define sampledFuncMaxInputs 16
+
+class Function {
+public:
+
+  Function();
+
+  virtual ~Function();
+
+  // Construct a function.  Returns NULL if unsuccessful.
+  static Function *parse(Object *funcObj);
+
+  // Initialize the entries common to all function types.
+  GBool init(Dict *dict);
+
+  virtual Function *copy() = 0;
+
+  // Return the function type:
+  //   -1 : identity
+  //    0 : sampled
+  //    2 : exponential
+  //    3 : stitching
+  //    4 : PostScript
+  virtual int getType() = 0;
+
+  // Return size of input and output tuples.
+  int getInputSize() { return m; }
+  int getOutputSize() { return n; }
+
+  double getDomainMin(int i) { return domain[i][0]; }
+  double getDomainMax(int i) { return domain[i][1]; }
+  double getRangeMin(int i) { return range[i][0]; }
+  double getRangeMax(int i) { return range[i][1]; }
+  GBool getHasRange() { return hasRange; }
+
+  // Transform an input tuple into an output tuple.
+  virtual void transform(double *in, double *out) = 0;
+
+  virtual GBool isOk() = 0;
+
+protected:
+
+  int m, n;			// size of input and output tuples
+  double			// min and max values for function domain
+    domain[funcMaxInputs][2];
+  double			// min and max values for function range
+    range[funcMaxOutputs][2];
+  GBool hasRange;		// set if range is defined
+};
+
+//------------------------------------------------------------------------
+// IdentityFunction
+//------------------------------------------------------------------------
+
+class IdentityFunction: public Function {
+public:
+
+  IdentityFunction();
+  virtual ~IdentityFunction();
+  virtual Function *copy() { return new IdentityFunction(); }
+  virtual int getType() { return -1; }
+  virtual void transform(double *in, double *out);
+  virtual GBool isOk() { return gTrue; }
+
+private:
+};
+
+//------------------------------------------------------------------------
+// SampledFunction
+//------------------------------------------------------------------------
+
+class SampledFunction: public Function {
+public:
+
+  SampledFunction(Object *funcObj, Dict *dict);
+  virtual ~SampledFunction();
+  virtual Function *copy() { return new SampledFunction(this); }
+  virtual int getType() { return 0; }
+  virtual void transform(double *in, double *out);
+  virtual GBool isOk() { return ok; }
+
+  int getSampleSize(int i) { return sampleSize[i]; }
+  double getEncodeMin(int i) { return encode[i][0]; }
+  double getEncodeMax(int i) { return encode[i][1]; }
+  double getDecodeMin(int i) { return decode[i][0]; }
+  double getDecodeMax(int i) { return decode[i][1]; }
+  double *getSamples() { return samples; }
+
+private:
+
+  SampledFunction(SampledFunction *func);
+
+  int				// number of samples for each domain element
+    sampleSize[funcMaxInputs];
+  double			// min and max values for domain encoder
+    encode[funcMaxInputs][2];
+  double			// min and max values for range decoder
+    decode[funcMaxOutputs][2];
+  double			// input multipliers
+    inputMul[funcMaxInputs];
+  int idxMul[funcMaxInputs];	// sample array index multipliers
+  double *samples;		// the samples
+  int nSamples;			// size of the samples array
+  double *sBuf;			// buffer for the transform function
+  GBool ok;
+};
+
+//------------------------------------------------------------------------
+// ExponentialFunction
+//------------------------------------------------------------------------
+
+class ExponentialFunction: public Function {
+public:
+
+  ExponentialFunction(Object *funcObj, Dict *dict);
+  virtual ~ExponentialFunction();
+  virtual Function *copy() { return new ExponentialFunction(this); }
+  virtual int getType() { return 2; }
+  virtual void transform(double *in, double *out);
+  virtual GBool isOk() { return ok; }
+
+  double *getC0() { return c0; }
+  double *getC1() { return c1; }
+  double getE() { return e; }
+
+private:
+
+  ExponentialFunction(ExponentialFunction *func);
+
+  double c0[funcMaxOutputs];
+  double c1[funcMaxOutputs];
+  double e;
+  GBool ok;
+};
+
+//------------------------------------------------------------------------
+// StitchingFunction
+//------------------------------------------------------------------------
+
+class StitchingFunction: public Function {
+public:
+
+  StitchingFunction(Object *funcObj, Dict *dict);
+  virtual ~StitchingFunction();
+  virtual Function *copy() { return new StitchingFunction(this); }
+  virtual int getType() { return 3; }
+  virtual void transform(double *in, double *out);
+  virtual GBool isOk() { return ok; }
+
+  int getNumFuncs() { return k; }
+  Function *getFunc(int i) { return funcs[i]; }
+  double *getBounds() { return bounds; }
+  double *getEncode() { return encode; }
+  double *getScale() { return scale; }
+
+private:
+
+  StitchingFunction(StitchingFunction *func);
+
+  int k;
+  Function **funcs;
+  double *bounds;
+  double *encode;
+  double *scale;
+  GBool ok;
+};
+
+//------------------------------------------------------------------------
+// PostScriptFunction
+//------------------------------------------------------------------------
+
+class PostScriptFunction: public Function {
+public:
+
+  PostScriptFunction(Object *funcObj, Dict *dict);
+  virtual ~PostScriptFunction();
+  virtual Function *copy() { return new PostScriptFunction(this); }
+  virtual int getType() { return 4; }
+  virtual void transform(double *in, double *out);
+  virtual GBool isOk() { return ok; }
+
+  GString *getCodeString() { return codeString; }
+
+private:
+
+  PostScriptFunction(PostScriptFunction *func);
+  GBool parseCode(Stream *str, int *codePtr);
+  GString *getToken(Stream *str);
+  void resizeCode(int newSize);
+  void exec(PSStack *stack, int codePtr);
+
+  GString *codeString;
+  PSObject *code;
+  int codeSize;
+  GBool ok;
+};
+
+#endif
diff --git a/xpdf/GHash.cc b/xpdf/GHash.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/GHash.cc
@@ -0,0 +1,380 @@
+//========================================================================
+//
+// GHash.cc
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include "gmem.h"
+#include "GString.h"
+#include "GHash.h"
+
+//------------------------------------------------------------------------
+
+struct GHashBucket {
+  GString *key;
+  union {
+    void *p;
+    int i;
+  } val;
+  GHashBucket *next;
+};
+
+struct GHashIter {
+  int h;
+  GHashBucket *p;
+};
+
+//------------------------------------------------------------------------
+
+GHash::GHash(GBool deleteKeysA) {
+  int h;
+
+  deleteKeys = deleteKeysA;
+  size = 7;
+  tab = (GHashBucket **)gmallocn(size, sizeof(GHashBucket *));
+  for (h = 0; h < size; ++h) {
+    tab[h] = NULL;
+  }
+  len = 0;
+}
+
+GHash::~GHash() {
+  GHashBucket *p;
+  int h;
+
+  for (h = 0; h < size; ++h) {
+    while (tab[h]) {
+      p = tab[h];
+      tab[h] = p->next;
+      if (deleteKeys) {
+	delete p->key;
+      }
+      delete p;
+    }
+  }
+  gfree(tab);
+}
+
+void GHash::add(GString *key, void *val) {
+  GHashBucket *p;
+  int h;
+
+  // expand the table if necessary
+  if (len >= size) {
+    expand();
+  }
+
+  // add the new symbol
+  p = new GHashBucket;
+  p->key = key;
+  p->val.p = val;
+  h = hash(key);
+  p->next = tab[h];
+  tab[h] = p;
+  ++len;
+}
+
+void GHash::add(GString *key, int val) {
+  GHashBucket *p;
+  int h;
+
+  // expand the table if necessary
+  if (len >= size) {
+    expand();
+  }
+
+  // add the new symbol
+  p = new GHashBucket;
+  p->key = key;
+  p->val.i = val;
+  h = hash(key);
+  p->next = tab[h];
+  tab[h] = p;
+  ++len;
+}
+
+void GHash::replace(GString *key, void *val) {
+  GHashBucket *p;
+  int h;
+
+  if ((p = find(key, &h))) {
+    p->val.p = val;
+    delete key;
+  } else {
+    add(key, val);
+  }
+}
+
+void GHash::replace(GString *key, int val) {
+  GHashBucket *p;
+  int h;
+
+  if ((p = find(key, &h))) {
+    p->val.i = val;
+    delete key;
+  } else {
+    add(key, val);
+  }
+}
+
+void *GHash::lookup(GString *key) {
+  GHashBucket *p;
+  int h;
+
+  if (!(p = find(key, &h))) {
+    return NULL;
+  }
+  return p->val.p;
+}
+
+int GHash::lookupInt(GString *key) {
+  GHashBucket *p;
+  int h;
+
+  if (!(p = find(key, &h))) {
+    return 0;
+  }
+  return p->val.i;
+}
+
+void *GHash::lookup(char *key) {
+  GHashBucket *p;
+  int h;
+
+  if (!(p = find(key, &h))) {
+    return NULL;
+  }
+  return p->val.p;
+}
+
+int GHash::lookupInt(char *key) {
+  GHashBucket *p;
+  int h;
+
+  if (!(p = find(key, &h))) {
+    return 0;
+  }
+  return p->val.i;
+}
+
+void *GHash::remove(GString *key) {
+  GHashBucket *p;
+  GHashBucket **q;
+  void *val;
+  int h;
+
+  if (!(p = find(key, &h))) {
+    return NULL;
+  }
+  q = &tab[h];
+  while (*q != p) {
+    q = &((*q)->next);
+  }
+  *q = p->next;
+  if (deleteKeys) {
+    delete p->key;
+  }
+  val = p->val.p;
+  delete p;
+  --len;
+  return val;
+}
+
+int GHash::removeInt(GString *key) {
+  GHashBucket *p;
+  GHashBucket **q;
+  int val;
+  int h;
+
+  if (!(p = find(key, &h))) {
+    return 0;
+  }
+  q = &tab[h];
+  while (*q != p) {
+    q = &((*q)->next);
+  }
+  *q = p->next;
+  if (deleteKeys) {
+    delete p->key;
+  }
+  val = p->val.i;
+  delete p;
+  --len;
+  return val;
+}
+
+void *GHash::remove(char *key) {
+  GHashBucket *p;
+  GHashBucket **q;
+  void *val;
+  int h;
+
+  if (!(p = find(key, &h))) {
+    return NULL;
+  }
+  q = &tab[h];
+  while (*q != p) {
+    q = &((*q)->next);
+  }
+  *q = p->next;
+  if (deleteKeys) {
+    delete p->key;
+  }
+  val = p->val.p;
+  delete p;
+  --len;
+  return val;
+}
+
+int GHash::removeInt(char *key) {
+  GHashBucket *p;
+  GHashBucket **q;
+  int val;
+  int h;
+
+  if (!(p = find(key, &h))) {
+    return 0;
+  }
+  q = &tab[h];
+  while (*q != p) {
+    q = &((*q)->next);
+  }
+  *q = p->next;
+  if (deleteKeys) {
+    delete p->key;
+  }
+  val = p->val.i;
+  delete p;
+  --len;
+  return val;
+}
+
+void GHash::startIter(GHashIter **iter) {
+  *iter = new GHashIter;
+  (*iter)->h = -1;
+  (*iter)->p = NULL;
+}
+
+GBool GHash::getNext(GHashIter **iter, GString **key, void **val) {
+  if (!*iter) {
+    return gFalse;
+  }
+  if ((*iter)->p) {
+    (*iter)->p = (*iter)->p->next;
+  }
+  while (!(*iter)->p) {
+    if (++(*iter)->h == size) {
+      delete *iter;
+      *iter = NULL;
+      return gFalse;
+    }
+    (*iter)->p = tab[(*iter)->h];
+  }
+  *key = (*iter)->p->key;
+  *val = (*iter)->p->val.p;
+  return gTrue;
+}
+
+GBool GHash::getNext(GHashIter **iter, GString **key, int *val) {
+  if (!*iter) {
+    return gFalse;
+  }
+  if ((*iter)->p) {
+    (*iter)->p = (*iter)->p->next;
+  }
+  while (!(*iter)->p) {
+    if (++(*iter)->h == size) {
+      delete *iter;
+      *iter = NULL;
+      return gFalse;
+    }
+    (*iter)->p = tab[(*iter)->h];
+  }
+  *key = (*iter)->p->key;
+  *val = (*iter)->p->val.i;
+  return gTrue;
+}
+
+void GHash::killIter(GHashIter **iter) {
+  delete *iter;
+  *iter = NULL;
+}
+
+void GHash::expand() {
+  GHashBucket **oldTab;
+  GHashBucket *p;
+  int oldSize, h, i;
+
+  oldSize = size;
+  oldTab = tab;
+  size = 2*size + 1;
+  tab = (GHashBucket **)gmallocn(size, sizeof(GHashBucket *));
+  for (h = 0; h < size; ++h) {
+    tab[h] = NULL;
+  }
+  for (i = 0; i < oldSize; ++i) {
+    while (oldTab[i]) {
+      p = oldTab[i];
+      oldTab[i] = oldTab[i]->next;
+      h = hash(p->key);
+      p->next = tab[h];
+      tab[h] = p;
+    }
+  }
+  gfree(oldTab);
+}
+
+GHashBucket *GHash::find(GString *key, int *h) {
+  GHashBucket *p;
+
+  *h = hash(key);
+  for (p = tab[*h]; p; p = p->next) {
+    if (!p->key->cmp(key)) {
+      return p;
+    }
+  }
+  return NULL;
+}
+
+GHashBucket *GHash::find(char *key, int *h) {
+  GHashBucket *p;
+
+  *h = hash(key);
+  for (p = tab[*h]; p; p = p->next) {
+    if (!p->key->cmp(key)) {
+      return p;
+    }
+  }
+  return NULL;
+}
+
+int GHash::hash(GString *key) {
+  char *p;
+  unsigned int h;
+  int i;
+
+  h = 0;
+  for (p = key->getCString(), i = 0; i < key->getLength(); ++p, ++i) {
+    h = 17 * h + (int)(*p & 0xff);
+  }
+  return (int)(h % size);
+}
+
+int GHash::hash(char *key) {
+  char *p;
+  unsigned int h;
+
+  h = 0;
+  for (p = key; *p; ++p) {
+    h = 17 * h + (int)(*p & 0xff);
+  }
+  return (int)(h % size);
+}
diff --git a/xpdf/GHash.h b/xpdf/GHash.h
new file mode 100644
--- /dev/null
+++ b/xpdf/GHash.h
@@ -0,0 +1,78 @@
+//========================================================================
+//
+// GHash.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef GHASH_H
+#define GHASH_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+
+class GString;
+struct GHashBucket;
+struct GHashIter;
+
+//------------------------------------------------------------------------
+
+class GHash {
+public:
+
+  GHash(GBool deleteKeysA = gFalse);
+  ~GHash();
+  void add(GString *key, void *val);
+  void add(GString *key, int val);
+  void replace(GString *key, void *val);
+  void replace(GString *key, int val);
+  void *lookup(GString *key);
+  int lookupInt(GString *key);
+  void *lookup(char *key);
+  int lookupInt(char *key);
+  void *remove(GString *key);
+  int removeInt(GString *key);
+  void *remove(char *key);
+  int removeInt(char *key);
+  int getLength() { return len; }
+  void startIter(GHashIter **iter);
+  GBool getNext(GHashIter **iter, GString **key, void **val);
+  GBool getNext(GHashIter **iter, GString **key, int *val);
+  void killIter(GHashIter **iter);
+
+private:
+
+  void expand();
+  GHashBucket *find(GString *key, int *h);
+  GHashBucket *find(char *key, int *h);
+  int hash(GString *key);
+  int hash(char *key);
+
+  GBool deleteKeys;		// set if key strings should be deleted
+  int size;			// number of buckets
+  int len;			// number of entries
+  GHashBucket **tab;
+};
+
+#define deleteGHash(hash, T)                       \
+  do {                                             \
+    GHash *_hash = (hash);                         \
+    {                                              \
+      GHashIter *_iter;                            \
+      GString *_key;                               \
+      void *_p;                                    \
+      _hash->startIter(&_iter);                    \
+      while (_hash->getNext(&_iter, &_key, &_p)) { \
+        delete (T*)_p;                             \
+      }                                            \
+      delete _hash;                                \
+    }                                              \
+  } while(0)
+
+#endif
diff --git a/xpdf/GList.cc b/xpdf/GList.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/GList.cc
@@ -0,0 +1,97 @@
+//========================================================================
+//
+// GList.cc
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "gmem.h"
+#include "GList.h"
+
+//------------------------------------------------------------------------
+// GList
+//------------------------------------------------------------------------
+
+GList::GList() {
+  size = 8;
+  data = (void **)gmallocn(size, sizeof(void*));
+  length = 0;
+  inc = 0;
+}
+
+GList::GList(int sizeA) {
+  size = sizeA;
+  data = (void **)gmallocn(size, sizeof(void*));
+  length = 0;
+  inc = 0;
+}
+
+GList::~GList() {
+  gfree(data);
+}
+
+void GList::append(void *p) {
+  if (length >= size) {
+    expand();
+  }
+  data[length++] = p;
+}
+
+void GList::append(GList *list) {
+  int i;
+
+  while (length + list->length > size) {
+    expand();
+  }
+  for (i = 0; i < list->length; ++i) {
+    data[length++] = list->data[i];
+  }
+}
+
+void GList::insert(int i, void *p) {
+  if (length >= size) {
+    expand();
+  }
+  if (i < length) {
+    memmove(data+i+1, data+i, (length - i) * sizeof(void *));
+  }
+  data[i] = p;
+  ++length;
+}
+
+void *GList::del(int i) {
+  void *p;
+
+  p = data[i];
+  if (i < length - 1) {
+    memmove(data+i, data+i+1, (length - i - 1) * sizeof(void *));
+  }
+  --length;
+  if (size - length >= ((inc > 0) ? inc : size/2)) {
+    shrink();
+  }
+  return p;
+}
+
+void GList::sort(int (*cmp)(const void *obj1, const void *obj2)) {
+  qsort(data, length, sizeof(void *), cmp);
+}
+
+void GList::expand() {
+  size += (inc > 0) ? inc : size;
+  data = (void **)greallocn(data, size, sizeof(void*));
+}
+
+void GList::shrink() {
+  size -= (inc > 0) ? inc : size/2;
+  data = (void **)greallocn(data, size, sizeof(void*));
+}
diff --git a/xpdf/GList.h b/xpdf/GList.h
new file mode 100644
--- /dev/null
+++ b/xpdf/GList.h
@@ -0,0 +1,96 @@
+//========================================================================
+//
+// GList.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef GLIST_H
+#define GLIST_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+
+//------------------------------------------------------------------------
+// GList
+//------------------------------------------------------------------------
+
+class GList {
+public:
+
+  // Create an empty list.
+  GList();
+
+  // Create an empty list with space for <size1> elements.
+  GList(int sizeA);
+
+  // Destructor - does not free pointed-to objects.
+  ~GList();
+
+  //----- general
+
+  // Get the number of elements.
+  int getLength() { return length; }
+
+  //----- ordered list support
+
+  // Return the <i>th element.
+  // Assumes 0 <= i < length.
+  void *get(int i) { return data[i]; }
+
+  // Append an element to the end of the list.
+  void append(void *p);
+
+  // Append another list to the end of this one.
+  void append(GList *list);
+
+  // Insert an element at index <i>.
+  // Assumes 0 <= i <= length.
+  void insert(int i, void *p);
+
+  // Deletes and returns the element at index <i>.
+  // Assumes 0 <= i < length.
+  void *del(int i);
+
+  // Sort the list accoring to the given comparison function.
+  // NB: this sorts an array of pointers, so the pointer args need to
+  // be double-dereferenced.
+  void sort(int (*cmp)(const void *ptr1, const void *ptr2));
+
+  //----- control
+
+  // Set allocation increment to <inc>.  If inc > 0, that many
+  // elements will be allocated every time the list is expanded.
+  // If inc <= 0, the list will be doubled in size.
+  void setAllocIncr(int incA) { inc = incA; }
+
+private:
+
+  void expand();
+  void shrink();
+
+  void **data;			// the list elements
+  int size;			// size of data array
+  int length;			// number of elements on list
+  int inc;			// allocation increment
+};
+
+#define deleteGList(list, T)                        \
+  do {                                              \
+    GList *_list = (list);                          \
+    {                                               \
+      int _i;                                       \
+      for (_i = 0; _i < _list->getLength(); ++_i) { \
+        delete (T*)_list->get(_i);                  \
+      }                                             \
+      delete _list;                                 \
+    }                                               \
+  } while (0)
+
+#endif
diff --git a/xpdf/GMutex.h b/xpdf/GMutex.h
new file mode 100644
--- /dev/null
+++ b/xpdf/GMutex.h
@@ -0,0 +1,49 @@
+//========================================================================
+//
+// GMutex.h
+//
+// Portable mutex macros.
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef GMUTEX_H
+#define GMUTEX_H
+
+// Usage:
+//
+// GMutex m;
+// gInitMutex(&m);
+// ...
+// gLockMutex(&m);
+//   ... critical section ...
+// gUnlockMutex(&m);
+// ...
+// gDestroyMutex(&m);
+
+#ifdef WIN32
+
+#include <windows.h>
+
+typedef CRITICAL_SECTION GMutex;
+
+#define gInitMutex(m) InitializeCriticalSection(m)
+#define gDestroyMutex(m) DeleteCriticalSection(m)
+#define gLockMutex(m) EnterCriticalSection(m)
+#define gUnlockMutex(m) LeaveCriticalSection(m)
+
+#else // assume pthreads
+
+#include <pthread.h>
+
+typedef pthread_mutex_t GMutex;
+
+#define gInitMutex(m) pthread_mutex_init(m, NULL)
+#define gDestroyMutex(m) pthread_mutex_destroy(m)
+#define gLockMutex(m) pthread_mutex_lock(m)
+#define gUnlockMutex(m) pthread_mutex_unlock(m)
+
+#endif
+
+#endif
diff --git a/xpdf/GString.cc b/xpdf/GString.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/GString.cc
@@ -0,0 +1,718 @@
+//========================================================================
+//
+// GString.cc
+//
+// Simple variable-length string type.
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+#include "gmem.h"
+#include "GString.h"
+
+//------------------------------------------------------------------------
+
+union GStringFormatArg {
+  int i;
+  Guint ui;
+  long l;
+  Gulong ul;
+  double f;
+  char c;
+  char *s;
+  GString *gs;
+};
+
+enum GStringFormatType {
+  fmtIntDecimal,
+  fmtIntHex,
+  fmtIntOctal,
+  fmtIntBinary,
+  fmtUIntDecimal,
+  fmtUIntHex,
+  fmtUIntOctal,
+  fmtUIntBinary,
+  fmtLongDecimal,
+  fmtLongHex,
+  fmtLongOctal,
+  fmtLongBinary,
+  fmtULongDecimal,
+  fmtULongHex,
+  fmtULongOctal,
+  fmtULongBinary,
+  fmtDouble,
+  fmtDoubleTrim,
+  fmtChar,
+  fmtString,
+  fmtGString,
+  fmtSpace
+};
+
+static char *formatStrings[] = {
+  "d", "x", "o", "b", "ud", "ux", "uo", "ub",
+  "ld", "lx", "lo", "lb", "uld", "ulx", "ulo", "ulb",
+  "f", "g",
+  "c",
+  "s",
+  "t",
+  "w",
+  NULL
+};
+
+//------------------------------------------------------------------------
+
+static inline int size(int len) {
+  int delta;
+  for (delta = 8; delta < len && delta < 0x100000; delta <<= 1) ;
+  // this is ((len + 1) + (delta - 1)) & ~(delta - 1)
+  return (len + delta) & ~(delta - 1);
+}
+
+inline void GString::resize(int length1) {
+  char *s1;
+
+  if (!s) {
+    s = new char[size(length1)];
+  } else if (size(length1) != size(length)) {
+    s1 = new char[size(length1)];
+    if (length1 < length) {
+      memcpy(s1, s, length1);
+      s1[length1] = '\0';
+    } else {
+      memcpy(s1, s, length + 1);
+    }
+    delete[] s;
+    s = s1;
+  }
+}
+
+GString::GString() {
+  s = NULL;
+  resize(length = 0);
+  s[0] = '\0';
+}
+
+GString::GString(const char *sA) {
+  int n = strlen(sA);
+
+  s = NULL;
+  resize(length = n);
+  memcpy(s, sA, n + 1);
+}
+
+GString::GString(const char *sA, int lengthA) {
+  s = NULL;
+  resize(length = lengthA);
+  memcpy(s, sA, length * sizeof(char));
+  s[length] = '\0';
+}
+
+GString::GString(GString *str, int idx, int lengthA) {
+  s = NULL;
+  resize(length = lengthA);
+  memcpy(s, str->getCString() + idx, length);
+  s[length] = '\0';
+}
+
+GString::GString(GString *str) {
+  s = NULL;
+  resize(length = str->getLength());
+  memcpy(s, str->getCString(), length + 1);
+}
+
+GString::GString(GString *str1, GString *str2) {
+  int n1 = str1->getLength();
+  int n2 = str2->getLength();
+
+  s = NULL;
+  resize(length = n1 + n2);
+  memcpy(s, str1->getCString(), n1);
+  memcpy(s + n1, str2->getCString(), n2 + 1);
+}
+
+GString *GString::fromInt(int x) {
+  char buf[24]; // enough space for 64-bit ints plus a little extra
+  char *p;
+  int len;
+
+  formatInt(x, buf, sizeof(buf), gFalse, 0, 10, &p, &len);
+  return new GString(p, len);
+}
+
+GString *GString::format(char *fmt, ...) {
+  va_list argList;
+  GString *s;
+
+  s = new GString();
+  va_start(argList, fmt);
+  s->appendfv(fmt, argList);
+  va_end(argList);
+  return s;
+}
+
+GString *GString::formatv(char *fmt, va_list argList) {
+  GString *s;
+
+  s = new GString();
+  s->appendfv(fmt, argList);
+  return s;
+}
+
+GString::~GString() {
+  delete[] s;
+}
+
+GString *GString::clear() {
+  s[length = 0] = '\0';
+  resize(0);
+  return this;
+}
+
+GString *GString::append(char c) {
+  resize(length + 1);
+  s[length++] = c;
+  s[length] = '\0';
+  return this;
+}
+
+GString *GString::append(GString *str) {
+  int n = str->getLength();
+
+  resize(length + n);
+  memcpy(s + length, str->getCString(), n + 1);
+  length += n;
+  return this;
+}
+
+GString *GString::append(const char *str) {
+  int n = strlen(str);
+
+  resize(length + n);
+  memcpy(s + length, str, n + 1);
+  length += n;
+  return this;
+}
+
+GString *GString::append(const char *str, int lengthA) {
+  resize(length + lengthA);
+  memcpy(s + length, str, lengthA);
+  length += lengthA;
+  s[length] = '\0';
+  return this;
+}
+
+GString *GString::appendf(char *fmt, ...) {
+  va_list argList;
+
+  va_start(argList, fmt);
+  appendfv(fmt, argList);
+  va_end(argList);
+  return this;
+}
+
+GString *GString::appendfv(char *fmt, va_list argList) {
+  GStringFormatArg *args;
+  int argsLen, argsSize;
+  GStringFormatArg arg;
+  int idx, width, prec;
+  GBool reverseAlign, zeroFill;
+  GStringFormatType ft;
+  char buf[65];
+  int len, i;
+  char *p0, *p1, *str;
+
+  argsLen = 0;
+  argsSize = 8;
+  args = (GStringFormatArg *)gmallocn(argsSize, sizeof(GStringFormatArg));
+
+  p0 = fmt;
+  while (*p0) {
+    if (*p0 == '{') {
+      ++p0;
+      if (*p0 == '{') {
+	++p0;
+	append('{');
+      } else {
+
+	// parse the format string
+	if (!(*p0 >= '0' && *p0 <= '9')) {
+	  break;
+	}
+	idx = *p0 - '0';
+	for (++p0; *p0 >= '0' && *p0 <= '9'; ++p0) {
+	  idx = 10 * idx + (*p0 - '0');
+	}
+	if (*p0 != ':') {
+	  break;
+	}
+	++p0;
+	if (*p0 == '-') {
+	  reverseAlign = gTrue;
+	  ++p0;
+	} else {
+	  reverseAlign = gFalse;
+	}
+	width = 0;
+	zeroFill = *p0 == '0';
+	for (; *p0 >= '0' && *p0 <= '9'; ++p0) {
+	  width = 10 * width + (*p0 - '0');
+	}
+	if (*p0 == '.') {
+	  ++p0;
+	  prec = 0;
+	  for (; *p0 >= '0' && *p0 <= '9'; ++p0) {
+	    prec = 10 * prec + (*p0 - '0');
+	  }
+	} else {
+	  prec = 0;
+	}
+	for (ft = (GStringFormatType)0;
+	     formatStrings[ft];
+	     ft = (GStringFormatType)(ft + 1)) {
+	  if (!strncmp(p0, formatStrings[ft], strlen(formatStrings[ft]))) {
+	    break;
+	  }
+	}
+	if (!formatStrings[ft]) {
+	  break;
+	}
+	p0 += strlen(formatStrings[ft]);
+	if (*p0 != '}') {
+	  break;
+	}
+	++p0;
+
+	// fetch the argument
+	if (idx > argsLen) {
+	  break;
+	}
+	if (idx == argsLen) {
+	  if (argsLen == argsSize) {
+	    argsSize *= 2;
+	    args = (GStringFormatArg *)greallocn(args, argsSize,
+						 sizeof(GStringFormatArg));
+	  }
+	  switch (ft) {
+	  case fmtIntDecimal:
+	  case fmtIntHex:
+	  case fmtIntOctal:
+	  case fmtIntBinary:
+	  case fmtSpace:
+	    args[argsLen].i = va_arg(argList, int);
+	    break;
+	  case fmtUIntDecimal:
+	  case fmtUIntHex:
+	  case fmtUIntOctal:
+	  case fmtUIntBinary:
+	    args[argsLen].ui = va_arg(argList, Guint);
+	    break;
+	  case fmtLongDecimal:
+	  case fmtLongHex:
+	  case fmtLongOctal:
+	  case fmtLongBinary:
+	    args[argsLen].l = va_arg(argList, long);
+	    break;
+	  case fmtULongDecimal:
+	  case fmtULongHex:
+	  case fmtULongOctal:
+	  case fmtULongBinary:
+	    args[argsLen].ul = va_arg(argList, Gulong);
+	    break;
+	  case fmtDouble:
+	  case fmtDoubleTrim:
+	    args[argsLen].f = va_arg(argList, double);
+	    break;
+	  case fmtChar:
+	    args[argsLen].c = (char)va_arg(argList, int);
+	    break;
+	  case fmtString:
+	    args[argsLen].s = va_arg(argList, char *);
+	    break;
+	  case fmtGString:
+	    args[argsLen].gs = va_arg(argList, GString *);
+	    break;
+	  }
+	  ++argsLen;
+	}
+
+	// format the argument
+	arg = args[idx];
+	switch (ft) {
+	case fmtIntDecimal:
+	  formatInt(arg.i, buf, sizeof(buf), zeroFill, width, 10, &str, &len);
+	  break;
+	case fmtIntHex:
+	  formatInt(arg.i, buf, sizeof(buf), zeroFill, width, 16, &str, &len);
+	  break;
+	case fmtIntOctal:
+	  formatInt(arg.i, buf, sizeof(buf), zeroFill, width, 8, &str, &len);
+	  break;
+	case fmtIntBinary:
+	  formatInt(arg.i, buf, sizeof(buf), zeroFill, width, 2, &str, &len);
+	  break;
+	case fmtUIntDecimal:
+	  formatUInt(arg.ui, buf, sizeof(buf), zeroFill, width, 10,
+		     &str, &len);
+	  break;
+	case fmtUIntHex:
+	  formatUInt(arg.ui, buf, sizeof(buf), zeroFill, width, 16,
+		     &str, &len);
+	  break;
+	case fmtUIntOctal:
+	  formatUInt(arg.ui, buf, sizeof(buf), zeroFill, width, 8, &str, &len);
+	  break;
+	case fmtUIntBinary:
+	  formatUInt(arg.ui, buf, sizeof(buf), zeroFill, width, 2, &str, &len);
+	  break;
+	case fmtLongDecimal:
+	  formatInt(arg.l, buf, sizeof(buf), zeroFill, width, 10, &str, &len);
+	  break;
+	case fmtLongHex:
+	  formatInt(arg.l, buf, sizeof(buf), zeroFill, width, 16, &str, &len);
+	  break;
+	case fmtLongOctal:
+	  formatInt(arg.l, buf, sizeof(buf), zeroFill, width, 8, &str, &len);
+	  break;
+	case fmtLongBinary:
+	  formatInt(arg.l, buf, sizeof(buf), zeroFill, width, 2, &str, &len);
+	  break;
+	case fmtULongDecimal:
+	  formatUInt(arg.ul, buf, sizeof(buf), zeroFill, width, 10,
+		     &str, &len);
+	  break;
+	case fmtULongHex:
+	  formatUInt(arg.ul, buf, sizeof(buf), zeroFill, width, 16,
+		     &str, &len);
+	  break;
+	case fmtULongOctal:
+	  formatUInt(arg.ul, buf, sizeof(buf), zeroFill, width, 8, &str, &len);
+	  break;
+	case fmtULongBinary:
+	  formatUInt(arg.ul, buf, sizeof(buf), zeroFill, width, 2, &str, &len);
+	  break;
+	case fmtDouble:
+	  formatDouble(arg.f, buf, sizeof(buf), prec, gFalse, &str, &len);
+	  break;
+	case fmtDoubleTrim:
+	  formatDouble(arg.f, buf, sizeof(buf), prec, gTrue, &str, &len);
+	  break;
+	case fmtChar:
+	  buf[0] = arg.c;
+	  str = buf;
+	  len = 1;
+	  reverseAlign = !reverseAlign;
+	  break;
+	case fmtString:
+	  str = arg.s;
+	  len = strlen(str);
+	  reverseAlign = !reverseAlign;
+	  break;
+	case fmtGString:
+	  str = arg.gs->getCString();
+	  len = arg.gs->getLength();
+	  reverseAlign = !reverseAlign;
+	  break;
+	case fmtSpace:
+	  str = buf;
+	  len = 0;
+	  width = arg.i;
+	  break;
+	}
+
+	// append the formatted arg, handling width and alignment
+	if (!reverseAlign && len < width) {
+	  for (i = len; i < width; ++i) {
+	    append(' ');
+	  }
+	}
+	append(str, len);
+	if (reverseAlign && len < width) {
+	  for (i = len; i < width; ++i) {
+	    append(' ');
+	  }
+	}
+      }
+
+    } else if (*p0 == '}') {
+      ++p0;
+      if (*p0 == '}') {
+	++p0;
+      }
+      append('}');
+      
+    } else {
+      for (p1 = p0 + 1; *p1 && *p1 != '{' && *p1 != '}'; ++p1) ;
+      append(p0, p1 - p0);
+      p0 = p1;
+    }
+  }
+
+  gfree(args);
+  return this;
+}
+
+void GString::formatInt(long x, char *buf, int bufSize,
+			GBool zeroFill, int width, int base,
+			char **p, int *len) {
+  static char vals[17] = "0123456789abcdef";
+  GBool neg;
+  int start, i, j;
+
+  i = bufSize;
+  if ((neg = x < 0)) {
+    x = -x;
+  }
+  start = neg ? 1 : 0;
+  if (x == 0) {
+    buf[--i] = '0';
+  } else {
+    while (i > start && x) {
+      buf[--i] = vals[x % base];
+      x /= base;
+    }
+  }
+  if (zeroFill) {
+    for (j = bufSize - i; i > start && j < width - start; ++j) {
+      buf[--i] = '0';
+    }
+  }
+  if (neg) {
+    buf[--i] = '-';
+  }
+  *p = buf + i;
+  *len = bufSize - i;
+}
+
+void GString::formatUInt(Gulong x, char *buf, int bufSize,
+			 GBool zeroFill, int width, int base,
+			 char **p, int *len) {
+  static char vals[17] = "0123456789abcdef";
+  int i, j;
+
+  i = bufSize;
+  if (x == 0) {
+    buf[--i] = '0';
+  } else {
+    while (i > 0 && x) {
+      buf[--i] = vals[x % base];
+      x /= base;
+    }
+  }
+  if (zeroFill) {
+    for (j = bufSize - i; i > 0 && j < width; ++j) {
+      buf[--i] = '0';
+    }
+  }
+  *p = buf + i;
+  *len = bufSize - i;
+}
+
+void GString::formatDouble(double x, char *buf, int bufSize, int prec,
+			   GBool trim, char **p, int *len) {
+  GBool neg, started;
+  double x2;
+  int d, i, j;
+
+  if ((neg = x < 0)) {
+    x = -x;
+  }
+  x = floor(x * pow(10, prec) + 0.5);
+  i = bufSize;
+  started = !trim;
+  for (j = 0; j < prec && i > 1; ++j) {
+    x2 = floor(0.1 * (x + 0.5));
+    d = (int)floor(x - 10 * x2 + 0.5);
+    if (started || d != 0) {
+      buf[--i] = '0' + d;
+      started = gTrue;
+    }
+    x = x2;
+  }
+  if (i > 1 && started) {
+    buf[--i] = '.';
+  }
+  if (i > 1) {
+    do {
+      x2 = floor(0.1 * (x + 0.5));
+      d = (int)floor(x - 10 * x2 + 0.5);
+      buf[--i] = '0' + d;
+      x = x2;
+    } while (i > 1 && x);
+  }
+  if (neg) {
+    buf[--i] = '-';
+  }
+  *p = buf + i;
+  *len = bufSize - i;
+}
+
+GString *GString::insert(int i, char c) {
+  int j;
+
+  resize(length + 1);
+  for (j = length + 1; j > i; --j)
+    s[j] = s[j-1];
+  s[i] = c;
+  ++length;
+  return this;
+}
+
+GString *GString::insert(int i, GString *str) {
+  int n = str->getLength();
+  int j;
+
+  resize(length + n);
+  for (j = length; j >= i; --j)
+    s[j+n] = s[j];
+  memcpy(s+i, str->getCString(), n);
+  length += n;
+  return this;
+}
+
+GString *GString::insert(int i, const char *str) {
+  int n = strlen(str);
+  int j;
+
+  resize(length + n);
+  for (j = length; j >= i; --j)
+    s[j+n] = s[j];
+  memcpy(s+i, str, n);
+  length += n;
+  return this;
+}
+
+GString *GString::insert(int i, const char *str, int lengthA) {
+  int j;
+
+  resize(length + lengthA);
+  for (j = length; j >= i; --j)
+    s[j+lengthA] = s[j];
+  memcpy(s+i, str, lengthA);
+  length += lengthA;
+  return this;
+}
+
+GString *GString::del(int i, int n) {
+  int j;
+
+  if (n > 0) {
+    if (i + n > length) {
+      n = length - i;
+    }
+    for (j = i; j <= length - n; ++j) {
+      s[j] = s[j + n];
+    }
+    resize(length -= n);
+  }
+  return this;
+}
+
+GString *GString::upperCase() {
+  int i;
+
+  for (i = 0; i < length; ++i) {
+    if (islower(s[i]))
+      s[i] = toupper(s[i]);
+  }
+  return this;
+}
+
+GString *GString::lowerCase() {
+  int i;
+
+  for (i = 0; i < length; ++i) {
+    if (isupper(s[i]))
+      s[i] = tolower(s[i]);
+  }
+  return this;
+}
+
+int GString::cmp(GString *str) {
+  int n1, n2, i, x;
+  char *p1, *p2;
+
+  n1 = length;
+  n2 = str->length;
+  for (i = 0, p1 = s, p2 = str->s; i < n1 && i < n2; ++i, ++p1, ++p2) {
+    x = *p1 - *p2;
+    if (x != 0) {
+      return x;
+    }
+  }
+  return n1 - n2;
+}
+
+int GString::cmpN(GString *str, int n) {
+  int n1, n2, i, x;
+  char *p1, *p2;
+
+  n1 = length;
+  n2 = str->length;
+  for (i = 0, p1 = s, p2 = str->s;
+       i < n1 && i < n2 && i < n;
+       ++i, ++p1, ++p2) {
+    x = *p1 - *p2;
+    if (x != 0) {
+      return x;
+    }
+  }
+  if (i == n) {
+    return 0;
+  }
+  return n1 - n2;
+}
+
+int GString::cmp(const char *sA) {
+  int n1, i, x;
+  const char *p1, *p2;
+
+  n1 = length;
+  for (i = 0, p1 = s, p2 = sA; i < n1 && *p2; ++i, ++p1, ++p2) {
+    x = *p1 - *p2;
+    if (x != 0) {
+      return x;
+    }
+  }
+  if (i < n1) {
+    return 1;
+  }
+  if (*p2) {
+    return -1;
+  }
+  return 0;
+}
+
+int GString::cmpN(const char *sA, int n) {
+  int n1, i, x;
+  const char *p1, *p2;
+
+  n1 = length;
+  for (i = 0, p1 = s, p2 = sA; i < n1 && *p2 && i < n; ++i, ++p1, ++p2) {
+    x = *p1 - *p2;
+    if (x != 0) {
+      return x;
+    }
+  }
+  if (i == n) {
+    return 0;
+  }
+  if (i < n1) {
+    return 1;
+  }
+  if (*p2) {
+    return -1;
+  }
+  return 0;
+}
diff --git a/xpdf/GString.h b/xpdf/GString.h
new file mode 100644
--- /dev/null
+++ b/xpdf/GString.h
@@ -0,0 +1,136 @@
+//========================================================================
+//
+// GString.h
+//
+// Simple variable-length string type.
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef GSTRING_H
+#define GSTRING_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <stdarg.h>
+#include "gtypes.h"
+
+class GString {
+public:
+
+  // Create an empty string.
+  GString();
+
+  // Create a string from a C string.
+  GString(const char *sA);
+
+  // Create a string from <lengthA> chars at <sA>.  This string
+  // can contain null characters.
+  GString(const char *sA, int lengthA);
+
+  // Create a string from <lengthA> chars at <idx> in <str>.
+  GString(GString *str, int idx, int lengthA);
+
+  // Copy a string.
+  GString(GString *str);
+  GString *copy() { return new GString(this); }
+
+  // Concatenate two strings.
+  GString(GString *str1, GString *str2);
+
+  // Convert an integer to a string.
+  static GString *fromInt(int x);
+
+  // Create a formatted string.  Similar to printf, but without the
+  // string overflow issues.  Formatting elements consist of:
+  //     {<arg>:[<width>][.<precision>]<type>}
+  // where:
+  // - <arg> is the argument number (arg 0 is the first argument
+  //   following the format string) -- NB: args must be first used in
+  //   order; they can be reused in any order
+  // - <width> is the field width -- negative to reverse the alignment;
+  //   starting with a leading zero to zero-fill (for integers)
+  // - <precision> is the number of digits to the right of the decimal
+  //   point (for floating point numbers)
+  // - <type> is one of:
+  //     d, x, o, b -- int in decimal, hex, octal, binary
+  //     ud, ux, uo, ub -- unsigned int
+  //     ld, lx, lo, lb, uld, ulx, ulo, ulb -- long, unsigned long
+  //     f, g -- double
+  //     c -- char
+  //     s -- string (char *)
+  //     t -- GString *
+  //     w -- blank space; arg determines width
+  // To get literal curly braces, use {{ or }}.
+  static GString *format(char *fmt, ...);
+  static GString *formatv(char *fmt, va_list argList);
+
+  // Destructor.
+  ~GString();
+
+  // Get length.
+  int getLength() { return length; }
+
+  // Get C string.
+  char *getCString() { return s; }
+
+  // Get <i>th character.
+  char getChar(int i) { return s[i]; }
+
+  // Change <i>th character.
+  void setChar(int i, char c) { s[i] = c; }
+
+  // Clear string to zero length.
+  GString *clear();
+
+  // Append a character or string.
+  GString *append(char c);
+  GString *append(GString *str);
+  GString *append(const char *str);
+  GString *append(const char *str, int lengthA);
+
+  // Append a formatted string.
+  GString *appendf(char *fmt, ...);
+  GString *appendfv(char *fmt, va_list argList);
+
+  // Insert a character or string.
+  GString *insert(int i, char c);
+  GString *insert(int i, GString *str);
+  GString *insert(int i, const char *str);
+  GString *insert(int i, const char *str, int lengthA);
+
+  // Delete a character or range of characters.
+  GString *del(int i, int n = 1);
+
+  // Convert string to all-upper/all-lower case.
+  GString *upperCase();
+  GString *lowerCase();
+
+  // Compare two strings:  -1:<  0:=  +1:>
+  int cmp(GString *str);
+  int cmpN(GString *str, int n);
+  int cmp(const char *sA);
+  int cmpN(const char *sA, int n);
+
+private:
+
+  int length;
+  char *s;
+
+  void resize(int length1);
+  static void formatInt(long x, char *buf, int bufSize,
+			GBool zeroFill, int width, int base,
+			char **p, int *len);
+  static void formatUInt(Gulong x, char *buf, int bufSize,
+			 GBool zeroFill, int width, int base,
+			 char **p, int *len);
+  static void formatDouble(double x, char *buf, int bufSize, int prec,
+			   GBool trim, char **p, int *len);
+};
+
+#endif
diff --git a/xpdf/Gfx.cc b/xpdf/Gfx.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Gfx.cc
@@ -0,0 +1,4181 @@
+//========================================================================
+//
+// Gfx.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <math.h>
+#include "gmem.h"
+#include "GlobalParams.h"
+#include "CharTypes.h"
+#include "Object.h"
+#include "Array.h"
+#include "Dict.h"
+#include "Stream.h"
+#include "Lexer.h"
+#include "Parser.h"
+#include "GfxFont.h"
+#include "GfxState.h"
+#include "OutputDev.h"
+#include "Page.h"
+#include "Annot.h"
+#include "Error.h"
+#include "Gfx.h"
+
+// the MSVC math.h doesn't define this
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+//------------------------------------------------------------------------
+// constants
+//------------------------------------------------------------------------
+
+// Max recursive depth for a function shading fill.
+#define functionMaxDepth 6
+
+// Max delta allowed in any color component for a function shading fill.
+#define functionColorDelta (dblToCol(1 / 256.0))
+
+// Max number of splits along the t axis for an axial shading fill.
+#define axialMaxSplits 256
+
+// Max delta allowed in any color component for an axial shading fill.
+#define axialColorDelta (dblToCol(1 / 256.0))
+
+// Max number of splits along the t axis for a radial shading fill.
+#define radialMaxSplits 256
+
+// Max delta allowed in any color component for a radial shading fill.
+#define radialColorDelta (dblToCol(1 / 256.0))
+
+// Max recursive depth for a Gouraud triangle shading fill.
+#define gouraudMaxDepth 6
+
+// Max delta allowed in any color component for a Gouraud triangle
+// shading fill.
+#define gouraudColorDelta (dblToCol(1 / 256.0))
+
+// Max recursive depth for a patch mesh shading fill.
+#define patchMaxDepth 6
+
+// Max delta allowed in any color component for a patch mesh shading
+// fill.
+#define patchColorDelta (dblToCol(1 / 256.0))
+
+//------------------------------------------------------------------------
+// Operator table
+//------------------------------------------------------------------------
+
+#ifdef WIN32 // this works around a bug in the VC7 compiler
+#  pragma optimize("",off)
+#endif
+
+Operator Gfx::opTab[] = {
+  {"\"",  3, {tchkNum,    tchkNum,    tchkString},
+          &Gfx::opMoveSetShowText},
+  {"'",   1, {tchkString},
+          &Gfx::opMoveShowText},
+  {"B",   0, {tchkNone},
+          &Gfx::opFillStroke},
+  {"B*",  0, {tchkNone},
+          &Gfx::opEOFillStroke},
+  {"BDC", 2, {tchkName,   tchkProps},
+          &Gfx::opBeginMarkedContent},
+  {"BI",  0, {tchkNone},
+          &Gfx::opBeginImage},
+  {"BMC", 1, {tchkName},
+          &Gfx::opBeginMarkedContent},
+  {"BT",  0, {tchkNone},
+          &Gfx::opBeginText},
+  {"BX",  0, {tchkNone},
+          &Gfx::opBeginIgnoreUndef},
+  {"CS",  1, {tchkName},
+          &Gfx::opSetStrokeColorSpace},
+  {"DP",  2, {tchkName,   tchkProps},
+          &Gfx::opMarkPoint},
+  {"Do",  1, {tchkName},
+          &Gfx::opXObject},
+  {"EI",  0, {tchkNone},
+          &Gfx::opEndImage},
+  {"EMC", 0, {tchkNone},
+          &Gfx::opEndMarkedContent},
+  {"ET",  0, {tchkNone},
+          &Gfx::opEndText},
+  {"EX",  0, {tchkNone},
+          &Gfx::opEndIgnoreUndef},
+  {"F",   0, {tchkNone},
+          &Gfx::opFill},
+  {"G",   1, {tchkNum},
+          &Gfx::opSetStrokeGray},
+  {"ID",  0, {tchkNone},
+          &Gfx::opImageData},
+  {"J",   1, {tchkInt},
+          &Gfx::opSetLineCap},
+  {"K",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
+          &Gfx::opSetStrokeCMYKColor},
+  {"M",   1, {tchkNum},
+          &Gfx::opSetMiterLimit},
+  {"MP",  1, {tchkName},
+          &Gfx::opMarkPoint},
+  {"Q",   0, {tchkNone},
+          &Gfx::opRestore},
+  {"RG",  3, {tchkNum,    tchkNum,    tchkNum},
+          &Gfx::opSetStrokeRGBColor},
+  {"S",   0, {tchkNone},
+          &Gfx::opStroke},
+  {"SC",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
+          &Gfx::opSetStrokeColor},
+  {"SCN", -33, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN},
+          &Gfx::opSetStrokeColorN},
+  {"T*",  0, {tchkNone},
+          &Gfx::opTextNextLine},
+  {"TD",  2, {tchkNum,    tchkNum},
+          &Gfx::opTextMoveSet},
+  {"TJ",  1, {tchkArray},
+          &Gfx::opShowSpaceText},
+  {"TL",  1, {tchkNum},
+          &Gfx::opSetTextLeading},
+  {"Tc",  1, {tchkNum},
+          &Gfx::opSetCharSpacing},
+  {"Td",  2, {tchkNum,    tchkNum},
+          &Gfx::opTextMove},
+  {"Tf",  2, {tchkName,   tchkNum},
+          &Gfx::opSetFont},
+  {"Tj",  1, {tchkString},
+          &Gfx::opShowText},
+  {"Tm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
+	      tchkNum,    tchkNum},
+          &Gfx::opSetTextMatrix},
+  {"Tr",  1, {tchkInt},
+          &Gfx::opSetTextRender},
+  {"Ts",  1, {tchkNum},
+          &Gfx::opSetTextRise},
+  {"Tw",  1, {tchkNum},
+          &Gfx::opSetWordSpacing},
+  {"Tz",  1, {tchkNum},
+          &Gfx::opSetHorizScaling},
+  {"W",   0, {tchkNone},
+          &Gfx::opClip},
+  {"W*",  0, {tchkNone},
+          &Gfx::opEOClip},
+  {"b",   0, {tchkNone},
+          &Gfx::opCloseFillStroke},
+  {"b*",  0, {tchkNone},
+          &Gfx::opCloseEOFillStroke},
+  {"c",   6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
+	      tchkNum,    tchkNum},
+          &Gfx::opCurveTo},
+  {"cm",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
+	      tchkNum,    tchkNum},
+          &Gfx::opConcat},
+  {"cs",  1, {tchkName},
+          &Gfx::opSetFillColorSpace},
+  {"d",   2, {tchkArray,  tchkNum},
+          &Gfx::opSetDash},
+  {"d0",  2, {tchkNum,    tchkNum},
+          &Gfx::opSetCharWidth},
+  {"d1",  6, {tchkNum,    tchkNum,    tchkNum,    tchkNum,
+	      tchkNum,    tchkNum},
+          &Gfx::opSetCacheDevice},
+  {"f",   0, {tchkNone},
+          &Gfx::opFill},
+  {"f*",  0, {tchkNone},
+          &Gfx::opEOFill},
+  {"g",   1, {tchkNum},
+          &Gfx::opSetFillGray},
+  {"gs",  1, {tchkName},
+          &Gfx::opSetExtGState},
+  {"h",   0, {tchkNone},
+          &Gfx::opClosePath},
+  {"i",   1, {tchkNum},
+          &Gfx::opSetFlat},
+  {"j",   1, {tchkInt},
+          &Gfx::opSetLineJoin},
+  {"k",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
+          &Gfx::opSetFillCMYKColor},
+  {"l",   2, {tchkNum,    tchkNum},
+          &Gfx::opLineTo},
+  {"m",   2, {tchkNum,    tchkNum},
+          &Gfx::opMoveTo},
+  {"n",   0, {tchkNone},
+          &Gfx::opEndPath},
+  {"q",   0, {tchkNone},
+          &Gfx::opSave},
+  {"re",  4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
+          &Gfx::opRectangle},
+  {"rg",  3, {tchkNum,    tchkNum,    tchkNum},
+          &Gfx::opSetFillRGBColor},
+  {"ri",  1, {tchkName},
+          &Gfx::opSetRenderingIntent},
+  {"s",   0, {tchkNone},
+          &Gfx::opCloseStroke},
+  {"sc",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
+          &Gfx::opSetFillColor},
+  {"scn", -33, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
+	        tchkSCN},
+          &Gfx::opSetFillColorN},
+  {"sh",  1, {tchkName},
+          &Gfx::opShFill},
+  {"v",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
+          &Gfx::opCurveTo1},
+  {"w",   1, {tchkNum},
+          &Gfx::opSetLineWidth},
+  {"y",   4, {tchkNum,    tchkNum,    tchkNum,    tchkNum},
+          &Gfx::opCurveTo2},
+};
+
+#ifdef WIN32 // this works around a bug in the VC7 compiler
+#  pragma optimize("",on)
+#endif
+
+#define numOps (sizeof(opTab) / sizeof(Operator))
+
+//------------------------------------------------------------------------
+// GfxResources
+//------------------------------------------------------------------------
+
+GfxResources::GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA) {
+  Object obj1, obj2;
+  Ref r;
+
+  if (resDict) {
+
+    // build font dictionary
+    fonts = NULL;
+    resDict->lookupNF("Font", &obj1);
+    if (obj1.isRef()) {
+      obj1.fetch(xref, &obj2);
+      if (obj2.isDict()) {
+	r = obj1.getRef();
+	fonts = new GfxFontDict(xref, &r, obj2.getDict());
+      }
+      obj2.free();
+    } else if (obj1.isDict()) {
+      fonts = new GfxFontDict(xref, NULL, obj1.getDict());
+    }
+    obj1.free();
+
+    // get XObject dictionary
+    resDict->lookup("XObject", &xObjDict);
+
+    // get color space dictionary
+    resDict->lookup("ColorSpace", &colorSpaceDict);
+
+    // get pattern dictionary
+    resDict->lookup("Pattern", &patternDict);
+
+    // get shading dictionary
+    resDict->lookup("Shading", &shadingDict);
+
+    // get graphics state parameter dictionary
+    resDict->lookup("ExtGState", &gStateDict);
+
+  } else {
+    fonts = NULL;
+    xObjDict.initNull();
+    colorSpaceDict.initNull();
+    patternDict.initNull();
+    shadingDict.initNull();
+    gStateDict.initNull();
+  }
+
+  next = nextA;
+}
+
+GfxResources::~GfxResources() {
+  if (fonts) {
+    delete fonts;
+  }
+  xObjDict.free();
+  colorSpaceDict.free();
+  patternDict.free();
+  shadingDict.free();
+  gStateDict.free();
+}
+
+GfxFont *GfxResources::lookupFont(char *name) {
+  GfxFont *font;
+  GfxResources *resPtr;
+
+  for (resPtr = this; resPtr; resPtr = resPtr->next) {
+    if (resPtr->fonts) {
+      if ((font = resPtr->fonts->lookup(name)))
+	return font;
+    }
+  }
+  error(-1, "Unknown font tag '%s'", name);
+  return NULL;
+}
+
+GBool GfxResources::lookupXObject(char *name, Object *obj) {
+  GfxResources *resPtr;
+
+  for (resPtr = this; resPtr; resPtr = resPtr->next) {
+    if (resPtr->xObjDict.isDict()) {
+      if (!resPtr->xObjDict.dictLookup(name, obj)->isNull())
+	return gTrue;
+      obj->free();
+    }
+  }
+  error(-1, "XObject '%s' is unknown", name);
+  return gFalse;
+}
+
+GBool GfxResources::lookupXObjectNF(char *name, Object *obj) {
+  GfxResources *resPtr;
+
+  for (resPtr = this; resPtr; resPtr = resPtr->next) {
+    if (resPtr->xObjDict.isDict()) {
+      if (!resPtr->xObjDict.dictLookupNF(name, obj)->isNull())
+	return gTrue;
+      obj->free();
+    }
+  }
+  error(-1, "XObject '%s' is unknown", name);
+  return gFalse;
+}
+
+void GfxResources::lookupColorSpace(char *name, Object *obj) {
+  GfxResources *resPtr;
+
+  for (resPtr = this; resPtr; resPtr = resPtr->next) {
+    if (resPtr->colorSpaceDict.isDict()) {
+      if (!resPtr->colorSpaceDict.dictLookup(name, obj)->isNull()) {
+	return;
+      }
+      obj->free();
+    }
+  }
+  obj->initNull();
+}
+
+GfxPattern *GfxResources::lookupPattern(char *name) {
+  GfxResources *resPtr;
+  GfxPattern *pattern;
+  Object obj;
+
+  for (resPtr = this; resPtr; resPtr = resPtr->next) {
+    if (resPtr->patternDict.isDict()) {
+      if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) {
+	pattern = GfxPattern::parse(&obj);
+	obj.free();
+	return pattern;
+      }
+      obj.free();
+    }
+  }
+  error(-1, "Unknown pattern '%s'", name);
+  return NULL;
+}
+
+GfxShading *GfxResources::lookupShading(char *name) {
+  GfxResources *resPtr;
+  GfxShading *shading;
+  Object obj;
+
+  for (resPtr = this; resPtr; resPtr = resPtr->next) {
+    if (resPtr->shadingDict.isDict()) {
+      if (!resPtr->shadingDict.dictLookup(name, &obj)->isNull()) {
+	shading = GfxShading::parse(&obj);
+	obj.free();
+	return shading;
+      }
+      obj.free();
+    }
+  }
+  error(-1, "Unknown shading '%s'", name);
+  return NULL;
+}
+
+GBool GfxResources::lookupGState(char *name, Object *obj) {
+  GfxResources *resPtr;
+
+  for (resPtr = this; resPtr; resPtr = resPtr->next) {
+    if (resPtr->gStateDict.isDict()) {
+      if (!resPtr->gStateDict.dictLookup(name, obj)->isNull()) {
+	return gTrue;
+      }
+      obj->free();
+    }
+  }
+  error(-1, "ExtGState '%s' is unknown", name);
+  return gFalse;
+}
+
+//------------------------------------------------------------------------
+// Gfx
+//------------------------------------------------------------------------
+
+Gfx::Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict,
+	 double hDPI, double vDPI, PDFRectangle *box,
+	 PDFRectangle *cropBox, int rotate,
+	 GBool (*abortCheckCbkA)(void *data),
+	 void *abortCheckCbkDataA) {
+  int i;
+
+  xref = xrefA;
+  subPage = gFalse;
+  printCommands = globalParams->getPrintCommands();
+
+  // start the resource stack
+  res = new GfxResources(xref, resDict, NULL);
+
+  // initialize
+  out = outA;
+  state = new GfxState(hDPI, vDPI, box, rotate, out->upsideDown());
+  fontChanged = gFalse;
+  clip = clipNone;
+  ignoreUndef = 0;
+  out->startPage(pageNum, state);
+  out->setDefaultCTM(state->getCTM());
+  out->updateAll(state);
+  for (i = 0; i < 6; ++i) {
+    baseMatrix[i] = state->getCTM()[i];
+  }
+  formDepth = 0;
+  abortCheckCbk = abortCheckCbkA;
+  abortCheckCbkData = abortCheckCbkDataA;
+
+  // set crop box
+  if (cropBox) {
+    state->moveTo(cropBox->x1, cropBox->y1);
+    state->lineTo(cropBox->x2, cropBox->y1);
+    state->lineTo(cropBox->x2, cropBox->y2);
+    state->lineTo(cropBox->x1, cropBox->y2);
+    state->closePath();
+    state->clip();
+    out->clip(state);
+    state->clearPath();
+  }
+}
+
+Gfx::Gfx(XRef *xrefA, OutputDev *outA, Dict *resDict,
+	 PDFRectangle *box, PDFRectangle *cropBox,
+	 GBool (*abortCheckCbkA)(void *data),
+	 void *abortCheckCbkDataA) {
+  int i;
+
+  xref = xrefA;
+  subPage = gTrue;
+  printCommands = globalParams->getPrintCommands();
+
+  // start the resource stack
+  res = new GfxResources(xref, resDict, NULL);
+
+  // initialize
+  out = outA;
+  state = new GfxState(72, 72, box, 0, gFalse);
+  fontChanged = gFalse;
+  clip = clipNone;
+  ignoreUndef = 0;
+  for (i = 0; i < 6; ++i) {
+    baseMatrix[i] = state->getCTM()[i];
+  }
+  formDepth = 0;
+  abortCheckCbk = abortCheckCbkA;
+  abortCheckCbkData = abortCheckCbkDataA;
+
+  // set crop box
+  if (cropBox) {
+    state->moveTo(cropBox->x1, cropBox->y1);
+    state->lineTo(cropBox->x2, cropBox->y1);
+    state->lineTo(cropBox->x2, cropBox->y2);
+    state->lineTo(cropBox->x1, cropBox->y2);
+    state->closePath();
+    state->clip();
+    out->clip(state);
+    state->clearPath();
+  }
+}
+
+Gfx::~Gfx() {
+  while (state->hasSaves()) {
+    restoreState();
+  }
+  if (!subPage) {
+    out->endPage();
+  }
+  while (res) {
+    popResources();
+  }
+  if (state) {
+    delete state;
+  }
+}
+
+void Gfx::display(Object *obj, GBool topLevel) {
+  Object obj2;
+  int i;
+
+  if (obj->isArray()) {
+    for (i = 0; i < obj->arrayGetLength(); ++i) {
+      obj->arrayGet(i, &obj2);
+      if (!obj2.isStream()) {
+	error(-1, "Weird page contents");
+	obj2.free();
+	return;
+      }
+      obj2.free();
+    }
+  } else if (!obj->isStream()) {
+    error(-1, "Weird page contents");
+    return;
+  }
+  parser = new Parser(xref, new Lexer(xref, obj), gFalse);
+  go(topLevel);
+  delete parser;
+  parser = NULL;
+}
+
+void Gfx::go(GBool topLevel) {
+  Object obj;
+  Object args[maxArgs];
+  int numArgs, i;
+  int lastAbortCheck;
+
+  // scan a sequence of objects
+  updateLevel = lastAbortCheck = 0;
+  numArgs = 0;
+  parser->getObj(&obj);
+  while (!obj.isEOF()) {
+
+    // got a command - execute it
+    if (obj.isCmd()) {
+      if (printCommands) {
+	obj.print(stdout);
+	for (i = 0; i < numArgs; ++i) {
+	  printf(" ");
+	  args[i].print(stdout);
+	}
+	printf("\n");
+	fflush(stdout);
+      }
+      execOp(&obj, args, numArgs);
+      obj.free();
+      for (i = 0; i < numArgs; ++i)
+	args[i].free();
+      numArgs = 0;
+
+      // periodically update display
+      if (++updateLevel >= 20000) {
+	out->dump();
+	updateLevel = 0;
+      }
+
+      // check for an abort
+      if (abortCheckCbk) {
+	if (updateLevel - lastAbortCheck > 10) {
+	  if ((*abortCheckCbk)(abortCheckCbkData)) {
+	    break;
+	  }
+	  lastAbortCheck = updateLevel;
+	}
+      }
+
+    // got an argument - save it
+    } else if (numArgs < maxArgs) {
+      args[numArgs++] = obj;
+
+    // too many arguments - something is wrong
+    } else {
+      error(getPos(), "Too many args in content stream");
+      if (printCommands) {
+	printf("throwing away arg: ");
+	obj.print(stdout);
+	printf("\n");
+	fflush(stdout);
+      }
+      obj.free();
+    }
+
+    // grab the next object
+    parser->getObj(&obj);
+  }
+  obj.free();
+
+  // args at end with no command
+  if (numArgs > 0) {
+    error(getPos(), "Leftover args in content stream");
+    if (printCommands) {
+      printf("%d leftovers:", numArgs);
+      for (i = 0; i < numArgs; ++i) {
+	printf(" ");
+	args[i].print(stdout);
+      }
+      printf("\n");
+      fflush(stdout);
+    }
+    for (i = 0; i < numArgs; ++i)
+      args[i].free();
+  }
+
+  // update display
+  if (topLevel && updateLevel > 0) {
+    out->dump();
+  }
+}
+
+void Gfx::execOp(Object *cmd, Object args[], int numArgs) {
+  Operator *op;
+  char *name;
+  Object *argPtr;
+  int i;
+
+  // find operator
+  name = cmd->getCmd();
+  if (!(op = findOp(name))) {
+    if (ignoreUndef == 0)
+      error(getPos(), "Unknown operator '%s'", name);
+    return;
+  }
+
+  // type check args
+  argPtr = args;
+  if (op->numArgs >= 0) {
+    if (numArgs < op->numArgs) {
+      error(getPos(), "Too few (%d) args to '%s' operator", numArgs, name);
+      return;
+    }
+    if (numArgs > op->numArgs) {
+#if 0
+      error(getPos(), "Too many (%d) args to '%s' operator", numArgs, name);
+#endif
+      argPtr += numArgs - op->numArgs;
+      numArgs = op->numArgs;
+    }
+  } else {
+    if (numArgs > -op->numArgs) {
+      error(getPos(), "Too many (%d) args to '%s' operator",
+	    numArgs, name);
+      return;
+    }
+  }
+  for (i = 0; i < numArgs; ++i) {
+    if (!checkArg(&argPtr[i], op->tchk[i])) {
+      error(getPos(), "Arg #%d to '%s' operator is wrong type (%s)",
+	    i, name, argPtr[i].getTypeName());
+      return;
+    }
+  }
+
+  // do it
+  (this->*op->func)(argPtr, numArgs);
+}
+
+Operator *Gfx::findOp(char *name) {
+  int a, b, m, cmp;
+
+  a = -1;
+  b = numOps;
+  // invariant: opTab[a] < name < opTab[b]
+  while (b - a > 1) {
+    m = (a + b) / 2;
+    cmp = strcmp(opTab[m].name, name);
+    if (cmp < 0)
+      a = m;
+    else if (cmp > 0)
+      b = m;
+    else
+      a = b = m;
+  }
+  if (cmp != 0)
+    return NULL;
+  return &opTab[a];
+}
+
+GBool Gfx::checkArg(Object *arg, TchkType type) {
+  switch (type) {
+  case tchkBool:   return arg->isBool();
+  case tchkInt:    return arg->isInt();
+  case tchkNum:    return arg->isNum();
+  case tchkString: return arg->isString();
+  case tchkName:   return arg->isName();
+  case tchkArray:  return arg->isArray();
+  case tchkProps:  return arg->isDict() || arg->isName();
+  case tchkSCN:    return arg->isNum() || arg->isName();
+  case tchkNone:   return gFalse;
+  }
+  return gFalse;
+}
+
+int Gfx::getPos() {
+  return parser ? parser->getPos() : -1;
+}
+
+//------------------------------------------------------------------------
+// graphics state operators
+//------------------------------------------------------------------------
+
+void Gfx::opSave(Object args[], int numArgs) {
+  saveState();
+}
+
+void Gfx::opRestore(Object args[], int numArgs) {
+  restoreState();
+}
+
+void Gfx::opConcat(Object args[], int numArgs) {
+  state->concatCTM(args[0].getNum(), args[1].getNum(),
+		   args[2].getNum(), args[3].getNum(),
+		   args[4].getNum(), args[5].getNum());
+  out->updateCTM(state, args[0].getNum(), args[1].getNum(),
+		 args[2].getNum(), args[3].getNum(),
+		 args[4].getNum(), args[5].getNum());
+  fontChanged = gTrue;
+}
+
+void Gfx::opSetDash(Object args[], int numArgs) {
+  Array *a;
+  int length;
+  Object obj;
+  double *dash;
+  int i;
+
+  a = args[0].getArray();
+  length = a->getLength();
+  if (length == 0) {
+    dash = NULL;
+  } else {
+    dash = (double *)gmallocn(length, sizeof(double));
+    for (i = 0; i < length; ++i) {
+      dash[i] = a->get(i, &obj)->getNum();
+      obj.free();
+    }
+  }
+  state->setLineDash(dash, length, args[1].getNum());
+  out->updateLineDash(state);
+}
+
+void Gfx::opSetFlat(Object args[], int numArgs) {
+  state->setFlatness((int)args[0].getNum());
+  out->updateFlatness(state);
+}
+
+void Gfx::opSetLineJoin(Object args[], int numArgs) {
+  state->setLineJoin(args[0].getInt());
+  out->updateLineJoin(state);
+}
+
+void Gfx::opSetLineCap(Object args[], int numArgs) {
+  state->setLineCap(args[0].getInt());
+  out->updateLineCap(state);
+}
+
+void Gfx::opSetMiterLimit(Object args[], int numArgs) {
+  state->setMiterLimit(args[0].getNum());
+  out->updateMiterLimit(state);
+}
+
+void Gfx::opSetLineWidth(Object args[], int numArgs) {
+  state->setLineWidth(args[0].getNum());
+  out->updateLineWidth(state);
+}
+
+void Gfx::opSetExtGState(Object args[], int numArgs) {
+  Object obj1, obj2, obj3, obj4, obj5;
+  GfxBlendMode mode;
+  GBool haveFillOP;
+  Function *funcs[4];
+  GfxColor backdropColor;
+  GBool haveBackdropColor;
+  GfxColorSpace *blendingColorSpace;
+  GBool alpha, isolated, knockout;
+  int i;
+
+  if (!res->lookupGState(args[0].getName(), &obj1)) {
+    return;
+  }
+  if (!obj1.isDict()) {
+    error(getPos(), "ExtGState '%s' is wrong type", args[0].getName());
+    obj1.free();
+    return;
+  }
+  if (printCommands) {
+    printf("  gfx state dict: ");
+    obj1.print();
+    printf("\n");
+  }
+
+  // transparency support: blend mode, fill/stroke opacity
+  if (!obj1.dictLookup("BM", &obj2)->isNull()) {
+    if (state->parseBlendMode(&obj2, &mode)) {
+      state->setBlendMode(mode);
+      out->updateBlendMode(state);
+    } else {
+      error(getPos(), "Invalid blend mode in ExtGState");
+    }
+  }
+  obj2.free();
+  if (obj1.dictLookup("ca", &obj2)->isNum()) {
+    state->setFillOpacity(obj2.getNum());
+    out->updateFillOpacity(state);
+  }
+  obj2.free();
+  if (obj1.dictLookup("CA", &obj2)->isNum()) {
+    state->setStrokeOpacity(obj2.getNum());
+    out->updateStrokeOpacity(state);
+  }
+  obj2.free();
+
+  // fill/stroke overprint
+  if ((haveFillOP = (obj1.dictLookup("op", &obj2)->isBool()))) {
+    state->setFillOverprint(obj2.getBool());
+    out->updateFillOverprint(state);
+  }
+  obj2.free();
+  if (obj1.dictLookup("OP", &obj2)->isBool()) {
+    state->setStrokeOverprint(obj2.getBool());
+    out->updateStrokeOverprint(state);
+    if (!haveFillOP) {
+      state->setFillOverprint(obj2.getBool());
+      out->updateFillOverprint(state);
+    }
+  }
+  obj2.free();
+
+  // stroke adjust
+  if (obj1.dictLookup("SA", &obj2)->isBool()) {
+    state->setStrokeAdjust(obj2.getBool());
+    out->updateStrokeAdjust(state);
+  }
+  obj2.free();
+
+  // transfer function
+  if (obj1.dictLookup("TR2", &obj2)->isNull()) {
+    obj2.free();
+    obj1.dictLookup("TR", &obj2);
+  }
+  if (obj2.isName("Default") ||
+      obj2.isName("Identity")) {
+    funcs[0] = funcs[1] = funcs[2] = funcs[3] = NULL;
+    state->setTransfer(funcs);
+    out->updateTransfer(state);
+  } else if (obj2.isArray() && obj2.arrayGetLength() == 4) {
+    for (i = 0; i < 4; ++i) {
+      obj2.arrayGet(i, &obj3);
+      funcs[i] = Function::parse(&obj3);
+      obj3.free();
+      if (!funcs[i]) {
+	break;
+      }
+    }
+    if (i == 4) {
+      state->setTransfer(funcs);
+      out->updateTransfer(state);
+    }
+  } else if (obj2.isName() || obj2.isDict() || obj2.isStream()) {
+    if ((funcs[0] = Function::parse(&obj2))) {
+      funcs[1] = funcs[2] = funcs[3] = NULL;
+      state->setTransfer(funcs);
+      out->updateTransfer(state);
+    }
+  } else if (!obj2.isNull()) {
+    error(getPos(), "Invalid transfer function in ExtGState");
+  }
+  obj2.free();
+
+  // soft mask
+  if (!obj1.dictLookup("SMask", &obj2)->isNull()) {
+    if (obj2.isName("None")) {
+      out->clearSoftMask(state);
+    } else if (obj2.isDict()) {
+      if (obj2.dictLookup("S", &obj3)->isName("Alpha")) {
+	alpha = gTrue;
+      } else { // "Luminosity"
+	alpha = gFalse;
+      }
+      obj3.free();
+      funcs[0] = NULL;
+      if (!obj2.dictLookup("TR", &obj3)->isNull()) {
+	funcs[0] = Function::parse(&obj3);
+	if (funcs[0]->getInputSize() != 1 ||
+	    funcs[0]->getOutputSize() != 1) {
+	  error(getPos(),
+		"Invalid transfer function in soft mask in ExtGState");
+	  delete funcs[0];
+	  funcs[0] = NULL;
+	}
+      }
+      obj3.free();
+      if ((haveBackdropColor = obj2.dictLookup("BC", &obj3)->isArray())) {
+	for (i = 0; i < gfxColorMaxComps; ++i) {
+	  backdropColor.c[i] = 0;
+	}
+	for (i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) {
+	  obj3.arrayGet(i, &obj4);
+	  if (obj4.isNum()) {
+	    backdropColor.c[i] = dblToCol(obj4.getNum());
+	  }
+	  obj4.free();
+	}
+      }
+      obj3.free();
+      if (obj2.dictLookup("G", &obj3)->isStream()) {
+	if (obj3.streamGetDict()->lookup("Group", &obj4)->isDict()) {
+	  blendingColorSpace = NULL;
+	  isolated = knockout = gFalse;
+	  if (!obj4.dictLookup("CS", &obj5)->isNull()) {
+	    blendingColorSpace = GfxColorSpace::parse(&obj5);
+	  }
+	  obj5.free();
+	  if (obj4.dictLookup("I", &obj5)->isBool()) {
+	    isolated = obj5.getBool();
+	  }
+	  obj5.free();
+	  if (obj4.dictLookup("K", &obj5)->isBool()) {
+	    knockout = obj5.getBool();
+	  }
+	  obj5.free();
+	  if (!haveBackdropColor) {
+	    if (blendingColorSpace) {
+	      blendingColorSpace->getDefaultColor(&backdropColor);
+	    } else {
+	      //~ need to get the parent or default color space (?)
+	      for (i = 0; i < gfxColorMaxComps; ++i) {
+		backdropColor.c[i] = 0;
+	      }
+	    }
+	  }
+	  doSoftMask(&obj3, alpha, blendingColorSpace,
+		     isolated, knockout, funcs[0], &backdropColor);
+	  if (funcs[0]) {
+	    delete funcs[0];
+	  }
+	} else {
+	  error(getPos(), "Invalid soft mask in ExtGState - missing group");
+	}
+	obj4.free();
+      } else {
+	error(getPos(), "Invalid soft mask in ExtGState - missing group");
+      }
+      obj3.free();
+    } else if (!obj2.isNull()) {
+      error(getPos(), "Invalid soft mask in ExtGState");
+    }
+  }
+  obj2.free();
+
+  obj1.free();
+}
+
+void Gfx::doSoftMask(Object *str, GBool alpha,
+		     GfxColorSpace *blendingColorSpace,
+		     GBool isolated, GBool knockout,
+		     Function *transferFunc, GfxColor *backdropColor) {
+  Dict *dict, *resDict;
+  double m[6], bbox[4];
+  Object obj1, obj2;
+  int i;
+
+  // check for excessive recursion
+  if (formDepth > 20) {
+    return;
+  }
+
+  // get stream dict
+  dict = str->streamGetDict();
+
+  // check form type
+  dict->lookup("FormType", &obj1);
+  if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
+    error(getPos(), "Unknown form type");
+  }
+  obj1.free();
+
+  // get bounding box
+  dict->lookup("BBox", &obj1);
+  if (!obj1.isArray()) {
+    obj1.free();
+    error(getPos(), "Bad form bounding box");
+    return;
+  }
+  for (i = 0; i < 4; ++i) {
+    obj1.arrayGet(i, &obj2);
+    bbox[i] = obj2.getNum();
+    obj2.free();
+  }
+  obj1.free();
+
+  // get matrix
+  dict->lookup("Matrix", &obj1);
+  if (obj1.isArray()) {
+    for (i = 0; i < 6; ++i) {
+      obj1.arrayGet(i, &obj2);
+      m[i] = obj2.getNum();
+      obj2.free();
+    }
+  } else {
+    m[0] = 1; m[1] = 0;
+    m[2] = 0; m[3] = 1;
+    m[4] = 0; m[5] = 0;
+  }
+  obj1.free();
+
+  // get resources
+  dict->lookup("Resources", &obj1);
+  resDict = obj1.isDict() ? obj1.getDict() : (Dict *)NULL;
+
+  // draw it
+  ++formDepth;
+  doForm1(str, resDict, m, bbox, gTrue, gTrue,
+	  blendingColorSpace, isolated, knockout,
+	  alpha, transferFunc, backdropColor);
+  --formDepth;
+
+  if (blendingColorSpace) {
+    delete blendingColorSpace;
+  }
+  obj1.free();
+}
+
+void Gfx::opSetRenderingIntent(Object args[], int numArgs) {
+}
+
+//------------------------------------------------------------------------
+// color operators
+//------------------------------------------------------------------------
+
+void Gfx::opSetFillGray(Object args[], int numArgs) {
+  GfxColor color;
+
+  state->setFillPattern(NULL);
+  state->setFillColorSpace(new GfxDeviceGrayColorSpace());
+  out->updateFillColorSpace(state);
+  color.c[0] = dblToCol(args[0].getNum());
+  state->setFillColor(&color);
+  out->updateFillColor(state);
+}
+
+void Gfx::opSetStrokeGray(Object args[], int numArgs) {
+  GfxColor color;
+
+  state->setStrokePattern(NULL);
+  state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
+  out->updateStrokeColorSpace(state);
+  color.c[0] = dblToCol(args[0].getNum());
+  state->setStrokeColor(&color);
+  out->updateStrokeColor(state);
+}
+
+void Gfx::opSetFillCMYKColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  state->setFillPattern(NULL);
+  state->setFillColorSpace(new GfxDeviceCMYKColorSpace());
+  out->updateFillColorSpace(state);
+  for (i = 0; i < 4; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setFillColor(&color);
+  out->updateFillColor(state);
+}
+
+void Gfx::opSetStrokeCMYKColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  state->setStrokePattern(NULL);
+  state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace());
+  out->updateStrokeColorSpace(state);
+  for (i = 0; i < 4; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setStrokeColor(&color);
+  out->updateStrokeColor(state);
+}
+
+void Gfx::opSetFillRGBColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  state->setFillPattern(NULL);
+  state->setFillColorSpace(new GfxDeviceRGBColorSpace());
+  out->updateFillColorSpace(state);
+  for (i = 0; i < 3; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setFillColor(&color);
+  out->updateFillColor(state);
+}
+
+void Gfx::opSetStrokeRGBColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  state->setStrokePattern(NULL);
+  state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
+  out->updateStrokeColorSpace(state);
+  for (i = 0; i < 3; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setStrokeColor(&color);
+  out->updateStrokeColor(state);
+}
+
+void Gfx::opSetFillColorSpace(Object args[], int numArgs) {
+  Object obj;
+  GfxColorSpace *colorSpace;
+  GfxColor color;
+
+  state->setFillPattern(NULL);
+  res->lookupColorSpace(args[0].getName(), &obj);
+  if (obj.isNull()) {
+    colorSpace = GfxColorSpace::parse(&args[0]);
+  } else {
+    colorSpace = GfxColorSpace::parse(&obj);
+  }
+  obj.free();
+  if (colorSpace) {
+    state->setFillColorSpace(colorSpace);
+    out->updateFillColorSpace(state);
+    colorSpace->getDefaultColor(&color);
+    state->setFillColor(&color);
+    out->updateFillColor(state);
+  } else {
+    error(getPos(), "Bad color space (fill)");
+  }
+}
+
+void Gfx::opSetStrokeColorSpace(Object args[], int numArgs) {
+  Object obj;
+  GfxColorSpace *colorSpace;
+  GfxColor color;
+
+  state->setStrokePattern(NULL);
+  res->lookupColorSpace(args[0].getName(), &obj);
+  if (obj.isNull()) {
+    colorSpace = GfxColorSpace::parse(&args[0]);
+  } else {
+    colorSpace = GfxColorSpace::parse(&obj);
+  }
+  obj.free();
+  if (colorSpace) {
+    state->setStrokeColorSpace(colorSpace);
+    out->updateStrokeColorSpace(state);
+    colorSpace->getDefaultColor(&color);
+    state->setStrokeColor(&color);
+    out->updateStrokeColor(state);
+  } else {
+    error(getPos(), "Bad color space (stroke)");
+  }
+}
+
+void Gfx::opSetFillColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  if (numArgs != state->getFillColorSpace()->getNComps()) {
+    error(getPos(), "Incorrect number of arguments in 'sc' command");
+    return;
+  }
+  state->setFillPattern(NULL);
+  for (i = 0; i < numArgs; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setFillColor(&color);
+  out->updateFillColor(state);
+}
+
+void Gfx::opSetStrokeColor(Object args[], int numArgs) {
+  GfxColor color;
+  int i;
+
+  if (numArgs != state->getStrokeColorSpace()->getNComps()) {
+    error(getPos(), "Incorrect number of arguments in 'SC' command");
+    return;
+  }
+  state->setStrokePattern(NULL);
+  for (i = 0; i < numArgs; ++i) {
+    color.c[i] = dblToCol(args[i].getNum());
+  }
+  state->setStrokeColor(&color);
+  out->updateStrokeColor(state);
+}
+
+void Gfx::opSetFillColorN(Object args[], int numArgs) {
+  GfxColor color;
+  GfxPattern *pattern;
+  int i;
+
+  if (state->getFillColorSpace()->getMode() == csPattern) {
+    if (numArgs > 1) {
+      if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() ||
+	  numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace())
+	                     ->getUnder()->getNComps()) {
+	error(getPos(), "Incorrect number of arguments in 'scn' command");
+	return;
+      }
+      for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
+	if (args[i].isNum()) {
+	  color.c[i] = dblToCol(args[i].getNum());
+	}
+      }
+      state->setFillColor(&color);
+      out->updateFillColor(state);
+    }
+    if (args[numArgs-1].isName() &&
+	(pattern = res->lookupPattern(args[numArgs-1].getName()))) {
+      state->setFillPattern(pattern);
+    }
+
+  } else {
+    if (numArgs != state->getFillColorSpace()->getNComps()) {
+      error(getPos(), "Incorrect number of arguments in 'scn' command");
+      return;
+    }
+    state->setFillPattern(NULL);
+    for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
+      if (args[i].isNum()) {
+	color.c[i] = dblToCol(args[i].getNum());
+      }
+    }
+    state->setFillColor(&color);
+    out->updateFillColor(state);
+  }
+}
+
+void Gfx::opSetStrokeColorN(Object args[], int numArgs) {
+  GfxColor color;
+  GfxPattern *pattern;
+  int i;
+
+  if (state->getStrokeColorSpace()->getMode() == csPattern) {
+    if (numArgs > 1) {
+      if (!((GfxPatternColorSpace *)state->getStrokeColorSpace())
+	       ->getUnder() ||
+	  numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace())
+	                     ->getUnder()->getNComps()) {
+	error(getPos(), "Incorrect number of arguments in 'SCN' command");
+	return;
+      }
+      for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
+	if (args[i].isNum()) {
+	  color.c[i] = dblToCol(args[i].getNum());
+	}
+      }
+      state->setStrokeColor(&color);
+      out->updateStrokeColor(state);
+    }
+    if (args[numArgs-1].isName() &&
+	(pattern = res->lookupPattern(args[numArgs-1].getName()))) {
+      state->setStrokePattern(pattern);
+    }
+
+  } else {
+    if (numArgs != state->getStrokeColorSpace()->getNComps()) {
+      error(getPos(), "Incorrect number of arguments in 'SCN' command");
+      return;
+    }
+    state->setStrokePattern(NULL);
+    for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
+      if (args[i].isNum()) {
+	color.c[i] = dblToCol(args[i].getNum());
+      }
+    }
+    state->setStrokeColor(&color);
+    out->updateStrokeColor(state);
+  }
+}
+
+//------------------------------------------------------------------------
+// path segment operators
+//------------------------------------------------------------------------
+
+void Gfx::opMoveTo(Object args[], int numArgs) {
+  state->moveTo(args[0].getNum(), args[1].getNum());
+}
+
+void Gfx::opLineTo(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    error(getPos(), "No current point in lineto");
+    return;
+  }
+  state->lineTo(args[0].getNum(), args[1].getNum());
+}
+
+void Gfx::opCurveTo(Object args[], int numArgs) {
+  double x1, y1, x2, y2, x3, y3;
+
+  if (!state->isCurPt()) {
+    error(getPos(), "No current point in curveto");
+    return;
+  }
+  x1 = args[0].getNum();
+  y1 = args[1].getNum();
+  x2 = args[2].getNum();
+  y2 = args[3].getNum();
+  x3 = args[4].getNum();
+  y3 = args[5].getNum();
+  state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+void Gfx::opCurveTo1(Object args[], int numArgs) {
+  double x1, y1, x2, y2, x3, y3;
+
+  if (!state->isCurPt()) {
+    error(getPos(), "No current point in curveto1");
+    return;
+  }
+  x1 = state->getCurX();
+  y1 = state->getCurY();
+  x2 = args[0].getNum();
+  y2 = args[1].getNum();
+  x3 = args[2].getNum();
+  y3 = args[3].getNum();
+  state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+void Gfx::opCurveTo2(Object args[], int numArgs) {
+  double x1, y1, x2, y2, x3, y3;
+
+  if (!state->isCurPt()) {
+    error(getPos(), "No current point in curveto2");
+    return;
+  }
+  x1 = args[0].getNum();
+  y1 = args[1].getNum();
+  x2 = args[2].getNum();
+  y2 = args[3].getNum();
+  x3 = x2;
+  y3 = y2;
+  state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+void Gfx::opRectangle(Object args[], int numArgs) {
+  double x, y, w, h;
+
+  x = args[0].getNum();
+  y = args[1].getNum();
+  w = args[2].getNum();
+  h = args[3].getNum();
+  state->moveTo(x, y);
+  state->lineTo(x + w, y);
+  state->lineTo(x + w, y + h);
+  state->lineTo(x, y + h);
+  state->closePath();
+}
+
+void Gfx::opClosePath(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    error(getPos(), "No current point in closepath");
+    return;
+  }
+  state->closePath();
+}
+
+//------------------------------------------------------------------------
+// path painting operators
+//------------------------------------------------------------------------
+
+void Gfx::opEndPath(Object args[], int numArgs) {
+  doEndPath();
+}
+
+void Gfx::opStroke(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in stroke");
+    return;
+  }
+  if (state->isPath()) {
+    if (state->getStrokeColorSpace()->getMode() == csPattern) {
+      doPatternStroke();
+    } else {
+      out->stroke(state);
+    }
+  }
+  doEndPath();
+}
+
+void Gfx::opCloseStroke(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in closepath/stroke");
+    return;
+  }
+  if (state->isPath()) {
+    state->closePath();
+    if (state->getStrokeColorSpace()->getMode() == csPattern) {
+      doPatternStroke();
+    } else {
+      out->stroke(state);
+    }
+  }
+  doEndPath();
+}
+
+void Gfx::opFill(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in fill");
+    return;
+  }
+  if (state->isPath()) {
+    if (state->getFillColorSpace()->getMode() == csPattern) {
+      doPatternFill(gFalse);
+    } else {
+      out->fill(state);
+    }
+  }
+  doEndPath();
+}
+
+void Gfx::opEOFill(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in eofill");
+    return;
+  }
+  if (state->isPath()) {
+    if (state->getFillColorSpace()->getMode() == csPattern) {
+      doPatternFill(gTrue);
+    } else {
+      out->eoFill(state);
+    }
+  }
+  doEndPath();
+}
+
+void Gfx::opFillStroke(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in fill/stroke");
+    return;
+  }
+  if (state->isPath()) {
+    if (state->getFillColorSpace()->getMode() == csPattern) {
+      doPatternFill(gFalse);
+    } else {
+      out->fill(state);
+    }
+    if (state->getStrokeColorSpace()->getMode() == csPattern) {
+      doPatternStroke();
+    } else {
+      out->stroke(state);
+    }
+  }
+  doEndPath();
+}
+
+void Gfx::opCloseFillStroke(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in closepath/fill/stroke");
+    return;
+  }
+  if (state->isPath()) {
+    state->closePath();
+    if (state->getFillColorSpace()->getMode() == csPattern) {
+      doPatternFill(gFalse);
+    } else {
+      out->fill(state);
+    }
+    if (state->getStrokeColorSpace()->getMode() == csPattern) {
+      doPatternStroke();
+    } else {
+      out->stroke(state);
+    }
+  }
+  doEndPath();
+}
+
+void Gfx::opEOFillStroke(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in eofill/stroke");
+    return;
+  }
+  if (state->isPath()) {
+    if (state->getFillColorSpace()->getMode() == csPattern) {
+      doPatternFill(gTrue);
+    } else {
+      out->eoFill(state);
+    }
+    if (state->getStrokeColorSpace()->getMode() == csPattern) {
+      doPatternStroke();
+    } else {
+      out->stroke(state);
+    }
+  }
+  doEndPath();
+}
+
+void Gfx::opCloseEOFillStroke(Object args[], int numArgs) {
+  if (!state->isCurPt()) {
+    //error(getPos(), "No path in closepath/eofill/stroke");
+    return;
+  }
+  if (state->isPath()) {
+    state->closePath();
+    if (state->getFillColorSpace()->getMode() == csPattern) {
+      doPatternFill(gTrue);
+    } else {
+      out->eoFill(state);
+    }
+    if (state->getStrokeColorSpace()->getMode() == csPattern) {
+      doPatternStroke();
+    } else {
+      out->stroke(state);
+    }
+  }
+  doEndPath();
+}
+
+void Gfx::doPatternFill(GBool eoFill) {
+  GfxPattern *pattern;
+
+  // this is a bit of a kludge -- patterns can be really slow, so we
+  // skip them if we're only doing text extraction, since they almost
+  // certainly don't contain any text
+  if (!out->needNonText()) {
+    return;
+  }
+
+  if (!(pattern = state->getFillPattern())) {
+    return;
+  }
+  switch (pattern->getType()) {
+  case 1:
+    doTilingPatternFill((GfxTilingPattern *)pattern, gFalse, eoFill);
+    break;
+  case 2:
+    doShadingPatternFill((GfxShadingPattern *)pattern, gFalse, eoFill);
+    break;
+  default:
+    error(getPos(), "Unimplemented pattern type (%d) in fill",
+	  pattern->getType());
+    break;
+  }
+}
+
+void Gfx::doPatternStroke() {
+  GfxPattern *pattern;
+
+  // this is a bit of a kludge -- patterns can be really slow, so we
+  // skip them if we're only doing text extraction, since they almost
+  // certainly don't contain any text
+  if (!out->needNonText()) {
+    return;
+  }
+
+  if (!(pattern = state->getStrokePattern())) {
+    return;
+  }
+  switch (pattern->getType()) {
+  case 1:
+    doTilingPatternFill((GfxTilingPattern *)pattern, gTrue, gFalse);
+    break;
+  case 2:
+    doShadingPatternFill((GfxShadingPattern *)pattern, gTrue, gFalse);
+    break;
+  default:
+    error(getPos(), "Unimplemented pattern type (%d) in stroke",
+	  pattern->getType());
+    break;
+  }
+}
+
+void Gfx::doTilingPatternFill(GfxTilingPattern *tPat,
+			      GBool stroke, GBool eoFill) {
+  GfxPatternColorSpace *patCS;
+  GfxColorSpace *cs;
+  GfxPath *savedPath;
+  double xMin, yMin, xMax, yMax, x, y, x1, y1;
+  double cxMin, cyMin, cxMax, cyMax;
+  int xi0, yi0, xi1, yi1, xi, yi;
+  double *ctm, *btm, *ptm;
+  double m[6], ictm[6], m1[6], imb[6];
+  double det;
+  double xstep, ystep;
+  int i;
+
+  // get color space
+  patCS = (GfxPatternColorSpace *)(stroke ? state->getStrokeColorSpace()
+				          : state->getFillColorSpace());
+
+  // construct a (pattern space) -> (current space) transform matrix
+  ctm = state->getCTM();
+  btm = baseMatrix;
+  ptm = tPat->getMatrix();
+  // iCTM = invert CTM
+  det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
+  ictm[0] = ctm[3] * det;
+  ictm[1] = -ctm[1] * det;
+  ictm[2] = -ctm[2] * det;
+  ictm[3] = ctm[0] * det;
+  ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
+  ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
+  // m1 = PTM * BTM = PTM * base transform matrix
+  m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
+  m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
+  m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
+  m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
+  m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
+  m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
+  // m = m1 * iCTM = (PTM * BTM) * (iCTM)
+  m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
+  m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
+  m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
+  m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
+  m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
+  m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
+
+  // construct a (device space) -> (pattern space) transform matrix
+  det = 1 / (m1[0] * m1[3] - m1[1] * m1[2]);
+  imb[0] = m1[3] * det;
+  imb[1] = -m1[1] * det;
+  imb[2] = -m1[2] * det;
+  imb[3] = m1[0] * det;
+  imb[4] = (m1[2] * m1[5] - m1[3] * m1[4]) * det;
+  imb[5] = (m1[1] * m1[4] - m1[0] * m1[5]) * det;
+
+  // save current graphics state
+  savedPath = state->getPath()->copy();
+  saveState();
+
+  // set underlying color space (for uncolored tiling patterns); set
+  // various other parameters (stroke color, line width) to match
+  // Adobe's behavior
+  if (tPat->getPaintType() == 2 && (cs = patCS->getUnder())) {
+    state->setFillColorSpace(cs->copy());
+    out->updateFillColorSpace(state);
+    state->setStrokeColorSpace(cs->copy());
+    out->updateStrokeColorSpace(state);
+    state->setStrokeColor(state->getFillColor());
+  } else {
+    state->setFillColorSpace(new GfxDeviceGrayColorSpace());
+    out->updateFillColorSpace(state);
+    state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
+    out->updateStrokeColorSpace(state);
+  }
+  state->setFillPattern(NULL);
+  out->updateFillColor(state);
+  state->setStrokePattern(NULL);
+  out->updateStrokeColor(state);
+  if (!stroke) {
+    state->setLineWidth(0);
+    out->updateLineWidth(state);
+  }
+
+  // clip to current path
+  if (stroke) {
+    state->clipToStrokePath();
+    out->clipToStrokePath(state);
+  } else {
+    state->clip();
+    if (eoFill) {
+      out->eoClip(state);
+    } else {
+      out->clip(state);
+    }
+  }
+  state->clearPath();
+
+  // get the clip region, check for empty
+  state->getClipBBox(&cxMin, &cyMin, &cxMax, &cyMax);
+  if (cxMin > cxMax || cyMin > cyMax) {
+    goto err;
+  }
+
+  // transform clip region bbox to pattern space
+  xMin = xMax = cxMin * imb[0] + cyMin * imb[2] + imb[4];
+  yMin = yMax = cxMin * imb[1] + cyMin * imb[3] + imb[5];
+  x1 = cxMin * imb[0] + cyMax * imb[2] + imb[4];
+  y1 = cxMin * imb[1] + cyMax * imb[3] + imb[5];
+  if (x1 < xMin) {
+    xMin = x1;
+  } else if (x1 > xMax) {
+    xMax = x1;
+  }
+  if (y1 < yMin) {
+    yMin = y1;
+  } else if (y1 > yMax) {
+    yMax = y1;
+  }
+  x1 = cxMax * imb[0] + cyMin * imb[2] + imb[4];
+  y1 = cxMax * imb[1] + cyMin * imb[3] + imb[5];
+  if (x1 < xMin) {
+    xMin = x1;
+  } else if (x1 > xMax) {
+    xMax = x1;
+  }
+  if (y1 < yMin) {
+    yMin = y1;
+  } else if (y1 > yMax) {
+    yMax = y1;
+  }
+  x1 = cxMax * imb[0] + cyMax * imb[2] + imb[4];
+  y1 = cxMax * imb[1] + cyMax * imb[3] + imb[5];
+  if (x1 < xMin) {
+    xMin = x1;
+  } else if (x1 > xMax) {
+    xMax = x1;
+  }
+  if (y1 < yMin) {
+    yMin = y1;
+  } else if (y1 > yMax) {
+    yMax = y1;
+  }
+
+  // draw the pattern
+  //~ this should treat negative steps differently -- start at right/top
+  //~ edge instead of left/bottom (?)
+  xstep = fabs(tPat->getXStep());
+  ystep = fabs(tPat->getYStep());
+  xi0 = (int)ceil((xMin - tPat->getBBox()[2]) / xstep);
+  xi1 = (int)floor((xMax - tPat->getBBox()[0]) / xstep) + 1;
+  yi0 = (int)ceil((yMin - tPat->getBBox()[3]) / ystep);
+  yi1 = (int)floor((yMax - tPat->getBBox()[1]) / ystep) + 1;
+  for (i = 0; i < 4; ++i) {
+    m1[i] = m[i];
+  }
+  if (out->useTilingPatternFill()) {
+    m1[4] = m[4];
+    m1[5] = m[5];
+    out->tilingPatternFill(state, tPat->getContentStream(),
+			   tPat->getPaintType(), tPat->getResDict(),
+			   m1, tPat->getBBox(),
+			   xi0, yi0, xi1, yi1, xstep, ystep);
+  } else {
+    for (yi = yi0; yi < yi1; ++yi) {
+      for (xi = xi0; xi < xi1; ++xi) {
+	x = xi * xstep;
+	y = yi * ystep;
+	m1[4] = x * m[0] + y * m[2] + m[4];
+	m1[5] = x * m[1] + y * m[3] + m[5];
+	doForm1(tPat->getContentStream(), tPat->getResDict(),
+		m1, tPat->getBBox());
+      }
+    }
+  }
+
+  // restore graphics state
+ err:
+  restoreState();
+  state->setPath(savedPath);
+}
+
+void Gfx::doShadingPatternFill(GfxShadingPattern *sPat,
+			       GBool stroke, GBool eoFill) {
+  GfxShading *shading;
+  GfxPath *savedPath;
+  double *ctm, *btm, *ptm;
+  double m[6], ictm[6], m1[6];
+  double xMin, yMin, xMax, yMax;
+  double det;
+
+  shading = sPat->getShading();
+
+  // save current graphics state
+  savedPath = state->getPath()->copy();
+  saveState();
+
+  // clip to bbox
+  if (shading->getHasBBox()) {
+    shading->getBBox(&xMin, &yMin, &xMax, &yMax);
+    state->moveTo(xMin, yMin);
+    state->lineTo(xMax, yMin);
+    state->lineTo(xMax, yMax);
+    state->lineTo(xMin, yMax);
+    state->closePath();
+    state->clip();
+    out->clip(state);
+    state->setPath(savedPath->copy());
+  }
+
+  // clip to current path
+  if (stroke) {
+    state->clipToStrokePath();
+    out->clipToStrokePath(state);
+  } else {
+    state->clip();
+    if (eoFill) {
+      out->eoClip(state);
+    } else {
+      out->clip(state);
+    }
+  }
+
+  // set the color space
+  state->setFillColorSpace(shading->getColorSpace()->copy());
+  out->updateFillColorSpace(state);
+
+  // background color fill
+  if (shading->getHasBackground()) {
+    state->setFillColor(shading->getBackground());
+    out->updateFillColor(state);
+    out->fill(state);
+  }
+  state->clearPath();
+
+  // construct a (pattern space) -> (current space) transform matrix
+  ctm = state->getCTM();
+  btm = baseMatrix;
+  ptm = sPat->getMatrix();
+  // iCTM = invert CTM
+  det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
+  ictm[0] = ctm[3] * det;
+  ictm[1] = -ctm[1] * det;
+  ictm[2] = -ctm[2] * det;
+  ictm[3] = ctm[0] * det;
+  ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
+  ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
+  // m1 = PTM * BTM = PTM * base transform matrix
+  m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
+  m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
+  m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
+  m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
+  m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
+  m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
+  // m = m1 * iCTM = (PTM * BTM) * (iCTM)
+  m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
+  m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
+  m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
+  m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
+  m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
+  m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
+
+  // set the new matrix
+  state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]);
+  out->updateCTM(state, m[0], m[1], m[2], m[3], m[4], m[5]);
+
+#if 1 //~tmp: turn off anti-aliasing temporarily
+  GBool vaa = out->getVectorAntialias();
+  if (vaa) {
+    out->setVectorAntialias(gFalse);
+  }
+#endif
+
+  // do shading type-specific operations
+  switch (shading->getType()) {
+  case 1:
+    doFunctionShFill((GfxFunctionShading *)shading);
+    break;
+  case 2:
+    doAxialShFill((GfxAxialShading *)shading);
+    break;
+  case 3:
+    doRadialShFill((GfxRadialShading *)shading);
+    break;
+  case 4:
+  case 5:
+    doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
+    break;
+  case 6:
+  case 7:
+    doPatchMeshShFill((GfxPatchMeshShading *)shading);
+    break;
+  }
+
+#if 1 //~tmp: turn off anti-aliasing temporarily
+  if (vaa) {
+    out->setVectorAntialias(gTrue);
+  }
+#endif
+
+  // restore graphics state
+  restoreState();
+  state->setPath(savedPath);
+}
+
+void Gfx::opShFill(Object args[], int numArgs) {
+  GfxShading *shading;
+  GfxPath *savedPath;
+  double xMin, yMin, xMax, yMax;
+
+  if (!(shading = res->lookupShading(args[0].getName()))) {
+    return;
+  }
+
+  // save current graphics state
+  savedPath = state->getPath()->copy();
+  saveState();
+
+  // clip to bbox
+  if (shading->getHasBBox()) {
+    shading->getBBox(&xMin, &yMin, &xMax, &yMax);
+    state->moveTo(xMin, yMin);
+    state->lineTo(xMax, yMin);
+    state->lineTo(xMax, yMax);
+    state->lineTo(xMin, yMax);
+    state->closePath();
+    state->clip();
+    out->clip(state);
+    state->clearPath();
+  }
+
+  // set the color space
+  state->setFillColorSpace(shading->getColorSpace()->copy());
+  out->updateFillColorSpace(state);
+
+#if 1 //~tmp: turn off anti-aliasing temporarily
+  GBool vaa = out->getVectorAntialias();
+  if (vaa) {
+    out->setVectorAntialias(gFalse);
+  }
+#endif
+
+  // do shading type-specific operations
+  switch (shading->getType()) {
+  case 1:
+    doFunctionShFill((GfxFunctionShading *)shading);
+    break;
+  case 2:
+    doAxialShFill((GfxAxialShading *)shading);
+    break;
+  case 3:
+    doRadialShFill((GfxRadialShading *)shading);
+    break;
+  case 4:
+  case 5:
+    doGouraudTriangleShFill((GfxGouraudTriangleShading *)shading);
+    break;
+  case 6:
+  case 7:
+    doPatchMeshShFill((GfxPatchMeshShading *)shading);
+    break;
+  }
+
+#if 1 //~tmp: turn off anti-aliasing temporarily
+  if (vaa) {
+    out->setVectorAntialias(gTrue);
+  }
+#endif
+
+  // restore graphics state
+  restoreState();
+  state->setPath(savedPath);
+
+  delete shading;
+}
+
+void Gfx::doFunctionShFill(GfxFunctionShading *shading) {
+  double x0, y0, x1, y1;
+  GfxColor colors[4];
+
+  if (out->useShadedFills() &&
+      out->functionShadedFill(state, shading)) {
+    return;
+  }
+
+  shading->getDomain(&x0, &y0, &x1, &y1);
+  shading->getColor(x0, y0, &colors[0]);
+  shading->getColor(x0, y1, &colors[1]);
+  shading->getColor(x1, y0, &colors[2]);
+  shading->getColor(x1, y1, &colors[3]);
+  doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0);
+}
+
+void Gfx::doFunctionShFill1(GfxFunctionShading *shading,
+			    double x0, double y0,
+			    double x1, double y1,
+			    GfxColor *colors, int depth) {
+  GfxColor fillColor;
+  GfxColor color0M, color1M, colorM0, colorM1, colorMM;
+  GfxColor colors2[4];
+  double *matrix;
+  double xM, yM;
+  int nComps, i, j;
+
+  nComps = shading->getColorSpace()->getNComps();
+  matrix = shading->getMatrix();
+
+  // compare the four corner colors
+  for (i = 0; i < 4; ++i) {
+    for (j = 0; j < nComps; ++j) {
+      if (abs(colors[i].c[j] - colors[(i+1)&3].c[j]) > functionColorDelta) {
+	break;
+      }
+    }
+    if (j < nComps) {
+      break;
+    }
+  }
+
+  // center of the rectangle
+  xM = 0.5 * (x0 + x1);
+  yM = 0.5 * (y0 + y1);
+
+  // the four corner colors are close (or we hit the recursive limit)
+  // -- fill the rectangle; but require at least one subdivision
+  // (depth==0) to avoid problems when the four outer corners of the
+  // shaded region are the same color
+  if ((i == 4 && depth > 0) || depth == functionMaxDepth) {
+
+    // use the center color
+    shading->getColor(xM, yM, &fillColor);
+    state->setFillColor(&fillColor);
+    out->updateFillColor(state);
+
+    // fill the rectangle
+    state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4],
+		  x0 * matrix[1] + y0 * matrix[3] + matrix[5]);
+    state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4],
+		  x1 * matrix[1] + y0 * matrix[3] + matrix[5]);
+    state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4],
+		  x1 * matrix[1] + y1 * matrix[3] + matrix[5]);
+    state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4],
+		  x0 * matrix[1] + y1 * matrix[3] + matrix[5]);
+    state->closePath();
+    out->fill(state);
+    state->clearPath();
+
+  // the four corner colors are not close enough -- subdivide the
+  // rectangle
+  } else {
+
+    // colors[0]       colorM0       colors[2]
+    //   (x0,y0)       (xM,y0)       (x1,y0)
+    //         +----------+----------+
+    //         |          |          |
+    //         |    UL    |    UR    |
+    // color0M |       colorMM       | color1M
+    // (x0,yM) +----------+----------+ (x1,yM)
+    //         |       (xM,yM)       |
+    //         |    LL    |    LR    |
+    //         |          |          |
+    //         +----------+----------+
+    // colors[1]       colorM1       colors[3]
+    //   (x0,y1)       (xM,y1)       (x1,y1)
+
+    shading->getColor(x0, yM, &color0M);
+    shading->getColor(x1, yM, &color1M);
+    shading->getColor(xM, y0, &colorM0);
+    shading->getColor(xM, y1, &colorM1);
+    shading->getColor(xM, yM, &colorMM);
+
+    // upper-left sub-rectangle
+    colors2[0] = colors[0];
+    colors2[1] = color0M;
+    colors2[2] = colorM0;
+    colors2[3] = colorMM;
+    doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1);
+    
+    // lower-left sub-rectangle
+    colors2[0] = color0M;
+    colors2[1] = colors[1];
+    colors2[2] = colorMM;
+    colors2[3] = colorM1;
+    doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1);
+    
+    // upper-right sub-rectangle
+    colors2[0] = colorM0;
+    colors2[1] = colorMM;
+    colors2[2] = colors[2];
+    colors2[3] = color1M;
+    doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1);
+
+    // lower-right sub-rectangle
+    colors2[0] = colorMM;
+    colors2[1] = colorM1;
+    colors2[2] = color1M;
+    colors2[3] = colors[3];
+    doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1);
+  }
+}
+
+void Gfx::doAxialShFill(GfxAxialShading *shading) {
+  double xMin, yMin, xMax, yMax;
+  double x0, y0, x1, y1;
+  double dx, dy, mul;
+  GBool dxZero, dyZero;
+  double tMin, tMax, t, tx, ty;
+  double s[4], sMin, sMax, tmp;
+  double ux0, uy0, ux1, uy1, vx0, vy0, vx1, vy1;
+  double t0, t1, tt;
+  double ta[axialMaxSplits + 1];
+  int next[axialMaxSplits + 1];
+  GfxColor color0, color1;
+  int nComps;
+  int i, j, k, kk;
+
+  if (out->useShadedFills() &&
+      out->axialShadedFill(state, shading)) {
+    return;
+  }
+
+  // get the clip region bbox
+  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
+
+  // compute min and max t values, based on the four corners of the
+  // clip region bbox
+  shading->getCoords(&x0, &y0, &x1, &y1);
+  dx = x1 - x0;
+  dy = y1 - y0;
+  dxZero = fabs(dx) < 0.01;
+  dyZero = fabs(dy) < 0.01;
+  if (dxZero && dyZero) {
+    tMin = tMax = 0;
+  } else {
+    mul = 1 / (dx * dx + dy * dy);
+    tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
+    t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    if (tMin < 0 && !shading->getExtend0()) {
+      tMin = 0;
+    }
+    if (tMax > 1 && !shading->getExtend1()) {
+      tMax = 1;
+    }
+  }
+
+  // get the function domain
+  t0 = shading->getDomain0();
+  t1 = shading->getDomain1();
+
+  // Traverse the t axis and do the shading.
+  //
+  // For each point (tx, ty) on the t axis, consider a line through
+  // that point perpendicular to the t axis:
+  //
+  //     x(s) = tx + s * -dy   -->   s = (x - tx) / -dy
+  //     y(s) = ty + s * dx    -->   s = (y - ty) / dx
+  //
+  // Then look at the intersection of this line with the bounding box
+  // (xMin, yMin, xMax, yMax).  In the general case, there are four
+  // intersection points:
+  //
+  //     s0 = (xMin - tx) / -dy
+  //     s1 = (xMax - tx) / -dy
+  //     s2 = (yMin - ty) / dx
+  //     s3 = (yMax - ty) / dx
+  //
+  // and we want the middle two s values.
+  //
+  // In the case where dx = 0, take s0 and s1; in the case where dy =
+  // 0, take s2 and s3.
+  //
+  // Each filled polygon is bounded by two of these line segments
+  // perpdendicular to the t axis.
+  //
+  // The t axis is bisected into smaller regions until the color
+  // difference across a region is small enough, and then the region
+  // is painted with a single color.
+
+  // set up: require at least one split to avoid problems when the two
+  // ends of the t axis have the same color
+  nComps = shading->getColorSpace()->getNComps();
+  ta[0] = tMin;
+  next[0] = axialMaxSplits / 2;
+  ta[axialMaxSplits / 2] = 0.5 * (tMin + tMax);
+  next[axialMaxSplits / 2] = axialMaxSplits;
+  ta[axialMaxSplits] = tMax;
+
+  // compute the color at t = tMin
+  if (tMin < 0) {
+    tt = t0;
+  } else if (tMin > 1) {
+    tt = t1;
+  } else {
+    tt = t0 + (t1 - t0) * tMin;
+  }
+  shading->getColor(tt, &color0);
+
+  // compute the coordinates of the point on the t axis at t = tMin;
+  // then compute the intersection of the perpendicular line with the
+  // bounding box
+  tx = x0 + tMin * dx;
+  ty = y0 + tMin * dy;
+  if (dxZero && dyZero) {
+    sMin = sMax = 0;
+  } else if (dxZero) {
+    sMin = (xMin - tx) / -dy;
+    sMax = (xMax - tx) / -dy;
+    if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
+  } else if (dyZero) {
+    sMin = (yMin - ty) / dx;
+    sMax = (yMax - ty) / dx;
+    if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
+  } else {
+    s[0] = (yMin - ty) / dx;
+    s[1] = (yMax - ty) / dx;
+    s[2] = (xMin - tx) / -dy;
+    s[3] = (xMax - tx) / -dy;
+    for (j = 0; j < 3; ++j) {
+      kk = j;
+      for (k = j + 1; k < 4; ++k) {
+	if (s[k] < s[kk]) {
+	  kk = k;
+	}
+      }
+      tmp = s[j]; s[j] = s[kk]; s[kk] = tmp;
+    }
+    sMin = s[1];
+    sMax = s[2];
+  }
+  ux0 = tx - sMin * dy;
+  uy0 = ty + sMin * dx;
+  vx0 = tx - sMax * dy;
+  vy0 = ty + sMax * dx;
+
+  i = 0;
+  while (i < axialMaxSplits) {
+
+    // bisect until color difference is small enough or we hit the
+    // bisection limit
+    j = next[i];
+    while (j > i + 1) {
+      if (ta[j] < 0) {
+	tt = t0;
+      } else if (ta[j] > 1) {
+	tt = t1;
+      } else {
+	tt = t0 + (t1 - t0) * ta[j];
+      }
+      shading->getColor(tt, &color1);
+      for (k = 0; k < nComps; ++k) {
+	if (abs(color1.c[k] - color0.c[k]) > axialColorDelta) {
+	  break;
+	}
+      }
+      if (k == nComps) {
+	break;
+      }
+      k = (i + j) / 2;
+      ta[k] = 0.5 * (ta[i] + ta[j]);
+      next[i] = k;
+      next[k] = j;
+      j = k;
+    }
+
+    // use the average of the colors of the two sides of the region
+    for (k = 0; k < nComps; ++k) {
+      color0.c[k] = (color0.c[k] + color1.c[k]) / 2;
+    }
+
+    // compute the coordinates of the point on the t axis; then
+    // compute the intersection of the perpendicular line with the
+    // bounding box
+    tx = x0 + ta[j] * dx;
+    ty = y0 + ta[j] * dy;
+    if (dxZero && dyZero) {
+      sMin = sMax = 0;
+    } else if (dxZero) {
+      sMin = (xMin - tx) / -dy;
+      sMax = (xMax - tx) / -dy;
+      if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
+    } else if (dyZero) {
+      sMin = (yMin - ty) / dx;
+      sMax = (yMax - ty) / dx;
+      if (sMin > sMax) { tmp = sMin; sMin = sMax; sMax = tmp; }
+    } else {
+      s[0] = (yMin - ty) / dx;
+      s[1] = (yMax - ty) / dx;
+      s[2] = (xMin - tx) / -dy;
+      s[3] = (xMax - tx) / -dy;
+      for (j = 0; j < 3; ++j) {
+	kk = j;
+	for (k = j + 1; k < 4; ++k) {
+	  if (s[k] < s[kk]) {
+	    kk = k;
+	  }
+	}
+	tmp = s[j]; s[j] = s[kk]; s[kk] = tmp;
+      }
+      sMin = s[1];
+      sMax = s[2];
+    }
+    ux1 = tx - sMin * dy;
+    uy1 = ty + sMin * dx;
+    vx1 = tx - sMax * dy;
+    vy1 = ty + sMax * dx;
+
+    // set the color
+    state->setFillColor(&color0);
+    out->updateFillColor(state);
+
+    // fill the region
+    state->moveTo(ux0, uy0);
+    state->lineTo(vx0, vy0);
+    state->lineTo(vx1, vy1);
+    state->lineTo(ux1, uy1);
+    state->closePath();
+    out->fill(state);
+    state->clearPath();
+
+    // set up for next region
+    ux0 = ux1;
+    uy0 = uy1;
+    vx0 = vx1;
+    vy0 = vy1;
+    color0 = color1;
+    i = next[i];
+  }
+}
+
+void Gfx::doRadialShFill(GfxRadialShading *shading) {
+  double xMin, yMin, xMax, yMax;
+  double x0, y0, r0, x1, y1, r1, t0, t1;
+  int nComps;
+  GfxColor colorA, colorB;
+  double xa, ya, xb, yb, ra, rb;
+  double ta, tb, sa, sb;
+  double sz, xz, yz, sMin, sMax;
+  GBool enclosed;
+  int ia, ib, k, n;
+  double *ctm;
+  double theta, alpha, angle, t;
+
+  if (out->useShadedFills() &&
+      out->radialShadedFill(state, shading)) {
+    return;
+  }
+
+  // get the shading info
+  shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
+  t0 = shading->getDomain0();
+  t1 = shading->getDomain1();
+  nComps = shading->getColorSpace()->getNComps();
+
+  // Compute the point at which r(s) = 0; check for the enclosed
+  // circles case; and compute the angles for the tangent lines.
+  if (x0 == x1 && y0 == y1) {
+    enclosed = gTrue;
+    theta = 0; // make gcc happy
+    sz = 0; // make gcc happy
+  } else if (r0 == r1) {
+    enclosed = gFalse;
+    theta = 0;
+    sz = 0; // make gcc happy
+  } else {
+    sz = -r0 / (r1 - r0);
+    xz = x0 + sz * (x1 - x0);
+    yz = y0 + sz * (y1 - y0);
+    enclosed = (xz - x0) * (xz - x0) + (yz - y0) * (yz - y0) <= r0 * r0;
+    theta = asin(r0 / sqrt((x0 - xz) * (x0 - xz) + (y0 - yz) * (y0 - yz)));
+    if (r0 > r1) {
+      theta = -theta;
+    }
+  }
+  if (enclosed) {
+    alpha = 0;
+  } else {
+    alpha = atan2(y1 - y0, x1 - x0);
+  }
+
+  // compute the (possibly extended) s range
+  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
+  if (enclosed) {
+    sMin = 0;
+    sMax = 1;
+  } else {
+    sMin = 1;
+    sMax = 0;
+    // solve for x(s) + r(s) = xMin
+    if ((x1 + r1) - (x0 + r0) != 0) {
+      sa = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // solve for x(s) - r(s) = xMax
+    if ((x1 - r1) - (x0 - r0) != 0) {
+      sa = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // solve for y(s) + r(s) = yMin
+    if ((y1 + r1) - (y0 + r0) != 0) {
+      sa = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // solve for y(s) - r(s) = yMax
+    if ((y1 - r1) - (y0 - r0) != 0) {
+      sa = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // check against sz
+    if (r0 < r1) {
+      if (sMin < sz) {
+	sMin = sz;
+      }
+    } else if (r0 > r1) {
+      if (sMax > sz) {
+	sMax = sz;
+      }
+    }
+    // check the 'extend' flags
+    if (!shading->getExtend0() && sMin < 0) {
+      sMin = 0;
+    }
+    if (!shading->getExtend1() && sMax > 1) {
+      sMax = 1;
+    }
+  }
+
+  // compute the number of steps into which circles must be divided to
+  // achieve a curve flatness of 0.1 pixel in device space for the
+  // largest circle (note that "device space" is 72 dpi when generating
+  // PostScript, hence the relatively small 0.1 pixel accuracy)
+  ctm = state->getCTM();
+  t = fabs(ctm[0]);
+  if (fabs(ctm[1]) > t) {
+    t = fabs(ctm[1]);
+  }
+  if (fabs(ctm[2]) > t) {
+    t = fabs(ctm[2]);
+  }
+  if (fabs(ctm[3]) > t) {
+    t = fabs(ctm[3]);
+  }
+  if (r0 > r1) {
+    t *= r0;
+  } else {
+    t *= r1;
+  }
+  if (t < 1) {
+    n = 3;
+  } else {
+    n = (int)(M_PI / acos(1 - 0.1 / t));
+    if (n < 3) {
+      n = 3;
+    } else if (n > 200) {
+      n = 200;
+    }
+  }
+
+  // setup for the start circle
+  ia = 0;
+  sa = sMin;
+  ta = t0 + sa * (t1 - t0);
+  xa = x0 + sa * (x1 - x0);
+  ya = y0 + sa * (y1 - y0);
+  ra = r0 + sa * (r1 - r0);
+  if (ta < t0) {
+    shading->getColor(t0, &colorA);
+  } else if (ta > t1) {
+    shading->getColor(t1, &colorA);
+  } else {
+    shading->getColor(ta, &colorA);
+  }
+
+  // fill the circles
+  while (ia < radialMaxSplits) {
+
+    // go as far along the t axis (toward t1) as we can, such that the
+    // color difference is within the tolerance (radialColorDelta) --
+    // this uses bisection (between the current value, t, and t1),
+    // limited to radialMaxSplits points along the t axis; require at
+    // least one split to avoid problems when the innermost and
+    // outermost colors are the same
+    ib = radialMaxSplits;
+    sb = sMax;
+    tb = t0 + sb * (t1 - t0);
+    if (tb < t0) {
+      shading->getColor(t0, &colorB);
+    } else if (tb > t1) {
+      shading->getColor(t1, &colorB);
+    } else {
+      shading->getColor(tb, &colorB);
+    }
+    while (ib - ia > 1) {
+      for (k = 0; k < nComps; ++k) {
+	if (abs(colorB.c[k] - colorA.c[k]) > radialColorDelta) {
+	  break;
+	}
+      }
+      if (k == nComps && ib < radialMaxSplits) {
+	break;
+      }
+      ib = (ia + ib) / 2;
+      sb = sMin + ((double)ib / (double)radialMaxSplits) * (sMax - sMin);
+      tb = t0 + sb * (t1 - t0);
+      if (tb < t0) {
+	shading->getColor(t0, &colorB);
+      } else if (tb > t1) {
+	shading->getColor(t1, &colorB);
+      } else {
+	shading->getColor(tb, &colorB);
+      }
+    }
+
+    // compute center and radius of the circle
+    xb = x0 + sb * (x1 - x0);
+    yb = y0 + sb * (y1 - y0);
+    rb = r0 + sb * (r1 - r0);
+
+    // use the average of the colors at the two circles
+    for (k = 0; k < nComps; ++k) {
+      colorA.c[k] = (colorA.c[k] + colorB.c[k]) / 2;
+    }
+    state->setFillColor(&colorA);
+    out->updateFillColor(state);
+
+    if (enclosed) {
+
+      // construct path for first circle (counterclockwise)
+      state->moveTo(xa + ra, ya);
+      for (k = 1; k < n; ++k) {
+	angle = ((double)k / (double)n) * 2 * M_PI;
+	state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
+      }
+      state->closePath();
+
+      // construct and append path for second circle (clockwise)
+      state->moveTo(xb + rb, yb);
+      for (k = 1; k < n; ++k) {
+	angle = -((double)k / (double)n) * 2 * M_PI;
+	state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
+      }
+      state->closePath();
+
+    } else {
+
+      // construct the first subpath (clockwise)
+      state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI),
+		    ya + ra * sin(alpha + theta + 0.5 * M_PI));
+      for (k = 0; k < n; ++k) {
+	angle = alpha + theta + 0.5 * M_PI
+	  - ((double)k / (double)n) * (2 * theta + M_PI);
+	state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
+      }
+      for (k = 0; k < n; ++k) {
+	angle = alpha - theta - 0.5 * M_PI
+	  + ((double)k / (double)n) * (2 * theta - M_PI);
+	state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
+      }
+      state->closePath();
+
+      // construct the second subpath (counterclockwise)
+      state->moveTo(xa + ra * cos(alpha + theta + 0.5 * M_PI),
+		    ya + ra * sin(alpha + theta + 0.5 * M_PI));
+      for (k = 0; k < n; ++k) {
+	angle = alpha + theta + 0.5 * M_PI
+	        + ((double)k / (double)n) * (-2 * theta + M_PI);
+	state->lineTo(xb + rb * cos(angle), yb + rb * sin(angle));
+      }
+      for (k = 0; k < n; ++k) {
+	angle = alpha - theta - 0.5 * M_PI
+	        + ((double)k / (double)n) * (2 * theta + M_PI);
+	state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
+      }
+      state->closePath();
+    }
+
+    // fill the path
+    out->fill(state);
+    state->clearPath();
+
+    // step to the next value of t
+    ia = ib;
+    sa = sb;
+    ta = tb;
+    xa = xb;
+    ya = yb;
+    ra = rb;
+    colorA = colorB;
+  }
+
+  if (enclosed) {
+    // extend the smaller circle
+    if ((shading->getExtend0() && r0 <= r1) ||
+	(shading->getExtend1() && r1 < r0)) {
+      if (r0 <= r1) {
+	ta = t0;
+	ra = r0;
+	xa = x0;
+	ya = y0;
+      } else {
+	ta = t1;
+	ra = r1;
+	xa = x1;
+	ya = y1;
+      }
+      shading->getColor(ta, &colorA);
+      state->setFillColor(&colorA);
+      out->updateFillColor(state);
+      state->moveTo(xa + ra, ya);
+      for (k = 1; k < n; ++k) {
+	angle = ((double)k / (double)n) * 2 * M_PI;
+	state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
+      }
+      state->closePath();
+      out->fill(state);
+      state->clearPath();
+    }
+
+    // extend the larger circle
+    if ((shading->getExtend0() && r0 > r1) ||
+	(shading->getExtend1() && r1 >= r0)) {
+      if (r0 > r1) {
+	ta = t0;
+	ra = r0;
+	xa = x0;
+	ya = y0;
+      } else {
+	ta = t1;
+	ra = r1;
+	xa = x1;
+	ya = y1;
+      }
+      shading->getColor(ta, &colorA);
+      state->setFillColor(&colorA);
+      out->updateFillColor(state);
+      state->moveTo(xMin, yMin);
+      state->lineTo(xMin, yMax);
+      state->lineTo(xMax, yMax);
+      state->lineTo(xMax, yMin);
+      state->closePath();
+      state->moveTo(xa + ra, ya);
+      for (k = 1; k < n; ++k) {
+	angle = ((double)k / (double)n) * 2 * M_PI;
+	state->lineTo(xa + ra * cos(angle), ya + ra * sin(angle));
+      }
+      state->closePath();
+      out->fill(state);
+      state->clearPath();
+    }
+  }
+}
+
+void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) {
+  double x0, y0, x1, y1, x2, y2;
+  GfxColor color0, color1, color2;
+  int i;
+
+  for (i = 0; i < shading->getNTriangles(); ++i) {
+    shading->getTriangle(i, &x0, &y0, &color0,
+			 &x1, &y1, &color1,
+			 &x2, &y2, &color2);
+    gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2,
+			shading->getColorSpace()->getNComps(), 0);
+  }
+}
+
+void Gfx::gouraudFillTriangle(double x0, double y0, GfxColor *color0,
+			      double x1, double y1, GfxColor *color1,
+			      double x2, double y2, GfxColor *color2,
+			      int nComps, int depth) {
+  double x01, y01, x12, y12, x20, y20;
+  GfxColor color01, color12, color20;
+  int i;
+
+  for (i = 0; i < nComps; ++i) {
+    if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta ||
+	abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) {
+      break;
+    }
+  }
+  if (i == nComps || depth == gouraudMaxDepth) {
+    state->setFillColor(color0);
+    out->updateFillColor(state);
+    state->moveTo(x0, y0);
+    state->lineTo(x1, y1);
+    state->lineTo(x2, y2);
+    state->closePath();
+    out->fill(state);
+    state->clearPath();
+  } else {
+    x01 = 0.5 * (x0 + x1);
+    y01 = 0.5 * (y0 + y1);
+    x12 = 0.5 * (x1 + x2);
+    y12 = 0.5 * (y1 + y2);
+    x20 = 0.5 * (x2 + x0);
+    y20 = 0.5 * (y2 + y0);
+    //~ if the shading has a Function, this should interpolate on the
+    //~ function parameter, not on the color components
+    for (i = 0; i < nComps; ++i) {
+      color01.c[i] = (color0->c[i] + color1->c[i]) / 2;
+      color12.c[i] = (color1->c[i] + color2->c[i]) / 2;
+      color20.c[i] = (color2->c[i] + color0->c[i]) / 2;
+    }
+    gouraudFillTriangle(x0, y0, color0, x01, y01, &color01,
+			x20, y20, &color20, nComps, depth + 1);
+    gouraudFillTriangle(x01, y01, &color01, x1, y1, color1,
+			x12, y12, &color12, nComps, depth + 1);
+    gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12,
+			x20, y20, &color20, nComps, depth + 1);
+    gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12,
+			x2, y2, color2, nComps, depth + 1);
+  }
+}
+
+void Gfx::doPatchMeshShFill(GfxPatchMeshShading *shading) {
+  int start, i;
+
+  if (shading->getNPatches() > 128) {
+    start = 3;
+  } else if (shading->getNPatches() > 64) {
+    start = 2;
+  } else if (shading->getNPatches() > 16) {
+    start = 1;
+  } else {
+    start = 0;
+  }
+  for (i = 0; i < shading->getNPatches(); ++i) {
+    fillPatch(shading->getPatch(i), shading->getColorSpace()->getNComps(),
+	      start);
+  }
+}
+
+void Gfx::fillPatch(GfxPatch *patch, int nComps, int depth) {
+  GfxPatch patch00, patch01, patch10, patch11;
+  double xx[4][8], yy[4][8];
+  double xxm, yym;
+  int i;
+
+  for (i = 0; i < nComps; ++i) {
+    if (abs(patch->color[0][0].c[i] - patch->color[0][1].c[i])
+	  > patchColorDelta ||
+	abs(patch->color[0][1].c[i] - patch->color[1][1].c[i])
+	  > patchColorDelta ||
+	abs(patch->color[1][1].c[i] - patch->color[1][0].c[i])
+	  > patchColorDelta ||
+	abs(patch->color[1][0].c[i] - patch->color[0][0].c[i])
+	  > patchColorDelta) {
+      break;
+    }
+  }
+  if (i == nComps || depth == patchMaxDepth) {
+    state->setFillColor(&patch->color[0][0]);
+    out->updateFillColor(state);
+    state->moveTo(patch->x[0][0], patch->y[0][0]);
+    state->curveTo(patch->x[0][1], patch->y[0][1],
+		   patch->x[0][2], patch->y[0][2],
+		   patch->x[0][3], patch->y[0][3]);
+    state->curveTo(patch->x[1][3], patch->y[1][3],
+		   patch->x[2][3], patch->y[2][3],
+		   patch->x[3][3], patch->y[3][3]);
+    state->curveTo(patch->x[3][2], patch->y[3][2],
+		   patch->x[3][1], patch->y[3][1],
+		   patch->x[3][0], patch->y[3][0]);
+    state->curveTo(patch->x[2][0], patch->y[2][0],
+		   patch->x[1][0], patch->y[1][0],
+		   patch->x[0][0], patch->y[0][0]);
+    state->closePath();
+    out->fill(state);
+    state->clearPath();
+  } else {
+    for (i = 0; i < 4; ++i) {
+      xx[i][0] = patch->x[i][0];
+      yy[i][0] = patch->y[i][0];
+      xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]);
+      yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]);
+      xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
+      yym = 0.5 * (patch->y[i][1] + patch->y[i][2]);
+      xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]);
+      yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]);
+      xx[i][2] = 0.5 * (xx[i][1] + xxm);
+      yy[i][2] = 0.5 * (yy[i][1] + yym);
+      xx[i][5] = 0.5 * (xxm + xx[i][6]);
+      yy[i][5] = 0.5 * (yym + yy[i][6]);
+      xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]);
+      yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]);
+      xx[i][7] = patch->x[i][3];
+      yy[i][7] = patch->y[i][3];
+    }
+    for (i = 0; i < 4; ++i) {
+      patch00.x[0][i] = xx[0][i];
+      patch00.y[0][i] = yy[0][i];
+      patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]);
+      patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]);
+      xxm = 0.5 * (xx[1][i] + xx[2][i]);
+      yym = 0.5 * (yy[1][i] + yy[2][i]);
+      patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]);
+      patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]);
+      patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm);
+      patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym);
+      patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]);
+      patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]);
+      patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]);
+      patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]);
+      patch10.x[0][i] = patch00.x[3][i];
+      patch10.y[0][i] = patch00.y[3][i];
+      patch10.x[3][i] = xx[3][i];
+      patch10.y[3][i] = yy[3][i];
+    }
+    for (i = 4; i < 8; ++i) {
+      patch01.x[0][i-4] = xx[0][i];
+      patch01.y[0][i-4] = yy[0][i];
+      patch01.x[1][i-4] = 0.5 * (xx[0][i] + xx[1][i]);
+      patch01.y[1][i-4] = 0.5 * (yy[0][i] + yy[1][i]);
+      xxm = 0.5 * (xx[1][i] + xx[2][i]);
+      yym = 0.5 * (yy[1][i] + yy[2][i]);
+      patch11.x[2][i-4] = 0.5 * (xx[2][i] + xx[3][i]);
+      patch11.y[2][i-4] = 0.5 * (yy[2][i] + yy[3][i]);
+      patch01.x[2][i-4] = 0.5 * (patch01.x[1][i-4] + xxm);
+      patch01.y[2][i-4] = 0.5 * (patch01.y[1][i-4] + yym);
+      patch11.x[1][i-4] = 0.5 * (xxm + patch11.x[2][i-4]);
+      patch11.y[1][i-4] = 0.5 * (yym + patch11.y[2][i-4]);
+      patch01.x[3][i-4] = 0.5 * (patch01.x[2][i-4] + patch11.x[1][i-4]);
+      patch01.y[3][i-4] = 0.5 * (patch01.y[2][i-4] + patch11.y[1][i-4]);
+      patch11.x[0][i-4] = patch01.x[3][i-4];
+      patch11.y[0][i-4] = patch01.y[3][i-4];
+      patch11.x[3][i-4] = xx[3][i];
+      patch11.y[3][i-4] = yy[3][i];
+    }
+    //~ if the shading has a Function, this should interpolate on the
+    //~ function parameter, not on the color components
+    for (i = 0; i < nComps; ++i) {
+      patch00.color[0][0].c[i] = patch->color[0][0].c[i];
+      patch00.color[0][1].c[i] = (patch->color[0][0].c[i] +
+				  patch->color[0][1].c[i]) / 2;
+      patch01.color[0][0].c[i] = patch00.color[0][1].c[i];
+      patch01.color[0][1].c[i] = patch->color[0][1].c[i];
+      patch01.color[1][1].c[i] = (patch->color[0][1].c[i] +
+				  patch->color[1][1].c[i]) / 2;
+      patch11.color[0][1].c[i] = patch01.color[1][1].c[i];
+      patch11.color[1][1].c[i] = patch->color[1][1].c[i];
+      patch11.color[1][0].c[i] = (patch->color[1][1].c[i] +
+				  patch->color[1][0].c[i]) / 2;
+      patch10.color[1][1].c[i] = patch11.color[1][0].c[i];
+      patch10.color[1][0].c[i] = patch->color[1][0].c[i];
+      patch10.color[0][0].c[i] = (patch->color[1][0].c[i] +
+				  patch->color[0][0].c[i]) / 2;
+      patch00.color[1][0].c[i] = patch10.color[0][0].c[i];
+      patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] +
+				  patch01.color[1][1].c[i]) / 2;
+      patch01.color[1][0].c[i] = patch00.color[1][1].c[i];
+      patch11.color[0][0].c[i] = patch00.color[1][1].c[i];
+      patch10.color[0][1].c[i] = patch00.color[1][1].c[i];
+    }
+    fillPatch(&patch00, nComps, depth + 1);
+    fillPatch(&patch10, nComps, depth + 1);
+    fillPatch(&patch01, nComps, depth + 1);
+    fillPatch(&patch11, nComps, depth + 1);
+  }
+}
+
+void Gfx::doEndPath() {
+  if (state->isCurPt() && clip != clipNone) {
+    state->clip();
+    if (clip == clipNormal) {
+      out->clip(state);
+    } else {
+      out->eoClip(state);
+    }
+  }
+  clip = clipNone;
+  state->clearPath();
+}
+
+//------------------------------------------------------------------------
+// path clipping operators
+//------------------------------------------------------------------------
+
+void Gfx::opClip(Object args[], int numArgs) {
+  clip = clipNormal;
+}
+
+void Gfx::opEOClip(Object args[], int numArgs) {
+  clip = clipEO;
+}
+
+//------------------------------------------------------------------------
+// text object operators
+//------------------------------------------------------------------------
+
+void Gfx::opBeginText(Object args[], int numArgs) {
+  state->setTextMat(1, 0, 0, 1, 0, 0);
+  state->textMoveTo(0, 0);
+  out->updateTextMat(state);
+  out->updateTextPos(state);
+  fontChanged = gTrue;
+}
+
+void Gfx::opEndText(Object args[], int numArgs) {
+  out->endTextObject(state);
+}
+
+//------------------------------------------------------------------------
+// text state operators
+//------------------------------------------------------------------------
+
+void Gfx::opSetCharSpacing(Object args[], int numArgs) {
+  state->setCharSpace(args[0].getNum());
+  out->updateCharSpace(state);
+}
+
+void Gfx::opSetFont(Object args[], int numArgs) {
+  GfxFont *font;
+
+  if (!(font = res->lookupFont(args[0].getName()))) {
+    return;
+  }
+  if (printCommands) {
+    printf("  font: tag=%s name='%s' %g\n",
+	   font->getTag()->getCString(),
+	   font->getName() ? font->getName()->getCString() : "???",
+	   args[1].getNum());
+    fflush(stdout);
+  }
+  state->setFont(font, args[1].getNum());
+  fontChanged = gTrue;
+}
+
+void Gfx::opSetTextLeading(Object args[], int numArgs) {
+  state->setLeading(args[0].getNum());
+}
+
+void Gfx::opSetTextRender(Object args[], int numArgs) {
+  state->setRender(args[0].getInt());
+  out->updateRender(state);
+}
+
+void Gfx::opSetTextRise(Object args[], int numArgs) {
+  state->setRise(args[0].getNum());
+  out->updateRise(state);
+}
+
+void Gfx::opSetWordSpacing(Object args[], int numArgs) {
+  state->setWordSpace(args[0].getNum());
+  out->updateWordSpace(state);
+}
+
+void Gfx::opSetHorizScaling(Object args[], int numArgs) {
+  state->setHorizScaling(args[0].getNum());
+  out->updateHorizScaling(state);
+  fontChanged = gTrue;
+}
+
+//------------------------------------------------------------------------
+// text positioning operators
+//------------------------------------------------------------------------
+
+void Gfx::opTextMove(Object args[], int numArgs) {
+  double tx, ty;
+
+  tx = state->getLineX() + args[0].getNum();
+  ty = state->getLineY() + args[1].getNum();
+  state->textMoveTo(tx, ty);
+  out->updateTextPos(state);
+}
+
+void Gfx::opTextMoveSet(Object args[], int numArgs) {
+  double tx, ty;
+
+  tx = state->getLineX() + args[0].getNum();
+  ty = args[1].getNum();
+  state->setLeading(-ty);
+  ty += state->getLineY();
+  state->textMoveTo(tx, ty);
+  out->updateTextPos(state);
+}
+
+void Gfx::opSetTextMatrix(Object args[], int numArgs) {
+  state->setTextMat(args[0].getNum(), args[1].getNum(),
+		    args[2].getNum(), args[3].getNum(),
+		    args[4].getNum(), args[5].getNum());
+  state->textMoveTo(0, 0);
+  out->updateTextMat(state);
+  out->updateTextPos(state);
+  fontChanged = gTrue;
+}
+
+void Gfx::opTextNextLine(Object args[], int numArgs) {
+  double tx, ty;
+
+  tx = state->getLineX();
+  ty = state->getLineY() - state->getLeading();
+  state->textMoveTo(tx, ty);
+  out->updateTextPos(state);
+}
+
+//------------------------------------------------------------------------
+// text string operators
+//------------------------------------------------------------------------
+
+void Gfx::opShowText(Object args[], int numArgs) {
+  if (!state->getFont()) {
+    error(getPos(), "No font in show");
+    return;
+  }
+  if (fontChanged) {
+    out->updateFont(state);
+    fontChanged = gFalse;
+  }
+  out->beginStringOp(state);
+  doShowText(args[0].getString());
+  out->endStringOp(state);
+}
+
+void Gfx::opMoveShowText(Object args[], int numArgs) {
+  double tx, ty;
+
+  if (!state->getFont()) {
+    error(getPos(), "No font in move/show");
+    return;
+  }
+  if (fontChanged) {
+    out->updateFont(state);
+    fontChanged = gFalse;
+  }
+  tx = state->getLineX();
+  ty = state->getLineY() - state->getLeading();
+  state->textMoveTo(tx, ty);
+  out->updateTextPos(state);
+  out->beginStringOp(state);
+  doShowText(args[0].getString());
+  out->endStringOp(state);
+}
+
+void Gfx::opMoveSetShowText(Object args[], int numArgs) {
+  double tx, ty;
+
+  if (!state->getFont()) {
+    error(getPos(), "No font in move/set/show");
+    return;
+  }
+  if (fontChanged) {
+    out->updateFont(state);
+    fontChanged = gFalse;
+  }
+  state->setWordSpace(args[0].getNum());
+  state->setCharSpace(args[1].getNum());
+  tx = state->getLineX();
+  ty = state->getLineY() - state->getLeading();
+  state->textMoveTo(tx, ty);
+  out->updateWordSpace(state);
+  out->updateCharSpace(state);
+  out->updateTextPos(state);
+  out->beginStringOp(state);
+  doShowText(args[2].getString());
+  out->endStringOp(state);
+}
+
+void Gfx::opShowSpaceText(Object args[], int numArgs) {
+  Array *a;
+  Object obj;
+  int wMode;
+  int i;
+
+  if (!state->getFont()) {
+    error(getPos(), "No font in show/space");
+    return;
+  }
+  if (fontChanged) {
+    out->updateFont(state);
+    fontChanged = gFalse;
+  }
+  out->beginStringOp(state);
+  wMode = state->getFont()->getWMode();
+  a = args[0].getArray();
+  for (i = 0; i < a->getLength(); ++i) {
+    a->get(i, &obj);
+    if (obj.isNum()) {
+      // this uses the absolute value of the font size to match
+      // Acrobat's behavior
+      if (wMode) {
+	state->textShift(0, -obj.getNum() * 0.001 *
+			    fabs(state->getFontSize()));
+      } else {
+	state->textShift(-obj.getNum() * 0.001 *
+			 fabs(state->getFontSize()), 0);
+      }
+      out->updateTextShift(state, obj.getNum());
+    } else if (obj.isString()) {
+      doShowText(obj.getString());
+    } else {
+      error(getPos(), "Element of show/space array must be number or string");
+    }
+    obj.free();
+  }
+  out->endStringOp(state);
+}
+
+void Gfx::doShowText(GString *s) {
+  GfxFont *font;
+  int wMode;
+  double riseX, riseY;
+  CharCode code;
+  Unicode u[8];
+  double x, y, dx, dy, dx2, dy2, curX, curY, tdx, tdy, lineX, lineY;
+  double originX, originY, tOriginX, tOriginY;
+  double oldCTM[6], newCTM[6];
+  double *mat;
+  Object charProc;
+  Dict *resDict;
+  Parser *oldParser;
+  char *p;
+  int len, n, uLen, nChars, nSpaces, i;
+
+  font = state->getFont();
+  wMode = font->getWMode();
+
+  if (out->useDrawChar()) {
+    out->beginString(state, s);
+  }
+
+  // handle a Type 3 char
+  if (font->getType() == fontType3 && out->interpretType3Chars()) {
+    mat = state->getCTM();
+    for (i = 0; i < 6; ++i) {
+      oldCTM[i] = mat[i];
+    }
+    mat = state->getTextMat();
+    newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
+    newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
+    newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
+    newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
+    mat = font->getFontMatrix();
+    newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2];
+    newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3];
+    newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2];
+    newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[3];
+    newCTM[0] *= state->getFontSize();
+    newCTM[1] *= state->getFontSize();
+    newCTM[2] *= state->getFontSize();
+    newCTM[3] *= state->getFontSize();
+    newCTM[0] *= state->getHorizScaling();
+    newCTM[2] *= state->getHorizScaling();
+    state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
+    curX = state->getCurX();
+    curY = state->getCurY();
+    lineX = state->getLineX();
+    lineY = state->getLineY();
+    oldParser = parser;
+    p = s->getCString();
+    len = s->getLength();
+    while (len > 0) {
+      n = font->getNextChar(p, len, &code,
+			    u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
+			    &dx, &dy, &originX, &originY);
+      dx = dx * state->getFontSize() + state->getCharSpace();
+      if (n == 1 && *p == ' ') {
+	dx += state->getWordSpace();
+      }
+      dx *= state->getHorizScaling();
+      dy *= state->getFontSize();
+      state->textTransformDelta(dx, dy, &tdx, &tdy);
+      state->transform(curX + riseX, curY + riseY, &x, &y);
+      saveState();
+      state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
+      //~ the CTM concat values here are wrong (but never used)
+      out->updateCTM(state, 1, 0, 0, 1, 0, 0);
+      if (!out->beginType3Char(state, curX + riseX, curY + riseY, tdx, tdy,
+			       code, u, uLen)) {
+	((Gfx8BitFont *)font)->getCharProc(code, &charProc);
+	if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
+	  pushResources(resDict);
+	}
+	if (charProc.isStream()) {
+	  display(&charProc, gFalse);
+	} else {
+	  error(getPos(), "Missing or bad Type3 CharProc entry");
+	}
+	out->endType3Char(state);
+	if (resDict) {
+	  popResources();
+	}
+	charProc.free();
+      }
+      restoreState();
+      // GfxState::restore() does *not* restore the current position,
+      // so we deal with it here using (curX, curY) and (lineX, lineY)
+      curX += tdx;
+      curY += tdy;
+      state->moveTo(curX, curY);
+      state->textSetPos(lineX, lineY);
+      p += n;
+      len -= n;
+    }
+    parser = oldParser;
+
+  } else if (out->useDrawChar()) {
+    state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
+    p = s->getCString();
+    len = s->getLength();
+    while (len > 0) {
+      n = font->getNextChar(p, len, &code,
+			    u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
+			    &dx, &dy, &originX, &originY);
+      if (wMode) {
+	dx *= state->getFontSize();
+	dy = dy * state->getFontSize() + state->getCharSpace();
+	if (n == 1 && *p == ' ') {
+	  dy += state->getWordSpace();
+	}
+      } else {
+	dx = dx * state->getFontSize() + state->getCharSpace();
+	if (n == 1 && *p == ' ') {
+	  dx += state->getWordSpace();
+	}
+	dx *= state->getHorizScaling();
+	dy *= state->getFontSize();
+      }
+      state->textTransformDelta(dx, dy, &tdx, &tdy);
+      originX *= state->getFontSize();
+      originY *= state->getFontSize();
+      state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
+      out->drawChar(state, state->getCurX() + riseX, state->getCurY() + riseY,
+		    tdx, tdy, tOriginX, tOriginY, code, n, u, uLen);
+      state->shift(tdx, tdy);
+      p += n;
+      len -= n;
+    }
+
+  } else {
+    dx = dy = 0;
+    p = s->getCString();
+    len = s->getLength();
+    nChars = nSpaces = 0;
+    while (len > 0) {
+      n = font->getNextChar(p, len, &code,
+			    u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
+			    &dx2, &dy2, &originX, &originY);
+      dx += dx2;
+      dy += dy2;
+      if (n == 1 && *p == ' ') {
+	++nSpaces;
+      }
+      ++nChars;
+      p += n;
+      len -= n;
+    }
+    if (wMode) {
+      dx *= state->getFontSize();
+      dy = dy * state->getFontSize()
+	   + nChars * state->getCharSpace()
+	   + nSpaces * state->getWordSpace();
+    } else {
+      dx = dx * state->getFontSize()
+	   + nChars * state->getCharSpace()
+	   + nSpaces * state->getWordSpace();
+      dx *= state->getHorizScaling();
+      dy *= state->getFontSize();
+    }
+    state->textTransformDelta(dx, dy, &tdx, &tdy);
+    out->drawString(state, s);
+    state->shift(tdx, tdy);
+  }
+
+  if (out->useDrawChar()) {
+    out->endString(state);
+  }
+
+  updateLevel += 10 * s->getLength();
+}
+
+//------------------------------------------------------------------------
+// XObject operators
+//------------------------------------------------------------------------
+
+void Gfx::opXObject(Object args[], int numArgs) {
+  char *name;
+  Object obj1, obj2, obj3, refObj;
+#if OPI_SUPPORT
+  Object opiDict;
+#endif
+
+  name = args[0].getName();
+  if (!res->lookupXObject(name, &obj1)) {
+    return;
+  }
+  if (!obj1.isStream()) {
+    error(getPos(), "XObject '%s' is wrong type", name);
+    obj1.free();
+    return;
+  }
+#if OPI_SUPPORT
+  obj1.streamGetDict()->lookup("OPI", &opiDict);
+  if (opiDict.isDict()) {
+    out->opiBegin(state, opiDict.getDict());
+  }
+#endif
+  obj1.streamGetDict()->lookup("Subtype", &obj2);
+  if (obj2.isName("Image")) {
+    if (out->needNonText()) {
+      res->lookupXObjectNF(name, &refObj);
+      doImage(&refObj, obj1.getStream(), gFalse);
+      refObj.free();
+    }
+  } else if (obj2.isName("Form")) {
+    res->lookupXObjectNF(name, &refObj);
+    if (out->useDrawForm() && refObj.isRef()) {
+      out->drawForm(refObj.getRef());
+    } else {
+      doForm(&obj1);
+    }
+    refObj.free();
+  } else if (obj2.isName("PS")) {
+    obj1.streamGetDict()->lookup("Level1", &obj3);
+    out->psXObject(obj1.getStream(),
+		   obj3.isStream() ? obj3.getStream() : (Stream *)NULL);
+  } else if (obj2.isName()) {
+    error(getPos(), "Unknown XObject subtype '%s'", obj2.getName());
+  } else {
+    error(getPos(), "XObject subtype is missing or wrong type");
+  }
+  obj2.free();
+#if OPI_SUPPORT
+  if (opiDict.isDict()) {
+    out->opiEnd(state, opiDict.getDict());
+  }
+  opiDict.free();
+#endif
+  obj1.free();
+}
+
+void Gfx::doImage(Object *ref, Stream *str, GBool inlineImg) {
+  Dict *dict, *maskDict;
+  int width, height;
+  int bits, maskBits;
+  StreamColorSpaceMode csMode;
+  GBool mask;
+  GBool invert;
+  GfxColorSpace *colorSpace, *maskColorSpace;
+  GfxImageColorMap *colorMap, *maskColorMap;
+  Object maskObj, smaskObj;
+  GBool haveColorKeyMask, haveExplicitMask, haveSoftMask;
+  int maskColors[2*gfxColorMaxComps];
+  int maskWidth, maskHeight;
+  GBool maskInvert;
+  Stream *maskStr;
+  Object obj1, obj2;
+  int i;
+
+  // get info from the stream
+  bits = 0;
+  csMode = streamCSNone;
+  str->getImageParams(&bits, &csMode);
+
+  // get stream dict
+  dict = str->getDict();
+
+  // get size
+  dict->lookup("Width", &obj1);
+  if (obj1.isNull()) {
+    obj1.free();
+    dict->lookup("W", &obj1);
+  }
+  if (!obj1.isInt())
+    goto err2;
+  width = obj1.getInt();
+  obj1.free();
+  dict->lookup("Height", &obj1);
+  if (obj1.isNull()) {
+    obj1.free();
+    dict->lookup("H", &obj1);
+  }
+  if (!obj1.isInt())
+    goto err2;
+  height = obj1.getInt();
+  obj1.free();
+
+  // image or mask?
+  dict->lookup("ImageMask", &obj1);
+  if (obj1.isNull()) {
+    obj1.free();
+    dict->lookup("IM", &obj1);
+  }
+  mask = gFalse;
+  if (obj1.isBool())
+    mask = obj1.getBool();
+  else if (!obj1.isNull())
+    goto err2;
+  obj1.free();
+
+  // bit depth
+  if (bits == 0) {
+    dict->lookup("BitsPerComponent", &obj1);
+    if (obj1.isNull()) {
+      obj1.free();
+      dict->lookup("BPC", &obj1);
+    }
+    if (obj1.isInt()) {
+      bits = obj1.getInt();
+    } else if (mask) {
+      bits = 1;
+    } else {
+      goto err2;
+    }
+    obj1.free();
+  }
+
+  // display a mask
+  if (mask) {
+
+    // check for inverted mask
+    if (bits != 1)
+      goto err1;
+    invert = gFalse;
+    dict->lookup("Decode", &obj1);
+    if (obj1.isNull()) {
+      obj1.free();
+      dict->lookup("D", &obj1);
+    }
+    if (obj1.isArray()) {
+      obj1.arrayGet(0, &obj2);
+      if (obj2.isInt() && obj2.getInt() == 1)
+	invert = gTrue;
+      obj2.free();
+    } else if (!obj1.isNull()) {
+      goto err2;
+    }
+    obj1.free();
+
+    // draw it
+    out->drawImageMask(state, ref, str, width, height, invert, inlineImg);
+
+  } else {
+
+    // get color space and color map
+    dict->lookup("ColorSpace", &obj1);
+    if (obj1.isNull()) {
+      obj1.free();
+      dict->lookup("CS", &obj1);
+    }
+    if (obj1.isName()) {
+      res->lookupColorSpace(obj1.getName(), &obj2);
+      if (!obj2.isNull()) {
+	obj1.free();
+	obj1 = obj2;
+      } else {
+	obj2.free();
+      }
+    }
+    if (!obj1.isNull()) {
+      colorSpace = GfxColorSpace::parse(&obj1);
+    } else if (csMode == streamCSDeviceGray) {
+      colorSpace = new GfxDeviceGrayColorSpace();
+    } else if (csMode == streamCSDeviceRGB) {
+      colorSpace = new GfxDeviceRGBColorSpace();
+    } else if (csMode == streamCSDeviceCMYK) {
+      colorSpace = new GfxDeviceCMYKColorSpace();
+    } else {
+      colorSpace = NULL;
+    }
+    obj1.free();
+    if (!colorSpace) {
+      goto err1;
+    }
+    dict->lookup("Decode", &obj1);
+    if (obj1.isNull()) {
+      obj1.free();
+      dict->lookup("D", &obj1);
+    }
+    colorMap = new GfxImageColorMap(bits, &obj1, colorSpace);
+    obj1.free();
+    if (!colorMap->isOk()) {
+      delete colorMap;
+      goto err1;
+    }
+
+    // get the mask
+    haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse;
+    maskStr = NULL; // make gcc happy
+    maskWidth = maskHeight = 0; // make gcc happy
+    maskInvert = gFalse; // make gcc happy
+    maskColorMap = NULL; // make gcc happy
+    dict->lookup("Mask", &maskObj);
+    dict->lookup("SMask", &smaskObj);
+    if (smaskObj.isStream()) {
+      // soft mask
+      if (inlineImg) {
+	goto err1;
+      }
+      maskStr = smaskObj.getStream();
+      maskDict = smaskObj.streamGetDict();
+      maskDict->lookup("Width", &obj1);
+      if (obj1.isNull()) {
+	obj1.free();
+	maskDict->lookup("W", &obj1);
+      }
+      if (!obj1.isInt()) {
+	goto err2;
+      }
+      maskWidth = obj1.getInt();
+      obj1.free();
+      maskDict->lookup("Height", &obj1);
+      if (obj1.isNull()) {
+	obj1.free();
+	maskDict->lookup("H", &obj1);
+      }
+      if (!obj1.isInt()) {
+	goto err2;
+      }
+      maskHeight = obj1.getInt();
+      obj1.free();
+      maskDict->lookup("BitsPerComponent", &obj1);
+      if (obj1.isNull()) {
+	obj1.free();
+	maskDict->lookup("BPC", &obj1);
+      }
+      if (!obj1.isInt()) {
+	goto err2;
+      }
+      maskBits = obj1.getInt();
+      obj1.free();
+      maskDict->lookup("ColorSpace", &obj1);
+      if (obj1.isNull()) {
+	obj1.free();
+	maskDict->lookup("CS", &obj1);
+      }
+      if (obj1.isName()) {
+	res->lookupColorSpace(obj1.getName(), &obj2);
+	if (!obj2.isNull()) {
+	  obj1.free();
+	  obj1 = obj2;
+	} else {
+	  obj2.free();
+	}
+      }
+      maskColorSpace = GfxColorSpace::parse(&obj1);
+      obj1.free();
+      if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) {
+	goto err1;
+      }
+      maskDict->lookup("Decode", &obj1);
+      if (obj1.isNull()) {
+	obj1.free();
+	maskDict->lookup("D", &obj1);
+      }
+      maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace);
+      obj1.free();
+      if (!maskColorMap->isOk()) {
+	delete maskColorMap;
+	goto err1;
+      }
+      //~ handle the Matte entry
+      haveSoftMask = gTrue;
+    } else if (maskObj.isArray()) {
+      // color key mask
+      for (i = 0;
+	   i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps;
+	   ++i) {
+	maskObj.arrayGet(i, &obj1);
+	maskColors[i] = obj1.getInt();
+	obj1.free();
+      }
+      haveColorKeyMask = gTrue;
+    } else if (maskObj.isStream()) {
+      // explicit mask
+      if (inlineImg) {
+	goto err1;
+      }
+      maskStr = maskObj.getStream();
+      maskDict = maskObj.streamGetDict();
+      maskDict->lookup("Width", &obj1);
+      if (obj1.isNull()) {
+	obj1.free();
+	maskDict->lookup("W", &obj1);
+      }
+      if (!obj1.isInt()) {
+	goto err2;
+      }
+      maskWidth = obj1.getInt();
+      obj1.free();
+      maskDict->lookup("Height", &obj1);
+      if (obj1.isNull()) {
+	obj1.free();
+	maskDict->lookup("H", &obj1);
+      }
+      if (!obj1.isInt()) {
+	goto err2;
+      }
+      maskHeight = obj1.getInt();
+      obj1.free();
+      maskDict->lookup("ImageMask", &obj1);
+      if (obj1.isNull()) {
+	obj1.free();
+	maskDict->lookup("IM", &obj1);
+      }
+      if (!obj1.isBool() || !obj1.getBool()) {
+	goto err2;
+      }
+      obj1.free();
+      maskInvert = gFalse;
+      maskDict->lookup("Decode", &obj1);
+      if (obj1.isNull()) {
+	obj1.free();
+	maskDict->lookup("D", &obj1);
+      }
+      if (obj1.isArray()) {
+	obj1.arrayGet(0, &obj2);
+	if (obj2.isInt() && obj2.getInt() == 1) {
+	  maskInvert = gTrue;
+	}
+	obj2.free();
+      } else if (!obj1.isNull()) {
+	goto err2;
+      }
+      obj1.free();
+      haveExplicitMask = gTrue;
+    }
+
+    // draw it
+    if (haveSoftMask) {
+      out->drawSoftMaskedImage(state, ref, str, width, height, colorMap,
+			       maskStr, maskWidth, maskHeight, maskColorMap);
+      delete maskColorMap;
+    } else if (haveExplicitMask) {
+      out->drawMaskedImage(state, ref, str, width, height, colorMap,
+			   maskStr, maskWidth, maskHeight, maskInvert);
+    } else {
+      out->drawImage(state, ref, str, width, height, colorMap,
+		     haveColorKeyMask ? maskColors : (int *)NULL, inlineImg);
+    }
+    delete colorMap;
+
+    maskObj.free();
+    smaskObj.free();
+  }
+
+  if ((i = width * height) > 1000) {
+    i = 1000;
+  }
+  updateLevel += i;
+
+  return;
+
+ err2:
+  obj1.free();
+ err1:
+  error(getPos(), "Bad image parameters");
+}
+
+void Gfx::doForm(Object *str) {
+  Dict *dict;
+  GBool transpGroup, isolated, knockout;
+  GfxColorSpace *blendingColorSpace;
+  Object matrixObj, bboxObj;
+  double m[6], bbox[4];
+  Object resObj;
+  Dict *resDict;
+  Object obj1, obj2, obj3;
+  int i;
+
+  // check for excessive recursion
+  if (formDepth > 20) {
+    return;
+  }
+
+  // get stream dict
+  dict = str->streamGetDict();
+
+  // check form type
+  dict->lookup("FormType", &obj1);
+  if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
+    error(getPos(), "Unknown form type");
+  }
+  obj1.free();
+
+  // get bounding box
+  dict->lookup("BBox", &bboxObj);
+  if (!bboxObj.isArray()) {
+    bboxObj.free();
+    error(getPos(), "Bad form bounding box");
+    return;
+  }
+  for (i = 0; i < 4; ++i) {
+    bboxObj.arrayGet(i, &obj1);
+    bbox[i] = obj1.getNum();
+    obj1.free();
+  }
+  bboxObj.free();
+
+  // get matrix
+  dict->lookup("Matrix", &matrixObj);
+  if (matrixObj.isArray()) {
+    for (i = 0; i < 6; ++i) {
+      matrixObj.arrayGet(i, &obj1);
+      m[i] = obj1.getNum();
+      obj1.free();
+    }
+  } else {
+    m[0] = 1; m[1] = 0;
+    m[2] = 0; m[3] = 1;
+    m[4] = 0; m[5] = 0;
+  }
+  matrixObj.free();
+
+  // get resources
+  dict->lookup("Resources", &resObj);
+  resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
+
+  // check for a transparency group
+  transpGroup = isolated = knockout = gFalse;
+  blendingColorSpace = NULL;
+  if (dict->lookup("Group", &obj1)->isDict()) {
+    if (obj1.dictLookup("S", &obj2)->isName("Transparency")) {
+      transpGroup = gTrue;
+      if (!obj1.dictLookup("CS", &obj3)->isNull()) {
+	blendingColorSpace = GfxColorSpace::parse(&obj3);
+      }
+      obj3.free();
+      if (obj1.dictLookup("I", &obj3)->isBool()) {
+	isolated = obj3.getBool();
+      }
+      obj3.free();
+      if (obj1.dictLookup("K", &obj3)->isBool()) {
+	knockout = obj3.getBool();
+      }
+      obj3.free();
+    }
+    obj2.free();
+  }
+  obj1.free();
+
+  // draw it
+  ++formDepth;
+  doForm1(str, resDict, m, bbox,
+	  transpGroup, gFalse, blendingColorSpace, isolated, knockout);
+  --formDepth;
+
+  if (blendingColorSpace) {
+    delete blendingColorSpace;
+  }
+  resObj.free();
+}
+
+void Gfx::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox,
+		  GBool transpGroup, GBool softMask,
+		  GfxColorSpace *blendingColorSpace,
+		  GBool isolated, GBool knockout,
+		  GBool alpha, Function *transferFunc,
+		  GfxColor *backdropColor) {
+  Parser *oldParser;
+  double oldBaseMatrix[6];
+  int i;
+
+  // push new resources on stack
+  pushResources(resDict);
+
+  // save current graphics state
+  saveState();
+
+  // kill any pre-existing path
+  state->clearPath();
+
+  // save current parser
+  oldParser = parser;
+
+  // set form transformation matrix
+  state->concatCTM(matrix[0], matrix[1], matrix[2],
+		   matrix[3], matrix[4], matrix[5]);
+  out->updateCTM(state, matrix[0], matrix[1], matrix[2],
+		 matrix[3], matrix[4], matrix[5]);
+
+  // set form bounding box
+  state->moveTo(bbox[0], bbox[1]);
+  state->lineTo(bbox[2], bbox[1]);
+  state->lineTo(bbox[2], bbox[3]);
+  state->lineTo(bbox[0], bbox[3]);
+  state->closePath();
+  state->clip();
+  out->clip(state);
+  state->clearPath();
+
+  if (softMask || transpGroup) {
+    if (state->getBlendMode() != gfxBlendNormal) {
+      state->setBlendMode(gfxBlendNormal);
+      out->updateBlendMode(state);
+    }
+    if (state->getFillOpacity() != 1) {
+      state->setFillOpacity(1);
+      out->updateFillOpacity(state);
+    }
+    if (state->getStrokeOpacity() != 1) {
+      state->setStrokeOpacity(1);
+      out->updateStrokeOpacity(state);
+    }
+    out->clearSoftMask(state);
+    out->beginTransparencyGroup(state, bbox, blendingColorSpace,
+				isolated, knockout, softMask);
+  }
+
+  // set new base matrix
+  for (i = 0; i < 6; ++i) {
+    oldBaseMatrix[i] = baseMatrix[i];
+    baseMatrix[i] = state->getCTM()[i];
+  }
+
+  // draw the form
+  display(str, gFalse);
+
+  if (softMask || transpGroup) {
+    out->endTransparencyGroup(state);
+  }
+
+  // restore base matrix
+  for (i = 0; i < 6; ++i) {
+    baseMatrix[i] = oldBaseMatrix[i];
+  }
+
+  // restore parser
+  parser = oldParser;
+
+  // restore graphics state
+  restoreState();
+
+  // pop resource stack
+  popResources();
+
+  if (softMask) {
+    out->setSoftMask(state, bbox, alpha, transferFunc, backdropColor);
+  } else if (transpGroup) {
+    out->paintTransparencyGroup(state, bbox);
+  }
+
+  return;
+}
+
+//------------------------------------------------------------------------
+// in-line image operators
+//------------------------------------------------------------------------
+
+void Gfx::opBeginImage(Object args[], int numArgs) {
+  Stream *str;
+  int c1, c2;
+
+  // build dict/stream
+  str = buildImageStream();
+
+  // display the image
+  if (str) {
+    doImage(NULL, str, gTrue);
+  
+    // skip 'EI' tag
+    c1 = str->getUndecodedStream()->getChar();
+    c2 = str->getUndecodedStream()->getChar();
+    while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
+      c1 = c2;
+      c2 = str->getUndecodedStream()->getChar();
+    }
+    delete str;
+  }
+}
+
+Stream *Gfx::buildImageStream() {
+  Object dict;
+  Object obj;
+  char *key;
+  Stream *str;
+
+  // build dictionary
+  dict.initDict(xref);
+  parser->getObj(&obj);
+  while (!obj.isCmd("ID") && !obj.isEOF()) {
+    if (!obj.isName()) {
+      error(getPos(), "Inline image dictionary key must be a name object");
+      obj.free();
+    } else {
+      key = copyString(obj.getName());
+      obj.free();
+      parser->getObj(&obj);
+      if (obj.isEOF() || obj.isError()) {
+	gfree(key);
+	break;
+      }
+      dict.dictAdd(key, &obj);
+    }
+    parser->getObj(&obj);
+  }
+  if (obj.isEOF()) {
+    error(getPos(), "End of file in inline image");
+    obj.free();
+    dict.free();
+    return NULL;
+  }
+  obj.free();
+
+  // make stream
+  str = new EmbedStream(parser->getStream(), &dict, gFalse, 0);
+  str = str->addFilters(&dict);
+
+  return str;
+}
+
+void Gfx::opImageData(Object args[], int numArgs) {
+  error(getPos(), "Internal: got 'ID' operator");
+}
+
+void Gfx::opEndImage(Object args[], int numArgs) {
+  error(getPos(), "Internal: got 'EI' operator");
+}
+
+//------------------------------------------------------------------------
+// type 3 font operators
+//------------------------------------------------------------------------
+
+void Gfx::opSetCharWidth(Object args[], int numArgs) {
+  out->type3D0(state, args[0].getNum(), args[1].getNum());
+}
+
+void Gfx::opSetCacheDevice(Object args[], int numArgs) {
+  out->type3D1(state, args[0].getNum(), args[1].getNum(),
+	       args[2].getNum(), args[3].getNum(),
+	       args[4].getNum(), args[5].getNum());
+}
+
+//------------------------------------------------------------------------
+// compatibility operators
+//------------------------------------------------------------------------
+
+void Gfx::opBeginIgnoreUndef(Object args[], int numArgs) {
+  ++ignoreUndef;
+}
+
+void Gfx::opEndIgnoreUndef(Object args[], int numArgs) {
+  if (ignoreUndef > 0)
+    --ignoreUndef;
+}
+
+//------------------------------------------------------------------------
+// marked content operators
+//------------------------------------------------------------------------
+
+void Gfx::opBeginMarkedContent(Object args[], int numArgs) {
+  if (printCommands) {
+    printf("  marked content: %s ", args[0].getName());
+    if (numArgs == 2)
+      args[2].print(stdout);
+    printf("\n");
+    fflush(stdout);
+  }
+}
+
+void Gfx::opEndMarkedContent(Object args[], int numArgs) {
+}
+
+void Gfx::opMarkPoint(Object args[], int numArgs) {
+  if (printCommands) {
+    printf("  mark point: %s ", args[0].getName());
+    if (numArgs == 2)
+      args[2].print(stdout);
+    printf("\n");
+    fflush(stdout);
+  }
+}
+
+//------------------------------------------------------------------------
+// misc
+//------------------------------------------------------------------------
+
+void Gfx::drawAnnot(Object *str, AnnotBorderStyle *borderStyle,
+		    double xMin, double yMin, double xMax, double yMax) {
+  Dict *dict, *resDict;
+  Object matrixObj, bboxObj, resObj;
+  Object obj1;
+  double m[6], bbox[4], ictm[6];
+  double *ctm;
+  double formX0, formY0, formX1, formY1;
+  double annotX0, annotY0, annotX1, annotY1;
+  double det, x, y, sx, sy;
+  double r, g, b;
+  GfxColor color;
+  double *dash, *dash2;
+  int dashLength;
+  int i;
+
+  //~ can we assume that we're in default user space?
+  //~ (i.e., baseMatrix = ctm)
+
+  // transform the annotation bbox from default user space to user
+  // space: (bbox * baseMatrix) * iCTM
+  ctm = state->getCTM();
+  det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
+  ictm[0] = ctm[3] * det;
+  ictm[1] = -ctm[1] * det;
+  ictm[2] = -ctm[2] * det;
+  ictm[3] = ctm[0] * det;
+  ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
+  ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
+  x = baseMatrix[0] * xMin + baseMatrix[2] * yMin + baseMatrix[4];
+  y = baseMatrix[1] * xMin + baseMatrix[3] * yMin + baseMatrix[5];
+  annotX0 = ictm[0] * x + ictm[2] * y + ictm[4];
+  annotY0 = ictm[1] * x + ictm[3] * y + ictm[5];
+  x = baseMatrix[0] * xMax + baseMatrix[2] * yMax + baseMatrix[4];
+  y = baseMatrix[1] * xMax + baseMatrix[3] * yMax + baseMatrix[5];
+  annotX1 = ictm[0] * x + ictm[2] * y + ictm[4];
+  annotY1 = ictm[1] * x + ictm[3] * y + ictm[5];
+  if (annotX0 > annotX1) {
+    x = annotX0; annotX0 = annotX1; annotX1 = x;
+  }
+  if (annotY0 > annotY1) {
+    y = annotY0; annotY0 = annotY1; annotY1 = y;
+  }
+
+  // draw the appearance stream (if there is one)
+  if (str->isStream()) {
+
+    // get stream dict
+    dict = str->streamGetDict();
+
+    // get the form bounding box
+    dict->lookup("BBox", &bboxObj);
+    if (!bboxObj.isArray()) {
+      bboxObj.free();
+      error(getPos(), "Bad form bounding box");
+      return;
+    }
+    for (i = 0; i < 4; ++i) {
+      bboxObj.arrayGet(i, &obj1);
+      bbox[i] = obj1.getNum();
+      obj1.free();
+    }
+    bboxObj.free();
+
+    // get the form matrix
+    dict->lookup("Matrix", &matrixObj);
+    if (matrixObj.isArray()) {
+      for (i = 0; i < 6; ++i) {
+	matrixObj.arrayGet(i, &obj1);
+	m[i] = obj1.getNum();
+	obj1.free();
+      }
+    } else {
+      m[0] = 1; m[1] = 0;
+      m[2] = 0; m[3] = 1;
+      m[4] = 0; m[5] = 0;
+    }
+    matrixObj.free();
+
+    // transform the form bbox from form space to user space
+    formX0 = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
+    formY0 = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
+    formX1 = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
+    formY1 = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
+    if (formX0 > formX1) {
+      x = formX0; formX0 = formX1; formX1 = x;
+    }
+    if (formY0 > formY1) {
+      y = formY0; formY0 = formY1; formY1 = y;
+    }
+
+    // scale the form to fit the annotation bbox
+    if (formX1 == formX0) {
+      // this shouldn't happen
+      sx = 1;
+    } else {
+      sx = (annotX1 - annotX0) / (formX1 - formX0);
+    }
+    if (formY1 == formY0) {
+      // this shouldn't happen
+      sy = 1;
+    } else {
+      sy = (annotY1 - annotY0) / (formY1 - formY0);
+    }
+    m[0] *= sx;
+    m[2] *= sx;
+    m[4] = (m[4] - formX0) * sx + annotX0;
+    m[1] *= sy;
+    m[3] *= sy;
+    m[5] = (m[5] - formY0) * sy + annotY0;
+
+    // get resources
+    dict->lookup("Resources", &resObj);
+    resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
+
+    // draw it
+    doForm1(str, resDict, m, bbox);
+
+    resObj.free();
+  }
+
+  // draw the border
+  if (borderStyle && borderStyle->getWidth() > 0) {
+    if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) {
+      state->setStrokePattern(NULL);
+      state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
+      out->updateStrokeColorSpace(state);
+    }
+    borderStyle->getColor(&r, &g, &b);
+    color.c[0] = dblToCol(r);
+    color.c[1] = dblToCol(g);
+    color.c[2] = dblToCol(b);
+    state->setStrokeColor(&color);
+    out->updateStrokeColor(state);
+    // compute the width scale factor when going from default user
+    // space to user space
+    x = (baseMatrix[0] + baseMatrix[2]) * ictm[0] +
+        (baseMatrix[1] + baseMatrix[3]) * ictm[2];
+    y = (baseMatrix[0] + baseMatrix[2]) * ictm[1] +
+        (baseMatrix[1] + baseMatrix[3]) * ictm[3];
+    x = sqrt(0.5 * (x * x + y * y));
+    state->setLineWidth(x * borderStyle->getWidth());
+    out->updateLineWidth(state);
+    borderStyle->getDash(&dash, &dashLength);
+    if (borderStyle->getType() == annotBorderDashed && dashLength > 0) {
+      dash2 = (double *)gmallocn(dashLength, sizeof(double));
+      for (i = 0; i < dashLength; ++i) {
+	dash2[i] = x * dash[i];
+      }
+      state->setLineDash(dash2, dashLength, 0);
+      out->updateLineDash(state);
+    }
+    //~ this doesn't currently handle the beveled and engraved styles
+    state->clearPath();
+    state->moveTo(annotX0, out->upsideDown() ? annotY1 : annotY0);
+    state->lineTo(annotX1, out->upsideDown() ? annotY1 : annotY0);
+    if (borderStyle->getType() != annotBorderUnderlined) {
+      state->lineTo(annotX1, out->upsideDown() ? annotY0 : annotY1);
+      state->lineTo(annotX0, out->upsideDown() ? annotY0 : annotY1);
+      state->closePath();
+    }
+    out->stroke(state);
+  }
+}
+
+void Gfx::saveState() {
+  out->saveState(state);
+  state = state->save();
+}
+
+void Gfx::restoreState() {
+  state = state->restore();
+  out->restoreState(state);
+}
+
+void Gfx::pushResources(Dict *resDict) {
+  res = new GfxResources(xref, resDict, res);
+}
+
+void Gfx::popResources() {
+  GfxResources *resPtr;
+
+  resPtr = res->getNext();
+  delete res;
+  res = resPtr;
+}
diff --git a/xpdf/Gfx.h b/xpdf/Gfx.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Gfx.h
@@ -0,0 +1,312 @@
+//========================================================================
+//
+// Gfx.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef GFX_H
+#define GFX_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+
+class GString;
+class XRef;
+class Array;
+class Stream;
+class Parser;
+class Dict;
+class Function;
+class OutputDev;
+class GfxFontDict;
+class GfxFont;
+class GfxPattern;
+class GfxTilingPattern;
+class GfxShadingPattern;
+class GfxShading;
+class GfxFunctionShading;
+class GfxAxialShading;
+class GfxRadialShading;
+class GfxGouraudTriangleShading;
+class GfxPatchMeshShading;
+struct GfxPatch;
+class GfxState;
+struct GfxColor;
+class GfxColorSpace;
+class Gfx;
+class PDFRectangle;
+class AnnotBorderStyle;
+
+//------------------------------------------------------------------------
+
+enum GfxClipType {
+  clipNone,
+  clipNormal,
+  clipEO
+};
+
+enum TchkType {
+  tchkBool,			// boolean
+  tchkInt,			// integer
+  tchkNum,			// number (integer or real)
+  tchkString,			// string
+  tchkName,			// name
+  tchkArray,			// array
+  tchkProps,			// properties (dictionary or name)
+  tchkSCN,			// scn/SCN args (number of name)
+  tchkNone			// used to avoid empty initializer lists
+};
+
+#define maxArgs 33
+
+struct Operator {
+  char name[4];
+  int numArgs;
+  TchkType tchk[maxArgs];
+  void (Gfx::*func)(Object args[], int numArgs);
+};
+
+//------------------------------------------------------------------------
+
+class GfxResources {
+public:
+
+  GfxResources(XRef *xref, Dict *resDict, GfxResources *nextA);
+  ~GfxResources();
+
+  GfxFont *lookupFont(char *name);
+  GBool lookupXObject(char *name, Object *obj);
+  GBool lookupXObjectNF(char *name, Object *obj);
+  void lookupColorSpace(char *name, Object *obj);
+  GfxPattern *lookupPattern(char *name);
+  GfxShading *lookupShading(char *name);
+  GBool lookupGState(char *name, Object *obj);
+
+  GfxResources *getNext() { return next; }
+
+private:
+
+  GfxFontDict *fonts;
+  Object xObjDict;
+  Object colorSpaceDict;
+  Object patternDict;
+  Object shadingDict;
+  Object gStateDict;
+  GfxResources *next;
+};
+
+//------------------------------------------------------------------------
+// Gfx
+//------------------------------------------------------------------------
+
+class Gfx {
+public:
+
+  // Constructor for regular output.
+  Gfx(XRef *xrefA, OutputDev *outA, int pageNum, Dict *resDict,
+      double hDPI, double vDPI, PDFRectangle *box,
+      PDFRectangle *cropBox, int rotate,
+      GBool (*abortCheckCbkA)(void *data) = NULL,
+      void *abortCheckCbkDataA = NULL);
+
+  // Constructor for a sub-page object.
+  Gfx(XRef *xrefA, OutputDev *outA, Dict *resDict,
+      PDFRectangle *box, PDFRectangle *cropBox,
+      GBool (*abortCheckCbkA)(void *data) = NULL,
+      void *abortCheckCbkDataA = NULL);
+
+  ~Gfx();
+
+  // Interpret a stream or array of streams.
+  void display(Object *obj, GBool topLevel = gTrue);
+
+  // Display an annotation, given its appearance (a Form XObject),
+  // border style, and bounding box (in default user space).
+  void drawAnnot(Object *str, AnnotBorderStyle *borderStyle,
+		 double xMin, double yMin, double xMax, double yMax);
+
+  // Save graphics state.
+  void saveState();
+
+  // Restore graphics state.
+  void restoreState();
+
+  // Get the current graphics state object.
+  GfxState *getState() { return state; }
+
+private:
+
+  XRef *xref;			// the xref table for this PDF file
+  OutputDev *out;		// output device
+  GBool subPage;		// is this a sub-page object?
+  GBool printCommands;		// print the drawing commands (for debugging)
+  GfxResources *res;		// resource stack
+  int updateLevel;
+
+  GfxState *state;		// current graphics state
+  GBool fontChanged;		// set if font or text matrix has changed
+  GfxClipType clip;		// do a clip?
+  int ignoreUndef;		// current BX/EX nesting level
+  double baseMatrix[6];		// default matrix for most recent
+				//   page/form/pattern
+  int formDepth;
+
+  Parser *parser;		// parser for page content stream(s)
+
+  GBool				// callback to check for an abort
+    (*abortCheckCbk)(void *data);
+  void *abortCheckCbkData;
+
+  static Operator opTab[];	// table of operators
+
+  void go(GBool topLevel);
+  void execOp(Object *cmd, Object args[], int numArgs);
+  Operator *findOp(char *name);
+  GBool checkArg(Object *arg, TchkType type);
+  int getPos();
+
+  // graphics state operators
+  void opSave(Object args[], int numArgs);
+  void opRestore(Object args[], int numArgs);
+  void opConcat(Object args[], int numArgs);
+  void opSetDash(Object args[], int numArgs);
+  void opSetFlat(Object args[], int numArgs);
+  void opSetLineJoin(Object args[], int numArgs);
+  void opSetLineCap(Object args[], int numArgs);
+  void opSetMiterLimit(Object args[], int numArgs);
+  void opSetLineWidth(Object args[], int numArgs);
+  void opSetExtGState(Object args[], int numArgs);
+  void doSoftMask(Object *str, GBool alpha,
+		  GfxColorSpace *blendingColorSpace,
+		  GBool isolated, GBool knockout,
+		  Function *transferFunc, GfxColor *backdropColor);
+  void opSetRenderingIntent(Object args[], int numArgs);
+
+  // color operators
+  void opSetFillGray(Object args[], int numArgs);
+  void opSetStrokeGray(Object args[], int numArgs);
+  void opSetFillCMYKColor(Object args[], int numArgs);
+  void opSetStrokeCMYKColor(Object args[], int numArgs);
+  void opSetFillRGBColor(Object args[], int numArgs);
+  void opSetStrokeRGBColor(Object args[], int numArgs);
+  void opSetFillColorSpace(Object args[], int numArgs);
+  void opSetStrokeColorSpace(Object args[], int numArgs);
+  void opSetFillColor(Object args[], int numArgs);
+  void opSetStrokeColor(Object args[], int numArgs);
+  void opSetFillColorN(Object args[], int numArgs);
+  void opSetStrokeColorN(Object args[], int numArgs);
+
+  // path segment operators
+  void opMoveTo(Object args[], int numArgs);
+  void opLineTo(Object args[], int numArgs);
+  void opCurveTo(Object args[], int numArgs);
+  void opCurveTo1(Object args[], int numArgs);
+  void opCurveTo2(Object args[], int numArgs);
+  void opRectangle(Object args[], int numArgs);
+  void opClosePath(Object args[], int numArgs);
+
+  // path painting operators
+  void opEndPath(Object args[], int numArgs);
+  void opStroke(Object args[], int numArgs);
+  void opCloseStroke(Object args[], int numArgs);
+  void opFill(Object args[], int numArgs);
+  void opEOFill(Object args[], int numArgs);
+  void opFillStroke(Object args[], int numArgs);
+  void opCloseFillStroke(Object args[], int numArgs);
+  void opEOFillStroke(Object args[], int numArgs);
+  void opCloseEOFillStroke(Object args[], int numArgs);
+  void doPatternFill(GBool eoFill);
+  void doPatternStroke();
+  void doTilingPatternFill(GfxTilingPattern *tPat,
+			   GBool stroke, GBool eoFill);
+  void doShadingPatternFill(GfxShadingPattern *sPat,
+			    GBool stroke, GBool eoFill);
+  void opShFill(Object args[], int numArgs);
+  void doFunctionShFill(GfxFunctionShading *shading);
+  void doFunctionShFill1(GfxFunctionShading *shading,
+			 double x0, double y0,
+			 double x1, double y1,
+			 GfxColor *colors, int depth);
+  void doAxialShFill(GfxAxialShading *shading);
+  void doRadialShFill(GfxRadialShading *shading);
+  void doGouraudTriangleShFill(GfxGouraudTriangleShading *shading);
+  void gouraudFillTriangle(double x0, double y0, GfxColor *color0,
+			   double x1, double y1, GfxColor *color1,
+			   double x2, double y2, GfxColor *color2,
+			   int nComps, int depth);
+  void doPatchMeshShFill(GfxPatchMeshShading *shading);
+  void fillPatch(GfxPatch *patch, int nComps, int depth);
+  void doEndPath();
+
+  // path clipping operators
+  void opClip(Object args[], int numArgs);
+  void opEOClip(Object args[], int numArgs);
+
+  // text object operators
+  void opBeginText(Object args[], int numArgs);
+  void opEndText(Object args[], int numArgs);
+
+  // text state operators
+  void opSetCharSpacing(Object args[], int numArgs);
+  void opSetFont(Object args[], int numArgs);
+  void opSetTextLeading(Object args[], int numArgs);
+  void opSetTextRender(Object args[], int numArgs);
+  void opSetTextRise(Object args[], int numArgs);
+  void opSetWordSpacing(Object args[], int numArgs);
+  void opSetHorizScaling(Object args[], int numArgs);
+
+  // text positioning operators
+  void opTextMove(Object args[], int numArgs);
+  void opTextMoveSet(Object args[], int numArgs);
+  void opSetTextMatrix(Object args[], int numArgs);
+  void opTextNextLine(Object args[], int numArgs);
+
+  // text string operators
+  void opShowText(Object args[], int numArgs);
+  void opMoveShowText(Object args[], int numArgs);
+  void opMoveSetShowText(Object args[], int numArgs);
+  void opShowSpaceText(Object args[], int numArgs);
+  void doShowText(GString *s);
+
+  // XObject operators
+  void opXObject(Object args[], int numArgs);
+  void doImage(Object *ref, Stream *str, GBool inlineImg);
+  void doForm(Object *str);
+  void doForm1(Object *str, Dict *resDict, double *matrix, double *bbox,
+	       GBool transpGroup = gFalse, GBool softMask = gFalse,
+	       GfxColorSpace *blendingColorSpace = NULL,
+	       GBool isolated = gFalse, GBool knockout = gFalse,
+	       GBool alpha = gFalse, Function *transferFunc = NULL,
+	       GfxColor *backdropColor = NULL);
+
+  // in-line image operators
+  void opBeginImage(Object args[], int numArgs);
+  Stream *buildImageStream();
+  void opImageData(Object args[], int numArgs);
+  void opEndImage(Object args[], int numArgs);
+
+  // type 3 font operators
+  void opSetCharWidth(Object args[], int numArgs);
+  void opSetCacheDevice(Object args[], int numArgs);
+
+  // compatibility operators
+  void opBeginIgnoreUndef(Object args[], int numArgs);
+  void opEndIgnoreUndef(Object args[], int numArgs);
+
+  // marked content operators
+  void opBeginMarkedContent(Object args[], int numArgs);
+  void opEndMarkedContent(Object args[], int numArgs);
+  void opMarkPoint(Object args[], int numArgs);
+
+  void pushResources(Dict *resDict);
+  void popResources();
+};
+
+#endif
diff --git a/xpdf/GfxFont.cc b/xpdf/GfxFont.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/GfxFont.cc
@@ -0,0 +1,1568 @@
+//========================================================================
+//
+// GfxFont.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "gmem.h"
+#include "Error.h"
+#include "Object.h"
+#include "Dict.h"
+#include "GlobalParams.h"
+#include "CMap.h"
+#include "CharCodeToUnicode.h"
+#include "FontEncodingTables.h"
+#include "BuiltinFontTables.h"
+#include "FoFiType1.h"
+#include "FoFiType1C.h"
+#include "FoFiTrueType.h"
+#include "GfxFont.h"
+
+//------------------------------------------------------------------------
+
+struct StdFontMapEntry {
+  char *altName;
+  char *properName;
+};
+
+// Acrobat 4.0 and earlier substituted Base14-compatible fonts without
+// providing Widths and a FontDescriptor, so we munge the names into
+// the proper Base14 names.  This table is from implementation note 44
+// in the PDF 1.4 spec, with some additions based on empirical
+// evidence.
+static StdFontMapEntry stdFontMap[] = {
+  { "Arial",                        "Helvetica" },
+  { "Arial,Bold",                   "Helvetica-Bold" },
+  { "Arial,BoldItalic",             "Helvetica-BoldOblique" },
+  { "Arial,Italic",                 "Helvetica-Oblique" },
+  { "Arial-Bold",                   "Helvetica-Bold" },
+  { "Arial-BoldItalic",             "Helvetica-BoldOblique" },
+  { "Arial-BoldItalicMT",           "Helvetica-BoldOblique" },
+  { "Arial-BoldMT",                 "Helvetica-Bold" },
+  { "Arial-Italic",                 "Helvetica-Oblique" },
+  { "Arial-ItalicMT",               "Helvetica-Oblique" },
+  { "ArialMT",                      "Helvetica" },
+  { "Courier,Bold",                 "Courier-Bold" },
+  { "Courier,BoldItalic",           "Courier-BoldOblique" },
+  { "Courier,Italic",               "Courier-Oblique" },
+  { "CourierNew",                   "Courier" },
+  { "CourierNew,Bold",              "Courier-Bold" },
+  { "CourierNew,BoldItalic",        "Courier-BoldOblique" },
+  { "CourierNew,Italic",            "Courier-Oblique" },
+  { "CourierNew-Bold",              "Courier-Bold" },
+  { "CourierNew-BoldItalic",        "Courier-BoldOblique" },
+  { "CourierNew-Italic",            "Courier-Oblique" },
+  { "CourierNewPS-BoldItalicMT",    "Courier-BoldOblique" },
+  { "CourierNewPS-BoldMT",          "Courier-Bold" },
+  { "CourierNewPS-ItalicMT",        "Courier-Oblique" },
+  { "CourierNewPSMT",               "Courier" },
+  { "Helvetica,Bold",               "Helvetica-Bold" },
+  { "Helvetica,BoldItalic",         "Helvetica-BoldOblique" },
+  { "Helvetica,Italic",             "Helvetica-Oblique" },
+  { "Helvetica-BoldItalic",         "Helvetica-BoldOblique" },
+  { "Helvetica-Italic",             "Helvetica-Oblique" },
+  { "Symbol,Bold",                  "Symbol" },
+  { "Symbol,BoldItalic",            "Symbol" },
+  { "Symbol,Italic",                "Symbol" },
+  { "TimesNewRoman",                "Times-Roman" },
+  { "TimesNewRoman,Bold",           "Times-Bold" },
+  { "TimesNewRoman,BoldItalic",     "Times-BoldItalic" },
+  { "TimesNewRoman,Italic",         "Times-Italic" },
+  { "TimesNewRoman-Bold",           "Times-Bold" },
+  { "TimesNewRoman-BoldItalic",     "Times-BoldItalic" },
+  { "TimesNewRoman-Italic",         "Times-Italic" },
+  { "TimesNewRomanPS",              "Times-Roman" },
+  { "TimesNewRomanPS-Bold",         "Times-Bold" },
+  { "TimesNewRomanPS-BoldItalic",   "Times-BoldItalic" },
+  { "TimesNewRomanPS-BoldItalicMT", "Times-BoldItalic" },
+  { "TimesNewRomanPS-BoldMT",       "Times-Bold" },
+  { "TimesNewRomanPS-Italic",       "Times-Italic" },
+  { "TimesNewRomanPS-ItalicMT",     "Times-Italic" },
+  { "TimesNewRomanPSMT",            "Times-Roman" },
+  { "TimesNewRomanPSMT,Bold",       "Times-Bold" },
+  { "TimesNewRomanPSMT,BoldItalic", "Times-BoldItalic" },
+  { "TimesNewRomanPSMT,Italic",     "Times-Italic" }
+};
+
+//------------------------------------------------------------------------
+// GfxFont
+//------------------------------------------------------------------------
+
+GfxFont *GfxFont::makeFont(XRef *xref, char *tagA, Ref idA, Dict *fontDict) {
+  GString *nameA;
+  GfxFont *font;
+  Object obj1;
+
+  // get base font name
+  nameA = NULL;
+  fontDict->lookup("BaseFont", &obj1);
+  if (obj1.isName()) {
+    nameA = new GString(obj1.getName());
+  }
+  obj1.free();
+
+  // get font type
+  font = NULL;
+  fontDict->lookup("Subtype", &obj1);
+  if (obj1.isName("Type1") || obj1.isName("MMType1")) {
+    font = new Gfx8BitFont(xref, tagA, idA, nameA, fontType1, fontDict);
+  } else if (obj1.isName("Type1C")) {
+    font = new Gfx8BitFont(xref, tagA, idA, nameA, fontType1C, fontDict);
+  } else if (obj1.isName("Type3")) {
+    font = new Gfx8BitFont(xref, tagA, idA, nameA, fontType3, fontDict);
+  } else if (obj1.isName("TrueType")) {
+    font = new Gfx8BitFont(xref, tagA, idA, nameA, fontTrueType, fontDict);
+  } else if (obj1.isName("Type0")) {
+    font = new GfxCIDFont(xref, tagA, idA, nameA, fontDict);
+  } else {
+    error(-1, "Unknown font type: '%s'",
+	  obj1.isName() ? obj1.getName() : "???");
+    font = new Gfx8BitFont(xref, tagA, idA, nameA, fontUnknownType, fontDict);
+  }
+  obj1.free();
+
+  return font;
+}
+
+GfxFont::GfxFont(char *tagA, Ref idA, GString *nameA) {
+  ok = gFalse;
+  tag = new GString(tagA);
+  id = idA;
+  name = nameA;
+  origName = nameA;
+  embFontName = NULL;
+  extFontFile = NULL;
+}
+
+GfxFont::~GfxFont() {
+  delete tag;
+  if (origName && origName != name) {
+    delete origName;
+  }
+  if (name) {
+    delete name;
+  }
+  if (embFontName) {
+    delete embFontName;
+  }
+  if (extFontFile) {
+    delete extFontFile;
+  }
+}
+
+void GfxFont::readFontDescriptor(XRef *xref, Dict *fontDict) {
+  Object obj1, obj2, obj3, obj4;
+  double t;
+  int i;
+
+  // assume Times-Roman by default (for substitution purposes)
+  flags = fontSerif;
+
+  embFontID.num = -1;
+  embFontID.gen = -1;
+  missingWidth = 0;
+
+  if (fontDict->lookup("FontDescriptor", &obj1)->isDict()) {
+
+    // get flags
+    if (obj1.dictLookup("Flags", &obj2)->isInt()) {
+      flags = obj2.getInt();
+    }
+    obj2.free();
+
+    // get name
+    obj1.dictLookup("FontName", &obj2);
+    if (obj2.isName()) {
+      embFontName = new GString(obj2.getName());
+    }
+    obj2.free();
+
+    // look for embedded font file
+    if (obj1.dictLookupNF("FontFile", &obj2)->isRef()) {
+      embFontID = obj2.getRef();
+      if (type != fontType1) {
+	error(-1, "Mismatch between font type and embedded font file");
+	type = fontType1;
+      }
+    }
+    obj2.free();
+    if (embFontID.num == -1 &&
+	obj1.dictLookupNF("FontFile2", &obj2)->isRef()) {
+      embFontID = obj2.getRef();
+      if (type != fontTrueType && type != fontCIDType2) {
+	error(-1, "Mismatch between font type and embedded font file");
+	type = type == fontCIDType0 ? fontCIDType2 : fontTrueType;
+      }
+    }
+    obj2.free();
+    if (embFontID.num == -1 &&
+	obj1.dictLookupNF("FontFile3", &obj2)->isRef()) {
+      if (obj2.fetch(xref, &obj3)->isStream()) {
+	obj3.streamGetDict()->lookup("Subtype", &obj4);
+	if (obj4.isName("Type1")) {
+	  embFontID = obj2.getRef();
+	  if (type != fontType1) {
+	    error(-1, "Mismatch between font type and embedded font file");
+	    type = fontType1;
+	  }
+	} else if (obj4.isName("Type1C")) {
+	  embFontID = obj2.getRef();
+	  if (type != fontType1 && type != fontType1C) {
+	    error(-1, "Mismatch between font type and embedded font file");
+	  }
+	  type = fontType1C;
+	} else if (obj4.isName("TrueType")) {
+	  embFontID = obj2.getRef();
+	  if (type != fontTrueType) {
+	    error(-1, "Mismatch between font type and embedded font file");
+	    type = fontTrueType;
+	  }
+	} else if (obj4.isName("CIDFontType0C")) {
+	  embFontID = obj2.getRef();
+	  if (type != fontCIDType0) {
+	    error(-1, "Mismatch between font type and embedded font file");
+	  }
+	  type = fontCIDType0C;
+	} else if (obj4.isName("OpenType")) {
+	  embFontID = obj2.getRef();
+	  if (type == fontTrueType) {
+	    type = fontTrueTypeOT;
+	  } else if (type == fontType1) {
+	    type = fontType1COT;
+	  } else if (type == fontCIDType0) {
+	    type = fontCIDType0COT;
+	  } else if (type == fontCIDType2) {
+	    type = fontCIDType2OT;
+	  } else {
+	    error(-1, "Mismatch between font type and embedded font file");
+	  }
+	} else {
+	  error(-1, "Unknown embedded font type '%s'",
+		obj4.isName() ? obj4.getName() : "???");
+	}
+	obj4.free();
+      }
+      obj3.free();
+    }
+    obj2.free();
+
+    // look for MissingWidth
+    obj1.dictLookup("MissingWidth", &obj2);
+    if (obj2.isNum()) {
+      missingWidth = obj2.getNum();
+    }
+    obj2.free();
+
+    // get Ascent and Descent
+    obj1.dictLookup("Ascent", &obj2);
+    if (obj2.isNum()) {
+      t = 0.001 * obj2.getNum();
+      // some broken font descriptors set ascent and descent to 0
+      if (t != 0) {
+	ascent = t;
+      }
+    }
+    obj2.free();
+    obj1.dictLookup("Descent", &obj2);
+    if (obj2.isNum()) {
+      t = 0.001 * obj2.getNum();
+      // some broken font descriptors set ascent and descent to 0
+      if (t != 0) {
+	descent = t;
+      }
+      // some broken font descriptors specify a positive descent
+      if (descent > 0) {
+	descent = -descent;
+      }
+    }
+    obj2.free();
+
+    // font FontBBox
+    if (obj1.dictLookup("FontBBox", &obj2)->isArray()) {
+      for (i = 0; i < 4 && i < obj2.arrayGetLength(); ++i) {
+	if (obj2.arrayGet(i, &obj3)->isNum()) {
+	  fontBBox[i] = 0.001 * obj3.getNum();
+	}
+	obj3.free();
+      }
+    }
+    obj2.free();
+
+  }
+  obj1.free();
+}
+
+CharCodeToUnicode *GfxFont::readToUnicodeCMap(Dict *fontDict, int nBits,
+					      CharCodeToUnicode *ctu) {
+  GString *buf;
+  Object obj1;
+  int c;
+
+  if (!fontDict->lookup("ToUnicode", &obj1)->isStream()) {
+    obj1.free();
+    return NULL;
+  }
+  buf = new GString();
+  obj1.streamReset();
+  while ((c = obj1.streamGetChar()) != EOF) {
+    buf->append(c);
+  }
+  obj1.streamClose();
+  obj1.free();
+  if (ctu) {
+    ctu->mergeCMap(buf, nBits);
+  } else {
+    ctu = CharCodeToUnicode::parseCMap(buf, nBits);
+  }
+  delete buf;
+  return ctu;
+}
+
+void GfxFont::findExtFontFile() {
+  static char *type1Exts[] = { ".pfa", ".pfb", ".ps", "", NULL };
+  static char *ttExts[] = { ".ttf", NULL };
+
+  if (name) {
+    if (type == fontType1) {
+      extFontFile = globalParams->findFontFile(name, type1Exts);
+    } else if (type == fontTrueType) {
+      extFontFile = globalParams->findFontFile(name, ttExts);
+    }
+  }
+}
+
+char *GfxFont::readExtFontFile(int *len) {
+  FILE *f;
+  char *buf;
+
+  if (!(f = fopen(extFontFile->getCString(), "rb"))) {
+    error(-1, "External font file '%s' vanished", extFontFile->getCString());
+    return NULL;
+  }
+  fseek(f, 0, SEEK_END);
+  *len = (int)ftell(f);
+  fseek(f, 0, SEEK_SET);
+  buf = (char *)gmalloc(*len);
+  if ((int)fread(buf, 1, *len, f) != *len) {
+    error(-1, "Error reading external font file '%s'",
+	  extFontFile->getCString());
+  }
+  fclose(f);
+  return buf;
+}
+
+char *GfxFont::readEmbFontFile(XRef *xref, int *len) {
+  char *buf;
+  Object obj1, obj2;
+  Stream *str;
+  int c;
+  int size, i;
+
+  obj1.initRef(embFontID.num, embFontID.gen);
+  obj1.fetch(xref, &obj2);
+  if (!obj2.isStream()) {
+    error(-1, "Embedded font file is not a stream");
+    obj2.free();
+    obj1.free();
+    embFontID.num = -1;
+    return NULL;
+  }
+  str = obj2.getStream();
+
+  buf = NULL;
+  i = size = 0;
+  str->reset();
+  while ((c = str->getChar()) != EOF) {
+    if (i == size) {
+      size += 4096;
+      buf = (char *)grealloc(buf, size);
+    }
+    buf[i++] = c;
+  }
+  *len = i;
+  str->close();
+
+  obj2.free();
+  obj1.free();
+
+  return buf;
+}
+
+//------------------------------------------------------------------------
+// Gfx8BitFont
+//------------------------------------------------------------------------
+
+Gfx8BitFont::Gfx8BitFont(XRef *xref, char *tagA, Ref idA, GString *nameA,
+			 GfxFontType typeA, Dict *fontDict):
+  GfxFont(tagA, idA, nameA)
+{
+  GString *name2;
+  BuiltinFont *builtinFont;
+  char **baseEnc;
+  GBool baseEncFromFontFile;
+  char *buf;
+  int len;
+  FoFiType1 *ffT1;
+  FoFiType1C *ffT1C;
+  int code, code2;
+  char *charName;
+  GBool missing, hex;
+  Unicode toUnicode[256];
+  CharCodeToUnicode *utu, *ctu2;
+  Unicode uBuf[8];
+  double mul;
+  int firstChar, lastChar;
+  Gushort w;
+  Object obj1, obj2, obj3;
+  int n, i, a, b, m;
+
+  type = typeA;
+  ctu = NULL;
+
+  // do font name substitution for various aliases of the Base 14 font
+  // names
+  if (name) {
+    name2 = name->copy();
+    i = 0;
+    while (i < name2->getLength()) {
+      if (name2->getChar(i) == ' ') {
+	name2->del(i);
+      } else {
+	++i;
+      }
+    }
+    a = 0;
+    b = sizeof(stdFontMap) / sizeof(StdFontMapEntry);
+    // invariant: stdFontMap[a].altName <= name2 < stdFontMap[b].altName
+    while (b - a > 1) {
+      m = (a + b) / 2;
+      if (name2->cmp(stdFontMap[m].altName) >= 0) {
+	a = m;
+      } else {
+	b = m;
+      }
+    }
+    if (!name2->cmp(stdFontMap[a].altName)) {
+      name = new GString(stdFontMap[a].properName);
+    }
+    delete name2;
+  }
+
+  // is it a built-in font?
+  builtinFont = NULL;
+  if (name) {
+    for (i = 0; i < nBuiltinFonts; ++i) {
+      if (!name->cmp(builtinFonts[i].name)) {
+	builtinFont = &builtinFonts[i];
+	break;
+      }
+    }
+  }
+
+  // default ascent/descent values
+  if (builtinFont) {
+    ascent = 0.001 * builtinFont->ascent;
+    descent = 0.001 * builtinFont->descent;
+    fontBBox[0] = 0.001 * builtinFont->bbox[0];
+    fontBBox[1] = 0.001 * builtinFont->bbox[1];
+    fontBBox[2] = 0.001 * builtinFont->bbox[2];
+    fontBBox[3] = 0.001 * builtinFont->bbox[3];
+  } else {
+    ascent = 0.95;
+    descent = -0.35;
+    fontBBox[0] = fontBBox[1] = fontBBox[2] = fontBBox[3] = 0;
+  }
+
+  // get info from font descriptor
+  readFontDescriptor(xref, fontDict);
+
+  // for non-embedded fonts, don't trust the ascent/descent/bbox
+  // values from the font descriptor
+  if (builtinFont && embFontID.num < 0) {
+    ascent = 0.001 * builtinFont->ascent;
+    descent = 0.001 * builtinFont->descent;
+    fontBBox[0] = 0.001 * builtinFont->bbox[0];
+    fontBBox[1] = 0.001 * builtinFont->bbox[1];
+    fontBBox[2] = 0.001 * builtinFont->bbox[2];
+    fontBBox[3] = 0.001 * builtinFont->bbox[3];
+  }
+
+  // look for an external font file
+  findExtFontFile();
+
+  // get font matrix
+  fontMat[0] = fontMat[3] = 1;
+  fontMat[1] = fontMat[2] = fontMat[4] = fontMat[5] = 0;
+  if (fontDict->lookup("FontMatrix", &obj1)->isArray()) {
+    for (i = 0; i < 6 && i < obj1.arrayGetLength(); ++i) {
+      if (obj1.arrayGet(i, &obj2)->isNum()) {
+	fontMat[i] = obj2.getNum();
+      }
+      obj2.free();
+    }
+  }
+  obj1.free();
+
+  // get Type 3 bounding box, font definition, and resources
+  if (type == fontType3) {
+    if (fontDict->lookup("FontBBox", &obj1)->isArray()) {
+      for (i = 0; i < 4 && i < obj1.arrayGetLength(); ++i) {
+	if (obj1.arrayGet(i, &obj2)->isNum()) {
+	  fontBBox[i] = obj2.getNum();
+	}
+	obj2.free();
+      }
+    }
+    obj1.free();
+    if (!fontDict->lookup("CharProcs", &charProcs)->isDict()) {
+      error(-1, "Missing or invalid CharProcs dictionary in Type 3 font");
+      charProcs.free();
+    }
+    if (!fontDict->lookup("Resources", &resources)->isDict()) {
+      resources.free();
+    }
+  }
+
+  //----- build the font encoding -----
+
+  // Encodings start with a base encoding, which can come from
+  // (in order of priority):
+  //   1. FontDict.Encoding or FontDict.Encoding.BaseEncoding
+  //        - MacRoman / MacExpert / WinAnsi / Standard
+  //   2. embedded or external font file
+  //   3. default:
+  //        - builtin --> builtin encoding
+  //        - TrueType --> WinAnsiEncoding
+  //        - others --> StandardEncoding
+  // and then add a list of differences (if any) from
+  // FontDict.Encoding.Differences.
+
+  // check FontDict for base encoding
+  hasEncoding = gFalse;
+  usesMacRomanEnc = gFalse;
+  baseEnc = NULL;
+  baseEncFromFontFile = gFalse;
+  fontDict->lookup("Encoding", &obj1);
+  if (obj1.isDict()) {
+    obj1.dictLookup("BaseEncoding", &obj2);
+    if (obj2.isName("MacRomanEncoding")) {
+      hasEncoding = gTrue;
+      usesMacRomanEnc = gTrue;
+      baseEnc = macRomanEncoding;
+    } else if (obj2.isName("MacExpertEncoding")) {
+      hasEncoding = gTrue;
+      baseEnc = macExpertEncoding;
+    } else if (obj2.isName("WinAnsiEncoding")) {
+      hasEncoding = gTrue;
+      baseEnc = winAnsiEncoding;
+    }
+    obj2.free();
+  } else if (obj1.isName("MacRomanEncoding")) {
+    hasEncoding = gTrue;
+    usesMacRomanEnc = gTrue;
+    baseEnc = macRomanEncoding;
+  } else if (obj1.isName("MacExpertEncoding")) {
+    hasEncoding = gTrue;
+    baseEnc = macExpertEncoding;
+  } else if (obj1.isName("WinAnsiEncoding")) {
+    hasEncoding = gTrue;
+    baseEnc = winAnsiEncoding;
+  }
+
+  // check embedded or external font file for base encoding
+  // (only for Type 1 fonts - trying to get an encoding out of a
+  // TrueType font is a losing proposition)
+  ffT1 = NULL;
+  ffT1C = NULL;
+  buf = NULL;
+  if (type == fontType1 && (extFontFile || embFontID.num >= 0)) {
+    if (extFontFile) {
+      ffT1 = FoFiType1::load(extFontFile->getCString());
+    } else {
+      buf = readEmbFontFile(xref, &len);
+      ffT1 = FoFiType1::make(buf, len);
+    }
+    if (ffT1) {
+      if (ffT1->getName()) {
+	if (embFontName) {
+	  delete embFontName;
+	}
+	embFontName = new GString(ffT1->getName());
+      }
+      if (!baseEnc) {
+	baseEnc = ffT1->getEncoding();
+	baseEncFromFontFile = gTrue;
+      }
+    }
+  } else if (type == fontType1C && (extFontFile || embFontID.num >= 0)) {
+    if (extFontFile) {
+      ffT1C = FoFiType1C::load(extFontFile->getCString());
+    } else {
+      buf = readEmbFontFile(xref, &len);
+      ffT1C = FoFiType1C::make(buf, len);
+    }
+    if (ffT1C) {
+      if (ffT1C->getName()) {
+	if (embFontName) {
+	  delete embFontName;
+	}
+	embFontName = new GString(ffT1C->getName());
+      }
+      if (!baseEnc) {
+	baseEnc = ffT1C->getEncoding();
+	baseEncFromFontFile = gTrue;
+      }
+    }
+  }
+  if (buf) {
+    gfree(buf);
+  }
+
+  // get default base encoding
+  if (!baseEnc) {
+    if (builtinFont && embFontID.num < 0) {
+      baseEnc = builtinFont->defaultBaseEnc;
+      hasEncoding = gTrue;
+    } else if (type == fontTrueType) {
+      baseEnc = winAnsiEncoding;
+    } else {
+      baseEnc = standardEncoding;
+    }
+  }
+
+  // copy the base encoding
+  for (i = 0; i < 256; ++i) {
+    enc[i] = baseEnc[i];
+    if ((encFree[i] = baseEncFromFontFile) && enc[i]) {
+      enc[i] = copyString(baseEnc[i]);
+    }
+  }
+
+  // some Type 1C font files have empty encodings, which can break the
+  // T1C->T1 conversion (since the 'seac' operator depends on having
+  // the accents in the encoding), so we fill in any gaps from
+  // StandardEncoding
+  if (type == fontType1C && (extFontFile || embFontID.num >= 0) &&
+      baseEncFromFontFile) {
+    for (i = 0; i < 256; ++i) {
+      if (!enc[i] && standardEncoding[i]) {
+	enc[i] = standardEncoding[i];
+	encFree[i] = gFalse;
+      }
+    }
+  }
+
+  // merge differences into encoding
+  if (obj1.isDict()) {
+    obj1.dictLookup("Differences", &obj2);
+    if (obj2.isArray()) {
+      hasEncoding = gTrue;
+      code = 0;
+      for (i = 0; i < obj2.arrayGetLength(); ++i) {
+	obj2.arrayGet(i, &obj3);
+	if (obj3.isInt()) {
+	  code = obj3.getInt();
+	} else if (obj3.isName()) {
+	  if (code >= 0 && code < 256) {
+	    if (encFree[code]) {
+	      gfree(enc[code]);
+	    }
+	    enc[code] = copyString(obj3.getName());
+	    encFree[code] = gTrue;
+	  }
+	  ++code;
+	} else {
+	  error(-1, "Wrong type in font encoding resource differences (%s)",
+		obj3.getTypeName());
+	}
+	obj3.free();
+      }
+    }
+    obj2.free();
+  }
+  obj1.free();
+  if (ffT1) {
+    delete ffT1;
+  }
+  if (ffT1C) {
+    delete ffT1C;
+  }
+
+  //----- build the mapping to Unicode -----
+
+  // pass 1: use the name-to-Unicode mapping table
+  missing = hex = gFalse;
+  for (code = 0; code < 256; ++code) {
+    if ((charName = enc[code])) {
+      if (!(toUnicode[code] = globalParams->mapNameToUnicode(charName)) &&
+	  strcmp(charName, ".notdef")) {
+	// if it wasn't in the name-to-Unicode table, check for a
+	// name that looks like 'Axx' or 'xx', where 'A' is any letter
+	// and 'xx' is two hex digits
+	if ((strlen(charName) == 3 &&
+	     isalpha(charName[0]) &&
+	     isxdigit(charName[1]) && isxdigit(charName[2]) &&
+	     ((charName[1] >= 'a' && charName[1] <= 'f') ||
+	      (charName[1] >= 'A' && charName[1] <= 'F') ||
+	      (charName[2] >= 'a' && charName[2] <= 'f') ||
+	      (charName[2] >= 'A' && charName[2] <= 'F'))) ||
+	    (strlen(charName) == 2 &&
+	     isxdigit(charName[0]) && isxdigit(charName[1]) &&
+	     ((charName[0] >= 'a' && charName[0] <= 'f') ||
+	      (charName[0] >= 'A' && charName[0] <= 'F') ||
+	      (charName[1] >= 'a' && charName[1] <= 'f') ||
+	      (charName[1] >= 'A' && charName[1] <= 'F')))) {
+	  hex = gTrue;
+	}
+	missing = gTrue;
+      }
+    } else {
+      toUnicode[code] = 0;
+    }
+  }
+
+  // pass 2: try to fill in the missing chars, looking for names of
+  // the form 'Axx', 'xx', 'Ann', 'ABnn', or 'nn', where 'A' and 'B'
+  // are any letters, 'xx' is two hex digits, and 'nn' is 2-4
+  // decimal digits
+  if (missing && globalParams->getMapNumericCharNames()) {
+    for (code = 0; code < 256; ++code) {
+      if ((charName = enc[code]) && !toUnicode[code] &&
+	  strcmp(charName, ".notdef")) {
+	n = strlen(charName);
+	code2 = -1;
+	if (hex && n == 3 && isalpha(charName[0]) &&
+	    isxdigit(charName[1]) && isxdigit(charName[2])) {
+	  sscanf(charName+1, "%x", &code2);
+	} else if (hex && n == 2 &&
+		   isxdigit(charName[0]) && isxdigit(charName[1])) {
+	  sscanf(charName, "%x", &code2);
+	} else if (!hex && n >= 2 && n <= 4 &&
+		   isdigit(charName[0]) && isdigit(charName[1])) {
+	  code2 = atoi(charName);
+	} else if (n >= 3 && n <= 5 &&
+		   isdigit(charName[1]) && isdigit(charName[2])) {
+	  code2 = atoi(charName+1);
+	} else if (n >= 4 && n <= 6 &&
+		   isdigit(charName[2]) && isdigit(charName[3])) {
+	  code2 = atoi(charName+2);
+	}
+	if (code2 >= 0 && code2 <= 0xff) {
+	  toUnicode[code] = (Unicode)code2;
+	}
+      }
+    }
+
+  // if the 'mapUnknownCharNames' flag is set, do a simple pass-through
+  // mapping for unknown character names
+  } else if (missing && globalParams->getMapUnknownCharNames()) {
+    for (code = 0; code < 256; ++code) {
+      if (!toUnicode[code]) {
+	toUnicode[code] = code;
+      }
+    }
+  }
+
+  // construct the char code -> Unicode mapping object
+  ctu = CharCodeToUnicode::make8BitToUnicode(toUnicode);
+
+  // merge in a ToUnicode CMap, if there is one -- this overwrites
+  // existing entries in ctu, i.e., the ToUnicode CMap takes
+  // precedence, but the other encoding info is allowed to fill in any
+  // holes
+  readToUnicodeCMap(fontDict, 8, ctu);
+
+  // look for a Unicode-to-Unicode mapping
+  if (name && (utu = globalParams->getUnicodeToUnicode(name))) {
+    for (i = 0; i < 256; ++i) {
+      toUnicode[i] = 0;
+    }
+    ctu2 = CharCodeToUnicode::make8BitToUnicode(toUnicode);
+    for (i = 0; i < 256; ++i) {
+      n = ctu->mapToUnicode((CharCode)i, uBuf, 8);
+      if (n >= 1) {
+	n = utu->mapToUnicode((CharCode)uBuf[0], uBuf, 8);
+	if (n >= 1) {
+	  ctu2->setMapping((CharCode)i, uBuf, n);
+	}
+      }
+    }
+    utu->decRefCnt();
+    delete ctu;
+    ctu = ctu2;
+  }
+
+  //----- get the character widths -----
+
+  // initialize all widths
+  for (code = 0; code < 256; ++code) {
+    widths[code] = missingWidth * 0.001;
+  }
+
+  // use widths from font dict, if present
+  fontDict->lookup("FirstChar", &obj1);
+  firstChar = obj1.isInt() ? obj1.getInt() : 0;
+  obj1.free();
+  if (firstChar < 0 || firstChar > 255) {
+    firstChar = 0;
+  }
+  fontDict->lookup("LastChar", &obj1);
+  lastChar = obj1.isInt() ? obj1.getInt() : 255;
+  obj1.free();
+  if (lastChar < 0 || lastChar > 255) {
+    lastChar = 255;
+  }
+  mul = (type == fontType3) ? fontMat[0] : 0.001;
+  fontDict->lookup("Widths", &obj1);
+  if (obj1.isArray()) {
+    flags |= fontFixedWidth;
+    if (obj1.arrayGetLength() < lastChar - firstChar + 1) {
+      lastChar = firstChar + obj1.arrayGetLength() - 1;
+    }
+    for (code = firstChar; code <= lastChar; ++code) {
+      obj1.arrayGet(code - firstChar, &obj2);
+      if (obj2.isNum()) {
+	widths[code] = obj2.getNum() * mul;
+	if (widths[code] != widths[firstChar]) {
+	  flags &= ~fontFixedWidth;
+	}
+      }
+      obj2.free();
+    }
+
+  // use widths from built-in font
+  } else if (builtinFont) {
+    // this is a kludge for broken PDF files that encode char 32
+    // as .notdef
+    if (builtinFont->widths->getWidth("space", &w)) {
+      widths[32] = 0.001 * w;
+    }
+    for (code = 0; code < 256; ++code) {
+      if (enc[code] && builtinFont->widths->getWidth(enc[code], &w)) {
+	widths[code] = 0.001 * w;
+      }
+    }
+
+  // couldn't find widths -- use defaults 
+  } else {
+    // this is technically an error -- the Widths entry is required
+    // for all but the Base-14 fonts -- but certain PDF generators
+    // apparently don't include widths for Arial and TimesNewRoman
+    if (isFixedWidth()) {
+      i = 0;
+    } else if (isSerif()) {
+      i = 8;
+    } else {
+      i = 4;
+    }
+    if (isBold()) {
+      i += 2;
+    }
+    if (isItalic()) {
+      i += 1;
+    }
+    builtinFont = builtinFontSubst[i];
+    // this is a kludge for broken PDF files that encode char 32
+    // as .notdef
+    if (builtinFont->widths->getWidth("space", &w)) {
+      widths[32] = 0.001 * w;
+    }
+    for (code = 0; code < 256; ++code) {
+      if (enc[code] && builtinFont->widths->getWidth(enc[code], &w)) {
+	widths[code] = 0.001 * w;
+      }
+    }
+  }
+  obj1.free();
+
+  ok = gTrue;
+}
+
+Gfx8BitFont::~Gfx8BitFont() {
+  int i;
+
+  for (i = 0; i < 256; ++i) {
+    if (encFree[i] && enc[i]) {
+      gfree(enc[i]);
+    }
+  }
+  ctu->decRefCnt();
+  if (charProcs.isDict()) {
+    charProcs.free();
+  }
+  if (resources.isDict()) {
+    resources.free();
+  }
+}
+
+int Gfx8BitFont::getNextChar(char *s, int len, CharCode *code,
+			     Unicode *u, int uSize, int *uLen,
+			     double *dx, double *dy, double *ox, double *oy) {
+  CharCode c;
+
+  *code = c = (CharCode)(*s & 0xff);
+  *uLen = ctu->mapToUnicode(c, u, uSize);
+  *dx = widths[c];
+  *dy = *ox = *oy = 0;
+  return 1;
+}
+
+CharCodeToUnicode *Gfx8BitFont::getToUnicode() {
+  ctu->incRefCnt();
+  return ctu;
+}
+
+Gushort *Gfx8BitFont::getCodeToGIDMap(FoFiTrueType *ff) {
+  Gushort *map;
+  int cmapPlatform, cmapEncoding;
+  int unicodeCmap, macRomanCmap, msSymbolCmap, cmap;
+  GBool useMacRoman, useUnicode;
+  char *charName;
+  Unicode u;
+  int code, i, n;
+
+  map = (Gushort *)gmallocn(256, sizeof(Gushort));
+  for (i = 0; i < 256; ++i) {
+    map[i] = 0;
+  }
+
+  // To match up with the Adobe-defined behaviour, we choose a cmap
+  // like this:
+  // 1. If the PDF font has an encoding:
+  //    1a. If the PDF font specified MacRomanEncoding and the
+  //        TrueType font has a Macintosh Roman cmap, use it, and
+  //        reverse map the char names through MacRomanEncoding to
+  //        get char codes.
+  //    1b. If the TrueType font has a Microsoft Unicode cmap or a
+  //        non-Microsoft Unicode cmap, use it, and use the Unicode
+  //        indexes, not the char codes.
+  //    1c. If the PDF font is symbolic and the TrueType font has a
+  //        Microsoft Symbol cmap, use it, and use char codes
+  //        directly (possibly with an offset of 0xf000).
+  //    1d. If the TrueType font has a Macintosh Roman cmap, use it,
+  //        as in case 1a.
+  // 2. If the PDF font does not have an encoding or the PDF font is
+  //    symbolic:
+  //    2a. If the TrueType font has a Macintosh Roman cmap, use it,
+  //        and use char codes directly (possibly with an offset of
+  //        0xf000).
+  //    2b. If the TrueType font has a Microsoft Symbol cmap, use it,
+  //        and use char codes directly (possible with an offset of
+  //        0xf000).
+  // 3. If none of these rules apply, use the first cmap and hope for
+  //    the best (this shouldn't happen).
+  unicodeCmap = macRomanCmap = msSymbolCmap = -1;
+  for (i = 0; i < ff->getNumCmaps(); ++i) {
+    cmapPlatform = ff->getCmapPlatform(i);
+    cmapEncoding = ff->getCmapEncoding(i);
+    if ((cmapPlatform == 3 && cmapEncoding == 1) ||
+	cmapPlatform == 0) {
+      unicodeCmap = i;
+    } else if (cmapPlatform == 1 && cmapEncoding == 0) {
+      macRomanCmap = i;
+    } else if (cmapPlatform == 3 && cmapEncoding == 0) {
+      msSymbolCmap = i;
+    }
+  }
+  cmap = 0;
+  useMacRoman = gFalse;
+  useUnicode = gFalse;
+  if (hasEncoding) {
+    if (usesMacRomanEnc && macRomanCmap >= 0) {
+      cmap = macRomanCmap;
+      useMacRoman = gTrue;
+    } else if (unicodeCmap >= 0) {
+      cmap = unicodeCmap;
+      useUnicode = gTrue;
+    } else if ((flags & fontSymbolic) && msSymbolCmap >= 0) {
+      cmap = msSymbolCmap;
+    } else if ((flags & fontSymbolic) && macRomanCmap >= 0) {
+      cmap = macRomanCmap;
+    } else if (macRomanCmap >= 0) {
+      cmap = macRomanCmap;
+      useMacRoman = gTrue;
+    }
+  } else {
+    if (msSymbolCmap >= 0) {
+      cmap = msSymbolCmap;
+    } else if (macRomanCmap >= 0) {
+      cmap = macRomanCmap;
+    }
+  }
+
+  // reverse map the char names through MacRomanEncoding, then map the
+  // char codes through the cmap
+  if (useMacRoman) {
+    for (i = 0; i < 256; ++i) {
+      if ((charName = enc[i])) {
+	if ((code = globalParams->getMacRomanCharCode(charName))) {
+	  map[i] = ff->mapCodeToGID(cmap, code);
+	}
+      }
+    }
+
+  // map Unicode through the cmap
+  } else if (useUnicode) {
+    for (i = 0; i < 256; ++i) {
+      if (((charName = enc[i]) &&
+	   (u = globalParams->mapNameToUnicode(charName))) ||
+	  (n = ctu->mapToUnicode((CharCode)i, &u, 1))) {
+	map[i] = ff->mapCodeToGID(cmap, u);
+      }
+    }
+
+  // map the char codes through the cmap, possibly with an offset of
+  // 0xf000
+  } else {
+    for (i = 0; i < 256; ++i) {
+      if (!(map[i] = ff->mapCodeToGID(cmap, i))) {
+	map[i] = ff->mapCodeToGID(cmap, 0xf000 + i);
+      }
+    }
+  }
+
+  // try the TrueType 'post' table to handle any unmapped characters
+  for (i = 0; i < 256; ++i) {
+    if (!map[i] && (charName = enc[i])) {
+      map[i] = (Gushort)(int)ff->mapNameToGID(charName);
+    }
+  }
+
+  return map;
+}
+
+Dict *Gfx8BitFont::getCharProcs() {
+  return charProcs.isDict() ? charProcs.getDict() : (Dict *)NULL;
+}
+
+Object *Gfx8BitFont::getCharProc(int code, Object *proc) {
+  if (enc[code] && charProcs.isDict()) {
+    charProcs.dictLookup(enc[code], proc);
+  } else {
+    proc->initNull();
+  }
+  return proc;
+}
+
+Dict *Gfx8BitFont::getResources() {
+  return resources.isDict() ? resources.getDict() : (Dict *)NULL;
+}
+
+//------------------------------------------------------------------------
+// GfxCIDFont
+//------------------------------------------------------------------------
+
+static int CDECL cmpWidthExcep(const void *w1, const void *w2) {
+  return ((GfxFontCIDWidthExcep *)w1)->first -
+         ((GfxFontCIDWidthExcep *)w2)->first;
+}
+
+static int CDECL cmpWidthExcepV(const void *w1, const void *w2) {
+  return ((GfxFontCIDWidthExcepV *)w1)->first -
+         ((GfxFontCIDWidthExcepV *)w2)->first;
+}
+
+GfxCIDFont::GfxCIDFont(XRef *xref, char *tagA, Ref idA, GString *nameA,
+		       Dict *fontDict):
+  GfxFont(tagA, idA, nameA)
+{
+  Dict *desFontDict;
+  GString *collection, *cMapName;
+  Object desFontDictObj;
+  Object obj1, obj2, obj3, obj4, obj5, obj6;
+  CharCodeToUnicode *utu;
+  CharCode c;
+  Unicode uBuf[8];
+  int c1, c2;
+  int excepsSize, i, j, k, n;
+
+  ascent = 0.95;
+  descent = -0.35;
+  fontBBox[0] = fontBBox[1] = fontBBox[2] = fontBBox[3] = 0;
+  cMap = NULL;
+  ctu = NULL;
+  widths.defWidth = 1.0;
+  widths.defHeight = -1.0;
+  widths.defVY = 0.880;
+  widths.exceps = NULL;
+  widths.nExceps = 0;
+  widths.excepsV = NULL;
+  widths.nExcepsV = 0;
+  cidToGID = NULL;
+  cidToGIDLen = 0;
+
+  // get the descendant font
+  if (!fontDict->lookup("DescendantFonts", &obj1)->isArray()) {
+    error(-1, "Missing DescendantFonts entry in Type 0 font");
+    obj1.free();
+    goto err1;
+  }
+  if (!obj1.arrayGet(0, &desFontDictObj)->isDict()) {
+    error(-1, "Bad descendant font in Type 0 font");
+    goto err3;
+  }
+  obj1.free();
+  desFontDict = desFontDictObj.getDict();
+
+  // font type
+  if (!desFontDict->lookup("Subtype", &obj1)) {
+    error(-1, "Missing Subtype entry in Type 0 descendant font");
+    goto err3;
+  }
+  if (obj1.isName("CIDFontType0")) {
+    type = fontCIDType0;
+  } else if (obj1.isName("CIDFontType2")) {
+    type = fontCIDType2;
+  } else {
+    error(-1, "Unknown Type 0 descendant font type '%s'",
+	  obj1.isName() ? obj1.getName() : "???");
+    goto err3;
+  }
+  obj1.free();
+
+  // get info from font descriptor
+  readFontDescriptor(xref, desFontDict);
+
+  // look for an external font file
+  findExtFontFile();
+
+  //----- encoding info -----
+
+  // char collection
+  if (!desFontDict->lookup("CIDSystemInfo", &obj1)->isDict()) {
+    error(-1, "Missing CIDSystemInfo dictionary in Type 0 descendant font");
+    goto err3;
+  }
+  obj1.dictLookup("Registry", &obj2);
+  obj1.dictLookup("Ordering", &obj3);
+  if (!obj2.isString() || !obj3.isString()) {
+    error(-1, "Invalid CIDSystemInfo dictionary in Type 0 descendant font");
+    goto err4;
+  }
+  collection = obj2.getString()->copy()->append('-')->append(obj3.getString());
+  obj3.free();
+  obj2.free();
+  obj1.free();
+
+  // look for a ToUnicode CMap
+  if (!(ctu = readToUnicodeCMap(fontDict, 16, NULL))) {
+
+    // the "Adobe-Identity" and "Adobe-UCS" collections don't have
+    // cidToUnicode files
+    if (collection->cmp("Adobe-Identity") &&
+	collection->cmp("Adobe-UCS")) {
+
+      // look for a user-supplied .cidToUnicode file
+      if (!(ctu = globalParams->getCIDToUnicode(collection))) {
+	error(-1, "Unknown character collection '%s'",
+	      collection->getCString());
+	// fall-through, assuming the Identity mapping -- this appears
+	// to match Adobe's behavior
+      }
+    }
+  }
+
+  // look for a Unicode-to-Unicode mapping
+  if (name && (utu = globalParams->getUnicodeToUnicode(name))) {
+    if (ctu) {
+      for (c = 0; c < ctu->getLength(); ++c) {
+	n = ctu->mapToUnicode(c, uBuf, 8);
+	if (n >= 1) {
+	  n = utu->mapToUnicode((CharCode)uBuf[0], uBuf, 8);
+	  if (n >= 1) {
+	    ctu->setMapping(c, uBuf, n);
+	  }
+	}
+      }
+      utu->decRefCnt();
+    } else {
+      ctu = utu;
+    }
+  }
+
+  // encoding (i.e., CMap)
+  //~ need to handle a CMap stream here
+  //~ also need to deal with the UseCMap entry in the stream dict
+  if (!fontDict->lookup("Encoding", &obj1)->isName()) {
+    error(-1, "Missing or invalid Encoding entry in Type 0 font");
+    delete collection;
+    goto err3;
+  }
+  cMapName = new GString(obj1.getName());
+  obj1.free();
+  if (!(cMap = globalParams->getCMap(collection, cMapName))) {
+    error(-1, "Unknown CMap '%s' for character collection '%s'",
+	  cMapName->getCString(), collection->getCString());
+    delete collection;
+    delete cMapName;
+    goto err2;
+  }
+  delete collection;
+  delete cMapName;
+
+  // CIDToGIDMap (for embedded TrueType fonts)
+  if (type == fontCIDType2) {
+    desFontDict->lookup("CIDToGIDMap", &obj1);
+    if (obj1.isStream()) {
+      cidToGIDLen = 0;
+      i = 64;
+      cidToGID = (Gushort *)gmallocn(i, sizeof(Gushort));
+      obj1.streamReset();
+      while ((c1 = obj1.streamGetChar()) != EOF &&
+	     (c2 = obj1.streamGetChar()) != EOF) {
+	if (cidToGIDLen == i) {
+	  i *= 2;
+	  cidToGID = (Gushort *)greallocn(cidToGID, i, sizeof(Gushort));
+	}
+	cidToGID[cidToGIDLen++] = (Gushort)((c1 << 8) + c2);
+      }
+    } else if (!obj1.isName("Identity") && !obj1.isNull()) {
+      error(-1, "Invalid CIDToGIDMap entry in CID font");
+    }
+    obj1.free();
+  }
+
+  //----- character metrics -----
+
+  // default char width
+  if (desFontDict->lookup("DW", &obj1)->isInt()) {
+    widths.defWidth = obj1.getInt() * 0.001;
+  }
+  obj1.free();
+
+  // char width exceptions
+  if (desFontDict->lookup("W", &obj1)->isArray()) {
+    excepsSize = 0;
+    i = 0;
+    while (i + 1 < obj1.arrayGetLength()) {
+      obj1.arrayGet(i, &obj2);
+      obj1.arrayGet(i + 1, &obj3);
+      if (obj2.isInt() && obj3.isInt() && i + 2 < obj1.arrayGetLength()) {
+	if (obj1.arrayGet(i + 2, &obj4)->isNum()) {
+	  if (widths.nExceps == excepsSize) {
+	    excepsSize += 16;
+	    widths.exceps = (GfxFontCIDWidthExcep *)
+	      greallocn(widths.exceps,
+			excepsSize, sizeof(GfxFontCIDWidthExcep));
+	  }
+	  widths.exceps[widths.nExceps].first = obj2.getInt();
+	  widths.exceps[widths.nExceps].last = obj3.getInt();
+	  widths.exceps[widths.nExceps].width = obj4.getNum() * 0.001;
+	  ++widths.nExceps;
+	} else {
+	  error(-1, "Bad widths array in Type 0 font");
+	}
+	obj4.free();
+	i += 3;
+      } else if (obj2.isInt() && obj3.isArray()) {
+	if (widths.nExceps + obj3.arrayGetLength() > excepsSize) {
+	  excepsSize = (widths.nExceps + obj3.arrayGetLength() + 15) & ~15;
+	  widths.exceps = (GfxFontCIDWidthExcep *)
+	    greallocn(widths.exceps,
+		      excepsSize, sizeof(GfxFontCIDWidthExcep));
+	}
+	j = obj2.getInt();
+	for (k = 0; k < obj3.arrayGetLength(); ++k) {
+	  if (obj3.arrayGet(k, &obj4)->isNum()) {
+	    widths.exceps[widths.nExceps].first = j;
+	    widths.exceps[widths.nExceps].last = j;
+	    widths.exceps[widths.nExceps].width = obj4.getNum() * 0.001;
+	    ++j;
+	    ++widths.nExceps;
+	  } else {
+	    error(-1, "Bad widths array in Type 0 font");
+	  }
+	  obj4.free();
+	}
+	i += 2;
+      } else {
+	error(-1, "Bad widths array in Type 0 font");
+	++i;
+      }
+      obj3.free();
+      obj2.free();
+    }
+    qsort(widths.exceps, widths.nExceps, sizeof(GfxFontCIDWidthExcep),
+	  &cmpWidthExcep);
+  }
+  obj1.free();
+
+  // default metrics for vertical font
+  if (desFontDict->lookup("DW2", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 2) {
+    if (obj1.arrayGet(0, &obj2)->isNum()) {
+      widths.defVY = obj2.getNum() * 0.001;
+    }
+    obj2.free();
+    if (obj1.arrayGet(1, &obj2)->isNum()) {
+      widths.defHeight = obj2.getNum() * 0.001;
+    }
+    obj2.free();
+  }
+  obj1.free();
+
+  // char metric exceptions for vertical font
+  if (desFontDict->lookup("W2", &obj1)->isArray()) {
+    excepsSize = 0;
+    i = 0;
+    while (i + 1 < obj1.arrayGetLength()) {
+      obj1.arrayGet(i, &obj2);
+      obj1.arrayGet(i+ 1, &obj3);
+      if (obj2.isInt() && obj3.isInt() && i + 4 < obj1.arrayGetLength()) {
+	if (obj1.arrayGet(i + 2, &obj4)->isNum() &&
+	    obj1.arrayGet(i + 3, &obj5)->isNum() &&
+	    obj1.arrayGet(i + 4, &obj6)->isNum()) {
+	  if (widths.nExcepsV == excepsSize) {
+	    excepsSize += 16;
+	    widths.excepsV = (GfxFontCIDWidthExcepV *)
+	      greallocn(widths.excepsV,
+			excepsSize, sizeof(GfxFontCIDWidthExcepV));
+	  }
+	  widths.excepsV[widths.nExcepsV].first = obj2.getInt();
+	  widths.excepsV[widths.nExcepsV].last = obj3.getInt();
+	  widths.excepsV[widths.nExcepsV].height = obj4.getNum() * 0.001;
+	  widths.excepsV[widths.nExcepsV].vx = obj5.getNum() * 0.001;
+	  widths.excepsV[widths.nExcepsV].vy = obj6.getNum() * 0.001;
+	  ++widths.nExcepsV;
+	} else {
+	  error(-1, "Bad widths (W2) array in Type 0 font");
+	}
+	obj6.free();
+	obj5.free();
+	obj4.free();
+	i += 5;
+      } else if (obj2.isInt() && obj3.isArray()) {
+	if (widths.nExcepsV + obj3.arrayGetLength() / 3 > excepsSize) {
+	  excepsSize =
+	    (widths.nExcepsV + obj3.arrayGetLength() / 3 + 15) & ~15;
+	  widths.excepsV = (GfxFontCIDWidthExcepV *)
+	    greallocn(widths.excepsV,
+		      excepsSize, sizeof(GfxFontCIDWidthExcepV));
+	}
+	j = obj2.getInt();
+	for (k = 0; k < obj3.arrayGetLength(); k += 3) {
+	  if (obj3.arrayGet(k, &obj4)->isNum() &&
+	      obj3.arrayGet(k+1, &obj5)->isNum() &&
+	      obj3.arrayGet(k+2, &obj6)->isNum()) {
+	    widths.excepsV[widths.nExceps].first = j;
+	    widths.excepsV[widths.nExceps].last = j;
+	    widths.excepsV[widths.nExceps].height = obj4.getNum() * 0.001;
+	    widths.excepsV[widths.nExceps].vx = obj5.getNum() * 0.001;
+	    widths.excepsV[widths.nExceps].vy = obj6.getNum() * 0.001;
+	    ++j;
+	    ++widths.nExcepsV;
+	  } else {
+	    error(-1, "Bad widths (W2) array in Type 0 font");
+	  }
+	  obj6.free();
+	  obj5.free();
+	  obj4.free();
+	}
+	i += 2;
+      } else {
+	error(-1, "Bad widths (W2) array in Type 0 font");
+	++i;
+      }
+      obj3.free();
+      obj2.free();
+    }
+    qsort(widths.excepsV, widths.nExcepsV, sizeof(GfxFontCIDWidthExcepV),
+	  &cmpWidthExcepV);
+  }
+  obj1.free();
+
+  desFontDictObj.free();
+  ok = gTrue;
+  return;
+
+ err4:
+  obj3.free();
+  obj2.free();
+ err3:
+  obj1.free();
+ err2:
+  desFontDictObj.free();
+ err1:;
+}
+
+GfxCIDFont::~GfxCIDFont() {
+  if (cMap) {
+    cMap->decRefCnt();
+  }
+  if (ctu) {
+    ctu->decRefCnt();
+  }
+  gfree(widths.exceps);
+  gfree(widths.excepsV);
+  if (cidToGID) {
+    gfree(cidToGID);
+  }
+}
+
+int GfxCIDFont::getNextChar(char *s, int len, CharCode *code,
+			    Unicode *u, int uSize, int *uLen,
+			    double *dx, double *dy, double *ox, double *oy) {
+  CID cid;
+  double w, h, vx, vy;
+  int n, a, b, m;
+
+  if (!cMap) {
+    *code = 0;
+    *uLen = 0;
+    *dx = *dy = 0;
+    return 1;
+  }
+
+  *code = (CharCode)(cid = cMap->getCID(s, len, &n));
+  if (ctu) {
+    *uLen = ctu->mapToUnicode(cid, u, uSize);
+  } else {
+    *uLen = 0;
+  }
+
+  // horizontal
+  if (cMap->getWMode() == 0) {
+    w = widths.defWidth;
+    h = vx = vy = 0;
+    if (widths.nExceps > 0 && cid >= widths.exceps[0].first) {
+      a = 0;
+      b = widths.nExceps;
+      // invariant: widths.exceps[a].first <= cid < widths.exceps[b].first
+      while (b - a > 1) {
+	m = (a + b) / 2;
+	if (widths.exceps[m].first <= cid) {
+	  a = m;
+	} else {
+	  b = m;
+	}
+      }
+      if (cid <= widths.exceps[a].last) {
+	w = widths.exceps[a].width;
+      }
+    }
+
+  // vertical
+  } else {
+    w = 0;
+    h = widths.defHeight;
+    vx = widths.defWidth / 2;
+    vy = widths.defVY;
+    if (widths.nExcepsV > 0 && cid >= widths.excepsV[0].first) {
+      a = 0;
+      b = widths.nExcepsV;
+      // invariant: widths.excepsV[a].first <= cid < widths.excepsV[b].first
+      while (b - a > 1) {
+	m = (a + b) / 2;
+	if (widths.excepsV[m].last <= cid) {
+	  a = m;
+	} else {
+	  b = m;
+	}
+      }
+      if (cid <= widths.excepsV[a].last) {
+	h = widths.excepsV[a].height;
+	vx = widths.excepsV[a].vx;
+	vy = widths.excepsV[a].vy;
+      }
+    }
+  }
+
+  *dx = w;
+  *dy = h;
+  *ox = vx;
+  *oy = vy;
+
+  return n;
+}
+
+int GfxCIDFont::getWMode() {
+  return cMap ? cMap->getWMode() : 0;
+}
+
+CharCodeToUnicode *GfxCIDFont::getToUnicode() {
+  if (ctu) {
+    ctu->incRefCnt();
+  }
+  return ctu;
+}
+
+GString *GfxCIDFont::getCollection() {
+  return cMap ? cMap->getCollection() : (GString *)NULL;
+}
+
+//------------------------------------------------------------------------
+// GfxFontDict
+//------------------------------------------------------------------------
+
+GfxFontDict::GfxFontDict(XRef *xref, Ref *fontDictRef, Dict *fontDict) {
+  int i;
+  Object obj1, obj2;
+  Ref r;
+
+  numFonts = fontDict->getLength();
+  fonts = (GfxFont **)gmallocn(numFonts, sizeof(GfxFont *));
+  for (i = 0; i < numFonts; ++i) {
+    fontDict->getValNF(i, &obj1);
+    obj1.fetch(xref, &obj2);
+    if (obj2.isDict()) {
+      if (obj1.isRef()) {
+	r = obj1.getRef();
+      } else {
+	// no indirect reference for this font, so invent a unique one
+	// (legal generation numbers are five digits, so any 6-digit
+	// number would be safe)
+	r.num = i;
+	if (fontDictRef) {
+	  r.gen = 100000 + fontDictRef->num;
+	} else {
+	  r.gen = 999999;
+	}
+      }
+      fonts[i] = GfxFont::makeFont(xref, fontDict->getKey(i),
+				   r, obj2.getDict());
+      if (fonts[i] && !fonts[i]->isOk()) {
+	delete fonts[i];
+	fonts[i] = NULL;
+      }
+    } else {
+      error(-1, "font resource is not a dictionary");
+      fonts[i] = NULL;
+    }
+    obj1.free();
+    obj2.free();
+  }
+}
+
+GfxFontDict::~GfxFontDict() {
+  int i;
+
+  for (i = 0; i < numFonts; ++i) {
+    if (fonts[i]) {
+      delete fonts[i];
+    }
+  }
+  gfree(fonts);
+}
+
+GfxFont *GfxFontDict::lookup(char *tag) {
+  int i;
+
+  for (i = 0; i < numFonts; ++i) {
+    if (fonts[i] && fonts[i]->matches(tag)) {
+      return fonts[i];
+    }
+  }
+  return NULL;
+}
diff --git a/xpdf/GfxFont.h b/xpdf/GfxFont.h
new file mode 100644
--- /dev/null
+++ b/xpdf/GfxFont.h
@@ -0,0 +1,320 @@
+//========================================================================
+//
+// GfxFont.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef GFXFONT_H
+#define GFXFONT_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "GString.h"
+#include "Object.h"
+#include "CharTypes.h"
+
+class Dict;
+class CMap;
+class CharCodeToUnicode;
+class FoFiTrueType;
+struct GfxFontCIDWidths;
+
+//------------------------------------------------------------------------
+// GfxFontType
+//------------------------------------------------------------------------
+
+enum GfxFontType {
+  //----- Gfx8BitFont
+  fontUnknownType,
+  fontType1,
+  fontType1C,
+  fontType1COT,
+  fontType3,
+  fontTrueType,
+  fontTrueTypeOT,
+  //----- GfxCIDFont
+  fontCIDType0,
+  fontCIDType0C,
+  fontCIDType0COT,
+  fontCIDType2,
+  fontCIDType2OT
+};
+
+//------------------------------------------------------------------------
+// GfxFontCIDWidths
+//------------------------------------------------------------------------
+
+struct GfxFontCIDWidthExcep {
+  CID first;			// this record applies to
+  CID last;			//   CIDs <first>..<last>
+  double width;			// char width
+};
+
+struct GfxFontCIDWidthExcepV {
+  CID first;			// this record applies to
+  CID last;			//   CIDs <first>..<last>
+  double height;		// char height
+  double vx, vy;		// origin position
+};
+
+struct GfxFontCIDWidths {
+  double defWidth;		// default char width
+  double defHeight;		// default char height
+  double defVY;			// default origin position
+  GfxFontCIDWidthExcep *exceps;	// exceptions
+  int nExceps;			// number of valid entries in exceps
+  GfxFontCIDWidthExcepV *	// exceptions for vertical font
+    excepsV;
+  int nExcepsV;			// number of valid entries in excepsV
+};
+
+//------------------------------------------------------------------------
+// GfxFont
+//------------------------------------------------------------------------
+
+#define fontFixedWidth (1 << 0)
+#define fontSerif      (1 << 1)
+#define fontSymbolic   (1 << 2)
+#define fontItalic     (1 << 6)
+#define fontBold       (1 << 18)
+
+class GfxFont {
+public:
+
+  // Build a GfxFont object.
+  static GfxFont *makeFont(XRef *xref, char *tagA, Ref idA, Dict *fontDict);
+
+  GfxFont(char *tagA, Ref idA, GString *nameA);
+
+  virtual ~GfxFont();
+
+  GBool isOk() { return ok; }
+
+  // Get font tag.
+  GString *getTag() { return tag; }
+
+  // Get font dictionary ID.
+  Ref *getID() { return &id; }
+
+  // Does this font match the tag?
+  GBool matches(char *tagA) { return !tag->cmp(tagA); }
+
+  // Get base font name.
+  GString *getName() { return name; }
+
+  // Get the original font name (ignornig any munging that might have
+  // been done to map to a canonical Base-14 font name).
+  GString *getOrigName() { return origName; }
+
+  // Get font type.
+  GfxFontType getType() { return type; }
+  virtual GBool isCIDFont() { return gFalse; }
+
+  // Get embedded font ID, i.e., a ref for the font file stream.
+  // Returns false if there is no embedded font.
+  GBool getEmbeddedFontID(Ref *embID)
+    { *embID = embFontID; return embFontID.num >= 0; }
+
+  // Get the PostScript font name for the embedded font.  Returns
+  // NULL if there is no embedded font.
+  GString *getEmbeddedFontName() { return embFontName; }
+
+  // Get the name of the external font file.  Returns NULL if there
+  // is no external font file.
+  GString *getExtFontFile() { return extFontFile; }
+
+  // Get font descriptor flags.
+  int getFlags() { return flags; }
+  GBool isFixedWidth() { return flags & fontFixedWidth; }
+  GBool isSerif() { return flags & fontSerif; }
+  GBool isSymbolic() { return flags & fontSymbolic; }
+  GBool isItalic() { return flags & fontItalic; }
+  GBool isBold() { return flags & fontBold; }
+
+  // Return the font matrix.
+  double *getFontMatrix() { return fontMat; }
+
+  // Return the font bounding box.
+  double *getFontBBox() { return fontBBox; }
+
+  // Return the ascent and descent values.
+  double getAscent() { return ascent; }
+  double getDescent() { return descent; }
+
+  // Return the writing mode (0=horizontal, 1=vertical).
+  virtual int getWMode() { return 0; }
+
+  // Read an external or embedded font file into a buffer.
+  char *readExtFontFile(int *len);
+  char *readEmbFontFile(XRef *xref, int *len);
+
+  // Get the next char from a string <s> of <len> bytes, returning the
+  // char <code>, its Unicode mapping <u>, its displacement vector
+  // (<dx>, <dy>), and its origin offset vector (<ox>, <oy>).  <uSize>
+  // is the number of entries available in <u>, and <uLen> is set to
+  // the number actually used.  Returns the number of bytes used by
+  // the char code.
+  virtual int getNextChar(char *s, int len, CharCode *code,
+			  Unicode *u, int uSize, int *uLen,
+			  double *dx, double *dy, double *ox, double *oy) = 0;
+
+protected:
+
+  void readFontDescriptor(XRef *xref, Dict *fontDict);
+  CharCodeToUnicode *readToUnicodeCMap(Dict *fontDict, int nBits,
+				       CharCodeToUnicode *ctu);
+  void findExtFontFile();
+
+  GString *tag;			// PDF font tag
+  Ref id;			// reference (used as unique ID)
+  GString *name;		// font name
+  GString *origName;		// original font name
+  GfxFontType type;		// type of font
+  int flags;			// font descriptor flags
+  GString *embFontName;		// name of embedded font
+  Ref embFontID;		// ref to embedded font file stream
+  GString *extFontFile;		// external font file name
+  double fontMat[6];		// font matrix (Type 3 only)
+  double fontBBox[4];		// font bounding box (Type 3 only)
+  double missingWidth;		// "default" width
+  double ascent;		// max height above baseline
+  double descent;		// max depth below baseline
+  GBool ok;
+};
+
+//------------------------------------------------------------------------
+// Gfx8BitFont
+//------------------------------------------------------------------------
+
+class Gfx8BitFont: public GfxFont {
+public:
+
+  Gfx8BitFont(XRef *xref, char *tagA, Ref idA, GString *nameA,
+	      GfxFontType typeA, Dict *fontDict);
+
+  virtual ~Gfx8BitFont();
+
+  virtual int getNextChar(char *s, int len, CharCode *code,
+			  Unicode *u, int uSize, int *uLen,
+			  double *dx, double *dy, double *ox, double *oy);
+
+  // Return the encoding.
+  char **getEncoding() { return enc; }
+
+  // Return the Unicode map.
+  CharCodeToUnicode *getToUnicode();
+
+  // Return the character name associated with <code>.
+  char *getCharName(int code) { return enc[code]; }
+
+  // Returns true if the PDF font specified an encoding.
+  GBool getHasEncoding() { return hasEncoding; }
+
+  // Returns true if the PDF font specified MacRomanEncoding.
+  GBool getUsesMacRomanEnc() { return usesMacRomanEnc; }
+
+  // Get width of a character.
+  double getWidth(Guchar c) { return widths[c]; }
+
+  // Return a char code-to-GID mapping for the provided font file.
+  // (This is only useful for TrueType fonts.)
+  Gushort *getCodeToGIDMap(FoFiTrueType *ff);
+
+  // Return the Type 3 CharProc dictionary, or NULL if none.
+  Dict *getCharProcs();
+
+  // Return the Type 3 CharProc for the character associated with <code>.
+  Object *getCharProc(int code, Object *proc);
+
+  // Return the Type 3 Resources dictionary, or NULL if none.
+  Dict *getResources();
+
+private:
+
+  char *enc[256];		// char code --> char name
+  char encFree[256];		// boolean for each char name: if set,
+				//   the string is malloc'ed
+  CharCodeToUnicode *ctu;	// char code --> Unicode
+  GBool hasEncoding;
+  GBool usesMacRomanEnc;
+  double widths[256];		// character widths
+  Object charProcs;		// Type 3 CharProcs dictionary
+  Object resources;		// Type 3 Resources dictionary
+};
+
+//------------------------------------------------------------------------
+// GfxCIDFont
+//------------------------------------------------------------------------
+
+class GfxCIDFont: public GfxFont {
+public:
+
+  GfxCIDFont(XRef *xref, char *tagA, Ref idA, GString *nameA,
+	     Dict *fontDict);
+
+  virtual ~GfxCIDFont();
+
+  virtual GBool isCIDFont() { return gTrue; }
+
+  virtual int getNextChar(char *s, int len, CharCode *code,
+			  Unicode *u, int uSize, int *uLen,
+			  double *dx, double *dy, double *ox, double *oy);
+
+  // Return the writing mode (0=horizontal, 1=vertical).
+  virtual int getWMode();
+
+  // Return the Unicode map.
+  CharCodeToUnicode *getToUnicode();
+
+  // Get the collection name (<registry>-<ordering>).
+  GString *getCollection();
+
+  // Return the CID-to-GID mapping table.  These should only be called
+  // if type is fontCIDType2.
+  Gushort *getCIDToGID() { return cidToGID; }
+  int getCIDToGIDLen() { return cidToGIDLen; }
+
+private:
+
+  CMap *cMap;			// char code --> CID
+  CharCodeToUnicode *ctu;	// CID --> Unicode
+  GfxFontCIDWidths widths;	// character widths
+  Gushort *cidToGID;		// CID --> GID mapping (for embedded
+				//   TrueType fonts)
+  int cidToGIDLen;
+};
+
+//------------------------------------------------------------------------
+// GfxFontDict
+//------------------------------------------------------------------------
+
+class GfxFontDict {
+public:
+
+  // Build the font dictionary, given the PDF font dictionary.
+  GfxFontDict(XRef *xref, Ref *fontDictRef, Dict *fontDict);
+
+  // Destructor.
+  ~GfxFontDict();
+
+  // Get the specified font.
+  GfxFont *lookup(char *tag);
+
+  // Iterative access.
+  int getNumFonts() { return numFonts; }
+  GfxFont *getFont(int i) { return fonts[i]; }
+
+private:
+
+  GfxFont **fonts;		// list of fonts
+  int numFonts;			// number of fonts
+};
+
+#endif
diff --git a/xpdf/GfxState.cc b/xpdf/GfxState.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/GfxState.cc
@@ -0,0 +1,4137 @@
+//========================================================================
+//
+// GfxState.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stddef.h>
+#include <math.h>
+#include <string.h>
+#include "gmem.h"
+#include "Error.h"
+#include "Object.h"
+#include "Array.h"
+#include "Page.h"
+#include "GfxState.h"
+
+//------------------------------------------------------------------------
+
+static inline GfxColorComp clip01(GfxColorComp x) {
+  return (x < 0) ? 0 : (x > gfxColorComp1) ? gfxColorComp1 : x;
+}
+
+static inline double clip01(double x) {
+  return (x < 0) ? 0 : (x > 1) ? 1 : x;
+}
+
+//------------------------------------------------------------------------
+
+struct GfxBlendModeInfo {
+  char *name;
+  GfxBlendMode mode;
+};
+
+static GfxBlendModeInfo gfxBlendModeNames[] = {
+  { "Normal",     gfxBlendNormal },
+  { "Compatible", gfxBlendNormal },
+  { "Multiply",   gfxBlendMultiply },
+  { "Screen",     gfxBlendScreen },
+  { "Overlay",    gfxBlendOverlay },
+  { "Darken",     gfxBlendDarken },
+  { "Lighten",    gfxBlendLighten },
+  { "ColorDodge", gfxBlendColorDodge },
+  { "ColorBurn",  gfxBlendColorBurn },
+  { "HardLight",  gfxBlendHardLight },
+  { "SoftLight",  gfxBlendSoftLight },
+  { "Difference", gfxBlendDifference },
+  { "Exclusion",  gfxBlendExclusion },
+  { "Hue",        gfxBlendHue },
+  { "Saturation", gfxBlendSaturation },
+  { "Color",      gfxBlendColor },
+  { "Luminosity", gfxBlendLuminosity }
+};
+
+#define nGfxBlendModeNames \
+          ((int)((sizeof(gfxBlendModeNames) / sizeof(GfxBlendModeInfo))))
+
+//------------------------------------------------------------------------
+
+// NB: This must match the GfxColorSpaceMode enum defined in
+// GfxState.h
+static char *gfxColorSpaceModeNames[] = {
+  "DeviceGray",
+  "CalGray",
+  "DeviceRGB",
+  "CalRGB",
+  "DeviceCMYK",
+  "Lab",
+  "ICCBased",
+  "Indexed",
+  "Separation",
+  "DeviceN",
+  "Pattern"
+};
+
+#define nGfxColorSpaceModes ((sizeof(gfxColorSpaceModeNames) / sizeof(char *)))
+
+//------------------------------------------------------------------------
+// GfxColorSpace
+//------------------------------------------------------------------------
+
+GfxColorSpace::GfxColorSpace() {
+}
+
+GfxColorSpace::~GfxColorSpace() {
+}
+
+GfxColorSpace *GfxColorSpace::parse(Object *csObj) {
+  GfxColorSpace *cs;
+  Object obj1;
+
+  cs = NULL;
+  if (csObj->isName()) {
+    if (csObj->isName("DeviceGray") || csObj->isName("G")) {
+      cs = new GfxDeviceGrayColorSpace();
+    } else if (csObj->isName("DeviceRGB") || csObj->isName("RGB")) {
+      cs = new GfxDeviceRGBColorSpace();
+    } else if (csObj->isName("DeviceCMYK") || csObj->isName("CMYK")) {
+      cs = new GfxDeviceCMYKColorSpace();
+    } else if (csObj->isName("Pattern")) {
+      cs = new GfxPatternColorSpace(NULL);
+    } else {
+      error(-1, "Bad color space '%s'", csObj->getName());
+    }
+  } else if (csObj->isArray()) {
+    csObj->arrayGet(0, &obj1);
+    if (obj1.isName("DeviceGray") || obj1.isName("G")) {
+      cs = new GfxDeviceGrayColorSpace();
+    } else if (obj1.isName("DeviceRGB") || obj1.isName("RGB")) {
+      cs = new GfxDeviceRGBColorSpace();
+    } else if (obj1.isName("DeviceCMYK") || obj1.isName("CMYK")) {
+      cs = new GfxDeviceCMYKColorSpace();
+    } else if (obj1.isName("CalGray")) {
+      cs = GfxCalGrayColorSpace::parse(csObj->getArray());
+    } else if (obj1.isName("CalRGB")) {
+      cs = GfxCalRGBColorSpace::parse(csObj->getArray());
+    } else if (obj1.isName("Lab")) {
+      cs = GfxLabColorSpace::parse(csObj->getArray());
+    } else if (obj1.isName("ICCBased")) {
+      cs = GfxICCBasedColorSpace::parse(csObj->getArray());
+    } else if (obj1.isName("Indexed") || obj1.isName("I")) {
+      cs = GfxIndexedColorSpace::parse(csObj->getArray());
+    } else if (obj1.isName("Separation")) {
+      cs = GfxSeparationColorSpace::parse(csObj->getArray());
+    } else if (obj1.isName("DeviceN")) {
+      cs = GfxDeviceNColorSpace::parse(csObj->getArray());
+    } else if (obj1.isName("Pattern")) {
+      cs = GfxPatternColorSpace::parse(csObj->getArray());
+    } else {
+      error(-1, "Bad color space");
+    }
+    obj1.free();
+  } else {
+    error(-1, "Bad color space - expected name or array");
+  }
+  return cs;
+}
+
+void GfxColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange,
+				     int maxImgPixel) {
+  int i;
+
+  for (i = 0; i < getNComps(); ++i) {
+    decodeLow[i] = 0;
+    decodeRange[i] = 1;
+  }
+}
+
+int GfxColorSpace::getNumColorSpaceModes() {
+  return nGfxColorSpaceModes;
+}
+
+char *GfxColorSpace::getColorSpaceModeName(int idx) {
+  return gfxColorSpaceModeNames[idx];
+}
+
+//------------------------------------------------------------------------
+// GfxDeviceGrayColorSpace
+//------------------------------------------------------------------------
+
+GfxDeviceGrayColorSpace::GfxDeviceGrayColorSpace() {
+}
+
+GfxDeviceGrayColorSpace::~GfxDeviceGrayColorSpace() {
+}
+
+GfxColorSpace *GfxDeviceGrayColorSpace::copy() {
+  return new GfxDeviceGrayColorSpace();
+}
+
+void GfxDeviceGrayColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+  *gray = clip01(color->c[0]);
+}
+
+void GfxDeviceGrayColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+  rgb->r = rgb->g = rgb->b = clip01(color->c[0]);
+}
+
+void GfxDeviceGrayColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+  cmyk->c = cmyk->m = cmyk->y = 0;
+  cmyk->k = clip01(gfxColorComp1 - color->c[0]);
+}
+
+void GfxDeviceGrayColorSpace::getDefaultColor(GfxColor *color) {
+  color->c[0] = 0;
+}
+
+//------------------------------------------------------------------------
+// GfxCalGrayColorSpace
+//------------------------------------------------------------------------
+
+GfxCalGrayColorSpace::GfxCalGrayColorSpace() {
+  whiteX = whiteY = whiteZ = 1;
+  blackX = blackY = blackZ = 0;
+  gamma = 1;
+}
+
+GfxCalGrayColorSpace::~GfxCalGrayColorSpace() {
+}
+
+GfxColorSpace *GfxCalGrayColorSpace::copy() {
+  GfxCalGrayColorSpace *cs;
+
+  cs = new GfxCalGrayColorSpace();
+  cs->whiteX = whiteX;
+  cs->whiteY = whiteY;
+  cs->whiteZ = whiteZ;
+  cs->blackX = blackX;
+  cs->blackY = blackY;
+  cs->blackZ = blackZ;
+  cs->gamma = gamma;
+  return cs;
+}
+
+GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr) {
+  GfxCalGrayColorSpace *cs;
+  Object obj1, obj2, obj3;
+
+  arr->get(1, &obj1);
+  if (!obj1.isDict()) {
+    error(-1, "Bad CalGray color space");
+    obj1.free();
+    return NULL;
+  }
+  cs = new GfxCalGrayColorSpace();
+  if (obj1.dictLookup("WhitePoint", &obj2)->isArray() &&
+      obj2.arrayGetLength() == 3) {
+    obj2.arrayGet(0, &obj3);
+    cs->whiteX = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(1, &obj3);
+    cs->whiteY = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(2, &obj3);
+    cs->whiteZ = obj3.getNum();
+    obj3.free();
+  }
+  obj2.free();
+  if (obj1.dictLookup("BlackPoint", &obj2)->isArray() &&
+      obj2.arrayGetLength() == 3) {
+    obj2.arrayGet(0, &obj3);
+    cs->blackX = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(1, &obj3);
+    cs->blackY = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(2, &obj3);
+    cs->blackZ = obj3.getNum();
+    obj3.free();
+  }
+  obj2.free();
+  if (obj1.dictLookup("Gamma", &obj2)->isNum()) {
+    cs->gamma = obj2.getNum();
+  }
+  obj2.free();
+  obj1.free();
+  return cs;
+}
+
+void GfxCalGrayColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+  *gray = clip01(color->c[0]);
+}
+
+void GfxCalGrayColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+  rgb->r = rgb->g = rgb->b = clip01(color->c[0]);
+}
+
+void GfxCalGrayColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+  cmyk->c = cmyk->m = cmyk->y = 0;
+  cmyk->k = clip01(gfxColorComp1 - color->c[0]);
+}
+
+void GfxCalGrayColorSpace::getDefaultColor(GfxColor *color) {
+  color->c[0] = 0;
+}
+
+//------------------------------------------------------------------------
+// GfxDeviceRGBColorSpace
+//------------------------------------------------------------------------
+
+GfxDeviceRGBColorSpace::GfxDeviceRGBColorSpace() {
+}
+
+GfxDeviceRGBColorSpace::~GfxDeviceRGBColorSpace() {
+}
+
+GfxColorSpace *GfxDeviceRGBColorSpace::copy() {
+  return new GfxDeviceRGBColorSpace();
+}
+
+void GfxDeviceRGBColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+  *gray = clip01((GfxColorComp)(0.3  * color->c[0] +
+				0.59 * color->c[1] +
+				0.11 * color->c[2] + 0.5));
+}
+
+void GfxDeviceRGBColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+  rgb->r = clip01(color->c[0]);
+  rgb->g = clip01(color->c[1]);
+  rgb->b = clip01(color->c[2]);
+}
+
+void GfxDeviceRGBColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+  GfxColorComp c, m, y, k;
+
+  c = clip01(gfxColorComp1 - color->c[0]);
+  m = clip01(gfxColorComp1 - color->c[1]);
+  y = clip01(gfxColorComp1 - color->c[2]);
+  k = c;
+  if (m < k) {
+    k = m;
+  }
+  if (y < k) {
+    k = y;
+  }
+  cmyk->c = c - k;
+  cmyk->m = m - k;
+  cmyk->y = y - k;
+  cmyk->k = k;
+}
+
+void GfxDeviceRGBColorSpace::getDefaultColor(GfxColor *color) {
+  color->c[0] = 0;
+  color->c[1] = 0;
+  color->c[2] = 0;
+}
+
+//------------------------------------------------------------------------
+// GfxCalRGBColorSpace
+//------------------------------------------------------------------------
+
+GfxCalRGBColorSpace::GfxCalRGBColorSpace() {
+  whiteX = whiteY = whiteZ = 1;
+  blackX = blackY = blackZ = 0;
+  gammaR = gammaG = gammaB = 1;
+  mat[0] = 1; mat[1] = 0; mat[2] = 0;
+  mat[3] = 0; mat[4] = 1; mat[5] = 0;
+  mat[6] = 0; mat[7] = 0; mat[8] = 1;
+}
+
+GfxCalRGBColorSpace::~GfxCalRGBColorSpace() {
+}
+
+GfxColorSpace *GfxCalRGBColorSpace::copy() {
+  GfxCalRGBColorSpace *cs;
+  int i;
+
+  cs = new GfxCalRGBColorSpace();
+  cs->whiteX = whiteX;
+  cs->whiteY = whiteY;
+  cs->whiteZ = whiteZ;
+  cs->blackX = blackX;
+  cs->blackY = blackY;
+  cs->blackZ = blackZ;
+  cs->gammaR = gammaR;
+  cs->gammaG = gammaG;
+  cs->gammaB = gammaB;
+  for (i = 0; i < 9; ++i) {
+    cs->mat[i] = mat[i];
+  }
+  return cs;
+}
+
+GfxColorSpace *GfxCalRGBColorSpace::parse(Array *arr) {
+  GfxCalRGBColorSpace *cs;
+  Object obj1, obj2, obj3;
+  int i;
+
+  arr->get(1, &obj1);
+  if (!obj1.isDict()) {
+    error(-1, "Bad CalRGB color space");
+    obj1.free();
+    return NULL;
+  }
+  cs = new GfxCalRGBColorSpace();
+  if (obj1.dictLookup("WhitePoint", &obj2)->isArray() &&
+      obj2.arrayGetLength() == 3) {
+    obj2.arrayGet(0, &obj3);
+    cs->whiteX = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(1, &obj3);
+    cs->whiteY = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(2, &obj3);
+    cs->whiteZ = obj3.getNum();
+    obj3.free();
+  }
+  obj2.free();
+  if (obj1.dictLookup("BlackPoint", &obj2)->isArray() &&
+      obj2.arrayGetLength() == 3) {
+    obj2.arrayGet(0, &obj3);
+    cs->blackX = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(1, &obj3);
+    cs->blackY = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(2, &obj3);
+    cs->blackZ = obj3.getNum();
+    obj3.free();
+  }
+  obj2.free();
+  if (obj1.dictLookup("Gamma", &obj2)->isArray() &&
+      obj2.arrayGetLength() == 3) {
+    obj2.arrayGet(0, &obj3);
+    cs->gammaR = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(1, &obj3);
+    cs->gammaG = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(2, &obj3);
+    cs->gammaB = obj3.getNum();
+    obj3.free();
+  }
+  obj2.free();
+  if (obj1.dictLookup("Matrix", &obj2)->isArray() &&
+      obj2.arrayGetLength() == 9) {
+    for (i = 0; i < 9; ++i) {
+      obj2.arrayGet(i, &obj3);
+      cs->mat[i] = obj3.getNum();
+      obj3.free();
+    }
+  }
+  obj2.free();
+  obj1.free();
+  return cs;
+}
+
+void GfxCalRGBColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+  *gray = clip01((GfxColorComp)(0.299 * color->c[0] +
+				0.587 * color->c[1] +
+				0.114 * color->c[2] + 0.5));
+}
+
+void GfxCalRGBColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+  rgb->r = clip01(color->c[0]);
+  rgb->g = clip01(color->c[1]);
+  rgb->b = clip01(color->c[2]);
+}
+
+void GfxCalRGBColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+  GfxColorComp c, m, y, k;
+
+  c = clip01(gfxColorComp1 - color->c[0]);
+  m = clip01(gfxColorComp1 - color->c[1]);
+  y = clip01(gfxColorComp1 - color->c[2]);
+  k = c;
+  if (m < k) {
+    k = m;
+  }
+  if (y < k) {
+    k = y;
+  }
+  cmyk->c = c - k;
+  cmyk->m = m - k;
+  cmyk->y = y - k;
+  cmyk->k = k;
+}
+
+void GfxCalRGBColorSpace::getDefaultColor(GfxColor *color) {
+  color->c[0] = 0;
+  color->c[1] = 0;
+  color->c[2] = 0;
+}
+
+//------------------------------------------------------------------------
+// GfxDeviceCMYKColorSpace
+//------------------------------------------------------------------------
+
+GfxDeviceCMYKColorSpace::GfxDeviceCMYKColorSpace() {
+}
+
+GfxDeviceCMYKColorSpace::~GfxDeviceCMYKColorSpace() {
+}
+
+GfxColorSpace *GfxDeviceCMYKColorSpace::copy() {
+  return new GfxDeviceCMYKColorSpace();
+}
+
+void GfxDeviceCMYKColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+  *gray = clip01((GfxColorComp)(gfxColorComp1 - color->c[3]
+				- 0.3  * color->c[0]
+				- 0.59 * color->c[1]
+				- 0.11 * color->c[2] + 0.5));
+}
+
+void GfxDeviceCMYKColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+  double c, m, y, k, c1, m1, y1, k1, r, g, b, x;
+
+  c = colToDbl(color->c[0]);
+  m = colToDbl(color->c[1]);
+  y = colToDbl(color->c[2]);
+  k = colToDbl(color->c[3]);
+  c1 = 1 - c;
+  m1 = 1 - m;
+  y1 = 1 - y;
+  k1 = 1 - k;
+  // this is a matrix multiplication, unrolled for performance
+  //                        C M Y K
+  x = c1 * m1 * y1 * k1; // 0 0 0 0
+  r = g = b = x;
+  x = c1 * m1 * y1 * k;  // 0 0 0 1
+  r += 0.1373 * x;
+  g += 0.1216 * x;
+  b += 0.1255 * x;
+  x = c1 * m1 * y  * k1; // 0 0 1 0
+  r += x;
+  g += 0.9490 * x;
+  x = c1 * m1 * y  * k;  // 0 0 1 1
+  r += 0.1098 * x;
+  g += 0.1020 * x;
+  x = c1 * m  * y1 * k1; // 0 1 0 0
+  r += 0.9255 * x;
+  b += 0.5490 * x;
+  x = c1 * m  * y1 * k;  // 0 1 0 1
+  r += 0.1412 * x;
+  x = c1 * m  * y  * k1; // 0 1 1 0
+  r += 0.9294 * x;
+  g += 0.1098 * x;
+  b += 0.1412 * x;
+  x = c1 * m  * y  * k;  // 0 1 1 1
+  r += 0.1333 * x;
+  x = c  * m1 * y1 * k1; // 1 0 0 0
+  g += 0.6784 * x;
+  b += 0.9373 * x;
+  x = c  * m1 * y1 * k;  // 1 0 0 1
+  g += 0.0588 * x;
+  b += 0.1412 * x;
+  x = c  * m1 * y  * k1; // 1 0 1 0
+  g += 0.6510 * x;
+  b += 0.3137 * x;
+  x = c  * m1 * y  * k;  // 1 0 1 1
+  g += 0.0745 * x;
+  x = c  * m  * y1 * k1; // 1 1 0 0
+  r += 0.1804 * x;
+  g += 0.1922 * x;
+  b += 0.5725 * x;
+  x = c  * m  * y1 * k;  // 1 1 0 1
+  b += 0.0078 * x;
+  x = c  * m  * y  * k1; // 1 1 1 0
+  r += 0.2118 * x;
+  g += 0.2119 * x;
+  b += 0.2235 * x;
+  rgb->r = clip01(dblToCol(r));
+  rgb->g = clip01(dblToCol(g));
+  rgb->b = clip01(dblToCol(b));
+}
+
+void GfxDeviceCMYKColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+  cmyk->c = clip01(color->c[0]);
+  cmyk->m = clip01(color->c[1]);
+  cmyk->y = clip01(color->c[2]);
+  cmyk->k = clip01(color->c[3]);
+}
+
+void GfxDeviceCMYKColorSpace::getDefaultColor(GfxColor *color) {
+  color->c[0] = 0;
+  color->c[1] = 0;
+  color->c[2] = 0;
+  color->c[3] = gfxColorComp1;
+}
+
+//------------------------------------------------------------------------
+// GfxLabColorSpace
+//------------------------------------------------------------------------
+
+// This is the inverse of MatrixLMN in Example 4.10 from the PostScript
+// Language Reference, Third Edition.
+static double xyzrgb[3][3] = {
+  {  3.240449, -1.537136, -0.498531 },
+  { -0.969265,  1.876011,  0.041556 },
+  {  0.055643, -0.204026,  1.057229 }
+};
+
+GfxLabColorSpace::GfxLabColorSpace() {
+  whiteX = whiteY = whiteZ = 1;
+  blackX = blackY = blackZ = 0;
+  aMin = bMin = -100;
+  aMax = bMax = 100;
+}
+
+GfxLabColorSpace::~GfxLabColorSpace() {
+}
+
+GfxColorSpace *GfxLabColorSpace::copy() {
+  GfxLabColorSpace *cs;
+
+  cs = new GfxLabColorSpace();
+  cs->whiteX = whiteX;
+  cs->whiteY = whiteY;
+  cs->whiteZ = whiteZ;
+  cs->blackX = blackX;
+  cs->blackY = blackY;
+  cs->blackZ = blackZ;
+  cs->aMin = aMin;
+  cs->aMax = aMax;
+  cs->bMin = bMin;
+  cs->bMax = bMax;
+  cs->kr = kr;
+  cs->kg = kg;
+  cs->kb = kb;
+  return cs;
+}
+
+GfxColorSpace *GfxLabColorSpace::parse(Array *arr) {
+  GfxLabColorSpace *cs;
+  Object obj1, obj2, obj3;
+
+  arr->get(1, &obj1);
+  if (!obj1.isDict()) {
+    error(-1, "Bad Lab color space");
+    obj1.free();
+    return NULL;
+  }
+  cs = new GfxLabColorSpace();
+  if (obj1.dictLookup("WhitePoint", &obj2)->isArray() &&
+      obj2.arrayGetLength() == 3) {
+    obj2.arrayGet(0, &obj3);
+    cs->whiteX = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(1, &obj3);
+    cs->whiteY = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(2, &obj3);
+    cs->whiteZ = obj3.getNum();
+    obj3.free();
+  }
+  obj2.free();
+  if (obj1.dictLookup("BlackPoint", &obj2)->isArray() &&
+      obj2.arrayGetLength() == 3) {
+    obj2.arrayGet(0, &obj3);
+    cs->blackX = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(1, &obj3);
+    cs->blackY = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(2, &obj3);
+    cs->blackZ = obj3.getNum();
+    obj3.free();
+  }
+  obj2.free();
+  if (obj1.dictLookup("Range", &obj2)->isArray() &&
+      obj2.arrayGetLength() == 4) {
+    obj2.arrayGet(0, &obj3);
+    cs->aMin = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(1, &obj3);
+    cs->aMax = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(2, &obj3);
+    cs->bMin = obj3.getNum();
+    obj3.free();
+    obj2.arrayGet(3, &obj3);
+    cs->bMax = obj3.getNum();
+    obj3.free();
+  }
+  obj2.free();
+  obj1.free();
+
+  cs->kr = 1 / (xyzrgb[0][0] * cs->whiteX +
+		xyzrgb[0][1] * cs->whiteY +
+		xyzrgb[0][2] * cs->whiteZ);
+  cs->kg = 1 / (xyzrgb[1][0] * cs->whiteX +
+		xyzrgb[1][1] * cs->whiteY +
+		xyzrgb[1][2] * cs->whiteZ);
+  cs->kb = 1 / (xyzrgb[2][0] * cs->whiteX +
+		xyzrgb[2][1] * cs->whiteY +
+		xyzrgb[2][2] * cs->whiteZ);
+
+  return cs;
+}
+
+void GfxLabColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+  GfxRGB rgb;
+
+  getRGB(color, &rgb);
+  *gray = clip01((GfxColorComp)(0.299 * rgb.r +
+				0.587 * rgb.g +
+				0.114 * rgb.b + 0.5));
+}
+
+void GfxLabColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+  double X, Y, Z;
+  double t1, t2;
+  double r, g, b;
+
+  // convert L*a*b* to CIE 1931 XYZ color space
+  t1 = (colToDbl(color->c[0]) + 16) / 116;
+  t2 = t1 + colToDbl(color->c[1]) / 500;
+  if (t2 >= (6.0 / 29.0)) {
+    X = t2 * t2 * t2;
+  } else {
+    X = (108.0 / 841.0) * (t2 - (4.0 / 29.0));
+  }
+  X *= whiteX;
+  if (t1 >= (6.0 / 29.0)) {
+    Y = t1 * t1 * t1;
+  } else {
+    Y = (108.0 / 841.0) * (t1 - (4.0 / 29.0));
+  }
+  Y *= whiteY;
+  t2 = t1 - colToDbl(color->c[2]) / 200;
+  if (t2 >= (6.0 / 29.0)) {
+    Z = t2 * t2 * t2;
+  } else {
+    Z = (108.0 / 841.0) * (t2 - (4.0 / 29.0));
+  }
+  Z *= whiteZ;
+
+  // convert XYZ to RGB, including gamut mapping and gamma correction
+  r = xyzrgb[0][0] * X + xyzrgb[0][1] * Y + xyzrgb[0][2] * Z;
+  g = xyzrgb[1][0] * X + xyzrgb[1][1] * Y + xyzrgb[1][2] * Z;
+  b = xyzrgb[2][0] * X + xyzrgb[2][1] * Y + xyzrgb[2][2] * Z;
+  rgb->r = dblToCol(pow(clip01(r * kr), 0.5));
+  rgb->g = dblToCol(pow(clip01(g * kg), 0.5));
+  rgb->b = dblToCol(pow(clip01(b * kb), 0.5));
+}
+
+void GfxLabColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+  GfxRGB rgb;
+  GfxColorComp c, m, y, k;
+
+  getRGB(color, &rgb);
+  c = clip01(gfxColorComp1 - rgb.r);
+  m = clip01(gfxColorComp1 - rgb.g);
+  y = clip01(gfxColorComp1 - rgb.b);
+  k = c;
+  if (m < k) {
+    k = m;
+  }
+  if (y < k) {
+    k = y;
+  }
+  cmyk->c = c - k;
+  cmyk->m = m - k;
+  cmyk->y = y - k;
+  cmyk->k = k;
+}
+
+void GfxLabColorSpace::getDefaultColor(GfxColor *color) {
+  color->c[0] = 0;
+  if (aMin > 0) {
+    color->c[1] = dblToCol(aMin);
+  } else if (aMax < 0) {
+    color->c[1] = dblToCol(aMax);
+  } else {
+    color->c[1] = 0;
+  }
+  if (bMin > 0) {
+    color->c[2] = dblToCol(bMin);
+  } else if (bMax < 0) {
+    color->c[2] = dblToCol(bMax);
+  } else {
+    color->c[2] = 0;
+  }
+}
+
+void GfxLabColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange,
+					int maxImgPixel) {
+  decodeLow[0] = 0;
+  decodeRange[0] = 100;
+  decodeLow[1] = aMin;
+  decodeRange[1] = aMax - aMin;
+  decodeLow[2] = bMin;
+  decodeRange[2] = bMax - bMin;
+}
+
+//------------------------------------------------------------------------
+// GfxICCBasedColorSpace
+//------------------------------------------------------------------------
+
+GfxICCBasedColorSpace::GfxICCBasedColorSpace(int nCompsA, GfxColorSpace *altA,
+					     Ref *iccProfileStreamA) {
+  nComps = nCompsA;
+  alt = altA;
+  iccProfileStream = *iccProfileStreamA;
+  rangeMin[0] = rangeMin[1] = rangeMin[2] = rangeMin[3] = 0;
+  rangeMax[0] = rangeMax[1] = rangeMax[2] = rangeMax[3] = 1;
+}
+
+GfxICCBasedColorSpace::~GfxICCBasedColorSpace() {
+  delete alt;
+}
+
+GfxColorSpace *GfxICCBasedColorSpace::copy() {
+  GfxICCBasedColorSpace *cs;
+  int i;
+
+  cs = new GfxICCBasedColorSpace(nComps, alt->copy(), &iccProfileStream);
+  for (i = 0; i < 4; ++i) {
+    cs->rangeMin[i] = rangeMin[i];
+    cs->rangeMax[i] = rangeMax[i];
+  }
+  return cs;
+}
+
+GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr) {
+  GfxICCBasedColorSpace *cs;
+  Ref iccProfileStreamA;
+  int nCompsA;
+  GfxColorSpace *altA;
+  Dict *dict;
+  Object obj1, obj2, obj3;
+  int i;
+
+  arr->getNF(1, &obj1);
+  if (obj1.isRef()) {
+    iccProfileStreamA = obj1.getRef();
+  } else {
+    iccProfileStreamA.num = 0;
+    iccProfileStreamA.gen = 0;
+  }
+  obj1.free();
+  arr->get(1, &obj1);
+  if (!obj1.isStream()) {
+    error(-1, "Bad ICCBased color space (stream)");
+    obj1.free();
+    return NULL;
+  }
+  dict = obj1.streamGetDict();
+  if (!dict->lookup("N", &obj2)->isInt()) {
+    error(-1, "Bad ICCBased color space (N)");
+    obj2.free();
+    obj1.free();
+    return NULL;
+  }
+  nCompsA = obj2.getInt();
+  obj2.free();
+  if (nCompsA > gfxColorMaxComps) {
+    error(-1, "ICCBased color space with too many (%d > %d) components",
+	  nCompsA, gfxColorMaxComps);
+    nCompsA = gfxColorMaxComps;
+  }
+  if (dict->lookup("Alternate", &obj2)->isNull() ||
+      !(altA = GfxColorSpace::parse(&obj2))) {
+    switch (nCompsA) {
+    case 1:
+      altA = new GfxDeviceGrayColorSpace();
+      break;
+    case 3:
+      altA = new GfxDeviceRGBColorSpace();
+      break;
+    case 4:
+      altA = new GfxDeviceCMYKColorSpace();
+      break;
+    default:
+      error(-1, "Bad ICCBased color space - invalid N");
+      obj2.free();
+      obj1.free();
+      return NULL;
+    }
+  }
+  obj2.free();
+  cs = new GfxICCBasedColorSpace(nCompsA, altA, &iccProfileStreamA);
+  if (dict->lookup("Range", &obj2)->isArray() &&
+      obj2.arrayGetLength() == 2 * nCompsA) {
+    for (i = 0; i < nCompsA; ++i) {
+      obj2.arrayGet(2*i, &obj3);
+      cs->rangeMin[i] = obj3.getNum();
+      obj3.free();
+      obj2.arrayGet(2*i+1, &obj3);
+      cs->rangeMax[i] = obj3.getNum();
+      obj3.free();
+    }
+  }
+  obj2.free();
+  obj1.free();
+  return cs;
+}
+
+void GfxICCBasedColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+  alt->getGray(color, gray);
+}
+
+void GfxICCBasedColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+  alt->getRGB(color, rgb);
+}
+
+void GfxICCBasedColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+  alt->getCMYK(color, cmyk);
+}
+
+void GfxICCBasedColorSpace::getDefaultColor(GfxColor *color) {
+  int i;
+
+  for (i = 0; i < nComps; ++i) {
+    if (rangeMin[i] > 0) {
+      color->c[i] = dblToCol(rangeMin[i]);
+    } else if (rangeMax[i] < 0) {
+      color->c[i] = dblToCol(rangeMax[i]);
+    } else {
+      color->c[i] = 0;
+    }
+  }
+}
+
+void GfxICCBasedColorSpace::getDefaultRanges(double *decodeLow,
+					     double *decodeRange,
+					     int maxImgPixel) {
+  alt->getDefaultRanges(decodeLow, decodeRange, maxImgPixel);
+
+#if 0
+  // this is nominally correct, but some PDF files don't set the
+  // correct ranges in the ICCBased dict
+  int i;
+
+  for (i = 0; i < nComps; ++i) {
+    decodeLow[i] = rangeMin[i];
+    decodeRange[i] = rangeMax[i] - rangeMin[i];
+  }
+#endif
+}
+
+//------------------------------------------------------------------------
+// GfxIndexedColorSpace
+//------------------------------------------------------------------------
+
+GfxIndexedColorSpace::GfxIndexedColorSpace(GfxColorSpace *baseA,
+					   int indexHighA) {
+  base = baseA;
+  indexHigh = indexHighA;
+  lookup = (Guchar *)gmallocn((indexHigh + 1) * base->getNComps(),
+			      sizeof(Guchar));
+}
+
+GfxIndexedColorSpace::~GfxIndexedColorSpace() {
+  delete base;
+  gfree(lookup);
+}
+
+GfxColorSpace *GfxIndexedColorSpace::copy() {
+  GfxIndexedColorSpace *cs;
+
+  cs = new GfxIndexedColorSpace(base->copy(), indexHigh);
+  memcpy(cs->lookup, lookup,
+	 (indexHigh + 1) * base->getNComps() * sizeof(Guchar));
+  return cs;
+}
+
+GfxColorSpace *GfxIndexedColorSpace::parse(Array *arr) {
+  GfxIndexedColorSpace *cs;
+  GfxColorSpace *baseA;
+  int indexHighA;
+  Object obj1;
+  int x;
+  char *s;
+  int n, i, j;
+
+  if (arr->getLength() != 4) {
+    error(-1, "Bad Indexed color space");
+    goto err1;
+  }
+  arr->get(1, &obj1);
+  if (!(baseA = GfxColorSpace::parse(&obj1))) {
+    error(-1, "Bad Indexed color space (base color space)");
+    goto err2;
+  }
+  obj1.free();
+  if (!arr->get(2, &obj1)->isInt()) {
+    error(-1, "Bad Indexed color space (hival)");
+    delete baseA;
+    goto err2;
+  }
+  indexHighA = obj1.getInt();
+  if (indexHighA < 0 || indexHighA > 255) {
+    // the PDF spec requires indexHigh to be in [0,255] -- allowing
+    // values larger than 255 creates a security hole: if nComps *
+    // indexHigh is greater than 2^31, the loop below may overwrite
+    // past the end of the array
+    error(-1, "Bad Indexed color space (invalid indexHigh value)");
+    delete baseA;
+    goto err2;
+  }
+  obj1.free();
+  cs = new GfxIndexedColorSpace(baseA, indexHighA);
+  arr->get(3, &obj1);
+  n = baseA->getNComps();
+  if (obj1.isStream()) {
+    obj1.streamReset();
+    for (i = 0; i <= indexHighA; ++i) {
+      for (j = 0; j < n; ++j) {
+	if ((x = obj1.streamGetChar()) == EOF) {
+	  error(-1, "Bad Indexed color space (lookup table stream too short)");
+	  goto err3;
+	}
+	cs->lookup[i*n + j] = (Guchar)x;
+      }
+    }
+    obj1.streamClose();
+  } else if (obj1.isString()) {
+    if (obj1.getString()->getLength() < (indexHighA + 1) * n) {
+      error(-1, "Bad Indexed color space (lookup table string too short)");
+      goto err3;
+    }
+    s = obj1.getString()->getCString();
+    for (i = 0; i <= indexHighA; ++i) {
+      for (j = 0; j < n; ++j) {
+	cs->lookup[i*n + j] = (Guchar)*s++;
+      }
+    }
+  } else {
+    error(-1, "Bad Indexed color space (lookup table)");
+    goto err3;
+  }
+  obj1.free();
+  return cs;
+
+ err3:
+  delete cs;
+ err2:
+  obj1.free();
+ err1:
+  return NULL;
+}
+
+GfxColor *GfxIndexedColorSpace::mapColorToBase(GfxColor *color,
+					       GfxColor *baseColor) {
+  Guchar *p;
+  double low[gfxColorMaxComps], range[gfxColorMaxComps];
+  int n, i;
+
+  n = base->getNComps();
+  base->getDefaultRanges(low, range, indexHigh);
+  p = &lookup[(int)(colToDbl(color->c[0]) + 0.5) * n];
+  for (i = 0; i < n; ++i) {
+    baseColor->c[i] = dblToCol(low[i] + (p[i] / 255.0) * range[i]);
+  }
+  return baseColor;
+}
+
+void GfxIndexedColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+  GfxColor color2;
+
+  base->getGray(mapColorToBase(color, &color2), gray);
+}
+
+void GfxIndexedColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+  GfxColor color2;
+
+  base->getRGB(mapColorToBase(color, &color2), rgb);
+}
+
+void GfxIndexedColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+  GfxColor color2;
+
+  base->getCMYK(mapColorToBase(color, &color2), cmyk);
+}
+
+void GfxIndexedColorSpace::getDefaultColor(GfxColor *color) {
+  color->c[0] = 0;
+}
+
+void GfxIndexedColorSpace::getDefaultRanges(double *decodeLow,
+					    double *decodeRange,
+					    int maxImgPixel) {
+  decodeLow[0] = 0;
+  decodeRange[0] = maxImgPixel;
+}
+
+//------------------------------------------------------------------------
+// GfxSeparationColorSpace
+//------------------------------------------------------------------------
+
+GfxSeparationColorSpace::GfxSeparationColorSpace(GString *nameA,
+						 GfxColorSpace *altA,
+						 Function *funcA) {
+  name = nameA;
+  alt = altA;
+  func = funcA;
+  nonMarking = !name->cmp("None");
+}
+
+GfxSeparationColorSpace::~GfxSeparationColorSpace() {
+  delete name;
+  delete alt;
+  delete func;
+}
+
+GfxColorSpace *GfxSeparationColorSpace::copy() {
+  return new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy());
+}
+
+//~ handle the 'All' and 'None' colorants
+GfxColorSpace *GfxSeparationColorSpace::parse(Array *arr) {
+  GfxSeparationColorSpace *cs;
+  GString *nameA;
+  GfxColorSpace *altA;
+  Function *funcA;
+  Object obj1;
+
+  if (arr->getLength() != 4) {
+    error(-1, "Bad Separation color space");
+    goto err1;
+  }
+  if (!arr->get(1, &obj1)->isName()) {
+    error(-1, "Bad Separation color space (name)");
+    goto err2;
+  }
+  nameA = new GString(obj1.getName());
+  obj1.free();
+  arr->get(2, &obj1);
+  if (!(altA = GfxColorSpace::parse(&obj1))) {
+    error(-1, "Bad Separation color space (alternate color space)");
+    goto err3;
+  }
+  obj1.free();
+  arr->get(3, &obj1);
+  if (!(funcA = Function::parse(&obj1))) {
+    goto err4;
+  }
+  obj1.free();
+  cs = new GfxSeparationColorSpace(nameA, altA, funcA);
+  return cs;
+
+ err4:
+  delete altA;
+ err3:
+  delete nameA;
+ err2:
+  obj1.free();
+ err1:
+  return NULL;
+}
+
+void GfxSeparationColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+  double x;
+  double c[gfxColorMaxComps];
+  GfxColor color2;
+  int i;
+
+  x = colToDbl(color->c[0]);
+  func->transform(&x, c);
+  for (i = 0; i < alt->getNComps(); ++i) {
+    color2.c[i] = dblToCol(c[i]);
+  }
+  alt->getGray(&color2, gray);
+}
+
+void GfxSeparationColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+  double x;
+  double c[gfxColorMaxComps];
+  GfxColor color2;
+  int i;
+
+  x = colToDbl(color->c[0]);
+  func->transform(&x, c);
+  for (i = 0; i < alt->getNComps(); ++i) {
+    color2.c[i] = dblToCol(c[i]);
+  }
+  alt->getRGB(&color2, rgb);
+}
+
+void GfxSeparationColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+  double x;
+  double c[gfxColorMaxComps];
+  GfxColor color2;
+  int i;
+
+  x = colToDbl(color->c[0]);
+  func->transform(&x, c);
+  for (i = 0; i < alt->getNComps(); ++i) {
+    color2.c[i] = dblToCol(c[i]);
+  }
+  alt->getCMYK(&color2, cmyk);
+}
+
+void GfxSeparationColorSpace::getDefaultColor(GfxColor *color) {
+  color->c[0] = gfxColorComp1;
+}
+
+//------------------------------------------------------------------------
+// GfxDeviceNColorSpace
+//------------------------------------------------------------------------
+
+GfxDeviceNColorSpace::GfxDeviceNColorSpace(int nCompsA,
+					   GfxColorSpace *altA,
+					   Function *funcA) {
+  nComps = nCompsA;
+  alt = altA;
+  func = funcA;
+  nonMarking = gFalse;
+}
+
+GfxDeviceNColorSpace::~GfxDeviceNColorSpace() {
+  int i;
+
+  for (i = 0; i < nComps; ++i) {
+    delete names[i];
+  }
+  delete alt;
+  delete func;
+}
+
+GfxColorSpace *GfxDeviceNColorSpace::copy() {
+  GfxDeviceNColorSpace *cs;
+  int i;
+
+  cs = new GfxDeviceNColorSpace(nComps, alt->copy(), func->copy());
+  for (i = 0; i < nComps; ++i) {
+    cs->names[i] = names[i]->copy();
+  }
+  cs->nonMarking = nonMarking;
+  return cs;
+}
+
+//~ handle the 'None' colorant
+GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr) {
+  GfxDeviceNColorSpace *cs;
+  int nCompsA;
+  GString *namesA[gfxColorMaxComps];
+  GfxColorSpace *altA;
+  Function *funcA;
+  Object obj1, obj2;
+  int i;
+
+  if (arr->getLength() != 4 && arr->getLength() != 5) {
+    error(-1, "Bad DeviceN color space");
+    goto err1;
+  }
+  if (!arr->get(1, &obj1)->isArray()) {
+    error(-1, "Bad DeviceN color space (names)");
+    goto err2;
+  }
+  nCompsA = obj1.arrayGetLength();
+  if (nCompsA > gfxColorMaxComps) {
+    error(-1, "DeviceN color space with too many (%d > %d) components",
+	  nCompsA, gfxColorMaxComps);
+    nCompsA = gfxColorMaxComps;
+  }
+  for (i = 0; i < nCompsA; ++i) {
+    if (!obj1.arrayGet(i, &obj2)->isName()) {
+      error(-1, "Bad DeviceN color space (names)");
+      obj2.free();
+      goto err2;
+    }
+    namesA[i] = new GString(obj2.getName());
+    obj2.free();
+  }
+  obj1.free();
+  arr->get(2, &obj1);
+  if (!(altA = GfxColorSpace::parse(&obj1))) {
+    error(-1, "Bad DeviceN color space (alternate color space)");
+    goto err3;
+  }
+  obj1.free();
+  arr->get(3, &obj1);
+  if (!(funcA = Function::parse(&obj1))) {
+    goto err4;
+  }
+  obj1.free();
+  cs = new GfxDeviceNColorSpace(nCompsA, altA, funcA);
+  cs->nonMarking = gTrue;
+  for (i = 0; i < nCompsA; ++i) {
+    cs->names[i] = namesA[i];
+    if (namesA[i]->cmp("None")) {
+      cs->nonMarking = gFalse;
+    }
+  }
+  return cs;
+
+ err4:
+  delete altA;
+ err3:
+  for (i = 0; i < nCompsA; ++i) {
+    delete namesA[i];
+  }
+ err2:
+  obj1.free();
+ err1:
+  return NULL;
+}
+
+void GfxDeviceNColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+  double x[gfxColorMaxComps], c[gfxColorMaxComps];
+  GfxColor color2;
+  int i;
+
+  for (i = 0; i < nComps; ++i) {
+    x[i] = colToDbl(color->c[i]);
+  }
+  func->transform(x, c);
+  for (i = 0; i < alt->getNComps(); ++i) {
+    color2.c[i] = dblToCol(c[i]);
+  }
+  alt->getGray(&color2, gray);
+}
+
+void GfxDeviceNColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+  double x[gfxColorMaxComps], c[gfxColorMaxComps];
+  GfxColor color2;
+  int i;
+
+  for (i = 0; i < nComps; ++i) {
+    x[i] = colToDbl(color->c[i]);
+  }
+  func->transform(x, c);
+  for (i = 0; i < alt->getNComps(); ++i) {
+    color2.c[i] = dblToCol(c[i]);
+  }
+  alt->getRGB(&color2, rgb);
+}
+
+void GfxDeviceNColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+  double x[gfxColorMaxComps], c[gfxColorMaxComps];
+  GfxColor color2;
+  int i;
+
+  for (i = 0; i < nComps; ++i) {
+    x[i] = colToDbl(color->c[i]);
+  }
+  func->transform(x, c);
+  for (i = 0; i < alt->getNComps(); ++i) {
+    color2.c[i] = dblToCol(c[i]);
+  }
+  alt->getCMYK(&color2, cmyk);
+}
+
+void GfxDeviceNColorSpace::getDefaultColor(GfxColor *color) {
+  int i;
+
+  for (i = 0; i < nComps; ++i) {
+    color->c[i] = gfxColorComp1;
+  }
+}
+
+//------------------------------------------------------------------------
+// GfxPatternColorSpace
+//------------------------------------------------------------------------
+
+GfxPatternColorSpace::GfxPatternColorSpace(GfxColorSpace *underA) {
+  under = underA;
+}
+
+GfxPatternColorSpace::~GfxPatternColorSpace() {
+  if (under) {
+    delete under;
+  }
+}
+
+GfxColorSpace *GfxPatternColorSpace::copy() {
+  return new GfxPatternColorSpace(under ? under->copy() :
+				          (GfxColorSpace *)NULL);
+}
+
+GfxColorSpace *GfxPatternColorSpace::parse(Array *arr) {
+  GfxPatternColorSpace *cs;
+  GfxColorSpace *underA;
+  Object obj1;
+
+  if (arr->getLength() != 1 && arr->getLength() != 2) {
+    error(-1, "Bad Pattern color space");
+    return NULL;
+  }
+  underA = NULL;
+  if (arr->getLength() == 2) {
+    arr->get(1, &obj1);
+    if (!(underA = GfxColorSpace::parse(&obj1))) {
+      error(-1, "Bad Pattern color space (underlying color space)");
+      obj1.free();
+      return NULL;
+    }
+    obj1.free();
+  }
+  cs = new GfxPatternColorSpace(underA);
+  return cs;
+}
+
+void GfxPatternColorSpace::getGray(GfxColor *color, GfxGray *gray) {
+  *gray = 0;
+}
+
+void GfxPatternColorSpace::getRGB(GfxColor *color, GfxRGB *rgb) {
+  rgb->r = rgb->g = rgb->b = 0;
+}
+
+void GfxPatternColorSpace::getCMYK(GfxColor *color, GfxCMYK *cmyk) {
+  cmyk->c = cmyk->m = cmyk->y = 0;
+  cmyk->k = 1;
+}
+
+void GfxPatternColorSpace::getDefaultColor(GfxColor *color) {
+  // not used
+}
+
+//------------------------------------------------------------------------
+// Pattern
+//------------------------------------------------------------------------
+
+GfxPattern::GfxPattern(int typeA) {
+  type = typeA;
+}
+
+GfxPattern::~GfxPattern() {
+}
+
+GfxPattern *GfxPattern::parse(Object *obj) {
+  GfxPattern *pattern;
+  Object obj1;
+
+  if (obj->isDict()) {
+    obj->dictLookup("PatternType", &obj1);
+  } else if (obj->isStream()) {
+    obj->streamGetDict()->lookup("PatternType", &obj1);
+  } else {
+    return NULL;
+  }
+  pattern = NULL;
+  if (obj1.isInt() && obj1.getInt() == 1) {
+    pattern = GfxTilingPattern::parse(obj);
+  } else if (obj1.isInt() && obj1.getInt() == 2) {
+    pattern = GfxShadingPattern::parse(obj);
+  }
+  obj1.free();
+  return pattern;
+}
+
+//------------------------------------------------------------------------
+// GfxTilingPattern
+//------------------------------------------------------------------------
+
+GfxTilingPattern *GfxTilingPattern::parse(Object *patObj) {
+  GfxTilingPattern *pat;
+  Dict *dict;
+  int paintTypeA, tilingTypeA;
+  double bboxA[4], matrixA[6];
+  double xStepA, yStepA;
+  Object resDictA;
+  Object obj1, obj2;
+  int i;
+
+  if (!patObj->isStream()) {
+    return NULL;
+  }
+  dict = patObj->streamGetDict();
+
+  if (dict->lookup("PaintType", &obj1)->isInt()) {
+    paintTypeA = obj1.getInt();
+  } else {
+    paintTypeA = 1;
+    error(-1, "Invalid or missing PaintType in pattern");
+  }
+  obj1.free();
+  if (dict->lookup("TilingType", &obj1)->isInt()) {
+    tilingTypeA = obj1.getInt();
+  } else {
+    tilingTypeA = 1;
+    error(-1, "Invalid or missing TilingType in pattern");
+  }
+  obj1.free();
+  bboxA[0] = bboxA[1] = 0;
+  bboxA[2] = bboxA[3] = 1;
+  if (dict->lookup("BBox", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 4) {
+    for (i = 0; i < 4; ++i) {
+      if (obj1.arrayGet(i, &obj2)->isNum()) {
+	bboxA[i] = obj2.getNum();
+      }
+      obj2.free();
+    }
+  } else {
+    error(-1, "Invalid or missing BBox in pattern");
+  }
+  obj1.free();
+  if (dict->lookup("XStep", &obj1)->isNum()) {
+    xStepA = obj1.getNum();
+  } else {
+    xStepA = 1;
+    error(-1, "Invalid or missing XStep in pattern");
+  }
+  obj1.free();
+  if (dict->lookup("YStep", &obj1)->isNum()) {
+    yStepA = obj1.getNum();
+  } else {
+    yStepA = 1;
+    error(-1, "Invalid or missing YStep in pattern");
+  }
+  obj1.free();
+  if (!dict->lookup("Resources", &resDictA)->isDict()) {
+    resDictA.free();
+    resDictA.initNull();
+    error(-1, "Invalid or missing Resources in pattern");
+  }
+  matrixA[0] = 1; matrixA[1] = 0;
+  matrixA[2] = 0; matrixA[3] = 1;
+  matrixA[4] = 0; matrixA[5] = 0;
+  if (dict->lookup("Matrix", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 6) {
+    for (i = 0; i < 6; ++i) {
+      if (obj1.arrayGet(i, &obj2)->isNum()) {
+	matrixA[i] = obj2.getNum();
+      }
+      obj2.free();
+    }
+  }
+  obj1.free();
+
+  pat = new GfxTilingPattern(paintTypeA, tilingTypeA, bboxA, xStepA, yStepA,
+			     &resDictA, matrixA, patObj);
+  resDictA.free();
+  return pat;
+}
+
+GfxTilingPattern::GfxTilingPattern(int paintTypeA, int tilingTypeA,
+				   double *bboxA, double xStepA, double yStepA,
+				   Object *resDictA, double *matrixA,
+				   Object *contentStreamA):
+  GfxPattern(1)
+{
+  int i;
+
+  paintType = paintTypeA;
+  tilingType = tilingTypeA;
+  for (i = 0; i < 4; ++i) {
+    bbox[i] = bboxA[i];
+  }
+  xStep = xStepA;
+  yStep = yStepA;
+  resDictA->copy(&resDict);
+  for (i = 0; i < 6; ++i) {
+    matrix[i] = matrixA[i];
+  }
+  contentStreamA->copy(&contentStream);
+}
+
+GfxTilingPattern::~GfxTilingPattern() {
+  resDict.free();
+  contentStream.free();
+}
+
+GfxPattern *GfxTilingPattern::copy() {
+  return new GfxTilingPattern(paintType, tilingType, bbox, xStep, yStep,
+			      &resDict, matrix, &contentStream);
+}
+
+//------------------------------------------------------------------------
+// GfxShadingPattern
+//------------------------------------------------------------------------
+
+GfxShadingPattern *GfxShadingPattern::parse(Object *patObj) {
+  Dict *dict;
+  GfxShading *shadingA;
+  double matrixA[6];
+  Object obj1, obj2;
+  int i;
+
+  if (!patObj->isDict()) {
+    return NULL;
+  }
+  dict = patObj->getDict();
+
+  dict->lookup("Shading", &obj1);
+  shadingA = GfxShading::parse(&obj1);
+  obj1.free();
+  if (!shadingA) {
+    return NULL;
+  }
+
+  matrixA[0] = 1; matrixA[1] = 0;
+  matrixA[2] = 0; matrixA[3] = 1;
+  matrixA[4] = 0; matrixA[5] = 0;
+  if (dict->lookup("Matrix", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 6) {
+    for (i = 0; i < 6; ++i) {
+      if (obj1.arrayGet(i, &obj2)->isNum()) {
+	matrixA[i] = obj2.getNum();
+      }
+      obj2.free();
+    }
+  }
+  obj1.free();
+
+  return new GfxShadingPattern(shadingA, matrixA);
+}
+
+GfxShadingPattern::GfxShadingPattern(GfxShading *shadingA, double *matrixA):
+  GfxPattern(2)
+{
+  int i;
+
+  shading = shadingA;
+  for (i = 0; i < 6; ++i) {
+    matrix[i] = matrixA[i];
+  }
+}
+
+GfxShadingPattern::~GfxShadingPattern() {
+  delete shading;
+}
+
+GfxPattern *GfxShadingPattern::copy() {
+  return new GfxShadingPattern(shading->copy(), matrix);
+}
+
+//------------------------------------------------------------------------
+// GfxShading
+//------------------------------------------------------------------------
+
+GfxShading::GfxShading(int typeA) {
+  type = typeA;
+  colorSpace = NULL;
+}
+
+GfxShading::GfxShading(GfxShading *shading) {
+  int i;
+
+  type = shading->type;
+  colorSpace = shading->colorSpace->copy();
+  for (i = 0; i < gfxColorMaxComps; ++i) {
+    background.c[i] = shading->background.c[i];
+  }
+  hasBackground = shading->hasBackground;
+  xMin = shading->xMin;
+  yMin = shading->yMin;
+  xMax = shading->xMax;
+  yMax = shading->yMax;
+  hasBBox = shading->hasBBox;
+}
+
+GfxShading::~GfxShading() {
+  if (colorSpace) {
+    delete colorSpace;
+  }
+}
+
+GfxShading *GfxShading::parse(Object *obj) {
+  GfxShading *shading;
+  Dict *dict;
+  int typeA;
+  Object obj1;
+
+  if (obj->isDict()) {
+    dict = obj->getDict();
+  } else if (obj->isStream()) {
+    dict = obj->streamGetDict();
+  } else {
+    return NULL;
+  }
+
+  if (!dict->lookup("ShadingType", &obj1)->isInt()) {
+    error(-1, "Invalid ShadingType in shading dictionary");
+    obj1.free();
+    return NULL;
+  }
+  typeA = obj1.getInt();
+  obj1.free();
+
+  switch (typeA) {
+  case 1:
+    shading = GfxFunctionShading::parse(dict);
+    break;
+  case 2:
+    shading = GfxAxialShading::parse(dict);
+    break;
+  case 3:
+    shading = GfxRadialShading::parse(dict);
+    break;
+  case 4:
+    if (obj->isStream()) {
+      shading = GfxGouraudTriangleShading::parse(4, dict, obj->getStream());
+    } else {
+      error(-1, "Invalid Type 4 shading object");
+      goto err1;
+    }
+    break;
+  case 5:
+    if (obj->isStream()) {
+      shading = GfxGouraudTriangleShading::parse(5, dict, obj->getStream());
+    } else {
+      error(-1, "Invalid Type 5 shading object");
+      goto err1;
+    }
+    break;
+  case 6:
+    if (obj->isStream()) {
+      shading = GfxPatchMeshShading::parse(6, dict, obj->getStream());
+    } else {
+      error(-1, "Invalid Type 6 shading object");
+      goto err1;
+    }
+    break;
+  case 7:
+    if (obj->isStream()) {
+      shading = GfxPatchMeshShading::parse(7, dict, obj->getStream());
+    } else {
+      error(-1, "Invalid Type 7 shading object");
+      goto err1;
+    }
+    break;
+  default:
+    error(-1, "Unimplemented shading type %d", typeA);
+    goto err1;
+  }
+
+  return shading;
+
+ err1:
+  return NULL;
+}
+
+GBool GfxShading::init(Dict *dict) {
+  Object obj1, obj2;
+  int i;
+
+  dict->lookup("ColorSpace", &obj1);
+  if (!(colorSpace = GfxColorSpace::parse(&obj1))) {
+    error(-1, "Bad color space in shading dictionary");
+    obj1.free();
+    return gFalse;
+  }
+  obj1.free();
+
+  for (i = 0; i < gfxColorMaxComps; ++i) {
+    background.c[i] = 0;
+  }
+  hasBackground = gFalse;
+  if (dict->lookup("Background", &obj1)->isArray()) {
+    if (obj1.arrayGetLength() == colorSpace->getNComps()) {
+      hasBackground = gTrue;
+      for (i = 0; i < colorSpace->getNComps(); ++i) {
+	background.c[i] = dblToCol(obj1.arrayGet(i, &obj2)->getNum());
+	obj2.free();
+      }
+    } else {
+      error(-1, "Bad Background in shading dictionary");
+    }
+  }
+  obj1.free();
+
+  xMin = yMin = xMax = yMax = 0;
+  hasBBox = gFalse;
+  if (dict->lookup("BBox", &obj1)->isArray()) {
+    if (obj1.arrayGetLength() == 4) {
+      hasBBox = gTrue;
+      xMin = obj1.arrayGet(0, &obj2)->getNum();
+      obj2.free();
+      yMin = obj1.arrayGet(1, &obj2)->getNum();
+      obj2.free();
+      xMax = obj1.arrayGet(2, &obj2)->getNum();
+      obj2.free();
+      yMax = obj1.arrayGet(3, &obj2)->getNum();
+      obj2.free();
+    } else {
+      error(-1, "Bad BBox in shading dictionary");
+    }
+  }
+  obj1.free();
+
+  return gTrue;
+}
+
+//------------------------------------------------------------------------
+// GfxFunctionShading
+//------------------------------------------------------------------------
+
+GfxFunctionShading::GfxFunctionShading(double x0A, double y0A,
+				       double x1A, double y1A,
+				       double *matrixA,
+				       Function **funcsA, int nFuncsA):
+  GfxShading(1)
+{
+  int i;
+
+  x0 = x0A;
+  y0 = y0A;
+  x1 = x1A;
+  y1 = y1A;
+  for (i = 0; i < 6; ++i) {
+    matrix[i] = matrixA[i];
+  }
+  nFuncs = nFuncsA;
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i] = funcsA[i];
+  }
+}
+
+GfxFunctionShading::GfxFunctionShading(GfxFunctionShading *shading):
+  GfxShading(shading)
+{
+  int i;
+
+  x0 = shading->x0;
+  y0 = shading->y0;
+  x1 = shading->x1;
+  y1 = shading->y1;
+  for (i = 0; i < 6; ++i) {
+    matrix[i] = shading->matrix[i];
+  }
+  nFuncs = shading->nFuncs;
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i] = shading->funcs[i]->copy();
+  }
+}
+
+GfxFunctionShading::~GfxFunctionShading() {
+  int i;
+
+  for (i = 0; i < nFuncs; ++i) {
+    delete funcs[i];
+  }
+}
+
+GfxFunctionShading *GfxFunctionShading::parse(Dict *dict) {
+  GfxFunctionShading *shading;
+  double x0A, y0A, x1A, y1A;
+  double matrixA[6];
+  Function *funcsA[gfxColorMaxComps];
+  int nFuncsA;
+  Object obj1, obj2;
+  int i;
+
+  x0A = y0A = 0;
+  x1A = y1A = 1;
+  if (dict->lookup("Domain", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 4) {
+    x0A = obj1.arrayGet(0, &obj2)->getNum();
+    obj2.free();
+    y0A = obj1.arrayGet(1, &obj2)->getNum();
+    obj2.free();
+    x1A = obj1.arrayGet(2, &obj2)->getNum();
+    obj2.free();
+    y1A = obj1.arrayGet(3, &obj2)->getNum();
+    obj2.free();
+  }
+  obj1.free();
+
+  matrixA[0] = 1; matrixA[1] = 0;
+  matrixA[2] = 0; matrixA[3] = 1;
+  matrixA[4] = 0; matrixA[5] = 0;
+  if (dict->lookup("Matrix", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 6) {
+    matrixA[0] = obj1.arrayGet(0, &obj2)->getNum();
+    obj2.free();
+    matrixA[1] = obj1.arrayGet(1, &obj2)->getNum();
+    obj2.free();
+    matrixA[2] = obj1.arrayGet(2, &obj2)->getNum();
+    obj2.free();
+    matrixA[3] = obj1.arrayGet(3, &obj2)->getNum();
+    obj2.free();
+    matrixA[4] = obj1.arrayGet(4, &obj2)->getNum();
+    obj2.free();
+    matrixA[5] = obj1.arrayGet(5, &obj2)->getNum();
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("Function", &obj1);
+  if (obj1.isArray()) {
+    nFuncsA = obj1.arrayGetLength();
+    if (nFuncsA > gfxColorMaxComps) {
+      error(-1, "Invalid Function array in shading dictionary");
+      goto err1;
+    }
+    for (i = 0; i < nFuncsA; ++i) {
+      obj1.arrayGet(i, &obj2);
+      if (!(funcsA[i] = Function::parse(&obj2))) {
+	goto err2;
+      }
+      obj2.free();
+    }
+  } else {
+    nFuncsA = 1;
+    if (!(funcsA[0] = Function::parse(&obj1))) {
+      goto err1;
+    }
+  }
+  obj1.free();
+
+  shading = new GfxFunctionShading(x0A, y0A, x1A, y1A, matrixA,
+				   funcsA, nFuncsA);
+  if (!shading->init(dict)) {
+    delete shading;
+    return NULL;
+  }
+  return shading;
+
+ err2:
+  obj2.free();
+ err1:
+  obj1.free();
+  return NULL;
+}
+
+GfxShading *GfxFunctionShading::copy() {
+  return new GfxFunctionShading(this);
+}
+
+void GfxFunctionShading::getColor(double x, double y, GfxColor *color) {
+  double in[2], out[gfxColorMaxComps];
+  int i;
+
+  // NB: there can be one function with n outputs or n functions with
+  // one output each (where n = number of color components)
+  for (i = 0; i < gfxColorMaxComps; ++i) {
+    out[i] = 0;
+  }
+  in[0] = x;
+  in[1] = y;
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i]->transform(in, &out[i]);
+  }
+  for (i = 0; i < gfxColorMaxComps; ++i) {
+    color->c[i] = dblToCol(out[i]);
+  }
+}
+
+//------------------------------------------------------------------------
+// GfxAxialShading
+//------------------------------------------------------------------------
+
+GfxAxialShading::GfxAxialShading(double x0A, double y0A,
+				 double x1A, double y1A,
+				 double t0A, double t1A,
+				 Function **funcsA, int nFuncsA,
+				 GBool extend0A, GBool extend1A):
+  GfxShading(2)
+{
+  int i;
+
+  x0 = x0A;
+  y0 = y0A;
+  x1 = x1A;
+  y1 = y1A;
+  t0 = t0A;
+  t1 = t1A;
+  nFuncs = nFuncsA;
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i] = funcsA[i];
+  }
+  extend0 = extend0A;
+  extend1 = extend1A;
+}
+
+GfxAxialShading::GfxAxialShading(GfxAxialShading *shading):
+  GfxShading(shading)
+{
+  int i;
+
+  x0 = shading->x0;
+  y0 = shading->y0;
+  x1 = shading->x1;
+  y1 = shading->y1;
+  t0 = shading->t0;
+  y1 = shading->t1;
+  nFuncs = shading->nFuncs;
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i] = shading->funcs[i]->copy();
+  }
+  extend0 = shading->extend0;
+  extend1 = shading->extend1;
+}
+
+GfxAxialShading::~GfxAxialShading() {
+  int i;
+
+  for (i = 0; i < nFuncs; ++i) {
+    delete funcs[i];
+  }
+}
+
+GfxAxialShading *GfxAxialShading::parse(Dict *dict) {
+  GfxAxialShading *shading;
+  double x0A, y0A, x1A, y1A;
+  double t0A, t1A;
+  Function *funcsA[gfxColorMaxComps];
+  int nFuncsA;
+  GBool extend0A, extend1A;
+  Object obj1, obj2;
+  int i;
+
+  x0A = y0A = x1A = y1A = 0;
+  if (dict->lookup("Coords", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 4) {
+    x0A = obj1.arrayGet(0, &obj2)->getNum();
+    obj2.free();
+    y0A = obj1.arrayGet(1, &obj2)->getNum();
+    obj2.free();
+    x1A = obj1.arrayGet(2, &obj2)->getNum();
+    obj2.free();
+    y1A = obj1.arrayGet(3, &obj2)->getNum();
+    obj2.free();
+  } else {
+    error(-1, "Missing or invalid Coords in shading dictionary");
+    goto err1;
+  }
+  obj1.free();
+
+  t0A = 0;
+  t1A = 1;
+  if (dict->lookup("Domain", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 2) {
+    t0A = obj1.arrayGet(0, &obj2)->getNum();
+    obj2.free();
+    t1A = obj1.arrayGet(1, &obj2)->getNum();
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("Function", &obj1);
+  if (obj1.isArray()) {
+    nFuncsA = obj1.arrayGetLength();
+    if (nFuncsA > gfxColorMaxComps) {
+      error(-1, "Invalid Function array in shading dictionary");
+      goto err1;
+    }
+    for (i = 0; i < nFuncsA; ++i) {
+      obj1.arrayGet(i, &obj2);
+      if (!(funcsA[i] = Function::parse(&obj2))) {
+	obj1.free();
+	obj2.free();
+	goto err1;
+      }
+      obj2.free();
+    }
+  } else {
+    nFuncsA = 1;
+    if (!(funcsA[0] = Function::parse(&obj1))) {
+      obj1.free();
+      goto err1;
+    }
+  }
+  obj1.free();
+
+  extend0A = extend1A = gFalse;
+  if (dict->lookup("Extend", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 2) {
+    extend0A = obj1.arrayGet(0, &obj2)->getBool();
+    obj2.free();
+    extend1A = obj1.arrayGet(1, &obj2)->getBool();
+    obj2.free();
+  }
+  obj1.free();
+
+  shading = new GfxAxialShading(x0A, y0A, x1A, y1A, t0A, t1A,
+				funcsA, nFuncsA, extend0A, extend1A);
+  if (!shading->init(dict)) {
+    delete shading;
+    return NULL;
+  }
+  return shading;
+
+ err1:
+  return NULL;
+}
+
+GfxShading *GfxAxialShading::copy() {
+  return new GfxAxialShading(this);
+}
+
+void GfxAxialShading::getColor(double t, GfxColor *color) {
+  double out[gfxColorMaxComps];
+  int i;
+
+  // NB: there can be one function with n outputs or n functions with
+  // one output each (where n = number of color components)
+  for (i = 0; i < gfxColorMaxComps; ++i) {
+    out[i] = 0;
+  }
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i]->transform(&t, &out[i]);
+  }
+  for (i = 0; i < gfxColorMaxComps; ++i) {
+    color->c[i] = dblToCol(out[i]);
+  }
+}
+
+//------------------------------------------------------------------------
+// GfxRadialShading
+//------------------------------------------------------------------------
+
+GfxRadialShading::GfxRadialShading(double x0A, double y0A, double r0A,
+				   double x1A, double y1A, double r1A,
+				   double t0A, double t1A,
+				   Function **funcsA, int nFuncsA,
+				   GBool extend0A, GBool extend1A):
+  GfxShading(3)
+{
+  int i;
+
+  x0 = x0A;
+  y0 = y0A;
+  r0 = r0A;
+  x1 = x1A;
+  y1 = y1A;
+  r1 = r1A;
+  t0 = t0A;
+  t1 = t1A;
+  nFuncs = nFuncsA;
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i] = funcsA[i];
+  }
+  extend0 = extend0A;
+  extend1 = extend1A;
+}
+
+GfxRadialShading::GfxRadialShading(GfxRadialShading *shading):
+  GfxShading(shading)
+{
+  int i;
+
+  x0 = shading->x0;
+  y0 = shading->y0;
+  r0 = shading->r0;
+  x1 = shading->x1;
+  y1 = shading->y1;
+  r1 = shading->r1;
+  t0 = shading->t0;
+  y1 = shading->t1;
+  nFuncs = shading->nFuncs;
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i] = shading->funcs[i]->copy();
+  }
+  extend0 = shading->extend0;
+  extend1 = shading->extend1;
+}
+
+GfxRadialShading::~GfxRadialShading() {
+  int i;
+
+  for (i = 0; i < nFuncs; ++i) {
+    delete funcs[i];
+  }
+}
+
+GfxRadialShading *GfxRadialShading::parse(Dict *dict) {
+  GfxRadialShading *shading;
+  double x0A, y0A, r0A, x1A, y1A, r1A;
+  double t0A, t1A;
+  Function *funcsA[gfxColorMaxComps];
+  int nFuncsA;
+  GBool extend0A, extend1A;
+  Object obj1, obj2;
+  int i;
+
+  x0A = y0A = r0A = x1A = y1A = r1A = 0;
+  if (dict->lookup("Coords", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 6) {
+    x0A = obj1.arrayGet(0, &obj2)->getNum();
+    obj2.free();
+    y0A = obj1.arrayGet(1, &obj2)->getNum();
+    obj2.free();
+    r0A = obj1.arrayGet(2, &obj2)->getNum();
+    obj2.free();
+    x1A = obj1.arrayGet(3, &obj2)->getNum();
+    obj2.free();
+    y1A = obj1.arrayGet(4, &obj2)->getNum();
+    obj2.free();
+    r1A = obj1.arrayGet(5, &obj2)->getNum();
+    obj2.free();
+  } else {
+    error(-1, "Missing or invalid Coords in shading dictionary");
+    goto err1;
+  }
+  obj1.free();
+
+  t0A = 0;
+  t1A = 1;
+  if (dict->lookup("Domain", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 2) {
+    t0A = obj1.arrayGet(0, &obj2)->getNum();
+    obj2.free();
+    t1A = obj1.arrayGet(1, &obj2)->getNum();
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("Function", &obj1);
+  if (obj1.isArray()) {
+    nFuncsA = obj1.arrayGetLength();
+    if (nFuncsA > gfxColorMaxComps) {
+      error(-1, "Invalid Function array in shading dictionary");
+      goto err1;
+    }
+    for (i = 0; i < nFuncsA; ++i) {
+      obj1.arrayGet(i, &obj2);
+      if (!(funcsA[i] = Function::parse(&obj2))) {
+	obj1.free();
+	obj2.free();
+	goto err1;
+      }
+      obj2.free();
+    }
+  } else {
+    nFuncsA = 1;
+    if (!(funcsA[0] = Function::parse(&obj1))) {
+      obj1.free();
+      goto err1;
+    }
+  }
+  obj1.free();
+
+  extend0A = extend1A = gFalse;
+  if (dict->lookup("Extend", &obj1)->isArray() &&
+      obj1.arrayGetLength() == 2) {
+    extend0A = obj1.arrayGet(0, &obj2)->getBool();
+    obj2.free();
+    extend1A = obj1.arrayGet(1, &obj2)->getBool();
+    obj2.free();
+  }
+  obj1.free();
+
+  shading = new GfxRadialShading(x0A, y0A, r0A, x1A, y1A, r1A, t0A, t1A,
+				 funcsA, nFuncsA, extend0A, extend1A);
+  if (!shading->init(dict)) {
+    delete shading;
+    return NULL;
+  }
+  return shading;
+
+ err1:
+  return NULL;
+}
+
+GfxShading *GfxRadialShading::copy() {
+  return new GfxRadialShading(this);
+}
+
+void GfxRadialShading::getColor(double t, GfxColor *color) {
+  double out[gfxColorMaxComps];
+  int i;
+
+  // NB: there can be one function with n outputs or n functions with
+  // one output each (where n = number of color components)
+  for (i = 0; i < gfxColorMaxComps; ++i) {
+    out[i] = 0;
+  }
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i]->transform(&t, &out[i]);
+  }
+  for (i = 0; i < gfxColorMaxComps; ++i) {
+    color->c[i] = dblToCol(out[i]);
+  }
+}
+
+//------------------------------------------------------------------------
+// GfxShadingBitBuf
+//------------------------------------------------------------------------
+
+class GfxShadingBitBuf {
+public:
+
+  GfxShadingBitBuf(Stream *strA);
+  ~GfxShadingBitBuf();
+  GBool getBits(int n, Guint *val);
+  void flushBits();
+
+private:
+
+  Stream *str;
+  int bitBuf;
+  int nBits;
+};
+
+GfxShadingBitBuf::GfxShadingBitBuf(Stream *strA) {
+  str = strA;
+  str->reset();
+  bitBuf = 0;
+  nBits = 0;
+}
+
+GfxShadingBitBuf::~GfxShadingBitBuf() {
+  str->close();
+}
+
+GBool GfxShadingBitBuf::getBits(int n, Guint *val) {
+  int x;
+
+  if (nBits >= n) {
+    x = (bitBuf >> (nBits - n)) & ((1 << n) - 1);
+    nBits -= n;
+  } else {
+    x = 0;
+    if (nBits > 0) {
+      x = bitBuf & ((1 << nBits) - 1);
+      n -= nBits;
+      nBits = 0;
+    }
+    while (n > 0) {
+      if ((bitBuf = str->getChar()) == EOF) {
+	nBits = 0;
+	return gFalse;
+      }
+      if (n >= 8) {
+	x = (x << 8) | bitBuf;
+	n -= 8;
+      } else {
+	x = (x << n) | (bitBuf >> (8 - n));
+	nBits = 8 - n;
+	n = 0;
+      }
+    }
+  }
+  *val = x;
+  return gTrue;
+}
+
+void GfxShadingBitBuf::flushBits() {
+  bitBuf = 0;
+  nBits = 0;
+}
+
+//------------------------------------------------------------------------
+// GfxGouraudTriangleShading
+//------------------------------------------------------------------------
+
+GfxGouraudTriangleShading::GfxGouraudTriangleShading(
+			       int typeA,
+			       GfxGouraudVertex *verticesA, int nVerticesA,
+			       int (*trianglesA)[3], int nTrianglesA,
+			       Function **funcsA, int nFuncsA):
+  GfxShading(typeA)
+{
+  int i;
+
+  vertices = verticesA;
+  nVertices = nVerticesA;
+  triangles = trianglesA;
+  nTriangles = nTrianglesA;
+  nFuncs = nFuncsA;
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i] = funcsA[i];
+  }
+}
+
+GfxGouraudTriangleShading::GfxGouraudTriangleShading(
+			       GfxGouraudTriangleShading *shading):
+  GfxShading(shading)
+{
+  int i;
+
+  nVertices = shading->nVertices;
+  vertices = (GfxGouraudVertex *)gmallocn(nVertices, sizeof(GfxGouraudVertex));
+  memcpy(vertices, shading->vertices, nVertices * sizeof(GfxGouraudVertex));
+  nTriangles = shading->nTriangles;
+  triangles = (int (*)[3])gmallocn(nTriangles * 3, sizeof(int));
+  memcpy(triangles, shading->triangles, nTriangles * 3 * sizeof(int));
+  nFuncs = shading->nFuncs;
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i] = shading->funcs[i]->copy();
+  }
+}
+
+GfxGouraudTriangleShading::~GfxGouraudTriangleShading() {
+  int i;
+
+  gfree(vertices);
+  gfree(triangles);
+  for (i = 0; i < nFuncs; ++i) {
+    delete funcs[i];
+  }
+}
+
+GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(int typeA,
+							    Dict *dict,
+							    Stream *str) {
+  GfxGouraudTriangleShading *shading;
+  Function *funcsA[gfxColorMaxComps];
+  int nFuncsA;
+  int coordBits, compBits, flagBits, vertsPerRow, nRows;
+  double xMin, xMax, yMin, yMax;
+  double cMin[gfxColorMaxComps], cMax[gfxColorMaxComps];
+  double xMul, yMul;
+  double cMul[gfxColorMaxComps];
+  GfxGouraudVertex *verticesA;
+  int (*trianglesA)[3];
+  int nComps, nVerticesA, nTrianglesA, vertSize, triSize;
+  Guint x, y, flag;
+  Guint c[gfxColorMaxComps];
+  GfxShadingBitBuf *bitBuf;
+  Object obj1, obj2;
+  int i, j, k, state;
+
+  if (dict->lookup("BitsPerCoordinate", &obj1)->isInt()) {
+    coordBits = obj1.getInt();
+  } else {
+    error(-1, "Missing or invalid BitsPerCoordinate in shading dictionary");
+    goto err2;
+  }
+  obj1.free();
+  if (dict->lookup("BitsPerComponent", &obj1)->isInt()) {
+    compBits = obj1.getInt();
+  } else {
+    error(-1, "Missing or invalid BitsPerComponent in shading dictionary");
+    goto err2;
+  }
+  obj1.free();
+  flagBits = vertsPerRow = 0; // make gcc happy
+  if (typeA == 4) {
+    if (dict->lookup("BitsPerFlag", &obj1)->isInt()) {
+      flagBits = obj1.getInt();
+    } else {
+      error(-1, "Missing or invalid BitsPerFlag in shading dictionary");
+      goto err2;
+    }
+    obj1.free();
+  } else {
+    if (dict->lookup("VerticesPerRow", &obj1)->isInt()) {
+      vertsPerRow = obj1.getInt();
+    } else {
+      error(-1, "Missing or invalid VerticesPerRow in shading dictionary");
+      goto err2;
+    }
+    obj1.free();
+  }
+  if (dict->lookup("Decode", &obj1)->isArray() &&
+      obj1.arrayGetLength() >= 6) {
+    xMin = obj1.arrayGet(0, &obj2)->getNum();
+    obj2.free();
+    xMax = obj1.arrayGet(1, &obj2)->getNum();
+    obj2.free();
+    xMul = (xMax - xMin) / (pow(2.0, coordBits) - 1);
+    yMin = obj1.arrayGet(2, &obj2)->getNum();
+    obj2.free();
+    yMax = obj1.arrayGet(3, &obj2)->getNum();
+    obj2.free();
+    yMul = (yMax - yMin) / (pow(2.0, coordBits) - 1);
+    for (i = 0; 5 + 2*i < obj1.arrayGetLength() && i < gfxColorMaxComps; ++i) {
+      cMin[i] = obj1.arrayGet(4 + 2*i, &obj2)->getNum();
+      obj2.free();
+      cMax[i] = obj1.arrayGet(5 + 2*i, &obj2)->getNum();
+      obj2.free();
+      cMul[i] = (cMax[i] - cMin[i]) / (double)((1 << compBits) - 1);
+    }
+    nComps = i;
+  } else {
+    error(-1, "Missing or invalid Decode array in shading dictionary");
+    goto err2;
+  }
+  obj1.free();
+
+  if (!dict->lookup("Function", &obj1)->isNull()) {
+    if (obj1.isArray()) {
+      nFuncsA = obj1.arrayGetLength();
+      if (nFuncsA > gfxColorMaxComps) {
+	error(-1, "Invalid Function array in shading dictionary");
+	goto err1;
+      }
+      for (i = 0; i < nFuncsA; ++i) {
+	obj1.arrayGet(i, &obj2);
+	if (!(funcsA[i] = Function::parse(&obj2))) {
+	  obj1.free();
+	  obj2.free();
+	  goto err1;
+	}
+	obj2.free();
+      }
+    } else {
+      nFuncsA = 1;
+      if (!(funcsA[0] = Function::parse(&obj1))) {
+	obj1.free();
+	goto err1;
+      }
+    }
+  } else {
+    nFuncsA = 0;
+  }
+  obj1.free();
+
+  nVerticesA = nTrianglesA = 0;
+  verticesA = NULL;
+  trianglesA = NULL;
+  vertSize = triSize = 0;
+  state = 0;
+  flag = 0; // make gcc happy
+  bitBuf = new GfxShadingBitBuf(str);
+  while (1) {
+    if (typeA == 4) {
+      if (!bitBuf->getBits(flagBits, &flag)) {
+	break;
+      }
+    }
+    if (!bitBuf->getBits(coordBits, &x) ||
+	!bitBuf->getBits(coordBits, &y)) {
+      break;
+    }
+    for (i = 0; i < nComps; ++i) {
+      if (!bitBuf->getBits(compBits, &c[i])) {
+	break;
+      }
+    }
+    if (i < nComps) {
+      break;
+    }
+    if (nVerticesA == vertSize) {
+      vertSize = (vertSize == 0) ? 16 : 2 * vertSize;
+      verticesA = (GfxGouraudVertex *)
+	              greallocn(verticesA, vertSize, sizeof(GfxGouraudVertex));
+    }
+    verticesA[nVerticesA].x = xMin + xMul * (double)x;
+    verticesA[nVerticesA].y = yMin + yMul * (double)y;
+    for (i = 0; i < nComps; ++i) {
+      verticesA[nVerticesA].color.c[i] =
+	  dblToCol(cMin[i] + cMul[i] * (double)c[i]);
+    }
+    ++nVerticesA;
+    bitBuf->flushBits();
+    if (typeA == 4) {
+      if (state == 0 || state == 1) {
+	++state;
+      } else if (state == 2 || flag > 0) {
+	if (nTrianglesA == triSize) {
+	  triSize = (triSize == 0) ? 16 : 2 * triSize;
+	  trianglesA = (int (*)[3])
+	                   greallocn(trianglesA, triSize * 3, sizeof(int));
+	}
+	if (state == 2) {
+	  trianglesA[nTrianglesA][0] = nVerticesA - 3;
+	  trianglesA[nTrianglesA][1] = nVerticesA - 2;
+	  trianglesA[nTrianglesA][2] = nVerticesA - 1;
+	  ++state;
+	} else if (flag == 1) {
+	  trianglesA[nTrianglesA][0] = trianglesA[nTrianglesA - 1][1];
+	  trianglesA[nTrianglesA][1] = trianglesA[nTrianglesA - 1][2];
+	  trianglesA[nTrianglesA][2] = nVerticesA - 1;
+	} else { // flag == 2
+	  trianglesA[nTrianglesA][0] = trianglesA[nTrianglesA - 1][0];
+	  trianglesA[nTrianglesA][1] = trianglesA[nTrianglesA - 1][2];
+	  trianglesA[nTrianglesA][2] = nVerticesA - 1;
+	}
+	++nTrianglesA;
+      } else { // state == 3 && flag == 0
+	state = 1;
+      }
+    }
+  }
+  delete bitBuf;
+  if (typeA == 5) {
+    nRows = nVerticesA / vertsPerRow;
+    nTrianglesA = (nRows - 1) * 2 * (vertsPerRow - 1);
+    trianglesA = (int (*)[3])gmallocn(nTrianglesA * 3, sizeof(int));
+    k = 0;
+    for (i = 0; i < nRows - 1; ++i) {
+      for (j = 0; j < vertsPerRow - 1; ++j) {
+	trianglesA[k][0] = i * vertsPerRow + j;
+	trianglesA[k][1] = i * vertsPerRow + j+1;
+	trianglesA[k][2] = (i+1) * vertsPerRow + j;
+	++k;
+	trianglesA[k][0] = i * vertsPerRow + j+1;
+	trianglesA[k][1] = (i+1) * vertsPerRow + j;
+	trianglesA[k][2] = (i+1) * vertsPerRow + j+1;
+	++k;
+      }
+    }
+  }
+
+  shading = new GfxGouraudTriangleShading(typeA, verticesA, nVerticesA,
+					  trianglesA, nTrianglesA,
+					  funcsA, nFuncsA);
+  if (!shading->init(dict)) {
+    delete shading;
+    return NULL;
+  }
+  return shading;
+
+ err2:
+  obj1.free();
+ err1:
+  return NULL;
+}
+
+GfxShading *GfxGouraudTriangleShading::copy() {
+  return new GfxGouraudTriangleShading(this);
+}
+
+void GfxGouraudTriangleShading::getTriangle(
+				    int i,
+				    double *x0, double *y0, GfxColor *color0,
+				    double *x1, double *y1, GfxColor *color1,
+				    double *x2, double *y2, GfxColor *color2) {
+  double in;
+  double out[gfxColorMaxComps];
+  int v, j;
+
+  v = triangles[i][0];
+  *x0 = vertices[v].x;
+  *y0 = vertices[v].y;
+  if (nFuncs > 0) {
+    in = colToDbl(vertices[v].color.c[0]);
+    for (j = 0; j < nFuncs; ++j) {
+      funcs[j]->transform(&in, &out[j]);
+    }
+    for (j = 0; j < gfxColorMaxComps; ++j) {
+      color0->c[j] = dblToCol(out[j]);
+    }
+  } else {
+    *color0 = vertices[v].color;
+  }
+  v = triangles[i][1];
+  *x1 = vertices[v].x;
+  *y1 = vertices[v].y;
+  if (nFuncs > 0) {
+    in = colToDbl(vertices[v].color.c[0]);
+    for (j = 0; j < nFuncs; ++j) {
+      funcs[j]->transform(&in, &out[j]);
+    }
+    for (j = 0; j < gfxColorMaxComps; ++j) {
+      color1->c[j] = dblToCol(out[j]);
+    }
+  } else {
+    *color1 = vertices[v].color;
+  }
+  v = triangles[i][2];
+  *x2 = vertices[v].x;
+  *y2 = vertices[v].y;
+  if (nFuncs > 0) {
+    in = colToDbl(vertices[v].color.c[0]);
+    for (j = 0; j < nFuncs; ++j) {
+      funcs[j]->transform(&in, &out[j]);
+    }
+    for (j = 0; j < gfxColorMaxComps; ++j) {
+      color2->c[j] = dblToCol(out[j]);
+    }
+  } else {
+    *color2 = vertices[v].color;
+  }
+}
+
+//------------------------------------------------------------------------
+// GfxPatchMeshShading
+//------------------------------------------------------------------------
+
+GfxPatchMeshShading::GfxPatchMeshShading(int typeA,
+					 GfxPatch *patchesA, int nPatchesA,
+					 Function **funcsA, int nFuncsA):
+  GfxShading(typeA)
+{
+  int i;
+
+  patches = patchesA;
+  nPatches = nPatchesA;
+  nFuncs = nFuncsA;
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i] = funcsA[i];
+  }
+}
+
+GfxPatchMeshShading::GfxPatchMeshShading(GfxPatchMeshShading *shading):
+  GfxShading(shading)
+{
+  int i;
+
+  nPatches = shading->nPatches;
+  patches = (GfxPatch *)gmallocn(nPatches, sizeof(GfxPatch));
+  memcpy(patches, shading->patches, nPatches * sizeof(GfxPatch));
+  nFuncs = shading->nFuncs;
+  for (i = 0; i < nFuncs; ++i) {
+    funcs[i] = shading->funcs[i]->copy();
+  }
+}
+
+GfxPatchMeshShading::~GfxPatchMeshShading() {
+  int i;
+
+  gfree(patches);
+  for (i = 0; i < nFuncs; ++i) {
+    delete funcs[i];
+  }
+}
+
+GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict,
+						Stream *str) {
+  GfxPatchMeshShading *shading;
+  Function *funcsA[gfxColorMaxComps];
+  int nFuncsA;
+  int coordBits, compBits, flagBits;
+  double xMin, xMax, yMin, yMax;
+  double cMin[gfxColorMaxComps], cMax[gfxColorMaxComps];
+  double xMul, yMul;
+  double cMul[gfxColorMaxComps];
+  GfxPatch *patchesA, *p;
+  int nComps, nPatchesA, patchesSize, nPts, nColors;
+  Guint flag;
+  double x[16], y[16];
+  Guint xi, yi;
+  GfxColorComp c[4][gfxColorMaxComps];
+  Guint ci[4];
+  GfxShadingBitBuf *bitBuf;
+  Object obj1, obj2;
+  int i, j;
+
+  if (dict->lookup("BitsPerCoordinate", &obj1)->isInt()) {
+    coordBits = obj1.getInt();
+  } else {
+    error(-1, "Missing or invalid BitsPerCoordinate in shading dictionary");
+    goto err2;
+  }
+  obj1.free();
+  if (dict->lookup("BitsPerComponent", &obj1)->isInt()) {
+    compBits = obj1.getInt();
+  } else {
+    error(-1, "Missing or invalid BitsPerComponent in shading dictionary");
+    goto err2;
+  }
+  obj1.free();
+  if (dict->lookup("BitsPerFlag", &obj1)->isInt()) {
+    flagBits = obj1.getInt();
+  } else {
+    error(-1, "Missing or invalid BitsPerFlag in shading dictionary");
+    goto err2;
+  }
+  obj1.free();
+  if (dict->lookup("Decode", &obj1)->isArray() &&
+      obj1.arrayGetLength() >= 6) {
+    xMin = obj1.arrayGet(0, &obj2)->getNum();
+    obj2.free();
+    xMax = obj1.arrayGet(1, &obj2)->getNum();
+    obj2.free();
+    xMul = (xMax - xMin) / (pow(2.0, coordBits) - 1);
+    yMin = obj1.arrayGet(2, &obj2)->getNum();
+    obj2.free();
+    yMax = obj1.arrayGet(3, &obj2)->getNum();
+    obj2.free();
+    yMul = (yMax - yMin) / (pow(2.0, coordBits) - 1);
+    for (i = 0; 5 + 2*i < obj1.arrayGetLength() && i < gfxColorMaxComps; ++i) {
+      cMin[i] = obj1.arrayGet(4 + 2*i, &obj2)->getNum();
+      obj2.free();
+      cMax[i] = obj1.arrayGet(5 + 2*i, &obj2)->getNum();
+      obj2.free();
+      cMul[i] = (cMax[i] - cMin[i]) / (double)((1 << compBits) - 1);
+    }
+    nComps = i;
+  } else {
+    error(-1, "Missing or invalid Decode array in shading dictionary");
+    goto err2;
+  }
+  obj1.free();
+
+  if (!dict->lookup("Function", &obj1)->isNull()) {
+    if (obj1.isArray()) {
+      nFuncsA = obj1.arrayGetLength();
+      if (nFuncsA > gfxColorMaxComps) {
+	error(-1, "Invalid Function array in shading dictionary");
+	goto err1;
+      }
+      for (i = 0; i < nFuncsA; ++i) {
+	obj1.arrayGet(i, &obj2);
+	if (!(funcsA[i] = Function::parse(&obj2))) {
+	  obj1.free();
+	  obj2.free();
+	  goto err1;
+	}
+	obj2.free();
+      }
+    } else {
+      nFuncsA = 1;
+      if (!(funcsA[0] = Function::parse(&obj1))) {
+	obj1.free();
+	goto err1;
+      }
+    }
+  } else {
+    nFuncsA = 0;
+  }
+  obj1.free();
+
+  nPatchesA = 0;
+  patchesA = NULL;
+  patchesSize = 0;
+  bitBuf = new GfxShadingBitBuf(str);
+  while (1) {
+    if (!bitBuf->getBits(flagBits, &flag)) {
+      break;
+    }
+    if (typeA == 6) {
+      switch (flag) {
+      case 0: nPts = 12; nColors = 4; break;
+      case 1:
+      case 2:
+      case 3:
+      default: nPts =  8; nColors = 2; break;
+      }
+    } else {
+      switch (flag) {
+      case 0: nPts = 16; nColors = 4; break;
+      case 1:
+      case 2:
+      case 3:
+      default: nPts = 12; nColors = 2; break;
+      }
+    }
+    for (i = 0; i < nPts; ++i) {
+      if (!bitBuf->getBits(coordBits, &xi) ||
+	  !bitBuf->getBits(coordBits, &yi)) {
+	break;
+      }
+      x[i] = xMin + xMul * (double)xi;
+      y[i] = yMin + yMul * (double)yi;
+    }
+    if (i < nPts) {
+      break;
+    }
+    for (i = 0; i < nColors; ++i) {
+      for (j = 0; j < nComps; ++j) {
+	if (!bitBuf->getBits(compBits, &ci[j])) {
+	  break;
+	}
+	c[i][j] = dblToCol(cMin[j] + cMul[j] * (double)ci[j]);
+      }
+      if (j < nComps) {
+	break;
+      }
+    }
+    if (i < nColors) {
+      break;
+    }
+    if (nPatchesA == patchesSize) {
+      patchesSize = (patchesSize == 0) ? 16 : 2 * patchesSize;
+      patchesA = (GfxPatch *)greallocn(patchesA,
+				       patchesSize, sizeof(GfxPatch));
+    }
+    p = &patchesA[nPatchesA];
+    if (typeA == 6) {
+      switch (flag) {
+      case 0:
+	p->x[0][0] = x[0];
+	p->y[0][0] = y[0];
+	p->x[0][1] = x[1];
+	p->y[0][1] = y[1];
+	p->x[0][2] = x[2];
+	p->y[0][2] = y[2];
+	p->x[0][3] = x[3];
+	p->y[0][3] = y[3];
+	p->x[1][3] = x[4];
+	p->y[1][3] = y[4];
+	p->x[2][3] = x[5];
+	p->y[2][3] = y[5];
+	p->x[3][3] = x[6];
+	p->y[3][3] = y[6];
+	p->x[3][2] = x[7];
+	p->y[3][2] = y[7];
+	p->x[3][1] = x[8];
+	p->y[3][1] = y[8];
+	p->x[3][0] = x[9];
+	p->y[3][0] = y[9];
+	p->x[2][0] = x[10];
+	p->y[2][0] = y[10];
+	p->x[1][0] = x[11];
+	p->y[1][0] = y[11];
+	for (j = 0; j < nComps; ++j) {
+	  p->color[0][0].c[j] = c[0][j];
+	  p->color[0][1].c[j] = c[1][j];
+	  p->color[1][1].c[j] = c[2][j];
+	  p->color[1][0].c[j] = c[3][j];
+	}
+	break;
+      case 1:
+	p->x[0][0] = patchesA[nPatchesA-1].x[0][3];
+	p->y[0][0] = patchesA[nPatchesA-1].y[0][3];
+	p->x[0][1] = patchesA[nPatchesA-1].x[1][3];
+	p->y[0][1] = patchesA[nPatchesA-1].y[1][3];
+	p->x[0][2] = patchesA[nPatchesA-1].x[2][3];
+	p->y[0][2] = patchesA[nPatchesA-1].y[2][3];
+	p->x[0][3] = patchesA[nPatchesA-1].x[3][3];
+	p->y[0][3] = patchesA[nPatchesA-1].y[3][3];
+	p->x[1][3] = x[0];
+	p->y[1][3] = y[0];
+	p->x[2][3] = x[1];
+	p->y[2][3] = y[1];
+	p->x[3][3] = x[2];
+	p->y[3][3] = y[2];
+	p->x[3][2] = x[3];
+	p->y[3][2] = y[3];
+	p->x[3][1] = x[4];
+	p->y[3][1] = y[4];
+	p->x[3][0] = x[5];
+	p->y[3][0] = y[5];
+	p->x[2][0] = x[6];
+	p->y[2][0] = y[6];
+	p->x[1][0] = x[7];
+	p->y[1][0] = y[7];
+	for (j = 0; j < nComps; ++j) {
+	  p->color[0][0].c[j] = patchesA[nPatchesA-1].color[0][1].c[j];
+	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][1].c[j];
+	  p->color[1][1].c[j] = c[0][j];
+	  p->color[1][0].c[j] = c[1][j];
+	}
+	break;
+      case 2:
+	p->x[0][0] = patchesA[nPatchesA-1].x[3][3];
+	p->y[0][0] = patchesA[nPatchesA-1].y[3][3];
+	p->x[0][1] = patchesA[nPatchesA-1].x[3][2];
+	p->y[0][1] = patchesA[nPatchesA-1].y[3][2];
+	p->x[0][2] = patchesA[nPatchesA-1].x[3][1];
+	p->y[0][2] = patchesA[nPatchesA-1].y[3][1];
+	p->x[0][3] = patchesA[nPatchesA-1].x[3][0];
+	p->y[0][3] = patchesA[nPatchesA-1].y[3][0];
+	p->x[1][3] = x[0];
+	p->y[1][3] = y[0];
+	p->x[2][3] = x[1];
+	p->y[2][3] = y[1];
+	p->x[3][3] = x[2];
+	p->y[3][3] = y[2];
+	p->x[3][2] = x[3];
+	p->y[3][2] = y[3];
+	p->x[3][1] = x[4];
+	p->y[3][1] = y[4];
+	p->x[3][0] = x[5];
+	p->y[3][0] = y[5];
+	p->x[2][0] = x[6];
+	p->y[2][0] = y[6];
+	p->x[1][0] = x[7];
+	p->y[1][0] = y[7];
+	for (j = 0; j < nComps; ++j) {
+	  p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][1].c[j];
+	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][0].c[j];
+	  p->color[1][1].c[j] = c[0][j];
+	  p->color[1][0].c[j] = c[1][j];
+	}
+	break;
+      case 3:
+	p->x[0][0] = patchesA[nPatchesA-1].x[3][0];
+	p->y[0][0] = patchesA[nPatchesA-1].y[3][0];
+	p->x[0][1] = patchesA[nPatchesA-1].x[2][0];
+	p->y[0][1] = patchesA[nPatchesA-1].y[2][0];
+	p->x[0][2] = patchesA[nPatchesA-1].x[1][0];
+	p->y[0][2] = patchesA[nPatchesA-1].y[1][0];
+	p->x[0][3] = patchesA[nPatchesA-1].x[0][0];
+	p->y[0][3] = patchesA[nPatchesA-1].y[0][0];
+	p->x[1][3] = x[0];
+	p->y[1][3] = y[0];
+	p->x[2][3] = x[1];
+	p->y[2][3] = y[1];
+	p->x[3][3] = x[2];
+	p->y[3][3] = y[2];
+	p->x[3][2] = x[3];
+	p->y[3][2] = y[3];
+	p->x[3][1] = x[4];
+	p->y[3][1] = y[4];
+	p->x[3][0] = x[5];
+	p->y[3][0] = y[5];
+	p->x[2][0] = x[6];
+	p->y[2][0] = y[6];
+	p->x[1][0] = x[7];
+	p->y[1][0] = y[7];
+	for (j = 0; j < nComps; ++j) {
+	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][0].c[j];
+	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[0][0].c[j];
+	  p->color[1][1].c[j] = c[0][j];
+	  p->color[1][0].c[j] = c[1][j];
+	}
+	break;
+      }
+    } else {
+      switch (flag) {
+      case 0:
+	p->x[0][0] = x[0];
+	p->y[0][0] = y[0];
+	p->x[0][1] = x[1];
+	p->y[0][1] = y[1];
+	p->x[0][2] = x[2];
+	p->y[0][2] = y[2];
+	p->x[0][3] = x[3];
+	p->y[0][3] = y[3];
+	p->x[1][3] = x[4];
+	p->y[1][3] = y[4];
+	p->x[2][3] = x[5];
+	p->y[2][3] = y[5];
+	p->x[3][3] = x[6];
+	p->y[3][3] = y[6];
+	p->x[3][2] = x[7];
+	p->y[3][2] = y[7];
+	p->x[3][1] = x[8];
+	p->y[3][1] = y[8];
+	p->x[3][0] = x[9];
+	p->y[3][0] = y[9];
+	p->x[2][0] = x[10];
+	p->y[2][0] = y[10];
+	p->x[1][0] = x[11];
+	p->y[1][0] = y[11];
+	p->x[1][1] = x[12];
+	p->y[1][1] = y[12];
+	p->x[1][2] = x[13];
+	p->y[1][2] = y[13];
+	p->x[2][2] = x[14];
+	p->y[2][2] = y[14];
+	p->x[2][1] = x[15];
+	p->y[2][1] = y[15];
+	for (j = 0; j < nComps; ++j) {
+	  p->color[0][0].c[j] = c[0][j];
+	  p->color[0][1].c[j] = c[1][j];
+	  p->color[1][1].c[j] = c[2][j];
+	  p->color[1][0].c[j] = c[3][j];
+	}
+	break;
+      case 1:
+	p->x[0][0] = patchesA[nPatchesA-1].x[0][3];
+	p->y[0][0] = patchesA[nPatchesA-1].y[0][3];
+	p->x[0][1] = patchesA[nPatchesA-1].x[1][3];
+	p->y[0][1] = patchesA[nPatchesA-1].y[1][3];
+	p->x[0][2] = patchesA[nPatchesA-1].x[2][3];
+	p->y[0][2] = patchesA[nPatchesA-1].y[2][3];
+	p->x[0][3] = patchesA[nPatchesA-1].x[3][3];
+	p->y[0][3] = patchesA[nPatchesA-1].y[3][3];
+	p->x[1][3] = x[0];
+	p->y[1][3] = y[0];
+	p->x[2][3] = x[1];
+	p->y[2][3] = y[1];
+	p->x[3][3] = x[2];
+	p->y[3][3] = y[2];
+	p->x[3][2] = x[3];
+	p->y[3][2] = y[3];
+	p->x[3][1] = x[4];
+	p->y[3][1] = y[4];
+	p->x[3][0] = x[5];
+	p->y[3][0] = y[5];
+	p->x[2][0] = x[6];
+	p->y[2][0] = y[6];
+	p->x[1][0] = x[7];
+	p->y[1][0] = y[7];
+	p->x[1][1] = x[8];
+	p->y[1][1] = y[8];
+	p->x[1][2] = x[9];
+	p->y[1][2] = y[9];
+	p->x[2][2] = x[10];
+	p->y[2][2] = y[10];
+	p->x[2][1] = x[11];
+	p->y[2][1] = y[11];
+	for (j = 0; j < nComps; ++j) {
+	  p->color[0][0].c[j] = patchesA[nPatchesA-1].color[0][1].c[j];
+	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][1].c[j];
+	  p->color[1][1].c[j] = c[0][j];
+	  p->color[1][0].c[j] = c[1][j];
+	}
+	break;
+      case 2:
+	p->x[0][0] = patchesA[nPatchesA-1].x[3][3];
+	p->y[0][0] = patchesA[nPatchesA-1].y[3][3];
+	p->x[0][1] = patchesA[nPatchesA-1].x[3][2];
+	p->y[0][1] = patchesA[nPatchesA-1].y[3][2];
+	p->x[0][2] = patchesA[nPatchesA-1].x[3][1];
+	p->y[0][2] = patchesA[nPatchesA-1].y[3][1];
+	p->x[0][3] = patchesA[nPatchesA-1].x[3][0];
+	p->y[0][3] = patchesA[nPatchesA-1].y[3][0];
+	p->x[1][3] = x[0];
+	p->y[1][3] = y[0];
+	p->x[2][3] = x[1];
+	p->y[2][3] = y[1];
+	p->x[3][3] = x[2];
+	p->y[3][3] = y[2];
+	p->x[3][2] = x[3];
+	p->y[3][2] = y[3];
+	p->x[3][1] = x[4];
+	p->y[3][1] = y[4];
+	p->x[3][0] = x[5];
+	p->y[3][0] = y[5];
+	p->x[2][0] = x[6];
+	p->y[2][0] = y[6];
+	p->x[1][0] = x[7];
+	p->y[1][0] = y[7];
+	p->x[1][1] = x[8];
+	p->y[1][1] = y[8];
+	p->x[1][2] = x[9];
+	p->y[1][2] = y[9];
+	p->x[2][2] = x[10];
+	p->y[2][2] = y[10];
+	p->x[2][1] = x[11];
+	p->y[2][1] = y[11];
+	for (j = 0; j < nComps; ++j) {
+	  p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][1].c[j];
+	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][0].c[j];
+	  p->color[1][1].c[j] = c[0][j];
+	  p->color[1][0].c[j] = c[1][j];
+	}
+	break;
+      case 3:
+	p->x[0][0] = patchesA[nPatchesA-1].x[3][0];
+	p->y[0][0] = patchesA[nPatchesA-1].y[3][0];
+	p->x[0][1] = patchesA[nPatchesA-1].x[2][0];
+	p->y[0][1] = patchesA[nPatchesA-1].y[2][0];
+	p->x[0][2] = patchesA[nPatchesA-1].x[1][0];
+	p->y[0][2] = patchesA[nPatchesA-1].y[1][0];
+	p->x[0][3] = patchesA[nPatchesA-1].x[0][0];
+	p->y[0][3] = patchesA[nPatchesA-1].y[0][0];
+	p->x[1][3] = x[0];
+	p->y[1][3] = y[0];
+	p->x[2][3] = x[1];
+	p->y[2][3] = y[1];
+	p->x[3][3] = x[2];
+	p->y[3][3] = y[2];
+	p->x[3][2] = x[3];
+	p->y[3][2] = y[3];
+	p->x[3][1] = x[4];
+	p->y[3][1] = y[4];
+	p->x[3][0] = x[5];
+	p->y[3][0] = y[5];
+	p->x[2][0] = x[6];
+	p->y[2][0] = y[6];
+	p->x[1][0] = x[7];
+	p->y[1][0] = y[7];
+	p->x[1][1] = x[8];
+	p->y[1][1] = y[8];
+	p->x[1][2] = x[9];
+	p->y[1][2] = y[9];
+	p->x[2][2] = x[10];
+	p->y[2][2] = y[10];
+	p->x[2][1] = x[11];
+	p->y[2][1] = y[11];
+	for (j = 0; j < nComps; ++j) {
+	  p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][0].c[j];
+	  p->color[0][1].c[j] = patchesA[nPatchesA-1].color[0][0].c[j];
+	  p->color[1][1].c[j] = c[0][j];
+	  p->color[1][0].c[j] = c[1][j];
+	}
+	break;
+      }
+    }
+    ++nPatchesA;
+    bitBuf->flushBits();
+  }
+  delete bitBuf;
+
+  if (typeA == 6) {
+    for (i = 0; i < nPatchesA; ++i) {
+      p = &patchesA[i];
+      p->x[1][1] = (-4 * p->x[0][0]
+		    +6 * (p->x[0][1] + p->x[1][0])
+		    -2 * (p->x[0][3] + p->x[3][0])
+		    +3 * (p->x[3][1] + p->x[1][3])
+		    - p->x[3][3]) / 9;
+      p->y[1][1] = (-4 * p->y[0][0]
+		    +6 * (p->y[0][1] + p->y[1][0])
+		    -2 * (p->y[0][3] + p->y[3][0])
+		    +3 * (p->y[3][1] + p->y[1][3])
+		    - p->y[3][3]) / 9;
+      p->x[1][2] = (-4 * p->x[0][3]
+		    +6 * (p->x[0][2] + p->x[1][3])
+		    -2 * (p->x[0][0] + p->x[3][3])
+		    +3 * (p->x[3][2] + p->x[1][0])
+		    - p->x[3][0]) / 9;
+      p->y[1][2] = (-4 * p->y[0][3]
+		    +6 * (p->y[0][2] + p->y[1][3])
+		    -2 * (p->y[0][0] + p->y[3][3])
+		    +3 * (p->y[3][2] + p->y[1][0])
+		    - p->y[3][0]) / 9;
+      p->x[2][1] = (-4 * p->x[3][0]
+		    +6 * (p->x[3][1] + p->x[2][0])
+		    -2 * (p->x[3][3] + p->x[0][0])
+		    +3 * (p->x[0][1] + p->x[2][3])
+		    - p->x[0][3]) / 9;
+      p->y[2][1] = (-4 * p->y[3][0]
+		    +6 * (p->y[3][1] + p->y[2][0])
+		    -2 * (p->y[3][3] + p->y[0][0])
+		    +3 * (p->y[0][1] + p->y[2][3])
+		    - p->y[0][3]) / 9;
+      p->x[2][2] = (-4 * p->x[3][3]
+		    +6 * (p->x[3][2] + p->x[2][3])
+		    -2 * (p->x[3][0] + p->x[0][3])
+		    +3 * (p->x[0][2] + p->x[2][0])
+		    - p->x[0][0]) / 9;
+      p->y[2][2] = (-4 * p->y[3][3]
+		    +6 * (p->y[3][2] + p->y[2][3])
+		    -2 * (p->y[3][0] + p->y[0][3])
+		    +3 * (p->y[0][2] + p->y[2][0])
+		    - p->y[0][0]) / 9;
+    }
+  }
+
+  shading = new GfxPatchMeshShading(typeA, patchesA, nPatchesA,
+				    funcsA, nFuncsA);
+  if (!shading->init(dict)) {
+    delete shading;
+    return NULL;
+  }
+  return shading;
+
+ err2:
+  obj1.free();
+ err1:
+  return NULL;
+}
+
+GfxShading *GfxPatchMeshShading::copy() {
+  return new GfxPatchMeshShading(this);
+}
+
+//------------------------------------------------------------------------
+// GfxImageColorMap
+//------------------------------------------------------------------------
+
+GfxImageColorMap::GfxImageColorMap(int bitsA, Object *decode,
+				   GfxColorSpace *colorSpaceA) {
+  GfxIndexedColorSpace *indexedCS;
+  GfxSeparationColorSpace *sepCS;
+  int maxPixel, indexHigh;
+  Guchar *lookup2;
+  Function *sepFunc;
+  Object obj;
+  double x[gfxColorMaxComps];
+  double y[gfxColorMaxComps];
+  int i, j, k;
+
+  ok = gTrue;
+
+  // bits per component and color space
+  bits = bitsA;
+  maxPixel = (1 << bits) - 1;
+  colorSpace = colorSpaceA;
+
+  // initialize
+  for (k = 0; k < gfxColorMaxComps; ++k) {
+    lookup[k] = NULL;
+  }
+
+  // get decode map
+  if (decode->isNull()) {
+    nComps = colorSpace->getNComps();
+    colorSpace->getDefaultRanges(decodeLow, decodeRange, maxPixel);
+  } else if (decode->isArray()) {
+    nComps = decode->arrayGetLength() / 2;
+    if (nComps != colorSpace->getNComps()) {
+      goto err1;
+    }
+    for (i = 0; i < nComps; ++i) {
+      decode->arrayGet(2*i, &obj);
+      if (!obj.isNum()) {
+	goto err2;
+      }
+      decodeLow[i] = obj.getNum();
+      obj.free();
+      decode->arrayGet(2*i+1, &obj);
+      if (!obj.isNum()) {
+	goto err2;
+      }
+      decodeRange[i] = obj.getNum() - decodeLow[i];
+      obj.free();
+    }
+  } else {
+    goto err1;
+  }
+
+  // Construct a lookup table -- this stores pre-computed decoded
+  // values for each component, i.e., the result of applying the
+  // decode mapping to each possible image pixel component value.
+  //
+  // Optimization: for Indexed and Separation color spaces (which have
+  // only one component), we store color values in the lookup table
+  // rather than component values.
+  colorSpace2 = NULL;
+  nComps2 = 0;
+  if (colorSpace->getMode() == csIndexed) {
+    // Note that indexHigh may not be the same as maxPixel --
+    // Distiller will remove unused palette entries, resulting in
+    // indexHigh < maxPixel.
+    indexedCS = (GfxIndexedColorSpace *)colorSpace;
+    colorSpace2 = indexedCS->getBase();
+    indexHigh = indexedCS->getIndexHigh();
+    nComps2 = colorSpace2->getNComps();
+    lookup2 = indexedCS->getLookup();
+    colorSpace2->getDefaultRanges(x, y, indexHigh);
+    for (k = 0; k < nComps2; ++k) {
+      lookup[k] = (GfxColorComp *)gmallocn(maxPixel + 1,
+					   sizeof(GfxColorComp));
+      for (i = 0; i <= maxPixel; ++i) {
+	j = (int)(decodeLow[0] + (i * decodeRange[0]) / maxPixel + 0.5);
+	if (j < 0) {
+	  j = 0;
+	} else if (j > indexHigh) {
+	  j = indexHigh;
+	}
+	lookup[k][i] =
+	    dblToCol(x[k] + (lookup2[j*nComps2 + k] / 255.0) * y[k]);
+      }
+    }
+  } else if (colorSpace->getMode() == csSeparation) {
+    sepCS = (GfxSeparationColorSpace *)colorSpace;
+    colorSpace2 = sepCS->getAlt();
+    nComps2 = colorSpace2->getNComps();
+    sepFunc = sepCS->getFunc();
+    for (k = 0; k < nComps2; ++k) {
+      lookup[k] = (GfxColorComp *)gmallocn(maxPixel + 1,
+					   sizeof(GfxColorComp));
+      for (i = 0; i <= maxPixel; ++i) {
+	x[0] = decodeLow[0] + (i * decodeRange[0]) / maxPixel;
+	sepFunc->transform(x, y);
+	lookup[k][i] = dblToCol(y[k]);
+      }
+    }
+  } else {
+    for (k = 0; k < nComps; ++k) {
+      lookup[k] = (GfxColorComp *)gmallocn(maxPixel + 1,
+					   sizeof(GfxColorComp));
+      for (i = 0; i <= maxPixel; ++i) {
+	lookup[k][i] = dblToCol(decodeLow[k] +
+				(i * decodeRange[k]) / maxPixel);
+      }
+    }
+  }
+
+  return;
+
+ err2:
+  obj.free();
+ err1:
+  ok = gFalse;
+}
+
+GfxImageColorMap::GfxImageColorMap(GfxImageColorMap *colorMap) {
+  int n, i, k;
+
+  colorSpace = colorMap->colorSpace->copy();
+  bits = colorMap->bits;
+  nComps = colorMap->nComps;
+  nComps2 = colorMap->nComps2;
+  colorSpace2 = NULL;
+  for (k = 0; k < gfxColorMaxComps; ++k) {
+    lookup[k] = NULL;
+  }
+  n = 1 << bits;
+  if (colorSpace->getMode() == csIndexed) {
+    colorSpace2 = ((GfxIndexedColorSpace *)colorSpace)->getBase();
+    for (k = 0; k < nComps2; ++k) {
+      lookup[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp));
+      memcpy(lookup[k], colorMap->lookup[k], n * sizeof(GfxColorComp));
+    }
+  } else if (colorSpace->getMode() == csSeparation) {
+    colorSpace2 = ((GfxSeparationColorSpace *)colorSpace)->getAlt();
+    for (k = 0; k < nComps2; ++k) {
+      lookup[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp));
+      memcpy(lookup[k], colorMap->lookup[k], n * sizeof(GfxColorComp));
+    }
+  } else {
+    for (k = 0; k < nComps; ++k) {
+      lookup[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp));
+      memcpy(lookup[k], colorMap->lookup[k], n * sizeof(GfxColorComp));
+    }
+  }
+  for (i = 0; i < nComps; ++i) {
+    decodeLow[i] = colorMap->decodeLow[i];
+    decodeRange[i] = colorMap->decodeRange[i];
+  }
+  ok = gTrue;
+}
+
+GfxImageColorMap::~GfxImageColorMap() {
+  int i;
+
+  delete colorSpace;
+  for (i = 0; i < gfxColorMaxComps; ++i) {
+    gfree(lookup[i]);
+  }
+}
+
+void GfxImageColorMap::getGray(Guchar *x, GfxGray *gray) {
+  GfxColor color;
+  int i;
+
+  if (colorSpace2) {
+    for (i = 0; i < nComps2; ++i) {
+      color.c[i] = lookup[i][x[0]];
+    }
+    colorSpace2->getGray(&color, gray);
+  } else {
+    for (i = 0; i < nComps; ++i) {
+      color.c[i] = lookup[i][x[i]];
+    }
+    colorSpace->getGray(&color, gray);
+  }
+}
+
+void GfxImageColorMap::getRGB(Guchar *x, GfxRGB *rgb) {
+  GfxColor color;
+  int i;
+
+  if (colorSpace2) {
+    for (i = 0; i < nComps2; ++i) {
+      color.c[i] = lookup[i][x[0]];
+    }
+    colorSpace2->getRGB(&color, rgb);
+  } else {
+    for (i = 0; i < nComps; ++i) {
+      color.c[i] = lookup[i][x[i]];
+    }
+    colorSpace->getRGB(&color, rgb);
+  }
+}
+
+void GfxImageColorMap::getCMYK(Guchar *x, GfxCMYK *cmyk) {
+  GfxColor color;
+  int i;
+
+  if (colorSpace2) {
+    for (i = 0; i < nComps2; ++i) {
+      color.c[i] = lookup[i][x[0]];
+    }
+    colorSpace2->getCMYK(&color, cmyk);
+  } else {
+    for (i = 0; i < nComps; ++i) {
+      color.c[i] = lookup[i][x[i]];
+    }
+    colorSpace->getCMYK(&color, cmyk);
+  }
+}
+
+void GfxImageColorMap::getColor(Guchar *x, GfxColor *color) {
+  int maxPixel, i;
+
+  maxPixel = (1 << bits) - 1;
+  for (i = 0; i < nComps; ++i) {
+    color->c[i] = dblToCol(decodeLow[i] + (x[i] * decodeRange[i]) / maxPixel);
+  }
+}
+
+//------------------------------------------------------------------------
+// GfxSubpath and GfxPath
+//------------------------------------------------------------------------
+
+GfxSubpath::GfxSubpath(double x1, double y1) {
+  size = 16;
+  x = (double *)gmallocn(size, sizeof(double));
+  y = (double *)gmallocn(size, sizeof(double));
+  curve = (GBool *)gmallocn(size, sizeof(GBool));
+  n = 1;
+  x[0] = x1;
+  y[0] = y1;
+  curve[0] = gFalse;
+  closed = gFalse;
+}
+
+GfxSubpath::~GfxSubpath() {
+  gfree(x);
+  gfree(y);
+  gfree(curve);
+}
+
+// Used for copy().
+GfxSubpath::GfxSubpath(GfxSubpath *subpath) {
+  size = subpath->size;
+  n = subpath->n;
+  x = (double *)gmallocn(size, sizeof(double));
+  y = (double *)gmallocn(size, sizeof(double));
+  curve = (GBool *)gmallocn(size, sizeof(GBool));
+  memcpy(x, subpath->x, n * sizeof(double));
+  memcpy(y, subpath->y, n * sizeof(double));
+  memcpy(curve, subpath->curve, n * sizeof(GBool));
+  closed = subpath->closed;
+}
+
+void GfxSubpath::lineTo(double x1, double y1) {
+  if (n >= size) {
+    size += 16;
+    x = (double *)greallocn(x, size, sizeof(double));
+    y = (double *)greallocn(y, size, sizeof(double));
+    curve = (GBool *)greallocn(curve, size, sizeof(GBool));
+  }
+  x[n] = x1;
+  y[n] = y1;
+  curve[n] = gFalse;
+  ++n;
+}
+
+void GfxSubpath::curveTo(double x1, double y1, double x2, double y2,
+			 double x3, double y3) {
+  if (n+3 > size) {
+    size += 16;
+    x = (double *)greallocn(x, size, sizeof(double));
+    y = (double *)greallocn(y, size, sizeof(double));
+    curve = (GBool *)greallocn(curve, size, sizeof(GBool));
+  }
+  x[n] = x1;
+  y[n] = y1;
+  x[n+1] = x2;
+  y[n+1] = y2;
+  x[n+2] = x3;
+  y[n+2] = y3;
+  curve[n] = curve[n+1] = gTrue;
+  curve[n+2] = gFalse;
+  n += 3;
+}
+
+void GfxSubpath::close() {
+  if (x[n-1] != x[0] || y[n-1] != y[0]) {
+    lineTo(x[0], y[0]);
+  }
+  closed = gTrue;
+}
+
+void GfxSubpath::offset(double dx, double dy) {
+  int i;
+
+  for (i = 0; i < n; ++i) {
+    x[i] += dx;
+    y[i] += dy;
+  }
+}
+
+GfxPath::GfxPath() {
+  justMoved = gFalse;
+  size = 16;
+  n = 0;
+  firstX = firstY = 0;
+  subpaths = (GfxSubpath **)gmallocn(size, sizeof(GfxSubpath *));
+}
+
+GfxPath::~GfxPath() {
+  int i;
+
+  for (i = 0; i < n; ++i)
+    delete subpaths[i];
+  gfree(subpaths);
+}
+
+// Used for copy().
+GfxPath::GfxPath(GBool justMoved1, double firstX1, double firstY1,
+		 GfxSubpath **subpaths1, int n1, int size1) {
+  int i;
+
+  justMoved = justMoved1;
+  firstX = firstX1;
+  firstY = firstY1;
+  size = size1;
+  n = n1;
+  subpaths = (GfxSubpath **)gmallocn(size, sizeof(GfxSubpath *));
+  for (i = 0; i < n; ++i)
+    subpaths[i] = subpaths1[i]->copy();
+}
+
+void GfxPath::moveTo(double x, double y) {
+  justMoved = gTrue;
+  firstX = x;
+  firstY = y;
+}
+
+void GfxPath::lineTo(double x, double y) {
+  if (justMoved) {
+    if (n >= size) {
+      size += 16;
+      subpaths = (GfxSubpath **)
+	           greallocn(subpaths, size, sizeof(GfxSubpath *));
+    }
+    subpaths[n] = new GfxSubpath(firstX, firstY);
+    ++n;
+    justMoved = gFalse;
+  }
+  subpaths[n-1]->lineTo(x, y);
+}
+
+void GfxPath::curveTo(double x1, double y1, double x2, double y2,
+	     double x3, double y3) {
+  if (justMoved) {
+    if (n >= size) {
+      size += 16;
+      subpaths = (GfxSubpath **)
+	           greallocn(subpaths, size, sizeof(GfxSubpath *));
+    }
+    subpaths[n] = new GfxSubpath(firstX, firstY);
+    ++n;
+    justMoved = gFalse;
+  }
+  subpaths[n-1]->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+void GfxPath::close() {
+  // this is necessary to handle the pathological case of
+  // moveto/closepath/clip, which defines an empty clipping region
+  if (justMoved) {
+    if (n >= size) {
+      size += 16;
+      subpaths = (GfxSubpath **)
+	           greallocn(subpaths, size, sizeof(GfxSubpath *));
+    }
+    subpaths[n] = new GfxSubpath(firstX, firstY);
+    ++n;
+    justMoved = gFalse;
+  }
+  subpaths[n-1]->close();
+}
+
+void GfxPath::append(GfxPath *path) {
+  int i;
+
+  if (n + path->n > size) {
+    size = n + path->n;
+    subpaths = (GfxSubpath **)
+                 greallocn(subpaths, size, sizeof(GfxSubpath *));
+  }
+  for (i = 0; i < path->n; ++i) {
+    subpaths[n++] = path->subpaths[i]->copy();
+  }
+  justMoved = gFalse;
+}
+
+void GfxPath::offset(double dx, double dy) {
+  int i;
+
+  for (i = 0; i < n; ++i) {
+    subpaths[i]->offset(dx, dy);
+  }
+}
+
+//------------------------------------------------------------------------
+// GfxState
+//------------------------------------------------------------------------
+
+GfxState::GfxState(double hDPIA, double vDPIA, PDFRectangle *pageBox,
+		   int rotateA, GBool upsideDown) {
+  double kx, ky;
+
+  hDPI = hDPIA;
+  vDPI = vDPIA;
+  rotate = rotateA;
+  px1 = pageBox->x1;
+  py1 = pageBox->y1;
+  px2 = pageBox->x2;
+  py2 = pageBox->y2;
+  kx = hDPI / 72.0;
+  ky = vDPI / 72.0;
+  if (rotate == 90) {
+    ctm[0] = 0;
+    ctm[1] = upsideDown ? ky : -ky;
+    ctm[2] = kx;
+    ctm[3] = 0;
+    ctm[4] = -kx * py1;
+    ctm[5] = ky * (upsideDown ? -px1 : px2);
+    pageWidth = kx * (py2 - py1);
+    pageHeight = ky * (px2 - px1);
+  } else if (rotate == 180) {
+    ctm[0] = -kx;
+    ctm[1] = 0;
+    ctm[2] = 0;
+    ctm[3] = upsideDown ? ky : -ky;
+    ctm[4] = kx * px2;
+    ctm[5] = ky * (upsideDown ? -py1 : py2);
+    pageWidth = kx * (px2 - px1);
+    pageHeight = ky * (py2 - py1);
+  } else if (rotate == 270) {
+    ctm[0] = 0;
+    ctm[1] = upsideDown ? -ky : ky;
+    ctm[2] = -kx;
+    ctm[3] = 0;
+    ctm[4] = kx * py2;
+    ctm[5] = ky * (upsideDown ? px2 : -px1);
+    pageWidth = kx * (py2 - py1);
+    pageHeight = ky * (px2 - px1);
+  } else {
+    ctm[0] = kx;
+    ctm[1] = 0;
+    ctm[2] = 0;
+    ctm[3] = upsideDown ? -ky : ky;
+    ctm[4] = -kx * px1;
+    ctm[5] = ky * (upsideDown ? py2 : -py1);
+    pageWidth = kx * (px2 - px1);
+    pageHeight = ky * (py2 - py1);
+  }
+
+  fillColorSpace = new GfxDeviceGrayColorSpace();
+  strokeColorSpace = new GfxDeviceGrayColorSpace();
+  fillColor.c[0] = 0;
+  strokeColor.c[0] = 0;
+  fillPattern = NULL;
+  strokePattern = NULL;
+  blendMode = gfxBlendNormal;
+  fillOpacity = 1;
+  strokeOpacity = 1;
+  fillOverprint = gFalse;
+  strokeOverprint = gFalse;
+  transfer[0] = transfer[1] = transfer[2] = transfer[3] = NULL;
+
+  lineWidth = 1;
+  lineDash = NULL;
+  lineDashLength = 0;
+  lineDashStart = 0;
+  flatness = 1;
+  lineJoin = 0;
+  lineCap = 0;
+  miterLimit = 10;
+  strokeAdjust = gFalse;
+
+  font = NULL;
+  fontSize = 0;
+  textMat[0] = 1; textMat[1] = 0;
+  textMat[2] = 0; textMat[3] = 1;
+  textMat[4] = 0; textMat[5] = 0;
+  charSpace = 0;
+  wordSpace = 0;
+  horizScaling = 1;
+  leading = 0;
+  rise = 0;
+  render = 0;
+
+  path = new GfxPath();
+  curX = curY = 0;
+  lineX = lineY = 0;
+
+  clipXMin = 0;
+  clipYMin = 0;
+  clipXMax = pageWidth;
+  clipYMax = pageHeight;
+
+  saved = NULL;
+}
+
+GfxState::~GfxState() {
+  int i;
+
+  if (fillColorSpace) {
+    delete fillColorSpace;
+  }
+  if (strokeColorSpace) {
+    delete strokeColorSpace;
+  }
+  if (fillPattern) {
+    delete fillPattern;
+  }
+  if (strokePattern) {
+    delete strokePattern;
+  }
+  for (i = 0; i < 4; ++i) {
+    if (transfer[i]) {
+      delete transfer[i];
+    }
+  }
+  gfree(lineDash);
+  if (path) {
+    // this gets set to NULL by restore()
+    delete path;
+  }
+  if (saved) {
+    delete saved;
+  }
+}
+
+// Used for copy();
+GfxState::GfxState(GfxState *state) {
+  int i;
+
+  memcpy(this, state, sizeof(GfxState));
+  if (fillColorSpace) {
+    fillColorSpace = state->fillColorSpace->copy();
+  }
+  if (strokeColorSpace) {
+    strokeColorSpace = state->strokeColorSpace->copy();
+  }
+  if (fillPattern) {
+    fillPattern = state->fillPattern->copy();
+  }
+  if (strokePattern) {
+    strokePattern = state->strokePattern->copy();
+  }
+  for (i = 0; i < 4; ++i) {
+    if (transfer[i]) {
+      transfer[i] = state->transfer[i]->copy();
+    }
+  }
+  if (lineDashLength > 0) {
+    lineDash = (double *)gmallocn(lineDashLength, sizeof(double));
+    memcpy(lineDash, state->lineDash, lineDashLength * sizeof(double));
+  }
+  saved = NULL;
+}
+
+void GfxState::setPath(GfxPath *pathA) {
+  delete path;
+  path = pathA;
+}
+
+void GfxState::getUserClipBBox(double *xMin, double *yMin,
+			       double *xMax, double *yMax) {
+  double ictm[6];
+  double xMin1, yMin1, xMax1, yMax1, det, tx, ty;
+
+  // invert the CTM
+  det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
+  ictm[0] = ctm[3] * det;
+  ictm[1] = -ctm[1] * det;
+  ictm[2] = -ctm[2] * det;
+  ictm[3] = ctm[0] * det;
+  ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
+  ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
+
+  // transform all four corners of the clip bbox; find the min and max
+  // x and y values
+  xMin1 = xMax1 = clipXMin * ictm[0] + clipYMin * ictm[2] + ictm[4];
+  yMin1 = yMax1 = clipXMin * ictm[1] + clipYMin * ictm[3] + ictm[5];
+  tx = clipXMin * ictm[0] + clipYMax * ictm[2] + ictm[4];
+  ty = clipXMin * ictm[1] + clipYMax * ictm[3] + ictm[5];
+  if (tx < xMin1) {
+    xMin1 = tx;
+  } else if (tx > xMax1) {
+    xMax1 = tx;
+  }
+  if (ty < yMin1) {
+    yMin1 = ty;
+  } else if (ty > yMax1) {
+    yMax1 = ty;
+  }
+  tx = clipXMax * ictm[0] + clipYMin * ictm[2] + ictm[4];
+  ty = clipXMax * ictm[1] + clipYMin * ictm[3] + ictm[5];
+  if (tx < xMin1) {
+    xMin1 = tx;
+  } else if (tx > xMax1) {
+    xMax1 = tx;
+  }
+  if (ty < yMin1) {
+    yMin1 = ty;
+  } else if (ty > yMax1) {
+    yMax1 = ty;
+  }
+  tx = clipXMax * ictm[0] + clipYMax * ictm[2] + ictm[4];
+  ty = clipXMax * ictm[1] + clipYMax * ictm[3] + ictm[5];
+  if (tx < xMin1) {
+    xMin1 = tx;
+  } else if (tx > xMax1) {
+    xMax1 = tx;
+  }
+  if (ty < yMin1) {
+    yMin1 = ty;
+  } else if (ty > yMax1) {
+    yMax1 = ty;
+  }
+
+  *xMin = xMin1;
+  *yMin = yMin1;
+  *xMax = xMax1;
+  *yMax = yMax1;
+}
+
+double GfxState::transformWidth(double w) {
+  double x, y;
+
+  x = ctm[0] + ctm[2];
+  y = ctm[1] + ctm[3];
+  return w * sqrt(0.5 * (x * x + y * y));
+}
+
+double GfxState::getTransformedFontSize() {
+  double x1, y1, x2, y2;
+
+  x1 = textMat[2] * fontSize;
+  y1 = textMat[3] * fontSize;
+  x2 = ctm[0] * x1 + ctm[2] * y1;
+  y2 = ctm[1] * x1 + ctm[3] * y1;
+  return sqrt(x2 * x2 + y2 * y2);
+}
+
+void GfxState::getFontTransMat(double *m11, double *m12,
+			       double *m21, double *m22) {
+  *m11 = (textMat[0] * ctm[0] + textMat[1] * ctm[2]) * fontSize;
+  *m12 = (textMat[0] * ctm[1] + textMat[1] * ctm[3]) * fontSize;
+  *m21 = (textMat[2] * ctm[0] + textMat[3] * ctm[2]) * fontSize;
+  *m22 = (textMat[2] * ctm[1] + textMat[3] * ctm[3]) * fontSize;
+}
+
+void GfxState::setCTM(double a, double b, double c,
+		      double d, double e, double f) {
+  int i;
+
+  ctm[0] = a;
+  ctm[1] = b;
+  ctm[2] = c;
+  ctm[3] = d;
+  ctm[4] = e;
+  ctm[5] = f;
+
+  // avoid FP exceptions on badly messed up PDF files
+  for (i = 0; i < 6; ++i) {
+    if (ctm[i] > 1e10) {
+      ctm[i] = 1e10;
+    } else if (ctm[i] < -1e10) {
+      ctm[i] = -1e10;
+    }
+  }
+}
+
+void GfxState::concatCTM(double a, double b, double c,
+			 double d, double e, double f) {
+  double a1 = ctm[0];
+  double b1 = ctm[1];
+  double c1 = ctm[2];
+  double d1 = ctm[3];
+  int i;
+
+  ctm[0] = a * a1 + b * c1;
+  ctm[1] = a * b1 + b * d1;
+  ctm[2] = c * a1 + d * c1;
+  ctm[3] = c * b1 + d * d1;
+  ctm[4] = e * a1 + f * c1 + ctm[4];
+  ctm[5] = e * b1 + f * d1 + ctm[5];
+
+  // avoid FP exceptions on badly messed up PDF files
+  for (i = 0; i < 6; ++i) {
+    if (ctm[i] > 1e10) {
+      ctm[i] = 1e10;
+    } else if (ctm[i] < -1e10) {
+      ctm[i] = -1e10;
+    }
+  }
+}
+
+void GfxState::shiftCTM(double tx, double ty) {
+  ctm[4] += tx;
+  ctm[5] += ty;
+  clipXMin += tx;
+  clipYMin += ty;
+  clipXMax += tx;
+  clipYMax += ty;
+}
+
+void GfxState::setFillColorSpace(GfxColorSpace *colorSpace) {
+  if (fillColorSpace) {
+    delete fillColorSpace;
+  }
+  fillColorSpace = colorSpace;
+}
+
+void GfxState::setStrokeColorSpace(GfxColorSpace *colorSpace) {
+  if (strokeColorSpace) {
+    delete strokeColorSpace;
+  }
+  strokeColorSpace = colorSpace;
+}
+
+void GfxState::setFillPattern(GfxPattern *pattern) {
+  if (fillPattern) {
+    delete fillPattern;
+  }
+  fillPattern = pattern;
+}
+
+void GfxState::setStrokePattern(GfxPattern *pattern) {
+  if (strokePattern) {
+    delete strokePattern;
+  }
+  strokePattern = pattern;
+}
+
+void GfxState::setTransfer(Function **funcs) {
+  int i;
+
+  for (i = 0; i < 4; ++i) {
+    if (transfer[i]) {
+      delete transfer[i];
+    }
+    transfer[i] = funcs[i];
+  }
+}
+
+void GfxState::setLineDash(double *dash, int length, double start) {
+  if (lineDash)
+    gfree(lineDash);
+  lineDash = dash;
+  lineDashLength = length;
+  lineDashStart = start;
+}
+
+void GfxState::clearPath() {
+  delete path;
+  path = new GfxPath();
+}
+
+void GfxState::clip() {
+  double xMin, yMin, xMax, yMax, x, y;
+  GfxSubpath *subpath;
+  int i, j;
+
+  xMin = xMax = yMin = yMax = 0; // make gcc happy
+  for (i = 0; i < path->getNumSubpaths(); ++i) {
+    subpath = path->getSubpath(i);
+    for (j = 0; j < subpath->getNumPoints(); ++j) {
+      transform(subpath->getX(j), subpath->getY(j), &x, &y);
+      if (i == 0 && j == 0) {
+	xMin = xMax = x;
+	yMin = yMax = y;
+      } else {
+	if (x < xMin) {
+	  xMin = x;
+	} else if (x > xMax) {
+	  xMax = x;
+	}
+	if (y < yMin) {
+	  yMin = y;
+	} else if (y > yMax) {
+	  yMax = y;
+	}
+      }
+    }
+  }
+  if (xMin > clipXMin) {
+    clipXMin = xMin;
+  }
+  if (yMin > clipYMin) {
+    clipYMin = yMin;
+  }
+  if (xMax < clipXMax) {
+    clipXMax = xMax;
+  }
+  if (yMax < clipYMax) {
+    clipYMax = yMax;
+  }
+}
+
+void GfxState::clipToStrokePath() {
+  double xMin, yMin, xMax, yMax, x, y, t0, t1;
+  GfxSubpath *subpath;
+  int i, j;
+
+  xMin = xMax = yMin = yMax = 0; // make gcc happy
+  for (i = 0; i < path->getNumSubpaths(); ++i) {
+    subpath = path->getSubpath(i);
+    for (j = 0; j < subpath->getNumPoints(); ++j) {
+      transform(subpath->getX(j), subpath->getY(j), &x, &y);
+      if (i == 0 && j == 0) {
+	xMin = xMax = x;
+	yMin = yMax = y;
+      } else {
+	if (x < xMin) {
+	  xMin = x;
+	} else if (x > xMax) {
+	  xMax = x;
+	}
+	if (y < yMin) {
+	  yMin = y;
+	} else if (y > yMax) {
+	  yMax = y;
+	}
+      }
+    }
+  }
+
+  // allow for the line width
+  //~ miter joins can extend farther than this
+  t0 = fabs(ctm[0]);
+  t1 = fabs(ctm[2]);
+  if (t0 > t1) {
+    xMin -= 0.5 * lineWidth * t0;
+    xMax += 0.5 * lineWidth * t0;
+  } else {
+    xMin -= 0.5 * lineWidth * t1;
+    xMax += 0.5 * lineWidth * t1;
+  }
+  t0 = fabs(ctm[0]);
+  t1 = fabs(ctm[3]);
+  if (t0 > t1) {
+    yMin -= 0.5 * lineWidth * t0;
+    yMax += 0.5 * lineWidth * t0;
+  } else {
+    yMin -= 0.5 * lineWidth * t1;
+    yMax += 0.5 * lineWidth * t1;
+  }
+
+  if (xMin > clipXMin) {
+    clipXMin = xMin;
+  }
+  if (yMin > clipYMin) {
+    clipYMin = yMin;
+  }
+  if (xMax < clipXMax) {
+    clipXMax = xMax;
+  }
+  if (yMax < clipYMax) {
+    clipYMax = yMax;
+  }
+}
+
+void GfxState::textShift(double tx, double ty) {
+  double dx, dy;
+
+  textTransformDelta(tx, ty, &dx, &dy);
+  curX += dx;
+  curY += dy;
+}
+
+void GfxState::shift(double dx, double dy) {
+  curX += dx;
+  curY += dy;
+}
+
+GfxState *GfxState::save() {
+  GfxState *newState;
+
+  newState = copy();
+  newState->saved = this;
+  return newState;
+}
+
+GfxState *GfxState::restore() {
+  GfxState *oldState;
+
+  if (saved) {
+    oldState = saved;
+
+    // these attributes aren't saved/restored by the q/Q operators
+    oldState->path = path;
+    oldState->curX = curX;
+    oldState->curY = curY;
+    oldState->lineX = lineX;
+    oldState->lineY = lineY;
+
+    path = NULL;
+    saved = NULL;
+    delete this;
+
+  } else {
+    oldState = this;
+  }
+
+  return oldState;
+}
+
+GBool GfxState::parseBlendMode(Object *obj, GfxBlendMode *mode) {
+  Object obj2;
+  int i, j;
+
+  if (obj->isName()) {
+    for (i = 0; i < nGfxBlendModeNames; ++i) {
+      if (!strcmp(obj->getName(), gfxBlendModeNames[i].name)) {
+	*mode = gfxBlendModeNames[i].mode;
+	return gTrue;
+      }
+    }
+    return gFalse;
+  } else if (obj->isArray()) {
+    for (i = 0; i < obj->arrayGetLength(); ++i) {
+      obj->arrayGet(i, &obj2);
+      if (!obj2.isName()) {
+	obj2.free();
+	return gFalse;
+      }
+      for (j = 0; j < nGfxBlendModeNames; ++j) {
+	if (!strcmp(obj2.getName(), gfxBlendModeNames[j].name)) {
+	  obj2.free();
+	  *mode = gfxBlendModeNames[j].mode;
+	  return gTrue;
+	}
+      }
+      obj2.free();
+    }
+    *mode = gfxBlendNormal;
+    return gTrue;
+  } else {
+    return gFalse;
+  }
+}
diff --git a/xpdf/GfxState.h b/xpdf/GfxState.h
new file mode 100644
--- /dev/null
+++ b/xpdf/GfxState.h
@@ -0,0 +1,1244 @@
+//========================================================================
+//
+// GfxState.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef GFXSTATE_H
+#define GFXSTATE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "Object.h"
+#include "Function.h"
+
+class Array;
+class GfxFont;
+class PDFRectangle;
+class GfxShading;
+
+//------------------------------------------------------------------------
+// GfxBlendMode
+//------------------------------------------------------------------------
+
+enum GfxBlendMode {
+  gfxBlendNormal,
+  gfxBlendMultiply,
+  gfxBlendScreen,
+  gfxBlendOverlay,
+  gfxBlendDarken,
+  gfxBlendLighten,
+  gfxBlendColorDodge,
+  gfxBlendColorBurn,
+  gfxBlendHardLight,
+  gfxBlendSoftLight,
+  gfxBlendDifference,
+  gfxBlendExclusion,
+  gfxBlendHue,
+  gfxBlendSaturation,
+  gfxBlendColor,
+  gfxBlendLuminosity
+};
+
+//------------------------------------------------------------------------
+// GfxColorComp
+//------------------------------------------------------------------------
+
+// 16.16 fixed point color component
+typedef int GfxColorComp;
+
+#define gfxColorComp1 0x10000
+
+static inline GfxColorComp dblToCol(double x) {
+  return (GfxColorComp)(x * gfxColorComp1);
+}
+
+static inline double colToDbl(GfxColorComp x) {
+  return (double)x / (double)gfxColorComp1;
+}
+
+static inline GfxColorComp byteToCol(Guchar x) {
+  // (x / 255) << 16  =  (0.0000000100000001... * x) << 16
+  //                  =  ((x << 8) + (x) + (x >> 8) + ...) << 16
+  //                  =  (x << 8) + (x) + (x >> 7)
+  //                                      [for rounding]
+  return (GfxColorComp)((x << 8) + x + (x >> 7));
+}
+
+static inline Guchar colToByte(GfxColorComp x) {
+  // 255 * x + 0.5  =  256 * x - x + 0x8000
+  return (Guchar)(((x << 8) - x + 0x8000) >> 16);
+}
+
+//------------------------------------------------------------------------
+// GfxColor
+//------------------------------------------------------------------------
+
+#define gfxColorMaxComps funcMaxOutputs
+
+struct GfxColor {
+  GfxColorComp c[gfxColorMaxComps];
+};
+
+//------------------------------------------------------------------------
+// GfxGray
+//------------------------------------------------------------------------
+
+typedef GfxColorComp GfxGray;
+
+//------------------------------------------------------------------------
+// GfxRGB
+//------------------------------------------------------------------------
+
+struct GfxRGB {
+  GfxColorComp r, g, b;
+};
+
+//------------------------------------------------------------------------
+// GfxCMYK
+//------------------------------------------------------------------------
+
+struct GfxCMYK {
+  GfxColorComp c, m, y, k;
+};
+
+//------------------------------------------------------------------------
+// GfxColorSpace
+//------------------------------------------------------------------------
+
+// NB: The nGfxColorSpaceModes constant and the gfxColorSpaceModeNames
+// array defined in GfxState.cc must match this enum.
+enum GfxColorSpaceMode {
+  csDeviceGray,
+  csCalGray,
+  csDeviceRGB,
+  csCalRGB,
+  csDeviceCMYK,
+  csLab,
+  csICCBased,
+  csIndexed,
+  csSeparation,
+  csDeviceN,
+  csPattern
+};
+
+class GfxColorSpace {
+public:
+
+  GfxColorSpace();
+  virtual ~GfxColorSpace();
+  virtual GfxColorSpace *copy() = 0;
+  virtual GfxColorSpaceMode getMode() = 0;
+
+  // Construct a color space.  Returns NULL if unsuccessful.
+  static GfxColorSpace *parse(Object *csObj);
+
+  // Convert to gray, RGB, or CMYK.
+  virtual void getGray(GfxColor *color, GfxGray *gray) = 0;
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb) = 0;
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk) = 0;
+
+  // Return the number of color components.
+  virtual int getNComps() = 0;
+
+  // Get this color space's default color.
+  virtual void getDefaultColor(GfxColor *color) = 0;
+
+  // Return the default ranges for each component, assuming an image
+  // with a max pixel value of <maxImgPixel>.
+  virtual void getDefaultRanges(double *decodeLow, double *decodeRange,
+				int maxImgPixel);
+
+  // Returns true if painting operations in this color space never
+  // mark the page (e.g., the "None" colorant).
+  virtual GBool isNonMarking() { return gFalse; }
+
+  // Return the number of color space modes
+  static int getNumColorSpaceModes();
+
+  // Return the name of the <idx>th color space mode.
+  static char *getColorSpaceModeName(int idx);
+
+private:
+};
+
+//------------------------------------------------------------------------
+// GfxDeviceGrayColorSpace
+//------------------------------------------------------------------------
+
+class GfxDeviceGrayColorSpace: public GfxColorSpace {
+public:
+
+  GfxDeviceGrayColorSpace();
+  virtual ~GfxDeviceGrayColorSpace();
+  virtual GfxColorSpace *copy();
+  virtual GfxColorSpaceMode getMode() { return csDeviceGray; }
+
+  virtual void getGray(GfxColor *color, GfxGray *gray);
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb);
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+
+  virtual int getNComps() { return 1; }
+  virtual void getDefaultColor(GfxColor *color);
+
+private:
+};
+
+//------------------------------------------------------------------------
+// GfxCalGrayColorSpace
+//------------------------------------------------------------------------
+
+class GfxCalGrayColorSpace: public GfxColorSpace {
+public:
+
+  GfxCalGrayColorSpace();
+  virtual ~GfxCalGrayColorSpace();
+  virtual GfxColorSpace *copy();
+  virtual GfxColorSpaceMode getMode() { return csCalGray; }
+
+  // Construct a CalGray color space.  Returns NULL if unsuccessful.
+  static GfxColorSpace *parse(Array *arr);
+
+  virtual void getGray(GfxColor *color, GfxGray *gray);
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb);
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+
+  virtual int getNComps() { return 1; }
+  virtual void getDefaultColor(GfxColor *color);
+
+  // CalGray-specific access.
+  double getWhiteX() { return whiteX; }
+  double getWhiteY() { return whiteY; }
+  double getWhiteZ() { return whiteZ; }
+  double getBlackX() { return blackX; }
+  double getBlackY() { return blackY; }
+  double getBlackZ() { return blackZ; }
+  double getGamma() { return gamma; }
+
+private:
+
+  double whiteX, whiteY, whiteZ;    // white point
+  double blackX, blackY, blackZ;    // black point
+  double gamma;			    // gamma value
+};
+
+//------------------------------------------------------------------------
+// GfxDeviceRGBColorSpace
+//------------------------------------------------------------------------
+
+class GfxDeviceRGBColorSpace: public GfxColorSpace {
+public:
+
+  GfxDeviceRGBColorSpace();
+  virtual ~GfxDeviceRGBColorSpace();
+  virtual GfxColorSpace *copy();
+  virtual GfxColorSpaceMode getMode() { return csDeviceRGB; }
+
+  virtual void getGray(GfxColor *color, GfxGray *gray);
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb);
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+
+  virtual int getNComps() { return 3; }
+  virtual void getDefaultColor(GfxColor *color);
+
+private:
+};
+
+//------------------------------------------------------------------------
+// GfxCalRGBColorSpace
+//------------------------------------------------------------------------
+
+class GfxCalRGBColorSpace: public GfxColorSpace {
+public:
+
+  GfxCalRGBColorSpace();
+  virtual ~GfxCalRGBColorSpace();
+  virtual GfxColorSpace *copy();
+  virtual GfxColorSpaceMode getMode() { return csCalRGB; }
+
+  // Construct a CalRGB color space.  Returns NULL if unsuccessful.
+  static GfxColorSpace *parse(Array *arr);
+
+  virtual void getGray(GfxColor *color, GfxGray *gray);
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb);
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+
+  virtual int getNComps() { return 3; }
+  virtual void getDefaultColor(GfxColor *color);
+
+  // CalRGB-specific access.
+  double getWhiteX() { return whiteX; }
+  double getWhiteY() { return whiteY; }
+  double getWhiteZ() { return whiteZ; }
+  double getBlackX() { return blackX; }
+  double getBlackY() { return blackY; }
+  double getBlackZ() { return blackZ; }
+  double getGammaR() { return gammaR; }
+  double getGammaG() { return gammaG; }
+  double getGammaB() { return gammaB; }
+  double *getMatrix() { return mat; }
+
+private:
+
+  double whiteX, whiteY, whiteZ;    // white point
+  double blackX, blackY, blackZ;    // black point
+  double gammaR, gammaG, gammaB;    // gamma values
+  double mat[9];		    // ABC -> XYZ transform matrix
+};
+
+//------------------------------------------------------------------------
+// GfxDeviceCMYKColorSpace
+//------------------------------------------------------------------------
+
+class GfxDeviceCMYKColorSpace: public GfxColorSpace {
+public:
+
+  GfxDeviceCMYKColorSpace();
+  virtual ~GfxDeviceCMYKColorSpace();
+  virtual GfxColorSpace *copy();
+  virtual GfxColorSpaceMode getMode() { return csDeviceCMYK; }
+
+  virtual void getGray(GfxColor *color, GfxGray *gray);
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb);
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+
+  virtual int getNComps() { return 4; }
+  virtual void getDefaultColor(GfxColor *color);
+
+private:
+};
+
+//------------------------------------------------------------------------
+// GfxLabColorSpace
+//------------------------------------------------------------------------
+
+class GfxLabColorSpace: public GfxColorSpace {
+public:
+
+  GfxLabColorSpace();
+  virtual ~GfxLabColorSpace();
+  virtual GfxColorSpace *copy();
+  virtual GfxColorSpaceMode getMode() { return csLab; }
+
+  // Construct a Lab color space.  Returns NULL if unsuccessful.
+  static GfxColorSpace *parse(Array *arr);
+
+  virtual void getGray(GfxColor *color, GfxGray *gray);
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb);
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+
+  virtual int getNComps() { return 3; }
+  virtual void getDefaultColor(GfxColor *color);
+
+  virtual void getDefaultRanges(double *decodeLow, double *decodeRange,
+				int maxImgPixel);
+
+  // Lab-specific access.
+  double getWhiteX() { return whiteX; }
+  double getWhiteY() { return whiteY; }
+  double getWhiteZ() { return whiteZ; }
+  double getBlackX() { return blackX; }
+  double getBlackY() { return blackY; }
+  double getBlackZ() { return blackZ; }
+  double getAMin() { return aMin; }
+  double getAMax() { return aMax; }
+  double getBMin() { return bMin; }
+  double getBMax() { return bMax; }
+
+private:
+
+  double whiteX, whiteY, whiteZ;    // white point
+  double blackX, blackY, blackZ;    // black point
+  double aMin, aMax, bMin, bMax;    // range for the a and b components
+  double kr, kg, kb;		    // gamut mapping mulitpliers
+};
+
+//------------------------------------------------------------------------
+// GfxICCBasedColorSpace
+//------------------------------------------------------------------------
+
+class GfxICCBasedColorSpace: public GfxColorSpace {
+public:
+
+  GfxICCBasedColorSpace(int nCompsA, GfxColorSpace *altA,
+			Ref *iccProfileStreamA);
+  virtual ~GfxICCBasedColorSpace();
+  virtual GfxColorSpace *copy();
+  virtual GfxColorSpaceMode getMode() { return csICCBased; }
+
+  // Construct an ICCBased color space.  Returns NULL if unsuccessful.
+  static GfxColorSpace *parse(Array *arr);
+
+  virtual void getGray(GfxColor *color, GfxGray *gray);
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb);
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+
+  virtual int getNComps() { return nComps; }
+  virtual void getDefaultColor(GfxColor *color);
+
+  virtual void getDefaultRanges(double *decodeLow, double *decodeRange,
+				int maxImgPixel);
+
+  // ICCBased-specific access.
+  GfxColorSpace *getAlt() { return alt; }
+
+private:
+
+  int nComps;			// number of color components (1, 3, or 4)
+  GfxColorSpace *alt;		// alternate color space
+  double rangeMin[4];		// min values for each component
+  double rangeMax[4];		// max values for each component
+  Ref iccProfileStream;		// the ICC profile
+};
+
+//------------------------------------------------------------------------
+// GfxIndexedColorSpace
+//------------------------------------------------------------------------
+
+class GfxIndexedColorSpace: public GfxColorSpace {
+public:
+
+  GfxIndexedColorSpace(GfxColorSpace *baseA, int indexHighA);
+  virtual ~GfxIndexedColorSpace();
+  virtual GfxColorSpace *copy();
+  virtual GfxColorSpaceMode getMode() { return csIndexed; }
+
+  // Construct a Lab color space.  Returns NULL if unsuccessful.
+  static GfxColorSpace *parse(Array *arr);
+
+  virtual void getGray(GfxColor *color, GfxGray *gray);
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb);
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+
+  virtual int getNComps() { return 1; }
+  virtual void getDefaultColor(GfxColor *color);
+
+  virtual void getDefaultRanges(double *decodeLow, double *decodeRange,
+				int maxImgPixel);
+
+  // Indexed-specific access.
+  GfxColorSpace *getBase() { return base; }
+  int getIndexHigh() { return indexHigh; }
+  Guchar *getLookup() { return lookup; }
+  GfxColor *mapColorToBase(GfxColor *color, GfxColor *baseColor);
+
+private:
+
+  GfxColorSpace *base;		// base color space
+  int indexHigh;		// max pixel value
+  Guchar *lookup;		// lookup table
+};
+
+//------------------------------------------------------------------------
+// GfxSeparationColorSpace
+//------------------------------------------------------------------------
+
+class GfxSeparationColorSpace: public GfxColorSpace {
+public:
+
+  GfxSeparationColorSpace(GString *nameA, GfxColorSpace *altA,
+			  Function *funcA);
+  virtual ~GfxSeparationColorSpace();
+  virtual GfxColorSpace *copy();
+  virtual GfxColorSpaceMode getMode() { return csSeparation; }
+
+  // Construct a Separation color space.  Returns NULL if unsuccessful.
+  static GfxColorSpace *parse(Array *arr);
+
+  virtual void getGray(GfxColor *color, GfxGray *gray);
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb);
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+
+  virtual int getNComps() { return 1; }
+  virtual void getDefaultColor(GfxColor *color);
+
+  virtual GBool isNonMarking() { return nonMarking; }
+
+  // Separation-specific access.
+  GString *getName() { return name; }
+  GfxColorSpace *getAlt() { return alt; }
+  Function *getFunc() { return func; }
+
+private:
+
+  GString *name;		// colorant name
+  GfxColorSpace *alt;		// alternate color space
+  Function *func;		// tint transform (into alternate color space)
+  GBool nonMarking;
+};
+
+//------------------------------------------------------------------------
+// GfxDeviceNColorSpace
+//------------------------------------------------------------------------
+
+class GfxDeviceNColorSpace: public GfxColorSpace {
+public:
+
+  GfxDeviceNColorSpace(int nCompsA, GfxColorSpace *alt, Function *func);
+  virtual ~GfxDeviceNColorSpace();
+  virtual GfxColorSpace *copy();
+  virtual GfxColorSpaceMode getMode() { return csDeviceN; }
+
+  // Construct a DeviceN color space.  Returns NULL if unsuccessful.
+  static GfxColorSpace *parse(Array *arr);
+
+  virtual void getGray(GfxColor *color, GfxGray *gray);
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb);
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+
+  virtual int getNComps() { return nComps; }
+  virtual void getDefaultColor(GfxColor *color);
+
+  virtual GBool isNonMarking() { return nonMarking; }
+
+  // DeviceN-specific access.
+  GString *getColorantName(int i) { return names[i]; }
+  GfxColorSpace *getAlt() { return alt; }
+  Function *getTintTransformFunc() { return func; }
+
+private:
+
+  int nComps;			// number of components
+  GString			// colorant names
+    *names[gfxColorMaxComps];
+  GfxColorSpace *alt;		// alternate color space
+  Function *func;		// tint transform (into alternate color space)
+  GBool nonMarking;
+};
+
+//------------------------------------------------------------------------
+// GfxPatternColorSpace
+//------------------------------------------------------------------------
+
+class GfxPatternColorSpace: public GfxColorSpace {
+public:
+
+  GfxPatternColorSpace(GfxColorSpace *underA);
+  virtual ~GfxPatternColorSpace();
+  virtual GfxColorSpace *copy();
+  virtual GfxColorSpaceMode getMode() { return csPattern; }
+
+  // Construct a Pattern color space.  Returns NULL if unsuccessful.
+  static GfxColorSpace *parse(Array *arr);
+
+  virtual void getGray(GfxColor *color, GfxGray *gray);
+  virtual void getRGB(GfxColor *color, GfxRGB *rgb);
+  virtual void getCMYK(GfxColor *color, GfxCMYK *cmyk);
+
+  virtual int getNComps() { return 0; }
+  virtual void getDefaultColor(GfxColor *color);
+
+  // Pattern-specific access.
+  GfxColorSpace *getUnder() { return under; }
+
+private:
+
+  GfxColorSpace *under;		// underlying color space (for uncolored
+				//   patterns)
+};
+
+//------------------------------------------------------------------------
+// GfxPattern
+//------------------------------------------------------------------------
+
+class GfxPattern {
+public:
+
+  GfxPattern(int typeA);
+  virtual ~GfxPattern();
+
+  static GfxPattern *parse(Object *obj);
+
+  virtual GfxPattern *copy() = 0;
+
+  int getType() { return type; }
+
+private:
+
+  int type;
+};
+
+//------------------------------------------------------------------------
+// GfxTilingPattern
+//------------------------------------------------------------------------
+
+class GfxTilingPattern: public GfxPattern {
+public:
+
+  static GfxTilingPattern *parse(Object *patObj);
+  virtual ~GfxTilingPattern();
+
+  virtual GfxPattern *copy();
+
+  int getPaintType() { return paintType; }
+  int getTilingType() { return tilingType; }
+  double *getBBox() { return bbox; }
+  double getXStep() { return xStep; }
+  double getYStep() { return yStep; }
+  Dict *getResDict()
+    { return resDict.isDict() ? resDict.getDict() : (Dict *)NULL; }
+  double *getMatrix() { return matrix; }
+  Object *getContentStream() { return &contentStream; }
+
+private:
+
+  GfxTilingPattern(int paintTypeA, int tilingTypeA,
+		   double *bboxA, double xStepA, double yStepA,
+		   Object *resDictA, double *matrixA,
+		   Object *contentStreamA);
+
+  int paintType;
+  int tilingType;
+  double bbox[4];
+  double xStep, yStep;
+  Object resDict;
+  double matrix[6];
+  Object contentStream;
+};
+
+//------------------------------------------------------------------------
+// GfxShadingPattern
+//------------------------------------------------------------------------
+
+class GfxShadingPattern: public GfxPattern {
+public:
+
+  static GfxShadingPattern *parse(Object *patObj);
+  virtual ~GfxShadingPattern();
+
+  virtual GfxPattern *copy();
+
+  GfxShading *getShading() { return shading; }
+  double *getMatrix() { return matrix; }
+
+private:
+
+  GfxShadingPattern(GfxShading *shadingA, double *matrixA);
+
+  GfxShading *shading;
+  double matrix[6];
+};
+
+//------------------------------------------------------------------------
+// GfxShading
+//------------------------------------------------------------------------
+
+class GfxShading {
+public:
+
+  GfxShading(int typeA);
+  GfxShading(GfxShading *shading);
+  virtual ~GfxShading();
+
+  static GfxShading *parse(Object *obj);
+
+  virtual GfxShading *copy() = 0;
+
+  int getType() { return type; }
+  GfxColorSpace *getColorSpace() { return colorSpace; }
+  GfxColor *getBackground() { return &background; }
+  GBool getHasBackground() { return hasBackground; }
+  void getBBox(double *xMinA, double *yMinA, double *xMaxA, double *yMaxA)
+    { *xMinA = xMin; *yMinA = yMin; *xMaxA = xMax; *yMaxA = yMax; }
+  GBool getHasBBox() { return hasBBox; }
+
+protected:
+
+  GBool init(Dict *dict);
+
+  int type;
+  GfxColorSpace *colorSpace;
+  GfxColor background;
+  GBool hasBackground;
+  double xMin, yMin, xMax, yMax;
+  GBool hasBBox;
+};
+
+//------------------------------------------------------------------------
+// GfxFunctionShading
+//------------------------------------------------------------------------
+
+class GfxFunctionShading: public GfxShading {
+public:
+
+  GfxFunctionShading(double x0A, double y0A,
+		     double x1A, double y1A,
+		     double *matrixA,
+		     Function **funcsA, int nFuncsA);
+  GfxFunctionShading(GfxFunctionShading *shading);
+  virtual ~GfxFunctionShading();
+
+  static GfxFunctionShading *parse(Dict *dict);
+
+  virtual GfxShading *copy();
+
+  void getDomain(double *x0A, double *y0A, double *x1A, double *y1A)
+    { *x0A = x0; *y0A = y0; *x1A = x1; *y1A = y1; }
+  double *getMatrix() { return matrix; }
+  int getNFuncs() { return nFuncs; }
+  Function *getFunc(int i) { return funcs[i]; }
+  void getColor(double x, double y, GfxColor *color);
+
+private:
+
+  double x0, y0, x1, y1;
+  double matrix[6];
+  Function *funcs[gfxColorMaxComps];
+  int nFuncs;
+};
+
+//------------------------------------------------------------------------
+// GfxAxialShading
+//------------------------------------------------------------------------
+
+class GfxAxialShading: public GfxShading {
+public:
+
+  GfxAxialShading(double x0A, double y0A,
+		  double x1A, double y1A,
+		  double t0A, double t1A,
+		  Function **funcsA, int nFuncsA,
+		  GBool extend0A, GBool extend1A);
+  GfxAxialShading(GfxAxialShading *shading);
+  virtual ~GfxAxialShading();
+
+  static GfxAxialShading *parse(Dict *dict);
+
+  virtual GfxShading *copy();
+
+  void getCoords(double *x0A, double *y0A, double *x1A, double *y1A)
+    { *x0A = x0; *y0A = y0; *x1A = x1; *y1A = y1; }
+  double getDomain0() { return t0; }
+  double getDomain1() { return t1; }
+  GBool getExtend0() { return extend0; }
+  GBool getExtend1() { return extend1; }
+  int getNFuncs() { return nFuncs; }
+  Function *getFunc(int i) { return funcs[i]; }
+  void getColor(double t, GfxColor *color);
+
+private:
+
+  double x0, y0, x1, y1;
+  double t0, t1;
+  Function *funcs[gfxColorMaxComps];
+  int nFuncs;
+  GBool extend0, extend1;
+};
+
+//------------------------------------------------------------------------
+// GfxRadialShading
+//------------------------------------------------------------------------
+
+class GfxRadialShading: public GfxShading {
+public:
+
+  GfxRadialShading(double x0A, double y0A, double r0A,
+		   double x1A, double y1A, double r1A,
+		   double t0A, double t1A,
+		   Function **funcsA, int nFuncsA,
+		   GBool extend0A, GBool extend1A);
+  GfxRadialShading(GfxRadialShading *shading);
+  virtual ~GfxRadialShading();
+
+  static GfxRadialShading *parse(Dict *dict);
+
+  virtual GfxShading *copy();
+
+  void getCoords(double *x0A, double *y0A, double *r0A,
+		 double *x1A, double *y1A, double *r1A)
+    { *x0A = x0; *y0A = y0; *r0A = r0; *x1A = x1; *y1A = y1; *r1A = r1; }
+  double getDomain0() { return t0; }
+  double getDomain1() { return t1; }
+  GBool getExtend0() { return extend0; }
+  GBool getExtend1() { return extend1; }
+  int getNFuncs() { return nFuncs; }
+  Function *getFunc(int i) { return funcs[i]; }
+  void getColor(double t, GfxColor *color);
+
+private:
+
+  double x0, y0, r0, x1, y1, r1;
+  double t0, t1;
+  Function *funcs[gfxColorMaxComps];
+  int nFuncs;
+  GBool extend0, extend1;
+};
+
+//------------------------------------------------------------------------
+// GfxGouraudTriangleShading
+//------------------------------------------------------------------------
+
+struct GfxGouraudVertex {
+  double x, y;
+  GfxColor color;
+};
+
+class GfxGouraudTriangleShading: public GfxShading {
+public:
+
+  GfxGouraudTriangleShading(int typeA,
+			    GfxGouraudVertex *verticesA, int nVerticesA,
+			    int (*trianglesA)[3], int nTrianglesA,
+			    Function **funcsA, int nFuncsA);
+  GfxGouraudTriangleShading(GfxGouraudTriangleShading *shading);
+  virtual ~GfxGouraudTriangleShading();
+
+  static GfxGouraudTriangleShading *parse(int typeA, Dict *dict, Stream *str);
+
+  virtual GfxShading *copy();
+
+  int getNTriangles() { return nTriangles; }
+  void getTriangle(int i, double *x0, double *y0, GfxColor *color0,
+		   double *x1, double *y1, GfxColor *color1,
+		   double *x2, double *y2, GfxColor *color2);
+
+private:
+
+  GfxGouraudVertex *vertices;
+  int nVertices;
+  int (*triangles)[3];
+  int nTriangles;
+  Function *funcs[gfxColorMaxComps];
+  int nFuncs;
+};
+
+//------------------------------------------------------------------------
+// GfxPatchMeshShading
+//------------------------------------------------------------------------
+
+struct GfxPatch {
+  double x[4][4];
+  double y[4][4];
+  GfxColor color[2][2];
+};
+
+class GfxPatchMeshShading: public GfxShading {
+public:
+
+  GfxPatchMeshShading(int typeA, GfxPatch *patchesA, int nPatchesA,
+		      Function **funcsA, int nFuncsA);
+  GfxPatchMeshShading(GfxPatchMeshShading *shading);
+  virtual ~GfxPatchMeshShading();
+
+  static GfxPatchMeshShading *parse(int typeA, Dict *dict, Stream *str);
+
+  virtual GfxShading *copy();
+
+  int getNPatches() { return nPatches; }
+  GfxPatch *getPatch(int i) { return &patches[i]; }
+
+private:
+
+  GfxPatch *patches;
+  int nPatches;
+  Function *funcs[gfxColorMaxComps];
+  int nFuncs;
+};
+
+//------------------------------------------------------------------------
+// GfxImageColorMap
+//------------------------------------------------------------------------
+
+class GfxImageColorMap {
+public:
+
+  // Constructor.
+  GfxImageColorMap(int bitsA, Object *decode, GfxColorSpace *colorSpaceA);
+
+  // Destructor.
+  ~GfxImageColorMap();
+
+  // Return a copy of this color map.
+  GfxImageColorMap *copy() { return new GfxImageColorMap(this); }
+
+  // Is color map valid?
+  GBool isOk() { return ok; }
+
+  // Get the color space.
+  GfxColorSpace *getColorSpace() { return colorSpace; }
+
+  // Get stream decoding info.
+  int getNumPixelComps() { return nComps; }
+  int getBits() { return bits; }
+
+  // Get decode table.
+  double getDecodeLow(int i) { return decodeLow[i]; }
+  double getDecodeHigh(int i) { return decodeLow[i] + decodeRange[i]; }
+
+  // Convert an image pixel to a color.
+  void getGray(Guchar *x, GfxGray *gray);
+  void getRGB(Guchar *x, GfxRGB *rgb);
+  void getCMYK(Guchar *x, GfxCMYK *cmyk);
+  void getColor(Guchar *x, GfxColor *color);
+
+private:
+
+  GfxImageColorMap(GfxImageColorMap *colorMap);
+
+  GfxColorSpace *colorSpace;	// the image color space
+  int bits;			// bits per component
+  int nComps;			// number of components in a pixel
+  GfxColorSpace *colorSpace2;	// secondary color space
+  int nComps2;			// number of components in colorSpace2
+  GfxColorComp *		// lookup table
+    lookup[gfxColorMaxComps];
+  double			// minimum values for each component
+    decodeLow[gfxColorMaxComps];
+  double			// max - min value for each component
+    decodeRange[gfxColorMaxComps];
+  GBool ok;
+};
+
+//------------------------------------------------------------------------
+// GfxSubpath and GfxPath
+//------------------------------------------------------------------------
+
+class GfxSubpath {
+public:
+
+  // Constructor.
+  GfxSubpath(double x1, double y1);
+
+  // Destructor.
+  ~GfxSubpath();
+
+  // Copy.
+  GfxSubpath *copy() { return new GfxSubpath(this); }
+
+  // Get points.
+  int getNumPoints() { return n; }
+  double getX(int i) { return x[i]; }
+  double getY(int i) { return y[i]; }
+  GBool getCurve(int i) { return curve[i]; }
+
+  // Get last point.
+  double getLastX() { return x[n-1]; }
+  double getLastY() { return y[n-1]; }
+
+  // Add a line segment.
+  void lineTo(double x1, double y1);
+
+  // Add a Bezier curve.
+  void curveTo(double x1, double y1, double x2, double y2,
+	       double x3, double y3);
+
+  // Close the subpath.
+  void close();
+  GBool isClosed() { return closed; }
+
+  // Add (<dx>, <dy>) to each point in the subpath.
+  void offset(double dx, double dy);
+
+private:
+
+  double *x, *y;		// points
+  GBool *curve;			// curve[i] => point i is a control point
+				//   for a Bezier curve
+  int n;			// number of points
+  int size;			// size of x/y arrays
+  GBool closed;			// set if path is closed
+
+  GfxSubpath(GfxSubpath *subpath);
+};
+
+class GfxPath {
+public:
+
+  // Constructor.
+  GfxPath();
+
+  // Destructor.
+  ~GfxPath();
+
+  // Copy.
+  GfxPath *copy()
+    { return new GfxPath(justMoved, firstX, firstY, subpaths, n, size); }
+
+  // Is there a current point?
+  GBool isCurPt() { return n > 0 || justMoved; }
+
+  // Is the path non-empty, i.e., is there at least one segment?
+  GBool isPath() { return n > 0; }
+
+  // Get subpaths.
+  int getNumSubpaths() { return n; }
+  GfxSubpath *getSubpath(int i) { return subpaths[i]; }
+
+  // Get last point on last subpath.
+  double getLastX() { return subpaths[n-1]->getLastX(); }
+  double getLastY() { return subpaths[n-1]->getLastY(); }
+
+  // Move the current point.
+  void moveTo(double x, double y);
+
+  // Add a segment to the last subpath.
+  void lineTo(double x, double y);
+
+  // Add a Bezier curve to the last subpath
+  void curveTo(double x1, double y1, double x2, double y2,
+	       double x3, double y3);
+
+  // Close the last subpath.
+  void close();
+
+  // Append <path> to <this>.
+  void append(GfxPath *path);
+
+  // Add (<dx>, <dy>) to each point in the path.
+  void offset(double dx, double dy);
+
+private:
+
+  GBool justMoved;		// set if a new subpath was just started
+  double firstX, firstY;	// first point in new subpath
+  GfxSubpath **subpaths;	// subpaths
+  int n;			// number of subpaths
+  int size;			// size of subpaths array
+
+  GfxPath(GBool justMoved1, double firstX1, double firstY1,
+	  GfxSubpath **subpaths1, int n1, int size1);
+};
+
+//------------------------------------------------------------------------
+// GfxState
+//------------------------------------------------------------------------
+
+class GfxState {
+public:
+
+  // Construct a default GfxState, for a device with resolution <hDPI>
+  // x <vDPI>, page box <pageBox>, page rotation <rotateA>, and
+  // coordinate system specified by <upsideDown>.
+  GfxState(double hDPIA, double vDPIA, PDFRectangle *pageBox,
+	   int rotateA, GBool upsideDown);
+
+  // Destructor.
+  ~GfxState();
+
+  // Copy.
+  GfxState *copy() { return new GfxState(this); }
+
+  // Accessors.
+  double getHDPI() { return hDPI; }
+  double getVDPI() { return vDPI; }
+  double *getCTM() { return ctm; }
+  double getX1() { return px1; }
+  double getY1() { return py1; }
+  double getX2() { return px2; }
+  double getY2() { return py2; }
+  double getPageWidth() { return pageWidth; }
+  double getPageHeight() { return pageHeight; }
+  int getRotate() { return rotate; }
+  GfxColor *getFillColor() { return &fillColor; }
+  GfxColor *getStrokeColor() { return &strokeColor; }
+  void getFillGray(GfxGray *gray)
+    { fillColorSpace->getGray(&fillColor, gray); }
+  void getStrokeGray(GfxGray *gray)
+    { strokeColorSpace->getGray(&strokeColor, gray); }
+  void getFillRGB(GfxRGB *rgb)
+    { fillColorSpace->getRGB(&fillColor, rgb); }
+  void getStrokeRGB(GfxRGB *rgb)
+    { strokeColorSpace->getRGB(&strokeColor, rgb); }
+  void getFillCMYK(GfxCMYK *cmyk)
+    { fillColorSpace->getCMYK(&fillColor, cmyk); }
+  void getStrokeCMYK(GfxCMYK *cmyk)
+    { strokeColorSpace->getCMYK(&strokeColor, cmyk); }
+  GfxColorSpace *getFillColorSpace() { return fillColorSpace; }
+  GfxColorSpace *getStrokeColorSpace() { return strokeColorSpace; }
+  GfxPattern *getFillPattern() { return fillPattern; }
+  GfxPattern *getStrokePattern() { return strokePattern; }
+  GfxBlendMode getBlendMode() { return blendMode; }
+  double getFillOpacity() { return fillOpacity; }
+  double getStrokeOpacity() { return strokeOpacity; }
+  GBool getFillOverprint() { return fillOverprint; }
+  GBool getStrokeOverprint() { return strokeOverprint; }
+  Function **getTransfer() { return transfer; }
+  double getLineWidth() { return lineWidth; }
+  void getLineDash(double **dash, int *length, double *start)
+    { *dash = lineDash; *length = lineDashLength; *start = lineDashStart; }
+  int getFlatness() { return flatness; }
+  int getLineJoin() { return lineJoin; }
+  int getLineCap() { return lineCap; }
+  double getMiterLimit() { return miterLimit; }
+  GBool getStrokeAdjust() { return strokeAdjust; }
+  GfxFont *getFont() { return font; }
+  double getFontSize() { return fontSize; }
+  double *getTextMat() { return textMat; }
+  double getCharSpace() { return charSpace; }
+  double getWordSpace() { return wordSpace; }
+  double getHorizScaling() { return horizScaling; }
+  double getLeading() { return leading; }
+  double getRise() { return rise; }
+  int getRender() { return render; }
+  GfxPath *getPath() { return path; }
+  void setPath(GfxPath *pathA);
+  double getCurX() { return curX; }
+  double getCurY() { return curY; }
+  void getClipBBox(double *xMin, double *yMin, double *xMax, double *yMax)
+    { *xMin = clipXMin; *yMin = clipYMin; *xMax = clipXMax; *yMax = clipYMax; }
+  void getUserClipBBox(double *xMin, double *yMin, double *xMax, double *yMax);
+  double getLineX() { return lineX; }
+  double getLineY() { return lineY; }
+
+  // Is there a current point/path?
+  GBool isCurPt() { return path->isCurPt(); }
+  GBool isPath() { return path->isPath(); }
+
+  // Transforms.
+  void transform(double x1, double y1, double *x2, double *y2)
+    { *x2 = ctm[0] * x1 + ctm[2] * y1 + ctm[4];
+      *y2 = ctm[1] * x1 + ctm[3] * y1 + ctm[5]; }
+  void transformDelta(double x1, double y1, double *x2, double *y2)
+    { *x2 = ctm[0] * x1 + ctm[2] * y1;
+      *y2 = ctm[1] * x1 + ctm[3] * y1; }
+  void textTransform(double x1, double y1, double *x2, double *y2)
+    { *x2 = textMat[0] * x1 + textMat[2] * y1 + textMat[4];
+      *y2 = textMat[1] * x1 + textMat[3] * y1 + textMat[5]; }
+  void textTransformDelta(double x1, double y1, double *x2, double *y2)
+    { *x2 = textMat[0] * x1 + textMat[2] * y1;
+      *y2 = textMat[1] * x1 + textMat[3] * y1; }
+  double transformWidth(double w);
+  double getTransformedLineWidth()
+    { return transformWidth(lineWidth); }
+  double getTransformedFontSize();
+  void getFontTransMat(double *m11, double *m12, double *m21, double *m22);
+
+  // Change state parameters.
+  void setCTM(double a, double b, double c,
+	      double d, double e, double f);
+  void concatCTM(double a, double b, double c,
+		 double d, double e, double f);
+  void shiftCTM(double tx, double ty);
+  void setFillColorSpace(GfxColorSpace *colorSpace);
+  void setStrokeColorSpace(GfxColorSpace *colorSpace);
+  void setFillColor(GfxColor *color) { fillColor = *color; }
+  void setStrokeColor(GfxColor *color) { strokeColor = *color; }
+  void setFillPattern(GfxPattern *pattern);
+  void setStrokePattern(GfxPattern *pattern);
+  void setBlendMode(GfxBlendMode mode) { blendMode = mode; }
+  void setFillOpacity(double opac) { fillOpacity = opac; }
+  void setStrokeOpacity(double opac) { strokeOpacity = opac; }
+  void setFillOverprint(GBool op) { fillOverprint = op; }
+  void setStrokeOverprint(GBool op) { strokeOverprint = op; }
+  void setTransfer(Function **funcs);
+  void setLineWidth(double width) { lineWidth = width; }
+  void setLineDash(double *dash, int length, double start);
+  void setFlatness(int flatness1) { flatness = flatness1; }
+  void setLineJoin(int lineJoin1) { lineJoin = lineJoin1; }
+  void setLineCap(int lineCap1) { lineCap = lineCap1; }
+  void setMiterLimit(double limit) { miterLimit = limit; }
+  void setStrokeAdjust(GBool sa) { strokeAdjust = sa; }
+  void setFont(GfxFont *fontA, double fontSizeA)
+    { font = fontA; fontSize = fontSizeA; }
+  void setTextMat(double a, double b, double c,
+		  double d, double e, double f)
+    { textMat[0] = a; textMat[1] = b; textMat[2] = c;
+      textMat[3] = d; textMat[4] = e; textMat[5] = f; }
+  void setCharSpace(double space)
+    { charSpace = space; }
+  void setWordSpace(double space)
+    { wordSpace = space; }
+  void setHorizScaling(double scale)
+    { horizScaling = 0.01 * scale; }
+  void setLeading(double leadingA)
+    { leading = leadingA; }
+  void setRise(double riseA)
+    { rise = riseA; }
+  void setRender(int renderA)
+    { render = renderA; }
+
+  // Add to path.
+  void moveTo(double x, double y)
+    { path->moveTo(curX = x, curY = y); }
+  void lineTo(double x, double y)
+    { path->lineTo(curX = x, curY = y); }
+  void curveTo(double x1, double y1, double x2, double y2,
+	       double x3, double y3)
+    { path->curveTo(x1, y1, x2, y2, curX = x3, curY = y3); }
+  void closePath()
+    { path->close(); curX = path->getLastX(); curY = path->getLastY(); }
+  void clearPath();
+
+  // Update clip region.
+  void clip();
+  void clipToStrokePath();
+
+  // Text position.
+  void textSetPos(double tx, double ty) { lineX = tx; lineY = ty; }
+  void textMoveTo(double tx, double ty)
+    { lineX = tx; lineY = ty; textTransform(tx, ty, &curX, &curY); }
+  void textShift(double tx, double ty);
+  void shift(double dx, double dy);
+
+  // Push/pop GfxState on/off stack.
+  GfxState *save();
+  GfxState *restore();
+  GBool hasSaves() { return saved != NULL; }
+
+  // Misc
+  GBool parseBlendMode(Object *obj, GfxBlendMode *mode);
+
+private:
+
+  double hDPI, vDPI;		// resolution
+  double ctm[6];		// coord transform matrix
+  double px1, py1, px2, py2;	// page corners (user coords)
+  double pageWidth, pageHeight;	// page size (pixels)
+  int rotate;			// page rotation angle
+
+  GfxColorSpace *fillColorSpace;   // fill color space
+  GfxColorSpace *strokeColorSpace; // stroke color space
+  GfxColor fillColor;		// fill color
+  GfxColor strokeColor;		// stroke color
+  GfxPattern *fillPattern;	// fill pattern
+  GfxPattern *strokePattern;	// stroke pattern
+  GfxBlendMode blendMode;	// transparency blend mode
+  double fillOpacity;		// fill opacity
+  double strokeOpacity;		// stroke opacity
+  GBool fillOverprint;		// fill overprint
+  GBool strokeOverprint;	// stroke overprint
+  Function *transfer[4];	// transfer function (entries may be: all
+				//   NULL = identity; last three NULL =
+				//   single function; all four non-NULL =
+				//   R,G,B,gray functions)
+
+  double lineWidth;		// line width
+  double *lineDash;		// line dash
+  int lineDashLength;
+  double lineDashStart;
+  int flatness;			// curve flatness
+  int lineJoin;			// line join style
+  int lineCap;			// line cap style
+  double miterLimit;		// line miter limit
+  GBool strokeAdjust;		// stroke adjustment
+
+  GfxFont *font;		// font
+  double fontSize;		// font size
+  double textMat[6];		// text matrix
+  double charSpace;		// character spacing
+  double wordSpace;		// word spacing
+  double horizScaling;		// horizontal scaling
+  double leading;		// text leading
+  double rise;			// text rise
+  int render;			// text rendering mode
+
+  GfxPath *path;		// array of path elements
+  double curX, curY;		// current point (user coords)
+  double lineX, lineY;		// start of current text line (text coords)
+
+  double clipXMin, clipYMin,	// bounding box for clip region
+         clipXMax, clipYMax;
+
+  GfxState *saved;		// next GfxState on stack
+
+  GfxState(GfxState *state);
+};
+
+#endif
diff --git a/xpdf/GlobalParams.cc b/xpdf/GlobalParams.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/GlobalParams.cc
@@ -0,0 +1,2908 @@
+//========================================================================
+//
+// GlobalParams.cc
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#ifdef ENABLE_PLUGINS
+#  ifndef WIN32
+#    include <dlfcn.h>
+#  endif
+#endif
+#ifdef WIN32
+#  include <shlobj.h>
+#endif
+#if HAVE_PAPER_H
+#include <paper.h>
+#endif
+#include "gmem.h"
+#include "GString.h"
+#include "GList.h"
+#include "GHash.h"
+#include "gfile.h"
+#include "Error.h"
+#include "NameToCharCode.h"
+#include "CharCodeToUnicode.h"
+#include "UnicodeMap.h"
+#include "CMap.h"
+#include "BuiltinFontTables.h"
+#include "FontEncodingTables.h"
+#ifdef ENABLE_PLUGINS
+#  include "XpdfPluginAPI.h"
+#endif
+#include "GlobalParams.h"
+
+#ifdef WIN32
+#  define strcasecmp stricmp
+#endif
+
+#if MULTITHREADED
+#  define lockGlobalParams            gLockMutex(&mutex)
+#  define lockUnicodeMapCache         gLockMutex(&unicodeMapCacheMutex)
+#  define lockCMapCache               gLockMutex(&cMapCacheMutex)
+#  define unlockGlobalParams          gUnlockMutex(&mutex)
+#  define unlockUnicodeMapCache       gUnlockMutex(&unicodeMapCacheMutex)
+#  define unlockCMapCache             gUnlockMutex(&cMapCacheMutex)
+#else
+#  define lockGlobalParams
+#  define lockUnicodeMapCache
+#  define lockCMapCache
+#  define unlockGlobalParams
+#  define unlockUnicodeMapCache
+#  define unlockCMapCache
+#endif
+
+#include "NameToUnicodeTable.h"
+#include "UnicodeMapTables.h"
+#include "UTF8.h"
+
+#ifdef ENABLE_PLUGINS
+#  ifdef WIN32
+extern XpdfPluginVecTable xpdfPluginVecTable;
+#  endif
+#endif
+
+//------------------------------------------------------------------------
+
+#define cidToUnicodeCacheSize     4
+#define unicodeToUnicodeCacheSize 4
+
+//------------------------------------------------------------------------
+
+static struct {
+  char *name;
+  char *t1FileName;
+  char *ttFileName;
+} displayFontTab[] = {
+  {"Courier",               "n022003l.pfb", "cour.ttf"},
+  {"Courier-Bold",          "n022004l.pfb", "courbd.ttf"},
+  {"Courier-BoldOblique",   "n022024l.pfb", "courbi.ttf"},
+  {"Courier-Oblique",       "n022023l.pfb", "couri.ttf"},
+  {"Helvetica",             "n019003l.pfb", "arial.ttf"},
+  {"Helvetica-Bold",        "n019004l.pfb", "arialbd.ttf"},
+  {"Helvetica-BoldOblique", "n019024l.pfb", "arialbi.ttf"},
+  {"Helvetica-Oblique",     "n019023l.pfb", "ariali.ttf"},
+  {"Symbol",                "s050000l.pfb", NULL},
+  {"Times-Bold",            "n021004l.pfb", "timesbd.ttf"},
+  {"Times-BoldItalic",      "n021024l.pfb", "timesbi.ttf"},
+  {"Times-Italic",          "n021023l.pfb", "timesi.ttf"},
+  {"Times-Roman",           "n021003l.pfb", "times.ttf"},
+  {"ZapfDingbats",          "d050000l.pfb", NULL},
+  {NULL}
+};
+
+#ifdef WIN32
+static char *displayFontDirs[] = {
+  "c:/windows/fonts",
+  "c:/winnt/fonts",
+  NULL
+};
+#else
+static char *displayFontDirs[] = {
+  "/usr/share/ghostscript/fonts",
+  "/usr/local/share/ghostscript/fonts",
+  "/usr/share/fonts/default/Type1",
+  "/usr/share/fonts/default/ghostscript",
+  "/usr/share/fonts/type1/gsfonts",
+  NULL
+};
+#endif
+
+//------------------------------------------------------------------------
+
+GlobalParams *globalParams = NULL;
+
+//------------------------------------------------------------------------
+// DisplayFontParam
+//------------------------------------------------------------------------
+
+DisplayFontParam::DisplayFontParam(GString *nameA,
+				   DisplayFontParamKind kindA) {
+  name = nameA;
+  kind = kindA;
+  switch (kind) {
+  case displayFontT1:
+    t1.fileName = NULL;
+    break;
+  case displayFontTT:
+    tt.fileName = NULL;
+    break;
+  }
+}
+
+DisplayFontParam::~DisplayFontParam() {
+  delete name;
+  switch (kind) {
+  case displayFontT1:
+    if (t1.fileName) {
+      delete t1.fileName;
+    }
+    break;
+  case displayFontTT:
+    if (tt.fileName) {
+      delete tt.fileName;
+    }
+    break;
+  }
+}
+
+#ifdef WIN32
+
+//------------------------------------------------------------------------
+// WinFontInfo
+//------------------------------------------------------------------------
+
+class WinFontInfo: public DisplayFontParam {
+public:
+
+  GBool bold, italic;
+
+  static WinFontInfo *make(GString *nameA, GBool boldA, GBool italicA,
+			   HKEY regKey, char *winFontDir);
+  WinFontInfo(GString *nameA, GBool boldA, GBool italicA,
+	      GString *fileNameA);
+  virtual ~WinFontInfo();
+  GBool equals(WinFontInfo *fi);
+};
+
+WinFontInfo *WinFontInfo::make(GString *nameA, GBool boldA, GBool italicA,
+			       HKEY regKey, char *winFontDir) {
+  GString *regName;
+  GString *fileNameA;
+  char buf[MAX_PATH];
+  DWORD n;
+  char c;
+  int i;
+
+  //----- find the font file
+  fileNameA = NULL;
+  regName = nameA->copy();
+  if (boldA) {
+    regName->append(" Bold");
+  }
+  if (italicA) {
+    regName->append(" Italic");
+  }
+  regName->append(" (TrueType)");
+  n = sizeof(buf);
+  if (RegQueryValueEx(regKey, regName->getCString(), NULL, NULL,
+		      (LPBYTE)buf, &n) == ERROR_SUCCESS) {
+    fileNameA = new GString(winFontDir);
+    fileNameA->append('\\')->append(buf);
+  }
+  delete regName;
+  if (!fileNameA) {
+    delete nameA;
+    return NULL;
+  }
+
+  //----- normalize the font name
+  i = 0;
+  while (i < nameA->getLength()) {
+    c = nameA->getChar(i);
+    if (c == ' ' || c == ',' || c == '-') {
+      nameA->del(i);
+    } else {
+      ++i;
+    }
+  }
+
+  return new WinFontInfo(nameA, boldA, italicA, fileNameA);
+}
+
+WinFontInfo::WinFontInfo(GString *nameA, GBool boldA, GBool italicA,
+			 GString *fileNameA):
+  DisplayFontParam(nameA, displayFontTT)
+{
+  bold = boldA;
+  italic = italicA;
+  tt.fileName = fileNameA;
+}
+
+WinFontInfo::~WinFontInfo() {
+}
+
+GBool WinFontInfo::equals(WinFontInfo *fi) {
+  return !name->cmp(fi->name) && bold == fi->bold && italic == fi->italic;
+}
+
+//------------------------------------------------------------------------
+// WinFontList
+//------------------------------------------------------------------------
+
+class WinFontList {
+public:
+
+  WinFontList(char *winFontDirA);
+  ~WinFontList();
+  WinFontInfo *find(GString *font);
+
+private:
+
+  void add(WinFontInfo *fi);
+  static int CALLBACK enumFunc1(CONST LOGFONT *font,
+				CONST TEXTMETRIC *metrics,
+				DWORD type, LPARAM data);
+  static int CALLBACK enumFunc2(CONST LOGFONT *font,
+				CONST TEXTMETRIC *metrics,
+				DWORD type, LPARAM data);
+
+  GList *fonts;			// [WinFontInfo]
+  HDC dc;			// (only used during enumeration)
+  HKEY regKey;			// (only used during enumeration)
+  char *winFontDir;		// (only used during enumeration)
+};
+
+WinFontList::WinFontList(char *winFontDirA) {
+  OSVERSIONINFO version;
+  char *path;
+
+  fonts = new GList();
+  dc = GetDC(NULL);
+  winFontDir = winFontDirA;
+  version.dwOSVersionInfoSize = sizeof(version);
+  GetVersionEx(&version);
+  if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) {
+    path = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts\\";
+  } else {
+    path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts\\";
+  }
+  if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, path, 0,
+		   KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS,
+		   &regKey) == ERROR_SUCCESS) {
+    EnumFonts(dc, NULL, &WinFontList::enumFunc1, (LPARAM)this);
+    RegCloseKey(regKey);
+  }
+  ReleaseDC(NULL, dc);
+}
+
+WinFontList::~WinFontList() {
+  deleteGList(fonts, WinFontInfo);
+}
+
+void WinFontList::add(WinFontInfo *fi) {
+  int i;
+
+  for (i = 0; i < fonts->getLength(); ++i) {
+    if (((WinFontInfo *)fonts->get(i))->equals(fi)) {
+      delete fi;
+      return;
+    }
+  }
+  fonts->append(fi);
+}
+
+WinFontInfo *WinFontList::find(GString *font) {
+  GString *name;
+  GBool bold, italic;
+  WinFontInfo *fi;
+  char c;
+  int n, i;
+
+  name = font->copy();
+
+  // remove space, comma, dash chars
+  i = 0;
+  while (i < name->getLength()) {
+    c = name->getChar(i);
+    if (c == ' ' || c == ',' || c == '-') {
+      name->del(i);
+    } else {
+      ++i;
+    }
+  }
+  n = name->getLength();
+
+  // remove trailing "MT" (Foo-MT, Foo-BoldMT, etc.)
+  if (!strcmp(name->getCString() + n - 2, "MT")) {
+    name->del(n - 2, 2);
+    n -= 2;
+  }
+
+  // look for "Italic"
+  if (!strcmp(name->getCString() + n - 6, "Italic")) {
+    name->del(n - 6, 6);
+    italic = gTrue;
+    n -= 6;
+  } else {
+    italic = gFalse;
+  }
+
+  // look for "Bold"
+  if (!strcmp(name->getCString() + n - 4, "Bold")) {
+    name->del(n - 4, 4);
+    bold = gTrue;
+    n -= 4;
+  } else {
+    bold = gFalse;
+  }
+
+  // remove trailing "MT" (FooMT-Bold, etc.)
+  if (!strcmp(name->getCString() + n - 2, "MT")) {
+    name->del(n - 2, 2);
+    n -= 2;
+  }
+
+  // remove trailing "PS"
+  if (!strcmp(name->getCString() + n - 2, "PS")) {
+    name->del(n - 2, 2);
+    n -= 2;
+  }
+
+  // search for the font
+  fi = NULL;
+  for (i = 0; i < fonts->getLength(); ++i) {
+    fi = (WinFontInfo *)fonts->get(i);
+    if (!fi->name->cmp(name) && fi->bold == bold && fi->italic == italic) {
+      break;
+    }
+    fi = NULL;
+  }
+
+  delete name;
+  return fi;
+}
+
+int CALLBACK WinFontList::enumFunc1(CONST LOGFONT *font,
+				    CONST TEXTMETRIC *metrics,
+				    DWORD type, LPARAM data) {
+  WinFontList *fl = (WinFontList *)data;
+
+  EnumFonts(fl->dc, font->lfFaceName, &WinFontList::enumFunc2, (LPARAM)fl);
+  return 1;
+}
+
+int CALLBACK WinFontList::enumFunc2(CONST LOGFONT *font,
+				    CONST TEXTMETRIC *metrics,
+				    DWORD type, LPARAM data) {
+  WinFontList *fl = (WinFontList *)data;
+  WinFontInfo *fi;
+
+  if (type & TRUETYPE_FONTTYPE) {
+    if ((fi = WinFontInfo::make(new GString(font->lfFaceName),
+				font->lfWeight >= 600,
+				font->lfItalic ? gTrue : gFalse,
+				fl->regKey, fl->winFontDir))) {
+      fl->add(fi);
+    }
+  }
+  return 1;
+}
+
+#endif // WIN32
+
+//------------------------------------------------------------------------
+// PSFontParam
+//------------------------------------------------------------------------
+
+PSFontParam::PSFontParam(GString *pdfFontNameA, int wModeA,
+			 GString *psFontNameA, GString *encodingA) {
+  pdfFontName = pdfFontNameA;
+  wMode = wModeA;
+  psFontName = psFontNameA;
+  encoding = encodingA;
+}
+
+PSFontParam::~PSFontParam() {
+  delete pdfFontName;
+  delete psFontName;
+  if (encoding) {
+    delete encoding;
+  }
+}
+
+//------------------------------------------------------------------------
+// KeyBinding
+//------------------------------------------------------------------------
+
+KeyBinding::KeyBinding(int codeA, int modsA, int contextA, char *cmd0) {
+  code = codeA;
+  mods = modsA;
+  context = contextA;
+  cmds = new GList();
+  cmds->append(new GString(cmd0));
+}
+
+KeyBinding::KeyBinding(int codeA, int modsA, int contextA,
+		       char *cmd0, char *cmd1) {
+  code = codeA;
+  mods = modsA;
+  context = contextA;
+  cmds = new GList();
+  cmds->append(new GString(cmd0));
+  cmds->append(new GString(cmd1));
+}
+
+KeyBinding::KeyBinding(int codeA, int modsA, int contextA, GList *cmdsA) {
+  code = codeA;
+  mods = modsA;
+  context = contextA;
+  cmds = cmdsA;
+}
+
+KeyBinding::~KeyBinding() {
+  deleteGList(cmds, GString);
+}
+
+#ifdef ENABLE_PLUGINS
+//------------------------------------------------------------------------
+// Plugin
+//------------------------------------------------------------------------
+
+class Plugin {
+public:
+
+  static Plugin *load(char *type, char *name);
+  ~Plugin();
+
+private:
+
+#ifdef WIN32
+  Plugin(HMODULE libA);
+  HMODULE lib;
+#else
+  Plugin(void *dlA);
+  void *dl;
+#endif
+};
+
+Plugin *Plugin::load(char *type, char *name) {
+  GString *path;
+  Plugin *plugin;
+  XpdfPluginVecTable *vt;
+  XpdfBool (*xpdfInitPlugin)(void);
+#ifdef WIN32
+  HMODULE libA;
+#else
+  void *dlA;
+#endif
+
+  path = globalParams->getBaseDir();
+  appendToPath(path, "plugins");
+  appendToPath(path, type);
+  appendToPath(path, name);
+
+#ifdef WIN32
+  path->append(".dll");
+  if (!(libA = LoadLibrary(path->getCString()))) {
+    error(-1, "Failed to load plugin '%s'",
+	  path->getCString());
+    goto err1;
+  }
+  if (!(vt = (XpdfPluginVecTable *)
+	         GetProcAddress(libA, "xpdfPluginVecTable"))) {
+    error(-1, "Failed to find xpdfPluginVecTable in plugin '%s'",
+	  path->getCString());
+    goto err2;
+  }
+#else
+  //~ need to deal with other extensions here
+  path->append(".so");
+  if (!(dlA = dlopen(path->getCString(), RTLD_NOW))) {
+    error(-1, "Failed to load plugin '%s': %s",
+	  path->getCString(), dlerror());
+    goto err1;
+  }
+  if (!(vt = (XpdfPluginVecTable *)dlsym(dlA, "xpdfPluginVecTable"))) {
+    error(-1, "Failed to find xpdfPluginVecTable in plugin '%s'",
+	  path->getCString());
+    goto err2;
+  }
+#endif
+
+  if (vt->version != xpdfPluginVecTable.version) {
+    error(-1, "Plugin '%s' is wrong version", path->getCString());
+    goto err2;
+  }
+  memcpy(vt, &xpdfPluginVecTable, sizeof(xpdfPluginVecTable));
+
+#ifdef WIN32
+  if (!(xpdfInitPlugin = (XpdfBool (*)(void))
+	                     GetProcAddress(libA, "xpdfInitPlugin"))) {
+    error(-1, "Failed to find xpdfInitPlugin in plugin '%s'",
+	  path->getCString());
+    goto err2;
+  }
+#else
+  if (!(xpdfInitPlugin = (XpdfBool (*)(void))dlsym(dlA, "xpdfInitPlugin"))) {
+    error(-1, "Failed to find xpdfInitPlugin in plugin '%s'",
+	  path->getCString());
+    goto err2;
+  }
+#endif
+
+  if (!(*xpdfInitPlugin)()) {
+    error(-1, "Initialization of plugin '%s' failed",
+	  path->getCString());
+    goto err2;
+  }
+
+#ifdef WIN32
+  plugin = new Plugin(libA);
+#else
+  plugin = new Plugin(dlA);
+#endif
+
+  delete path;
+  return plugin;
+
+ err2:
+#ifdef WIN32
+  FreeLibrary(libA);
+#else
+  dlclose(dlA);
+#endif
+ err1:
+  delete path;
+  return NULL;
+}
+
+#ifdef WIN32
+Plugin::Plugin(HMODULE libA) {
+  lib = libA;
+}
+#else
+Plugin::Plugin(void *dlA) {
+  dl = dlA;
+}
+#endif
+
+Plugin::~Plugin() {
+  void (*xpdfFreePlugin)(void);
+
+#ifdef WIN32
+  if ((xpdfFreePlugin = (void (*)(void))
+                            GetProcAddress(lib, "xpdfFreePlugin"))) {
+    (*xpdfFreePlugin)();
+  }
+  FreeLibrary(lib);
+#else
+  if ((xpdfFreePlugin = (void (*)(void))dlsym(dl, "xpdfFreePlugin"))) {
+    (*xpdfFreePlugin)();
+  }
+  dlclose(dl);
+#endif
+}
+
+#endif // ENABLE_PLUGINS
+
+//------------------------------------------------------------------------
+// parsing
+//------------------------------------------------------------------------
+
+GlobalParams::GlobalParams(char *cfgFileName) {
+  UnicodeMap *map;
+  GString *fileName;
+  FILE *f;
+  int i;
+
+#if MULTITHREADED
+  gInitMutex(&mutex);
+  gInitMutex(&unicodeMapCacheMutex);
+  gInitMutex(&cMapCacheMutex);
+#endif
+
+  initBuiltinFontTables();
+
+  // scan the encoding in reverse because we want the lowest-numbered
+  // index for each char name ('space' is encoded twice)
+  macRomanReverseMap = new NameToCharCode();
+  for (i = 255; i >= 0; --i) {
+    if (macRomanEncoding[i]) {
+      macRomanReverseMap->add(macRomanEncoding[i], (CharCode)i);
+    }
+  }
+
+#ifdef WIN32
+  // baseDir will be set by a call to setBaseDir
+  baseDir = new GString();
+#else
+  baseDir = appendToPath(getHomeDir(), ".xpdf");
+#endif
+  nameToUnicode = new NameToCharCode();
+  cidToUnicodes = new GHash(gTrue);
+  unicodeToUnicodes = new GHash(gTrue);
+  residentUnicodeMaps = new GHash();
+  unicodeMaps = new GHash(gTrue);
+  cMapDirs = new GHash(gTrue);
+  toUnicodeDirs = new GList();
+  displayFonts = new GHash();
+  displayCIDFonts = new GHash();
+  displayNamedCIDFonts = new GHash();
+#if HAVE_PAPER_H
+  char *paperName;
+  const struct paper *paperType;
+  paperinit();
+  if ((paperName = systempapername())) {
+    paperType = paperinfo(paperName);
+    psPaperWidth = (int)paperpswidth(paperType);
+    psPaperHeight = (int)paperpsheight(paperType);
+  } else {
+    error(-1, "No paper information available - using defaults");
+    psPaperWidth = defPaperWidth;
+    psPaperHeight = defPaperHeight;
+  }
+  paperdone();
+#else
+  psPaperWidth = defPaperWidth;
+  psPaperHeight = defPaperHeight;
+#endif
+  psImageableLLX = psImageableLLY = 0;
+  psImageableURX = psPaperWidth;
+  psImageableURY = psPaperHeight;
+  psCrop = gTrue;
+  psExpandSmaller = gFalse;
+  psShrinkLarger = gTrue;
+  psCenter = gTrue;
+  psDuplex = gFalse;
+  psLevel = psLevel2;
+  psFile = NULL;
+  psFonts = new GHash();
+  psNamedFonts16 = new GList();
+  psFonts16 = new GList();
+  psEmbedType1 = gTrue;
+  psEmbedTrueType = gTrue;
+  psEmbedCIDPostScript = gTrue;
+  psEmbedCIDTrueType = gTrue;
+  psPreload = gFalse;
+  psOPI = gFalse;
+  psASCIIHex = gFalse;
+  textEncoding = new GString("Latin1");
+#if defined(WIN32)
+  textEOL = eolDOS;
+#elif defined(MACOS)
+  textEOL = eolMac;
+#else
+  textEOL = eolUnix;
+#endif
+  textPageBreaks = gTrue;
+  textKeepTinyChars = gFalse;
+  fontDirs = new GList();
+  initialZoom = new GString("125");
+  continuousView = gFalse;
+  enableT1lib = gTrue;
+  enableFreeType = gTrue;
+  antialias = gTrue;
+  vectorAntialias = gTrue;
+  strokeAdjust = gTrue;
+  screenType = screenUnset;
+  screenSize = -1;
+  screenDotRadius = -1;
+  screenGamma = 1.0;
+  screenBlackThreshold = 0.0;
+  screenWhiteThreshold = 1.0;
+  urlCommand = NULL;
+  movieCommand = NULL;
+  mapNumericCharNames = gTrue;
+  mapUnknownCharNames = gFalse;
+  createDefaultKeyBindings();
+  printCommands = gFalse;
+  errQuiet = gFalse;
+
+  cidToUnicodeCache = new CharCodeToUnicodeCache(cidToUnicodeCacheSize);
+  unicodeToUnicodeCache =
+      new CharCodeToUnicodeCache(unicodeToUnicodeCacheSize);
+  unicodeMapCache = new UnicodeMapCache();
+  cMapCache = new CMapCache();
+
+#ifdef WIN32
+  winFontList = NULL;
+#endif
+
+#ifdef ENABLE_PLUGINS
+  plugins = new GList();
+  securityHandlers = new GList();
+#endif
+
+  // set up the initial nameToUnicode table
+  for (i = 0; nameToUnicodeTab[i].name; ++i) {
+    nameToUnicode->add(nameToUnicodeTab[i].name, nameToUnicodeTab[i].u);
+  }
+
+  // set up the residentUnicodeMaps table
+  map = new UnicodeMap("Latin1", gFalse,
+		       latin1UnicodeMapRanges, latin1UnicodeMapLen);
+  residentUnicodeMaps->add(map->getEncodingName(), map);
+  map = new UnicodeMap("ASCII7", gFalse,
+		       ascii7UnicodeMapRanges, ascii7UnicodeMapLen);
+  residentUnicodeMaps->add(map->getEncodingName(), map);
+  map = new UnicodeMap("Symbol", gFalse,
+		       symbolUnicodeMapRanges, symbolUnicodeMapLen);
+  residentUnicodeMaps->add(map->getEncodingName(), map);
+  map = new UnicodeMap("ZapfDingbats", gFalse, zapfDingbatsUnicodeMapRanges,
+		       zapfDingbatsUnicodeMapLen);
+  residentUnicodeMaps->add(map->getEncodingName(), map);
+  map = new UnicodeMap("UTF-8", gTrue, &mapUTF8);
+  residentUnicodeMaps->add(map->getEncodingName(), map);
+  map = new UnicodeMap("UCS-2", gTrue, &mapUCS2);
+  residentUnicodeMaps->add(map->getEncodingName(), map);
+
+  // look for a user config file, then a system-wide config file
+  f = NULL;
+  fileName = NULL;
+  if (cfgFileName && cfgFileName[0]) {
+    fileName = new GString(cfgFileName);
+    if (!(f = fopen(fileName->getCString(), "r"))) {
+      delete fileName;
+    }
+  }
+  if (!f) {
+    fileName = appendToPath(getHomeDir(), xpdfUserConfigFile);
+    if (!(f = fopen(fileName->getCString(), "r"))) {
+      delete fileName;
+    }
+  }
+  if (!f) {
+#if defined(WIN32) && !defined(__CYGWIN32__)
+    char buf[512];
+    i = GetModuleFileName(NULL, buf, sizeof(buf));
+    if (i <= 0 || i >= sizeof(buf)) {
+      // error or path too long for buffer - just use the current dir
+      buf[0] = '\0';
+    }
+    fileName = grabPath(buf);
+    appendToPath(fileName, xpdfSysConfigFile);
+#else
+    fileName = new GString(xpdfSysConfigFile);
+#endif
+    if (!(f = fopen(fileName->getCString(), "r"))) {
+      delete fileName;
+    }
+  }
+  if (f) {
+    parseFile(fileName, f);
+    delete fileName;
+    fclose(f);
+  }
+}
+
+void GlobalParams::createDefaultKeyBindings() {
+  keyBindings = new GList();
+
+  //----- mouse buttons
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress1, xpdfKeyModNone,
+				     xpdfKeyContextAny, "startSelection"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMouseRelease1, xpdfKeyModNone,
+				     xpdfKeyContextAny, "endSelection",
+				     "followLinkNoSel"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress2, xpdfKeyModNone,
+				     xpdfKeyContextAny, "startPan"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMouseRelease2, xpdfKeyModNone,
+				     xpdfKeyContextAny, "endPan"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress3, xpdfKeyModNone,
+				     xpdfKeyContextAny, "postPopupMenu"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress4, xpdfKeyModNone,
+				     xpdfKeyContextAny,
+				     "scrollUpPrevPage(16)"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress5, xpdfKeyModNone,
+				     xpdfKeyContextAny,
+				     "scrollDownNextPage(16)"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress6, xpdfKeyModNone,
+				     xpdfKeyContextAny, "scrollLeft(16)"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress7, xpdfKeyModNone,
+				     xpdfKeyContextAny, "scrollRight(16)"));
+
+  //----- keys
+  keyBindings->append(new KeyBinding(xpdfKeyCodeHome, xpdfKeyModCtrl,
+				     xpdfKeyContextAny, "gotoPage(1)"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeHome, xpdfKeyModNone,
+				     xpdfKeyContextAny, "scrollToTopLeft"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeEnd, xpdfKeyModCtrl,
+				     xpdfKeyContextAny, "gotoLastPage"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeEnd, xpdfKeyModNone,
+				     xpdfKeyContextAny,
+				     "scrollToBottomRight"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodePgUp, xpdfKeyModNone,
+				     xpdfKeyContextAny, "pageUp"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeBackspace, xpdfKeyModNone,
+				     xpdfKeyContextAny, "pageUp"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeDelete, xpdfKeyModNone,
+				     xpdfKeyContextAny, "pageUp"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodePgDn, xpdfKeyModNone,
+				     xpdfKeyContextAny, "pageDown"));
+  keyBindings->append(new KeyBinding(' ', xpdfKeyModNone,
+				     xpdfKeyContextAny, "pageDown"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeLeft, xpdfKeyModNone,
+				     xpdfKeyContextAny, "scrollLeft(16)"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeRight, xpdfKeyModNone,
+				     xpdfKeyContextAny, "scrollRight(16)"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeUp, xpdfKeyModNone,
+				     xpdfKeyContextAny, "scrollUp(16)"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeDown, xpdfKeyModNone,
+				     xpdfKeyContextAny, "scrollDown(16)"));
+  keyBindings->append(new KeyBinding('o', xpdfKeyModNone,
+				     xpdfKeyContextAny, "open"));
+  keyBindings->append(new KeyBinding('O', xpdfKeyModNone,
+				     xpdfKeyContextAny, "open"));
+  keyBindings->append(new KeyBinding('r', xpdfKeyModNone,
+				     xpdfKeyContextAny, "reload"));
+  keyBindings->append(new KeyBinding('R', xpdfKeyModNone,
+				     xpdfKeyContextAny, "reload"));
+  keyBindings->append(new KeyBinding('f', xpdfKeyModNone,
+				     xpdfKeyContextAny, "find"));
+  keyBindings->append(new KeyBinding('F', xpdfKeyModNone,
+				     xpdfKeyContextAny, "find"));
+  keyBindings->append(new KeyBinding('f', xpdfKeyModCtrl,
+				     xpdfKeyContextAny, "find"));
+  keyBindings->append(new KeyBinding('g', xpdfKeyModCtrl,
+				     xpdfKeyContextAny, "findNext"));
+  keyBindings->append(new KeyBinding('p', xpdfKeyModCtrl,
+				     xpdfKeyContextAny, "print"));
+  keyBindings->append(new KeyBinding('n', xpdfKeyModNone,
+				     xpdfKeyContextScrLockOff, "nextPage"));
+  keyBindings->append(new KeyBinding('N', xpdfKeyModNone,
+				     xpdfKeyContextScrLockOff, "nextPage"));
+  keyBindings->append(new KeyBinding('n', xpdfKeyModNone,
+				     xpdfKeyContextScrLockOn,
+				     "nextPageNoScroll"));
+  keyBindings->append(new KeyBinding('N', xpdfKeyModNone,
+				     xpdfKeyContextScrLockOn,
+				     "nextPageNoScroll"));
+  keyBindings->append(new KeyBinding('p', xpdfKeyModNone,
+				     xpdfKeyContextScrLockOff, "prevPage"));
+  keyBindings->append(new KeyBinding('P', xpdfKeyModNone,
+				     xpdfKeyContextScrLockOff, "prevPage"));
+  keyBindings->append(new KeyBinding('p', xpdfKeyModNone,
+				     xpdfKeyContextScrLockOn,
+				     "prevPageNoScroll"));
+  keyBindings->append(new KeyBinding('P', xpdfKeyModNone,
+				     xpdfKeyContextScrLockOn,
+				     "prevPageNoScroll"));
+  keyBindings->append(new KeyBinding('v', xpdfKeyModNone,
+				     xpdfKeyContextAny, "goForward"));
+  keyBindings->append(new KeyBinding('b', xpdfKeyModNone,
+				     xpdfKeyContextAny, "goBackward"));
+  keyBindings->append(new KeyBinding('g', xpdfKeyModNone,
+				     xpdfKeyContextAny, "focusToPageNum"));
+  keyBindings->append(new KeyBinding('0', xpdfKeyModNone,
+				     xpdfKeyContextAny, "zoomPercent(125)"));
+  keyBindings->append(new KeyBinding('+', xpdfKeyModNone,
+				     xpdfKeyContextAny, "zoomIn"));
+  keyBindings->append(new KeyBinding('-', xpdfKeyModNone,
+				     xpdfKeyContextAny, "zoomOut"));
+  keyBindings->append(new KeyBinding('z', xpdfKeyModNone,
+				     xpdfKeyContextAny, "zoomFitPage"));
+  keyBindings->append(new KeyBinding('w', xpdfKeyModNone,
+				     xpdfKeyContextAny, "zoomFitWidth"));
+  keyBindings->append(new KeyBinding('f', xpdfKeyModAlt,
+				     xpdfKeyContextAny,
+				     "toggleFullScreenMode"));
+  keyBindings->append(new KeyBinding('l', xpdfKeyModCtrl,
+				     xpdfKeyContextAny, "redraw"));
+  keyBindings->append(new KeyBinding('w', xpdfKeyModCtrl,
+				     xpdfKeyContextAny, "closeWindow"));
+  keyBindings->append(new KeyBinding('?', xpdfKeyModNone,
+				     xpdfKeyContextAny, "about"));
+  keyBindings->append(new KeyBinding('q', xpdfKeyModNone,
+				     xpdfKeyContextAny, "quit"));
+  keyBindings->append(new KeyBinding('Q', xpdfKeyModNone,
+				     xpdfKeyContextAny, "quit"));
+}
+
+void GlobalParams::parseFile(GString *fileName, FILE *f) {
+  int line;
+  char buf[512];
+
+  line = 1;
+  while (getLine(buf, sizeof(buf) - 1, f)) {
+    parseLine(buf, fileName, line);
+    ++line;
+  }
+}
+
+void GlobalParams::parseLine(char *buf, GString *fileName, int line) {
+  GList *tokens;
+  GString *cmd, *incFile;
+  char *p1, *p2;
+  FILE *f2;
+
+  // break the line into tokens
+  tokens = new GList();
+  p1 = buf;
+  while (*p1) {
+    for (; *p1 && isspace(*p1); ++p1) ;
+    if (!*p1) {
+      break;
+    }
+    if (*p1 == '"' || *p1 == '\'') {
+      for (p2 = p1 + 1; *p2 && *p2 != *p1; ++p2) ;
+      ++p1;
+    } else {
+      for (p2 = p1 + 1; *p2 && !isspace(*p2); ++p2) ;
+    }
+    tokens->append(new GString(p1, p2 - p1));
+    p1 = *p2 ? p2 + 1 : p2;
+  }
+
+  // parse the line
+  if (tokens->getLength() > 0 &&
+      ((GString *)tokens->get(0))->getChar(0) != '#') {
+    cmd = (GString *)tokens->get(0);
+    if (!cmd->cmp("include")) {
+      if (tokens->getLength() == 2) {
+	incFile = (GString *)tokens->get(1);
+	if ((f2 = fopen(incFile->getCString(), "r"))) {
+	  parseFile(incFile, f2);
+	  fclose(f2);
+	} else {
+	  error(-1, "Couldn't find included config file: '%s' (%s:%d)",
+		incFile->getCString(), fileName->getCString(), line);
+	}
+      } else {
+	error(-1, "Bad 'include' config file command (%s:%d)",
+	      fileName->getCString(), line);
+      }
+    } else if (!cmd->cmp("nameToUnicode")) {
+      parseNameToUnicode(tokens, fileName, line);
+    } else if (!cmd->cmp("cidToUnicode")) {
+      parseCIDToUnicode(tokens, fileName, line);
+    } else if (!cmd->cmp("unicodeToUnicode")) {
+      parseUnicodeToUnicode(tokens, fileName, line);
+    } else if (!cmd->cmp("unicodeMap")) {
+      parseUnicodeMap(tokens, fileName, line);
+    } else if (!cmd->cmp("cMapDir")) {
+      parseCMapDir(tokens, fileName, line);
+    } else if (!cmd->cmp("toUnicodeDir")) {
+      parseToUnicodeDir(tokens, fileName, line);
+    } else if (!cmd->cmp("displayFontT1")) {
+      parseDisplayFont(tokens, displayFonts, displayFontT1, fileName, line);
+    } else if (!cmd->cmp("displayFontTT")) {
+      parseDisplayFont(tokens, displayFonts, displayFontTT, fileName, line);
+    } else if (!cmd->cmp("displayNamedCIDFontT1")) {
+      parseDisplayFont(tokens, displayNamedCIDFonts,
+		       displayFontT1, fileName, line);
+    } else if (!cmd->cmp("displayCIDFontT1")) {
+      parseDisplayFont(tokens, displayCIDFonts,
+		       displayFontT1, fileName, line);
+    } else if (!cmd->cmp("displayNamedCIDFontTT")) {
+      parseDisplayFont(tokens, displayNamedCIDFonts,
+		       displayFontTT, fileName, line);
+    } else if (!cmd->cmp("displayCIDFontTT")) {
+      parseDisplayFont(tokens, displayCIDFonts,
+		       displayFontTT, fileName, line);
+    } else if (!cmd->cmp("psFile")) {
+      parsePSFile(tokens, fileName, line);
+    } else if (!cmd->cmp("psFont")) {
+      parsePSFont(tokens, fileName, line);
+    } else if (!cmd->cmp("psNamedFont16")) {
+      parsePSFont16("psNamedFont16", psNamedFonts16,
+		    tokens, fileName, line);
+    } else if (!cmd->cmp("psFont16")) {
+      parsePSFont16("psFont16", psFonts16, tokens, fileName, line);
+    } else if (!cmd->cmp("psPaperSize")) {
+      parsePSPaperSize(tokens, fileName, line);
+    } else if (!cmd->cmp("psImageableArea")) {
+      parsePSImageableArea(tokens, fileName, line);
+    } else if (!cmd->cmp("psCrop")) {
+      parseYesNo("psCrop", &psCrop, tokens, fileName, line);
+    } else if (!cmd->cmp("psExpandSmaller")) {
+      parseYesNo("psExpandSmaller", &psExpandSmaller,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("psShrinkLarger")) {
+      parseYesNo("psShrinkLarger", &psShrinkLarger, tokens, fileName, line);
+    } else if (!cmd->cmp("psCenter")) {
+      parseYesNo("psCenter", &psCenter, tokens, fileName, line);
+    } else if (!cmd->cmp("psDuplex")) {
+      parseYesNo("psDuplex", &psDuplex, tokens, fileName, line);
+    } else if (!cmd->cmp("psLevel")) {
+      parsePSLevel(tokens, fileName, line);
+    } else if (!cmd->cmp("psEmbedType1Fonts")) {
+      parseYesNo("psEmbedType1", &psEmbedType1, tokens, fileName, line);
+    } else if (!cmd->cmp("psEmbedTrueTypeFonts")) {
+      parseYesNo("psEmbedTrueType", &psEmbedTrueType,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("psEmbedCIDPostScriptFonts")) {
+      parseYesNo("psEmbedCIDPostScript", &psEmbedCIDPostScript,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("psEmbedCIDTrueTypeFonts")) {
+      parseYesNo("psEmbedCIDTrueType", &psEmbedCIDTrueType,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("psPreload")) {
+      parseYesNo("psPreload", &psPreload, tokens, fileName, line);
+    } else if (!cmd->cmp("psOPI")) {
+      parseYesNo("psOPI", &psOPI, tokens, fileName, line);
+    } else if (!cmd->cmp("psASCIIHex")) {
+      parseYesNo("psASCIIHex", &psASCIIHex, tokens, fileName, line);
+    } else if (!cmd->cmp("textEncoding")) {
+      parseTextEncoding(tokens, fileName, line);
+    } else if (!cmd->cmp("textEOL")) {
+      parseTextEOL(tokens, fileName, line);
+    } else if (!cmd->cmp("textPageBreaks")) {
+      parseYesNo("textPageBreaks", &textPageBreaks,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("textKeepTinyChars")) {
+      parseYesNo("textKeepTinyChars", &textKeepTinyChars,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("fontDir")) {
+      parseFontDir(tokens, fileName, line);
+    } else if (!cmd->cmp("initialZoom")) {
+      parseInitialZoom(tokens, fileName, line);
+    } else if (!cmd->cmp("continuousView")) {
+      parseYesNo("continuousView", &continuousView, tokens, fileName, line);
+    } else if (!cmd->cmp("enableT1lib")) {
+      parseYesNo("enableT1lib", &enableT1lib, tokens, fileName, line);
+    } else if (!cmd->cmp("enableFreeType")) {
+      parseYesNo("enableFreeType", &enableFreeType, tokens, fileName, line);
+    } else if (!cmd->cmp("antialias")) {
+      parseYesNo("antialias", &antialias, tokens, fileName, line);
+    } else if (!cmd->cmp("vectorAntialias")) {
+      parseYesNo("vectorAntialias", &vectorAntialias,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("strokeAdjust")) {
+      parseYesNo("strokeAdjust", &strokeAdjust, tokens, fileName, line);
+    } else if (!cmd->cmp("screenType")) {
+      parseScreenType(tokens, fileName, line);
+    } else if (!cmd->cmp("screenSize")) {
+      parseInteger("screenSize", &screenSize, tokens, fileName, line);
+    } else if (!cmd->cmp("screenDotRadius")) {
+      parseInteger("screenDotRadius", &screenDotRadius,
+		   tokens, fileName, line);
+    } else if (!cmd->cmp("screenGamma")) {
+      parseFloat("screenGamma", &screenGamma,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("screenBlackThreshold")) {
+      parseFloat("screenBlackThreshold", &screenBlackThreshold,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("screenWhiteThreshold")) {
+      parseFloat("screenWhiteThreshold", &screenWhiteThreshold,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("urlCommand")) {
+      parseCommand("urlCommand", &urlCommand, tokens, fileName, line);
+    } else if (!cmd->cmp("movieCommand")) {
+      parseCommand("movieCommand", &movieCommand, tokens, fileName, line);
+    } else if (!cmd->cmp("mapNumericCharNames")) {
+      parseYesNo("mapNumericCharNames", &mapNumericCharNames,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("mapUnknownCharNames")) {
+      parseYesNo("mapUnknownCharNames", &mapUnknownCharNames,
+		 tokens, fileName, line);
+    } else if (!cmd->cmp("bind")) {
+      parseBind(tokens, fileName, line);
+    } else if (!cmd->cmp("unbind")) {
+      parseUnbind(tokens, fileName, line);
+    } else if (!cmd->cmp("printCommands")) {
+      parseYesNo("printCommands", &printCommands, tokens, fileName, line);
+    } else if (!cmd->cmp("errQuiet")) {
+      parseYesNo("errQuiet", &errQuiet, tokens, fileName, line);
+    } else {
+      error(-1, "Unknown config file command '%s' (%s:%d)",
+	    cmd->getCString(), fileName->getCString(), line);
+      if (!cmd->cmp("displayFontX") ||
+	  !cmd->cmp("displayNamedCIDFontX") ||
+	  !cmd->cmp("displayCIDFontX")) {
+	error(-1, "-- Xpdf no longer supports X fonts");
+      } else if (!cmd->cmp("t1libControl") || !cmd->cmp("freetypeControl")) {
+	error(-1, "-- The t1libControl and freetypeControl options have been replaced");
+	error(-1, "   by the enableT1lib, enableFreeType, and antialias options");
+      } else if (!cmd->cmp("fontpath") || !cmd->cmp("fontmap")) {
+	error(-1, "-- the config file format has changed since Xpdf 0.9x");
+      }
+    }
+  }
+
+  deleteGList(tokens, GString);
+}
+
+void GlobalParams::parseNameToUnicode(GList *tokens, GString *fileName,
+					 int line) {
+  GString *name;
+  char *tok1, *tok2;
+  FILE *f;
+  char buf[256];
+  int line2;
+  Unicode u;
+
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad 'nameToUnicode' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  name = (GString *)tokens->get(1);
+  if (!(f = fopen(name->getCString(), "r"))) {
+    error(-1, "Couldn't open 'nameToUnicode' file '%s'",
+	  name->getCString());
+    return;
+  }
+  line2 = 1;
+  while (getLine(buf, sizeof(buf), f)) {
+    tok1 = strtok(buf, " \t\r\n");
+    tok2 = strtok(NULL, " \t\r\n");
+    if (tok1 && tok2) {
+      sscanf(tok1, "%x", &u);
+      nameToUnicode->add(tok2, u);
+    } else {
+      error(-1, "Bad line in 'nameToUnicode' file (%s:%d)", name, line2);
+    }
+    ++line2;
+  }
+  fclose(f);
+}
+
+void GlobalParams::parseCIDToUnicode(GList *tokens, GString *fileName,
+				     int line) {
+  GString *collection, *name, *old;
+
+  if (tokens->getLength() != 3) {
+    error(-1, "Bad 'cidToUnicode' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  collection = (GString *)tokens->get(1);
+  name = (GString *)tokens->get(2);
+  if ((old = (GString *)cidToUnicodes->remove(collection))) {
+    delete old;
+  }
+  cidToUnicodes->add(collection->copy(), name->copy());
+}
+
+void GlobalParams::parseUnicodeToUnicode(GList *tokens, GString *fileName,
+					 int line) {
+  GString *font, *file, *old;
+
+  if (tokens->getLength() != 3) {
+    error(-1, "Bad 'unicodeToUnicode' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  font = (GString *)tokens->get(1);
+  file = (GString *)tokens->get(2);
+  if ((old = (GString *)unicodeToUnicodes->remove(font))) {
+    delete old;
+  }
+  unicodeToUnicodes->add(font->copy(), file->copy());
+}
+
+void GlobalParams::parseUnicodeMap(GList *tokens, GString *fileName,
+				   int line) {
+  GString *encodingName, *name, *old;
+
+  if (tokens->getLength() != 3) {
+    error(-1, "Bad 'unicodeMap' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  encodingName = (GString *)tokens->get(1);
+  name = (GString *)tokens->get(2);
+  if ((old = (GString *)unicodeMaps->remove(encodingName))) {
+    delete old;
+  }
+  unicodeMaps->add(encodingName->copy(), name->copy());
+}
+
+void GlobalParams::parseCMapDir(GList *tokens, GString *fileName, int line) {
+  GString *collection, *dir;
+  GList *list;
+
+  if (tokens->getLength() != 3) {
+    error(-1, "Bad 'cMapDir' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  collection = (GString *)tokens->get(1);
+  dir = (GString *)tokens->get(2);
+  if (!(list = (GList *)cMapDirs->lookup(collection))) {
+    list = new GList();
+    cMapDirs->add(collection->copy(), list);
+  }
+  list->append(dir->copy());
+}
+
+void GlobalParams::parseToUnicodeDir(GList *tokens, GString *fileName,
+				     int line) {
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad 'toUnicodeDir' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  toUnicodeDirs->append(((GString *)tokens->get(1))->copy());
+}
+
+void GlobalParams::parseDisplayFont(GList *tokens, GHash *fontHash,
+				    DisplayFontParamKind kind,
+				    GString *fileName, int line) {
+  DisplayFontParam *param, *old;
+
+  if (tokens->getLength() < 2) {
+    goto err1;
+  }
+  param = new DisplayFontParam(((GString *)tokens->get(1))->copy(), kind);
+  
+  switch (kind) {
+  case displayFontT1:
+    if (tokens->getLength() != 3) {
+      goto err2;
+    }
+    param->t1.fileName = ((GString *)tokens->get(2))->copy();
+    break;
+  case displayFontTT:
+    if (tokens->getLength() != 3) {
+      goto err2;
+    }
+    param->tt.fileName = ((GString *)tokens->get(2))->copy();
+    break;
+  }
+
+  if ((old = (DisplayFontParam *)fontHash->remove(param->name))) {
+    delete old;
+  }
+  fontHash->add(param->name, param);
+  return;
+
+ err2:
+  delete param;
+ err1:
+  error(-1, "Bad 'display*Font*' config file command (%s:%d)",
+	fileName->getCString(), line);
+}
+
+void GlobalParams::parsePSPaperSize(GList *tokens, GString *fileName,
+				    int line) {
+  GString *tok;
+
+  if (tokens->getLength() == 2) {
+    tok = (GString *)tokens->get(1);
+    if (!setPSPaperSize(tok->getCString())) {
+      error(-1, "Bad 'psPaperSize' config file command (%s:%d)",
+	    fileName->getCString(), line);
+    }
+  } else if (tokens->getLength() == 3) {
+    tok = (GString *)tokens->get(1);
+    psPaperWidth = atoi(tok->getCString());
+    tok = (GString *)tokens->get(2);
+    psPaperHeight = atoi(tok->getCString());
+    psImageableLLX = psImageableLLY = 0;
+    psImageableURX = psPaperWidth;
+    psImageableURY = psPaperHeight;
+  } else {
+    error(-1, "Bad 'psPaperSize' config file command (%s:%d)",
+	  fileName->getCString(), line);
+  }
+}
+
+void GlobalParams::parsePSImageableArea(GList *tokens, GString *fileName,
+					int line) {
+  if (tokens->getLength() != 5) {
+    error(-1, "Bad 'psImageableArea' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  psImageableLLX = atoi(((GString *)tokens->get(1))->getCString());
+  psImageableLLY = atoi(((GString *)tokens->get(2))->getCString());
+  psImageableURX = atoi(((GString *)tokens->get(3))->getCString());
+  psImageableURY = atoi(((GString *)tokens->get(4))->getCString());
+}
+
+void GlobalParams::parsePSLevel(GList *tokens, GString *fileName, int line) {
+  GString *tok;
+
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad 'psLevel' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  tok = (GString *)tokens->get(1);
+  if (!tok->cmp("level1")) {
+    psLevel = psLevel1;
+  } else if (!tok->cmp("level1sep")) {
+    psLevel = psLevel1Sep;
+  } else if (!tok->cmp("level2")) {
+    psLevel = psLevel2;
+  } else if (!tok->cmp("level2sep")) {
+    psLevel = psLevel2Sep;
+  } else if (!tok->cmp("level3")) {
+    psLevel = psLevel3;
+  } else if (!tok->cmp("level3Sep")) {
+    psLevel = psLevel3Sep;
+  } else {
+    error(-1, "Bad 'psLevel' config file command (%s:%d)",
+	  fileName->getCString(), line);
+  }
+}
+
+void GlobalParams::parsePSFile(GList *tokens, GString *fileName, int line) {
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad 'psFile' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  if (psFile) {
+    delete psFile;
+  }
+  psFile = ((GString *)tokens->get(1))->copy();
+}
+
+void GlobalParams::parsePSFont(GList *tokens, GString *fileName, int line) {
+  PSFontParam *param;
+
+  if (tokens->getLength() != 3) {
+    error(-1, "Bad 'psFont' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  param = new PSFontParam(((GString *)tokens->get(1))->copy(), 0,
+			  ((GString *)tokens->get(2))->copy(), NULL);
+  psFonts->add(param->pdfFontName, param);
+}
+
+void GlobalParams::parsePSFont16(char *cmdName, GList *fontList,
+				 GList *tokens, GString *fileName, int line) {
+  PSFontParam *param;
+  int wMode;
+  GString *tok;
+
+  if (tokens->getLength() != 5) {
+    error(-1, "Bad '%s' config file command (%s:%d)",
+	  cmdName, fileName->getCString(), line);
+    return;
+  }
+  tok = (GString *)tokens->get(2);
+  if (!tok->cmp("H")) {
+    wMode = 0;
+  } else if (!tok->cmp("V")) {
+    wMode = 1;
+  } else {
+    error(-1, "Bad '%s' config file command (%s:%d)",
+	  cmdName, fileName->getCString(), line);
+    return;
+  }
+  param = new PSFontParam(((GString *)tokens->get(1))->copy(),
+			  wMode,
+			  ((GString *)tokens->get(3))->copy(),
+			  ((GString *)tokens->get(4))->copy());
+  fontList->append(param);
+}
+
+void GlobalParams::parseTextEncoding(GList *tokens, GString *fileName,
+				     int line) {
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad 'textEncoding' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  delete textEncoding;
+  textEncoding = ((GString *)tokens->get(1))->copy();
+}
+
+void GlobalParams::parseTextEOL(GList *tokens, GString *fileName, int line) {
+  GString *tok;
+
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad 'textEOL' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  tok = (GString *)tokens->get(1);
+  if (!tok->cmp("unix")) {
+    textEOL = eolUnix;
+  } else if (!tok->cmp("dos")) {
+    textEOL = eolDOS;
+  } else if (!tok->cmp("mac")) {
+    textEOL = eolMac;
+  } else {
+    error(-1, "Bad 'textEOL' config file command (%s:%d)",
+	  fileName->getCString(), line);
+  }
+}
+
+void GlobalParams::parseFontDir(GList *tokens, GString *fileName, int line) {
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad 'fontDir' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  fontDirs->append(((GString *)tokens->get(1))->copy());
+}
+
+void GlobalParams::parseInitialZoom(GList *tokens,
+				    GString *fileName, int line) {
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad 'initialZoom' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  delete initialZoom;
+  initialZoom = ((GString *)tokens->get(1))->copy();
+}
+
+void GlobalParams::parseScreenType(GList *tokens, GString *fileName,
+				   int line) {
+  GString *tok;
+
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad 'screenType' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  tok = (GString *)tokens->get(1);
+  if (!tok->cmp("dispersed")) {
+    screenType = screenDispersed;
+  } else if (!tok->cmp("clustered")) {
+    screenType = screenClustered;
+  } else if (!tok->cmp("stochasticClustered")) {
+    screenType = screenStochasticClustered;
+  } else {
+    error(-1, "Bad 'screenType' config file command (%s:%d)",
+	  fileName->getCString(), line);
+  }
+}
+
+void GlobalParams::parseBind(GList *tokens, GString *fileName, int line) {
+  KeyBinding *binding;
+  GList *cmds;
+  int code, mods, context, i;
+
+  if (tokens->getLength() < 4) {
+    error(-1, "Bad 'bind' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  if (!parseKey((GString *)tokens->get(1), (GString *)tokens->get(2),
+		&code, &mods, &context,
+		"bind", tokens, fileName, line)) {
+    return;
+  }
+  for (i = 0; i < keyBindings->getLength(); ++i) {
+    binding = (KeyBinding *)keyBindings->get(i);
+    if (binding->code == code &&
+	binding->mods == mods &&
+	binding->context == context) {
+      delete (KeyBinding *)keyBindings->del(i);
+      break;
+    }
+  }
+  cmds = new GList();
+  for (i = 3; i < tokens->getLength(); ++i) {
+    cmds->append(((GString *)tokens->get(i))->copy());
+  }
+  keyBindings->append(new KeyBinding(code, mods, context, cmds));
+}
+
+void GlobalParams::parseUnbind(GList *tokens, GString *fileName, int line) {
+  KeyBinding *binding;
+  int code, mods, context, i;
+
+  if (tokens->getLength() != 3) {
+    error(-1, "Bad 'unbind' config file command (%s:%d)",
+	  fileName->getCString(), line);
+    return;
+  }
+  if (!parseKey((GString *)tokens->get(1), (GString *)tokens->get(2),
+		&code, &mods, &context,
+		"unbind", tokens, fileName, line)) {
+    return;
+  }
+  for (i = 0; i < keyBindings->getLength(); ++i) {
+    binding = (KeyBinding *)keyBindings->get(i);
+    if (binding->code == code &&
+	binding->mods == mods &&
+	binding->context == context) {
+      delete (KeyBinding *)keyBindings->del(i);
+      break;
+    }
+  }
+}
+
+GBool GlobalParams::parseKey(GString *modKeyStr, GString *contextStr,
+			     int *code, int *mods, int *context,
+			     char *cmdName,
+			     GList *tokens, GString *fileName, int line) {
+  char *p0;
+
+  *mods = xpdfKeyModNone;
+  p0 = modKeyStr->getCString();
+  while (1) {
+    if (!strncmp(p0, "shift-", 6)) {
+      *mods |= xpdfKeyModShift;
+      p0 += 6;
+    } else if (!strncmp(p0, "ctrl-", 5)) {
+      *mods |= xpdfKeyModCtrl;
+      p0 += 5;
+    } else if (!strncmp(p0, "alt-", 4)) {
+      *mods |= xpdfKeyModAlt;
+      p0 += 4;
+    } else {
+      break;
+    }
+  }
+
+  if (!strcmp(p0, "space")) {
+    *code = ' ';
+  } else if (!strcmp(p0, "tab")) {
+    *code = xpdfKeyCodeTab;
+  } else if (!strcmp(p0, "return")) {
+    *code = xpdfKeyCodeReturn;
+  } else if (!strcmp(p0, "enter")) {
+    *code = xpdfKeyCodeEnter;
+  } else if (!strcmp(p0, "backspace")) {
+    *code = xpdfKeyCodeBackspace;
+  } else if (!strcmp(p0, "insert")) {
+    *code = xpdfKeyCodeInsert;
+  } else if (!strcmp(p0, "delete")) {
+    *code = xpdfKeyCodeDelete;
+  } else if (!strcmp(p0, "home")) {
+    *code = xpdfKeyCodeHome;
+  } else if (!strcmp(p0, "end")) {
+    *code = xpdfKeyCodeEnd;
+  } else if (!strcmp(p0, "pgup")) {
+    *code = xpdfKeyCodePgUp;
+  } else if (!strcmp(p0, "pgdn")) {
+    *code = xpdfKeyCodePgDn;
+  } else if (!strcmp(p0, "left")) {
+    *code = xpdfKeyCodeLeft;
+  } else if (!strcmp(p0, "right")) {
+    *code = xpdfKeyCodeRight;
+  } else if (!strcmp(p0, "up")) {
+    *code = xpdfKeyCodeUp;
+  } else if (!strcmp(p0, "down")) {
+    *code = xpdfKeyCodeDown;
+  } else if (p0[0] == 'f' && p0[1] >= '1' && p0[1] <= '9' && !p0[2]) {
+    *code = xpdfKeyCodeF1 + (p0[1] - '1');
+  } else if (p0[0] == 'f' &&
+	     ((p0[1] >= '1' && p0[1] <= '2' && p0[2] >= '0' && p0[2] <= '9') ||
+	      (p0[1] == '3' && p0[2] >= '0' && p0[2] <= '5')) &&
+	     !p0[3]) {
+    *code = xpdfKeyCodeF1 + 10 * (p0[1] - '0') + (p0[2] - '0') - 1;
+  } else if (!strncmp(p0, "mousePress", 10) &&
+	     p0[10] >= '1' && p0[10] <= '7' && !p0[11]) {
+    *code = xpdfKeyCodeMousePress1 + (p0[10] - '1');
+  } else if (!strncmp(p0, "mouseRelease", 12) &&
+	     p0[12] >= '1' && p0[12] <= '7' && !p0[13]) {
+    *code = xpdfKeyCodeMouseRelease1 + (p0[12] - '1');
+  } else if (*p0 >= 0x20 && *p0 <= 0x7e && !p0[1]) {
+    *code = (int)*p0;
+  } else {
+    error(-1, "Bad key/modifier in '%s' config file command (%s:%d)",
+	  cmdName, fileName->getCString(), line);
+    return gFalse;
+  }
+
+  p0 = contextStr->getCString();
+  if (!strcmp(p0, "any")) {
+    *context = xpdfKeyContextAny;
+  } else {
+    *context = xpdfKeyContextAny;
+    while (1) {
+      if (!strncmp(p0, "fullScreen", 10)) {
+	*context |= xpdfKeyContextFullScreen;
+	p0 += 10;
+      } else if (!strncmp(p0, "window", 6)) {
+	*context |= xpdfKeyContextWindow;
+	p0 += 6;
+      } else if (!strncmp(p0, "continuous", 10)) {
+	*context |= xpdfKeyContextContinuous;
+	p0 += 10;
+      } else if (!strncmp(p0, "singlePage", 10)) {
+	*context |= xpdfKeyContextSinglePage;
+	p0 += 10;
+      } else if (!strncmp(p0, "overLink", 8)) {
+	*context |= xpdfKeyContextOverLink;
+	p0 += 8;
+      } else if (!strncmp(p0, "offLink", 7)) {
+	*context |= xpdfKeyContextOffLink;
+	p0 += 7;
+      } else if (!strncmp(p0, "outline", 7)) {
+	*context |= xpdfKeyContextOutline;
+	p0 += 7;
+      } else if (!strncmp(p0, "mainWin", 7)) {
+	*context |= xpdfKeyContextMainWin;
+	p0 += 7;
+      } else if (!strncmp(p0, "scrLockOn", 9)) {
+	*context |= xpdfKeyContextScrLockOn;
+	p0 += 9;
+      } else if (!strncmp(p0, "scrLockOff", 10)) {
+	*context |= xpdfKeyContextScrLockOff;
+	p0 += 10;
+      } else {
+	error(-1, "Bad context in '%s' config file command (%s:%d)",
+	      cmdName, fileName->getCString(), line);
+	return gFalse;
+      }
+      if (!*p0) {
+	break;
+      }
+      if (*p0 != ',') {
+	error(-1, "Bad context in '%s' config file command (%s:%d)",
+	      cmdName, fileName->getCString(), line);
+	return gFalse;
+      }
+      ++p0;
+    }
+  }
+
+  return gTrue;
+}
+
+void GlobalParams::parseCommand(char *cmdName, GString **val,
+				GList *tokens, GString *fileName, int line) {
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad '%s' config file command (%s:%d)",
+	  cmdName, fileName->getCString(), line);
+    return;
+  }
+  if (*val) {
+    delete *val;
+  }
+  *val = ((GString *)tokens->get(1))->copy();
+}
+
+void GlobalParams::parseYesNo(char *cmdName, GBool *flag,
+			      GList *tokens, GString *fileName, int line) {
+  GString *tok;
+
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad '%s' config file command (%s:%d)",
+	  cmdName, fileName->getCString(), line);
+    return;
+  }
+  tok = (GString *)tokens->get(1);
+  if (!parseYesNo2(tok->getCString(), flag)) {
+    error(-1, "Bad '%s' config file command (%s:%d)",
+	  cmdName, fileName->getCString(), line);
+  }
+}
+
+GBool GlobalParams::parseYesNo2(char *token, GBool *flag) {
+  if (!strcmp(token, "yes")) {
+    *flag = gTrue;
+  } else if (!strcmp(token, "no")) {
+    *flag = gFalse;
+  } else {
+    return gFalse;
+  }
+  return gTrue;
+}
+
+void GlobalParams::parseInteger(char *cmdName, int *val,
+				GList *tokens, GString *fileName, int line) {
+  GString *tok;
+  int i;
+
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad '%s' config file command (%s:%d)",
+	  cmdName, fileName->getCString(), line);
+    return;
+  }
+  tok = (GString *)tokens->get(1);
+  if (tok->getLength() == 0) {
+    error(-1, "Bad '%s' config file command (%s:%d)",
+	  cmdName, fileName->getCString(), line);
+    return;
+  }
+  if (tok->getChar(0) == '-') {
+    i = 1;
+  } else {
+    i = 0;
+  }
+  for (; i < tok->getLength(); ++i) {
+    if (tok->getChar(i) < '0' || tok->getChar(i) > '9') {
+      error(-1, "Bad '%s' config file command (%s:%d)",
+	    cmdName, fileName->getCString(), line);
+      return;
+    }
+  }
+  *val = atoi(tok->getCString());
+}
+
+void GlobalParams::parseFloat(char *cmdName, double *val,
+			      GList *tokens, GString *fileName, int line) {
+  GString *tok;
+  int i;
+
+  if (tokens->getLength() != 2) {
+    error(-1, "Bad '%s' config file command (%s:%d)",
+	  cmdName, fileName->getCString(), line);
+    return;
+  }
+  tok = (GString *)tokens->get(1);
+  if (tok->getLength() == 0) {
+    error(-1, "Bad '%s' config file command (%s:%d)",
+	  cmdName, fileName->getCString(), line);
+    return;
+  }
+  if (tok->getChar(0) == '-') {
+    i = 1;
+  } else {
+    i = 0;
+  }
+  for (; i < tok->getLength(); ++i) {
+    if (!((tok->getChar(i) >= '0' && tok->getChar(i) <= '9') ||
+	  tok->getChar(i) == '.')) {
+      error(-1, "Bad '%s' config file command (%s:%d)",
+	    cmdName, fileName->getCString(), line);
+      return;
+    }
+  }
+  *val = atof(tok->getCString());
+}
+
+GlobalParams::~GlobalParams() {
+  GHashIter *iter;
+  GString *key;
+  GList *list;
+
+  freeBuiltinFontTables();
+
+  delete macRomanReverseMap;
+
+  delete baseDir;
+  delete nameToUnicode;
+  deleteGHash(cidToUnicodes, GString);
+  deleteGHash(unicodeToUnicodes, GString);
+  deleteGHash(residentUnicodeMaps, UnicodeMap);
+  deleteGHash(unicodeMaps, GString);
+  deleteGList(toUnicodeDirs, GString);
+  deleteGHash(displayFonts, DisplayFontParam);
+  deleteGHash(displayCIDFonts, DisplayFontParam);
+  deleteGHash(displayNamedCIDFonts, DisplayFontParam);
+#ifdef WIN32
+  if (winFontList) {
+    delete winFontList;
+  }
+#endif
+  if (psFile) {
+    delete psFile;
+  }
+  deleteGHash(psFonts, PSFontParam);
+  deleteGList(psNamedFonts16, PSFontParam);
+  deleteGList(psFonts16, PSFontParam);
+  delete textEncoding;
+  deleteGList(fontDirs, GString);
+  delete initialZoom;
+  if (urlCommand) {
+    delete urlCommand;
+  }
+  if (movieCommand) {
+    delete movieCommand;
+  }
+  deleteGList(keyBindings, KeyBinding);
+
+  cMapDirs->startIter(&iter);
+  while (cMapDirs->getNext(&iter, &key, (void **)&list)) {
+    deleteGList(list, GString);
+  }
+  delete cMapDirs;
+
+  delete cidToUnicodeCache;
+  delete unicodeToUnicodeCache;
+  delete unicodeMapCache;
+  delete cMapCache;
+
+#ifdef ENABLE_PLUGINS
+  delete securityHandlers;
+  deleteGList(plugins, Plugin);
+#endif
+
+#if MULTITHREADED
+  gDestroyMutex(&mutex);
+  gDestroyMutex(&unicodeMapCacheMutex);
+  gDestroyMutex(&cMapCacheMutex);
+#endif
+}
+
+//------------------------------------------------------------------------
+
+void GlobalParams::setBaseDir(char *dir) {
+  delete baseDir;
+  baseDir = new GString(dir);
+}
+
+void GlobalParams::setupBaseFonts(char *dir) {
+  GString *fontName;
+  GString *fileName;
+#ifdef WIN32
+  HMODULE shell32Lib;
+  BOOL (__stdcall *SHGetSpecialFolderPathFunc)(HWND hwndOwner,
+					       LPTSTR lpszPath,
+					       int nFolder,
+					       BOOL fCreate);
+  char winFontDir[MAX_PATH];
+#endif
+  FILE *f;
+  DisplayFontParamKind kind;
+  DisplayFontParam *dfp;
+  int i, j;
+
+#ifdef WIN32
+  // SHGetSpecialFolderPath isn't available in older versions of
+  // shell32.dll (Win95 and WinNT4), so do a dynamic load
+  winFontDir[0] = '\0';
+  if ((shell32Lib = LoadLibrary("shell32.dll"))) {
+    if ((SHGetSpecialFolderPathFunc = 
+	 (BOOL (__stdcall *)(HWND hwndOwner, LPTSTR lpszPath,
+			     int nFolder, BOOL fCreate))
+	 GetProcAddress(shell32Lib, "SHGetSpecialFolderPathA"))) {
+      if (!(*SHGetSpecialFolderPathFunc)(NULL, winFontDir,
+					 CSIDL_FONTS, FALSE)) {
+	winFontDir[0] = '\0';
+      }
+    }
+  }
+#endif
+  for (i = 0; displayFontTab[i].name; ++i) {
+    fontName = new GString(displayFontTab[i].name);
+    if (getDisplayFont(fontName)) {
+      delete fontName;
+      continue;
+    }
+    fileName = NULL;
+    kind = displayFontT1; // make gcc happy
+    if (dir) {
+      fileName = appendToPath(new GString(dir), displayFontTab[i].t1FileName);
+      kind = displayFontT1;
+      if ((f = fopen(fileName->getCString(), "rb"))) {
+	fclose(f);
+      } else {
+	delete fileName;
+	fileName = NULL;
+      }
+    }
+#ifdef WIN32
+    if (!fileName && winFontDir[0] && displayFontTab[i].ttFileName) {
+      fileName = appendToPath(new GString(winFontDir),
+			      displayFontTab[i].ttFileName);
+      kind = displayFontTT;
+      if ((f = fopen(fileName->getCString(), "rb"))) {
+	fclose(f);
+      } else {
+	delete fileName;
+	fileName = NULL;
+      }
+    }
+    // SHGetSpecialFolderPath(CSIDL_FONTS) doesn't work on Win 2k Server
+    // or Win2003 Server, or with older versions of shell32.dll, so check
+    // the "standard" directories
+    if (displayFontTab[i].ttFileName) {
+      for (j = 0; !fileName && displayFontDirs[j]; ++j) {
+	fileName = appendToPath(new GString(displayFontDirs[j]),
+				displayFontTab[i].ttFileName);
+	kind = displayFontTT;
+	if ((f = fopen(fileName->getCString(), "rb"))) {
+	  fclose(f);
+	} else {
+	  delete fileName;
+	  fileName = NULL;
+	}
+      }
+    }
+#else
+    for (j = 0; !fileName && displayFontDirs[j]; ++j) {
+      fileName = appendToPath(new GString(displayFontDirs[j]),
+			      displayFontTab[i].t1FileName);
+      kind = displayFontT1;
+      if ((f = fopen(fileName->getCString(), "rb"))) {
+	fclose(f);
+      } else {
+	delete fileName;
+	fileName = NULL;
+      }
+    }
+#endif
+    if (!fileName) {
+      error(-1, "No display font for '%s'", displayFontTab[i].name);
+      delete fontName;
+      continue;
+    }
+    dfp = new DisplayFontParam(fontName, kind);
+    dfp->t1.fileName = fileName;
+    globalParams->addDisplayFont(dfp);
+  }
+
+#ifdef WIN32
+  if (winFontDir[0]) {
+    winFontList = new WinFontList(winFontDir);
+  }
+#endif
+}
+
+//------------------------------------------------------------------------
+// accessors
+//------------------------------------------------------------------------
+
+CharCode GlobalParams::getMacRomanCharCode(char *charName) {
+  // no need to lock - macRomanReverseMap is constant
+  return macRomanReverseMap->lookup(charName);
+}
+
+GString *GlobalParams::getBaseDir() {
+  GString *s;
+
+  lockGlobalParams;
+  s = baseDir->copy();
+  unlockGlobalParams;
+  return s;
+}
+
+Unicode GlobalParams::mapNameToUnicode(char *charName) {
+  // no need to lock - nameToUnicode is constant
+  return nameToUnicode->lookup(charName);
+}
+
+UnicodeMap *GlobalParams::getResidentUnicodeMap(GString *encodingName) {
+  UnicodeMap *map;
+
+  lockGlobalParams;
+  map = (UnicodeMap *)residentUnicodeMaps->lookup(encodingName);
+  unlockGlobalParams;
+  if (map) {
+    map->incRefCnt();
+  }
+  return map;
+}
+
+FILE *GlobalParams::getUnicodeMapFile(GString *encodingName) {
+  GString *fileName;
+  FILE *f;
+
+  lockGlobalParams;
+  if ((fileName = (GString *)unicodeMaps->lookup(encodingName))) {
+    f = fopen(fileName->getCString(), "r");
+  } else {
+    f = NULL;
+  }
+  unlockGlobalParams;
+  return f;
+}
+
+FILE *GlobalParams::findCMapFile(GString *collection, GString *cMapName) {
+  GList *list;
+  GString *dir;
+  GString *fileName;
+  FILE *f;
+  int i;
+
+  lockGlobalParams;
+  if (!(list = (GList *)cMapDirs->lookup(collection))) {
+    unlockGlobalParams;
+    return NULL;
+  }
+  for (i = 0; i < list->getLength(); ++i) {
+    dir = (GString *)list->get(i);
+    fileName = appendToPath(dir->copy(), cMapName->getCString());
+    f = fopen(fileName->getCString(), "r");
+    delete fileName;
+    if (f) {
+      unlockGlobalParams;
+      return f;
+    }
+  }
+  unlockGlobalParams;
+  return NULL;
+}
+
+FILE *GlobalParams::findToUnicodeFile(GString *name) {
+  GString *dir, *fileName;
+  FILE *f;
+  int i;
+
+  lockGlobalParams;
+  for (i = 0; i < toUnicodeDirs->getLength(); ++i) {
+    dir = (GString *)toUnicodeDirs->get(i);
+    fileName = appendToPath(dir->copy(), name->getCString());
+    f = fopen(fileName->getCString(), "r");
+    delete fileName;
+    if (f) {
+      unlockGlobalParams;
+      return f;
+    }
+  }
+  unlockGlobalParams;
+  return NULL;
+}
+
+DisplayFontParam *GlobalParams::getDisplayFont(GString *fontName) {
+  DisplayFontParam *dfp;
+
+  lockGlobalParams;
+  dfp = (DisplayFontParam *)displayFonts->lookup(fontName);
+#ifdef WIN32
+  if (!dfp && winFontList) {
+    dfp = winFontList->find(fontName);
+  }
+#endif
+  unlockGlobalParams;
+  return dfp;
+}
+
+DisplayFontParam *GlobalParams::getDisplayCIDFont(GString *fontName,
+						  GString *collection) {
+  DisplayFontParam *dfp;
+
+  lockGlobalParams;
+  if (!fontName ||
+      !(dfp = (DisplayFontParam *)displayNamedCIDFonts->lookup(fontName))) {
+    dfp = (DisplayFontParam *)displayCIDFonts->lookup(collection);
+  }
+  unlockGlobalParams;
+  return dfp;
+}
+
+GString *GlobalParams::getPSFile() {
+  GString *s;
+
+  lockGlobalParams;
+  s = psFile ? psFile->copy() : (GString *)NULL;
+  unlockGlobalParams;
+  return s;
+}
+
+int GlobalParams::getPSPaperWidth() {
+  int w;
+
+  lockGlobalParams;
+  w = psPaperWidth;
+  unlockGlobalParams;
+  return w;
+}
+
+int GlobalParams::getPSPaperHeight() {
+  int h;
+
+  lockGlobalParams;
+  h = psPaperHeight;
+  unlockGlobalParams;
+  return h;
+}
+
+void GlobalParams::getPSImageableArea(int *llx, int *lly, int *urx, int *ury) {
+  lockGlobalParams;
+  *llx = psImageableLLX;
+  *lly = psImageableLLY;
+  *urx = psImageableURX;
+  *ury = psImageableURY;
+  unlockGlobalParams;
+}
+
+GBool GlobalParams::getPSCrop() {
+  GBool f;
+
+  lockGlobalParams;
+  f = psCrop;
+  unlockGlobalParams;
+  return f;
+}
+
+GBool GlobalParams::getPSExpandSmaller() {
+  GBool f;
+
+  lockGlobalParams;
+  f = psExpandSmaller;
+  unlockGlobalParams;
+  return f;
+}
+
+GBool GlobalParams::getPSShrinkLarger() {
+  GBool f;
+
+  lockGlobalParams;
+  f = psShrinkLarger;
+  unlockGlobalParams;
+  return f;
+}
+
+GBool GlobalParams::getPSCenter() {
+  GBool f;
+
+  lockGlobalParams;
+  f = psCenter;
+  unlockGlobalParams;
+  return f;
+}
+
+GBool GlobalParams::getPSDuplex() {
+  GBool d;
+
+  lockGlobalParams;
+  d = psDuplex;
+  unlockGlobalParams;
+  return d;
+}
+
+PSLevel GlobalParams::getPSLevel() {
+  PSLevel level;
+
+  lockGlobalParams;
+  level = psLevel;
+  unlockGlobalParams;
+  return level;
+}
+
+PSFontParam *GlobalParams::getPSFont(GString *fontName) {
+  PSFontParam *p;
+
+  lockGlobalParams;
+  p = (PSFontParam *)psFonts->lookup(fontName);
+  unlockGlobalParams;
+  return p;
+}
+
+PSFontParam *GlobalParams::getPSFont16(GString *fontName,
+				       GString *collection, int wMode) {
+  PSFontParam *p;
+  int i;
+
+  lockGlobalParams;
+  p = NULL;
+  if (fontName) {
+    for (i = 0; i < psNamedFonts16->getLength(); ++i) {
+      p = (PSFontParam *)psNamedFonts16->get(i);
+      if (!p->pdfFontName->cmp(fontName) &&
+	  p->wMode == wMode) {
+	break;
+      }
+      p = NULL;
+    }
+  }
+  if (!p && collection) {
+    for (i = 0; i < psFonts16->getLength(); ++i) {
+      p = (PSFontParam *)psFonts16->get(i);
+      if (!p->pdfFontName->cmp(collection) &&
+	  p->wMode == wMode) {
+	break;
+      }
+      p = NULL;
+    }
+  }
+  unlockGlobalParams;
+  return p;
+}
+
+GBool GlobalParams::getPSEmbedType1() {
+  GBool e;
+
+  lockGlobalParams;
+  e = psEmbedType1;
+  unlockGlobalParams;
+  return e;
+}
+
+GBool GlobalParams::getPSEmbedTrueType() {
+  GBool e;
+
+  lockGlobalParams;
+  e = psEmbedTrueType;
+  unlockGlobalParams;
+  return e;
+}
+
+GBool GlobalParams::getPSEmbedCIDPostScript() {
+  GBool e;
+
+  lockGlobalParams;
+  e = psEmbedCIDPostScript;
+  unlockGlobalParams;
+  return e;
+}
+
+GBool GlobalParams::getPSEmbedCIDTrueType() {
+  GBool e;
+
+  lockGlobalParams;
+  e = psEmbedCIDTrueType;
+  unlockGlobalParams;
+  return e;
+}
+
+GBool GlobalParams::getPSPreload() {
+  GBool preload;
+
+  lockGlobalParams;
+  preload = psPreload;
+  unlockGlobalParams;
+  return preload;
+}
+
+GBool GlobalParams::getPSOPI() {
+  GBool opi;
+
+  lockGlobalParams;
+  opi = psOPI;
+  unlockGlobalParams;
+  return opi;
+}
+
+GBool GlobalParams::getPSASCIIHex() {
+  GBool ah;
+
+  lockGlobalParams;
+  ah = psASCIIHex;
+  unlockGlobalParams;
+  return ah;
+}
+
+GString *GlobalParams::getTextEncodingName() {
+  GString *s;
+
+  lockGlobalParams;
+  s = textEncoding->copy();
+  unlockGlobalParams;
+  return s;
+}
+
+EndOfLineKind GlobalParams::getTextEOL() {
+  EndOfLineKind eol;
+
+  lockGlobalParams;
+  eol = textEOL;
+  unlockGlobalParams;
+  return eol;
+}
+
+GBool GlobalParams::getTextPageBreaks() {
+  GBool pageBreaks;
+
+  lockGlobalParams;
+  pageBreaks = textPageBreaks;
+  unlockGlobalParams;
+  return pageBreaks;
+}
+
+GBool GlobalParams::getTextKeepTinyChars() {
+  GBool tiny;
+
+  lockGlobalParams;
+  tiny = textKeepTinyChars;
+  unlockGlobalParams;
+  return tiny;
+}
+
+GString *GlobalParams::findFontFile(GString *fontName, char **exts) {
+  GString *dir, *fileName;
+  char **ext;
+  FILE *f;
+  int i;
+
+  lockGlobalParams;
+  for (i = 0; i < fontDirs->getLength(); ++i) {
+    dir = (GString *)fontDirs->get(i);
+    for (ext = exts; *ext; ++ext) {
+      fileName = appendToPath(dir->copy(), fontName->getCString());
+      fileName->append(*ext);
+      if ((f = fopen(fileName->getCString(), "rb"))) {
+	fclose(f);
+	unlockGlobalParams;
+	return fileName;
+      }
+      delete fileName;
+    }
+  }
+  unlockGlobalParams;
+  return NULL;
+}
+
+GString *GlobalParams::getInitialZoom() {
+  GString *s;
+
+  lockGlobalParams;
+  s = initialZoom->copy();
+  unlockGlobalParams;
+  return s;
+}
+
+GBool GlobalParams::getContinuousView() {
+  GBool f;
+
+  lockGlobalParams;
+  f = continuousView;
+  unlockGlobalParams;
+  return f;
+}
+
+GBool GlobalParams::getEnableT1lib() {
+  GBool f;
+
+  lockGlobalParams;
+  f = enableT1lib;
+  unlockGlobalParams;
+  return f;
+}
+
+GBool GlobalParams::getEnableFreeType() {
+  GBool f;
+
+  lockGlobalParams;
+  f = enableFreeType;
+  unlockGlobalParams;
+  return f;
+}
+
+
+GBool GlobalParams::getAntialias() {
+  GBool f;
+
+  lockGlobalParams;
+  f = antialias;
+  unlockGlobalParams;
+  return f;
+}
+
+GBool GlobalParams::getVectorAntialias() {
+  GBool f;
+
+  lockGlobalParams;
+  f = vectorAntialias;
+  unlockGlobalParams;
+  return f;
+}
+
+GBool GlobalParams::getStrokeAdjust() {
+  GBool f;
+
+  lockGlobalParams;
+  f = strokeAdjust;
+  unlockGlobalParams;
+  return f;
+}
+
+ScreenType GlobalParams::getScreenType() {
+  ScreenType t;
+
+  lockGlobalParams;
+  t = screenType;
+  unlockGlobalParams;
+  return t;
+}
+
+int GlobalParams::getScreenSize() {
+  int size;
+
+  lockGlobalParams;
+  size = screenSize;
+  unlockGlobalParams;
+  return size;
+}
+
+int GlobalParams::getScreenDotRadius() {
+  int r;
+
+  lockGlobalParams;
+  r = screenDotRadius;
+  unlockGlobalParams;
+  return r;
+}
+
+double GlobalParams::getScreenGamma() {
+  double gamma;
+
+  lockGlobalParams;
+  gamma = screenGamma;
+  unlockGlobalParams;
+  return gamma;
+}
+
+double GlobalParams::getScreenBlackThreshold() {
+  double thresh;
+
+  lockGlobalParams;
+  thresh = screenBlackThreshold;
+  unlockGlobalParams;
+  return thresh;
+}
+
+double GlobalParams::getScreenWhiteThreshold() {
+  double thresh;
+
+  lockGlobalParams;
+  thresh = screenWhiteThreshold;
+  unlockGlobalParams;
+  return thresh;
+}
+
+GBool GlobalParams::getMapNumericCharNames() {
+  GBool map;
+
+  lockGlobalParams;
+  map = mapNumericCharNames;
+  unlockGlobalParams;
+  return map;
+}
+
+GBool GlobalParams::getMapUnknownCharNames() {
+  GBool map;
+
+  lockGlobalParams;
+  map = mapUnknownCharNames;
+  unlockGlobalParams;
+  return map;
+}
+
+GList *GlobalParams::getKeyBinding(int code, int mods, int context) {
+  KeyBinding *binding;
+  GList *cmds;
+  int modMask;
+  int i, j;
+
+  lockGlobalParams;
+  cmds = NULL;
+  // for ASCII chars, ignore the shift modifier
+  modMask = code <= 0xff ? ~xpdfKeyModShift : ~0;
+  for (i = 0; i < keyBindings->getLength(); ++i) {
+    binding = (KeyBinding *)keyBindings->get(i);
+    if (binding->code == code &&
+	(binding->mods & modMask) == (mods & modMask) &&
+	(~binding->context | context) == ~0) {
+      cmds = new GList();
+      for (j = 0; j < binding->cmds->getLength(); ++j) {
+	cmds->append(((GString *)binding->cmds->get(j))->copy());
+      }
+      break;
+    }
+  }
+  unlockGlobalParams;
+  return cmds;
+}
+
+GBool GlobalParams::getPrintCommands() {
+  GBool p;
+
+  lockGlobalParams;
+  p = printCommands;
+  unlockGlobalParams;
+  return p;
+}
+
+GBool GlobalParams::getErrQuiet() {
+  // no locking -- this function may get called from inside a locked
+  // section
+  return errQuiet;
+}
+
+CharCodeToUnicode *GlobalParams::getCIDToUnicode(GString *collection) {
+  GString *fileName;
+  CharCodeToUnicode *ctu;
+
+  lockGlobalParams;
+  if (!(ctu = cidToUnicodeCache->getCharCodeToUnicode(collection))) {
+    if ((fileName = (GString *)cidToUnicodes->lookup(collection)) &&
+	(ctu = CharCodeToUnicode::parseCIDToUnicode(fileName, collection))) {
+      cidToUnicodeCache->add(ctu);
+    }
+  }
+  unlockGlobalParams;
+  return ctu;
+}
+
+CharCodeToUnicode *GlobalParams::getUnicodeToUnicode(GString *fontName) {
+  CharCodeToUnicode *ctu;
+  GHashIter *iter;
+  GString *fontPattern, *fileName;
+
+  lockGlobalParams;
+  fileName = NULL;
+  unicodeToUnicodes->startIter(&iter);
+  while (unicodeToUnicodes->getNext(&iter, &fontPattern, (void **)&fileName)) {
+    if (strstr(fontName->getCString(), fontPattern->getCString())) {
+      unicodeToUnicodes->killIter(&iter);
+      break;
+    }
+    fileName = NULL;
+  }
+  if (fileName) {
+    if (!(ctu = unicodeToUnicodeCache->getCharCodeToUnicode(fileName))) {
+      if ((ctu = CharCodeToUnicode::parseUnicodeToUnicode(fileName))) {
+	unicodeToUnicodeCache->add(ctu);
+      }
+    }
+  } else {
+    ctu = NULL;
+  }
+  unlockGlobalParams;
+  return ctu;
+}
+
+UnicodeMap *GlobalParams::getUnicodeMap(GString *encodingName) {
+  return getUnicodeMap2(encodingName);
+}
+
+UnicodeMap *GlobalParams::getUnicodeMap2(GString *encodingName) {
+  UnicodeMap *map;
+
+  if (!(map = getResidentUnicodeMap(encodingName))) {
+    lockUnicodeMapCache;
+    map = unicodeMapCache->getUnicodeMap(encodingName);
+    unlockUnicodeMapCache;
+  }
+  return map;
+}
+
+CMap *GlobalParams::getCMap(GString *collection, GString *cMapName) {
+  CMap *cMap;
+
+  lockCMapCache;
+  cMap = cMapCache->getCMap(collection, cMapName);
+  unlockCMapCache;
+  return cMap;
+}
+
+UnicodeMap *GlobalParams::getTextEncoding() {
+  return getUnicodeMap2(textEncoding);
+}
+
+//------------------------------------------------------------------------
+// functions to set parameters
+//------------------------------------------------------------------------
+
+void GlobalParams::addDisplayFont(DisplayFontParam *param) {
+  DisplayFontParam *old;
+
+  lockGlobalParams;
+  if ((old = (DisplayFontParam *)displayFonts->remove(param->name))) {
+    delete old;
+  }
+  displayFonts->add(param->name, param);
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSFile(char *file) {
+  lockGlobalParams;
+  if (psFile) {
+    delete psFile;
+  }
+  psFile = new GString(file);
+  unlockGlobalParams;
+}
+
+GBool GlobalParams::setPSPaperSize(char *size) {
+  lockGlobalParams;
+  if (!strcmp(size, "match")) {
+    psPaperWidth = psPaperHeight = -1;
+  } else if (!strcmp(size, "letter")) {
+    psPaperWidth = 612;
+    psPaperHeight = 792;
+  } else if (!strcmp(size, "legal")) {
+    psPaperWidth = 612;
+    psPaperHeight = 1008;
+  } else if (!strcmp(size, "A4")) {
+    psPaperWidth = 595;
+    psPaperHeight = 842;
+  } else if (!strcmp(size, "A3")) {
+    psPaperWidth = 842;
+    psPaperHeight = 1190;
+  } else {
+    unlockGlobalParams;
+    return gFalse;
+  }
+  psImageableLLX = psImageableLLY = 0;
+  psImageableURX = psPaperWidth;
+  psImageableURY = psPaperHeight;
+  unlockGlobalParams;
+  return gTrue;
+}
+
+void GlobalParams::setPSPaperWidth(int width) {
+  lockGlobalParams;
+  psPaperWidth = width;
+  psImageableLLX = 0;
+  psImageableURX = psPaperWidth;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSPaperHeight(int height) {
+  lockGlobalParams;
+  psPaperHeight = height;
+  psImageableLLY = 0;
+  psImageableURY = psPaperHeight;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSImageableArea(int llx, int lly, int urx, int ury) {
+  lockGlobalParams;
+  psImageableLLX = llx;
+  psImageableLLY = lly;
+  psImageableURX = urx;
+  psImageableURY = ury;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSCrop(GBool crop) {
+  lockGlobalParams;
+  psCrop = crop;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSExpandSmaller(GBool expand) {
+  lockGlobalParams;
+  psExpandSmaller = expand;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSShrinkLarger(GBool shrink) {
+  lockGlobalParams;
+  psShrinkLarger = shrink;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSCenter(GBool center) {
+  lockGlobalParams;
+  psCenter = center;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSDuplex(GBool duplex) {
+  lockGlobalParams;
+  psDuplex = duplex;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSLevel(PSLevel level) {
+  lockGlobalParams;
+  psLevel = level;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSEmbedType1(GBool embed) {
+  lockGlobalParams;
+  psEmbedType1 = embed;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSEmbedTrueType(GBool embed) {
+  lockGlobalParams;
+  psEmbedTrueType = embed;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSEmbedCIDPostScript(GBool embed) {
+  lockGlobalParams;
+  psEmbedCIDPostScript = embed;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSEmbedCIDTrueType(GBool embed) {
+  lockGlobalParams;
+  psEmbedCIDTrueType = embed;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSPreload(GBool preload) {
+  lockGlobalParams;
+  psPreload = preload;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSOPI(GBool opi) {
+  lockGlobalParams;
+  psOPI = opi;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPSASCIIHex(GBool hex) {
+  lockGlobalParams;
+  psASCIIHex = hex;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setTextEncoding(char *encodingName) {
+  lockGlobalParams;
+  delete textEncoding;
+  textEncoding = new GString(encodingName);
+  unlockGlobalParams;
+}
+
+GBool GlobalParams::setTextEOL(char *s) {
+  lockGlobalParams;
+  if (!strcmp(s, "unix")) {
+    textEOL = eolUnix;
+  } else if (!strcmp(s, "dos")) {
+    textEOL = eolDOS;
+  } else if (!strcmp(s, "mac")) {
+    textEOL = eolMac;
+  } else {
+    unlockGlobalParams;
+    return gFalse;
+  }
+  unlockGlobalParams;
+  return gTrue;
+}
+
+void GlobalParams::setTextPageBreaks(GBool pageBreaks) {
+  lockGlobalParams;
+  textPageBreaks = pageBreaks;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setTextKeepTinyChars(GBool keep) {
+  lockGlobalParams;
+  textKeepTinyChars = keep;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setInitialZoom(char *s) {
+  lockGlobalParams;
+  delete initialZoom;
+  initialZoom = new GString(s);
+  unlockGlobalParams;
+}
+
+void GlobalParams::setContinuousView(GBool cont) {
+  lockGlobalParams;
+  continuousView = cont;
+  unlockGlobalParams;
+}
+
+GBool GlobalParams::setEnableT1lib(char *s) {
+  GBool ok;
+
+  lockGlobalParams;
+  ok = parseYesNo2(s, &enableT1lib);
+  unlockGlobalParams;
+  return ok;
+}
+
+GBool GlobalParams::setEnableFreeType(char *s) {
+  GBool ok;
+
+  lockGlobalParams;
+  ok = parseYesNo2(s, &enableFreeType);
+  unlockGlobalParams;
+  return ok;
+}
+
+
+GBool GlobalParams::setAntialias(char *s) {
+  GBool ok;
+
+  lockGlobalParams;
+  ok = parseYesNo2(s, &antialias);
+  unlockGlobalParams;
+  return ok;
+}
+
+GBool GlobalParams::setVectorAntialias(char *s) {
+  GBool ok;
+
+  lockGlobalParams;
+  ok = parseYesNo2(s, &vectorAntialias);
+  unlockGlobalParams;
+  return ok;
+}
+
+void GlobalParams::setScreenType(ScreenType t) {
+  lockGlobalParams;
+  screenType = t;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setScreenSize(int size) {
+  lockGlobalParams;
+  screenSize = size;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setScreenDotRadius(int r) {
+  lockGlobalParams;
+  screenDotRadius = r;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setScreenGamma(double gamma) {
+  lockGlobalParams;
+  screenGamma = gamma;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setScreenBlackThreshold(double thresh) {
+  lockGlobalParams;
+  screenBlackThreshold = thresh;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setScreenWhiteThreshold(double thresh) {
+  lockGlobalParams;
+  screenWhiteThreshold = thresh;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setMapNumericCharNames(GBool map) {
+  lockGlobalParams;
+  mapNumericCharNames = map;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setMapUnknownCharNames(GBool map) {
+  lockGlobalParams;
+  mapUnknownCharNames = map;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setPrintCommands(GBool printCommandsA) {
+  lockGlobalParams;
+  printCommands = printCommandsA;
+  unlockGlobalParams;
+}
+
+void GlobalParams::setErrQuiet(GBool errQuietA) {
+  lockGlobalParams;
+  errQuiet = errQuietA;
+  unlockGlobalParams;
+}
+
+void GlobalParams::addSecurityHandler(XpdfSecurityHandler *handler) {
+#ifdef ENABLE_PLUGINS
+  lockGlobalParams;
+  securityHandlers->append(handler);
+  unlockGlobalParams;
+#endif
+}
+
+XpdfSecurityHandler *GlobalParams::getSecurityHandler(char *name) {
+#ifdef ENABLE_PLUGINS
+  XpdfSecurityHandler *hdlr;
+  int i;
+
+  lockGlobalParams;
+  for (i = 0; i < securityHandlers->getLength(); ++i) {
+    hdlr = (XpdfSecurityHandler *)securityHandlers->get(i);
+    if (!strcasecmp(hdlr->name, name)) {
+      unlockGlobalParams;
+      return hdlr;
+    }
+  }
+  unlockGlobalParams;
+
+  if (!loadPlugin("security", name)) {
+    return NULL;
+  }
+
+  lockGlobalParams;
+  for (i = 0; i < securityHandlers->getLength(); ++i) {
+    hdlr = (XpdfSecurityHandler *)securityHandlers->get(i);
+    if (!strcmp(hdlr->name, name)) {
+      unlockGlobalParams;
+      return hdlr;
+    }
+  }
+  unlockGlobalParams;
+#endif
+
+  return NULL;
+}
+
+#ifdef ENABLE_PLUGINS
+//------------------------------------------------------------------------
+// plugins
+//------------------------------------------------------------------------
+
+GBool GlobalParams::loadPlugin(char *type, char *name) {
+  Plugin *plugin;
+
+  if (!(plugin = Plugin::load(type, name))) {
+    return gFalse;
+  }
+  lockGlobalParams;
+  plugins->append(plugin);
+  unlockGlobalParams;
+  return gTrue;
+}
+
+#endif // ENABLE_PLUGINS
diff --git a/xpdf/GlobalParams.h b/xpdf/GlobalParams.h
new file mode 100644
--- /dev/null
+++ b/xpdf/GlobalParams.h
@@ -0,0 +1,463 @@
+//========================================================================
+//
+// GlobalParams.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef GLOBALPARAMS_H
+#define GLOBALPARAMS_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <stdio.h>
+#include "gtypes.h"
+#include "CharTypes.h"
+
+#if MULTITHREADED
+#include "GMutex.h"
+#endif
+
+class GString;
+class GList;
+class GHash;
+class NameToCharCode;
+class CharCodeToUnicode;
+class CharCodeToUnicodeCache;
+class UnicodeMap;
+class UnicodeMapCache;
+class CMap;
+class CMapCache;
+struct XpdfSecurityHandler;
+class GlobalParams;
+#ifdef WIN32
+class WinFontList;
+#endif
+
+//------------------------------------------------------------------------
+
+// The global parameters object.
+extern GlobalParams *globalParams;
+
+//------------------------------------------------------------------------
+
+enum DisplayFontParamKind {
+  displayFontT1,
+  displayFontTT
+};
+
+struct DisplayFontParamT1 {
+  GString *fileName;
+};
+
+struct DisplayFontParamTT {
+  GString *fileName;
+};
+
+class DisplayFontParam {
+public:
+
+  GString *name;		// font name for 8-bit fonts and named
+				//   CID fonts; collection name for
+				//   generic CID fonts
+  DisplayFontParamKind kind;
+  union {
+    DisplayFontParamT1 t1;
+    DisplayFontParamTT tt;
+  };
+
+  DisplayFontParam(GString *nameA, DisplayFontParamKind kindA);
+  virtual ~DisplayFontParam();
+};
+
+//------------------------------------------------------------------------
+
+class PSFontParam {
+public:
+
+  GString *pdfFontName;		// PDF font name for 8-bit fonts and
+				//   named 16-bit fonts; char collection
+				//   name for generic 16-bit fonts
+  int wMode;			// writing mode (0=horiz, 1=vert) for
+				//   16-bit fonts
+  GString *psFontName;		// PostScript font name
+  GString *encoding;		// encoding, for 16-bit fonts only
+
+  PSFontParam(GString *pdfFontNameA, int wModeA,
+	      GString *psFontNameA, GString *encodingA);
+  ~PSFontParam();
+};
+
+//------------------------------------------------------------------------
+
+enum PSLevel {
+  psLevel1,
+  psLevel1Sep,
+  psLevel2,
+  psLevel2Sep,
+  psLevel3,
+  psLevel3Sep
+};
+
+//------------------------------------------------------------------------
+
+enum EndOfLineKind {
+  eolUnix,			// LF
+  eolDOS,			// CR+LF
+  eolMac			// CR
+};
+
+//------------------------------------------------------------------------
+
+enum ScreenType {
+  screenUnset,
+  screenDispersed,
+  screenClustered,
+  screenStochasticClustered
+};
+
+//------------------------------------------------------------------------
+
+class KeyBinding {
+public:
+
+  int code;			// 0x20 .. 0xfe = ASCII,
+				//   >=0x10000 = special keys, mouse buttons,
+				//   etc. (xpdfKeyCode* symbols)
+  int mods;			// modifiers (xpdfKeyMod* symbols, or-ed
+				//   together)
+  int context;			// context (xpdfKeyContext* symbols, or-ed
+				//   together)
+  GList *cmds;			// list of commands [GString]
+
+  KeyBinding(int codeA, int modsA, int contextA, char *cmd0);
+  KeyBinding(int codeA, int modsA, int contextA, char *cmd0, char *cmd1);
+  KeyBinding(int codeA, int modsA, int contextA, GList *cmdsA);
+  ~KeyBinding();
+};
+
+#define xpdfKeyCodeTab            0x1000
+#define xpdfKeyCodeReturn         0x1001
+#define xpdfKeyCodeEnter          0x1002
+#define xpdfKeyCodeBackspace      0x1003
+#define xpdfKeyCodeInsert         0x1004
+#define xpdfKeyCodeDelete         0x1005
+#define xpdfKeyCodeHome           0x1006
+#define xpdfKeyCodeEnd            0x1007
+#define xpdfKeyCodePgUp           0x1008
+#define xpdfKeyCodePgDn           0x1009
+#define xpdfKeyCodeLeft           0x100a
+#define xpdfKeyCodeRight          0x100b
+#define xpdfKeyCodeUp             0x100c
+#define xpdfKeyCodeDown           0x100d
+#define xpdfKeyCodeF1             0x1100
+#define xpdfKeyCodeF35            0x1122
+#define xpdfKeyCodeMousePress1    0x2001
+#define xpdfKeyCodeMousePress2    0x2002
+#define xpdfKeyCodeMousePress3    0x2003
+#define xpdfKeyCodeMousePress4    0x2004
+#define xpdfKeyCodeMousePress5    0x2005
+#define xpdfKeyCodeMousePress6    0x2006
+#define xpdfKeyCodeMousePress7    0x2007
+#define xpdfKeyCodeMouseRelease1  0x2101
+#define xpdfKeyCodeMouseRelease2  0x2102
+#define xpdfKeyCodeMouseRelease3  0x2103
+#define xpdfKeyCodeMouseRelease4  0x2104
+#define xpdfKeyCodeMouseRelease5  0x2105
+#define xpdfKeyCodeMouseRelease6  0x2106
+#define xpdfKeyCodeMouseRelease7  0x2107
+#define xpdfKeyModNone            0
+#define xpdfKeyModShift           (1 << 0)
+#define xpdfKeyModCtrl            (1 << 1)
+#define xpdfKeyModAlt             (1 << 2)
+#define xpdfKeyContextAny         0
+#define xpdfKeyContextFullScreen  (1 << 0)
+#define xpdfKeyContextWindow      (2 << 0)
+#define xpdfKeyContextContinuous  (1 << 2)
+#define xpdfKeyContextSinglePage  (2 << 2)
+#define xpdfKeyContextOverLink    (1 << 4)
+#define xpdfKeyContextOffLink     (2 << 4)
+#define xpdfKeyContextOutline     (1 << 6)
+#define xpdfKeyContextMainWin     (2 << 6)
+#define xpdfKeyContextScrLockOn   (1 << 8)
+#define xpdfKeyContextScrLockOff  (2 << 8)
+
+//------------------------------------------------------------------------
+
+class GlobalParams {
+public:
+
+  // Initialize the global parameters by attempting to read a config
+  // file.
+  GlobalParams(char *cfgFileName);
+
+  ~GlobalParams();
+
+  void setBaseDir(char *dir);
+  void setupBaseFonts(char *dir);
+
+  void parseLine(char *buf, GString *fileName, int line);
+
+  //----- accessors
+
+  CharCode getMacRomanCharCode(char *charName);
+
+  GString *getBaseDir();
+  Unicode mapNameToUnicode(char *charName);
+  UnicodeMap *getResidentUnicodeMap(GString *encodingName);
+  FILE *getUnicodeMapFile(GString *encodingName);
+  FILE *findCMapFile(GString *collection, GString *cMapName);
+  FILE *findToUnicodeFile(GString *name);
+  DisplayFontParam *getDisplayFont(GString *fontName);
+  DisplayFontParam *getDisplayCIDFont(GString *fontName, GString *collection);
+  GString *getPSFile();
+  int getPSPaperWidth();
+  int getPSPaperHeight();
+  void getPSImageableArea(int *llx, int *lly, int *urx, int *ury);
+  GBool getPSDuplex();
+  GBool getPSCrop();
+  GBool getPSExpandSmaller();
+  GBool getPSShrinkLarger();
+  GBool getPSCenter();
+  PSLevel getPSLevel();
+  PSFontParam *getPSFont(GString *fontName);
+  PSFontParam *getPSFont16(GString *fontName, GString *collection, int wMode);
+  GBool getPSEmbedType1();
+  GBool getPSEmbedTrueType();
+  GBool getPSEmbedCIDPostScript();
+  GBool getPSEmbedCIDTrueType();
+  GBool getPSPreload();
+  GBool getPSOPI();
+  GBool getPSASCIIHex();
+  GString *getTextEncodingName();
+  EndOfLineKind getTextEOL();
+  GBool getTextPageBreaks();
+  GBool getTextKeepTinyChars();
+  GString *findFontFile(GString *fontName, char **exts);
+  GString *getInitialZoom();
+  GBool getContinuousView();
+  GBool getEnableT1lib();
+  GBool getEnableFreeType();
+  GBool getAntialias();
+  GBool getVectorAntialias();
+  GBool getStrokeAdjust();
+  ScreenType getScreenType();
+  int getScreenSize();
+  int getScreenDotRadius();
+  double getScreenGamma();
+  double getScreenBlackThreshold();
+  double getScreenWhiteThreshold();
+  GString *getURLCommand() { return urlCommand; }
+  GString *getMovieCommand() { return movieCommand; }
+  GBool getMapNumericCharNames();
+  GBool getMapUnknownCharNames();
+  GList *getKeyBinding(int code, int mods, int context);
+  GBool getPrintCommands();
+  GBool getErrQuiet();
+
+  CharCodeToUnicode *getCIDToUnicode(GString *collection);
+  CharCodeToUnicode *getUnicodeToUnicode(GString *fontName);
+  UnicodeMap *getUnicodeMap(GString *encodingName);
+  CMap *getCMap(GString *collection, GString *cMapName);
+  UnicodeMap *getTextEncoding();
+
+  //----- functions to set parameters
+
+  void addDisplayFont(DisplayFontParam *param);
+  void setPSFile(char *file);
+  GBool setPSPaperSize(char *size);
+  void setPSPaperWidth(int width);
+  void setPSPaperHeight(int height);
+  void setPSImageableArea(int llx, int lly, int urx, int ury);
+  void setPSDuplex(GBool duplex);
+  void setPSCrop(GBool crop);
+  void setPSExpandSmaller(GBool expand);
+  void setPSShrinkLarger(GBool shrink);
+  void setPSCenter(GBool center);
+  void setPSLevel(PSLevel level);
+  void setPSEmbedType1(GBool embed);
+  void setPSEmbedTrueType(GBool embed);
+  void setPSEmbedCIDPostScript(GBool embed);
+  void setPSEmbedCIDTrueType(GBool embed);
+  void setPSPreload(GBool preload);
+  void setPSOPI(GBool opi);
+  void setPSASCIIHex(GBool hex);
+  void setTextEncoding(char *encodingName);
+  GBool setTextEOL(char *s);
+  void setTextPageBreaks(GBool pageBreaks);
+  void setTextKeepTinyChars(GBool keep);
+  void setInitialZoom(char *s);
+  void setContinuousView(GBool cont);
+  GBool setEnableT1lib(char *s);
+  GBool setEnableFreeType(char *s);
+  GBool setAntialias(char *s);
+  GBool setVectorAntialias(char *s);
+  void setScreenType(ScreenType t);
+  void setScreenSize(int size);
+  void setScreenDotRadius(int r);
+  void setScreenGamma(double gamma);
+  void setScreenBlackThreshold(double thresh);
+  void setScreenWhiteThreshold(double thresh);
+  void setMapNumericCharNames(GBool map);
+  void setMapUnknownCharNames(GBool map);
+  void setPrintCommands(GBool printCommandsA);
+  void setErrQuiet(GBool errQuietA);
+
+  //----- security handlers
+
+  void addSecurityHandler(XpdfSecurityHandler *handler);
+  XpdfSecurityHandler *getSecurityHandler(char *name);
+
+private:
+
+  void createDefaultKeyBindings();
+  void parseFile(GString *fileName, FILE *f);
+  void parseNameToUnicode(GList *tokens, GString *fileName, int line);
+  void parseCIDToUnicode(GList *tokens, GString *fileName, int line);
+  void parseUnicodeToUnicode(GList *tokens, GString *fileName, int line);
+  void parseUnicodeMap(GList *tokens, GString *fileName, int line);
+  void parseCMapDir(GList *tokens, GString *fileName, int line);
+  void parseToUnicodeDir(GList *tokens, GString *fileName, int line);
+  void parseDisplayFont(GList *tokens, GHash *fontHash,
+			DisplayFontParamKind kind,
+			GString *fileName, int line);
+  void parsePSFile(GList *tokens, GString *fileName, int line);
+  void parsePSPaperSize(GList *tokens, GString *fileName, int line);
+  void parsePSImageableArea(GList *tokens, GString *fileName, int line);
+  void parsePSLevel(GList *tokens, GString *fileName, int line);
+  void parsePSFont(GList *tokens, GString *fileName, int line);
+  void parsePSFont16(char *cmdName, GList *fontList,
+		     GList *tokens, GString *fileName, int line);
+  void parseTextEncoding(GList *tokens, GString *fileName, int line);
+  void parseTextEOL(GList *tokens, GString *fileName, int line);
+  void parseFontDir(GList *tokens, GString *fileName, int line);
+  void parseInitialZoom(GList *tokens, GString *fileName, int line);
+  void parseScreenType(GList *tokens, GString *fileName, int line);
+  void parseBind(GList *tokens, GString *fileName, int line);
+  void parseUnbind(GList *tokens, GString *fileName, int line);
+  GBool parseKey(GString *modKeyStr, GString *contextStr,
+		 int *code, int *mods, int *context,
+		 char *cmdName,
+		 GList *tokens, GString *fileName, int line);
+  void parseCommand(char *cmdName, GString **val,
+		    GList *tokens, GString *fileName, int line);
+  void parseYesNo(char *cmdName, GBool *flag,
+		  GList *tokens, GString *fileName, int line);
+  GBool parseYesNo2(char *token, GBool *flag);
+  void parseInteger(char *cmdName, int *val,
+		    GList *tokens, GString *fileName, int line);
+  void parseFloat(char *cmdName, double *val,
+		  GList *tokens, GString *fileName, int line);
+  UnicodeMap *getUnicodeMap2(GString *encodingName);
+#ifdef ENABLE_PLUGINS
+  GBool loadPlugin(char *type, char *name);
+#endif
+
+  //----- static tables
+
+  NameToCharCode *		// mapping from char name to
+    macRomanReverseMap;		//   MacRomanEncoding index
+
+  //----- user-modifiable settings
+
+  GString *baseDir;		// base directory - for plugins, etc.
+  NameToCharCode *		// mapping from char name to Unicode
+    nameToUnicode;
+  GHash *cidToUnicodes;		// files for mappings from char collections
+				//   to Unicode, indexed by collection name
+				//   [GString]
+  GHash *unicodeToUnicodes;	// files for Unicode-to-Unicode mappings,
+				//   indexed by font name pattern [GString]
+  GHash *residentUnicodeMaps;	// mappings from Unicode to char codes,
+				//   indexed by encoding name [UnicodeMap]
+  GHash *unicodeMaps;		// files for mappings from Unicode to char
+				//   codes, indexed by encoding name [GString]
+  GHash *cMapDirs;		// list of CMap dirs, indexed by collection
+				//   name [GList[GString]]
+  GList *toUnicodeDirs;		// list of ToUnicode CMap dirs [GString]
+  GHash *displayFonts;		// display font info, indexed by font name
+				//   [DisplayFontParam]
+#ifdef WIN32
+  WinFontList *winFontList;	// system TrueType fonts
+#endif
+  GHash *displayCIDFonts;	// display CID font info, indexed by
+				//   collection [DisplayFontParam]
+  GHash *displayNamedCIDFonts;	// display CID font info, indexed by
+				//   font name [DisplayFontParam]
+  GString *psFile;		// PostScript file or command (for xpdf)
+  int psPaperWidth;		// paper size, in PostScript points, for
+  int psPaperHeight;		//   PostScript output
+  int psImageableLLX,		// imageable area, in PostScript points,
+      psImageableLLY,		//   for PostScript output
+      psImageableURX,
+      psImageableURY;
+  GBool psCrop;			// crop PS output to CropBox
+  GBool psExpandSmaller;	// expand smaller pages to fill paper
+  GBool psShrinkLarger;		// shrink larger pages to fit paper
+  GBool psCenter;		// center pages on the paper
+  GBool psDuplex;		// enable duplexing in PostScript?
+  PSLevel psLevel;		// PostScript level to generate
+  GHash *psFonts;		// PostScript font info, indexed by PDF
+				//   font name [PSFontParam]
+  GList *psNamedFonts16;	// named 16-bit fonts [PSFontParam]
+  GList *psFonts16;		// generic 16-bit fonts [PSFontParam]
+  GBool psEmbedType1;		// embed Type 1 fonts?
+  GBool psEmbedTrueType;	// embed TrueType fonts?
+  GBool psEmbedCIDPostScript;	// embed CID PostScript fonts?
+  GBool psEmbedCIDTrueType;	// embed CID TrueType fonts?
+  GBool psPreload;		// preload PostScript images and forms into
+				//   memory
+  GBool psOPI;			// generate PostScript OPI comments?
+  GBool psASCIIHex;		// use ASCIIHex instead of ASCII85?
+  GString *textEncoding;	// encoding (unicodeMap) to use for text
+				//   output
+  EndOfLineKind textEOL;	// type of EOL marker to use for text
+				//   output
+  GBool textPageBreaks;		// insert end-of-page markers?
+  GBool textKeepTinyChars;	// keep all characters in text output
+  GList *fontDirs;		// list of font dirs [GString]
+  GString *initialZoom;		// initial zoom level
+  GBool continuousView;		// continuous view mode
+  GBool enableT1lib;		// t1lib enable flag
+  GBool enableFreeType;		// FreeType enable flag
+  GBool antialias;		// font anti-aliasing enable flag
+  GBool vectorAntialias;	// vector anti-aliasing enable flag
+  GBool strokeAdjust;		// stroke adjustment enable flag
+  ScreenType screenType;	// halftone screen type
+  int screenSize;		// screen matrix size
+  int screenDotRadius;		// screen dot radius
+  double screenGamma;		// screen gamma correction
+  double screenBlackThreshold;	// screen black clamping threshold
+  double screenWhiteThreshold;	// screen white clamping threshold
+  GString *urlCommand;		// command executed for URL links
+  GString *movieCommand;	// command executed for movie annotations
+  GBool mapNumericCharNames;	// map numeric char names (from font subsets)?
+  GBool mapUnknownCharNames;	// map unknown char names?
+  GList *keyBindings;		// key & mouse button bindings [KeyBinding]
+  GBool printCommands;		// print the drawing commands
+  GBool errQuiet;		// suppress error messages?
+
+  CharCodeToUnicodeCache *cidToUnicodeCache;
+  CharCodeToUnicodeCache *unicodeToUnicodeCache;
+  UnicodeMapCache *unicodeMapCache;
+  CMapCache *cMapCache;
+
+#ifdef ENABLE_PLUGINS
+  GList *plugins;		// list of plugins [Plugin]
+  GList *securityHandlers;	// list of loaded security handlers
+				//   [XpdfSecurityHandler]
+#endif
+
+#if MULTITHREADED
+  GMutex mutex;
+  GMutex unicodeMapCacheMutex;
+  GMutex cMapCacheMutex;
+#endif
+};
+
+#endif
diff --git a/xpdf/ImageOutputDev.cc b/xpdf/ImageOutputDev.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/ImageOutputDev.cc
@@ -0,0 +1,195 @@
+//========================================================================
+//
+// ImageOutputDev.cc
+//
+// Copyright 1998-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <ctype.h>
+#include "gmem.h"
+#include "config.h"
+#include "Error.h"
+#include "GfxState.h"
+#include "Object.h"
+#include "Stream.h"
+#include "ImageOutputDev.h"
+
+ImageOutputDev::ImageOutputDev(char *fileRootA, GBool dumpJPEGA) {
+  fileRoot = copyString(fileRootA);
+  fileName = (char *)gmalloc(strlen(fileRoot) + 20);
+  dumpJPEG = dumpJPEGA;
+  imgNum = 0;
+  ok = gTrue;
+}
+
+ImageOutputDev::~ImageOutputDev() {
+  gfree(fileName);
+  gfree(fileRoot);
+}
+
+void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
+				   int width, int height, GBool invert,
+				   GBool inlineImg) {
+  FILE *f;
+  int c;
+  int size, i;
+
+  // dump JPEG file
+  if (dumpJPEG && str->getKind() == strDCT && !inlineImg) {
+
+    // open the image file
+    sprintf(fileName, "%s-%03d.jpg", fileRoot, imgNum);
+    ++imgNum;
+    if (!(f = fopen(fileName, "wb"))) {
+      error(-1, "Couldn't open image file '%s'", fileName);
+      return;
+    }
+
+    // initialize stream
+    str = ((DCTStream *)str)->getRawStream();
+    str->reset();
+
+    // copy the stream
+    while ((c = str->getChar()) != EOF)
+      fputc(c, f);
+
+    str->close();
+    fclose(f);
+
+  // dump PBM file
+  } else {
+
+    // open the image file and write the PBM header
+    sprintf(fileName, "%s-%03d.pbm", fileRoot, imgNum);
+    ++imgNum;
+    if (!(f = fopen(fileName, "wb"))) {
+      error(-1, "Couldn't open image file '%s'", fileName);
+      return;
+    }
+    fprintf(f, "P4\n");
+    fprintf(f, "%d %d\n", width, height);
+
+    // initialize stream
+    str->reset();
+
+    // copy the stream
+    size = height * ((width + 7) / 8);
+    for (i = 0; i < size; ++i) {
+      fputc(str->getChar(), f);
+    }
+
+    str->close();
+    fclose(f);
+  }
+}
+
+void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
+			       int width, int height,
+			       GfxImageColorMap *colorMap,
+			       int *maskColors, GBool inlineImg) {
+  FILE *f;
+  ImageStream *imgStr;
+  Guchar *p;
+  GfxRGB rgb;
+  int x, y;
+  int c;
+  int size, i;
+
+  // dump JPEG file
+  if (dumpJPEG && str->getKind() == strDCT &&
+      (colorMap->getNumPixelComps() == 1 ||
+       colorMap->getNumPixelComps() == 3) &&
+      !inlineImg) {
+
+    // open the image file
+    sprintf(fileName, "%s-%03d.jpg", fileRoot, imgNum);
+    ++imgNum;
+    if (!(f = fopen(fileName, "wb"))) {
+      error(-1, "Couldn't open image file '%s'", fileName);
+      return;
+    }
+
+    // initialize stream
+    str = ((DCTStream *)str)->getRawStream();
+    str->reset();
+
+    // copy the stream
+    while ((c = str->getChar()) != EOF)
+      fputc(c, f);
+
+    str->close();
+    fclose(f);
+
+  // dump PBM file
+  } else if (colorMap->getNumPixelComps() == 1 &&
+	     colorMap->getBits() == 1) {
+
+    // open the image file and write the PBM header
+    sprintf(fileName, "%s-%03d.pbm", fileRoot, imgNum);
+    ++imgNum;
+    if (!(f = fopen(fileName, "wb"))) {
+      error(-1, "Couldn't open image file '%s'", fileName);
+      return;
+    }
+    fprintf(f, "P4\n");
+    fprintf(f, "%d %d\n", width, height);
+
+    // initialize stream
+    str->reset();
+
+    // copy the stream
+    size = height * ((width + 7) / 8);
+    for (i = 0; i < size; ++i) {
+      fputc(str->getChar() ^ 0xff, f);
+    }
+
+    str->close();
+    fclose(f);
+
+  // dump PPM file
+  } else {
+
+    // open the image file and write the PPM header
+    sprintf(fileName, "%s-%03d.ppm", fileRoot, imgNum);
+    ++imgNum;
+    if (!(f = fopen(fileName, "wb"))) {
+      error(-1, "Couldn't open image file '%s'", fileName);
+      return;
+    }
+    fprintf(f, "P6\n");
+    fprintf(f, "%d %d\n", width, height);
+    fprintf(f, "255\n");
+
+    // initialize stream
+    imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
+			     colorMap->getBits());
+    imgStr->reset();
+
+    // for each line...
+    for (y = 0; y < height; ++y) {
+
+      // write the line
+      p = imgStr->getLine();
+      for (x = 0; x < width; ++x) {
+	colorMap->getRGB(p, &rgb);
+	fputc(colToByte(rgb.r), f);
+	fputc(colToByte(rgb.g), f);
+	fputc(colToByte(rgb.b), f);
+	p += colorMap->getNumPixelComps();
+      }
+    }
+    delete imgStr;
+
+    fclose(f);
+  }
+}
diff --git a/xpdf/ImageOutputDev.h b/xpdf/ImageOutputDev.h
new file mode 100644
--- /dev/null
+++ b/xpdf/ImageOutputDev.h
@@ -0,0 +1,76 @@
+//========================================================================
+//
+// ImageOutputDev.h
+//
+// Copyright 1998-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef IMAGEOUTPUTDEV_H
+#define IMAGEOUTPUTDEV_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <stdio.h>
+#include "gtypes.h"
+#include "OutputDev.h"
+
+class GfxState;
+
+//------------------------------------------------------------------------
+// ImageOutputDev
+//------------------------------------------------------------------------
+
+class ImageOutputDev: public OutputDev {
+public:
+
+  // Create an OutputDev which will write images to files named
+  // <fileRoot>-NNN.<type>.  Normally, all images are written as PBM
+  // (.pbm) or PPM (.ppm) files.  If <dumpJPEG> is set, JPEG images are
+  // written as JPEG (.jpg) files.
+  ImageOutputDev(char *fileRootA, GBool dumpJPEGA);
+
+  // Destructor.
+  virtual ~ImageOutputDev();
+
+  // Check if file was successfully created.
+  virtual GBool isOk() { return ok; }
+
+  // Does this device use beginType3Char/endType3Char?  Otherwise,
+  // text in Type 3 fonts will be drawn with drawChar/drawString.
+  virtual GBool interpretType3Chars() { return gFalse; }
+
+  // Does this device need non-text content?
+  virtual GBool needNonText() { return gTrue; }
+
+  //---- get info about output device
+
+  // Does this device use upside-down coordinates?
+  // (Upside-down means (0,0) is the top left corner of the page.)
+  virtual GBool upsideDown() { return gTrue; }
+
+  // Does this device use drawChar() or drawString()?
+  virtual GBool useDrawChar() { return gFalse; }
+
+  //----- image drawing
+  virtual void drawImageMask(GfxState *state, Object *ref, Stream *str,
+			     int width, int height, GBool invert,
+			     GBool inlineImg);
+  virtual void drawImage(GfxState *state, Object *ref, Stream *str,
+			 int width, int height, GfxImageColorMap *colorMap,
+			 int *maskColors, GBool inlineImg);
+
+private:
+
+  char *fileRoot;		// root of output file names
+  char *fileName;		// buffer for output file names
+  GBool dumpJPEG;		// set to dump native JPEG files
+  int imgNum;			// current image number
+  GBool ok;			// set up ok?
+};
+
+#endif
diff --git a/xpdf/JArithmeticDecoder.cc b/xpdf/JArithmeticDecoder.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/JArithmeticDecoder.cc
@@ -0,0 +1,322 @@
+//========================================================================
+//
+// JArithmeticDecoder.cc
+//
+// Copyright 2002-2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include "Object.h"
+#include "Stream.h"
+#include "JArithmeticDecoder.h"
+
+//------------------------------------------------------------------------
+// JArithmeticDecoderStates
+//------------------------------------------------------------------------
+
+JArithmeticDecoderStats::JArithmeticDecoderStats(int contextSizeA) {
+  contextSize = contextSizeA;
+  cxTab = (Guchar *)gmallocn(contextSize, sizeof(Guchar));
+  reset();
+}
+
+JArithmeticDecoderStats::~JArithmeticDecoderStats() {
+  gfree(cxTab);
+}
+
+JArithmeticDecoderStats *JArithmeticDecoderStats::copy() {
+  JArithmeticDecoderStats *stats;
+
+  stats = new JArithmeticDecoderStats(contextSize);
+  memcpy(stats->cxTab, cxTab, contextSize);
+  return stats;
+}
+
+void JArithmeticDecoderStats::reset() {
+  memset(cxTab, 0, contextSize);
+}
+
+void JArithmeticDecoderStats::copyFrom(JArithmeticDecoderStats *stats) {
+  memcpy(cxTab, stats->cxTab, contextSize);
+}
+
+void JArithmeticDecoderStats::setEntry(Guint cx, int i, int mps) {
+  cxTab[cx] = (i << 1) + mps;
+}
+
+//------------------------------------------------------------------------
+// JArithmeticDecoder
+//------------------------------------------------------------------------
+
+Guint JArithmeticDecoder::qeTab[47] = {
+  0x56010000, 0x34010000, 0x18010000, 0x0AC10000,
+  0x05210000, 0x02210000, 0x56010000, 0x54010000,
+  0x48010000, 0x38010000, 0x30010000, 0x24010000,
+  0x1C010000, 0x16010000, 0x56010000, 0x54010000,
+  0x51010000, 0x48010000, 0x38010000, 0x34010000,
+  0x30010000, 0x28010000, 0x24010000, 0x22010000,
+  0x1C010000, 0x18010000, 0x16010000, 0x14010000,
+  0x12010000, 0x11010000, 0x0AC10000, 0x09C10000,
+  0x08A10000, 0x05210000, 0x04410000, 0x02A10000,
+  0x02210000, 0x01410000, 0x01110000, 0x00850000,
+  0x00490000, 0x00250000, 0x00150000, 0x00090000,
+  0x00050000, 0x00010000, 0x56010000
+};
+
+int JArithmeticDecoder::nmpsTab[47] = {
+   1,  2,  3,  4,  5, 38,  7,  8,  9, 10, 11, 12, 13, 29, 15, 16,
+  17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
+  33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 45, 46
+};
+
+int JArithmeticDecoder::nlpsTab[47] = {
+   1,  6,  9, 12, 29, 33,  6, 14, 14, 14, 17, 18, 20, 21, 14, 14,
+  15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+  30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 46
+};
+
+int JArithmeticDecoder::switchTab[47] = {
+  1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 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
+};
+
+JArithmeticDecoder::JArithmeticDecoder() {
+  str = NULL;
+  dataLen = 0;
+  limitStream = gFalse;
+}
+
+inline Guint JArithmeticDecoder::readByte() {
+  if (limitStream) {
+    --dataLen;
+    if (dataLen < 0) {
+      return 0xff;
+    }
+  }
+  return (Guint)str->getChar() & 0xff;
+}
+
+JArithmeticDecoder::~JArithmeticDecoder() {
+  cleanup();
+}
+
+void JArithmeticDecoder::start() {
+  buf0 = readByte();
+  buf1 = readByte();
+
+  // INITDEC
+  c = (buf0 ^ 0xff) << 16;
+  byteIn();
+  c <<= 7;
+  ct -= 7;
+  a = 0x80000000;
+}
+
+void JArithmeticDecoder::restart(int dataLenA) {
+  int oldDataLen;
+
+  oldDataLen = dataLen;
+  dataLen = dataLenA;
+  if (oldDataLen == -1) {
+    buf1 = readByte();
+  } else if (oldDataLen <= -2) {
+    buf0 = readByte();
+    buf1 = readByte();
+  }
+}
+
+void JArithmeticDecoder::cleanup() {
+  if (limitStream) {
+    while (dataLen > 0) {
+      buf0 = buf1;
+      buf1 = readByte();
+    }
+  }
+}
+
+int JArithmeticDecoder::decodeBit(Guint context,
+				  JArithmeticDecoderStats *stats) {
+  int bit;
+  Guint qe;
+  int iCX, mpsCX;
+
+  iCX = stats->cxTab[context] >> 1;
+  mpsCX = stats->cxTab[context] & 1;
+  qe = qeTab[iCX];
+  a -= qe;
+  if (c < a) {
+    if (a & 0x80000000) {
+      bit = mpsCX;
+    } else {
+      // MPS_EXCHANGE
+      if (a < qe) {
+	bit = 1 - mpsCX;
+	if (switchTab[iCX]) {
+	  stats->cxTab[context] = (nlpsTab[iCX] << 1) | (1 - mpsCX);
+	} else {
+	  stats->cxTab[context] = (nlpsTab[iCX] << 1) | mpsCX;
+	}
+      } else {
+	bit = mpsCX;
+	stats->cxTab[context] = (nmpsTab[iCX] << 1) | mpsCX;
+      }
+      // RENORMD
+      do {
+	if (ct == 0) {
+	  byteIn();
+	}
+	a <<= 1;
+	c <<= 1;
+	--ct;
+      } while (!(a & 0x80000000));
+    }
+  } else {
+    c -= a;
+    // LPS_EXCHANGE
+    if (a < qe) {
+      bit = mpsCX;
+      stats->cxTab[context] = (nmpsTab[iCX] << 1) | mpsCX;
+    } else {
+      bit = 1 - mpsCX;
+      if (switchTab[iCX]) {
+	stats->cxTab[context] = (nlpsTab[iCX] << 1) | (1 - mpsCX);
+      } else {
+	stats->cxTab[context] = (nlpsTab[iCX] << 1) | mpsCX;
+      }
+    }
+    a = qe;
+    // RENORMD
+    do {
+      if (ct == 0) {
+	byteIn();
+      }
+      a <<= 1;
+      c <<= 1;
+      --ct;
+    } while (!(a & 0x80000000));
+  }
+  return bit;
+}
+
+int JArithmeticDecoder::decodeByte(Guint context,
+				   JArithmeticDecoderStats *stats) {
+  int byte;
+  int i;
+
+  byte = 0;
+  for (i = 0; i < 8; ++i) {
+    byte = (byte << 1) | decodeBit(context, stats);
+  }
+  return byte;
+}
+
+GBool JArithmeticDecoder::decodeInt(int *x, JArithmeticDecoderStats *stats) {
+  int s;
+  Guint v;
+  int i;
+
+  prev = 1;
+  s = decodeIntBit(stats);
+  if (decodeIntBit(stats)) {
+    if (decodeIntBit(stats)) {
+      if (decodeIntBit(stats)) {
+	if (decodeIntBit(stats)) {
+	  if (decodeIntBit(stats)) {
+	    v = 0;
+	    for (i = 0; i < 32; ++i) {
+	      v = (v << 1) | decodeIntBit(stats);
+	    }
+	    v += 4436;
+	  } else {
+	    v = 0;
+	    for (i = 0; i < 12; ++i) {
+	      v = (v << 1) | decodeIntBit(stats);
+	    }
+	    v += 340;
+	  }
+	} else {
+	  v = 0;
+	  for (i = 0; i < 8; ++i) {
+	    v = (v << 1) | decodeIntBit(stats);
+	  }
+	  v += 84;
+	}
+      } else {
+	v = 0;
+	for (i = 0; i < 6; ++i) {
+	  v = (v << 1) | decodeIntBit(stats);
+	}
+	v += 20;
+      }
+    } else {
+      v = decodeIntBit(stats);
+      v = (v << 1) | decodeIntBit(stats);
+      v = (v << 1) | decodeIntBit(stats);
+      v = (v << 1) | decodeIntBit(stats);
+      v += 4;
+    }
+  } else {
+    v = decodeIntBit(stats);
+    v = (v << 1) | decodeIntBit(stats);
+  }
+
+  if (s) {
+    if (v == 0) {
+      return gFalse;
+    }
+    *x = -(int)v;
+  } else {
+    *x = (int)v;
+  }
+  return gTrue;
+}
+
+int JArithmeticDecoder::decodeIntBit(JArithmeticDecoderStats *stats) {
+  int bit;
+
+  bit = decodeBit(prev, stats);
+  if (prev < 0x100) {
+    prev = (prev << 1) | bit;
+  } else {
+    prev = (((prev << 1) | bit) & 0x1ff) | 0x100;
+  }
+  return bit;
+}
+
+Guint JArithmeticDecoder::decodeIAID(Guint codeLen,
+				     JArithmeticDecoderStats *stats) {
+  Guint i;
+  int bit;
+
+  prev = 1;
+  for (i = 0; i < codeLen; ++i) {
+    bit = decodeBit(prev, stats);
+    prev = (prev << 1) | bit;
+  }
+  return prev - (1 << codeLen);
+}
+
+void JArithmeticDecoder::byteIn() {
+  if (buf0 == 0xff) {
+    if (buf1 > 0x8f) {
+      ct = 8;
+    } else {
+      buf0 = buf1;
+      buf1 = readByte();
+      c = c + 0xfe00 - (buf0 << 9);
+      ct = 7;
+    }
+  } else {
+    buf0 = buf1;
+    buf1 = readByte();
+    c = c + 0xff00 - (buf0 << 8);
+    ct = 8;
+  }
+}
diff --git a/xpdf/JArithmeticDecoder.h b/xpdf/JArithmeticDecoder.h
new file mode 100644
--- /dev/null
+++ b/xpdf/JArithmeticDecoder.h
@@ -0,0 +1,109 @@
+//========================================================================
+//
+// JArithmeticDecoder.h
+//
+// Arithmetic decoder used by the JBIG2 and JPEG2000 decoders.
+//
+// Copyright 2002-2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef JARITHMETICDECODER_H
+#define JARITHMETICDECODER_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+
+class Stream;
+
+//------------------------------------------------------------------------
+// JArithmeticDecoderStats
+//------------------------------------------------------------------------
+
+class JArithmeticDecoderStats {
+public:
+
+  JArithmeticDecoderStats(int contextSizeA);
+  ~JArithmeticDecoderStats();
+  JArithmeticDecoderStats *copy();
+  void reset();
+  int getContextSize() { return contextSize; }
+  void copyFrom(JArithmeticDecoderStats *stats);
+  void setEntry(Guint cx, int i, int mps);
+
+private:
+
+  Guchar *cxTab;		// cxTab[cx] = (i[cx] << 1) + mps[cx]
+  int contextSize;
+
+  friend class JArithmeticDecoder;
+};
+
+//------------------------------------------------------------------------
+// JArithmeticDecoder
+//------------------------------------------------------------------------
+
+class JArithmeticDecoder {
+public:
+
+  JArithmeticDecoder();
+  ~JArithmeticDecoder();
+
+  void setStream(Stream *strA)
+    { str = strA; dataLen = 0; limitStream = gFalse; }
+  void setStream(Stream *strA, int dataLenA)
+    { str = strA; dataLen = dataLenA; limitStream = gTrue; }
+
+  // Start decoding on a new stream.  This fills the byte buffers and
+  // runs INITDEC.
+  void start();
+
+  // Restart decoding on an interrupted stream.  This refills the
+  // buffers if needed, but does not run INITDEC.  (This is used in
+  // JPEG 2000 streams when codeblock data is split across multiple
+  // packets/layers.)
+  void restart(int dataLenA);
+
+  // Read any leftover data in the stream.
+  void cleanup();
+
+  // Decode one bit.
+  int decodeBit(Guint context, JArithmeticDecoderStats *stats);
+
+  // Decode eight bits.
+  int decodeByte(Guint context, JArithmeticDecoderStats *stats);
+
+  // Returns false for OOB, otherwise sets *<x> and returns true.
+  GBool decodeInt(int *x, JArithmeticDecoderStats *stats);
+
+  Guint decodeIAID(Guint codeLen,
+		   JArithmeticDecoderStats *stats);
+
+private:
+
+  Guint readByte();
+  int decodeIntBit(JArithmeticDecoderStats *stats);
+  void byteIn();
+
+  static Guint qeTab[47];
+  static int nmpsTab[47];
+  static int nlpsTab[47];
+  static int switchTab[47];
+
+  Guint buf0, buf1;
+  Guint c, a;
+  int ct;
+
+  Guint prev;			// for the integer decoder
+
+  Stream *str;
+  int dataLen;
+  GBool limitStream;
+};
+
+#endif
diff --git a/xpdf/JBIG2Stream.cc b/xpdf/JBIG2Stream.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/JBIG2Stream.cc
@@ -0,0 +1,3413 @@
+//========================================================================
+//
+// JBIG2Stream.cc
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <limits.h>
+#include "GList.h"
+#include "Error.h"
+#include "JArithmeticDecoder.h"
+#include "JBIG2Stream.h"
+
+//~ share these tables
+#include "Stream-CCITT.h"
+
+//------------------------------------------------------------------------
+
+static int contextSize[4] = { 16, 13, 10, 10 };
+static int refContextSize[2] = { 13, 10 };
+
+//------------------------------------------------------------------------
+// JBIG2HuffmanTable
+//------------------------------------------------------------------------
+
+#define jbig2HuffmanLOW 0xfffffffd
+#define jbig2HuffmanOOB 0xfffffffe
+#define jbig2HuffmanEOT 0xffffffff
+
+struct JBIG2HuffmanTable {
+  int val;
+  Guint prefixLen;
+  Guint rangeLen;		// can also be LOW, OOB, or EOT
+  Guint prefix;
+};
+
+JBIG2HuffmanTable huffTableA[] = {
+  {     0, 1,  4,              0x000 },
+  {    16, 2,  8,              0x002 },
+  {   272, 3, 16,              0x006 },
+  { 65808, 3, 32,              0x007 },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableB[] = {
+  {     0, 1,  0,              0x000 },
+  {     1, 2,  0,              0x002 },
+  {     2, 3,  0,              0x006 },
+  {     3, 4,  3,              0x00e },
+  {    11, 5,  6,              0x01e },
+  {    75, 6, 32,              0x03e },
+  {     0, 6, jbig2HuffmanOOB, 0x03f },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableC[] = {
+  {     0, 1,  0,              0x000 },
+  {     1, 2,  0,              0x002 },
+  {     2, 3,  0,              0x006 },
+  {     3, 4,  3,              0x00e },
+  {    11, 5,  6,              0x01e },
+  {     0, 6, jbig2HuffmanOOB, 0x03e },
+  {    75, 7, 32,              0x0fe },
+  {  -256, 8,  8,              0x0fe },
+  {  -257, 8, jbig2HuffmanLOW, 0x0ff },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableD[] = {
+  {     1, 1,  0,              0x000 },
+  {     2, 2,  0,              0x002 },
+  {     3, 3,  0,              0x006 },
+  {     4, 4,  3,              0x00e },
+  {    12, 5,  6,              0x01e },
+  {    76, 5, 32,              0x01f },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableE[] = {
+  {     1, 1,  0,              0x000 },
+  {     2, 2,  0,              0x002 },
+  {     3, 3,  0,              0x006 },
+  {     4, 4,  3,              0x00e },
+  {    12, 5,  6,              0x01e },
+  {    76, 6, 32,              0x03e },
+  {  -255, 7,  8,              0x07e },
+  {  -256, 7, jbig2HuffmanLOW, 0x07f },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableF[] = {
+  {     0, 2,  7,              0x000 },
+  {   128, 3,  7,              0x002 },
+  {   256, 3,  8,              0x003 },
+  { -1024, 4,  9,              0x008 },
+  {  -512, 4,  8,              0x009 },
+  {  -256, 4,  7,              0x00a },
+  {   -32, 4,  5,              0x00b },
+  {   512, 4,  9,              0x00c },
+  {  1024, 4, 10,              0x00d },
+  { -2048, 5, 10,              0x01c },
+  {  -128, 5,  6,              0x01d },
+  {   -64, 5,  5,              0x01e },
+  { -2049, 6, jbig2HuffmanLOW, 0x03e },
+  {  2048, 6, 32,              0x03f },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableG[] = {
+  {  -512, 3,  8,              0x000 },
+  {   256, 3,  8,              0x001 },
+  {   512, 3,  9,              0x002 },
+  {  1024, 3, 10,              0x003 },
+  { -1024, 4,  9,              0x008 },
+  {  -256, 4,  7,              0x009 },
+  {   -32, 4,  5,              0x00a },
+  {     0, 4,  5,              0x00b },
+  {   128, 4,  7,              0x00c },
+  {  -128, 5,  6,              0x01a },
+  {   -64, 5,  5,              0x01b },
+  {    32, 5,  5,              0x01c },
+  {    64, 5,  6,              0x01d },
+  { -1025, 5, jbig2HuffmanLOW, 0x01e },
+  {  2048, 5, 32,              0x01f },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableH[] = {
+  {     0, 2,  1,              0x000 },
+  {     0, 2, jbig2HuffmanOOB, 0x001 },
+  {     4, 3,  4,              0x004 },
+  {    -1, 4,  0,              0x00a },
+  {    22, 4,  4,              0x00b },
+  {    38, 4,  5,              0x00c },
+  {     2, 5,  0,              0x01a },
+  {    70, 5,  6,              0x01b },
+  {   134, 5,  7,              0x01c },
+  {     3, 6,  0,              0x03a },
+  {    20, 6,  1,              0x03b },
+  {   262, 6,  7,              0x03c },
+  {   646, 6, 10,              0x03d },
+  {    -2, 7,  0,              0x07c },
+  {   390, 7,  8,              0x07d },
+  {   -15, 8,  3,              0x0fc },
+  {    -5, 8,  1,              0x0fd },
+  {    -7, 9,  1,              0x1fc },
+  {    -3, 9,  0,              0x1fd },
+  {   -16, 9, jbig2HuffmanLOW, 0x1fe },
+  {  1670, 9, 32,              0x1ff },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableI[] = {
+  {     0, 2, jbig2HuffmanOOB, 0x000 },
+  {    -1, 3,  1,              0x002 },
+  {     1, 3,  1,              0x003 },
+  {     7, 3,  5,              0x004 },
+  {    -3, 4,  1,              0x00a },
+  {    43, 4,  5,              0x00b },
+  {    75, 4,  6,              0x00c },
+  {     3, 5,  1,              0x01a },
+  {   139, 5,  7,              0x01b },
+  {   267, 5,  8,              0x01c },
+  {     5, 6,  1,              0x03a },
+  {    39, 6,  2,              0x03b },
+  {   523, 6,  8,              0x03c },
+  {  1291, 6, 11,              0x03d },
+  {    -5, 7,  1,              0x07c },
+  {   779, 7,  9,              0x07d },
+  {   -31, 8,  4,              0x0fc },
+  {   -11, 8,  2,              0x0fd },
+  {   -15, 9,  2,              0x1fc },
+  {    -7, 9,  1,              0x1fd },
+  {   -32, 9, jbig2HuffmanLOW, 0x1fe },
+  {  3339, 9, 32,              0x1ff },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableJ[] = {
+  {    -2, 2,  2,              0x000 },
+  {     6, 2,  6,              0x001 },
+  {     0, 2, jbig2HuffmanOOB, 0x002 },
+  {    -3, 5,  0,              0x018 },
+  {     2, 5,  0,              0x019 },
+  {    70, 5,  5,              0x01a },
+  {     3, 6,  0,              0x036 },
+  {   102, 6,  5,              0x037 },
+  {   134, 6,  6,              0x038 },
+  {   198, 6,  7,              0x039 },
+  {   326, 6,  8,              0x03a },
+  {   582, 6,  9,              0x03b },
+  {  1094, 6, 10,              0x03c },
+  {   -21, 7,  4,              0x07a },
+  {    -4, 7,  0,              0x07b },
+  {     4, 7,  0,              0x07c },
+  {  2118, 7, 11,              0x07d },
+  {    -5, 8,  0,              0x0fc },
+  {     5, 8,  0,              0x0fd },
+  {   -22, 8, jbig2HuffmanLOW, 0x0fe },
+  {  4166, 8, 32,              0x0ff },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableK[] = {
+  {     1, 1,  0,              0x000 },
+  {     2, 2,  1,              0x002 },
+  {     4, 4,  0,              0x00c },
+  {     5, 4,  1,              0x00d },
+  {     7, 5,  1,              0x01c },
+  {     9, 5,  2,              0x01d },
+  {    13, 6,  2,              0x03c },
+  {    17, 7,  2,              0x07a },
+  {    21, 7,  3,              0x07b },
+  {    29, 7,  4,              0x07c },
+  {    45, 7,  5,              0x07d },
+  {    77, 7,  6,              0x07e },
+  {   141, 7, 32,              0x07f },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableL[] = {
+  {     1, 1,  0,              0x000 },
+  {     2, 2,  0,              0x002 },
+  {     3, 3,  1,              0x006 },
+  {     5, 5,  0,              0x01c },
+  {     6, 5,  1,              0x01d },
+  {     8, 6,  1,              0x03c },
+  {    10, 7,  0,              0x07a },
+  {    11, 7,  1,              0x07b },
+  {    13, 7,  2,              0x07c },
+  {    17, 7,  3,              0x07d },
+  {    25, 7,  4,              0x07e },
+  {    41, 8,  5,              0x0fe },
+  {    73, 8, 32,              0x0ff },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableM[] = {
+  {     1, 1,  0,              0x000 },
+  {     2, 3,  0,              0x004 },
+  {     7, 3,  3,              0x005 },
+  {     3, 4,  0,              0x00c },
+  {     5, 4,  1,              0x00d },
+  {     4, 5,  0,              0x01c },
+  {    15, 6,  1,              0x03a },
+  {    17, 6,  2,              0x03b },
+  {    21, 6,  3,              0x03c },
+  {    29, 6,  4,              0x03d },
+  {    45, 6,  5,              0x03e },
+  {    77, 7,  6,              0x07e },
+  {   141, 7, 32,              0x07f },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableN[] = {
+  {     0, 1,  0,              0x000 },
+  {    -2, 3,  0,              0x004 },
+  {    -1, 3,  0,              0x005 },
+  {     1, 3,  0,              0x006 },
+  {     2, 3,  0,              0x007 },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+JBIG2HuffmanTable huffTableO[] = {
+  {     0, 1,  0,              0x000 },
+  {    -1, 3,  0,              0x004 },
+  {     1, 3,  0,              0x005 },
+  {    -2, 4,  0,              0x00c },
+  {     2, 4,  0,              0x00d },
+  {    -4, 5,  1,              0x01c },
+  {     3, 5,  1,              0x01d },
+  {    -8, 6,  2,              0x03c },
+  {     5, 6,  2,              0x03d },
+  {   -24, 7,  4,              0x07c },
+  {     9, 7,  4,              0x07d },
+  {   -25, 7, jbig2HuffmanLOW, 0x07e },
+  {    25, 7, 32,              0x07f },
+  {     0, 0, jbig2HuffmanEOT, 0     }
+};
+
+//------------------------------------------------------------------------
+// JBIG2HuffmanDecoder
+//------------------------------------------------------------------------
+
+class JBIG2HuffmanDecoder {
+public:
+
+  JBIG2HuffmanDecoder();
+  ~JBIG2HuffmanDecoder();
+  void setStream(Stream *strA) { str = strA; }
+
+  void reset();
+
+  // Returns false for OOB, otherwise sets *<x> and returns true.
+  GBool decodeInt(int *x, JBIG2HuffmanTable *table);
+
+  Guint readBits(Guint n);
+  Guint readBit();
+
+  // Sort the table by prefix length and assign prefix values.
+  void buildTable(JBIG2HuffmanTable *table, Guint len);
+
+private:
+
+  Stream *str;
+  Guint buf;
+  Guint bufLen;
+};
+
+JBIG2HuffmanDecoder::JBIG2HuffmanDecoder() {
+  str = NULL;
+  reset();
+}
+
+JBIG2HuffmanDecoder::~JBIG2HuffmanDecoder() {
+}
+
+void JBIG2HuffmanDecoder::reset() {
+  buf = 0;
+  bufLen = 0;
+}
+
+//~ optimize this
+GBool JBIG2HuffmanDecoder::decodeInt(int *x, JBIG2HuffmanTable *table) {
+  Guint i, len, prefix;
+
+  i = 0;
+  len = 0;
+  prefix = 0;
+  while (table[i].rangeLen != jbig2HuffmanEOT) {
+    while (len < table[i].prefixLen) {
+      prefix = (prefix << 1) | readBit();
+      ++len;
+    }
+    if (prefix == table[i].prefix) {
+      if (table[i].rangeLen == jbig2HuffmanOOB) {
+	return gFalse;
+      }
+      if (table[i].rangeLen == jbig2HuffmanLOW) {
+	*x = table[i].val - readBits(32);
+      } else if (table[i].rangeLen > 0) {
+	*x = table[i].val + readBits(table[i].rangeLen);
+      } else {
+	*x = table[i].val;
+      }
+      return gTrue;
+    }
+    ++i;
+  }
+  return gFalse;
+}
+
+Guint JBIG2HuffmanDecoder::readBits(Guint n) {
+  Guint x, mask, nLeft;
+
+  mask = (n == 32) ? 0xffffffff : ((1 << n) - 1);
+  if (bufLen >= n) {
+    x = (buf >> (bufLen - n)) & mask;
+    bufLen -= n;
+  } else {
+    x = buf & ((1 << bufLen) - 1);
+    nLeft = n - bufLen;
+    bufLen = 0;
+    while (nLeft >= 8) {
+      x = (x << 8) | (str->getChar() & 0xff);
+      nLeft -= 8;
+    }
+    if (nLeft > 0) {
+      buf = str->getChar();
+      bufLen = 8 - nLeft;
+      x = (x << nLeft) | ((buf >> bufLen) & ((1 << nLeft) - 1));
+    }
+  }
+  return x;
+}
+
+Guint JBIG2HuffmanDecoder::readBit() {
+  if (bufLen == 0) {
+    buf = str->getChar();
+    bufLen = 8;
+  }
+  --bufLen;
+  return (buf >> bufLen) & 1;
+}
+
+void JBIG2HuffmanDecoder::buildTable(JBIG2HuffmanTable *table, Guint len) {
+  Guint i, j, k, prefix;
+  JBIG2HuffmanTable tab;
+
+  // stable selection sort:
+  // - entries with prefixLen > 0, in ascending prefixLen order
+  // - entry with prefixLen = 0, rangeLen = EOT
+  // - all other entries with prefixLen = 0
+  // (on entry, table[len] has prefixLen = 0, rangeLen = EOT)
+  for (i = 0; i < len; ++i) {
+    for (j = i; j < len && table[j].prefixLen == 0; ++j) ;
+    if (j == len) {
+      break;
+    }
+    for (k = j + 1; k < len; ++k) {
+      if (table[k].prefixLen > 0 &&
+	  table[k].prefixLen < table[j].prefixLen) {
+	j = k;
+      }
+    }
+    if (j != i) {
+      tab = table[j];
+      for (k = j; k > i; --k) {
+	table[k] = table[k - 1];
+      }
+      table[i] = tab;
+    }
+  }
+  table[i] = table[len];
+
+  // assign prefixes
+  i = 0;
+  prefix = 0;
+  table[i++].prefix = prefix++;
+  for (; table[i].rangeLen != jbig2HuffmanEOT; ++i) {
+    prefix <<= table[i].prefixLen - table[i-1].prefixLen;
+    table[i].prefix = prefix++;
+  }
+}
+
+//------------------------------------------------------------------------
+// JBIG2MMRDecoder
+//------------------------------------------------------------------------
+
+class JBIG2MMRDecoder {
+public:
+
+  JBIG2MMRDecoder();
+  ~JBIG2MMRDecoder();
+  void setStream(Stream *strA) { str = strA; }
+  void reset();
+  int get2DCode();
+  int getBlackCode();
+  int getWhiteCode();
+  Guint get24Bits();
+  void skipTo(Guint length);
+
+private:
+
+  Stream *str;
+  Guint buf;
+  Guint bufLen;
+  Guint nBytesRead;
+};
+
+JBIG2MMRDecoder::JBIG2MMRDecoder() {
+  str = NULL;
+  reset();
+}
+
+JBIG2MMRDecoder::~JBIG2MMRDecoder() {
+}
+
+void JBIG2MMRDecoder::reset() {
+  buf = 0;
+  bufLen = 0;
+  nBytesRead = 0;
+}
+
+int JBIG2MMRDecoder::get2DCode() {
+  CCITTCode *p;
+
+  if (bufLen == 0) {
+    buf = str->getChar() & 0xff;
+    bufLen = 8;
+    ++nBytesRead;
+    p = &twoDimTab1[(buf >> 1) & 0x7f];
+  } else if (bufLen == 8) {
+    p = &twoDimTab1[(buf >> 1) & 0x7f];
+  } else {
+    p = &twoDimTab1[(buf << (7 - bufLen)) & 0x7f];
+    if (p->bits < 0 || p->bits > (int)bufLen) {
+      buf = (buf << 8) | (str->getChar() & 0xff);
+      bufLen += 8;
+      ++nBytesRead;
+      p = &twoDimTab1[(buf >> (bufLen - 7)) & 0x7f];
+    }
+  }
+  if (p->bits < 0) {
+    error(str->getPos(), "Bad two dim code in JBIG2 MMR stream");
+    return 0;
+  }
+  bufLen -= p->bits;
+  return p->n;
+}
+
+int JBIG2MMRDecoder::getWhiteCode() {
+  CCITTCode *p;
+  Guint code;
+
+  if (bufLen == 0) {
+    buf = str->getChar() & 0xff;
+    bufLen = 8;
+    ++nBytesRead;
+  }
+  while (1) {
+    if (bufLen >= 7 && ((buf >> (bufLen - 7)) & 0x7f) == 0) {
+      if (bufLen <= 12) {
+	code = buf << (12 - bufLen);
+      } else {
+	code = buf >> (bufLen - 12);
+      }
+      p = &whiteTab1[code & 0x1f];
+    } else {
+      if (bufLen <= 9) {
+	code = buf << (9 - bufLen);
+      } else {
+	code = buf >> (bufLen - 9);
+      }
+      p = &whiteTab2[code & 0x1ff];
+    }
+    if (p->bits > 0 && p->bits <= (int)bufLen) {
+      bufLen -= p->bits;
+      return p->n;
+    }
+    if (bufLen >= 12) {
+      break;
+    }
+    buf = (buf << 8) | (str->getChar() & 0xff);
+    bufLen += 8;
+    ++nBytesRead;
+  }
+  error(str->getPos(), "Bad white code in JBIG2 MMR stream");
+  // eat a bit and return a positive number so that the caller doesn't
+  // go into an infinite loop
+  --bufLen;
+  return 1;
+}
+
+int JBIG2MMRDecoder::getBlackCode() {
+  CCITTCode *p;
+  Guint code;
+
+  if (bufLen == 0) {
+    buf = str->getChar() & 0xff;
+    bufLen = 8;
+    ++nBytesRead;
+  }
+  while (1) {
+    if (bufLen >= 6 && ((buf >> (bufLen - 6)) & 0x3f) == 0) {
+      if (bufLen <= 13) {
+	code = buf << (13 - bufLen);
+      } else {
+	code = buf >> (bufLen - 13);
+      }
+      p = &blackTab1[code & 0x7f];
+    } else if (bufLen >= 4 && ((buf >> (bufLen - 4)) & 0x0f) == 0) {
+      if (bufLen <= 12) {
+	code = buf << (12 - bufLen);
+      } else {
+	code = buf >> (bufLen - 12);
+      }
+      p = &blackTab2[(code & 0xff) - 64];
+    } else {
+      if (bufLen <= 6) {
+	code = buf << (6 - bufLen);
+      } else {
+	code = buf >> (bufLen - 6);
+      }
+      p = &blackTab3[code & 0x3f];
+    }
+    if (p->bits > 0 && p->bits <= (int)bufLen) {
+      bufLen -= p->bits;
+      return p->n;
+    }
+    if (bufLen >= 13) {
+      break;
+    }
+    buf = (buf << 8) | (str->getChar() & 0xff);
+    bufLen += 8;
+    ++nBytesRead;
+  }
+  error(str->getPos(), "Bad black code in JBIG2 MMR stream");
+  // eat a bit and return a positive number so that the caller doesn't
+  // go into an infinite loop
+  --bufLen;
+  return 1;
+}
+
+Guint JBIG2MMRDecoder::get24Bits() {
+  while (bufLen < 24) {
+    buf = (buf << 8) | (str->getChar() & 0xff);
+    bufLen += 8;
+    ++nBytesRead;
+  }
+  return (buf >> (bufLen - 24)) & 0xffffff;
+}
+
+void JBIG2MMRDecoder::skipTo(Guint length) {
+  while (nBytesRead < length) {
+    str->getChar();
+    ++nBytesRead;
+  }
+}
+
+//------------------------------------------------------------------------
+// JBIG2Segment
+//------------------------------------------------------------------------
+
+enum JBIG2SegmentType {
+  jbig2SegBitmap,
+  jbig2SegSymbolDict,
+  jbig2SegPatternDict,
+  jbig2SegCodeTable
+};
+
+class JBIG2Segment {
+public:
+
+  JBIG2Segment(Guint segNumA) { segNum = segNumA; }
+  virtual ~JBIG2Segment() {}
+  void setSegNum(Guint segNumA) { segNum = segNumA; }
+  Guint getSegNum() { return segNum; }
+  virtual JBIG2SegmentType getType() = 0;
+
+private:
+
+  Guint segNum;
+};
+
+//------------------------------------------------------------------------
+// JBIG2Bitmap
+//------------------------------------------------------------------------
+
+struct JBIG2BitmapPtr {
+  Guchar *p;
+  int shift;
+  int x;
+};
+
+class JBIG2Bitmap: public JBIG2Segment {
+public:
+
+  JBIG2Bitmap(Guint segNumA, int wA, int hA);
+  virtual ~JBIG2Bitmap();
+  virtual JBIG2SegmentType getType() { return jbig2SegBitmap; }
+  JBIG2Bitmap *copy() { return new JBIG2Bitmap(0, this); }
+  JBIG2Bitmap *getSlice(Guint x, Guint y, Guint wA, Guint hA);
+  void expand(int newH, Guint pixel);
+  void clearToZero();
+  void clearToOne();
+  int getWidth() { return w; }
+  int getHeight() { return h; }
+  int getPixel(int x, int y)
+    { return (x < 0 || x >= w || y < 0 || y >= h) ? 0 :
+             (data[y * line + (x >> 3)] >> (7 - (x & 7))) & 1; }
+  void setPixel(int x, int y)
+    { data[y * line + (x >> 3)] |= 1 << (7 - (x & 7)); }
+  void clearPixel(int x, int y)
+    { data[y * line + (x >> 3)] &= 0x7f7f >> (x & 7); }
+  void getPixelPtr(int x, int y, JBIG2BitmapPtr *ptr);
+  int nextPixel(JBIG2BitmapPtr *ptr);
+  void duplicateRow(int yDest, int ySrc);
+  void combine(JBIG2Bitmap *bitmap, int x, int y, Guint combOp);
+  Guchar *getDataPtr() { return data; }
+  int getDataSize() { return h * line; }
+
+private:
+
+  JBIG2Bitmap(Guint segNumA, JBIG2Bitmap *bitmap);
+
+  int w, h, line;
+  Guchar *data;
+};
+
+JBIG2Bitmap::JBIG2Bitmap(Guint segNumA, int wA, int hA):
+  JBIG2Segment(segNumA)
+{
+  w = wA;
+  h = hA;
+  line = (wA + 7) >> 3;
+  if (w <= 0 || h <= 0 || line <= 0 || h >= (INT_MAX - 1) / line) {
+    data = NULL;
+    return;
+  }
+  // need to allocate one extra guard byte for use in combine()
+  data = (Guchar *)gmalloc(h * line + 1);
+  data[h * line] = 0;
+}
+
+JBIG2Bitmap::JBIG2Bitmap(Guint segNumA, JBIG2Bitmap *bitmap):
+  JBIG2Segment(segNumA)
+{
+  w = bitmap->w;
+  h = bitmap->h;
+  line = bitmap->line;
+  if (w <= 0 || h <= 0 || line <= 0 || h >= (INT_MAX - 1) / line) {
+    data = NULL;
+    return;
+  }
+  // need to allocate one extra guard byte for use in combine()
+  data = (Guchar *)gmalloc(h * line + 1);
+  memcpy(data, bitmap->data, h * line);
+  data[h * line] = 0;
+}
+
+JBIG2Bitmap::~JBIG2Bitmap() {
+  gfree(data);
+}
+
+//~ optimize this
+JBIG2Bitmap *JBIG2Bitmap::getSlice(Guint x, Guint y, Guint wA, Guint hA) {
+  JBIG2Bitmap *slice;
+  Guint xx, yy;
+
+  slice = new JBIG2Bitmap(0, wA, hA);
+  slice->clearToZero();
+  for (yy = 0; yy < hA; ++yy) {
+    for (xx = 0; xx < wA; ++xx) {
+      if (getPixel(x + xx, y + yy)) {
+	slice->setPixel(xx, yy);
+      }
+    }
+  }
+  return slice;
+}
+
+void JBIG2Bitmap::expand(int newH, Guint pixel) {
+  if (newH <= h || line <= 0 || newH >= (INT_MAX - 1) / line) {
+    return;
+  }
+  // need to allocate one extra guard byte for use in combine()
+  data = (Guchar *)grealloc(data, newH * line + 1);
+  if (pixel) {
+    memset(data + h * line, 0xff, (newH - h) * line);
+  } else {
+    memset(data + h * line, 0x00, (newH - h) * line);
+  }
+  h = newH;
+  data[h * line] = 0;
+}
+
+void JBIG2Bitmap::clearToZero() {
+  memset(data, 0, h * line);
+}
+
+void JBIG2Bitmap::clearToOne() {
+  memset(data, 0xff, h * line);
+}
+
+inline void JBIG2Bitmap::getPixelPtr(int x, int y, JBIG2BitmapPtr *ptr) {
+  if (y < 0 || y >= h || x >= w) {
+    ptr->p = NULL;
+  } else if (x < 0) {
+    ptr->p = &data[y * line];
+    ptr->shift = 7;
+    ptr->x = x;
+  } else {
+    ptr->p = &data[y * line + (x >> 3)];
+    ptr->shift = 7 - (x & 7);
+    ptr->x = x;
+  }
+}
+
+inline int JBIG2Bitmap::nextPixel(JBIG2BitmapPtr *ptr) {
+  int pix;
+
+  if (!ptr->p) {
+    pix = 0;
+  } else if (ptr->x < 0) {
+    ++ptr->x;
+    pix = 0;
+  } else {
+    pix = (*ptr->p >> ptr->shift) & 1;
+    if (++ptr->x == w) {
+      ptr->p = NULL;
+    } else if (ptr->shift == 0) {
+      ++ptr->p;
+      ptr->shift = 7;
+    } else {
+      --ptr->shift;
+    }
+  }
+  return pix;
+}
+
+void JBIG2Bitmap::duplicateRow(int yDest, int ySrc) {
+  memcpy(data + yDest * line, data + ySrc * line, line);
+}
+
+void JBIG2Bitmap::combine(JBIG2Bitmap *bitmap, int x, int y,
+			  Guint combOp) {
+  int x0, x1, y0, y1, xx, yy;
+  Guchar *srcPtr, *destPtr;
+  Guint src0, src1, src, dest, s1, s2, m1, m2, m3;
+  GBool oneByte;
+
+  if (y < 0) {
+    y0 = -y;
+  } else {
+    y0 = 0;
+  }
+  if (y + bitmap->h > h) {
+    y1 = h - y;
+  } else {
+    y1 = bitmap->h;
+  }
+  if (y0 >= y1) {
+    return;
+  }
+
+  if (x >= 0) {
+    x0 = x & ~7;
+  } else {
+    x0 = 0;
+  }
+  x1 = x + bitmap->w;
+  if (x1 > w) {
+    x1 = w;
+  }
+  if (x0 >= x1) {
+    return;
+  }
+
+  s1 = x & 7;
+  s2 = 8 - s1;
+  m1 = 0xff >> (x1 & 7);
+  m2 = 0xff << (((x1 & 7) == 0) ? 0 : 8 - (x1 & 7));
+  m3 = (0xff >> s1) & m2;
+
+  oneByte = x0 == ((x1 - 1) & ~7);
+
+  for (yy = y0; yy < y1; ++yy) {
+
+    // one byte per line -- need to mask both left and right side
+    if (oneByte) {
+      if (x >= 0) {
+	destPtr = data + (y + yy) * line + (x >> 3);
+	srcPtr = bitmap->data + yy * bitmap->line;
+	dest = *destPtr;
+	src1 = *srcPtr;
+	switch (combOp) {
+	case 0: // or
+	  dest |= (src1 >> s1) & m2;
+	  break;
+	case 1: // and
+	  dest &= ((0xff00 | src1) >> s1) | m1;
+	  break;
+	case 2: // xor
+	  dest ^= (src1 >> s1) & m2;
+	  break;
+	case 3: // xnor
+	  dest ^= ((src1 ^ 0xff) >> s1) & m2;
+	  break;
+	case 4: // replace
+	  dest = (dest & ~m3) | ((src1 >> s1) & m3);
+	  break;
+	}
+	*destPtr = dest;
+      } else {
+	destPtr = data + (y + yy) * line;
+	srcPtr = bitmap->data + yy * bitmap->line + (-x >> 3);
+	dest = *destPtr;
+	src1 = *srcPtr;
+	switch (combOp) {
+	case 0: // or
+	  dest |= src1 & m2;
+	  break;
+	case 1: // and
+	  dest &= src1 | m1;
+	  break;
+	case 2: // xor
+	  dest ^= src1 & m2;
+	  break;
+	case 3: // xnor
+	  dest ^= (src1 ^ 0xff) & m2;
+	  break;
+	case 4: // replace
+	  dest = (src1 & m2) | (dest & m1);
+	  break;
+	}
+	*destPtr = dest;
+      }
+
+    // multiple bytes per line -- need to mask left side of left-most
+    // byte and right side of right-most byte
+    } else {
+
+      // left-most byte
+      if (x >= 0) {
+	destPtr = data + (y + yy) * line + (x >> 3);
+	srcPtr = bitmap->data + yy * bitmap->line;
+	src1 = *srcPtr++;
+	dest = *destPtr;
+	switch (combOp) {
+	case 0: // or
+	  dest |= src1 >> s1;
+	  break;
+	case 1: // and
+	  dest &= (0xff00 | src1) >> s1;
+	  break;
+	case 2: // xor
+	  dest ^= src1 >> s1;
+	  break;
+	case 3: // xnor
+	  dest ^= (src1 ^ 0xff) >> s1;
+	  break;
+	case 4: // replace
+	  dest = (dest & (0xff << s2)) | (src1 >> s1);
+	  break;
+	}
+	*destPtr++ = dest;
+	xx = x0 + 8;
+      } else {
+	destPtr = data + (y + yy) * line;
+	srcPtr = bitmap->data + yy * bitmap->line + (-x >> 3);
+	src1 = *srcPtr++;
+	xx = x0;
+      }
+
+      // middle bytes
+      for (; xx < x1 - 8; xx += 8) {
+	dest = *destPtr;
+	src0 = src1;
+	src1 = *srcPtr++;
+	src = (((src0 << 8) | src1) >> s1) & 0xff;
+	switch (combOp) {
+	case 0: // or
+	  dest |= src;
+	  break;
+	case 1: // and
+	  dest &= src;
+	  break;
+	case 2: // xor
+	  dest ^= src;
+	  break;
+	case 3: // xnor
+	  dest ^= src ^ 0xff;
+	  break;
+	case 4: // replace
+	  dest = src;
+	  break;
+	}
+	*destPtr++ = dest;
+      }
+
+      // right-most byte
+      // note: this last byte (src1) may not actually be used, depending
+      // on the values of s1, m1, and m2 - and in fact, it may be off
+      // the edge of the source bitmap, which means we need to allocate
+      // one extra guard byte at the end of each bitmap
+      dest = *destPtr;
+      src0 = src1;
+      src1 = *srcPtr++;
+      src = (((src0 << 8) | src1) >> s1) & 0xff;
+      switch (combOp) {
+      case 0: // or
+	dest |= src & m2;
+	break;
+      case 1: // and
+	dest &= src | m1;
+	break;
+      case 2: // xor
+	dest ^= src & m2;
+	break;
+      case 3: // xnor
+	dest ^= (src ^ 0xff) & m2;
+	break;
+      case 4: // replace
+	dest = (src & m2) | (dest & m1);
+	break;
+      }
+      *destPtr = dest;
+    }
+  }
+}
+
+//------------------------------------------------------------------------
+// JBIG2SymbolDict
+//------------------------------------------------------------------------
+
+class JBIG2SymbolDict: public JBIG2Segment {
+public:
+
+  JBIG2SymbolDict(Guint segNumA, Guint sizeA);
+  virtual ~JBIG2SymbolDict();
+  virtual JBIG2SegmentType getType() { return jbig2SegSymbolDict; }
+  Guint getSize() { return size; }
+  void setBitmap(Guint idx, JBIG2Bitmap *bitmap) { bitmaps[idx] = bitmap; }
+  JBIG2Bitmap *getBitmap(Guint idx) { return bitmaps[idx]; }
+  void setGenericRegionStats(JArithmeticDecoderStats *stats)
+    { genericRegionStats = stats; }
+  void setRefinementRegionStats(JArithmeticDecoderStats *stats)
+    { refinementRegionStats = stats; }
+  JArithmeticDecoderStats *getGenericRegionStats()
+    { return genericRegionStats; }
+  JArithmeticDecoderStats *getRefinementRegionStats()
+    { return refinementRegionStats; }
+
+private:
+
+  Guint size;
+  JBIG2Bitmap **bitmaps;
+  JArithmeticDecoderStats *genericRegionStats;
+  JArithmeticDecoderStats *refinementRegionStats;
+};
+
+JBIG2SymbolDict::JBIG2SymbolDict(Guint segNumA, Guint sizeA):
+  JBIG2Segment(segNumA)
+{
+  size = sizeA;
+  bitmaps = (JBIG2Bitmap **)gmallocn(size, sizeof(JBIG2Bitmap *));
+  genericRegionStats = NULL;
+  refinementRegionStats = NULL;
+}
+
+JBIG2SymbolDict::~JBIG2SymbolDict() {
+  Guint i;
+
+  for (i = 0; i < size; ++i) {
+    delete bitmaps[i];
+  }
+  gfree(bitmaps);
+  if (genericRegionStats) {
+    delete genericRegionStats;
+  }
+  if (refinementRegionStats) {
+    delete refinementRegionStats;
+  }
+}
+
+//------------------------------------------------------------------------
+// JBIG2PatternDict
+//------------------------------------------------------------------------
+
+class JBIG2PatternDict: public JBIG2Segment {
+public:
+
+  JBIG2PatternDict(Guint segNumA, Guint sizeA);
+  virtual ~JBIG2PatternDict();
+  virtual JBIG2SegmentType getType() { return jbig2SegPatternDict; }
+  Guint getSize() { return size; }
+  void setBitmap(Guint idx, JBIG2Bitmap *bitmap) { bitmaps[idx] = bitmap; }
+  JBIG2Bitmap *getBitmap(Guint idx) { return bitmaps[idx]; }
+
+private:
+
+  Guint size;
+  JBIG2Bitmap **bitmaps;
+};
+
+JBIG2PatternDict::JBIG2PatternDict(Guint segNumA, Guint sizeA):
+  JBIG2Segment(segNumA)
+{
+  size = sizeA;
+  bitmaps = (JBIG2Bitmap **)gmallocn(size, sizeof(JBIG2Bitmap *));
+}
+
+JBIG2PatternDict::~JBIG2PatternDict() {
+  Guint i;
+
+  for (i = 0; i < size; ++i) {
+    delete bitmaps[i];
+  }
+  gfree(bitmaps);
+}
+
+//------------------------------------------------------------------------
+// JBIG2CodeTable
+//------------------------------------------------------------------------
+
+class JBIG2CodeTable: public JBIG2Segment {
+public:
+
+  JBIG2CodeTable(Guint segNumA, JBIG2HuffmanTable *tableA);
+  virtual ~JBIG2CodeTable();
+  virtual JBIG2SegmentType getType() { return jbig2SegCodeTable; }
+  JBIG2HuffmanTable *getHuffTable() { return table; }
+
+private:
+
+  JBIG2HuffmanTable *table;
+};
+
+JBIG2CodeTable::JBIG2CodeTable(Guint segNumA, JBIG2HuffmanTable *tableA):
+  JBIG2Segment(segNumA)
+{
+  table = tableA;
+}
+
+JBIG2CodeTable::~JBIG2CodeTable() {
+  gfree(table);
+}
+
+//------------------------------------------------------------------------
+// JBIG2Stream
+//------------------------------------------------------------------------
+
+JBIG2Stream::JBIG2Stream(Stream *strA, Object *globalsStreamA):
+  FilterStream(strA)
+{
+  pageBitmap = NULL;
+
+  arithDecoder = new JArithmeticDecoder();
+  genericRegionStats = new JArithmeticDecoderStats(1 << 1);
+  refinementRegionStats = new JArithmeticDecoderStats(1 << 1);
+  iadhStats = new JArithmeticDecoderStats(1 << 9);
+  iadwStats = new JArithmeticDecoderStats(1 << 9);
+  iaexStats = new JArithmeticDecoderStats(1 << 9);
+  iaaiStats = new JArithmeticDecoderStats(1 << 9);
+  iadtStats = new JArithmeticDecoderStats(1 << 9);
+  iaitStats = new JArithmeticDecoderStats(1 << 9);
+  iafsStats = new JArithmeticDecoderStats(1 << 9);
+  iadsStats = new JArithmeticDecoderStats(1 << 9);
+  iardxStats = new JArithmeticDecoderStats(1 << 9);
+  iardyStats = new JArithmeticDecoderStats(1 << 9);
+  iardwStats = new JArithmeticDecoderStats(1 << 9);
+  iardhStats = new JArithmeticDecoderStats(1 << 9);
+  iariStats = new JArithmeticDecoderStats(1 << 9);
+  iaidStats = new JArithmeticDecoderStats(1 << 1);
+  huffDecoder = new JBIG2HuffmanDecoder();
+  mmrDecoder = new JBIG2MMRDecoder();
+
+  globalsStreamA->copy(&globalsStream);
+  segments = globalSegments = NULL;
+  curStr = NULL;
+  dataPtr = dataEnd = NULL;
+}
+
+JBIG2Stream::~JBIG2Stream() {
+  close();
+  globalsStream.free();
+  delete arithDecoder;
+  delete genericRegionStats;
+  delete refinementRegionStats;
+  delete iadhStats;
+  delete iadwStats;
+  delete iaexStats;
+  delete iaaiStats;
+  delete iadtStats;
+  delete iaitStats;
+  delete iafsStats;
+  delete iadsStats;
+  delete iardxStats;
+  delete iardyStats;
+  delete iardwStats;
+  delete iardhStats;
+  delete iariStats;
+  delete iaidStats;
+  delete huffDecoder;
+  delete mmrDecoder;
+  delete str;
+}
+
+void JBIG2Stream::reset() {
+  // read the globals stream
+  globalSegments = new GList();
+  if (globalsStream.isStream()) {
+    segments = globalSegments;
+    curStr = globalsStream.getStream();
+    curStr->reset();
+    arithDecoder->setStream(curStr);
+    huffDecoder->setStream(curStr);
+    mmrDecoder->setStream(curStr);
+    readSegments();
+    curStr->close();
+  }
+
+  // read the main stream
+  segments = new GList();
+  curStr = str;
+  curStr->reset();
+  arithDecoder->setStream(curStr);
+  huffDecoder->setStream(curStr);
+  mmrDecoder->setStream(curStr);
+  readSegments();
+
+  if (pageBitmap) {
+    dataPtr = pageBitmap->getDataPtr();
+    dataEnd = dataPtr + pageBitmap->getDataSize();
+  } else {
+    dataPtr = dataEnd = NULL;
+  }
+}
+
+void JBIG2Stream::close() {
+  if (pageBitmap) {
+    delete pageBitmap;
+    pageBitmap = NULL;
+  }
+  if (segments) {
+    deleteGList(segments, JBIG2Segment);
+    segments = NULL;
+  }
+  if (globalSegments) {
+    deleteGList(globalSegments, JBIG2Segment);
+    globalSegments = NULL;
+  }
+  dataPtr = dataEnd = NULL;
+  FilterStream::close();
+}
+
+int JBIG2Stream::getChar() {
+  if (dataPtr && dataPtr < dataEnd) {
+    return (*dataPtr++ ^ 0xff) & 0xff;
+  }
+  return EOF;
+}
+
+int JBIG2Stream::lookChar() {
+  if (dataPtr && dataPtr < dataEnd) {
+    return (*dataPtr ^ 0xff) & 0xff;
+  }
+  return EOF;
+}
+
+GString *JBIG2Stream::getPSFilter(int psLevel, char *indent) {
+  return NULL;
+}
+
+GBool JBIG2Stream::isBinary(GBool last) {
+  return str->isBinary(gTrue);
+}
+
+void JBIG2Stream::readSegments() {
+  Guint segNum, segFlags, segType, page, segLength;
+  Guint refFlags, nRefSegs;
+  Guint *refSegs;
+  int c1, c2, c3;
+  Guint i;
+
+  while (readULong(&segNum)) {
+
+    // segment header flags
+    if (!readUByte(&segFlags)) {
+      goto eofError1;
+    }
+    segType = segFlags & 0x3f;
+
+    // referred-to segment count and retention flags
+    if (!readUByte(&refFlags)) {
+      goto eofError1;
+    }
+    nRefSegs = refFlags >> 5;
+    if (nRefSegs == 7) {
+      if ((c1 = curStr->getChar()) == EOF ||
+	  (c2 = curStr->getChar()) == EOF ||
+	  (c3 = curStr->getChar()) == EOF) {
+	goto eofError1;
+      }
+      refFlags = (refFlags << 24) | (c1 << 16) | (c2 << 8) | c3;
+      nRefSegs = refFlags & 0x1fffffff;
+      for (i = 0; i < (nRefSegs + 9) >> 3; ++i) {
+	c1 = curStr->getChar();
+      }
+    }
+
+    // referred-to segment numbers
+    refSegs = (Guint *)gmallocn(nRefSegs, sizeof(Guint));
+    if (segNum <= 256) {
+      for (i = 0; i < nRefSegs; ++i) {
+	if (!readUByte(&refSegs[i])) {
+	  goto eofError2;
+	}
+      }
+    } else if (segNum <= 65536) {
+      for (i = 0; i < nRefSegs; ++i) {
+	if (!readUWord(&refSegs[i])) {
+	  goto eofError2;
+	}
+      }
+    } else {
+      for (i = 0; i < nRefSegs; ++i) {
+	if (!readULong(&refSegs[i])) {
+	  goto eofError2;
+	}
+      }
+    }
+
+    // segment page association
+    if (segFlags & 0x40) {
+      if (!readULong(&page)) {
+	goto eofError2;
+      }
+    } else {
+      if (!readUByte(&page)) {
+	goto eofError2;
+      }
+    }
+
+    // segment data length
+    if (!readULong(&segLength)) {
+      goto eofError2;
+    }
+
+    // read the segment data
+    switch (segType) {
+    case 0:
+      if (!readSymbolDictSeg(segNum, segLength, refSegs, nRefSegs)) {
+	goto syntaxError;
+      }
+      break;
+    case 4:
+      readTextRegionSeg(segNum, gFalse, gFalse, segLength, refSegs, nRefSegs);
+      break;
+    case 6:
+      readTextRegionSeg(segNum, gTrue, gFalse, segLength, refSegs, nRefSegs);
+      break;
+    case 7:
+      readTextRegionSeg(segNum, gTrue, gTrue, segLength, refSegs, nRefSegs);
+      break;
+    case 16:
+      readPatternDictSeg(segNum, segLength);
+      break;
+    case 20:
+      readHalftoneRegionSeg(segNum, gFalse, gFalse, segLength,
+			    refSegs, nRefSegs);
+      break;
+    case 22:
+      readHalftoneRegionSeg(segNum, gTrue, gFalse, segLength,
+			    refSegs, nRefSegs);
+      break;
+    case 23:
+      readHalftoneRegionSeg(segNum, gTrue, gTrue, segLength,
+			    refSegs, nRefSegs);
+      break;
+    case 36:
+      readGenericRegionSeg(segNum, gFalse, gFalse, segLength);
+      break;
+    case 38:
+      readGenericRegionSeg(segNum, gTrue, gFalse, segLength);
+      break;
+    case 39:
+      readGenericRegionSeg(segNum, gTrue, gTrue, segLength);
+      break;
+    case 40:
+      readGenericRefinementRegionSeg(segNum, gFalse, gFalse, segLength,
+				     refSegs, nRefSegs);
+      break;
+    case 42:
+      readGenericRefinementRegionSeg(segNum, gTrue, gFalse, segLength,
+				     refSegs, nRefSegs);
+      break;
+    case 43:
+      readGenericRefinementRegionSeg(segNum, gTrue, gTrue, segLength,
+				     refSegs, nRefSegs);
+      break;
+    case 48:
+      readPageInfoSeg(segLength);
+      break;
+    case 50:
+      readEndOfStripeSeg(segLength);
+      break;
+    case 52:
+      readProfilesSeg(segLength);
+      break;
+    case 53:
+      readCodeTableSeg(segNum, segLength);
+      break;
+    case 62:
+      readExtensionSeg(segLength);
+      break;
+    default:
+      error(getPos(), "Unknown segment type in JBIG2 stream");
+      for (i = 0; i < segLength; ++i) {
+	if ((c1 = curStr->getChar()) == EOF) {
+	  goto eofError2;
+	}
+      }
+      break;
+    }
+
+    gfree(refSegs);
+  }
+
+  return;
+
+ syntaxError:
+  gfree(refSegs);
+  return;
+
+ eofError2:
+  gfree(refSegs);
+ eofError1:
+  error(getPos(), "Unexpected EOF in JBIG2 stream");
+}
+
+GBool JBIG2Stream::readSymbolDictSeg(Guint segNum, Guint length,
+				     Guint *refSegs, Guint nRefSegs) {
+  JBIG2SymbolDict *symbolDict;
+  JBIG2HuffmanTable *huffDHTable, *huffDWTable;
+  JBIG2HuffmanTable *huffBMSizeTable, *huffAggInstTable;
+  JBIG2Segment *seg;
+  GList *codeTables;
+  JBIG2SymbolDict *inputSymbolDict;
+  Guint flags, sdTemplate, sdrTemplate, huff, refAgg;
+  Guint huffDH, huffDW, huffBMSize, huffAggInst;
+  Guint contextUsed, contextRetained;
+  int sdATX[4], sdATY[4], sdrATX[2], sdrATY[2];
+  Guint numExSyms, numNewSyms, numInputSyms, symCodeLen;
+  JBIG2Bitmap **bitmaps;
+  JBIG2Bitmap *collBitmap, *refBitmap;
+  Guint *symWidths;
+  Guint symHeight, symWidth, totalWidth, x, symID;
+  int dh, dw, refAggNum, refDX, refDY, bmSize;
+  GBool ex;
+  int run, cnt;
+  Guint i, j, k;
+  Guchar *p;
+
+  // symbol dictionary flags
+  if (!readUWord(&flags)) {
+    goto eofError;
+  }
+  sdTemplate = (flags >> 10) & 3;
+  sdrTemplate = (flags >> 12) & 1;
+  huff = flags & 1;
+  refAgg = (flags >> 1) & 1;
+  huffDH = (flags >> 2) & 3;
+  huffDW = (flags >> 4) & 3;
+  huffBMSize = (flags >> 6) & 1;
+  huffAggInst = (flags >> 7) & 1;
+  contextUsed = (flags >> 8) & 1;
+  contextRetained = (flags >> 9) & 1;
+
+  // symbol dictionary AT flags
+  if (!huff) {
+    if (sdTemplate == 0) {
+      if (!readByte(&sdATX[0]) ||
+	  !readByte(&sdATY[0]) ||
+	  !readByte(&sdATX[1]) ||
+	  !readByte(&sdATY[1]) ||
+	  !readByte(&sdATX[2]) ||
+	  !readByte(&sdATY[2]) ||
+	  !readByte(&sdATX[3]) ||
+	  !readByte(&sdATY[3])) {
+	goto eofError;
+      }
+    } else {
+      if (!readByte(&sdATX[0]) ||
+	  !readByte(&sdATY[0])) {
+	goto eofError;
+      }
+    }
+  }
+
+  // symbol dictionary refinement AT flags
+  if (refAgg && !sdrTemplate) {
+    if (!readByte(&sdrATX[0]) ||
+	!readByte(&sdrATY[0]) ||
+	!readByte(&sdrATX[1]) ||
+	!readByte(&sdrATY[1])) {
+      goto eofError;
+    }
+  }
+
+  // SDNUMEXSYMS and SDNUMNEWSYMS
+  if (!readULong(&numExSyms) || !readULong(&numNewSyms)) {
+    goto eofError;
+  }
+
+  // get referenced segments: input symbol dictionaries and code tables
+  codeTables = new GList();
+  numInputSyms = 0;
+  for (i = 0; i < nRefSegs; ++i) {
+    seg = findSegment(refSegs[i]);
+    if (seg->getType() == jbig2SegSymbolDict) {
+      numInputSyms += ((JBIG2SymbolDict *)seg)->getSize();
+    } else if (seg->getType() == jbig2SegCodeTable) {
+      codeTables->append(seg);
+    }
+  }
+
+  // compute symbol code length
+  symCodeLen = 0;
+  i = 1;
+  while (i < numInputSyms + numNewSyms) {
+    ++symCodeLen;
+    i <<= 1;
+  }
+
+  // get the input symbol bitmaps
+  bitmaps = (JBIG2Bitmap **)gmallocn(numInputSyms + numNewSyms,
+				     sizeof(JBIG2Bitmap *));
+  for (i = 0; i < numInputSyms + numNewSyms; ++i) {
+    bitmaps[i] = NULL;
+  }
+  k = 0;
+  inputSymbolDict = NULL;
+  for (i = 0; i < nRefSegs; ++i) {
+    seg = findSegment(refSegs[i]);
+    if (seg->getType() == jbig2SegSymbolDict) {
+      inputSymbolDict = (JBIG2SymbolDict *)seg;
+      for (j = 0; j < inputSymbolDict->getSize(); ++j) {
+	bitmaps[k++] = inputSymbolDict->getBitmap(j);
+      }
+    }
+  }
+
+  // get the Huffman tables
+  huffDHTable = huffDWTable = NULL; // make gcc happy
+  huffBMSizeTable = huffAggInstTable = NULL; // make gcc happy
+  i = 0;
+  if (huff) {
+    if (huffDH == 0) {
+      huffDHTable = huffTableD;
+    } else if (huffDH == 1) {
+      huffDHTable = huffTableE;
+    } else {
+      huffDHTable = ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+    if (huffDW == 0) {
+      huffDWTable = huffTableB;
+    } else if (huffDW == 1) {
+      huffDWTable = huffTableC;
+    } else {
+      huffDWTable = ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+    if (huffBMSize == 0) {
+      huffBMSizeTable = huffTableA;
+    } else {
+      huffBMSizeTable =
+	  ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+    if (huffAggInst == 0) {
+      huffAggInstTable = huffTableA;
+    } else {
+      huffAggInstTable =
+	  ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+  }
+  delete codeTables;
+
+  // set up the Huffman decoder
+  if (huff) {
+    huffDecoder->reset();
+
+  // set up the arithmetic decoder
+  } else {
+    if (contextUsed && inputSymbolDict) {
+      resetGenericStats(sdTemplate, inputSymbolDict->getGenericRegionStats());
+    } else {
+      resetGenericStats(sdTemplate, NULL);
+    }
+    resetIntStats(symCodeLen);
+    arithDecoder->start();
+  }
+
+  // set up the arithmetic decoder for refinement/aggregation
+  if (refAgg) {
+    if (contextUsed && inputSymbolDict) {
+      resetRefinementStats(sdrTemplate,
+			   inputSymbolDict->getRefinementRegionStats());
+    } else {
+      resetRefinementStats(sdrTemplate, NULL);
+    }
+  }
+
+  // allocate symbol widths storage
+  symWidths = NULL;
+  if (huff && !refAgg) {
+    symWidths = (Guint *)gmallocn(numNewSyms, sizeof(Guint));
+  }
+
+  symHeight = 0;
+  i = 0;
+  while (i < numNewSyms) {
+
+    // read the height class delta height
+    if (huff) {
+      huffDecoder->decodeInt(&dh, huffDHTable);
+    } else {
+      arithDecoder->decodeInt(&dh, iadhStats);
+    }
+    if (dh < 0 && (Guint)-dh >= symHeight) {
+      error(getPos(), "Bad delta-height value in JBIG2 symbol dictionary");
+      goto syntaxError;
+    }
+    symHeight += dh;
+    symWidth = 0;
+    totalWidth = 0;
+    j = i;
+
+    // read the symbols in this height class
+    while (1) {
+
+      // read the delta width
+      if (huff) {
+	if (!huffDecoder->decodeInt(&dw, huffDWTable)) {
+	  break;
+	}
+      } else {
+	if (!arithDecoder->decodeInt(&dw, iadwStats)) {
+	  break;
+	}
+      }
+      if (dw < 0 && (Guint)-dw >= symWidth) {
+	error(getPos(), "Bad delta-height value in JBIG2 symbol dictionary");
+	goto syntaxError;
+      }
+      symWidth += dw;
+
+      // using a collective bitmap, so don't read a bitmap here
+      if (huff && !refAgg) {
+	symWidths[i] = symWidth;
+	totalWidth += symWidth;
+
+      // refinement/aggregate coding
+      } else if (refAgg) {
+	if (huff) {
+	  if (!huffDecoder->decodeInt(&refAggNum, huffAggInstTable)) {
+	    break;
+	  }
+	} else {
+	  if (!arithDecoder->decodeInt(&refAggNum, iaaiStats)) {
+	    break;
+	  }
+	}
+#if 0 //~ This special case was added about a year before the final draft
+      //~ of the JBIG2 spec was released.  I have encountered some old
+      //~ JBIG2 images that predate it.
+	if (0) {
+#else
+	if (refAggNum == 1) {
+#endif
+	  if (huff) {
+	    symID = huffDecoder->readBits(symCodeLen);
+	    huffDecoder->decodeInt(&refDX, huffTableO);
+	    huffDecoder->decodeInt(&refDY, huffTableO);
+	    huffDecoder->decodeInt(&bmSize, huffTableA);
+	    huffDecoder->reset();
+	    arithDecoder->start();
+	  } else {
+	    symID = arithDecoder->decodeIAID(symCodeLen, iaidStats);
+	    arithDecoder->decodeInt(&refDX, iardxStats);
+	    arithDecoder->decodeInt(&refDY, iardyStats);
+	  }
+	  refBitmap = bitmaps[symID];
+	  bitmaps[numInputSyms + i] =
+	      readGenericRefinementRegion(symWidth, symHeight,
+					  sdrTemplate, gFalse,
+					  refBitmap, refDX, refDY,
+					  sdrATX, sdrATY);
+	  //~ do we need to use the bmSize value here (in Huffman mode)?
+	} else {
+	  bitmaps[numInputSyms + i] =
+	      readTextRegion(huff, gTrue, symWidth, symHeight,
+			     refAggNum, 0, numInputSyms + i, NULL,
+			     symCodeLen, bitmaps, 0, 0, 0, 1, 0,
+			     huffTableF, huffTableH, huffTableK, huffTableO,
+			     huffTableO, huffTableO, huffTableO, huffTableA,
+			     sdrTemplate, sdrATX, sdrATY);
+	}
+
+      // non-ref/agg coding
+      } else {
+	bitmaps[numInputSyms + i] =
+	    readGenericBitmap(gFalse, symWidth, symHeight,
+			      sdTemplate, gFalse, gFalse, NULL,
+			      sdATX, sdATY, 0);
+      }
+
+      ++i;
+    }
+
+    // read the collective bitmap
+    if (huff && !refAgg) {
+      huffDecoder->decodeInt(&bmSize, huffBMSizeTable);
+      huffDecoder->reset();
+      if (bmSize == 0) {
+	collBitmap = new JBIG2Bitmap(0, totalWidth, symHeight);
+	bmSize = symHeight * ((totalWidth + 7) >> 3);
+	p = collBitmap->getDataPtr();
+	for (k = 0; k < (Guint)bmSize; ++k) {
+	  *p++ = curStr->getChar();
+	}
+      } else {
+	collBitmap = readGenericBitmap(gTrue, totalWidth, symHeight,
+				       0, gFalse, gFalse, NULL, NULL, NULL,
+				       bmSize);
+      }
+      x = 0;
+      for (; j < i; ++j) {
+	bitmaps[numInputSyms + j] =
+	    collBitmap->getSlice(x, 0, symWidths[j], symHeight);
+	x += symWidths[j];
+      }
+      delete collBitmap;
+    }
+  }
+
+  // create the symbol dict object
+  symbolDict = new JBIG2SymbolDict(segNum, numExSyms);
+
+  // exported symbol list
+  i = j = 0;
+  ex = gFalse;
+  while (i < numInputSyms + numNewSyms) {
+    if (huff) {
+      huffDecoder->decodeInt(&run, huffTableA);
+    } else {
+      arithDecoder->decodeInt(&run, iaexStats);
+    }
+    if (ex) {
+      for (cnt = 0; cnt < run; ++cnt) {
+	symbolDict->setBitmap(j++, bitmaps[i++]->copy());
+      }
+    } else {
+      i += run;
+    }
+    ex = !ex;
+  }
+
+  for (i = 0; i < numNewSyms; ++i) {
+    delete bitmaps[numInputSyms + i];
+  }
+  gfree(bitmaps);
+  if (symWidths) {
+    gfree(symWidths);
+  }
+
+  // save the arithmetic decoder stats
+  if (!huff && contextRetained) {
+    symbolDict->setGenericRegionStats(genericRegionStats->copy());
+    if (refAgg) {
+      symbolDict->setRefinementRegionStats(refinementRegionStats->copy());
+    }
+  }
+
+  // store the new symbol dict
+  segments->append(symbolDict);
+
+  return gTrue;
+
+ syntaxError:
+  for (i = 0; i < numNewSyms; ++i) {
+    if (bitmaps[numInputSyms + i]) {
+      delete bitmaps[numInputSyms + i];
+    }
+  }
+  gfree(bitmaps);
+  if (symWidths) {
+    gfree(symWidths);
+  }
+  return gFalse;
+
+ eofError:
+  error(getPos(), "Unexpected EOF in JBIG2 stream");
+  return gFalse;
+}
+
+void JBIG2Stream::readTextRegionSeg(Guint segNum, GBool imm,
+				    GBool lossless, Guint length,
+				    Guint *refSegs, Guint nRefSegs) {
+  JBIG2Bitmap *bitmap;
+  JBIG2HuffmanTable runLengthTab[36];
+  JBIG2HuffmanTable *symCodeTab;
+  JBIG2HuffmanTable *huffFSTable, *huffDSTable, *huffDTTable;
+  JBIG2HuffmanTable *huffRDWTable, *huffRDHTable;
+  JBIG2HuffmanTable *huffRDXTable, *huffRDYTable, *huffRSizeTable;
+  JBIG2Segment *seg;
+  GList *codeTables;
+  JBIG2SymbolDict *symbolDict;
+  JBIG2Bitmap **syms;
+  Guint w, h, x, y, segInfoFlags, extCombOp;
+  Guint flags, huff, refine, logStrips, refCorner, transposed;
+  Guint combOp, defPixel, templ;
+  int sOffset;
+  Guint huffFlags, huffFS, huffDS, huffDT;
+  Guint huffRDW, huffRDH, huffRDX, huffRDY, huffRSize;
+  Guint numInstances, numSyms, symCodeLen;
+  int atx[2], aty[2];
+  Guint i, k, kk;
+  int j;
+
+  // region segment info field
+  if (!readULong(&w) || !readULong(&h) ||
+      !readULong(&x) || !readULong(&y) ||
+      !readUByte(&segInfoFlags)) {
+    goto eofError;
+  }
+  extCombOp = segInfoFlags & 7;
+
+  // rest of the text region header
+  if (!readUWord(&flags)) {
+    goto eofError;
+  }
+  huff = flags & 1;
+  refine = (flags >> 1) & 1;
+  logStrips = (flags >> 2) & 3;
+  refCorner = (flags >> 4) & 3;
+  transposed = (flags >> 6) & 1;
+  combOp = (flags >> 7) & 3;
+  defPixel = (flags >> 9) & 1;
+  sOffset = (flags >> 10) & 0x1f;
+  if (sOffset & 0x10) {
+    sOffset |= -1 - 0x0f;
+  }
+  templ = (flags >> 15) & 1;
+  huffFS = huffDS = huffDT = 0; // make gcc happy
+  huffRDW = huffRDH = huffRDX = huffRDY = huffRSize = 0; // make gcc happy
+  if (huff) {
+    if (!readUWord(&huffFlags)) {
+      goto eofError;
+    }
+    huffFS = huffFlags & 3;
+    huffDS = (huffFlags >> 2) & 3;
+    huffDT = (huffFlags >> 4) & 3;
+    huffRDW = (huffFlags >> 6) & 3;
+    huffRDH = (huffFlags >> 8) & 3;
+    huffRDX = (huffFlags >> 10) & 3;
+    huffRDY = (huffFlags >> 12) & 3;
+    huffRSize = (huffFlags >> 14) & 1;
+  }
+  if (refine && templ == 0) {
+    if (!readByte(&atx[0]) || !readByte(&aty[0]) ||
+	!readByte(&atx[1]) || !readByte(&aty[1])) {
+      goto eofError;
+    }
+  }
+  if (!readULong(&numInstances)) {
+    goto eofError;
+  }
+
+  // get symbol dictionaries and tables
+  codeTables = new GList();
+  numSyms = 0;
+  for (i = 0; i < nRefSegs; ++i) {
+    if ((seg = findSegment(refSegs[i]))) {
+      if (seg->getType() == jbig2SegSymbolDict) {
+	numSyms += ((JBIG2SymbolDict *)seg)->getSize();
+      } else if (seg->getType() == jbig2SegCodeTable) {
+	codeTables->append(seg);
+      }
+    } else {
+      error(getPos(), "Invalid segment reference in JBIG2 text region");
+    }
+  }
+  symCodeLen = 0;
+  i = 1;
+  while (i < numSyms) {
+    ++symCodeLen;
+    i <<= 1;
+  }
+
+  // get the symbol bitmaps
+  syms = (JBIG2Bitmap **)gmallocn(numSyms, sizeof(JBIG2Bitmap *));
+  kk = 0;
+  for (i = 0; i < nRefSegs; ++i) {
+    if ((seg = findSegment(refSegs[i]))) {
+      if (seg->getType() == jbig2SegSymbolDict) {
+	symbolDict = (JBIG2SymbolDict *)seg;
+	for (k = 0; k < symbolDict->getSize(); ++k) {
+	  syms[kk++] = symbolDict->getBitmap(k);
+	}
+      }
+    }
+  }
+
+  // get the Huffman tables
+  huffFSTable = huffDSTable = huffDTTable = NULL; // make gcc happy
+  huffRDWTable = huffRDHTable = NULL; // make gcc happy
+  huffRDXTable = huffRDYTable = huffRSizeTable = NULL; // make gcc happy
+  i = 0;
+  if (huff) {
+    if (huffFS == 0) {
+      huffFSTable = huffTableF;
+    } else if (huffFS == 1) {
+      huffFSTable = huffTableG;
+    } else {
+      huffFSTable = ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+    if (huffDS == 0) {
+      huffDSTable = huffTableH;
+    } else if (huffDS == 1) {
+      huffDSTable = huffTableI;
+    } else if (huffDS == 2) {
+      huffDSTable = huffTableJ;
+    } else {
+      huffDSTable = ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+    if (huffDT == 0) {
+      huffDTTable = huffTableK;
+    } else if (huffDT == 1) {
+      huffDTTable = huffTableL;
+    } else if (huffDT == 2) {
+      huffDTTable = huffTableM;
+    } else {
+      huffDTTable = ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+    if (huffRDW == 0) {
+      huffRDWTable = huffTableN;
+    } else if (huffRDW == 1) {
+      huffRDWTable = huffTableO;
+    } else {
+      huffRDWTable = ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+    if (huffRDH == 0) {
+      huffRDHTable = huffTableN;
+    } else if (huffRDH == 1) {
+      huffRDHTable = huffTableO;
+    } else {
+      huffRDHTable = ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+    if (huffRDX == 0) {
+      huffRDXTable = huffTableN;
+    } else if (huffRDX == 1) {
+      huffRDXTable = huffTableO;
+    } else {
+      huffRDXTable = ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+    if (huffRDY == 0) {
+      huffRDYTable = huffTableN;
+    } else if (huffRDY == 1) {
+      huffRDYTable = huffTableO;
+    } else {
+      huffRDYTable = ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+    if (huffRSize == 0) {
+      huffRSizeTable = huffTableA;
+    } else {
+      huffRSizeTable =
+	  ((JBIG2CodeTable *)codeTables->get(i++))->getHuffTable();
+    }
+  }
+  delete codeTables;
+
+  // symbol ID Huffman decoding table
+  if (huff) {
+    huffDecoder->reset();
+    for (i = 0; i < 32; ++i) {
+      runLengthTab[i].val = i;
+      runLengthTab[i].prefixLen = huffDecoder->readBits(4);
+      runLengthTab[i].rangeLen = 0;
+    }
+    runLengthTab[32].val = 0x103;
+    runLengthTab[32].prefixLen = huffDecoder->readBits(4);
+    runLengthTab[32].rangeLen = 2;
+    runLengthTab[33].val = 0x203;
+    runLengthTab[33].prefixLen = huffDecoder->readBits(4);
+    runLengthTab[33].rangeLen = 3;
+    runLengthTab[34].val = 0x20b;
+    runLengthTab[34].prefixLen = huffDecoder->readBits(4);
+    runLengthTab[34].rangeLen = 7;
+    runLengthTab[35].prefixLen = 0;
+    runLengthTab[35].rangeLen = jbig2HuffmanEOT;
+    huffDecoder->buildTable(runLengthTab, 35);
+    symCodeTab = (JBIG2HuffmanTable *)gmallocn(numSyms + 1,
+					       sizeof(JBIG2HuffmanTable));
+    for (i = 0; i < numSyms; ++i) {
+      symCodeTab[i].val = i;
+      symCodeTab[i].rangeLen = 0;
+    }
+    i = 0;
+    while (i < numSyms) {
+      huffDecoder->decodeInt(&j, runLengthTab);
+      if (j > 0x200) {
+	for (j -= 0x200; j && i < numSyms; --j) {
+	  symCodeTab[i++].prefixLen = 0;
+	}
+      } else if (j > 0x100) {
+	for (j -= 0x100; j && i < numSyms; --j) {
+	  symCodeTab[i].prefixLen = symCodeTab[i-1].prefixLen;
+	  ++i;
+	}
+      } else {
+	symCodeTab[i++].prefixLen = j;
+      }
+    }
+    symCodeTab[numSyms].prefixLen = 0;
+    symCodeTab[numSyms].rangeLen = jbig2HuffmanEOT;
+    huffDecoder->buildTable(symCodeTab, numSyms);
+    huffDecoder->reset();
+
+  // set up the arithmetic decoder
+  } else {
+    symCodeTab = NULL;
+    resetIntStats(symCodeLen);
+    arithDecoder->start();
+  }
+  if (refine) {
+    resetRefinementStats(templ, NULL);
+  }
+
+  bitmap = readTextRegion(huff, refine, w, h, numInstances,
+			  logStrips, numSyms, symCodeTab, symCodeLen, syms,
+			  defPixel, combOp, transposed, refCorner, sOffset,
+			  huffFSTable, huffDSTable, huffDTTable,
+			  huffRDWTable, huffRDHTable,
+			  huffRDXTable, huffRDYTable, huffRSizeTable,
+			  templ, atx, aty);
+
+  gfree(syms);
+
+  // combine the region bitmap into the page bitmap
+  if (imm) {
+    if (pageH == 0xffffffff && y + h > curPageH) {
+      pageBitmap->expand(y + h, pageDefPixel);
+    }
+    pageBitmap->combine(bitmap, x, y, extCombOp);
+    delete bitmap;
+
+  // store the region bitmap
+  } else {
+    bitmap->setSegNum(segNum);
+    segments->append(bitmap);
+  }
+
+  // clean up the Huffman decoder
+  if (huff) {
+    gfree(symCodeTab);
+  }
+
+  return;
+
+ eofError:
+  error(getPos(), "Unexpected EOF in JBIG2 stream");
+}
+
+JBIG2Bitmap *JBIG2Stream::readTextRegion(GBool huff, GBool refine,
+					 int w, int h,
+					 Guint numInstances,
+					 Guint logStrips,
+					 int numSyms,
+					 JBIG2HuffmanTable *symCodeTab,
+					 Guint symCodeLen,
+					 JBIG2Bitmap **syms,
+					 Guint defPixel, Guint combOp,
+					 Guint transposed, Guint refCorner,
+					 int sOffset,
+					 JBIG2HuffmanTable *huffFSTable,
+					 JBIG2HuffmanTable *huffDSTable,
+					 JBIG2HuffmanTable *huffDTTable,
+					 JBIG2HuffmanTable *huffRDWTable,
+					 JBIG2HuffmanTable *huffRDHTable,
+					 JBIG2HuffmanTable *huffRDXTable,
+					 JBIG2HuffmanTable *huffRDYTable,
+					 JBIG2HuffmanTable *huffRSizeTable,
+					 Guint templ,
+					 int *atx, int *aty) {
+  JBIG2Bitmap *bitmap;
+  JBIG2Bitmap *symbolBitmap;
+  Guint strips;
+  int t, dt, tt, s, ds, sFirst, j;
+  int rdw, rdh, rdx, rdy, ri, refDX, refDY, bmSize;
+  Guint symID, inst, bw, bh;
+
+  strips = 1 << logStrips;
+
+  // allocate the bitmap
+  bitmap = new JBIG2Bitmap(0, w, h);
+  if (defPixel) {
+    bitmap->clearToOne();
+  } else {
+    bitmap->clearToZero();
+  }
+
+  // decode initial T value
+  if (huff) {
+    huffDecoder->decodeInt(&t, huffDTTable);
+  } else {
+    arithDecoder->decodeInt(&t, iadtStats);
+  }
+  t *= -(int)strips;
+
+  inst = 0;
+  sFirst = 0;
+  while (inst < numInstances) {
+
+    // decode delta-T
+    if (huff) {
+      huffDecoder->decodeInt(&dt, huffDTTable);
+    } else {
+      arithDecoder->decodeInt(&dt, iadtStats);
+    }
+    t += dt * strips;
+
+    // first S value
+    if (huff) {
+      huffDecoder->decodeInt(&ds, huffFSTable);
+    } else {
+      arithDecoder->decodeInt(&ds, iafsStats);
+    }
+    sFirst += ds;
+    s = sFirst;
+
+    // read the instances
+    while (1) {
+
+      // T value
+      if (strips == 1) {
+	dt = 0;
+      } else if (huff) {
+	dt = huffDecoder->readBits(logStrips);
+      } else {
+	arithDecoder->decodeInt(&dt, iaitStats);
+      }
+      tt = t + dt;
+
+      // symbol ID
+      if (huff) {
+	if (symCodeTab) {
+	  huffDecoder->decodeInt(&j, symCodeTab);
+	  symID = (Guint)j;
+	} else {
+	  symID = huffDecoder->readBits(symCodeLen);
+	}
+      } else {
+	symID = arithDecoder->decodeIAID(symCodeLen, iaidStats);
+      }
+
+      if (symID >= (Guint)numSyms) {
+	error(getPos(), "Invalid symbol number in JBIG2 text region");
+      } else {
+
+	// get the symbol bitmap
+	symbolBitmap = NULL;
+	if (refine) {
+	  if (huff) {
+	    ri = (int)huffDecoder->readBit();
+	  } else {
+	    arithDecoder->decodeInt(&ri, iariStats);
+	  }
+	} else {
+	  ri = 0;
+	}
+	if (ri) {
+	  if (huff) {
+	    huffDecoder->decodeInt(&rdw, huffRDWTable);
+	    huffDecoder->decodeInt(&rdh, huffRDHTable);
+	    huffDecoder->decodeInt(&rdx, huffRDXTable);
+	    huffDecoder->decodeInt(&rdy, huffRDYTable);
+	    huffDecoder->decodeInt(&bmSize, huffRSizeTable);
+	    huffDecoder->reset();
+	    arithDecoder->start();
+	  } else {
+	    arithDecoder->decodeInt(&rdw, iardwStats);
+	    arithDecoder->decodeInt(&rdh, iardhStats);
+	    arithDecoder->decodeInt(&rdx, iardxStats);
+	    arithDecoder->decodeInt(&rdy, iardyStats);
+	  }
+	  refDX = ((rdw >= 0) ? rdw : rdw - 1) / 2 + rdx;
+	  refDY = ((rdh >= 0) ? rdh : rdh - 1) / 2 + rdy;
+
+	  symbolBitmap =
+	    readGenericRefinementRegion(rdw + syms[symID]->getWidth(),
+					rdh + syms[symID]->getHeight(),
+					templ, gFalse, syms[symID],
+					refDX, refDY, atx, aty);
+	  //~ do we need to use the bmSize value here (in Huffman mode)?
+	} else {
+	  symbolBitmap = syms[symID];
+	}
+
+	// combine the symbol bitmap into the region bitmap
+	//~ something is wrong here - refCorner shouldn't degenerate into
+	//~   two cases
+	bw = symbolBitmap->getWidth() - 1;
+	bh = symbolBitmap->getHeight() - 1;
+	if (transposed) {
+	  switch (refCorner) {
+	  case 0: // bottom left
+	    bitmap->combine(symbolBitmap, tt, s, combOp);
+	    break;
+	  case 1: // top left
+	    bitmap->combine(symbolBitmap, tt, s, combOp);
+	    break;
+	  case 2: // bottom right
+	    bitmap->combine(symbolBitmap, tt - bw, s, combOp);
+	    break;
+	  case 3: // top right
+	    bitmap->combine(symbolBitmap, tt - bw, s, combOp);
+	    break;
+	  }
+	  s += bh;
+	} else {
+	  switch (refCorner) {
+	  case 0: // bottom left
+	    bitmap->combine(symbolBitmap, s, tt - bh, combOp);
+	    break;
+	  case 1: // top left
+	    bitmap->combine(symbolBitmap, s, tt, combOp);
+	    break;
+	  case 2: // bottom right
+	    bitmap->combine(symbolBitmap, s, tt - bh, combOp);
+	    break;
+	  case 3: // top right
+	    bitmap->combine(symbolBitmap, s, tt, combOp);
+	    break;
+	  }
+	  s += bw;
+	}
+	if (ri) {
+	  delete symbolBitmap;
+	}
+      }
+
+      // next instance
+      ++inst;
+
+      // next S value
+      if (huff) {
+	if (!huffDecoder->decodeInt(&ds, huffDSTable)) {
+	  break;
+	}
+      } else {
+	if (!arithDecoder->decodeInt(&ds, iadsStats)) {
+	  break;
+	}
+      }
+      s += sOffset + ds;
+    }
+  }
+
+  return bitmap;
+}
+
+void JBIG2Stream::readPatternDictSeg(Guint segNum, Guint length) {
+  JBIG2PatternDict *patternDict;
+  JBIG2Bitmap *bitmap;
+  Guint flags, patternW, patternH, grayMax, templ, mmr;
+  int atx[4], aty[4];
+  Guint i, x;
+
+  // halftone dictionary flags, pattern width and height, max gray value
+  if (!readUByte(&flags) ||
+      !readUByte(&patternW) ||
+      !readUByte(&patternH) ||
+      !readULong(&grayMax)) {
+    goto eofError;
+  }
+  templ = (flags >> 1) & 3;
+  mmr = flags & 1;
+
+  // set up the arithmetic decoder
+  if (!mmr) {
+    resetGenericStats(templ, NULL);
+    arithDecoder->start();
+  }
+
+  // read the bitmap
+  atx[0] = -(int)patternW; aty[0] =  0;
+  atx[1] = -3;             aty[1] = -1;
+  atx[2] =  2;             aty[2] = -2;
+  atx[3] = -2;             aty[3] = -2;
+  bitmap = readGenericBitmap(mmr, (grayMax + 1) * patternW, patternH,
+			     templ, gFalse, gFalse, NULL,
+			     atx, aty, length - 7);
+
+  // create the pattern dict object
+  patternDict = new JBIG2PatternDict(segNum, grayMax + 1);
+
+  // split up the bitmap
+  x = 0;
+  for (i = 0; i <= grayMax; ++i) {
+    patternDict->setBitmap(i, bitmap->getSlice(x, 0, patternW, patternH));
+    x += patternW;
+  }
+
+  // free memory
+  delete bitmap;
+
+  // store the new pattern dict
+  segments->append(patternDict);
+
+  return;
+
+ eofError:
+  error(getPos(), "Unexpected EOF in JBIG2 stream");
+}
+
+void JBIG2Stream::readHalftoneRegionSeg(Guint segNum, GBool imm,
+					GBool lossless, Guint length,
+					Guint *refSegs, Guint nRefSegs) {
+  JBIG2Bitmap *bitmap;
+  JBIG2Segment *seg;
+  JBIG2PatternDict *patternDict;
+  JBIG2Bitmap *skipBitmap;
+  Guint *grayImg;
+  JBIG2Bitmap *grayBitmap;
+  JBIG2Bitmap *patternBitmap;
+  Guint w, h, x, y, segInfoFlags, extCombOp;
+  Guint flags, mmr, templ, enableSkip, combOp;
+  Guint gridW, gridH, stepX, stepY, patW, patH;
+  int atx[4], aty[4];
+  int gridX, gridY, xx, yy, bit, j;
+  Guint bpp, m, n, i;
+
+  // region segment info field
+  if (!readULong(&w) || !readULong(&h) ||
+      !readULong(&x) || !readULong(&y) ||
+      !readUByte(&segInfoFlags)) {
+    goto eofError;
+  }
+  extCombOp = segInfoFlags & 7;
+
+  // rest of the halftone region header
+  if (!readUByte(&flags)) {
+    goto eofError;
+  }
+  mmr = flags & 1;
+  templ = (flags >> 1) & 3;
+  enableSkip = (flags >> 3) & 1;
+  combOp = (flags >> 4) & 7;
+  if (!readULong(&gridW) || !readULong(&gridH) ||
+      !readLong(&gridX) || !readLong(&gridY) ||
+      !readUWord(&stepX) || !readUWord(&stepY)) {
+    goto eofError;
+  }
+  if (w == 0 || h == 0 || w >= INT_MAX / h) {
+    error(getPos(), "Bad bitmap size in JBIG2 halftone segment");
+    return;
+  }
+  if (gridH == 0 || gridW >= INT_MAX / gridH) {
+    error(getPos(), "Bad grid size in JBIG2 halftone segment");
+    return;
+  }
+
+  // get pattern dictionary
+  if (nRefSegs != 1) {
+    error(getPos(), "Bad symbol dictionary reference in JBIG2 halftone segment");
+    return;
+  }
+  seg = findSegment(refSegs[0]);
+  if (seg->getType() != jbig2SegPatternDict) {
+    error(getPos(), "Bad symbol dictionary reference in JBIG2 halftone segment");
+    return;
+  }
+  patternDict = (JBIG2PatternDict *)seg;
+  bpp = 0;
+  i = 1;
+  while (i < patternDict->getSize()) {
+    ++bpp;
+    i <<= 1;
+  }
+  patW = patternDict->getBitmap(0)->getWidth();
+  patH = patternDict->getBitmap(0)->getHeight();
+
+  // set up the arithmetic decoder
+  if (!mmr) {
+    resetGenericStats(templ, NULL);
+    arithDecoder->start();
+  }
+
+  // allocate the bitmap
+  bitmap = new JBIG2Bitmap(segNum, w, h);
+  if (flags & 0x80) { // HDEFPIXEL
+    bitmap->clearToOne();
+  } else {
+    bitmap->clearToZero();
+  }
+
+  // compute the skip bitmap
+  skipBitmap = NULL;
+  if (enableSkip) {
+    skipBitmap = new JBIG2Bitmap(0, gridW, gridH);
+    skipBitmap->clearToZero();
+    for (m = 0; m < gridH; ++m) {
+      for (n = 0; n < gridW; ++n) {
+	xx = gridX + m * stepY + n * stepX;
+	yy = gridY + m * stepX - n * stepY;
+	if (((xx + (int)patW) >> 8) <= 0 || (xx >> 8) >= (int)w ||
+	    ((yy + (int)patH) >> 8) <= 0 || (yy >> 8) >= (int)h) {
+	  skipBitmap->setPixel(n, m);
+	}
+      }
+    }
+  }
+
+  // read the gray-scale image
+  grayImg = (Guint *)gmallocn(gridW * gridH, sizeof(Guint));
+  memset(grayImg, 0, gridW * gridH * sizeof(Guint));
+  atx[0] = templ <= 1 ? 3 : 2;  aty[0] = -1;
+  atx[1] = -3;                  aty[1] = -1;
+  atx[2] =  2;                  aty[2] = -2;
+  atx[3] = -2;                  aty[3] = -2;
+  for (j = bpp - 1; j >= 0; --j) {
+    grayBitmap = readGenericBitmap(mmr, gridW, gridH, templ, gFalse,
+				   enableSkip, skipBitmap, atx, aty, -1);
+    i = 0;
+    for (m = 0; m < gridH; ++m) {
+      for (n = 0; n < gridW; ++n) {
+	bit = grayBitmap->getPixel(n, m) ^ (grayImg[i] & 1);
+	grayImg[i] = (grayImg[i] << 1) | bit;
+	++i;
+      }
+    }
+    delete grayBitmap;
+  }
+
+  // decode the image
+  i = 0;
+  for (m = 0; m < gridH; ++m) {
+    xx = gridX + m * stepY;
+    yy = gridY + m * stepX;
+    for (n = 0; n < gridW; ++n) {
+      if (!(enableSkip && skipBitmap->getPixel(n, m))) {
+	patternBitmap = patternDict->getBitmap(grayImg[i]);
+	bitmap->combine(patternBitmap, xx >> 8, yy >> 8, combOp);
+      }
+      xx += stepX;
+      yy -= stepY;
+      ++i;
+    }
+  }
+
+  gfree(grayImg);
+  if (skipBitmap) {
+    delete skipBitmap;
+  }
+
+  // combine the region bitmap into the page bitmap
+  if (imm) {
+    if (pageH == 0xffffffff && y + h > curPageH) {
+      pageBitmap->expand(y + h, pageDefPixel);
+    }
+    pageBitmap->combine(bitmap, x, y, extCombOp);
+    delete bitmap;
+
+  // store the region bitmap
+  } else {
+    segments->append(bitmap);
+  }
+
+  return;
+
+ eofError:
+  error(getPos(), "Unexpected EOF in JBIG2 stream");
+}
+
+void JBIG2Stream::readGenericRegionSeg(Guint segNum, GBool imm,
+				       GBool lossless, Guint length) {
+  JBIG2Bitmap *bitmap;
+  Guint w, h, x, y, segInfoFlags, extCombOp;
+  Guint flags, mmr, templ, tpgdOn;
+  int atx[4], aty[4];
+
+  // region segment info field
+  if (!readULong(&w) || !readULong(&h) ||
+      !readULong(&x) || !readULong(&y) ||
+      !readUByte(&segInfoFlags)) {
+    goto eofError;
+  }
+  extCombOp = segInfoFlags & 7;
+
+  // rest of the generic region segment header
+  if (!readUByte(&flags)) {
+    goto eofError;
+  }
+  mmr = flags & 1;
+  templ = (flags >> 1) & 3;
+  tpgdOn = (flags >> 3) & 1;
+
+  // AT flags
+  if (!mmr) {
+    if (templ == 0) {
+      if (!readByte(&atx[0]) ||
+	  !readByte(&aty[0]) ||
+	  !readByte(&atx[1]) ||
+	  !readByte(&aty[1]) ||
+	  !readByte(&atx[2]) ||
+	  !readByte(&aty[2]) ||
+	  !readByte(&atx[3]) ||
+	  !readByte(&aty[3])) {
+	goto eofError;
+      }
+    } else {
+      if (!readByte(&atx[0]) ||
+	  !readByte(&aty[0])) {
+	goto eofError;
+      }
+    }
+  }
+
+  // set up the arithmetic decoder
+  if (!mmr) {
+    resetGenericStats(templ, NULL);
+    arithDecoder->start();
+  }
+
+  // read the bitmap
+  bitmap = readGenericBitmap(mmr, w, h, templ, tpgdOn, gFalse,
+			     NULL, atx, aty, mmr ? 0 : length - 18);
+
+  // combine the region bitmap into the page bitmap
+  if (imm) {
+    if (pageH == 0xffffffff && y + h > curPageH) {
+      pageBitmap->expand(y + h, pageDefPixel);
+    }
+    pageBitmap->combine(bitmap, x, y, extCombOp);
+    delete bitmap;
+
+  // store the region bitmap
+  } else {
+    bitmap->setSegNum(segNum);
+    segments->append(bitmap);
+  }
+
+  return;
+
+ eofError:
+  error(getPos(), "Unexpected EOF in JBIG2 stream");
+}
+
+JBIG2Bitmap *JBIG2Stream::readGenericBitmap(GBool mmr, int w, int h,
+					    int templ, GBool tpgdOn,
+					    GBool useSkip, JBIG2Bitmap *skip,
+					    int *atx, int *aty,
+					    int mmrDataLength) {
+  JBIG2Bitmap *bitmap;
+  GBool ltp;
+  Guint ltpCX, cx, cx0, cx1, cx2;
+  JBIG2BitmapPtr cxPtr0, cxPtr1;
+  JBIG2BitmapPtr atPtr0, atPtr1, atPtr2, atPtr3;
+  int *refLine, *codingLine;
+  int code1, code2, code3;
+  int x, y, a0, pix, i, refI, codingI;
+
+  bitmap = new JBIG2Bitmap(0, w, h);
+  bitmap->clearToZero();
+
+  //----- MMR decode
+
+  if (mmr) {
+
+    mmrDecoder->reset();
+    refLine = (int *)gmallocn(w + 2, sizeof(int));
+    codingLine = (int *)gmallocn(w + 2, sizeof(int));
+    codingLine[0] = codingLine[1] = w;
+
+    for (y = 0; y < h; ++y) {
+
+      // copy coding line to ref line
+      for (i = 0; codingLine[i] < w; ++i) {
+	refLine[i] = codingLine[i];
+      }
+      refLine[i] = refLine[i + 1] = w;
+
+      // decode a line
+      refI = 0;     // b1 = refLine[refI]
+      codingI = 0;  // a1 = codingLine[codingI]
+      a0 = 0;
+      do {
+	code1 = mmrDecoder->get2DCode();
+	switch (code1) {
+	case twoDimPass:
+	  if (refLine[refI] < w) {
+	    a0 = refLine[refI + 1];
+	    refI += 2;
+	  }
+	  break;
+	case twoDimHoriz:
+	  if (codingI & 1) {
+	    code1 = 0;
+	    do {
+	      code1 += code3 = mmrDecoder->getBlackCode();
+	    } while (code3 >= 64);
+	    code2 = 0;
+	    do {
+	      code2 += code3 = mmrDecoder->getWhiteCode();
+	    } while (code3 >= 64);
+	  } else {
+	    code1 = 0;
+	    do {
+	      code1 += code3 = mmrDecoder->getWhiteCode();
+	    } while (code3 >= 64);
+	    code2 = 0;
+	    do {
+	      code2 += code3 = mmrDecoder->getBlackCode();
+	    } while (code3 >= 64);
+	  }
+	  if (code1 > 0 || code2 > 0) {
+	    a0 = codingLine[codingI++] = a0 + code1;
+	    a0 = codingLine[codingI++] = a0 + code2;
+	    while (refLine[refI] <= a0 && refLine[refI] < w) {
+	      refI += 2;
+	    }
+	  }
+	  break;
+	case twoDimVert0:
+	  a0 = codingLine[codingI++] = refLine[refI];
+	  if (refLine[refI] < w) {
+	    ++refI;
+	  }
+	  break;
+	case twoDimVertR1:
+	  a0 = codingLine[codingI++] = refLine[refI] + 1;
+	  if (refLine[refI] < w) {
+	    ++refI;
+	    while (refLine[refI] <= a0 && refLine[refI] < w) {
+	      refI += 2;
+	    }
+	  }
+	  break;
+	case twoDimVertR2:
+	  a0 = codingLine[codingI++] = refLine[refI] + 2;
+	  if (refLine[refI] < w) {
+	    ++refI;
+	    while (refLine[refI] <= a0 && refLine[refI] < w) {
+	      refI += 2;
+	    }
+	  }
+	  break;
+	case twoDimVertR3:
+	  a0 = codingLine[codingI++] = refLine[refI] + 3;
+	  if (refLine[refI] < w) {
+	    ++refI;
+	    while (refLine[refI] <= a0 && refLine[refI] < w) {
+	      refI += 2;
+	    }
+	  }
+	  break;
+	case twoDimVertL1:
+	  a0 = codingLine[codingI++] = refLine[refI] - 1;
+	  if (refI > 0) {
+	    --refI;
+	  } else {
+	    ++refI;
+	  }
+	  while (refLine[refI] <= a0 && refLine[refI] < w) {
+	    refI += 2;
+	  }
+	  break;
+	case twoDimVertL2:
+	  a0 = codingLine[codingI++] = refLine[refI] - 2;
+	  if (refI > 0) {
+	    --refI;
+	  } else {
+	    ++refI;
+	  }
+	  while (refLine[refI] <= a0 && refLine[refI] < w) {
+	    refI += 2;
+	  }
+	  break;
+	case twoDimVertL3:
+	  a0 = codingLine[codingI++] = refLine[refI] - 3;
+	  if (refI > 0) {
+	    --refI;
+	  } else {
+	    ++refI;
+	  }
+	  while (refLine[refI] <= a0 && refLine[refI] < w) {
+	    refI += 2;
+	  }
+	  break;
+	default:
+	  error(getPos(), "Illegal code in JBIG2 MMR bitmap data");
+	  break;
+	}
+      } while (a0 < w);
+      codingLine[codingI++] = w;
+
+      // convert the run lengths to a bitmap line
+      i = 0;
+      while (codingLine[i] < w) {
+	for (x = codingLine[i]; x < codingLine[i+1]; ++x) {
+	  bitmap->setPixel(x, y);
+	}
+	i += 2;
+      }
+    }
+
+    if (mmrDataLength >= 0) {
+      mmrDecoder->skipTo(mmrDataLength);
+    } else {
+      if (mmrDecoder->get24Bits() != 0x001001) {
+	error(getPos(), "Missing EOFB in JBIG2 MMR bitmap data");
+      }
+    }
+
+    gfree(refLine);
+    gfree(codingLine);
+
+  //----- arithmetic decode
+
+  } else {
+    // set up the typical row context
+    ltpCX = 0; // make gcc happy
+    if (tpgdOn) {
+      switch (templ) {
+      case 0:
+	ltpCX = 0x3953; // 001 11001 0101 0011
+	break;
+      case 1:
+	ltpCX = 0x079a; // 0011 11001 101 0
+	break;
+      case 2:
+	ltpCX = 0x0e3; // 001 1100 01 1
+	break;
+      case 3:
+	ltpCX = 0x18a; // 01100 0101 1
+	break;
+      }
+    }
+
+    ltp = 0;
+    cx = cx0 = cx1 = cx2 = 0; // make gcc happy
+    for (y = 0; y < h; ++y) {
+
+      // check for a "typical" (duplicate) row
+      if (tpgdOn) {
+	if (arithDecoder->decodeBit(ltpCX, genericRegionStats)) {
+	  ltp = !ltp;
+	}
+	if (ltp) {
+	  bitmap->duplicateRow(y, y-1);
+	  continue;
+	}
+      }
+
+      switch (templ) {
+      case 0:
+
+	// set up the context
+	bitmap->getPixelPtr(0, y-2, &cxPtr0);
+	cx0 = bitmap->nextPixel(&cxPtr0);
+	cx0 = (cx0 << 1) | bitmap->nextPixel(&cxPtr0);
+	bitmap->getPixelPtr(0, y-1, &cxPtr1);
+	cx1 = bitmap->nextPixel(&cxPtr1);
+	cx1 = (cx1 << 1) | bitmap->nextPixel(&cxPtr1);
+	cx1 = (cx1 << 1) | bitmap->nextPixel(&cxPtr1);
+	cx2 = 0;
+	bitmap->getPixelPtr(atx[0], y + aty[0], &atPtr0);
+	bitmap->getPixelPtr(atx[1], y + aty[1], &atPtr1);
+	bitmap->getPixelPtr(atx[2], y + aty[2], &atPtr2);
+	bitmap->getPixelPtr(atx[3], y + aty[3], &atPtr3);
+
+	// decode the row
+	for (x = 0; x < w; ++x) {
+
+	  // build the context
+	  cx = (cx0 << 13) | (cx1 << 8) | (cx2 << 4) |
+	       (bitmap->nextPixel(&atPtr0) << 3) |
+	       (bitmap->nextPixel(&atPtr1) << 2) |
+	       (bitmap->nextPixel(&atPtr2) << 1) |
+	       bitmap->nextPixel(&atPtr3);
+
+	  // check for a skipped pixel
+	  if (useSkip && skip->getPixel(x, y)) {
+	    pix = 0;
+
+	  // decode the pixel
+	  } else if ((pix = arithDecoder->decodeBit(cx, genericRegionStats))) {
+	    bitmap->setPixel(x, y);
+	  }
+
+	  // update the context
+	  cx0 = ((cx0 << 1) | bitmap->nextPixel(&cxPtr0)) & 0x07;
+	  cx1 = ((cx1 << 1) | bitmap->nextPixel(&cxPtr1)) & 0x1f;
+	  cx2 = ((cx2 << 1) | pix) & 0x0f;
+	}
+	break;
+
+      case 1:
+
+	// set up the context
+	bitmap->getPixelPtr(0, y-2, &cxPtr0);
+	cx0 = bitmap->nextPixel(&cxPtr0);
+	cx0 = (cx0 << 1) | bitmap->nextPixel(&cxPtr0);
+	cx0 = (cx0 << 1) | bitmap->nextPixel(&cxPtr0);
+	bitmap->getPixelPtr(0, y-1, &cxPtr1);
+	cx1 = bitmap->nextPixel(&cxPtr1);
+	cx1 = (cx1 << 1) | bitmap->nextPixel(&cxPtr1);
+	cx1 = (cx1 << 1) | bitmap->nextPixel(&cxPtr1);
+	cx2 = 0;
+	bitmap->getPixelPtr(atx[0], y + aty[0], &atPtr0);
+
+	// decode the row
+	for (x = 0; x < w; ++x) {
+
+	  // build the context
+	  cx = (cx0 << 9) | (cx1 << 4) | (cx2 << 1) |
+	       bitmap->nextPixel(&atPtr0);
+
+	  // check for a skipped pixel
+	  if (useSkip && skip->getPixel(x, y)) {
+	    pix = 0;
+
+	  // decode the pixel
+	  } else if ((pix = arithDecoder->decodeBit(cx, genericRegionStats))) {
+	    bitmap->setPixel(x, y);
+	  }
+
+	  // update the context
+	  cx0 = ((cx0 << 1) | bitmap->nextPixel(&cxPtr0)) & 0x0f;
+	  cx1 = ((cx1 << 1) | bitmap->nextPixel(&cxPtr1)) & 0x1f;
+	  cx2 = ((cx2 << 1) | pix) & 0x07;
+	}
+	break;
+
+      case 2:
+
+	// set up the context
+	bitmap->getPixelPtr(0, y-2, &cxPtr0);
+	cx0 = bitmap->nextPixel(&cxPtr0);
+	cx0 = (cx0 << 1) | bitmap->nextPixel(&cxPtr0);
+	bitmap->getPixelPtr(0, y-1, &cxPtr1);
+	cx1 = bitmap->nextPixel(&cxPtr1);
+	cx1 = (cx1 << 1) | bitmap->nextPixel(&cxPtr1);
+	cx2 = 0;
+	bitmap->getPixelPtr(atx[0], y + aty[0], &atPtr0);
+
+	// decode the row
+	for (x = 0; x < w; ++x) {
+
+	  // build the context
+	  cx = (cx0 << 7) | (cx1 << 3) | (cx2 << 1) |
+	       bitmap->nextPixel(&atPtr0);
+
+	  // check for a skipped pixel
+	  if (useSkip && skip->getPixel(x, y)) {
+	    pix = 0;
+
+	  // decode the pixel
+	  } else if ((pix = arithDecoder->decodeBit(cx, genericRegionStats))) {
+	    bitmap->setPixel(x, y);
+	  }
+
+	  // update the context
+	  cx0 = ((cx0 << 1) | bitmap->nextPixel(&cxPtr0)) & 0x07;
+	  cx1 = ((cx1 << 1) | bitmap->nextPixel(&cxPtr1)) & 0x0f;
+	  cx2 = ((cx2 << 1) | pix) & 0x03;
+	}
+	break;
+
+      case 3:
+
+	// set up the context
+	bitmap->getPixelPtr(0, y-1, &cxPtr1);
+	cx1 = bitmap->nextPixel(&cxPtr1);
+	cx1 = (cx1 << 1) | bitmap->nextPixel(&cxPtr1);
+	cx2 = 0;
+	bitmap->getPixelPtr(atx[0], y + aty[0], &atPtr0);
+
+	// decode the row
+	for (x = 0; x < w; ++x) {
+
+	  // build the context
+	  cx = (cx1 << 5) | (cx2 << 1) |
+	       bitmap->nextPixel(&atPtr0);
+
+	  // check for a skipped pixel
+	  if (useSkip && skip->getPixel(x, y)) {
+	    pix = 0;
+
+	  // decode the pixel
+	  } else if ((pix = arithDecoder->decodeBit(cx, genericRegionStats))) {
+	    bitmap->setPixel(x, y);
+	  }
+
+	  // update the context
+	  cx1 = ((cx1 << 1) | bitmap->nextPixel(&cxPtr1)) & 0x1f;
+	  cx2 = ((cx2 << 1) | pix) & 0x0f;
+	}
+	break;
+      }
+    }
+  }
+
+  return bitmap;
+}
+
+void JBIG2Stream::readGenericRefinementRegionSeg(Guint segNum, GBool imm,
+						 GBool lossless, Guint length,
+						 Guint *refSegs,
+						 Guint nRefSegs) {
+  JBIG2Bitmap *bitmap, *refBitmap;
+  Guint w, h, x, y, segInfoFlags, extCombOp;
+  Guint flags, templ, tpgrOn;
+  int atx[2], aty[2];
+  JBIG2Segment *seg;
+
+  // region segment info field
+  if (!readULong(&w) || !readULong(&h) ||
+      !readULong(&x) || !readULong(&y) ||
+      !readUByte(&segInfoFlags)) {
+    goto eofError;
+  }
+  extCombOp = segInfoFlags & 7;
+
+  // rest of the generic refinement region segment header
+  if (!readUByte(&flags)) {
+    goto eofError;
+  }
+  templ = flags & 1;
+  tpgrOn = (flags >> 1) & 1;
+
+  // AT flags
+  if (!templ) {
+    if (!readByte(&atx[0]) || !readByte(&aty[0]) ||
+	!readByte(&atx[1]) || !readByte(&aty[1])) {
+      goto eofError;
+    }
+  }
+
+  // resize the page bitmap if needed
+  if (nRefSegs == 0 || imm) {
+    if (pageH == 0xffffffff && y + h > curPageH) {
+      pageBitmap->expand(y + h, pageDefPixel);
+    }
+  }
+
+  // get referenced bitmap
+  if (nRefSegs > 1) {
+    error(getPos(), "Bad reference in JBIG2 generic refinement segment");
+    return;
+  }
+  if (nRefSegs == 1) {
+    seg = findSegment(refSegs[0]);
+    if (seg->getType() != jbig2SegBitmap) {
+      error(getPos(), "Bad bitmap reference in JBIG2 generic refinement segment");
+      return;
+    }
+    refBitmap = (JBIG2Bitmap *)seg;
+  } else {
+    refBitmap = pageBitmap->getSlice(x, y, w, h);
+  }
+
+  // set up the arithmetic decoder
+  resetRefinementStats(templ, NULL);
+  arithDecoder->start();
+
+  // read
+  bitmap = readGenericRefinementRegion(w, h, templ, tpgrOn,
+				       refBitmap, 0, 0, atx, aty);
+
+  // combine the region bitmap into the page bitmap
+  if (imm) {
+    pageBitmap->combine(bitmap, x, y, extCombOp);
+    delete bitmap;
+
+  // store the region bitmap
+  } else {
+    bitmap->setSegNum(segNum);
+    segments->append(bitmap);
+  }
+
+  // delete the referenced bitmap
+  if (nRefSegs == 1) {
+    discardSegment(refSegs[0]);
+  } else {
+    delete refBitmap;
+  }
+
+  return;
+
+ eofError:
+  error(getPos(), "Unexpected EOF in JBIG2 stream");
+}
+
+JBIG2Bitmap *JBIG2Stream::readGenericRefinementRegion(int w, int h,
+						      int templ, GBool tpgrOn,
+						      JBIG2Bitmap *refBitmap,
+						      int refDX, int refDY,
+						      int *atx, int *aty) {
+  JBIG2Bitmap *bitmap;
+  GBool ltp;
+  Guint ltpCX, cx, cx0, cx2, cx3, cx4, tpgrCX0, tpgrCX1, tpgrCX2;
+  JBIG2BitmapPtr cxPtr0, cxPtr1, cxPtr2, cxPtr3, cxPtr4, cxPtr5, cxPtr6;
+  JBIG2BitmapPtr tpgrCXPtr0, tpgrCXPtr1, tpgrCXPtr2;
+  int x, y, pix;
+
+  bitmap = new JBIG2Bitmap(0, w, h);
+  bitmap->clearToZero();
+
+  // set up the typical row context
+  if (templ) {
+    ltpCX = 0x008;
+  } else {
+    ltpCX = 0x0010;
+  }
+
+  ltp = 0;
+  for (y = 0; y < h; ++y) {
+
+    if (templ) {
+
+      // set up the context
+      bitmap->getPixelPtr(0, y-1, &cxPtr0);
+      cx0 = bitmap->nextPixel(&cxPtr0);
+      bitmap->getPixelPtr(-1, y, &cxPtr1);
+      refBitmap->getPixelPtr(-refDX, y-1-refDY, &cxPtr2);
+      refBitmap->getPixelPtr(-1-refDX, y-refDY, &cxPtr3);
+      cx3 = refBitmap->nextPixel(&cxPtr3);
+      cx3 = (cx3 << 1) | refBitmap->nextPixel(&cxPtr3);
+      refBitmap->getPixelPtr(-refDX, y+1-refDY, &cxPtr4);
+      cx4 = refBitmap->nextPixel(&cxPtr4);
+
+      // set up the typical prediction context
+      tpgrCX0 = tpgrCX1 = tpgrCX2 = 0; // make gcc happy
+      if (tpgrOn) {
+	refBitmap->getPixelPtr(-1-refDX, y-1-refDY, &tpgrCXPtr0);
+	tpgrCX0 = refBitmap->nextPixel(&tpgrCXPtr0);
+	tpgrCX0 = (tpgrCX0 << 1) | refBitmap->nextPixel(&tpgrCXPtr0);
+	tpgrCX0 = (tpgrCX0 << 1) | refBitmap->nextPixel(&tpgrCXPtr0);
+	refBitmap->getPixelPtr(-1-refDX, y-refDY, &tpgrCXPtr1);
+	tpgrCX1 = refBitmap->nextPixel(&tpgrCXPtr1);
+	tpgrCX1 = (tpgrCX1 << 1) | refBitmap->nextPixel(&tpgrCXPtr1);
+	tpgrCX1 = (tpgrCX1 << 1) | refBitmap->nextPixel(&tpgrCXPtr1);
+	refBitmap->getPixelPtr(-1-refDX, y+1-refDY, &tpgrCXPtr2);
+	tpgrCX2 = refBitmap->nextPixel(&tpgrCXPtr2);
+	tpgrCX2 = (tpgrCX2 << 1) | refBitmap->nextPixel(&tpgrCXPtr2);
+	tpgrCX2 = (tpgrCX2 << 1) | refBitmap->nextPixel(&tpgrCXPtr2);
+      }
+
+      for (x = 0; x < w; ++x) {
+
+	// update the context
+	cx0 = ((cx0 << 1) | bitmap->nextPixel(&cxPtr0)) & 7;
+	cx3 = ((cx3 << 1) | refBitmap->nextPixel(&cxPtr3)) & 7;
+	cx4 = ((cx4 << 1) | refBitmap->nextPixel(&cxPtr4)) & 3;
+
+	if (tpgrOn) {
+	  // update the typical predictor context
+	  tpgrCX0 = ((tpgrCX0 << 1) | refBitmap->nextPixel(&tpgrCXPtr0)) & 7;
+	  tpgrCX1 = ((tpgrCX1 << 1) | refBitmap->nextPixel(&tpgrCXPtr1)) & 7;
+	  tpgrCX2 = ((tpgrCX2 << 1) | refBitmap->nextPixel(&tpgrCXPtr2)) & 7;
+
+	  // check for a "typical" pixel
+	  if (arithDecoder->decodeBit(ltpCX, refinementRegionStats)) {
+	    ltp = !ltp;
+	  }
+	  if (tpgrCX0 == 0 && tpgrCX1 == 0 && tpgrCX2 == 0) {
+	    bitmap->clearPixel(x, y);
+	    continue;
+	  } else if (tpgrCX0 == 7 && tpgrCX1 == 7 && tpgrCX2 == 7) {
+	    bitmap->setPixel(x, y);
+	    continue;
+	  }
+	}
+
+	// build the context
+	cx = (cx0 << 7) | (bitmap->nextPixel(&cxPtr1) << 6) |
+	     (refBitmap->nextPixel(&cxPtr2) << 5) |
+	     (cx3 << 2) | cx4;
+
+	// decode the pixel
+	if ((pix = arithDecoder->decodeBit(cx, refinementRegionStats))) {
+	  bitmap->setPixel(x, y);
+	}
+      }
+
+    } else {
+
+      // set up the context
+      bitmap->getPixelPtr(0, y-1, &cxPtr0);
+      cx0 = bitmap->nextPixel(&cxPtr0);
+      bitmap->getPixelPtr(-1, y, &cxPtr1);
+      refBitmap->getPixelPtr(-refDX, y-1-refDY, &cxPtr2);
+      cx2 = refBitmap->nextPixel(&cxPtr2);
+      refBitmap->getPixelPtr(-1-refDX, y-refDY, &cxPtr3);
+      cx3 = refBitmap->nextPixel(&cxPtr3);
+      cx3 = (cx3 << 1) | refBitmap->nextPixel(&cxPtr3);
+      refBitmap->getPixelPtr(-1-refDX, y+1-refDY, &cxPtr4);
+      cx4 = refBitmap->nextPixel(&cxPtr4);
+      cx4 = (cx4 << 1) | refBitmap->nextPixel(&cxPtr4);
+      bitmap->getPixelPtr(atx[0], y+aty[0], &cxPtr5);
+      refBitmap->getPixelPtr(atx[1]-refDX, y+aty[1]-refDY, &cxPtr6);
+
+      // set up the typical prediction context
+      tpgrCX0 = tpgrCX1 = tpgrCX2 = 0; // make gcc happy
+      if (tpgrOn) {
+	refBitmap->getPixelPtr(-1-refDX, y-1-refDY, &tpgrCXPtr0);
+	tpgrCX0 = refBitmap->nextPixel(&tpgrCXPtr0);
+	tpgrCX0 = (tpgrCX0 << 1) | refBitmap->nextPixel(&tpgrCXPtr0);
+	tpgrCX0 = (tpgrCX0 << 1) | refBitmap->nextPixel(&tpgrCXPtr0);
+	refBitmap->getPixelPtr(-1-refDX, y-refDY, &tpgrCXPtr1);
+	tpgrCX1 = refBitmap->nextPixel(&tpgrCXPtr1);
+	tpgrCX1 = (tpgrCX1 << 1) | refBitmap->nextPixel(&tpgrCXPtr1);
+	tpgrCX1 = (tpgrCX1 << 1) | refBitmap->nextPixel(&tpgrCXPtr1);
+	refBitmap->getPixelPtr(-1-refDX, y+1-refDY, &tpgrCXPtr2);
+	tpgrCX2 = refBitmap->nextPixel(&tpgrCXPtr2);
+	tpgrCX2 = (tpgrCX2 << 1) | refBitmap->nextPixel(&tpgrCXPtr2);
+	tpgrCX2 = (tpgrCX2 << 1) | refBitmap->nextPixel(&tpgrCXPtr2);
+      }
+
+      for (x = 0; x < w; ++x) {
+
+	// update the context
+	cx0 = ((cx0 << 1) | bitmap->nextPixel(&cxPtr0)) & 3;
+	cx2 = ((cx2 << 1) | refBitmap->nextPixel(&cxPtr2)) & 3;
+	cx3 = ((cx3 << 1) | refBitmap->nextPixel(&cxPtr3)) & 7;
+	cx4 = ((cx4 << 1) | refBitmap->nextPixel(&cxPtr4)) & 7;
+
+	if (tpgrOn) {
+	  // update the typical predictor context
+	  tpgrCX0 = ((tpgrCX0 << 1) | refBitmap->nextPixel(&tpgrCXPtr0)) & 7;
+	  tpgrCX1 = ((tpgrCX1 << 1) | refBitmap->nextPixel(&tpgrCXPtr1)) & 7;
+	  tpgrCX2 = ((tpgrCX2 << 1) | refBitmap->nextPixel(&tpgrCXPtr2)) & 7;
+
+	  // check for a "typical" pixel
+	  if (arithDecoder->decodeBit(ltpCX, refinementRegionStats)) {
+	    ltp = !ltp;
+	  }
+	  if (tpgrCX0 == 0 && tpgrCX1 == 0 && tpgrCX2 == 0) {
+	    bitmap->clearPixel(x, y);
+	    continue;
+	  } else if (tpgrCX0 == 7 && tpgrCX1 == 7 && tpgrCX2 == 7) {
+	    bitmap->setPixel(x, y);
+	    continue;
+	  }
+	}
+
+	// build the context
+	cx = (cx0 << 11) | (bitmap->nextPixel(&cxPtr1) << 10) |
+	     (cx2 << 8) | (cx3 << 5) | (cx4 << 2) |
+	     (bitmap->nextPixel(&cxPtr5) << 1) |
+	     refBitmap->nextPixel(&cxPtr6);
+
+	// decode the pixel
+	if ((pix = arithDecoder->decodeBit(cx, refinementRegionStats))) {
+	  bitmap->setPixel(x, y);
+	}
+      }
+    }
+  }
+
+  return bitmap;
+}
+
+void JBIG2Stream::readPageInfoSeg(Guint length) {
+  Guint xRes, yRes, flags, striping;
+
+  if (!readULong(&pageW) || !readULong(&pageH) ||
+      !readULong(&xRes) || !readULong(&yRes) ||
+      !readUByte(&flags) || !readUWord(&striping)) {
+    goto eofError;
+  }
+  pageDefPixel = (flags >> 2) & 1;
+  defCombOp = (flags >> 3) & 3;
+
+  // allocate the page bitmap
+  if (pageH == 0xffffffff) {
+    curPageH = striping & 0x7fff;
+  } else {
+    curPageH = pageH;
+  }
+  pageBitmap = new JBIG2Bitmap(0, pageW, curPageH);
+
+  // default pixel value
+  if (pageDefPixel) {
+    pageBitmap->clearToOne();
+  } else {
+    pageBitmap->clearToZero();
+  }
+
+  return;
+
+ eofError:
+  error(getPos(), "Unexpected EOF in JBIG2 stream");
+}
+
+void JBIG2Stream::readEndOfStripeSeg(Guint length) {
+  Guint i;
+
+  // skip the segment
+  for (i = 0; i < length; ++i) {
+    curStr->getChar();
+  }
+}
+
+void JBIG2Stream::readProfilesSeg(Guint length) {
+  Guint i;
+
+  // skip the segment
+  for (i = 0; i < length; ++i) {
+    curStr->getChar();
+  }
+}
+
+void JBIG2Stream::readCodeTableSeg(Guint segNum, Guint length) {
+  JBIG2HuffmanTable *huffTab;
+  Guint flags, oob, prefixBits, rangeBits;
+  int lowVal, highVal, val;
+  Guint huffTabSize, i;
+
+  if (!readUByte(&flags) || !readLong(&lowVal) || !readLong(&highVal)) {
+    goto eofError;
+  }
+  oob = flags & 1;
+  prefixBits = ((flags >> 1) & 7) + 1;
+  rangeBits = ((flags >> 4) & 7) + 1;
+
+  huffDecoder->reset();
+  huffTabSize = 8;
+  huffTab = (JBIG2HuffmanTable *)
+                gmallocn(huffTabSize, sizeof(JBIG2HuffmanTable));
+  i = 0;
+  val = lowVal;
+  while (val < highVal) {
+    if (i == huffTabSize) {
+      huffTabSize *= 2;
+      huffTab = (JBIG2HuffmanTable *)
+	            greallocn(huffTab, huffTabSize, sizeof(JBIG2HuffmanTable));
+    }
+    huffTab[i].val = val;
+    huffTab[i].prefixLen = huffDecoder->readBits(prefixBits);
+    huffTab[i].rangeLen = huffDecoder->readBits(rangeBits);
+    val += 1 << huffTab[i].rangeLen;
+    ++i;
+  }
+  if (i + oob + 3 > huffTabSize) {
+    huffTabSize = i + oob + 3;
+    huffTab = (JBIG2HuffmanTable *)
+                  greallocn(huffTab, huffTabSize, sizeof(JBIG2HuffmanTable));
+  }
+  huffTab[i].val = lowVal - 1;
+  huffTab[i].prefixLen = huffDecoder->readBits(prefixBits);
+  huffTab[i].rangeLen = jbig2HuffmanLOW;
+  ++i;
+  huffTab[i].val = highVal;
+  huffTab[i].prefixLen = huffDecoder->readBits(prefixBits);
+  huffTab[i].rangeLen = 32;
+  ++i;
+  if (oob) {
+    huffTab[i].val = 0;
+    huffTab[i].prefixLen = huffDecoder->readBits(prefixBits);
+    huffTab[i].rangeLen = jbig2HuffmanOOB;
+    ++i;
+  }
+  huffTab[i].val = 0;
+  huffTab[i].prefixLen = 0;
+  huffTab[i].rangeLen = jbig2HuffmanEOT;
+  huffDecoder->buildTable(huffTab, i);
+
+  // create and store the new table segment
+  segments->append(new JBIG2CodeTable(segNum, huffTab));
+
+  return;
+
+ eofError:
+  error(getPos(), "Unexpected EOF in JBIG2 stream");
+}
+
+void JBIG2Stream::readExtensionSeg(Guint length) {
+  Guint i;
+
+  // skip the segment
+  for (i = 0; i < length; ++i) {
+    curStr->getChar();
+  }
+}
+
+JBIG2Segment *JBIG2Stream::findSegment(Guint segNum) {
+  JBIG2Segment *seg;
+  int i;
+
+  for (i = 0; i < globalSegments->getLength(); ++i) {
+    seg = (JBIG2Segment *)globalSegments->get(i);
+    if (seg->getSegNum() == segNum) {
+      return seg;
+    }
+  }
+  for (i = 0; i < segments->getLength(); ++i) {
+    seg = (JBIG2Segment *)segments->get(i);
+    if (seg->getSegNum() == segNum) {
+      return seg;
+    }
+  }
+  return NULL;
+}
+
+void JBIG2Stream::discardSegment(Guint segNum) {
+  JBIG2Segment *seg;
+  int i;
+
+  for (i = 0; i < globalSegments->getLength(); ++i) {
+    seg = (JBIG2Segment *)globalSegments->get(i);
+    if (seg->getSegNum() == segNum) {
+      globalSegments->del(i);
+      return;
+    }
+  }
+  for (i = 0; i < segments->getLength(); ++i) {
+    seg = (JBIG2Segment *)segments->get(i);
+    if (seg->getSegNum() == segNum) {
+      segments->del(i);
+      return;
+    }
+  }
+}
+
+void JBIG2Stream::resetGenericStats(Guint templ,
+				    JArithmeticDecoderStats *prevStats) {
+  int size;
+
+  size = contextSize[templ];
+  if (prevStats && prevStats->getContextSize() == size) {
+    if (genericRegionStats->getContextSize() == size) {
+      genericRegionStats->copyFrom(prevStats);
+    } else {
+      delete genericRegionStats;
+      genericRegionStats = prevStats->copy();
+    }
+  } else {
+    if (genericRegionStats->getContextSize() == size) {
+      genericRegionStats->reset();
+    } else {
+      delete genericRegionStats;
+      genericRegionStats = new JArithmeticDecoderStats(1 << size);
+    }
+  }
+}
+
+void JBIG2Stream::resetRefinementStats(Guint templ,
+				       JArithmeticDecoderStats *prevStats) {
+  int size;
+
+  size = refContextSize[templ];
+  if (prevStats && prevStats->getContextSize() == size) {
+    if (refinementRegionStats->getContextSize() == size) {
+      refinementRegionStats->copyFrom(prevStats);
+    } else {
+      delete refinementRegionStats;
+      refinementRegionStats = prevStats->copy();
+    }
+  } else {
+    if (refinementRegionStats->getContextSize() == size) {
+      refinementRegionStats->reset();
+    } else {
+      delete refinementRegionStats;
+      refinementRegionStats = new JArithmeticDecoderStats(1 << size);
+    }
+  }
+}
+
+void JBIG2Stream::resetIntStats(int symCodeLen) {
+  iadhStats->reset();
+  iadwStats->reset();
+  iaexStats->reset();
+  iaaiStats->reset();
+  iadtStats->reset();
+  iaitStats->reset();
+  iafsStats->reset();
+  iadsStats->reset();
+  iardxStats->reset();
+  iardyStats->reset();
+  iardwStats->reset();
+  iardhStats->reset();
+  iariStats->reset();
+  if (iaidStats->getContextSize() == 1 << (symCodeLen + 1)) {
+    iaidStats->reset();
+  } else {
+    delete iaidStats;
+    iaidStats = new JArithmeticDecoderStats(1 << (symCodeLen + 1));
+  }
+}
+
+GBool JBIG2Stream::readUByte(Guint *x) {
+  int c0;
+
+  if ((c0 = curStr->getChar()) == EOF) {
+    return gFalse;
+  }
+  *x = (Guint)c0;
+  return gTrue;
+}
+
+GBool JBIG2Stream::readByte(int *x) {
+ int c0;
+
+  if ((c0 = curStr->getChar()) == EOF) {
+    return gFalse;
+  }
+  *x = c0;
+  if (c0 & 0x80) {
+    *x |= -1 - 0xff;
+  }
+  return gTrue;
+}
+
+GBool JBIG2Stream::readUWord(Guint *x) {
+  int c0, c1;
+
+  if ((c0 = curStr->getChar()) == EOF ||
+      (c1 = curStr->getChar()) == EOF) {
+    return gFalse;
+  }
+  *x = (Guint)((c0 << 8) | c1);
+  return gTrue;
+}
+
+GBool JBIG2Stream::readULong(Guint *x) {
+  int c0, c1, c2, c3;
+
+  if ((c0 = curStr->getChar()) == EOF ||
+      (c1 = curStr->getChar()) == EOF ||
+      (c2 = curStr->getChar()) == EOF ||
+      (c3 = curStr->getChar()) == EOF) {
+    return gFalse;
+  }
+  *x = (Guint)((c0 << 24) | (c1 << 16) | (c2 << 8) | c3);
+  return gTrue;
+}
+
+GBool JBIG2Stream::readLong(int *x) {
+  int c0, c1, c2, c3;
+
+  if ((c0 = curStr->getChar()) == EOF ||
+      (c1 = curStr->getChar()) == EOF ||
+      (c2 = curStr->getChar()) == EOF ||
+      (c3 = curStr->getChar()) == EOF) {
+    return gFalse;
+  }
+  *x = ((c0 << 24) | (c1 << 16) | (c2 << 8) | c3);
+  if (c0 & 0x80) {
+    *x |= -1 - (int)0xffffffff;
+  }
+  return gTrue;
+}
diff --git a/xpdf/JBIG2Stream.h b/xpdf/JBIG2Stream.h
new file mode 100644
--- /dev/null
+++ b/xpdf/JBIG2Stream.h
@@ -0,0 +1,145 @@
+//========================================================================
+//
+// JBIG2Stream.h
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef JBIG2STREAM_H
+#define JBIG2STREAM_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "Object.h"
+#include "Stream.h"
+
+class GList;
+class JBIG2Segment;
+class JBIG2Bitmap;
+class JArithmeticDecoder;
+class JArithmeticDecoderStats;
+class JBIG2HuffmanDecoder;
+struct JBIG2HuffmanTable;
+class JBIG2MMRDecoder;
+
+//------------------------------------------------------------------------
+
+class JBIG2Stream: public FilterStream {
+public:
+
+  JBIG2Stream(Stream *strA, Object *globalsStreamA);
+  virtual ~JBIG2Stream();
+  virtual StreamKind getKind() { return strJBIG2; }
+  virtual void reset();
+  virtual void close();
+  virtual int getChar();
+  virtual int lookChar();
+  virtual GString *getPSFilter(int psLevel, char *indent);
+  virtual GBool isBinary(GBool last = gTrue);
+
+private:
+
+  void readSegments();
+  GBool readSymbolDictSeg(Guint segNum, Guint length,
+			  Guint *refSegs, Guint nRefSegs);
+  void readTextRegionSeg(Guint segNum, GBool imm,
+			 GBool lossless, Guint length,
+			 Guint *refSegs, Guint nRefSegs);
+  JBIG2Bitmap *readTextRegion(GBool huff, GBool refine,
+			      int w, int h,
+			      Guint numInstances,
+			      Guint logStrips,
+			      int numSyms,
+			      JBIG2HuffmanTable *symCodeTab,
+			      Guint symCodeLen,
+			      JBIG2Bitmap **syms,
+			      Guint defPixel, Guint combOp,
+			      Guint transposed, Guint refCorner,
+			      int sOffset,
+			      JBIG2HuffmanTable *huffFSTable,
+			      JBIG2HuffmanTable *huffDSTable,
+			      JBIG2HuffmanTable *huffDTTable,
+			      JBIG2HuffmanTable *huffRDWTable,
+			      JBIG2HuffmanTable *huffRDHTable,
+			      JBIG2HuffmanTable *huffRDXTable,
+			      JBIG2HuffmanTable *huffRDYTable,
+			      JBIG2HuffmanTable *huffRSizeTable,
+			      Guint templ,
+			      int *atx, int *aty);
+  void readPatternDictSeg(Guint segNum, Guint length);
+  void readHalftoneRegionSeg(Guint segNum, GBool imm,
+			     GBool lossless, Guint length,
+			     Guint *refSegs, Guint nRefSegs);
+  void readGenericRegionSeg(Guint segNum, GBool imm,
+			    GBool lossless, Guint length);
+  JBIG2Bitmap *readGenericBitmap(GBool mmr, int w, int h,
+				 int templ, GBool tpgdOn,
+				 GBool useSkip, JBIG2Bitmap *skip,
+				 int *atx, int *aty,
+				 int mmrDataLength);
+  void readGenericRefinementRegionSeg(Guint segNum, GBool imm,
+				      GBool lossless, Guint length,
+				      Guint *refSegs,
+				      Guint nRefSegs);
+  JBIG2Bitmap *readGenericRefinementRegion(int w, int h,
+					   int templ, GBool tpgrOn,
+					   JBIG2Bitmap *refBitmap,
+					   int refDX, int refDY,
+					   int *atx, int *aty);
+  void readPageInfoSeg(Guint length);
+  void readEndOfStripeSeg(Guint length);
+  void readProfilesSeg(Guint length);
+  void readCodeTableSeg(Guint segNum, Guint length);
+  void readExtensionSeg(Guint length);
+  JBIG2Segment *findSegment(Guint segNum);
+  void discardSegment(Guint segNum);
+  void resetGenericStats(Guint templ,
+			 JArithmeticDecoderStats *prevStats);
+  void resetRefinementStats(Guint templ,
+			    JArithmeticDecoderStats *prevStats);
+  void resetIntStats(int symCodeLen);
+  GBool readUByte(Guint *x);
+  GBool readByte(int *x);
+  GBool readUWord(Guint *x);
+  GBool readULong(Guint *x);
+  GBool readLong(int *x);
+
+  Object globalsStream;
+  Guint pageW, pageH, curPageH;
+  Guint pageDefPixel;
+  JBIG2Bitmap *pageBitmap;
+  Guint defCombOp;
+  GList *segments;		// [JBIG2Segment]
+  GList *globalSegments;	// [JBIG2Segment]
+  Stream *curStr;
+  Guchar *dataPtr;
+  Guchar *dataEnd;
+
+  JArithmeticDecoder *arithDecoder;
+  JArithmeticDecoderStats *genericRegionStats;
+  JArithmeticDecoderStats *refinementRegionStats;
+  JArithmeticDecoderStats *iadhStats;
+  JArithmeticDecoderStats *iadwStats;
+  JArithmeticDecoderStats *iaexStats;
+  JArithmeticDecoderStats *iaaiStats;
+  JArithmeticDecoderStats *iadtStats;
+  JArithmeticDecoderStats *iaitStats;
+  JArithmeticDecoderStats *iafsStats;
+  JArithmeticDecoderStats *iadsStats;
+  JArithmeticDecoderStats *iardxStats;
+  JArithmeticDecoderStats *iardyStats;
+  JArithmeticDecoderStats *iardwStats;
+  JArithmeticDecoderStats *iardhStats;
+  JArithmeticDecoderStats *iariStats;
+  JArithmeticDecoderStats *iaidStats;
+  JBIG2HuffmanDecoder *huffDecoder;
+  JBIG2MMRDecoder *mmrDecoder;
+};
+
+#endif
diff --git a/xpdf/JPXStream.cc b/xpdf/JPXStream.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/JPXStream.cc
@@ -0,0 +1,3144 @@
+//========================================================================
+//
+// JPXStream.cc
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <limits.h>
+#include "gmem.h"
+#include "Error.h"
+#include "JArithmeticDecoder.h"
+#include "JPXStream.h"
+
+//~ to do:
+//  - precincts
+//  - ROI
+//  - progression order changes
+//  - packed packet headers
+//  - support for palettes, channel maps, etc.
+//  - make sure all needed JP2/JPX subboxes are parsed (readBoxes)
+//  - can we assume that QCC segments must come after the QCD segment?
+//  - skip EPH markers (readTilePartData)
+//  - handle tilePartToEOC in readTilePartData
+//  - deal with multiple codeword segments (readTilePartData,
+//    readCodeBlockData)
+//  - progression orders 2, 3, and 4
+//  - in coefficient decoding (readCodeBlockData):
+//    - termination pattern: terminate after every coding pass
+//    - error resilience segmentation symbol
+//    - selective arithmetic coding bypass
+//    - vertically causal context formation
+//    - coeffs longer than 31 bits (should just ignore the extra bits?)
+//  - handle boxes larger than 2^32 bytes
+//  - the fixed-point arithmetic won't handle 16-bit pixels
+
+//------------------------------------------------------------------------
+
+// number of contexts for the arithmetic decoder
+#define jpxNContexts        19
+
+#define jpxContextSigProp    0	// 0 - 8: significance prop and cleanup
+#define jpxContextSign       9	// 9 - 13: sign
+#define jpxContextMagRef    14	// 14 -16: magnitude refinement
+#define jpxContextRunLength 17	// cleanup: run length
+#define jpxContextUniform   18	// cleanup: first signif coeff
+
+//------------------------------------------------------------------------
+
+#define jpxPassSigProp       0
+#define jpxPassMagRef        1
+#define jpxPassCleanup       2
+
+//------------------------------------------------------------------------
+
+// arithmetic decoder context for the significance propagation and
+// cleanup passes:
+//     [horiz][vert][diag][subband]
+// where subband = 0 for HL
+//               = 1 for LH and LL
+//               = 2 for HH
+static Guint sigPropContext[3][3][5][3] = {
+  {{{ 0, 0, 0 },   // horiz=0, vert=0, diag=0
+    { 1, 1, 3 },   // horiz=0, vert=0, diag=1
+    { 2, 2, 6 },   // horiz=0, vert=0, diag=2
+    { 2, 2, 8 },   // horiz=0, vert=0, diag=3
+    { 2, 2, 8 }},  // horiz=0, vert=0, diag=4
+   {{ 5, 3, 1 },   // horiz=0, vert=1, diag=0
+    { 6, 3, 4 },   // horiz=0, vert=1, diag=1
+    { 6, 3, 7 },   // horiz=0, vert=1, diag=2
+    { 6, 3, 8 },   // horiz=0, vert=1, diag=3
+    { 6, 3, 8 }},  // horiz=0, vert=1, diag=4
+   {{ 8, 4, 2 },   // horiz=0, vert=2, diag=0
+    { 8, 4, 5 },   // horiz=0, vert=2, diag=1
+    { 8, 4, 7 },   // horiz=0, vert=2, diag=2
+    { 8, 4, 8 },   // horiz=0, vert=2, diag=3
+    { 8, 4, 8 }}}, // horiz=0, vert=2, diag=4
+  {{{ 3, 5, 1 },   // horiz=1, vert=0, diag=0
+    { 3, 6, 4 },   // horiz=1, vert=0, diag=1
+    { 3, 6, 7 },   // horiz=1, vert=0, diag=2
+    { 3, 6, 8 },   // horiz=1, vert=0, diag=3
+    { 3, 6, 8 }},  // horiz=1, vert=0, diag=4
+   {{ 7, 7, 2 },   // horiz=1, vert=1, diag=0
+    { 7, 7, 5 },   // horiz=1, vert=1, diag=1
+    { 7, 7, 7 },   // horiz=1, vert=1, diag=2
+    { 7, 7, 8 },   // horiz=1, vert=1, diag=3
+    { 7, 7, 8 }},  // horiz=1, vert=1, diag=4
+   {{ 8, 7, 2 },   // horiz=1, vert=2, diag=0
+    { 8, 7, 5 },   // horiz=1, vert=2, diag=1
+    { 8, 7, 7 },   // horiz=1, vert=2, diag=2
+    { 8, 7, 8 },   // horiz=1, vert=2, diag=3
+    { 8, 7, 8 }}}, // horiz=1, vert=2, diag=4
+  {{{ 4, 8, 2 },   // horiz=2, vert=0, diag=0
+    { 4, 8, 5 },   // horiz=2, vert=0, diag=1
+    { 4, 8, 7 },   // horiz=2, vert=0, diag=2
+    { 4, 8, 8 },   // horiz=2, vert=0, diag=3
+    { 4, 8, 8 }},  // horiz=2, vert=0, diag=4
+   {{ 7, 8, 2 },   // horiz=2, vert=1, diag=0
+    { 7, 8, 5 },   // horiz=2, vert=1, diag=1
+    { 7, 8, 7 },   // horiz=2, vert=1, diag=2
+    { 7, 8, 8 },   // horiz=2, vert=1, diag=3
+    { 7, 8, 8 }},  // horiz=2, vert=1, diag=4
+   {{ 8, 8, 2 },   // horiz=2, vert=2, diag=0
+    { 8, 8, 5 },   // horiz=2, vert=2, diag=1
+    { 8, 8, 7 },   // horiz=2, vert=2, diag=2
+    { 8, 8, 8 },   // horiz=2, vert=2, diag=3
+    { 8, 8, 8 }}}  // horiz=2, vert=2, diag=4
+};
+
+// arithmetic decoder context and xor bit for the sign bit in the
+// significance propagation pass:
+//     [horiz][vert][k]
+// where horiz/vert are offset by 2 (i.e., range is -2 .. 2)
+// and k = 0 for the context
+//       = 1 for the xor bit
+static Guint signContext[5][5][2] = {
+  {{ 13, 1 },  // horiz=-2, vert=-2
+   { 13, 1 },  // horiz=-2, vert=-1
+   { 12, 1 },  // horiz=-2, vert= 0
+   { 11, 1 },  // horiz=-2, vert=+1
+   { 11, 1 }}, // horiz=-2, vert=+2
+  {{ 13, 1 },  // horiz=-1, vert=-2
+   { 13, 1 },  // horiz=-1, vert=-1
+   { 12, 1 },  // horiz=-1, vert= 0
+   { 11, 1 },  // horiz=-1, vert=+1
+   { 11, 1 }}, // horiz=-1, vert=+2
+  {{ 10, 1 },  // horiz= 0, vert=-2
+   { 10, 1 },  // horiz= 0, vert=-1
+   {  9, 0 },  // horiz= 0, vert= 0
+   { 10, 0 },  // horiz= 0, vert=+1
+   { 10, 0 }}, // horiz= 0, vert=+2
+  {{ 11, 0 },  // horiz=+1, vert=-2
+   { 11, 0 },  // horiz=+1, vert=-1
+   { 12, 0 },  // horiz=+1, vert= 0
+   { 13, 0 },  // horiz=+1, vert=+1
+   { 13, 0 }}, // horiz=+1, vert=+2
+  {{ 11, 0 },  // horiz=+2, vert=-2
+   { 11, 0 },  // horiz=+2, vert=-1
+   { 12, 0 },  // horiz=+2, vert= 0
+   { 13, 0 },  // horiz=+2, vert=+1
+   { 13, 0 }}, // horiz=+2, vert=+2
+};
+
+//------------------------------------------------------------------------
+
+// constants used in the IDWT
+#define idwtAlpha  -1.586134342059924
+#define idwtBeta   -0.052980118572961
+#define idwtGamma   0.882911075530934
+#define idwtDelta   0.443506852043971
+#define idwtKappa   1.230174104914001
+#define idwtIKappa  (1.0 / idwtKappa)
+
+// number of bits to the right of the decimal point for the fixed
+// point arithmetic used in the IDWT
+#define fracBits 16
+
+//------------------------------------------------------------------------
+
+// floor(x / y)
+#define jpxFloorDiv(x, y) ((x) / (y))
+
+// floor(x / 2^y)
+#define jpxFloorDivPow2(x, y) ((x) >> (y))
+
+// ceil(x / y)
+#define jpxCeilDiv(x, y) (((x) + (y) - 1) / (y))
+
+// ceil(x / 2^y)
+#define jpxCeilDivPow2(x, y) (((x) + (1 << (y)) - 1) >> (y))
+
+//------------------------------------------------------------------------
+
+#if 1 //----- disable coverage tracking
+
+#define cover(idx)
+
+#else //----- enable coverage tracking
+
+class JPXCover {
+public:
+
+  JPXCover(int sizeA);
+  ~JPXCover();
+  void incr(int idx);
+
+private:
+
+  int size, used;
+  int *data;
+};
+
+JPXCover::JPXCover(int sizeA) {
+  size = sizeA;
+  used = -1;
+  data = (int *)gmallocn(size, sizeof(int));
+  memset(data, 0, size * sizeof(int));
+}
+
+JPXCover::~JPXCover() {
+  int i;
+
+  printf("JPX coverage:\n");
+  for (i = 0; i <= used; ++i) {
+    printf("  %4d: %8d\n", i, data[i]);
+  }
+  gfree(data);
+}
+
+void JPXCover::incr(int idx) {
+  if (idx < size) {
+    ++data[idx];
+    if (idx > used) {
+      used = idx;
+    }
+  }
+}
+
+JPXCover jpxCover(150);
+
+#define cover(idx) jpxCover.incr(idx)
+
+#endif //----- coverage tracking
+
+//------------------------------------------------------------------------
+
+JPXStream::JPXStream(Stream *strA):
+  FilterStream(strA)
+{
+  nComps = 0;
+  bpc = NULL;
+  width = height = 0;
+  haveCS = gFalse;
+  havePalette = gFalse;
+  haveCompMap = gFalse;
+  haveChannelDefn = gFalse;
+
+  img.tiles = NULL;
+  bitBuf = 0;
+  bitBufLen = 0;
+  bitBufSkip = gFalse;
+  byteCount = 0;
+}
+
+JPXStream::~JPXStream() {
+  close();
+  delete str;
+}
+
+void JPXStream::reset() {
+  str->reset();
+  if (readBoxes()) {
+    curY = img.yOffset;
+  } else {
+    // readBoxes reported an error, so we go immediately to EOF
+    curY = img.ySize;
+  }
+  curX = img.xOffset;
+  curComp = 0;
+  readBufLen = 0;
+}
+
+void JPXStream::close() {
+  JPXTile *tile;
+  JPXTileComp *tileComp;
+  JPXResLevel *resLevel;
+  JPXPrecinct *precinct;
+  JPXSubband *subband;
+  JPXCodeBlock *cb;
+  Guint comp, i, k, r, pre, sb;
+
+  gfree(bpc);
+  bpc = NULL;
+  if (havePalette) {
+    gfree(palette.bpc);
+    gfree(palette.c);
+    havePalette = gFalse;
+  }
+  if (haveCompMap) {
+    gfree(compMap.comp);
+    gfree(compMap.type);
+    gfree(compMap.pComp);
+    haveCompMap = gFalse;
+  }
+  if (haveChannelDefn) {
+    gfree(channelDefn.idx);
+    gfree(channelDefn.type);
+    gfree(channelDefn.assoc);
+    haveChannelDefn = gFalse;
+  }
+
+  if (img.tiles) {
+    for (i = 0; i < img.nXTiles * img.nYTiles; ++i) {
+      tile = &img.tiles[i];
+      if (tile->tileComps) {
+	for (comp = 0; comp < img.nComps; ++comp) {
+	  tileComp = &tile->tileComps[comp];
+	  gfree(tileComp->quantSteps);
+	  gfree(tileComp->data);
+	  gfree(tileComp->buf);
+	  if (tileComp->resLevels) {
+	    for (r = 0; r <= tileComp->nDecompLevels; ++r) {
+	      resLevel = &tileComp->resLevels[r];
+	      if (resLevel->precincts) {
+		for (pre = 0; pre < 1; ++pre) {
+		  precinct = &resLevel->precincts[pre];
+		  if (precinct->subbands) {
+		    for (sb = 0; sb < (Guint)(r == 0 ? 1 : 3); ++sb) {
+		      subband = &precinct->subbands[sb];
+		      gfree(subband->inclusion);
+		      gfree(subband->zeroBitPlane);
+		      if (subband->cbs) {
+			for (k = 0; k < subband->nXCBs * subband->nYCBs; ++k) {
+			  cb = &subband->cbs[k];
+			  gfree(cb->coeffs);
+			  if (cb->arithDecoder) {
+			    delete cb->arithDecoder;
+			  }
+			  if (cb->stats) {
+			    delete cb->stats;
+			  }
+			}
+			gfree(subband->cbs);
+		      }
+		    }
+		    gfree(precinct->subbands);
+		  }
+		}
+		gfree(img.tiles[i].tileComps[comp].resLevels[r].precincts);
+	      }
+	    }
+	    gfree(img.tiles[i].tileComps[comp].resLevels);
+	  }
+	}
+	gfree(img.tiles[i].tileComps);
+      }
+    }
+    gfree(img.tiles);
+    img.tiles = NULL;
+  }
+  FilterStream::close();
+}
+
+int JPXStream::getChar() {
+  int c;
+
+  if (readBufLen < 8) {
+    fillReadBuf();
+  }
+  if (readBufLen == 8) {
+    c = readBuf & 0xff;
+    readBufLen = 0;
+  } else if (readBufLen > 8) {
+    c = (readBuf >> (readBufLen - 8)) & 0xff;
+    readBufLen -= 8;
+  } else if (readBufLen == 0) {
+    c = EOF;
+  } else {
+    c = (readBuf << (8 - readBufLen)) & 0xff;
+    readBufLen = 0;
+  }
+  return c;
+}
+
+int JPXStream::lookChar() {
+  int c;
+
+  if (readBufLen < 8) {
+    fillReadBuf();
+  }
+  if (readBufLen == 8) {
+    c = readBuf & 0xff;
+  } else if (readBufLen > 8) {
+    c = (readBuf >> (readBufLen - 8)) & 0xff;
+  } else if (readBufLen == 0) {
+    c = EOF;
+  } else {
+    c = (readBuf << (8 - readBufLen)) & 0xff;
+  }
+  return c;
+}
+
+void JPXStream::fillReadBuf() {
+  JPXTileComp *tileComp;
+  Guint tileIdx, tx, ty;
+  int pix, pixBits;
+
+  do {
+    if (curY >= img.ySize) {
+      return;
+    }
+    tileIdx = ((curY - img.yTileOffset) / img.yTileSize) * img.nXTiles
+              + (curX - img.xTileOffset) / img.xTileSize;
+#if 1 //~ ignore the palette, assume the PDF ColorSpace object is valid
+    tileComp = &img.tiles[tileIdx].tileComps[curComp];
+#else
+    tileComp = &img.tiles[tileIdx].tileComps[havePalette ? 0 : curComp];
+#endif
+    tx = jpxCeilDiv((curX - img.xTileOffset) % img.xTileSize, tileComp->hSep);
+    ty = jpxCeilDiv((curY - img.yTileOffset) % img.yTileSize, tileComp->vSep);
+    pix = (int)tileComp->data[ty * (tileComp->x1 - tileComp->x0) + tx];
+    pixBits = tileComp->prec;
+#if 1 //~ ignore the palette, assume the PDF ColorSpace object is valid
+    if (++curComp == img.nComps) {
+#else
+    if (havePalette) {
+      if (pix >= 0 && pix < palette.nEntries) {
+	pix = palette.c[pix * palette.nComps + curComp];
+      } else {
+	pix = 
+      pixBits = palette.bpc[curComp];
+    }
+    if (++curComp == (Guint)(havePalette ? palette.nComps : img.nComps)) {
+#endif
+      curComp = 0;
+      if (++curX == img.xSize) {
+	curX = img.xOffset;
+	++curY;
+      }
+    }
+    if (pixBits == 8) {
+      readBuf = (readBuf << 8) | (pix & 0xff);
+    } else {
+      readBuf = (readBuf << pixBits) | (pix & ((1 << pixBits) - 1));
+    }
+    readBufLen += pixBits;
+  } while (readBufLen < 8);
+}
+
+GString *JPXStream::getPSFilter(int psLevel, char *indent) {
+  return NULL;
+}
+
+GBool JPXStream::isBinary(GBool last) {
+  return str->isBinary(gTrue);
+}
+
+void JPXStream::getImageParams(int *bitsPerComponent,
+			       StreamColorSpaceMode *csMode) {
+  Guint boxType, boxLen, dataLen, csEnum;
+  Guint bpc1, dummy, i;
+  int csMeth, csPrec, csPrec1, dummy2;
+  StreamColorSpaceMode csMode1;
+  GBool haveBPC, haveCSMode;
+
+  csPrec = 0; // make gcc happy
+  haveBPC = haveCSMode = gFalse;
+  str->reset();
+  if (str->lookChar() == 0xff) {
+    getImageParams2(bitsPerComponent, csMode);
+  } else {
+    while (readBoxHdr(&boxType, &boxLen, &dataLen)) {
+      if (boxType == 0x6a703268) { // JP2 header
+	cover(0);
+	// skip the superbox
+      } else if (boxType == 0x69686472) { // image header
+	cover(1);
+	if (readULong(&dummy) &&
+	    readULong(&dummy) &&
+	    readUWord(&dummy) &&
+	    readUByte(&bpc1) &&
+	    readUByte(&dummy) &&
+	    readUByte(&dummy) &&
+	    readUByte(&dummy)) {
+	  *bitsPerComponent = bpc1 + 1;
+	  haveBPC = gTrue;
+	}
+      } else if (boxType == 0x636F6C72) { // color specification
+	cover(2);
+	if (readByte(&csMeth) &&
+	    readByte(&csPrec1) &&
+	    readByte(&dummy2)) {
+	  if (csMeth == 1) {
+	    if (readULong(&csEnum)) {
+	      csMode1 = streamCSNone;
+	      if (csEnum == jpxCSBiLevel ||
+		  csEnum == jpxCSGrayscale) {
+		csMode1 = streamCSDeviceGray;
+	      } else if (csEnum == jpxCSCMYK) {
+		csMode1 = streamCSDeviceCMYK;
+	      } else if (csEnum == jpxCSsRGB ||
+			 csEnum == jpxCSCISesRGB ||
+			 csEnum == jpxCSROMMRGB) {
+		csMode1 = streamCSDeviceRGB;
+	      }
+	      if (csMode1 != streamCSNone &&
+		  (!haveCSMode || csPrec1 > csPrec)) {
+		*csMode = csMode1;
+		csPrec = csPrec1;
+		haveCSMode = gTrue;
+	      }
+	      for (i = 0; i < dataLen - 7; ++i) {
+		str->getChar();
+	      }
+	    }
+	  } else {
+	    for (i = 0; i < dataLen - 3; ++i) {
+	      str->getChar();
+	    }
+	  }
+	}
+      } else if (boxType == 0x6A703263) { // codestream
+	cover(3);
+	if (!(haveBPC && haveCSMode)) {
+	  getImageParams2(bitsPerComponent, csMode);
+	}
+	break;
+      } else {
+	cover(4);
+	for (i = 0; i < dataLen; ++i) {
+	  str->getChar();
+	}
+      }
+    }
+  }
+  str->close();
+}
+
+// Get image parameters from the codestream.
+void JPXStream::getImageParams2(int *bitsPerComponent,
+				StreamColorSpaceMode *csMode) {
+  int segType;
+  Guint segLen, nComps1, bpc1, dummy, i;
+
+  while (readMarkerHdr(&segType, &segLen)) {
+    if (segType == 0x51) { // SIZ - image and tile size
+      cover(5);
+      if (readUWord(&dummy) &&
+	  readULong(&dummy) &&
+	  readULong(&dummy) &&
+	  readULong(&dummy) &&
+	  readULong(&dummy) &&
+	  readULong(&dummy) &&
+	  readULong(&dummy) &&
+	  readULong(&dummy) &&
+	  readULong(&dummy) &&
+	  readUWord(&nComps1) &&
+	  readUByte(&bpc1)) {
+	*bitsPerComponent = (bpc1 & 0x7f) + 1;
+	// if there's no color space info, take a guess
+	if (nComps1 == 1) {
+	  *csMode = streamCSDeviceGray;
+	} else if (nComps1 == 3) {
+	  *csMode = streamCSDeviceRGB;
+	} else if (nComps1 == 4) {
+	  *csMode = streamCSDeviceCMYK;
+	}
+      }
+      break;
+    } else {
+      cover(6);
+      if (segLen > 2) {
+	for (i = 0; i < segLen - 2; ++i) {
+	  str->getChar();
+	}
+      }
+    }
+  }
+}
+
+GBool JPXStream::readBoxes() {
+  Guint boxType, boxLen, dataLen;
+  Guint bpc1, compression, unknownColorspace, ipr;
+  Guint i, j;
+
+  haveImgHdr = gFalse;
+
+  // check for a naked JPEG 2000 codestream (without the JP2/JPX
+  // wrapper) -- this appears to be a violation of the PDF spec, but
+  // Acrobat allows it
+  if (str->lookChar() == 0xff) {
+    cover(7);
+    error(getPos(), "Naked JPEG 2000 codestream, missing JP2/JPX wrapper");
+    readCodestream(0);
+    nComps = img.nComps;
+    bpc = (Guint *)gmallocn(nComps, sizeof(Guint));
+    for (i = 0; i < nComps; ++i) {
+      bpc[i] = img.tiles[0].tileComps[i].prec;
+    }
+    width = img.xSize - img.xOffset;
+    height = img.ySize - img.yOffset;
+    return gTrue;
+  }
+
+  while (readBoxHdr(&boxType, &boxLen, &dataLen)) {
+    switch (boxType) {
+    case 0x6a703268:		// JP2 header
+      // this is a grouping box ('superbox') which has no real
+      // contents and doesn't appear to be used consistently, i.e.,
+      // some things which should be subboxes of the JP2 header box
+      // show up outside of it - so we simply ignore the JP2 header
+      // box
+      cover(8);
+      break;
+    case 0x69686472:		// image header
+      cover(9);
+      if (!readULong(&height) ||
+	  !readULong(&width) ||
+	  !readUWord(&nComps) ||
+	  !readUByte(&bpc1) ||
+	  !readUByte(&compression) ||
+	  !readUByte(&unknownColorspace) ||
+	  !readUByte(&ipr)) {
+	error(getPos(), "Unexpected EOF in JPX stream");
+	return gFalse;
+      }
+      if (compression != 7) {
+	error(getPos(), "Unknown compression type in JPX stream");
+	return gFalse;
+      }
+      bpc = (Guint *)gmallocn(nComps, sizeof(Guint));
+      for (i = 0; i < nComps; ++i) {
+	bpc[i] = bpc1;
+      }
+      haveImgHdr = gTrue;
+      break;
+    case 0x62706363:		// bits per component
+      cover(10);
+      if (!haveImgHdr) {
+	error(getPos(), "Found bits per component box before image header box in JPX stream");
+	return gFalse;
+      }
+      if (dataLen != nComps) {
+	error(getPos(), "Invalid bits per component box in JPX stream");
+	return gFalse;
+      }
+      for (i = 0; i < nComps; ++i) {
+	if (!readUByte(&bpc[i])) {
+	  error(getPos(), "Unexpected EOF in JPX stream");
+	  return gFalse;
+	}
+      }
+      break;
+    case 0x636F6C72:		// color specification
+      cover(11);
+      if (!readColorSpecBox(dataLen)) {
+	return gFalse;
+      }
+      break;
+    case 0x70636c72:		// palette
+      cover(12);
+      if (!readUWord(&palette.nEntries) ||
+	  !readUByte(&palette.nComps)) {
+	error(getPos(), "Unexpected EOF in JPX stream");
+	return gFalse;
+      }
+      palette.bpc = (Guint *)gmallocn(palette.nComps, sizeof(Guint));
+      palette.c =
+          (int *)gmallocn(palette.nEntries * palette.nComps, sizeof(int));
+      for (i = 0; i < palette.nComps; ++i) {
+	if (!readUByte(&palette.bpc[i])) {
+	  error(getPos(), "Unexpected EOF in JPX stream");
+	  return gFalse;
+	}
+	++palette.bpc[i];
+      }
+      for (i = 0; i < palette.nEntries; ++i) {
+	for (j = 0; j < palette.nComps; ++j) {
+	  if (!readNBytes(((palette.bpc[j] & 0x7f) + 7) >> 3,
+			  (palette.bpc[j] & 0x80) ? gTrue : gFalse,
+			  &palette.c[i * palette.nComps + j])) {
+	    error(getPos(), "Unexpected EOF in JPX stream");
+	    return gFalse;
+	  }
+	}
+      }
+      havePalette = gTrue;
+      break;
+    case 0x636d6170:		// component mapping
+      cover(13);
+      compMap.nChannels = dataLen / 4;
+      compMap.comp = (Guint *)gmallocn(compMap.nChannels, sizeof(Guint));
+      compMap.type = (Guint *)gmallocn(compMap.nChannels, sizeof(Guint));
+      compMap.pComp = (Guint *)gmallocn(compMap.nChannels, sizeof(Guint));
+      for (i = 0; i < compMap.nChannels; ++i) {
+	if (!readUWord(&compMap.comp[i]) ||
+	    !readUByte(&compMap.type[i]) ||
+	    !readUByte(&compMap.pComp[i])) {
+	  error(getPos(), "Unexpected EOF in JPX stream");
+	  return gFalse;
+	}
+      }
+      haveCompMap = gTrue;
+      break;
+    case 0x63646566:		// channel definition
+      cover(14);
+      if (!readUWord(&channelDefn.nChannels)) {
+	error(getPos(), "Unexpected EOF in JPX stream");
+	return gFalse;
+      }
+      channelDefn.idx =
+	  (Guint *)gmallocn(channelDefn.nChannels, sizeof(Guint));
+      channelDefn.type =
+	  (Guint *)gmallocn(channelDefn.nChannels, sizeof(Guint));
+      channelDefn.assoc =
+	  (Guint *)gmallocn(channelDefn.nChannels, sizeof(Guint));
+      for (i = 0; i < channelDefn.nChannels; ++i) {
+	if (!readUWord(&channelDefn.idx[i]) ||
+	    !readUWord(&channelDefn.type[i]) ||
+	    !readUWord(&channelDefn.assoc[i])) {
+	  error(getPos(), "Unexpected EOF in JPX stream");
+	  return gFalse;
+	}
+      }
+      haveChannelDefn = gTrue;
+      break;
+    case 0x6A703263:		// contiguous codestream
+      cover(15);
+      if (!bpc) {
+	error(getPos(), "JPX stream is missing the image header box");
+      }
+      if (!haveCS) {
+	error(getPos(), "JPX stream has no supported color spec");
+      }
+      if (!readCodestream(dataLen)) {
+	return gFalse;
+      }
+      break;
+    default:
+      cover(16);
+      for (i = 0; i < dataLen; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Unexpected EOF in JPX stream");
+	  return gFalse;
+	}
+      }
+      break;
+    }
+  }
+  return gTrue;
+}
+
+GBool JPXStream::readColorSpecBox(Guint dataLen) {
+  JPXColorSpec newCS;
+  Guint csApprox, csEnum;
+  Guint i;
+  GBool ok;
+
+  ok = gFalse;
+  if (!readUByte(&newCS.meth) ||
+      !readByte(&newCS.prec) ||
+      !readUByte(&csApprox)) {
+    goto err;
+  }
+  switch (newCS.meth) {
+  case 1:			// enumerated colorspace
+    cover(17);
+    if (!readULong(&csEnum)) {
+      goto err;
+    }
+    newCS.enumerated.type = (JPXColorSpaceType)csEnum;
+    switch (newCS.enumerated.type) {
+    case jpxCSBiLevel:
+      ok = gTrue;
+      break;
+    case jpxCSYCbCr1:
+      ok = gTrue;
+      break;
+    case jpxCSYCbCr2:
+      ok = gTrue;
+      break;
+    case jpxCSYCBCr3:
+      ok = gTrue;
+      break;
+    case jpxCSPhotoYCC:
+      ok = gTrue;
+      break;
+    case jpxCSCMY:
+      ok = gTrue;
+      break;
+    case jpxCSCMYK:
+      ok = gTrue;
+      break;
+    case jpxCSYCCK:
+      ok = gTrue;
+      break;
+    case jpxCSCIELab:
+      if (dataLen == 7 + 7*4) {
+	if (!readULong(&newCS.enumerated.cieLab.rl) ||
+	    !readULong(&newCS.enumerated.cieLab.ol) ||
+	    !readULong(&newCS.enumerated.cieLab.ra) ||
+	    !readULong(&newCS.enumerated.cieLab.oa) ||
+	    !readULong(&newCS.enumerated.cieLab.rb) ||
+	    !readULong(&newCS.enumerated.cieLab.ob) ||
+	    !readULong(&newCS.enumerated.cieLab.il)) {
+	  goto err;
+	}
+      } else if (dataLen == 7) {
+	//~ this assumes the 8-bit case
+	cover(92);
+	newCS.enumerated.cieLab.rl = 100;
+	newCS.enumerated.cieLab.ol = 0;
+	newCS.enumerated.cieLab.ra = 255;
+	newCS.enumerated.cieLab.oa = 128;
+	newCS.enumerated.cieLab.rb = 255;
+	newCS.enumerated.cieLab.ob = 96;
+	newCS.enumerated.cieLab.il = 0x00443530;
+      } else {
+	goto err;
+      }
+      ok = gTrue;
+      break;
+    case jpxCSsRGB:
+      ok = gTrue;
+      break;
+    case jpxCSGrayscale:
+      ok = gTrue;
+      break;
+    case jpxCSBiLevel2:
+      ok = gTrue;
+      break;
+    case jpxCSCIEJab:
+      // not allowed in PDF
+      goto err;
+    case jpxCSCISesRGB:
+      ok = gTrue;
+      break;
+    case jpxCSROMMRGB:
+      ok = gTrue;
+      break;
+    case jpxCSsRGBYCbCr:
+      ok = gTrue;
+      break;
+    case jpxCSYPbPr1125:
+      ok = gTrue;
+      break;
+    case jpxCSYPbPr1250:
+      ok = gTrue;
+      break;
+    default:
+      goto err;
+    }
+    break;
+  case 2:			// restricted ICC profile
+  case 3: 			// any ICC profile (JPX)
+  case 4:			// vendor color (JPX)
+    cover(18);
+    for (i = 0; i < dataLen - 3; ++i) {
+      if (str->getChar() == EOF) {
+	goto err;
+      }
+    }
+    break;
+  }
+
+  if (ok && (!haveCS || newCS.prec > cs.prec)) {
+    cs = newCS;
+    haveCS = gTrue;
+  }
+
+  return gTrue;
+
+ err:
+  error(getPos(), "Error in JPX color spec");
+  return gFalse;
+}
+
+GBool JPXStream::readCodestream(Guint len) {
+  JPXTile *tile;
+  JPXTileComp *tileComp;
+  int segType;
+  GBool haveSIZ, haveCOD, haveQCD, haveSOT;
+  Guint precinctSize, style;
+  Guint segLen, capabilities, comp, i, j, r;
+
+  //----- main header
+  haveSIZ = haveCOD = haveQCD = haveSOT = gFalse;
+  do {
+    if (!readMarkerHdr(&segType, &segLen)) {
+      error(getPos(), "Error in JPX codestream");
+      return gFalse;
+    }
+    switch (segType) {
+    case 0x4f:			// SOC - start of codestream
+      // marker only
+      cover(19);
+      break;
+    case 0x51:			// SIZ - image and tile size
+      cover(20);
+      if (!readUWord(&capabilities) ||
+	  !readULong(&img.xSize) ||
+	  !readULong(&img.ySize) ||
+	  !readULong(&img.xOffset) ||
+	  !readULong(&img.yOffset) ||
+	  !readULong(&img.xTileSize) ||
+	  !readULong(&img.yTileSize) ||
+	  !readULong(&img.xTileOffset) ||
+	  !readULong(&img.yTileOffset) ||
+	  !readUWord(&img.nComps)) {
+	error(getPos(), "Error in JPX SIZ marker segment");
+	return gFalse;
+      }
+      if (haveImgHdr && img.nComps != nComps) {
+	error(getPos(), "Different number of components in JPX SIZ marker segment");
+	return gFalse;
+      }
+      img.nXTiles = (img.xSize - img.xTileOffset + img.xTileSize - 1)
+	            / img.xTileSize;
+      img.nYTiles = (img.ySize - img.yTileOffset + img.yTileSize - 1)
+	            / img.yTileSize;
+      // check for overflow before allocating memory
+      if (img.nXTiles <= 0 || img.nYTiles <= 0 ||
+	  img.nXTiles >= INT_MAX / img.nYTiles) {
+	error(getPos(), "Bad tile count in JPX SIZ marker segment");
+	return gFalse;
+      }
+      img.tiles = (JPXTile *)gmallocn(img.nXTiles * img.nYTiles,
+				      sizeof(JPXTile));
+      for (i = 0; i < img.nXTiles * img.nYTiles; ++i) {
+	img.tiles[i].tileComps = (JPXTileComp *)gmallocn(img.nComps,
+							 sizeof(JPXTileComp));
+	for (comp = 0; comp < img.nComps; ++comp) {
+	  img.tiles[i].tileComps[comp].quantSteps = NULL;
+	  img.tiles[i].tileComps[comp].data = NULL;
+	  img.tiles[i].tileComps[comp].buf = NULL;
+	  img.tiles[i].tileComps[comp].resLevels = NULL;
+	}
+      }
+      for (comp = 0; comp < img.nComps; ++comp) {
+	if (!readUByte(&img.tiles[0].tileComps[comp].prec) ||
+	    !readUByte(&img.tiles[0].tileComps[comp].hSep) ||
+	    !readUByte(&img.tiles[0].tileComps[comp].vSep)) {
+	  error(getPos(), "Error in JPX SIZ marker segment");
+	  return gFalse;
+	}
+	img.tiles[0].tileComps[comp].sgned =
+	    (img.tiles[0].tileComps[comp].prec & 0x80) ? gTrue : gFalse;
+	img.tiles[0].tileComps[comp].prec =
+	    (img.tiles[0].tileComps[comp].prec & 0x7f) + 1;
+	for (i = 1; i < img.nXTiles * img.nYTiles; ++i) {
+	  img.tiles[i].tileComps[comp] = img.tiles[0].tileComps[comp];
+	}
+      }
+      haveSIZ = gTrue;
+      break;
+    case 0x52:			// COD - coding style default
+      cover(21);
+      if (!readUByte(&img.tiles[0].tileComps[0].style) ||
+	  !readUByte(&img.tiles[0].progOrder) ||
+	  !readUWord(&img.tiles[0].nLayers) ||
+	  !readUByte(&img.tiles[0].multiComp) ||
+	  !readUByte(&img.tiles[0].tileComps[0].nDecompLevels) ||
+	  !readUByte(&img.tiles[0].tileComps[0].codeBlockW) ||
+	  !readUByte(&img.tiles[0].tileComps[0].codeBlockH) ||
+	  !readUByte(&img.tiles[0].tileComps[0].codeBlockStyle) ||
+	  !readUByte(&img.tiles[0].tileComps[0].transform)) {
+	error(getPos(), "Error in JPX COD marker segment");
+	return gFalse;
+      }
+      img.tiles[0].tileComps[0].codeBlockW += 2;
+      img.tiles[0].tileComps[0].codeBlockH += 2;
+      for (i = 0; i < img.nXTiles * img.nYTiles; ++i) {
+	if (i != 0) {
+	  img.tiles[i].progOrder = img.tiles[0].progOrder;
+	  img.tiles[i].nLayers = img.tiles[0].nLayers;
+	  img.tiles[i].multiComp = img.tiles[0].multiComp;
+	}
+	for (comp = 0; comp < img.nComps; ++comp) {
+	  if (!(i == 0 && comp == 0)) {
+	    img.tiles[i].tileComps[comp].style =
+	        img.tiles[0].tileComps[0].style;
+	    img.tiles[i].tileComps[comp].nDecompLevels =
+	        img.tiles[0].tileComps[0].nDecompLevels;
+	    img.tiles[i].tileComps[comp].codeBlockW =
+	        img.tiles[0].tileComps[0].codeBlockW;
+	    img.tiles[i].tileComps[comp].codeBlockH =
+	        img.tiles[0].tileComps[0].codeBlockH;
+	    img.tiles[i].tileComps[comp].codeBlockStyle =
+	        img.tiles[0].tileComps[0].codeBlockStyle;
+	    img.tiles[i].tileComps[comp].transform =
+	        img.tiles[0].tileComps[0].transform;
+	  }
+	  img.tiles[i].tileComps[comp].resLevels =
+	      (JPXResLevel *)gmallocn(
+		     (img.tiles[i].tileComps[comp].nDecompLevels + 1),
+		     sizeof(JPXResLevel));
+	  for (r = 0; r <= img.tiles[i].tileComps[comp].nDecompLevels; ++r) {
+	    img.tiles[i].tileComps[comp].resLevels[r].precincts = NULL;
+	  }
+	}
+      }
+      for (r = 0; r <= img.tiles[0].tileComps[0].nDecompLevels; ++r) {
+	if (img.tiles[0].tileComps[0].style & 0x01) {
+	  cover(91);
+	  if (!readUByte(&precinctSize)) {
+	    error(getPos(), "Error in JPX COD marker segment");
+	    return gFalse;
+	  }
+	  img.tiles[0].tileComps[0].resLevels[r].precinctWidth =
+	      precinctSize & 0x0f;
+	  img.tiles[0].tileComps[0].resLevels[r].precinctHeight =
+	      (precinctSize >> 4) & 0x0f;
+	} else {
+	  img.tiles[0].tileComps[0].resLevels[r].precinctWidth = 15;
+	  img.tiles[0].tileComps[0].resLevels[r].precinctHeight = 15;
+	}
+      }
+      for (i = 0; i < img.nXTiles * img.nYTiles; ++i) {
+	for (comp = 0; comp < img.nComps; ++comp) {
+	  if (!(i == 0 && comp == 0)) {
+	    for (r = 0; r <= img.tiles[i].tileComps[comp].nDecompLevels; ++r) {
+	      img.tiles[i].tileComps[comp].resLevels[r].precinctWidth =
+		  img.tiles[0].tileComps[0].resLevels[r].precinctWidth;
+	      img.tiles[i].tileComps[comp].resLevels[r].precinctHeight =
+		  img.tiles[0].tileComps[0].resLevels[r].precinctHeight;
+	    }
+	  }
+	}
+      }
+      haveCOD = gTrue;
+      break;
+    case 0x53:			// COC - coding style component
+      cover(22);
+      if (!haveCOD) {
+	error(getPos(), "JPX COC marker segment before COD segment");
+	return gFalse;
+      }
+      if ((img.nComps > 256 && !readUWord(&comp)) ||
+	  (img.nComps <= 256 && !readUByte(&comp)) ||
+	  comp >= img.nComps ||
+	  !readUByte(&style) ||
+	  !readUByte(&img.tiles[0].tileComps[comp].nDecompLevels) ||
+	  !readUByte(&img.tiles[0].tileComps[comp].codeBlockW) ||
+	  !readUByte(&img.tiles[0].tileComps[comp].codeBlockH) ||
+	  !readUByte(&img.tiles[0].tileComps[comp].codeBlockStyle) ||
+	  !readUByte(&img.tiles[0].tileComps[comp].transform)) {
+	error(getPos(), "Error in JPX COC marker segment");
+	return gFalse;
+      }
+      img.tiles[0].tileComps[comp].style =
+	  (img.tiles[0].tileComps[comp].style & ~1) | (style & 1);
+      img.tiles[0].tileComps[comp].codeBlockW += 2;
+      img.tiles[0].tileComps[comp].codeBlockH += 2;
+      for (i = 0; i < img.nXTiles * img.nYTiles; ++i) {
+	if (i != 0) {
+	  img.tiles[i].tileComps[comp].style =
+	      img.tiles[0].tileComps[comp].style;
+	  img.tiles[i].tileComps[comp].nDecompLevels =
+	      img.tiles[0].tileComps[comp].nDecompLevels;
+	  img.tiles[i].tileComps[comp].codeBlockW =
+	      img.tiles[0].tileComps[comp].codeBlockW;
+	  img.tiles[i].tileComps[comp].codeBlockH =
+	      img.tiles[0].tileComps[comp].codeBlockH;
+	  img.tiles[i].tileComps[comp].codeBlockStyle =
+	      img.tiles[0].tileComps[comp].codeBlockStyle;
+	  img.tiles[i].tileComps[comp].transform =
+	      img.tiles[0].tileComps[comp].transform;
+	}
+	img.tiles[i].tileComps[comp].resLevels =
+	    (JPXResLevel *)greallocn(
+		     img.tiles[i].tileComps[comp].resLevels,
+		     (img.tiles[i].tileComps[comp].nDecompLevels + 1),
+		     sizeof(JPXResLevel));
+	for (r = 0; r <= img.tiles[i].tileComps[comp].nDecompLevels; ++r) {
+	  img.tiles[i].tileComps[comp].resLevels[r].precincts = NULL;
+	}
+      }
+      for (r = 0; r <= img.tiles[0].tileComps[comp].nDecompLevels; ++r) {
+	if (img.tiles[0].tileComps[comp].style & 0x01) {
+	  if (!readUByte(&precinctSize)) {
+	    error(getPos(), "Error in JPX COD marker segment");
+	    return gFalse;
+	  }
+	  img.tiles[0].tileComps[comp].resLevels[r].precinctWidth =
+	      precinctSize & 0x0f;
+	  img.tiles[0].tileComps[comp].resLevels[r].precinctHeight =
+	      (precinctSize >> 4) & 0x0f;
+	} else {
+	  img.tiles[0].tileComps[comp].resLevels[r].precinctWidth = 15;
+	  img.tiles[0].tileComps[comp].resLevels[r].precinctHeight = 15;
+	}
+      }
+      for (i = 1; i < img.nXTiles * img.nYTiles; ++i) {
+	for (r = 0; r <= img.tiles[i].tileComps[comp].nDecompLevels; ++r) {
+	  img.tiles[i].tileComps[comp].resLevels[r].precinctWidth =
+	      img.tiles[0].tileComps[comp].resLevels[r].precinctWidth;
+	  img.tiles[i].tileComps[comp].resLevels[r].precinctHeight =
+	      img.tiles[0].tileComps[comp].resLevels[r].precinctHeight;
+	}
+      }
+      break;
+    case 0x5c:			// QCD - quantization default
+      cover(23);
+      if (!readUByte(&img.tiles[0].tileComps[0].quantStyle)) {
+	error(getPos(), "Error in JPX QCD marker segment");
+	return gFalse;
+      }
+      if ((img.tiles[0].tileComps[0].quantStyle & 0x1f) == 0x00) {
+	img.tiles[0].tileComps[0].nQuantSteps = segLen - 3;
+	img.tiles[0].tileComps[0].quantSteps =
+	    (Guint *)greallocn(img.tiles[0].tileComps[0].quantSteps,
+			       img.tiles[0].tileComps[0].nQuantSteps,
+			       sizeof(Guint));
+	for (i = 0; i < img.tiles[0].tileComps[0].nQuantSteps; ++i) {
+	  if (!readUByte(&img.tiles[0].tileComps[0].quantSteps[i])) {
+	    error(getPos(), "Error in JPX QCD marker segment");
+	    return gFalse;
+	  }
+	}
+      } else if ((img.tiles[0].tileComps[0].quantStyle & 0x1f) == 0x01) {
+	img.tiles[0].tileComps[0].nQuantSteps = 1;
+	img.tiles[0].tileComps[0].quantSteps =
+	    (Guint *)greallocn(img.tiles[0].tileComps[0].quantSteps,
+			       img.tiles[0].tileComps[0].nQuantSteps,
+			       sizeof(Guint));
+	if (!readUWord(&img.tiles[0].tileComps[0].quantSteps[0])) {
+	  error(getPos(), "Error in JPX QCD marker segment");
+	  return gFalse;
+	}
+      } else if ((img.tiles[0].tileComps[0].quantStyle & 0x1f) == 0x02) {
+	img.tiles[0].tileComps[0].nQuantSteps = (segLen - 3) / 2;
+	img.tiles[0].tileComps[0].quantSteps =
+	    (Guint *)greallocn(img.tiles[0].tileComps[0].quantSteps,
+			       img.tiles[0].tileComps[0].nQuantSteps,
+			       sizeof(Guint));
+	for (i = 0; i < img.tiles[0].tileComps[0].nQuantSteps; ++i) {
+	  if (!readUWord(&img.tiles[0].tileComps[0].quantSteps[i])) {
+	    error(getPos(), "Error in JPX QCD marker segment");
+	    return gFalse;
+	  }
+	}
+      } else {
+	error(getPos(), "Error in JPX QCD marker segment");
+	return gFalse;
+      }
+      for (i = 0; i < img.nXTiles * img.nYTiles; ++i) {
+	for (comp = 0; comp < img.nComps; ++comp) {
+	  if (!(i == 0 && comp == 0)) {
+	    img.tiles[i].tileComps[comp].quantStyle =
+	        img.tiles[0].tileComps[0].quantStyle;
+	    img.tiles[i].tileComps[comp].nQuantSteps =
+	        img.tiles[0].tileComps[0].nQuantSteps;
+	    img.tiles[i].tileComps[comp].quantSteps = 
+	        (Guint *)greallocn(img.tiles[i].tileComps[comp].quantSteps,
+				   img.tiles[0].tileComps[0].nQuantSteps,
+				   sizeof(Guint));
+	    for (j = 0; j < img.tiles[0].tileComps[0].nQuantSteps; ++j) {
+	      img.tiles[i].tileComps[comp].quantSteps[j] =
+		  img.tiles[0].tileComps[0].quantSteps[j];
+	    }
+	  }
+	}
+      }
+      haveQCD = gTrue;
+      break;
+    case 0x5d:			// QCC - quantization component
+      cover(24);
+      if (!haveQCD) {
+	error(getPos(), "JPX QCC marker segment before QCD segment");
+	return gFalse;
+      }
+      if ((img.nComps > 256 && !readUWord(&comp)) ||
+	  (img.nComps <= 256 && !readUByte(&comp)) ||
+	  comp >= img.nComps ||
+	  !readUByte(&img.tiles[0].tileComps[comp].quantStyle)) {
+	error(getPos(), "Error in JPX QCC marker segment");
+	return gFalse;
+      }
+      if ((img.tiles[0].tileComps[comp].quantStyle & 0x1f) == 0x00) {
+	img.tiles[0].tileComps[comp].nQuantSteps =
+	    segLen - (img.nComps > 256 ? 5 : 4);
+	img.tiles[0].tileComps[comp].quantSteps =
+	    (Guint *)greallocn(img.tiles[0].tileComps[comp].quantSteps,
+			       img.tiles[0].tileComps[comp].nQuantSteps,
+			       sizeof(Guint));
+	for (i = 0; i < img.tiles[0].tileComps[comp].nQuantSteps; ++i) {
+	  if (!readUByte(&img.tiles[0].tileComps[comp].quantSteps[i])) {
+	    error(getPos(), "Error in JPX QCC marker segment");
+	    return gFalse;
+	  }
+	}
+      } else if ((img.tiles[0].tileComps[comp].quantStyle & 0x1f) == 0x01) {
+	img.tiles[0].tileComps[comp].nQuantSteps = 1;
+	img.tiles[0].tileComps[comp].quantSteps =
+	    (Guint *)greallocn(img.tiles[0].tileComps[comp].quantSteps,
+			       img.tiles[0].tileComps[comp].nQuantSteps,
+			       sizeof(Guint));
+	if (!readUWord(&img.tiles[0].tileComps[comp].quantSteps[0])) {
+	  error(getPos(), "Error in JPX QCC marker segment");
+	  return gFalse;
+	}
+      } else if ((img.tiles[0].tileComps[comp].quantStyle & 0x1f) == 0x02) {
+	img.tiles[0].tileComps[comp].nQuantSteps =
+	    (segLen - (img.nComps > 256 ? 5 : 4)) / 2;
+	img.tiles[0].tileComps[comp].quantSteps =
+	    (Guint *)greallocn(img.tiles[0].tileComps[comp].quantSteps,
+			       img.tiles[0].tileComps[comp].nQuantSteps,
+			       sizeof(Guint));
+	for (i = 0; i < img.tiles[0].tileComps[comp].nQuantSteps; ++i) {
+	  if (!readUWord(&img.tiles[0].tileComps[comp].quantSteps[i])) {
+	    error(getPos(), "Error in JPX QCD marker segment");
+	    return gFalse;
+	  }
+	}
+      } else {
+	error(getPos(), "Error in JPX QCC marker segment");
+	return gFalse;
+      }
+      for (i = 1; i < img.nXTiles * img.nYTiles; ++i) {
+	img.tiles[i].tileComps[comp].quantStyle =
+	    img.tiles[0].tileComps[comp].quantStyle;
+	img.tiles[i].tileComps[comp].nQuantSteps =
+	    img.tiles[0].tileComps[comp].nQuantSteps;
+	img.tiles[i].tileComps[comp].quantSteps = 
+	    (Guint *)greallocn(img.tiles[i].tileComps[comp].quantSteps,
+			       img.tiles[0].tileComps[comp].nQuantSteps,
+			       sizeof(Guint));
+	for (j = 0; j < img.tiles[0].tileComps[comp].nQuantSteps; ++j) {
+	  img.tiles[i].tileComps[comp].quantSteps[j] =
+	      img.tiles[0].tileComps[comp].quantSteps[j];
+	}
+      }
+      break;
+    case 0x5e:			// RGN - region of interest
+      cover(25);
+#if 1 //~ ROI is unimplemented
+      fprintf(stderr, "RGN\n");
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX PPM marker segment");
+	  return gFalse;
+	}
+      }
+#else
+      if ((img.nComps > 256 && !readUWord(&comp)) ||
+	  (img.nComps <= 256 && !readUByte(&comp)) ||
+	  comp >= img.nComps ||
+	  !readUByte(&compInfo[comp].defROI.style) ||
+	  !readUByte(&compInfo[comp].defROI.shift)) {
+	error(getPos(), "Error in JPX RGN marker segment");
+	return gFalse;
+      }
+#endif
+      break;
+    case 0x5f:			// POC - progression order change
+      cover(26);
+#if 1 //~ progression order changes are unimplemented
+      fprintf(stderr, "POC\n");
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX PPM marker segment");
+	  return gFalse;
+	}
+      }
+#else
+      nProgs = (segLen - 2) / (img.nComps > 256 ? 9 : 7);
+      progs = (JPXProgOrder *)gmallocn(nProgs, sizeof(JPXProgOrder));
+      for (i = 0; i < nProgs; ++i) {
+	if (!readUByte(&progs[i].startRes) ||
+	    !(img.nComps > 256 && readUWord(&progs[i].startComp)) ||
+	    !(img.nComps <= 256 && readUByte(&progs[i].startComp)) ||
+	    !readUWord(&progs[i].endLayer) ||
+	    !readUByte(&progs[i].endRes) ||
+	    !(img.nComps > 256 && readUWord(&progs[i].endComp)) ||
+	    !(img.nComps <= 256 && readUByte(&progs[i].endComp)) ||
+	    !readUByte(&progs[i].progOrder)) {
+	  error(getPos(), "Error in JPX POC marker segment");
+	  return gFalse;
+	}
+      }
+#endif
+      break;
+    case 0x60:			// PPM - packed packet headers, main header
+      cover(27);
+#if 1 //~ packed packet headers are unimplemented
+      fprintf(stderr, "PPM\n");
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX PPM marker segment");
+	  return gFalse;
+	}
+      }
+#endif
+      break;
+    case 0x55:			// TLM - tile-part lengths
+      // skipped
+      cover(28);
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX TLM marker segment");
+	  return gFalse;
+	}
+      }
+      break;
+    case 0x57:			// PLM - packet length, main header
+      // skipped
+      cover(29);
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX PLM marker segment");
+	  return gFalse;
+	}
+      }
+      break;
+    case 0x63:			// CRG - component registration
+      // skipped
+      cover(30);
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX CRG marker segment");
+	  return gFalse;
+	}
+      }
+      break;
+    case 0x64:			// COM - comment
+      // skipped
+      cover(31);
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX COM marker segment");
+	  return gFalse;
+	}
+      }
+      break;
+    case 0x90:			// SOT - start of tile
+      cover(32);
+      haveSOT = gTrue;
+      break;
+    default:
+      cover(33);
+      error(getPos(), "Unknown marker segment %02x in JPX stream", segType);
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  break;
+	}
+      }
+      break;
+    }
+  } while (!haveSOT);
+
+  if (!haveSIZ) {
+    error(getPos(), "Missing SIZ marker segment in JPX stream");
+    return gFalse;
+  }
+  if (!haveCOD) {
+    error(getPos(), "Missing COD marker segment in JPX stream");
+    return gFalse;
+  }
+  if (!haveQCD) {
+    error(getPos(), "Missing QCD marker segment in JPX stream");
+    return gFalse;
+  }
+
+  //----- read the tile-parts
+  while (1) {
+    if (!readTilePart()) {
+      return gFalse;
+    }
+    if (!readMarkerHdr(&segType, &segLen)) {
+      error(getPos(), "Error in JPX codestream");
+      return gFalse;
+    }
+    if (segType != 0x90) {	// SOT - start of tile
+      break;
+    }
+  }
+
+  if (segType != 0xd9) {	// EOC - end of codestream
+    error(getPos(), "Missing EOC marker in JPX codestream");
+    return gFalse;
+  }
+
+  //----- finish decoding the image
+  for (i = 0; i < img.nXTiles * img.nYTiles; ++i) {
+    tile = &img.tiles[i];
+    for (comp = 0; comp < img.nComps; ++comp) {
+      tileComp = &tile->tileComps[comp];
+      inverseTransform(tileComp);
+    }
+    if (!inverseMultiCompAndDC(tile)) {
+      return gFalse;
+    }
+  }
+
+  //~ can free memory below tileComps here, and also tileComp.buf
+
+  return gTrue;
+}
+
+GBool JPXStream::readTilePart() {
+  JPXTile *tile;
+  JPXTileComp *tileComp;
+  JPXResLevel *resLevel;
+  JPXPrecinct *precinct;
+  JPXSubband *subband;
+  JPXCodeBlock *cb;
+  GBool haveSOD;
+  Guint tileIdx, tilePartLen, tilePartIdx, nTileParts;
+  GBool tilePartToEOC;
+  Guint precinctSize, style;
+  Guint n, nSBs, nx, ny, sbx0, sby0, comp, segLen;
+  Guint i, j, k, cbX, cbY, r, pre, sb, cbi;
+  int segType, level;
+
+  // process the SOT marker segment
+  if (!readUWord(&tileIdx) ||
+      !readULong(&tilePartLen) ||
+      !readUByte(&tilePartIdx) ||
+      !readUByte(&nTileParts)) {
+    error(getPos(), "Error in JPX SOT marker segment");
+    return gFalse;
+  }
+
+  if (tileIdx >= img.nXTiles * img.nYTiles) {
+    error(getPos(), "Weird tile index in JPX stream");
+    return gFalse;
+  }
+
+  tilePartToEOC = tilePartLen == 0;
+  tilePartLen -= 12; // subtract size of SOT segment
+
+  haveSOD = gFalse;
+  do {
+    if (!readMarkerHdr(&segType, &segLen)) {
+      error(getPos(), "Error in JPX tile-part codestream");
+      return gFalse;
+    }
+    tilePartLen -= 2 + segLen;
+    switch (segType) {
+    case 0x52:			// COD - coding style default
+      cover(34);
+      if (!readUByte(&img.tiles[tileIdx].tileComps[0].style) ||
+	  !readUByte(&img.tiles[tileIdx].progOrder) ||
+	  !readUWord(&img.tiles[tileIdx].nLayers) ||
+	  !readUByte(&img.tiles[tileIdx].multiComp) ||
+	  !readUByte(&img.tiles[tileIdx].tileComps[0].nDecompLevels) ||
+	  !readUByte(&img.tiles[tileIdx].tileComps[0].codeBlockW) ||
+	  !readUByte(&img.tiles[tileIdx].tileComps[0].codeBlockH) ||
+	  !readUByte(&img.tiles[tileIdx].tileComps[0].codeBlockStyle) ||
+	  !readUByte(&img.tiles[tileIdx].tileComps[0].transform)) {
+	error(getPos(), "Error in JPX COD marker segment");
+	return gFalse;
+      }
+      img.tiles[tileIdx].tileComps[0].codeBlockW += 2;
+      img.tiles[tileIdx].tileComps[0].codeBlockH += 2;
+      for (comp = 0; comp < img.nComps; ++comp) {
+	if (comp != 0) {
+	  img.tiles[tileIdx].tileComps[comp].style =
+	      img.tiles[tileIdx].tileComps[0].style;
+	  img.tiles[tileIdx].tileComps[comp].nDecompLevels =
+	      img.tiles[tileIdx].tileComps[0].nDecompLevels;
+	  img.tiles[tileIdx].tileComps[comp].codeBlockW =
+	      img.tiles[tileIdx].tileComps[0].codeBlockW;
+	  img.tiles[tileIdx].tileComps[comp].codeBlockH =
+	      img.tiles[tileIdx].tileComps[0].codeBlockH;
+	  img.tiles[tileIdx].tileComps[comp].codeBlockStyle =
+	      img.tiles[tileIdx].tileComps[0].codeBlockStyle;
+	  img.tiles[tileIdx].tileComps[comp].transform =
+	      img.tiles[tileIdx].tileComps[0].transform;
+	}
+	img.tiles[tileIdx].tileComps[comp].resLevels =
+	    (JPXResLevel *)greallocn(
+		     img.tiles[tileIdx].tileComps[comp].resLevels,
+		     (img.tiles[tileIdx].tileComps[comp].nDecompLevels + 1),
+		     sizeof(JPXResLevel));
+	for (r = 0;
+	     r <= img.tiles[tileIdx].tileComps[comp].nDecompLevels;
+	     ++r) {
+	  img.tiles[tileIdx].tileComps[comp].resLevels[r].precincts = NULL;
+	}
+      }
+      for (r = 0; r <= img.tiles[tileIdx].tileComps[0].nDecompLevels; ++r) {
+	if (img.tiles[tileIdx].tileComps[0].style & 0x01) {
+	  if (!readUByte(&precinctSize)) {
+	    error(getPos(), "Error in JPX COD marker segment");
+	    return gFalse;
+	  }
+	  img.tiles[tileIdx].tileComps[0].resLevels[r].precinctWidth =
+	      precinctSize & 0x0f;
+	  img.tiles[tileIdx].tileComps[0].resLevels[r].precinctHeight =
+	      (precinctSize >> 4) & 0x0f;
+	} else {
+	  img.tiles[tileIdx].tileComps[0].resLevels[r].precinctWidth = 15;
+	  img.tiles[tileIdx].tileComps[0].resLevels[r].precinctHeight = 15;
+	}
+      }
+      for (comp = 1; comp < img.nComps; ++comp) {
+	for (r = 0;
+	     r <= img.tiles[tileIdx].tileComps[comp].nDecompLevels;
+	     ++r) {
+	  img.tiles[tileIdx].tileComps[comp].resLevels[r].precinctWidth =
+	      img.tiles[tileIdx].tileComps[0].resLevels[r].precinctWidth;
+	  img.tiles[tileIdx].tileComps[comp].resLevels[r].precinctHeight =
+	      img.tiles[tileIdx].tileComps[0].resLevels[r].precinctHeight;
+	}
+      }
+      break;
+    case 0x53:			// COC - coding style component
+      cover(35);
+      if ((img.nComps > 256 && !readUWord(&comp)) ||
+	  (img.nComps <= 256 && !readUByte(&comp)) ||
+	  comp >= img.nComps ||
+	  !readUByte(&style) ||
+	  !readUByte(&img.tiles[tileIdx].tileComps[comp].nDecompLevels) ||
+	  !readUByte(&img.tiles[tileIdx].tileComps[comp].codeBlockW) ||
+	  !readUByte(&img.tiles[tileIdx].tileComps[comp].codeBlockH) ||
+	  !readUByte(&img.tiles[tileIdx].tileComps[comp].codeBlockStyle) ||
+	  !readUByte(&img.tiles[tileIdx].tileComps[comp].transform)) {
+	error(getPos(), "Error in JPX COC marker segment");
+	return gFalse;
+      }
+      img.tiles[tileIdx].tileComps[comp].style =
+	  (img.tiles[tileIdx].tileComps[comp].style & ~1) | (style & 1);
+      img.tiles[tileIdx].tileComps[comp].codeBlockW += 2;
+      img.tiles[tileIdx].tileComps[comp].codeBlockH += 2;
+      img.tiles[tileIdx].tileComps[comp].resLevels =
+	  (JPXResLevel *)greallocn(
+		     img.tiles[tileIdx].tileComps[comp].resLevels,
+		     (img.tiles[tileIdx].tileComps[comp].nDecompLevels + 1),
+		     sizeof(JPXResLevel));
+      for (r = 0; r <= img.tiles[tileIdx].tileComps[comp].nDecompLevels; ++r) {
+	img.tiles[tileIdx].tileComps[comp].resLevels[r].precincts = NULL;
+      }
+      for (r = 0; r <= img.tiles[tileIdx].tileComps[comp].nDecompLevels; ++r) {
+	if (img.tiles[tileIdx].tileComps[comp].style & 0x01) {
+	  if (!readUByte(&precinctSize)) {
+	    error(getPos(), "Error in JPX COD marker segment");
+	    return gFalse;
+	  }
+	  img.tiles[tileIdx].tileComps[comp].resLevels[r].precinctWidth =
+	      precinctSize & 0x0f;
+	  img.tiles[tileIdx].tileComps[comp].resLevels[r].precinctHeight =
+	      (precinctSize >> 4) & 0x0f;
+	} else {
+	  img.tiles[tileIdx].tileComps[comp].resLevels[r].precinctWidth = 15;
+	  img.tiles[tileIdx].tileComps[comp].resLevels[r].precinctHeight = 15;
+	}
+      }
+      break;
+    case 0x5c:			// QCD - quantization default
+      cover(36);
+      if (!readUByte(&img.tiles[tileIdx].tileComps[0].quantStyle)) {
+	error(getPos(), "Error in JPX QCD marker segment");
+	return gFalse;
+      }
+      if ((img.tiles[tileIdx].tileComps[0].quantStyle & 0x1f) == 0x00) {
+	img.tiles[tileIdx].tileComps[0].nQuantSteps =
+	    segLen - 3;
+	img.tiles[tileIdx].tileComps[0].quantSteps =
+	    (Guint *)greallocn(img.tiles[tileIdx].tileComps[0].quantSteps,
+			       img.tiles[tileIdx].tileComps[0].nQuantSteps,
+			       sizeof(Guint));
+	for (i = 0; i < img.tiles[tileIdx].tileComps[0].nQuantSteps; ++i) {
+	  if (!readUByte(&img.tiles[tileIdx].tileComps[0].quantSteps[i])) {
+	    error(getPos(), "Error in JPX QCD marker segment");
+	    return gFalse;
+	  }
+	}
+      } else if ((img.tiles[tileIdx].tileComps[0].quantStyle & 0x1f) == 0x01) {
+	img.tiles[tileIdx].tileComps[0].nQuantSteps = 1;
+	img.tiles[tileIdx].tileComps[0].quantSteps =
+	    (Guint *)greallocn(img.tiles[tileIdx].tileComps[0].quantSteps,
+			       img.tiles[tileIdx].tileComps[0].nQuantSteps,
+			       sizeof(Guint));
+	if (!readUWord(&img.tiles[tileIdx].tileComps[0].quantSteps[0])) {
+	  error(getPos(), "Error in JPX QCD marker segment");
+	  return gFalse;
+	}
+      } else if ((img.tiles[tileIdx].tileComps[0].quantStyle & 0x1f) == 0x02) {
+	img.tiles[tileIdx].tileComps[0].nQuantSteps = (segLen - 3) / 2;
+	img.tiles[tileIdx].tileComps[0].quantSteps =
+	    (Guint *)greallocn(img.tiles[tileIdx].tileComps[0].quantSteps,
+			       img.tiles[tileIdx].tileComps[0].nQuantSteps,
+			       sizeof(Guint));
+	for (i = 0; i < img.tiles[tileIdx].tileComps[0].nQuantSteps; ++i) {
+	  if (!readUWord(&img.tiles[tileIdx].tileComps[0].quantSteps[i])) {
+	    error(getPos(), "Error in JPX QCD marker segment");
+	    return gFalse;
+	  }
+	}
+      } else {
+	error(getPos(), "Error in JPX QCD marker segment");
+	return gFalse;
+      }
+      for (comp = 1; comp < img.nComps; ++comp) {
+	img.tiles[tileIdx].tileComps[comp].quantStyle =
+	    img.tiles[tileIdx].tileComps[0].quantStyle;
+	img.tiles[tileIdx].tileComps[comp].nQuantSteps =
+	    img.tiles[tileIdx].tileComps[0].nQuantSteps;
+	img.tiles[tileIdx].tileComps[comp].quantSteps = 
+	    (Guint *)greallocn(img.tiles[tileIdx].tileComps[comp].quantSteps,
+			       img.tiles[tileIdx].tileComps[0].nQuantSteps,
+			       sizeof(Guint));
+	for (j = 0; j < img.tiles[tileIdx].tileComps[0].nQuantSteps; ++j) {
+	  img.tiles[tileIdx].tileComps[comp].quantSteps[j] =
+	      img.tiles[tileIdx].tileComps[0].quantSteps[j];
+	}
+      }
+      break;
+    case 0x5d:			// QCC - quantization component
+      cover(37);
+      if ((img.nComps > 256 && !readUWord(&comp)) ||
+	  (img.nComps <= 256 && !readUByte(&comp)) ||
+	  comp >= img.nComps ||
+	  !readUByte(&img.tiles[tileIdx].tileComps[comp].quantStyle)) {
+	error(getPos(), "Error in JPX QCC marker segment");
+	return gFalse;
+      }
+      if ((img.tiles[tileIdx].tileComps[comp].quantStyle & 0x1f) == 0x00) {
+	img.tiles[tileIdx].tileComps[comp].nQuantSteps =
+	    segLen - (img.nComps > 256 ? 5 : 4);
+	img.tiles[tileIdx].tileComps[comp].quantSteps =
+	    (Guint *)greallocn(img.tiles[tileIdx].tileComps[comp].quantSteps,
+			       img.tiles[tileIdx].tileComps[comp].nQuantSteps,
+			       sizeof(Guint));
+	for (i = 0; i < img.tiles[tileIdx].tileComps[comp].nQuantSteps; ++i) {
+	  if (!readUByte(&img.tiles[tileIdx].tileComps[comp].quantSteps[i])) {
+	    error(getPos(), "Error in JPX QCC marker segment");
+	    return gFalse;
+	  }
+	}
+      } else if ((img.tiles[tileIdx].tileComps[comp].quantStyle & 0x1f)
+		 == 0x01) {
+	img.tiles[tileIdx].tileComps[comp].nQuantSteps = 1;
+	img.tiles[tileIdx].tileComps[comp].quantSteps =
+	    (Guint *)greallocn(img.tiles[tileIdx].tileComps[comp].quantSteps,
+			       img.tiles[tileIdx].tileComps[comp].nQuantSteps,
+			       sizeof(Guint));
+	if (!readUWord(&img.tiles[tileIdx].tileComps[comp].quantSteps[0])) {
+	  error(getPos(), "Error in JPX QCC marker segment");
+	  return gFalse;
+	}
+      } else if ((img.tiles[tileIdx].tileComps[comp].quantStyle & 0x1f)
+		 == 0x02) {
+	img.tiles[tileIdx].tileComps[comp].nQuantSteps =
+	    (segLen - (img.nComps > 256 ? 5 : 4)) / 2;
+	img.tiles[tileIdx].tileComps[comp].quantSteps =
+	    (Guint *)greallocn(img.tiles[tileIdx].tileComps[comp].quantSteps,
+			       img.tiles[tileIdx].tileComps[comp].nQuantSteps,
+			       sizeof(Guint));
+	for (i = 0; i < img.tiles[tileIdx].tileComps[comp].nQuantSteps; ++i) {
+	  if (!readUWord(&img.tiles[tileIdx].tileComps[comp].quantSteps[i])) {
+	    error(getPos(), "Error in JPX QCD marker segment");
+	    return gFalse;
+	  }
+	}
+      } else {
+	error(getPos(), "Error in JPX QCC marker segment");
+	return gFalse;
+      }
+      break;
+    case 0x5e:			// RGN - region of interest
+      cover(38);
+#if 1 //~ ROI is unimplemented
+      fprintf(stderr, "RGN\n");
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX PPM marker segment");
+	  return gFalse;
+	}
+      }
+#else
+      if ((img.nComps > 256 && !readUWord(&comp)) ||
+	  (img.nComps <= 256 && !readUByte(&comp)) ||
+	  comp >= img.nComps ||
+	  !readUByte(&compInfo[comp].roi.style) ||
+	  !readUByte(&compInfo[comp].roi.shift)) {
+	error(getPos(), "Error in JPX RGN marker segment");
+	return gFalse;
+      }
+#endif
+      break;
+    case 0x5f:			// POC - progression order change
+      cover(39);
+#if 1 //~ progression order changes are unimplemented
+      fprintf(stderr, "POC\n");
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX PPM marker segment");
+	  return gFalse;
+	}
+      }
+#else
+      nTileProgs = (segLen - 2) / (img.nComps > 256 ? 9 : 7);
+      tileProgs = (JPXProgOrder *)gmallocn(nTileProgs, sizeof(JPXProgOrder));
+      for (i = 0; i < nTileProgs; ++i) {
+	if (!readUByte(&tileProgs[i].startRes) ||
+	    !(img.nComps > 256 && readUWord(&tileProgs[i].startComp)) ||
+	    !(img.nComps <= 256 && readUByte(&tileProgs[i].startComp)) ||
+	    !readUWord(&tileProgs[i].endLayer) ||
+	    !readUByte(&tileProgs[i].endRes) ||
+	    !(img.nComps > 256 && readUWord(&tileProgs[i].endComp)) ||
+	    !(img.nComps <= 256 && readUByte(&tileProgs[i].endComp)) ||
+	    !readUByte(&tileProgs[i].progOrder)) {
+	  error(getPos(), "Error in JPX POC marker segment");
+	  return gFalse;
+	}
+      }
+#endif
+      break;
+    case 0x61:			// PPT - packed packet headers, tile-part hdr
+      cover(40);
+#if 1 //~ packed packet headers are unimplemented
+      fprintf(stderr, "PPT\n");
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX PPT marker segment");
+	  return gFalse;
+	}
+      }
+#endif
+    case 0x58:			// PLT - packet length, tile-part header
+      // skipped
+      cover(41);
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX PLT marker segment");
+	  return gFalse;
+	}
+      }
+      break;
+    case 0x64:			// COM - comment
+      // skipped
+      cover(42);
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  error(getPos(), "Error in JPX COM marker segment");
+	  return gFalse;
+	}
+      }
+      break;
+    case 0x93:			// SOD - start of data
+      cover(43);
+      haveSOD = gTrue;
+      break;
+    default:
+      cover(44);
+      error(getPos(), "Unknown marker segment %02x in JPX tile-part stream",
+	    segType);
+      for (i = 0; i < segLen - 2; ++i) {
+	if (str->getChar() == EOF) {
+	  break;
+	}
+      }
+      break;
+    }
+  } while (!haveSOD);
+
+  //----- initialize the tile, precincts, and code-blocks
+  if (tilePartIdx == 0) {
+    tile = &img.tiles[tileIdx];
+    i = tileIdx / img.nXTiles;
+    j = tileIdx % img.nXTiles;
+    if ((tile->x0 = img.xTileOffset + j * img.xTileSize) < img.xOffset) {
+      tile->x0 = img.xOffset;
+    }
+    if ((tile->y0 = img.yTileOffset + i * img.yTileSize) < img.yOffset) {
+      tile->y0 = img.yOffset;
+    }
+    if ((tile->x1 = img.xTileOffset + (j + 1) * img.xTileSize) > img.xSize) {
+      tile->x1 = img.xSize;
+    }
+    if ((tile->y1 = img.yTileOffset + (i + 1) * img.yTileSize) > img.ySize) {
+      tile->y1 = img.ySize;
+    }
+    tile->comp = 0;
+    tile->res = 0;
+    tile->precinct = 0;
+    tile->layer = 0;
+    tile->maxNDecompLevels = 0;
+    for (comp = 0; comp < img.nComps; ++comp) {
+      tileComp = &tile->tileComps[comp];
+      if (tileComp->nDecompLevels > tile->maxNDecompLevels) {
+	tile->maxNDecompLevels = tileComp->nDecompLevels;
+      }
+      tileComp->x0 = jpxCeilDiv(tile->x0, tileComp->hSep);
+      tileComp->y0 = jpxCeilDiv(tile->y0, tileComp->hSep);
+      tileComp->x1 = jpxCeilDiv(tile->x1, tileComp->hSep);
+      tileComp->y1 = jpxCeilDiv(tile->y1, tileComp->hSep);
+      tileComp->cbW = 1 << tileComp->codeBlockW;
+      tileComp->cbH = 1 << tileComp->codeBlockH;
+      tileComp->data = (int *)gmallocn((tileComp->x1 - tileComp->x0) *
+				       (tileComp->y1 - tileComp->y0),
+				       sizeof(int));
+      if (tileComp->x1 - tileComp->x0 > tileComp->y1 - tileComp->y0) {
+	n = tileComp->x1 - tileComp->x0;
+      } else {
+	n = tileComp->y1 - tileComp->y0;
+      }
+      tileComp->buf = (int *)gmallocn(n + 8, sizeof(int));
+      for (r = 0; r <= tileComp->nDecompLevels; ++r) {
+	resLevel = &tileComp->resLevels[r];
+	k = r == 0 ? tileComp->nDecompLevels
+	           : tileComp->nDecompLevels - r + 1;
+	resLevel->x0 = jpxCeilDivPow2(tileComp->x0, k);
+	resLevel->y0 = jpxCeilDivPow2(tileComp->y0, k);
+	resLevel->x1 = jpxCeilDivPow2(tileComp->x1, k);
+	resLevel->y1 = jpxCeilDivPow2(tileComp->y1, k);
+	if (r == 0) {
+	  resLevel->bx0[0] = resLevel->x0;
+	  resLevel->by0[0] = resLevel->y0;
+	  resLevel->bx1[0] = resLevel->x1;
+	  resLevel->by1[0] = resLevel->y1;
+	} else {
+	  resLevel->bx0[0] = jpxCeilDivPow2(tileComp->x0 - (1 << (k-1)), k);
+	  resLevel->by0[0] = resLevel->y0;
+	  resLevel->bx1[0] = jpxCeilDivPow2(tileComp->x1 - (1 << (k-1)), k);
+	  resLevel->by1[0] = resLevel->y1;
+	  resLevel->bx0[1] = resLevel->x0;
+	  resLevel->by0[1] = jpxCeilDivPow2(tileComp->y0 - (1 << (k-1)), k);
+	  resLevel->bx1[1] = resLevel->x1;
+	  resLevel->by1[1] = jpxCeilDivPow2(tileComp->y1 - (1 << (k-1)), k);
+	  resLevel->bx0[2] = jpxCeilDivPow2(tileComp->x0 - (1 << (k-1)), k);
+	  resLevel->by0[2] = jpxCeilDivPow2(tileComp->y0 - (1 << (k-1)), k);
+	  resLevel->bx1[2] = jpxCeilDivPow2(tileComp->x1 - (1 << (k-1)), k);
+	  resLevel->by1[2] = jpxCeilDivPow2(tileComp->y1 - (1 << (k-1)), k);
+	}
+	resLevel->precincts = (JPXPrecinct *)gmallocn(1, sizeof(JPXPrecinct));
+	for (pre = 0; pre < 1; ++pre) {
+	  precinct = &resLevel->precincts[pre];
+	  precinct->x0 = resLevel->x0;
+	  precinct->y0 = resLevel->y0;
+	  precinct->x1 = resLevel->x1;
+	  precinct->y1 = resLevel->y1;
+	  nSBs = r == 0 ? 1 : 3;
+	  precinct->subbands =
+	      (JPXSubband *)gmallocn(nSBs, sizeof(JPXSubband));
+	  for (sb = 0; sb < nSBs; ++sb) {
+	    subband = &precinct->subbands[sb];
+	    subband->x0 = resLevel->bx0[sb];
+	    subband->y0 = resLevel->by0[sb];
+	    subband->x1 = resLevel->bx1[sb];
+	    subband->y1 = resLevel->by1[sb];
+	    subband->nXCBs = jpxCeilDivPow2(subband->x1,
+					    tileComp->codeBlockW)
+	                     - jpxFloorDivPow2(subband->x0,
+					       tileComp->codeBlockW);
+	    subband->nYCBs = jpxCeilDivPow2(subband->y1,
+					    tileComp->codeBlockH)
+	                     - jpxFloorDivPow2(subband->y0,
+					       tileComp->codeBlockH);
+	    n = subband->nXCBs > subband->nYCBs ? subband->nXCBs
+	                                        : subband->nYCBs;
+	    for (subband->maxTTLevel = 0, --n;
+		 n;
+		 ++subband->maxTTLevel, n >>= 1) ;
+	    n = 0;
+	    for (level = subband->maxTTLevel; level >= 0; --level) {
+	      nx = jpxCeilDivPow2(subband->nXCBs, level);
+	      ny = jpxCeilDivPow2(subband->nYCBs, level);
+	      n += nx * ny;
+	    }
+	    subband->inclusion =
+	        (JPXTagTreeNode *)gmallocn(n, sizeof(JPXTagTreeNode));
+	    subband->zeroBitPlane =
+	        (JPXTagTreeNode *)gmallocn(n, sizeof(JPXTagTreeNode));
+	    for (k = 0; k < n; ++k) {
+	      subband->inclusion[k].finished = gFalse;
+	      subband->inclusion[k].val = 0;
+	      subband->zeroBitPlane[k].finished = gFalse;
+	      subband->zeroBitPlane[k].val = 0;
+	    }
+	    subband->cbs = (JPXCodeBlock *)gmallocn(subband->nXCBs *
+						      subband->nYCBs,
+						    sizeof(JPXCodeBlock));
+	    sbx0 = jpxFloorDivPow2(subband->x0, tileComp->codeBlockW);
+	    sby0 = jpxFloorDivPow2(subband->y0, tileComp->codeBlockH);
+	    cb = subband->cbs;
+	    for (cbY = 0; cbY < subband->nYCBs; ++cbY) {
+	      for (cbX = 0; cbX < subband->nXCBs; ++cbX) {
+		cb->x0 = (sbx0 + cbX) << tileComp->codeBlockW;
+		cb->x1 = cb->x0 + tileComp->cbW;
+		if (subband->x0 > cb->x0) {
+		  cb->x0 = subband->x0;
+		}
+		if (subband->x1 < cb->x1) {
+		  cb->x1 = subband->x1;
+		}
+		cb->y0 = (sby0 + cbY) << tileComp->codeBlockH;
+		cb->y1 = cb->y0 + tileComp->cbH;
+		if (subband->y0 > cb->y0) {
+		  cb->y0 = subband->y0;
+		}
+		if (subband->y1 < cb->y1) {
+		  cb->y1 = subband->y1;
+		}
+		cb->seen = gFalse;
+		cb->lBlock = 3;
+		cb->nextPass = jpxPassCleanup;
+		cb->nZeroBitPlanes = 0;
+		cb->coeffs =
+		    (JPXCoeff *)gmallocn((1 << (tileComp->codeBlockW
+						+ tileComp->codeBlockH)),
+					 sizeof(JPXCoeff));
+		for (cbi = 0;
+		     cbi < (Guint)(1 << (tileComp->codeBlockW
+					 + tileComp->codeBlockH));
+		     ++cbi) {
+		  cb->coeffs[cbi].flags = 0;
+		  cb->coeffs[cbi].len = 0;
+		  cb->coeffs[cbi].mag = 0;
+		}
+		cb->arithDecoder = NULL;
+		cb->stats = NULL;
+		++cb;
+	      }
+	    }
+	  }
+	}
+      }
+    }
+  }
+
+  return readTilePartData(tileIdx, tilePartLen, tilePartToEOC);
+}
+
+GBool JPXStream::readTilePartData(Guint tileIdx,
+				  Guint tilePartLen, GBool tilePartToEOC) {
+  JPXTile *tile;
+  JPXTileComp *tileComp;
+  JPXResLevel *resLevel;
+  JPXPrecinct *precinct;
+  JPXSubband *subband;
+  JPXCodeBlock *cb;
+  Guint ttVal;
+  Guint bits, cbX, cbY, nx, ny, i, j, n, sb;
+  int level;
+
+  tile = &img.tiles[tileIdx];
+
+  // read all packets from this tile-part
+  while (1) {
+    if (tilePartToEOC) {
+      //~ peek for an EOC marker
+      cover(93);
+    } else if (tilePartLen == 0) {
+      break;
+    }
+
+    tileComp = &tile->tileComps[tile->comp];
+    resLevel = &tileComp->resLevels[tile->res];
+    precinct = &resLevel->precincts[tile->precinct];
+
+    //----- packet header
+
+    // setup
+    startBitBuf(tilePartLen);
+
+    // zero-length flag
+    if (!readBits(1, &bits)) {
+      goto err;
+    }
+    if (!bits) {
+      // packet is empty -- clear all code-block inclusion flags
+      cover(45);
+      for (sb = 0; sb < (Guint)(tile->res == 0 ? 1 : 3); ++sb) {
+	subband = &precinct->subbands[sb];
+	for (cbY = 0; cbY < subband->nYCBs; ++cbY) {
+	  for (cbX = 0; cbX < subband->nXCBs; ++cbX) {
+	    cb = &subband->cbs[cbY * subband->nXCBs + cbX];
+	    cb->included = gFalse;
+	  }
+	}
+      }
+    } else {
+
+      for (sb = 0; sb < (Guint)(tile->res == 0 ? 1 : 3); ++sb) {
+	subband = &precinct->subbands[sb];
+	for (cbY = 0; cbY < subband->nYCBs; ++cbY) {
+	  for (cbX = 0; cbX < subband->nXCBs; ++cbX) {
+	    cb = &subband->cbs[cbY * subband->nXCBs + cbX];
+
+	    // skip code-blocks with no coefficients
+	    if (cb->x0 >= cb->x1 || cb->y0 >= cb->y1) {
+	      cover(46);
+	      cb->included = gFalse;
+	      continue;
+	    }
+
+	    // code-block inclusion
+	    if (cb->seen) {
+	      cover(47);
+	      if (!readBits(1, &cb->included)) {
+		goto err;
+	      }
+	    } else {
+	      cover(48);
+	      ttVal = 0;
+	      i = 0;
+	      for (level = subband->maxTTLevel; level >= 0; --level) {
+		nx = jpxCeilDivPow2(subband->nXCBs, level);
+		ny = jpxCeilDivPow2(subband->nYCBs, level);
+		j = i + (cbY >> level) * nx + (cbX >> level);
+		if (!subband->inclusion[j].finished &&
+		    !subband->inclusion[j].val) {
+		  subband->inclusion[j].val = ttVal;
+		} else {
+		  ttVal = subband->inclusion[j].val;
+		}
+		while (!subband->inclusion[j].finished &&
+		       ttVal <= tile->layer) {
+		  if (!readBits(1, &bits)) {
+		    goto err;
+		  }
+		  if (bits == 1) {
+		    subband->inclusion[j].finished = gTrue;
+		  } else {
+		    ++ttVal;
+		  }
+		}
+		subband->inclusion[j].val = ttVal;
+		if (ttVal > tile->layer) {
+		  break;
+		}
+		i += nx * ny;
+	      }
+	      cb->included = level < 0;
+	    }
+
+	    if (cb->included) {
+	      cover(49);
+
+	      // zero bit-plane count
+	      if (!cb->seen) {
+		cover(50);
+		ttVal = 0;
+		i = 0;
+		for (level = subband->maxTTLevel; level >= 0; --level) {
+		  nx = jpxCeilDivPow2(subband->nXCBs, level);
+		  ny = jpxCeilDivPow2(subband->nYCBs, level);
+		  j = i + (cbY >> level) * nx + (cbX >> level);
+		  if (!subband->zeroBitPlane[j].finished &&
+		      !subband->zeroBitPlane[j].val) {
+		    subband->zeroBitPlane[j].val = ttVal;
+		  } else {
+		    ttVal = subband->zeroBitPlane[j].val;
+		  }
+		  while (!subband->zeroBitPlane[j].finished) {
+		    if (!readBits(1, &bits)) {
+		      goto err;
+		    }
+		    if (bits == 1) {
+		      subband->zeroBitPlane[j].finished = gTrue;
+		    } else {
+		      ++ttVal;
+		    }
+		  }
+		  subband->zeroBitPlane[j].val = ttVal;
+		  i += nx * ny;
+		}
+		cb->nZeroBitPlanes = ttVal;
+	      }
+
+	      // number of coding passes
+	      if (!readBits(1, &bits)) {
+		goto err;
+	      }
+	      if (bits == 0) {
+		cover(51);
+		cb->nCodingPasses = 1;
+	      } else {
+		if (!readBits(1, &bits)) {
+		  goto err;
+		}
+		if (bits == 0) {
+		  cover(52);
+		  cb->nCodingPasses = 2;
+		} else {
+		  cover(53);
+		  if (!readBits(2, &bits)) {
+		    goto err;
+		  }
+		  if (bits < 3) {
+		    cover(54);
+		    cb->nCodingPasses = 3 + bits;
+		  } else {
+		    cover(55);
+		    if (!readBits(5, &bits)) {
+		      goto err;
+		    }
+		    if (bits < 31) {
+		      cover(56);
+		      cb->nCodingPasses = 6 + bits;
+		    } else {
+		      cover(57);
+		      if (!readBits(7, &bits)) {
+			goto err;
+		      }
+		      cb->nCodingPasses = 37 + bits;
+		    }
+		  }
+		}
+	      }
+
+	      // update Lblock
+	      while (1) {
+		if (!readBits(1, &bits)) {
+		  goto err;
+		}
+		if (!bits) {
+		  break;
+		}
+		++cb->lBlock;
+	      }
+
+	      // length of compressed data
+	      //~ deal with multiple codeword segments
+	      for (n = cb->lBlock, i = cb->nCodingPasses >> 1;
+		   i;
+		   ++n, i >>= 1) ;
+	      if (!readBits(n, &cb->dataLen)) {
+		goto err;
+	      }
+	    }
+	  }
+	}
+      }
+    }
+    tilePartLen = finishBitBuf();
+
+    //----- packet data
+
+    for (sb = 0; sb < (Guint)(tile->res == 0 ? 1 : 3); ++sb) {
+      subband = &precinct->subbands[sb];
+      for (cbY = 0; cbY < subband->nYCBs; ++cbY) {
+	for (cbX = 0; cbX < subband->nXCBs; ++cbX) {
+	  cb = &subband->cbs[cbY * subband->nXCBs + cbX];
+	  if (cb->included) {
+	    if (!readCodeBlockData(tileComp, resLevel, precinct, subband,
+				   tile->res, sb, cb)) {
+	      return gFalse;
+	    }
+	    tilePartLen -= cb->dataLen;
+	    cb->seen = gTrue;
+	  }
+	}
+      }
+    }
+
+    //----- next packet
+
+    switch (tile->progOrder) {
+    case 0: // layer, resolution level, component, precinct
+      cover(58);
+      if (++tile->comp == img.nComps) {
+	tile->comp = 0;
+	if (++tile->res == tile->maxNDecompLevels + 1) {
+	  tile->res = 0;
+	  if (++tile->layer == tile->nLayers) {
+	    tile->layer = 0;
+	  }
+	}
+      }
+      break;
+    case 1: // resolution level, layer, component, precinct
+      cover(59);
+      if (++tile->comp == img.nComps) {
+	tile->comp = 0;
+	if (++tile->layer == tile->nLayers) {
+	  tile->layer = 0;
+	  if (++tile->res == tile->maxNDecompLevels + 1) {
+	    tile->res = 0;
+	  }
+	}
+      }
+      break;
+    case 2: // resolution level, precinct, component, layer
+      //~ this isn't correct -- see B.12.1.3
+      cover(60);
+      if (++tile->layer == tile->nLayers) {
+	tile->layer = 0;
+	if (++tile->comp == img.nComps) {
+	  tile->comp = 0;
+	  if (++tile->res == tile->maxNDecompLevels + 1) {
+	    tile->res = 0;
+	  }
+	}
+      }
+      break;
+    case 3: // precinct, component, resolution level, layer
+      //~ this isn't correct -- see B.12.1.4
+      cover(61);
+      if (++tile->layer == tile->nLayers) {
+	tile->layer = 0;
+	if (++tile->res == tile->maxNDecompLevels + 1) {
+	  tile->res = 0;
+	  if (++tile->comp == img.nComps) {
+	    tile->comp = 0;
+	  }
+	}
+      }
+      break;
+    case 4: // component, precinct, resolution level, layer
+      //~ this isn't correct -- see B.12.1.5
+      cover(62);
+      if (++tile->layer == tile->nLayers) {
+	tile->layer = 0;
+	if (++tile->res == tile->maxNDecompLevels + 1) {
+	  tile->res = 0;
+	  if (++tile->comp == img.nComps) {
+	    tile->comp = 0;
+	  }
+	}
+      }
+      break;
+    }
+  }
+
+  return gTrue;
+
+ err:
+  error(getPos(), "Error in JPX stream");
+  return gFalse;
+}
+
+GBool JPXStream::readCodeBlockData(JPXTileComp *tileComp,
+				   JPXResLevel *resLevel,
+				   JPXPrecinct *precinct,
+				   JPXSubband *subband,
+				   Guint res, Guint sb,
+				   JPXCodeBlock *cb) {
+  JPXCoeff *coeff0, *coeff1, *coeff;
+  Guint horiz, vert, diag, all, cx, xorBit;
+  int horizSign, vertSign;
+  Guint i, x, y0, y1, y2;
+
+  if (cb->arithDecoder) {
+    cover(63);
+    cb->arithDecoder->restart(cb->dataLen);
+  } else {
+    cover(64);
+    cb->arithDecoder = new JArithmeticDecoder();
+    cb->arithDecoder->setStream(str, cb->dataLen);
+    cb->arithDecoder->start();
+    cb->stats = new JArithmeticDecoderStats(jpxNContexts);
+    cb->stats->setEntry(jpxContextSigProp, 4, 0);
+    cb->stats->setEntry(jpxContextRunLength, 3, 0);
+    cb->stats->setEntry(jpxContextUniform, 46, 0);
+  }
+
+  for (i = 0; i < cb->nCodingPasses; ++i) {
+    switch (cb->nextPass) {
+
+    //----- significance propagation pass
+    case jpxPassSigProp:
+      cover(65);
+      for (y0 = cb->y0, coeff0 = cb->coeffs;
+	   y0 < cb->y1;
+	   y0 += 4, coeff0 += 4 << tileComp->codeBlockW) {
+	for (x = cb->x0, coeff1 = coeff0;
+	     x < cb->x1;
+	     ++x, ++coeff1) {
+	  for (y1 = 0, coeff = coeff1;
+	       y1 < 4 && y0+y1 < cb->y1;
+	       ++y1, coeff += tileComp->cbW) {
+	    if (!(coeff->flags & jpxCoeffSignificant)) {
+	      horiz = vert = diag = 0;
+	      horizSign = vertSign = 2;
+	      if (x > cb->x0) {
+		if (coeff[-1].flags & jpxCoeffSignificant) {
+		  ++horiz;
+		  horizSign += (coeff[-1].flags & jpxCoeffSign) ? -1 : 1;
+		}
+		if (y0+y1 > cb->y0) {
+		  diag += (coeff[-(int)tileComp->cbW - 1].flags
+			   >> jpxCoeffSignificantB) & 1;
+		}
+		if (y0+y1 < cb->y1 - 1) {
+		  diag += (coeff[tileComp->cbW - 1].flags
+			   >> jpxCoeffSignificantB) & 1;
+		}
+	      }
+	      if (x < cb->x1 - 1) {
+		if (coeff[1].flags & jpxCoeffSignificant) {
+		  ++horiz;
+		  horizSign += (coeff[1].flags & jpxCoeffSign) ? -1 : 1;
+		}
+		if (y0+y1 > cb->y0) {
+		  diag += (coeff[-(int)tileComp->cbW + 1].flags
+			   >> jpxCoeffSignificantB) & 1;
+		}
+		if (y0+y1 < cb->y1 - 1) {
+		  diag += (coeff[tileComp->cbW + 1].flags
+			   >> jpxCoeffSignificantB) & 1;
+		}
+	      }
+	      if (y0+y1 > cb->y0) {
+		if (coeff[-(int)tileComp->cbW].flags & jpxCoeffSignificant) {
+		  ++vert;
+		  vertSign += (coeff[-(int)tileComp->cbW].flags & jpxCoeffSign)
+		              ? -1 : 1;
+		}
+	      }
+	      if (y0+y1 < cb->y1 - 1) {
+		if (coeff[tileComp->cbW].flags & jpxCoeffSignificant) {
+		  ++vert;
+		  vertSign += (coeff[tileComp->cbW].flags & jpxCoeffSign)
+		              ? -1 : 1;
+		}
+	      }
+	      cx = sigPropContext[horiz][vert][diag][res == 0 ? 1 : sb];
+	      if (cx != 0) {
+		if (cb->arithDecoder->decodeBit(cx, cb->stats)) {
+		  coeff->flags |= jpxCoeffSignificant | jpxCoeffFirstMagRef;
+		  coeff->mag = (coeff->mag << 1) | 1;
+		  cx = signContext[horizSign][vertSign][0];
+		  xorBit = signContext[horizSign][vertSign][1];
+		  if (cb->arithDecoder->decodeBit(cx, cb->stats) ^ xorBit) {
+		    coeff->flags |= jpxCoeffSign;
+		  }
+		}
+		++coeff->len;
+		coeff->flags |= jpxCoeffTouched;
+	      }
+	    }
+	  }
+	}
+      }
+      ++cb->nextPass;
+      break;
+
+    //----- magnitude refinement pass
+    case jpxPassMagRef:
+      cover(66);
+      for (y0 = cb->y0, coeff0 = cb->coeffs;
+	   y0 < cb->y1;
+	   y0 += 4, coeff0 += 4 << tileComp->codeBlockW) {
+	for (x = cb->x0, coeff1 = coeff0;
+	     x < cb->x1;
+	     ++x, ++coeff1) {
+	  for (y1 = 0, coeff = coeff1;
+	       y1 < 4 && y0+y1 < cb->y1;
+	       ++y1, coeff += tileComp->cbW) {
+	    if ((coeff->flags & jpxCoeffSignificant) &&
+		!(coeff->flags & jpxCoeffTouched)) {
+	      if (coeff->flags & jpxCoeffFirstMagRef) {
+		all = 0;
+		if (x > cb->x0) {
+		  all += (coeff[-1].flags >> jpxCoeffSignificantB) & 1;
+		  if (y0+y1 > cb->y0) {
+		    all += (coeff[-(int)tileComp->cbW - 1].flags
+			    >> jpxCoeffSignificantB) & 1;
+		  }
+		  if (y0+y1 < cb->y1 - 1) {
+		    all += (coeff[tileComp->cbW - 1].flags
+			    >> jpxCoeffSignificantB) & 1;
+		  }
+		}
+		if (x < cb->x1 - 1) {
+		  all += (coeff[1].flags >> jpxCoeffSignificantB) & 1;
+		  if (y0+y1 > cb->y0) {
+		    all += (coeff[-(int)tileComp->cbW + 1].flags
+			    >> jpxCoeffSignificantB) & 1;
+		  }
+		  if (y0+y1 < cb->y1 - 1) {
+		    all += (coeff[tileComp->cbW + 1].flags
+			    >> jpxCoeffSignificantB) & 1;
+		  }
+		}
+		if (y0+y1 > cb->y0) {
+		  all += (coeff[-(int)tileComp->cbW].flags
+			  >> jpxCoeffSignificantB) & 1;
+		}
+		if (y0+y1 < cb->y1 - 1) {
+		  all += (coeff[tileComp->cbW].flags
+			  >> jpxCoeffSignificantB) & 1;
+		}
+		cx = all ? 15 : 14;
+	      } else {
+		cx = 16;
+	      }
+	      coeff->mag = (coeff->mag << 1) |
+		           cb->arithDecoder->decodeBit(cx, cb->stats);
+	      ++coeff->len;
+	      coeff->flags |= jpxCoeffTouched;
+	      coeff->flags &= ~jpxCoeffFirstMagRef;
+	    }
+	  }
+	}
+      }
+      ++cb->nextPass;
+      break;
+
+    //----- cleanup pass
+    case jpxPassCleanup:
+      cover(67);
+      for (y0 = cb->y0, coeff0 = cb->coeffs;
+	   y0 < cb->y1;
+	   y0 += 4, coeff0 += 4 << tileComp->codeBlockW) {
+	for (x = cb->x0, coeff1 = coeff0;
+	     x < cb->x1;
+	     ++x, ++coeff1) {
+	  y1 = 0;
+	  if (y0 + 3 < cb->y1 &&
+	      !(coeff1->flags & jpxCoeffTouched) &&
+	      !(coeff1[tileComp->cbW].flags & jpxCoeffTouched) &&
+	      !(coeff1[2 * tileComp->cbW].flags & jpxCoeffTouched) &&
+	      !(coeff1[3 * tileComp->cbW].flags & jpxCoeffTouched) &&
+	      (x == cb->x0 || y0 == cb->y0 ||
+	       !(coeff1[-(int)tileComp->cbW - 1].flags
+		 & jpxCoeffSignificant)) &&
+	      (y0 == cb->y0 ||
+	       !(coeff1[-(int)tileComp->cbW].flags
+		 & jpxCoeffSignificant)) &&
+	      (x == cb->x1 - 1 || y0 == cb->y0 ||
+	       !(coeff1[-(int)tileComp->cbW + 1].flags
+		 & jpxCoeffSignificant)) &&
+	      (x == cb->x0 ||
+	       (!(coeff1[-1].flags & jpxCoeffSignificant) &&
+		!(coeff1[tileComp->cbW - 1].flags
+		  & jpxCoeffSignificant) &&
+		!(coeff1[2 * tileComp->cbW - 1].flags
+		  & jpxCoeffSignificant) && 
+		!(coeff1[3 * tileComp->cbW - 1].flags
+		  & jpxCoeffSignificant))) &&
+	      (x == cb->x1 - 1 ||
+	       (!(coeff1[1].flags & jpxCoeffSignificant) &&
+		!(coeff1[tileComp->cbW + 1].flags
+		  & jpxCoeffSignificant) &&
+		!(coeff1[2 * tileComp->cbW + 1].flags
+		  & jpxCoeffSignificant) &&
+		!(coeff1[3 * tileComp->cbW + 1].flags
+		  & jpxCoeffSignificant))) &&
+	      (x == cb->x0 || y0+4 == cb->y1 ||
+	       !(coeff1[4 * tileComp->cbW - 1].flags & jpxCoeffSignificant)) &&
+	      (y0+4 == cb->y1 ||
+	       !(coeff1[4 * tileComp->cbW].flags & jpxCoeffSignificant)) &&
+	      (x == cb->x1 - 1 || y0+4 == cb->y1 ||
+	       !(coeff1[4 * tileComp->cbW + 1].flags
+		 & jpxCoeffSignificant))) {
+	    if (cb->arithDecoder->decodeBit(jpxContextRunLength, cb->stats)) {
+	      y1 = cb->arithDecoder->decodeBit(jpxContextUniform, cb->stats);
+	      y1 = (y1 << 1) |
+		   cb->arithDecoder->decodeBit(jpxContextUniform, cb->stats);
+	      for (y2 = 0, coeff = coeff1;
+		   y2 < y1;
+		   ++y2, coeff += tileComp->cbW) {
+		++coeff->len;
+	      }
+	      coeff->flags |= jpxCoeffSignificant | jpxCoeffFirstMagRef;
+	      coeff->mag = (coeff->mag << 1) | 1;
+	      ++coeff->len;
+	      cx = signContext[2][2][0];
+	      xorBit = signContext[2][2][1];
+	      if (cb->arithDecoder->decodeBit(cx, cb->stats) ^ xorBit) {
+		coeff->flags |= jpxCoeffSign;
+	      }
+	      ++y1;
+	    } else {
+	      for (y1 = 0, coeff = coeff1;
+		   y1 < 4;
+		   ++y1, coeff += tileComp->cbW) {
+		++coeff->len;
+	      }
+	      y1 = 4;
+	    }
+	  }
+	  for (coeff = &coeff1[y1 << tileComp->codeBlockW];
+	       y1 < 4 && y0 + y1 < cb->y1;
+	       ++y1, coeff += tileComp->cbW) {
+	    if (!(coeff->flags & jpxCoeffTouched)) {
+	      horiz = vert = diag = 0;
+	      horizSign = vertSign = 2;
+	      if (x > cb->x0) {
+		if (coeff[-1].flags & jpxCoeffSignificant) {
+		  ++horiz;
+		  horizSign += (coeff[-1].flags & jpxCoeffSign) ? -1 : 1;
+		}
+		if (y0+y1 > cb->y0) {
+		  diag += (coeff[-(int)tileComp->cbW - 1].flags
+			   >> jpxCoeffSignificantB) & 1;
+		}
+		if (y0+y1 < cb->y1 - 1) {
+		  diag += (coeff[tileComp->cbW - 1].flags
+			   >> jpxCoeffSignificantB) & 1;
+		}
+	      }
+	      if (x < cb->x1 - 1) {
+		if (coeff[1].flags & jpxCoeffSignificant) {
+		  ++horiz;
+		  horizSign += (coeff[1].flags & jpxCoeffSign) ? -1 : 1;
+		}
+		if (y0+y1 > cb->y0) {
+		  diag += (coeff[-(int)tileComp->cbW + 1].flags
+			   >> jpxCoeffSignificantB) & 1;
+		}
+		if (y0+y1 < cb->y1 - 1) {
+		  diag += (coeff[tileComp->cbW + 1].flags
+			   >> jpxCoeffSignificantB) & 1;
+		}
+	      }
+	      if (y0+y1 > cb->y0) {
+		if (coeff[-(int)tileComp->cbW].flags & jpxCoeffSignificant) {
+		  ++vert;
+		  vertSign += (coeff[-(int)tileComp->cbW].flags & jpxCoeffSign)
+		              ? -1 : 1;
+		}
+	      }
+	      if (y0+y1 < cb->y1 - 1) {
+		if (coeff[tileComp->cbW].flags & jpxCoeffSignificant) {
+		  ++vert;
+		  vertSign += (coeff[tileComp->cbW].flags & jpxCoeffSign)
+		              ? -1 : 1;
+		}
+	      }
+	      cx = sigPropContext[horiz][vert][diag][res == 0 ? 1 : sb];
+	      if (cb->arithDecoder->decodeBit(cx, cb->stats)) {
+		coeff->flags |= jpxCoeffSignificant | jpxCoeffFirstMagRef;
+		coeff->mag = (coeff->mag << 1) | 1;
+		cx = signContext[horizSign][vertSign][0];
+		xorBit = signContext[horizSign][vertSign][1];
+		if (cb->arithDecoder->decodeBit(cx, cb->stats) ^ xorBit) {
+		  coeff->flags |= jpxCoeffSign;
+		}
+	      }
+	      ++coeff->len;
+	    } else {
+	      coeff->flags &= ~jpxCoeffTouched;
+	    }
+	  }
+	}
+      }
+      cb->nextPass = jpxPassSigProp;
+      break;
+    }
+  }
+
+  cb->arithDecoder->cleanup();
+  return gTrue;
+}
+
+// Inverse quantization, and wavelet transform (IDWT).  This also does
+// the initial shift to convert to fixed point format.
+void JPXStream::inverseTransform(JPXTileComp *tileComp) {
+  JPXResLevel *resLevel;
+  JPXPrecinct *precinct;
+  JPXSubband *subband;
+  JPXCodeBlock *cb;
+  JPXCoeff *coeff0, *coeff;
+  Guint qStyle, guard, eps, shift;
+  int shift2;
+  double mu;
+  int val;
+  int *dataPtr;
+  Guint nx0, ny0, nx1, ny1;
+  Guint r, cbX, cbY, x, y;
+
+  cover(68);
+
+  //----- (NL)LL subband (resolution level 0)
+
+  resLevel = &tileComp->resLevels[0];
+  precinct = &resLevel->precincts[0];
+  subband = &precinct->subbands[0];
+
+  // i-quant parameters
+  qStyle = tileComp->quantStyle & 0x1f;
+  guard = (tileComp->quantStyle >> 5) & 7;
+  if (qStyle == 0) {
+    cover(69);
+    eps = (tileComp->quantSteps[0] >> 3) & 0x1f;
+    shift = guard + eps - 1;
+    mu = 0; // make gcc happy
+  } else {
+    cover(70);
+    shift = guard - 1 + tileComp->prec;
+    mu = (double)(0x800 + (tileComp->quantSteps[0] & 0x7ff)) / 2048.0;
+  }
+  if (tileComp->transform == 0) {
+    cover(71);
+    shift += fracBits;
+  }
+
+  // copy (NL)LL into the upper-left corner of the data array, doing
+  // the fixed point adjustment and dequantization along the way
+  cb = subband->cbs;
+  for (cbY = 0; cbY < subband->nYCBs; ++cbY) {
+    for (cbX = 0; cbX < subband->nXCBs; ++cbX) {
+      for (y = cb->y0, coeff0 = cb->coeffs;
+	   y < cb->y1;
+	   ++y, coeff0 += tileComp->cbW) {
+	dataPtr = &tileComp->data[(y - subband->y0)
+				  * (tileComp->x1 - tileComp->x0)
+				  + (cb->x0 - subband->x0)];
+	for (x = cb->x0, coeff = coeff0; x < cb->x1; ++x, ++coeff) {
+	  val = (int)coeff->mag;
+	  if (val != 0) {
+	    shift2 = shift - (cb->nZeroBitPlanes + coeff->len);
+	    if (shift2 > 0) {
+	      cover(94);
+	      val = (val << shift2) + (1 << (shift2 - 1));
+	    } else {
+	      cover(95);
+	      val >>= -shift2;
+	    }
+	    if (qStyle == 0) {
+	      cover(96);
+	      if (tileComp->transform == 0) {
+		cover(97);
+		val &= -1 << fracBits;
+	      }
+	    } else {
+	      cover(98);
+	      val = (int)((double)val * mu);
+	    }
+	    if (coeff->flags & jpxCoeffSign) {
+	      cover(99);
+	      val = -val;
+	    }
+	  }
+	  *dataPtr++ = val;
+	}
+      }
+      ++cb;
+    }
+  }
+
+  //----- IDWT for each level
+
+  for (r = 1; r <= tileComp->nDecompLevels; ++r) {
+    resLevel = &tileComp->resLevels[r];
+
+    // (n)LL is already in the upper-left corner of the
+    // tile-component data array -- interleave with (n)HL/LH/HH
+    // and inverse transform to get (n-1)LL, which will be stored
+    // in the upper-left corner of the tile-component data array
+    if (r == tileComp->nDecompLevels) {
+      cover(72);
+      nx0 = tileComp->x0;
+      ny0 = tileComp->y0;
+      nx1 = tileComp->x1;
+      ny1 = tileComp->y1;
+    } else {
+      cover(73);
+      nx0 = tileComp->resLevels[r+1].x0;
+      ny0 = tileComp->resLevels[r+1].y0;
+      nx1 = tileComp->resLevels[r+1].x1;
+      ny1 = tileComp->resLevels[r+1].y1;
+    }
+    inverseTransformLevel(tileComp, r, resLevel, nx0, ny0, nx1, ny1);
+  }
+}
+
+// Do one level of the inverse transform:
+// - take (n)LL from the tile-component data array
+// - take (n)HL/LH/HH from <resLevel>
+// - leave the resulting (n-1)LL in the tile-component data array
+void JPXStream::inverseTransformLevel(JPXTileComp *tileComp,
+				      Guint r, JPXResLevel *resLevel,
+				      Guint nx0, Guint ny0,
+				      Guint nx1, Guint ny1) {
+  JPXPrecinct *precinct;
+  JPXSubband *subband;
+  JPXCodeBlock *cb;
+  JPXCoeff *coeff0, *coeff;
+  Guint qStyle, guard, eps, shift, t;
+  int shift2;
+  double mu;
+  int val;
+  int *dataPtr;
+  Guint xo, yo;
+  Guint x, y, sb, cbX, cbY;
+  int xx, yy;
+
+  //----- interleave
+
+  // spread out LL
+  for (yy = resLevel->y1 - 1; yy >= (int)resLevel->y0; --yy) {
+    for (xx = resLevel->x1 - 1; xx >= (int)resLevel->x0; --xx) {
+      tileComp->data[(2 * yy - ny0) * (tileComp->x1 - tileComp->x0)
+		     + (2 * xx - nx0)] =
+	  tileComp->data[(yy - resLevel->y0) * (tileComp->x1 - tileComp->x0)
+			 + (xx - resLevel->x0)];
+    }
+  }
+
+  // i-quant parameters
+  qStyle = tileComp->quantStyle & 0x1f;
+  guard = (tileComp->quantStyle >> 5) & 7;
+
+  // interleave HL/LH/HH
+  precinct = &resLevel->precincts[0];
+  for (sb = 0; sb < 3; ++sb) {
+
+    // i-quant parameters
+    if (qStyle == 0) {
+      cover(100);
+      eps = (tileComp->quantSteps[3*r - 2 + sb] >> 3) & 0x1f;
+      shift = guard + eps - 1;
+      mu = 0; // make gcc happy
+    } else {
+      cover(101);
+      shift = guard + tileComp->prec;
+      if (sb == 2) {
+	cover(102);
+	++shift;
+      }
+      t = tileComp->quantSteps[qStyle == 1 ? 0 : (3*r - 2 + sb)];
+      mu = (double)(0x800 + (t & 0x7ff)) / 2048.0;
+    }
+    if (tileComp->transform == 0) {
+      cover(103);
+      shift += fracBits;
+    }
+
+    // copy the subband coefficients into the data array, doing the
+    // fixed point adjustment and dequantization along the way
+    xo = (sb & 1) ? 0 : 1;
+    yo = (sb > 0) ? 1 : 0;
+    subband = &precinct->subbands[sb];
+    cb = subband->cbs;
+    for (cbY = 0; cbY < subband->nYCBs; ++cbY) {
+      for (cbX = 0; cbX < subband->nXCBs; ++cbX) {
+	for (y = cb->y0, coeff0 = cb->coeffs;
+	     y < cb->y1;
+	     ++y, coeff0 += tileComp->cbW) {
+	  dataPtr = &tileComp->data[(2 * y + yo - ny0)
+				    * (tileComp->x1 - tileComp->x0)
+				    + (2 * cb->x0 + xo - nx0)];
+	  for (x = cb->x0, coeff = coeff0; x < cb->x1; ++x, ++coeff) {
+	    val = (int)coeff->mag;
+	    if (val != 0) {
+	      shift2 = shift - (cb->nZeroBitPlanes + coeff->len);
+	      if (shift2 > 0) {
+		cover(74);
+		val = (val << shift2) + (1 << (shift2 - 1));
+	      } else {
+		cover(75);
+		val >>= -shift2;
+	      }
+	      if (qStyle == 0) {
+		cover(76);
+		if (tileComp->transform == 0) {
+		  val &= -1 << fracBits;
+		}
+	      } else {
+		cover(77);
+		val = (int)((double)val * mu);
+	      }
+	      if (coeff->flags & jpxCoeffSign) {
+		cover(78);
+		val = -val;
+	      }
+	    }
+	    *dataPtr = val;
+	    dataPtr += 2;
+	  }
+	}
+	++cb;
+      }
+    }
+  }
+
+  //----- horizontal (row) transforms
+  dataPtr = tileComp->data;
+  for (y = 0; y < ny1 - ny0; ++y) {
+    inverseTransform1D(tileComp, dataPtr, 1, nx0, nx1);
+    dataPtr += tileComp->x1 - tileComp->x0;
+  }
+
+  //----- vertical (column) transforms
+  dataPtr = tileComp->data;
+  for (x = 0; x < nx1 - nx0; ++x) {
+    inverseTransform1D(tileComp, dataPtr,
+		       tileComp->x1 - tileComp->x0, ny0, ny1);
+    ++dataPtr;
+  }
+}
+
+void JPXStream::inverseTransform1D(JPXTileComp *tileComp,
+				   int *data, Guint stride,
+				   Guint i0, Guint i1) {
+  int *buf;
+  Guint offset, end, i;
+
+  //----- special case for length = 1
+  if (i1 - i0 == 1) {
+    cover(79);
+    if (i0 & 1) {
+      cover(104);
+      *data >>= 1;
+    }
+
+  } else {
+    cover(80);
+
+    // choose an offset: this makes even buf[] indexes correspond to
+    // odd values of i, and vice versa
+    offset = 3 + (i0 & 1);
+    end = offset + i1 - i0;
+
+    //----- gather
+    buf = tileComp->buf;
+    for (i = 0; i < i1 - i0; ++i) {
+      buf[offset + i] = data[i * stride];
+    }
+
+    //----- extend right
+    buf[end] = buf[end - 2];
+    if (i1 - i0 == 2) {
+      cover(81);
+      buf[end+1] = buf[offset + 1];
+      buf[end+2] = buf[offset];
+      buf[end+3] = buf[offset + 1];
+    } else {
+      cover(82);
+      buf[end+1] = buf[end - 3];
+      if (i1 - i0 == 3) {
+	cover(105);
+	buf[end+2] = buf[offset + 1];
+	buf[end+3] = buf[offset + 2];
+      } else {
+	cover(106);
+	buf[end+2] = buf[end - 4];
+	if (i1 - i0 == 4) {
+	  cover(107);
+	  buf[end+3] = buf[offset + 1];
+	} else {
+	  cover(108);
+	  buf[end+3] = buf[end - 5];
+	}
+      }
+    }
+
+    //----- extend left
+    buf[offset - 1] = buf[offset + 1];
+    buf[offset - 2] = buf[offset + 2];
+    buf[offset - 3] = buf[offset + 3];
+    if (offset == 4) {
+      cover(83);
+      buf[0] = buf[offset + 4];
+    }
+
+    //----- 9-7 irreversible filter
+
+    if (tileComp->transform == 0) {
+      cover(84);
+      // step 1 (even)
+      for (i = 1; i <= end + 2; i += 2) {
+	buf[i] = (int)(idwtKappa * buf[i]);
+      }
+      // step 2 (odd)
+      for (i = 0; i <= end + 3; i += 2) {
+	buf[i] = (int)(idwtIKappa * buf[i]);
+      }
+      // step 3 (even)
+      for (i = 1; i <= end + 2; i += 2) {
+	buf[i] = (int)(buf[i] - idwtDelta * (buf[i-1] + buf[i+1]));
+      }
+      // step 4 (odd)
+      for (i = 2; i <= end + 1; i += 2) {
+	buf[i] = (int)(buf[i] - idwtGamma * (buf[i-1] + buf[i+1]));
+      }
+      // step 5 (even)
+      for (i = 3; i <= end; i += 2) {
+	buf[i] = (int)(buf[i] - idwtBeta * (buf[i-1] + buf[i+1]));
+      }
+      // step 6 (odd)
+      for (i = 4; i <= end - 1; i += 2) {
+	buf[i] = (int)(buf[i] - idwtAlpha * (buf[i-1] + buf[i+1]));
+      }
+
+    //----- 5-3 reversible filter
+
+    } else {
+      cover(85);
+      // step 1 (even)
+      for (i = 3; i <= end; i += 2) {
+	buf[i] -= (buf[i-1] + buf[i+1] + 2) >> 2;
+      }
+      // step 2 (odd)
+      for (i = 4; i < end; i += 2) {
+	buf[i] += (buf[i-1] + buf[i+1]) >> 1;
+      }
+    }
+
+    //----- scatter
+    for (i = 0; i < i1 - i0; ++i) {
+      data[i * stride] = buf[offset + i];
+    }
+  }
+}
+
+// Inverse multi-component transform and DC level shift.  This also
+// converts fixed point samples back to integers.
+GBool JPXStream::inverseMultiCompAndDC(JPXTile *tile) {
+  JPXTileComp *tileComp;
+  int coeff, d0, d1, d2, t, minVal, maxVal, zeroVal;
+  int *dataPtr;
+  Guint j, comp, x, y;
+
+  //----- inverse multi-component transform
+
+  if (tile->multiComp == 1) {
+    cover(86);
+    if (img.nComps < 3 ||
+	tile->tileComps[0].hSep != tile->tileComps[1].hSep ||
+	tile->tileComps[0].vSep != tile->tileComps[1].vSep ||
+	tile->tileComps[1].hSep != tile->tileComps[2].hSep ||
+	tile->tileComps[1].vSep != tile->tileComps[2].vSep) {
+      return gFalse;
+    }
+
+    // inverse irreversible multiple component transform
+    if (tile->tileComps[0].transform == 0) {
+      cover(87);
+      j = 0;
+      for (y = 0; y < tile->tileComps[0].y1 - tile->tileComps[0].y0; ++y) {
+	for (x = 0; x < tile->tileComps[0].x1 - tile->tileComps[0].x0; ++x) {
+	  d0 = tile->tileComps[0].data[j];
+	  d1 = tile->tileComps[1].data[j];
+	  d2 = tile->tileComps[2].data[j];
+	  tile->tileComps[0].data[j] = (int)(d0 + 1.402 * d2 + 0.5);
+	  tile->tileComps[1].data[j] =
+	      (int)(d0 - 0.34413 * d1 - 0.71414 * d2 + 0.5);
+	  tile->tileComps[2].data[j] = (int)(d0 + 1.772 * d1 + 0.5);
+	  ++j;
+	}
+      }
+
+    // inverse reversible multiple component transform
+    } else {
+      cover(88);
+      j = 0;
+      for (y = 0; y < tile->tileComps[0].y1 - tile->tileComps[0].y0; ++y) {
+	for (x = 0; x < tile->tileComps[0].x1 - tile->tileComps[0].x0; ++x) {
+	  d0 = tile->tileComps[0].data[j];
+	  d1 = tile->tileComps[1].data[j];
+	  d2 = tile->tileComps[2].data[j];
+	  tile->tileComps[1].data[j] = t = d0 - ((d2 + d1) >> 2);
+	  tile->tileComps[0].data[j] = d2 + t;
+	  tile->tileComps[2].data[j] = d1 + t;
+	  ++j;
+	}
+      }
+    }
+  }
+
+  //----- DC level shift
+  for (comp = 0; comp < img.nComps; ++comp) {
+    tileComp = &tile->tileComps[comp];
+
+    // signed: clip
+    if (tileComp->sgned) {
+      cover(89);
+      minVal = -(1 << (tileComp->prec - 1));
+      maxVal = (1 << (tileComp->prec - 1)) - 1;
+      dataPtr = tileComp->data;
+      for (y = 0; y < tileComp->y1 - tileComp->y0; ++y) {
+	for (x = 0; x < tileComp->x1 - tileComp->x0; ++x) {
+	  coeff = *dataPtr;
+	  if (tileComp->transform == 0) {
+	    cover(109);
+	    coeff >>= fracBits;
+	  }
+	  if (coeff < minVal) {
+	    cover(110);
+	    coeff = minVal;
+	  } else if (coeff > maxVal) {
+	    cover(111);
+	    coeff = maxVal;
+	  }
+	  *dataPtr++ = coeff;
+	}
+      }
+
+    // unsigned: inverse DC level shift and clip
+    } else {
+      cover(90);
+      maxVal = (1 << tileComp->prec) - 1;
+      zeroVal = 1 << (tileComp->prec - 1);
+      dataPtr = tileComp->data;
+      for (y = 0; y < tileComp->y1 - tileComp->y0; ++y) {
+	for (x = 0; x < tileComp->x1 - tileComp->x0; ++x) {
+	  coeff = *dataPtr;
+	  if (tileComp->transform == 0) {
+	    cover(112);
+	    coeff >>= fracBits;
+	  }
+	  coeff += zeroVal;
+	  if (coeff < 0) {
+	    cover(113);
+	    coeff = 0;
+	  } else if (coeff > maxVal) {
+	    cover(114);
+	    coeff = maxVal;
+	  }
+	  *dataPtr++ = coeff;
+	}
+      }
+    }
+  }
+
+  return gTrue;
+}
+
+GBool JPXStream::readBoxHdr(Guint *boxType, Guint *boxLen, Guint *dataLen) {
+  Guint len, lenH;
+
+  if (!readULong(&len) ||
+      !readULong(boxType)) {
+    return gFalse;
+  }
+  if (len == 1) {
+    if (!readULong(&lenH) || !readULong(&len)) {
+      return gFalse;
+    }
+    if (lenH) {
+      error(getPos(), "JPX stream contains a box larger than 2^32 bytes");
+      return gFalse;
+    }
+    *boxLen = len;
+    *dataLen = len - 16;
+  } else if (len == 0) {
+    *boxLen = 0;
+    *dataLen = 0;
+  } else {
+    *boxLen = len;
+    *dataLen = len - 8;
+  }
+  return gTrue;
+}
+
+int JPXStream::readMarkerHdr(int *segType, Guint *segLen) {
+  int c;
+
+  do {
+    do {
+      if ((c = str->getChar()) == EOF) {
+	return gFalse;
+      }
+    } while (c != 0xff);
+    do {
+      if ((c = str->getChar()) == EOF) {
+	return gFalse;
+      }
+    } while (c == 0xff);
+  } while (c == 0x00);
+  *segType = c;
+  if ((c >= 0x30 && c <= 0x3f) ||
+      c == 0x4f || c == 0x92 || c == 0x93 || c == 0xd9) {
+    *segLen = 0;
+    return gTrue;
+  }
+  return readUWord(segLen);
+}
+
+GBool JPXStream::readUByte(Guint *x) {
+  int c0;
+
+  if ((c0 = str->getChar()) == EOF) {
+    return gFalse;
+  }
+  *x = (Guint)c0;
+  return gTrue;
+}
+
+GBool JPXStream::readByte(int *x) {
+ int c0;
+
+  if ((c0 = str->getChar()) == EOF) {
+    return gFalse;
+  }
+  *x = c0;
+  if (c0 & 0x80) {
+    *x |= -1 - 0xff;
+  }
+  return gTrue;
+}
+
+GBool JPXStream::readUWord(Guint *x) {
+  int c0, c1;
+
+  if ((c0 = str->getChar()) == EOF ||
+      (c1 = str->getChar()) == EOF) {
+    return gFalse;
+  }
+  *x = (Guint)((c0 << 8) | c1);
+  return gTrue;
+}
+
+GBool JPXStream::readULong(Guint *x) {
+  int c0, c1, c2, c3;
+
+  if ((c0 = str->getChar()) == EOF ||
+      (c1 = str->getChar()) == EOF ||
+      (c2 = str->getChar()) == EOF ||
+      (c3 = str->getChar()) == EOF) {
+    return gFalse;
+  }
+  *x = (Guint)((c0 << 24) | (c1 << 16) | (c2 << 8) | c3);
+  return gTrue;
+}
+
+GBool JPXStream::readNBytes(int nBytes, GBool signd, int *x) {
+  int y, c, i;
+
+  y = 0;
+  for (i = 0; i < nBytes; ++i) {
+    if ((c = str->getChar()) == EOF) {
+      return gFalse;
+    }
+    y = (y << 8) + c;
+  }
+  if (signd) {
+    if (y & (1 << (8 * nBytes - 1))) {
+      y |= -1 << (8 * nBytes);
+    }
+  }
+  *x = y;
+  return gTrue;
+}
+
+GBool JPXStream::readBits(int nBits, Guint *x) {
+  int c;
+
+  while (bitBufLen < nBits) {
+    if (byteCount == 0 || (c = str->getChar()) == EOF) {
+      return gFalse;
+    }
+    --byteCount;
+    if (bitBufSkip) {
+      bitBuf = (bitBuf << 7) | (c & 0x7f);
+      bitBufLen += 7;
+    } else {
+      bitBuf = (bitBuf << 8) | (c & 0xff);
+      bitBufLen += 8;
+    }
+    bitBufSkip = c == 0xff;
+  }
+  *x = (bitBuf >> (bitBufLen - nBits)) & ((1 << nBits) - 1);
+  bitBufLen -= nBits;
+  return gTrue;
+}
+
+void JPXStream::startBitBuf(Guint byteCountA) {
+  bitBufLen = 0;
+  bitBufSkip = gFalse;
+  byteCount = byteCountA;
+}
+
+Guint JPXStream::finishBitBuf() {
+  if (bitBufSkip) {
+    str->getChar();
+    --byteCount;
+  }
+  return byteCount;
+}
diff --git a/xpdf/JPXStream.h b/xpdf/JPXStream.h
new file mode 100644
--- /dev/null
+++ b/xpdf/JPXStream.h
@@ -0,0 +1,351 @@
+//========================================================================
+//
+// JPXStream.h
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef JPXSTREAM_H
+#define JPXSTREAM_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "Object.h"
+#include "Stream.h"
+
+class JArithmeticDecoder;
+class JArithmeticDecoderStats;
+
+//------------------------------------------------------------------------
+
+enum JPXColorSpaceType {
+  jpxCSBiLevel = 0,
+  jpxCSYCbCr1 = 1,
+  jpxCSYCbCr2 = 3,
+  jpxCSYCBCr3 = 4,
+  jpxCSPhotoYCC = 9,
+  jpxCSCMY = 11,
+  jpxCSCMYK = 12,
+  jpxCSYCCK = 13,
+  jpxCSCIELab = 14,
+  jpxCSsRGB = 16,
+  jpxCSGrayscale = 17,
+  jpxCSBiLevel2 = 18,
+  jpxCSCIEJab = 19,
+  jpxCSCISesRGB = 20,
+  jpxCSROMMRGB = 21,
+  jpxCSsRGBYCbCr = 22,
+  jpxCSYPbPr1125 = 23,
+  jpxCSYPbPr1250 = 24
+};
+
+struct JPXColorSpecCIELab {
+  Guint rl, ol, ra, oa, rb, ob, il;
+};
+
+struct JPXColorSpecEnumerated {
+  JPXColorSpaceType type;	// color space type
+  union {
+    JPXColorSpecCIELab cieLab;
+  };
+};
+
+struct JPXColorSpec {
+  Guint meth;			// method
+  int prec;			// precedence
+  union {
+    JPXColorSpecEnumerated enumerated;
+  };
+};
+
+//------------------------------------------------------------------------
+
+struct JPXPalette {
+  Guint nEntries;		// number of entries in the palette
+  Guint nComps;			// number of components in each entry
+  Guint *bpc;			// bits per component, for each component
+  int *c;			// color data:
+				//   c[i*nComps+j] = entry i, component j
+};
+
+//------------------------------------------------------------------------
+
+struct JPXCompMap {
+  Guint nChannels;		// number of channels
+  Guint *comp;			// codestream components mapped to each channel
+  Guint *type;			// 0 for direct use, 1 for palette mapping
+  Guint *pComp;			// palette components to use
+};
+
+//------------------------------------------------------------------------
+
+struct JPXChannelDefn {
+  Guint nChannels;		// number of channels
+  Guint *idx;			// channel indexes
+  Guint *type;			// channel types
+  Guint *assoc;			// channel associations
+};
+
+//------------------------------------------------------------------------
+
+struct JPXTagTreeNode {
+  GBool finished;		// true if this node is finished
+  Guint val;			// current value
+};
+
+//------------------------------------------------------------------------
+
+struct JPXCoeff {
+  Gushort flags;		// flag bits
+  Gushort len;			// number of significant bits in mag
+  Guint mag;			// magnitude value
+};
+
+// coefficient flags
+#define jpxCoeffSignificantB  0
+#define jpxCoeffTouchedB      1
+#define jpxCoeffFirstMagRefB  2
+#define jpxCoeffSignB         7
+#define jpxCoeffSignificant   (1 << jpxCoeffSignificantB)
+#define jpxCoeffTouched       (1 << jpxCoeffTouchedB)
+#define jpxCoeffFirstMagRef   (1 << jpxCoeffFirstMagRefB)
+#define jpxCoeffSign          (1 << jpxCoeffSignB)
+
+//------------------------------------------------------------------------
+
+struct JPXCodeBlock {
+  //----- size
+  Guint x0, y0, x1, y1;		// bounds
+
+  //----- persistent state
+  GBool seen;			// true if this code-block has already
+				//   been seen
+  Guint lBlock;			// base number of bits used for pkt data length
+  Guint nextPass;		// next coding pass
+
+  //---- info from first packet
+  Guint nZeroBitPlanes;		// number of zero bit planes
+
+  //----- info for the current packet
+  Guint included;		// code-block inclusion in this packet:
+				//   0=not included, 1=included
+  Guint nCodingPasses;		// number of coding passes in this pkt
+  Guint dataLen;		// pkt data length
+
+  //----- coefficient data
+  JPXCoeff *coeffs;		// the coefficients
+  JArithmeticDecoder		// arithmetic decoder
+    *arithDecoder;
+  JArithmeticDecoderStats	// arithmetic decoder stats
+    *stats;
+};
+
+//------------------------------------------------------------------------
+
+struct JPXSubband {
+  //----- computed
+  Guint x0, y0, x1, y1;		// bounds
+  Guint nXCBs, nYCBs;		// number of code-blocks in the x and y
+				//   directions
+
+  //----- tag trees
+  Guint maxTTLevel;		// max tag tree level
+  JPXTagTreeNode *inclusion;	// inclusion tag tree for each subband
+  JPXTagTreeNode *zeroBitPlane;	// zero-bit plane tag tree for each
+				//   subband
+
+  //----- children
+  JPXCodeBlock *cbs;		// the code-blocks (len = nXCBs * nYCBs)
+};
+
+//------------------------------------------------------------------------
+
+struct JPXPrecinct {
+  //----- computed
+  Guint x0, y0, x1, y1;		// bounds of the precinct
+
+  //----- children
+  JPXSubband *subbands;		// the subbands
+};
+
+//------------------------------------------------------------------------
+
+struct JPXResLevel {
+  //----- from the COD and COC segments (main and tile)
+  Guint precinctWidth;		// log2(precinct width)
+  Guint precinctHeight;		// log2(precinct height)
+
+  //----- computed
+  Guint x0, y0, x1, y1;		// bounds of the tile-comp (for this res level)
+  Guint bx0[3], by0[3],		// subband bounds
+        bx1[3], by1[3];
+
+  //---- children
+  JPXPrecinct *precincts;	// the precincts
+};
+
+//------------------------------------------------------------------------
+
+struct JPXTileComp {
+  //----- from the SIZ segment
+  GBool sgned;			// 1 for signed, 0 for unsigned
+  Guint prec;			// precision, in bits
+  Guint hSep;			// horizontal separation of samples
+  Guint vSep;			// vertical separation of samples
+
+  //----- from the COD and COC segments (main and tile)
+  Guint style;			// coding style parameter (Scod / Scoc)
+  Guint nDecompLevels;		// number of decomposition levels
+  Guint codeBlockW;		// log2(code-block width)
+  Guint codeBlockH;		// log2(code-block height)
+  Guint codeBlockStyle;		// code-block style
+  Guint transform;		// wavelet transformation
+
+  //----- from the QCD and QCC segments (main and tile)
+  Guint quantStyle;		// quantization style
+  Guint *quantSteps;		// quantization step size for each subband
+  Guint nQuantSteps;		// number of entries in quantSteps
+
+  //----- computed
+  Guint x0, y0, x1, y1;		// bounds of the tile-comp, in ref coords
+  Guint cbW;			// code-block width
+  Guint cbH;			// code-block height
+
+  //----- image data
+  int *data;			// the decoded image data
+  int *buf;			// intermediate buffer for the inverse
+				//   transform
+
+  //----- children
+  JPXResLevel *resLevels;	// the resolution levels
+				//   (len = nDecompLevels + 1)
+};
+
+//------------------------------------------------------------------------
+
+struct JPXTile {
+  //----- from the COD segments (main and tile)
+  Guint progOrder;		// progression order
+  Guint nLayers;		// number of layers
+  Guint multiComp;		// multiple component transformation
+
+  //----- computed
+  Guint x0, y0, x1, y1;		// bounds of the tile, in ref coords
+  Guint maxNDecompLevels;	// max number of decomposition levels used
+				//   in any component in this tile
+
+  //----- progression order loop counters
+  Guint comp;			//   component
+  Guint res;			//   resolution level
+  Guint precinct;		//   precinct
+  Guint layer;			//   layer
+
+  //----- children
+  JPXTileComp *tileComps;	// the tile-components (len = JPXImage.nComps)
+};
+
+//------------------------------------------------------------------------
+
+struct JPXImage {
+  //----- from the SIZ segment
+  Guint xSize, ySize;		// size of reference grid
+  Guint xOffset, yOffset;	// image offset
+  Guint xTileSize, yTileSize;	// size of tiles
+  Guint xTileOffset,		// offset of first tile
+        yTileOffset;
+  Guint nComps;			// number of components
+
+  //----- computed
+  Guint nXTiles;		// number of tiles in x direction
+  Guint nYTiles;		// number of tiles in y direction
+
+  //----- children
+  JPXTile *tiles;		// the tiles (len = nXTiles * nYTiles)
+};
+
+//------------------------------------------------------------------------
+
+class JPXStream: public FilterStream {
+public:
+
+  JPXStream(Stream *strA);
+  virtual ~JPXStream();
+  virtual StreamKind getKind() { return strJPX; }
+  virtual void reset();
+  virtual void close();
+  virtual int getChar();
+  virtual int lookChar();
+  virtual GString *getPSFilter(int psLevel, char *indent);
+  virtual GBool isBinary(GBool last = gTrue);
+  virtual void getImageParams(int *bitsPerComponent,
+			      StreamColorSpaceMode *csMode);
+
+private:
+
+  void fillReadBuf();
+  void getImageParams2(int *bitsPerComponent, StreamColorSpaceMode *csMode);
+  GBool readBoxes();
+  GBool readColorSpecBox(Guint dataLen);
+  GBool readCodestream(Guint len);
+  GBool readTilePart();
+  GBool readTilePartData(Guint tileIdx,
+			 Guint tilePartLen, GBool tilePartToEOC);
+  GBool readCodeBlockData(JPXTileComp *tileComp,
+			  JPXResLevel *resLevel,
+			  JPXPrecinct *precinct,
+			  JPXSubband *subband,
+			  Guint res, Guint sb,
+			  JPXCodeBlock *cb);
+  void inverseTransform(JPXTileComp *tileComp);
+  void inverseTransformLevel(JPXTileComp *tileComp,
+			     Guint r, JPXResLevel *resLevel,
+			     Guint nx0, Guint ny0,
+			     Guint nx1, Guint ny1);
+  void inverseTransform1D(JPXTileComp *tileComp,
+			  int *data, Guint stride,
+			  Guint i0, Guint i1);
+  GBool inverseMultiCompAndDC(JPXTile *tile);
+  GBool readBoxHdr(Guint *boxType, Guint *boxLen, Guint *dataLen);
+  int readMarkerHdr(int *segType, Guint *segLen);
+  GBool readUByte(Guint *x);
+  GBool readByte(int *x);
+  GBool readUWord(Guint *x);
+  GBool readULong(Guint *x);
+  GBool readNBytes(int nBytes, GBool signd, int *x);
+  GBool readBits(int nBits, Guint *x);
+  void startBitBuf(Guint byteCountA);
+  Guint finishBitBuf();
+
+  Guint nComps;			// number of components
+  Guint *bpc;			// bits per component, for each component
+  Guint width, height;		// image size
+  GBool haveImgHdr;		// set if a JP2/JPX image header has been
+				//   found
+  JPXColorSpec cs;		// color specification
+  GBool haveCS;			// set if a color spec has been found
+  JPXPalette palette;		// the palette
+  GBool havePalette;		// set if a palette has been found
+  JPXCompMap compMap;		// the component mapping
+  GBool haveCompMap;		// set if a component mapping has been found
+  JPXChannelDefn channelDefn;	// channel definition
+  GBool haveChannelDefn;	// set if a channel defn has been found
+
+  JPXImage img;			// JPEG2000 decoder data
+  Guint bitBuf;			// buffer for bit reads
+  int bitBufLen;		// number of bits in bitBuf
+  GBool bitBufSkip;		// true if next bit should be skipped
+				//   (for bit stuffing)
+  Guint byteCount;		// number of available bytes left
+
+  Guint curX, curY, curComp;	// current position for lookChar/getChar
+  Guint readBuf;		// read buffer
+  Guint readBufLen;		// number of valid bits in readBuf
+};
+
+#endif
diff --git a/xpdf/Lexer.cc b/xpdf/Lexer.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Lexer.cc
@@ -0,0 +1,485 @@
+//========================================================================
+//
+// Lexer.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <ctype.h>
+#include "Lexer.h"
+#include "Error.h"
+
+//------------------------------------------------------------------------
+
+// A '1' in this array means the character is white space.  A '1' or
+// '2' means the character ends a name or command.
+static char specialChars[256] = {
+  1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,   // 0x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 1x
+  1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2,   // 2x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0,   // 3x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 4x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0,   // 5x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 6x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0,   // 7x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 8x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 9x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // ax
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // bx
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // cx
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // dx
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // ex
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0    // fx
+};
+
+//------------------------------------------------------------------------
+// Lexer
+//------------------------------------------------------------------------
+
+Lexer::Lexer(XRef *xref, Stream *str) {
+  Object obj;
+
+  curStr.initStream(str);
+  streams = new Array(xref);
+  streams->add(curStr.copy(&obj));
+  strPtr = 0;
+  freeArray = gTrue;
+  curStr.streamReset();
+}
+
+Lexer::Lexer(XRef *xref, Object *obj) {
+  Object obj2;
+
+  if (obj->isStream()) {
+    streams = new Array(xref);
+    freeArray = gTrue;
+    streams->add(obj->copy(&obj2));
+  } else {
+    streams = obj->getArray();
+    freeArray = gFalse;
+  }
+  strPtr = 0;
+  if (streams->getLength() > 0) {
+    streams->get(strPtr, &curStr);
+    curStr.streamReset();
+  }
+}
+
+Lexer::~Lexer() {
+  if (!curStr.isNone()) {
+    curStr.streamClose();
+    curStr.free();
+  }
+  if (freeArray) {
+    delete streams;
+  }
+}
+
+int Lexer::getChar() {
+  int c;
+
+  c = EOF;
+  while (!curStr.isNone() && (c = curStr.streamGetChar()) == EOF) {
+    curStr.streamClose();
+    curStr.free();
+    ++strPtr;
+    if (strPtr < streams->getLength()) {
+      streams->get(strPtr, &curStr);
+      curStr.streamReset();
+    }
+  }
+  return c;
+}
+
+int Lexer::lookChar() {
+  if (curStr.isNone()) {
+    return EOF;
+  }
+  return curStr.streamLookChar();
+}
+
+Object *Lexer::getObj(Object *obj) {
+  char *p;
+  int c, c2;
+  GBool comment, neg, done;
+  int numParen;
+  int xi;
+  double xf, scale;
+  GString *s;
+  int n, m;
+
+  // skip whitespace and comments
+  comment = gFalse;
+  while (1) {
+    if ((c = getChar()) == EOF) {
+      return obj->initEOF();
+    }
+    if (comment) {
+      if (c == '\r' || c == '\n')
+	comment = gFalse;
+    } else if (c == '%') {
+      comment = gTrue;
+    } else if (specialChars[c] != 1) {
+      break;
+    }
+  }
+
+  // start reading token
+  switch (c) {
+
+  // number
+  case '0': case '1': case '2': case '3': case '4':
+  case '5': case '6': case '7': case '8': case '9':
+  case '-': case '.':
+    neg = gFalse;
+    xi = 0;
+    if (c == '-') {
+      neg = gTrue;
+    } else if (c == '.') {
+      goto doReal;
+    } else {
+      xi = c - '0';
+    }
+    while (1) {
+      c = lookChar();
+      if (isdigit(c)) {
+	getChar();
+	xi = xi * 10 + (c - '0');
+      } else if (c == '.') {
+	getChar();
+	goto doReal;
+      } else {
+	break;
+      }
+    }
+    if (neg)
+      xi = -xi;
+    obj->initInt(xi);
+    break;
+  doReal:
+    xf = xi;
+    scale = 0.1;
+    while (1) {
+      c = lookChar();
+      if (c == '-') {
+	// ignore minus signs in the middle of numbers to match
+	// Adobe's behavior
+	error(getPos(), "Badly formatted number");
+	getChar();
+	continue;
+      }
+      if (!isdigit(c)) {
+	break;
+      }
+      getChar();
+      xf = xf + scale * (c - '0');
+      scale *= 0.1;
+    }
+    if (neg)
+      xf = -xf;
+    obj->initReal(xf);
+    break;
+
+  // string
+  case '(':
+    p = tokBuf;
+    n = 0;
+    numParen = 1;
+    done = gFalse;
+    s = NULL;
+    do {
+      c2 = EOF;
+      switch (c = getChar()) {
+
+      case EOF:
+#if 0
+      // This breaks some PDF files, e.g., ones from Photoshop.
+      case '\r':
+      case '\n':
+#endif
+	error(getPos(), "Unterminated string");
+	done = gTrue;
+	break;
+
+      case '(':
+	++numParen;
+	c2 = c;
+	break;
+
+      case ')':
+	if (--numParen == 0) {
+	  done = gTrue;
+	} else {
+	  c2 = c;
+	}
+	break;
+
+      case '\\':
+	switch (c = getChar()) {
+	case 'n':
+	  c2 = '\n';
+	  break;
+	case 'r':
+	  c2 = '\r';
+	  break;
+	case 't':
+	  c2 = '\t';
+	  break;
+	case 'b':
+	  c2 = '\b';
+	  break;
+	case 'f':
+	  c2 = '\f';
+	  break;
+	case '\\':
+	case '(':
+	case ')':
+	  c2 = c;
+	  break;
+	case '0': case '1': case '2': case '3':
+	case '4': case '5': case '6': case '7':
+	  c2 = c - '0';
+	  c = lookChar();
+	  if (c >= '0' && c <= '7') {
+	    getChar();
+	    c2 = (c2 << 3) + (c - '0');
+	    c = lookChar();
+	    if (c >= '0' && c <= '7') {
+	      getChar();
+	      c2 = (c2 << 3) + (c - '0');
+	    }
+	  }
+	  break;
+	case '\r':
+	  c = lookChar();
+	  if (c == '\n') {
+	    getChar();
+	  }
+	  break;
+	case '\n':
+	  break;
+	case EOF:
+	  error(getPos(), "Unterminated string");
+	  done = gTrue;
+	  break;
+	default:
+	  c2 = c;
+	  break;
+	}
+	break;
+
+      default:
+	c2 = c;
+	break;
+      }
+
+      if (c2 != EOF) {
+	if (n == tokBufSize) {
+	  if (!s)
+	    s = new GString(tokBuf, tokBufSize);
+	  else
+	    s->append(tokBuf, tokBufSize);
+	  p = tokBuf;
+	  n = 0;
+	}
+	*p++ = (char)c2;
+	++n;
+      }
+    } while (!done);
+    if (!s)
+      s = new GString(tokBuf, n);
+    else
+      s->append(tokBuf, n);
+    obj->initString(s);
+    break;
+
+  // name
+  case '/':
+    p = tokBuf;
+    n = 0;
+    while ((c = lookChar()) != EOF && !specialChars[c]) {
+      getChar();
+      if (c == '#') {
+	c2 = lookChar();
+	if (c2 >= '0' && c2 <= '9') {
+	  c = c2 - '0';
+	} else if (c2 >= 'A' && c2 <= 'F') {
+	  c = c2 - 'A' + 10;
+	} else if (c2 >= 'a' && c2 <= 'f') {
+	  c = c2 - 'a' + 10;
+	} else {
+	  goto notEscChar;
+	}
+	getChar();
+	c <<= 4;
+	c2 = getChar();
+	if (c2 >= '0' && c2 <= '9') {
+	  c += c2 - '0';
+	} else if (c2 >= 'A' && c2 <= 'F') {
+	  c += c2 - 'A' + 10;
+	} else if (c2 >= 'a' && c2 <= 'f') {
+	  c += c2 - 'a' + 10;
+	} else {
+	  error(getPos(), "Illegal digit in hex char in name");
+	}
+      }
+     notEscChar:
+      if (++n == tokBufSize) {
+	error(getPos(), "Name token too long");
+	break;
+      }
+      *p++ = c;
+    }
+    *p = '\0';
+    obj->initName(tokBuf);
+    break;
+
+  // array punctuation
+  case '[':
+  case ']':
+    tokBuf[0] = c;
+    tokBuf[1] = '\0';
+    obj->initCmd(tokBuf);
+    break;
+
+  // hex string or dict punctuation
+  case '<':
+    c = lookChar();
+
+    // dict punctuation
+    if (c == '<') {
+      getChar();
+      tokBuf[0] = tokBuf[1] = '<';
+      tokBuf[2] = '\0';
+      obj->initCmd(tokBuf);
+
+    // hex string
+    } else {
+      p = tokBuf;
+      m = n = 0;
+      c2 = 0;
+      s = NULL;
+      while (1) {
+	c = getChar();
+	if (c == '>') {
+	  break;
+	} else if (c == EOF) {
+	  error(getPos(), "Unterminated hex string");
+	  break;
+	} else if (specialChars[c] != 1) {
+	  c2 = c2 << 4;
+	  if (c >= '0' && c <= '9')
+	    c2 += c - '0';
+	  else if (c >= 'A' && c <= 'F')
+	    c2 += c - 'A' + 10;
+	  else if (c >= 'a' && c <= 'f')
+	    c2 += c - 'a' + 10;
+	  else
+	    error(getPos(), "Illegal character <%02x> in hex string", c);
+	  if (++m == 2) {
+	    if (n == tokBufSize) {
+	      if (!s)
+		s = new GString(tokBuf, tokBufSize);
+	      else
+		s->append(tokBuf, tokBufSize);
+	      p = tokBuf;
+	      n = 0;
+	    }
+	    *p++ = (char)c2;
+	    ++n;
+	    c2 = 0;
+	    m = 0;
+	  }
+	}
+      }
+      if (!s)
+	s = new GString(tokBuf, n);
+      else
+	s->append(tokBuf, n);
+      if (m == 1)
+	s->append((char)(c2 << 4));
+      obj->initString(s);
+    }
+    break;
+
+  // dict punctuation
+  case '>':
+    c = lookChar();
+    if (c == '>') {
+      getChar();
+      tokBuf[0] = tokBuf[1] = '>';
+      tokBuf[2] = '\0';
+      obj->initCmd(tokBuf);
+    } else {
+      error(getPos(), "Illegal character '>'");
+      obj->initError();
+    }
+    break;
+
+  // error
+  case ')':
+  case '{':
+  case '}':
+    error(getPos(), "Illegal character '%c'", c);
+    obj->initError();
+    break;
+
+  // command
+  default:
+    p = tokBuf;
+    *p++ = c;
+    n = 1;
+    while ((c = lookChar()) != EOF && !specialChars[c]) {
+      getChar();
+      if (++n == tokBufSize) {
+	error(getPos(), "Command token too long");
+	break;
+      }
+      *p++ = c;
+    }
+    *p = '\0';
+    if (tokBuf[0] == 't' && !strcmp(tokBuf, "true")) {
+      obj->initBool(gTrue);
+    } else if (tokBuf[0] == 'f' && !strcmp(tokBuf, "false")) {
+      obj->initBool(gFalse);
+    } else if (tokBuf[0] == 'n' && !strcmp(tokBuf, "null")) {
+      obj->initNull();
+    } else {
+      obj->initCmd(tokBuf);
+    }
+    break;
+  }
+
+  return obj;
+}
+
+void Lexer::skipToNextLine() {
+  int c;
+
+  while (1) {
+    c = getChar();
+    if (c == EOF || c == '\n') {
+      return;
+    }
+    if (c == '\r') {
+      if ((c = lookChar()) == '\n') {
+	getChar();
+      }
+      return;
+    }
+  }
+}
+
+GBool Lexer::isSpace(int c) {
+  return c >= 0 && c <= 0xff && specialChars[c] == 1;
+}
diff --git a/xpdf/Lexer.h b/xpdf/Lexer.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Lexer.h
@@ -0,0 +1,80 @@
+//========================================================================
+//
+// Lexer.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef LEXER_H
+#define LEXER_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "Object.h"
+#include "Stream.h"
+
+class XRef;
+
+#define tokBufSize 128		// size of token buffer
+
+//------------------------------------------------------------------------
+// Lexer
+//------------------------------------------------------------------------
+
+class Lexer {
+public:
+
+  // Construct a lexer for a single stream.  Deletes the stream when
+  // lexer is deleted.
+  Lexer(XRef *xref, Stream *str);
+
+  // Construct a lexer for a stream or array of streams (assumes obj
+  // is either a stream or array of streams).
+  Lexer(XRef *xref, Object *obj);
+
+  // Destructor.
+  ~Lexer();
+
+  // Get the next object from the input stream.
+  Object *getObj(Object *obj);
+
+  // Skip to the beginning of the next line in the input stream.
+  void skipToNextLine();
+
+  // Skip over one character.
+  void skipChar() { getChar(); }
+
+  // Get stream.
+  Stream *getStream()
+    { return curStr.isNone() ? (Stream *)NULL : curStr.getStream(); }
+
+  // Get current position in file.  This is only used for error
+  // messages, so it returns an int instead of a Guint.
+  int getPos()
+    { return curStr.isNone() ? -1 : (int)curStr.streamGetPos(); }
+
+  // Set position in file.
+  void setPos(Guint pos, int dir = 0)
+    { if (!curStr.isNone()) curStr.streamSetPos(pos, dir); }
+
+  // Returns true if <c> is a whitespace character.
+  static GBool isSpace(int c);
+
+private:
+
+  int getChar();
+  int lookChar();
+
+  Array *streams;		// array of input streams
+  int strPtr;			// index of current stream
+  Object curStr;		// current stream
+  GBool freeArray;		// should lexer free the streams array?
+  char tokBuf[tokBufSize];	// temporary token buffer
+};
+
+#endif
diff --git a/xpdf/Link.cc b/xpdf/Link.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Link.cc
@@ -0,0 +1,784 @@
+//========================================================================
+//
+// Link.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stddef.h>
+#include <string.h>
+#include "gmem.h"
+#include "GString.h"
+#include "Error.h"
+#include "Object.h"
+#include "Array.h"
+#include "Dict.h"
+#include "Link.h"
+
+//------------------------------------------------------------------------
+// LinkAction
+//------------------------------------------------------------------------
+
+LinkAction *LinkAction::parseDest(Object *obj) {
+  LinkAction *action;
+
+  action = new LinkGoTo(obj);
+  if (!action->isOk()) {
+    delete action;
+    return NULL;
+  }
+  return action;
+}
+
+LinkAction *LinkAction::parseAction(Object *obj, GString *baseURI) {
+  LinkAction *action;
+  Object obj2, obj3, obj4;
+
+  if (!obj->isDict()) {
+    error(-1, "Bad annotation action");
+    return NULL;
+  }
+
+  obj->dictLookup("S", &obj2);
+
+  // GoTo action
+  if (obj2.isName("GoTo")) {
+    obj->dictLookup("D", &obj3);
+    action = new LinkGoTo(&obj3);
+    obj3.free();
+
+  // GoToR action
+  } else if (obj2.isName("GoToR")) {
+    obj->dictLookup("F", &obj3);
+    obj->dictLookup("D", &obj4);
+    action = new LinkGoToR(&obj3, &obj4);
+    obj3.free();
+    obj4.free();
+
+  // Launch action
+  } else if (obj2.isName("Launch")) {
+    action = new LinkLaunch(obj);
+
+  // URI action
+  } else if (obj2.isName("URI")) {
+    obj->dictLookup("URI", &obj3);
+    action = new LinkURI(&obj3, baseURI);
+    obj3.free();
+
+  // Named action
+  } else if (obj2.isName("Named")) {
+    obj->dictLookup("N", &obj3);
+    action = new LinkNamed(&obj3);
+    obj3.free();
+
+  // Movie action
+  } else if (obj2.isName("Movie")) {
+    obj->dictLookupNF("Annot", &obj3);
+    obj->dictLookup("T", &obj4);
+    action = new LinkMovie(&obj3, &obj4);
+    obj3.free();
+    obj4.free();
+
+  // unknown action
+  } else if (obj2.isName()) {
+    action = new LinkUnknown(obj2.getName());
+
+  // action is missing or wrong type
+  } else {
+    error(-1, "Bad annotation action");
+    action = NULL;
+  }
+
+  obj2.free();
+
+  if (action && !action->isOk()) {
+    delete action;
+    return NULL;
+  }
+  return action;
+}
+
+GString *LinkAction::getFileSpecName(Object *fileSpecObj) {
+  GString *name;
+  Object obj1;
+
+  name = NULL;
+
+  // string
+  if (fileSpecObj->isString()) {
+    name = fileSpecObj->getString()->copy();
+
+  // dictionary
+  } else if (fileSpecObj->isDict()) {
+#ifdef WIN32
+    if (!fileSpecObj->dictLookup("DOS", &obj1)->isString()) {
+#else
+    if (!fileSpecObj->dictLookup("Unix", &obj1)->isString()) {
+#endif
+      obj1.free();
+      fileSpecObj->dictLookup("F", &obj1);
+    }
+    if (obj1.isString()) {
+      name = obj1.getString()->copy();
+    } else {
+      error(-1, "Illegal file spec in link");
+    }
+    obj1.free();
+
+  // error
+  } else {
+    error(-1, "Illegal file spec in link");
+  }
+
+  // system-dependent path manipulation
+  if (name) {
+#ifdef WIN32
+    int i, j;
+
+    // "//...."             --> "\...."
+    // "/x/...."            --> "x:\...."
+    // "/server/share/...." --> "\\server\share\...."
+    // convert escaped slashes to slashes and unescaped slashes to backslashes
+    i = 0;
+    if (name->getChar(0) == '/') {
+      if (name->getLength() >= 2 && name->getChar(1) == '/') {
+	name->del(0);
+	i = 0;
+      } else if (name->getLength() >= 2 &&
+		 ((name->getChar(1) >= 'a' && name->getChar(1) <= 'z') ||
+		  (name->getChar(1) >= 'A' && name->getChar(1) <= 'Z')) &&
+		 (name->getLength() == 2 || name->getChar(2) == '/')) {
+	name->setChar(0, name->getChar(1));
+	name->setChar(1, ':');
+	i = 2;
+      } else {
+	for (j = 2; j < name->getLength(); ++j) {
+	  if (name->getChar(j-1) != '\\' &&
+	      name->getChar(j) == '/') {
+	    break;
+	  }
+	}
+	if (j < name->getLength()) {
+	  name->setChar(0, '\\');
+	  name->insert(0, '\\');
+	  i = 2;
+	}
+      }
+    }
+    for (; i < name->getLength(); ++i) {
+      if (name->getChar(i) == '/') {
+	name->setChar(i, '\\');
+      } else if (name->getChar(i) == '\\' &&
+		 i+1 < name->getLength() &&
+		 name->getChar(i+1) == '/') {
+	name->del(i);
+      }
+    }
+#else
+    // no manipulation needed for Unix
+#endif
+  }
+
+  return name;
+}
+
+//------------------------------------------------------------------------
+// LinkDest
+//------------------------------------------------------------------------
+
+LinkDest::LinkDest(Array *a) {
+  Object obj1, obj2;
+
+  // initialize fields
+  left = bottom = right = top = zoom = 0;
+  ok = gFalse;
+
+  // get page
+  if (a->getLength() < 2) {
+    error(-1, "Annotation destination array is too short");
+    return;
+  }
+  a->getNF(0, &obj1);
+  if (obj1.isInt()) {
+    pageNum = obj1.getInt() + 1;
+    pageIsRef = gFalse;
+  } else if (obj1.isRef()) {
+    pageRef.num = obj1.getRefNum();
+    pageRef.gen = obj1.getRefGen();
+    pageIsRef = gTrue;
+  } else {
+    error(-1, "Bad annotation destination");
+    goto err2;
+  }
+  obj1.free();
+
+  // get destination type
+  a->get(1, &obj1);
+
+  // XYZ link
+  if (obj1.isName("XYZ")) {
+    kind = destXYZ;
+    if (a->getLength() < 3) {
+      changeLeft = gFalse;
+    } else {
+      a->get(2, &obj2);
+      if (obj2.isNull()) {
+	changeLeft = gFalse;
+      } else if (obj2.isNum()) {
+	changeLeft = gTrue;
+	left = obj2.getNum();
+      } else {
+	error(-1, "Bad annotation destination position");
+	goto err1;
+      }
+      obj2.free();
+    }
+    if (a->getLength() < 4) {
+      changeTop = gFalse;
+    } else {
+      a->get(3, &obj2);
+      if (obj2.isNull()) {
+	changeTop = gFalse;
+      } else if (obj2.isNum()) {
+	changeTop = gTrue;
+	top = obj2.getNum();
+      } else {
+	error(-1, "Bad annotation destination position");
+	goto err1;
+      }
+      obj2.free();
+    }
+    if (a->getLength() < 5) {
+      changeZoom = gFalse;
+    } else {
+      a->get(4, &obj2);
+      if (obj2.isNull()) {
+	changeZoom = gFalse;
+      } else if (obj2.isNum()) {
+	changeZoom = gTrue;
+	zoom = obj2.getNum();
+      } else {
+	error(-1, "Bad annotation destination position");
+	goto err1;
+      }
+      obj2.free();
+    }
+
+  // Fit link
+  } else if (obj1.isName("Fit")) {
+    if (a->getLength() < 2) {
+      error(-1, "Annotation destination array is too short");
+      goto err2;
+    }
+    kind = destFit;
+
+  // FitH link
+  } else if (obj1.isName("FitH")) {
+    if (a->getLength() < 3) {
+      error(-1, "Annotation destination array is too short");
+      goto err2;
+    }
+    kind = destFitH;
+    if (!a->get(2, &obj2)->isNum()) {
+      error(-1, "Bad annotation destination position");
+      kind = destFit;
+    }
+    top = obj2.getNum();
+    obj2.free();
+
+  // FitV link
+  } else if (obj1.isName("FitV")) {
+    if (a->getLength() < 3) {
+      error(-1, "Annotation destination array is too short");
+      goto err2;
+    }
+    kind = destFitV;
+    if (!a->get(2, &obj2)->isNum()) {
+      error(-1, "Bad annotation destination position");
+      kind = destFit;
+    }
+    left = obj2.getNum();
+    obj2.free();
+
+  // FitR link
+  } else if (obj1.isName("FitR")) {
+    if (a->getLength() < 6) {
+      error(-1, "Annotation destination array is too short");
+      goto err2;
+    }
+    kind = destFitR;
+    if (!a->get(2, &obj2)->isNum()) {
+      error(-1, "Bad annotation destination position");
+      kind = destFit;
+    }
+    left = obj2.getNum();
+    obj2.free();
+    if (!a->get(3, &obj2)->isNum()) {
+      error(-1, "Bad annotation destination position");
+      kind = destFit;
+    }
+    bottom = obj2.getNum();
+    obj2.free();
+    if (!a->get(4, &obj2)->isNum()) {
+      error(-1, "Bad annotation destination position");
+      kind = destFit;
+    }
+    right = obj2.getNum();
+    obj2.free();
+    if (!a->get(5, &obj2)->isNum()) {
+      error(-1, "Bad annotation destination position");
+      kind = destFit;
+    }
+    top = obj2.getNum();
+    obj2.free();
+
+  // FitB link
+  } else if (obj1.isName("FitB")) {
+    if (a->getLength() < 2) {
+      error(-1, "Annotation destination array is too short");
+      goto err2;
+    }
+    kind = destFitB;
+
+  // FitBH link
+  } else if (obj1.isName("FitBH")) {
+    if (a->getLength() < 3) {
+      error(-1, "Annotation destination array is too short");
+      goto err2;
+    }
+    kind = destFitBH;
+    if (!a->get(2, &obj2)->isNum()) {
+      error(-1, "Bad annotation destination position");
+      kind = destFit;
+    }
+    top = obj2.getNum();
+    obj2.free();
+
+  // FitBV link
+  } else if (obj1.isName("FitBV")) {
+    if (a->getLength() < 3) {
+      error(-1, "Annotation destination array is too short");
+      goto err2;
+    }
+    kind = destFitBV;
+    if (!a->get(2, &obj2)->isNum()) {
+      error(-1, "Bad annotation destination position");
+      kind = destFit;
+    }
+    left = obj2.getNum();
+    obj2.free();
+
+  // unknown link kind
+  } else {
+    error(-1, "Unknown annotation destination type");
+    goto err2;
+  }
+
+  obj1.free();
+  ok = gTrue;
+  return;
+
+ err1:
+  obj2.free();
+ err2:
+  obj1.free();
+}
+
+LinkDest::LinkDest(LinkDest *dest) {
+  kind = dest->kind;
+  pageIsRef = dest->pageIsRef;
+  if (pageIsRef)
+    pageRef = dest->pageRef;
+  else
+    pageNum = dest->pageNum;
+  left = dest->left;
+  bottom = dest->bottom;
+  right = dest->right;
+  top = dest->top;
+  zoom = dest->zoom;
+  changeLeft = dest->changeLeft;
+  changeTop = dest->changeTop;
+  changeZoom = dest->changeZoom;
+  ok = gTrue;
+}
+
+//------------------------------------------------------------------------
+// LinkGoTo
+//------------------------------------------------------------------------
+
+LinkGoTo::LinkGoTo(Object *destObj) {
+  dest = NULL;
+  namedDest = NULL;
+
+  // named destination
+  if (destObj->isName()) {
+    namedDest = new GString(destObj->getName());
+  } else if (destObj->isString()) {
+    namedDest = destObj->getString()->copy();
+
+  // destination dictionary
+  } else if (destObj->isArray()) {
+    dest = new LinkDest(destObj->getArray());
+    if (!dest->isOk()) {
+      delete dest;
+      dest = NULL;
+    }
+
+  // error
+  } else {
+    error(-1, "Illegal annotation destination");
+  }
+}
+
+LinkGoTo::~LinkGoTo() {
+  if (dest)
+    delete dest;
+  if (namedDest)
+    delete namedDest;
+}
+
+//------------------------------------------------------------------------
+// LinkGoToR
+//------------------------------------------------------------------------
+
+LinkGoToR::LinkGoToR(Object *fileSpecObj, Object *destObj) {
+  dest = NULL;
+  namedDest = NULL;
+
+  // get file name
+  fileName = getFileSpecName(fileSpecObj);
+
+  // named destination
+  if (destObj->isName()) {
+    namedDest = new GString(destObj->getName());
+  } else if (destObj->isString()) {
+    namedDest = destObj->getString()->copy();
+
+  // destination dictionary
+  } else if (destObj->isArray()) {
+    dest = new LinkDest(destObj->getArray());
+    if (!dest->isOk()) {
+      delete dest;
+      dest = NULL;
+    }
+
+  // error
+  } else {
+    error(-1, "Illegal annotation destination");
+  }
+}
+
+LinkGoToR::~LinkGoToR() {
+  if (fileName)
+    delete fileName;
+  if (dest)
+    delete dest;
+  if (namedDest)
+    delete namedDest;
+}
+
+
+//------------------------------------------------------------------------
+// LinkLaunch
+//------------------------------------------------------------------------
+
+LinkLaunch::LinkLaunch(Object *actionObj) {
+  Object obj1, obj2;
+
+  fileName = NULL;
+  params = NULL;
+
+  if (actionObj->isDict()) {
+    if (!actionObj->dictLookup("F", &obj1)->isNull()) {
+      fileName = getFileSpecName(&obj1);
+    } else {
+      obj1.free();
+#ifdef WIN32
+      if (actionObj->dictLookup("Win", &obj1)->isDict()) {
+	obj1.dictLookup("F", &obj2);
+	fileName = getFileSpecName(&obj2);
+	obj2.free();
+	if (obj1.dictLookup("P", &obj2)->isString()) {
+	  params = obj2.getString()->copy();
+	}
+	obj2.free();
+      } else {
+	error(-1, "Bad launch-type link action");
+      }
+#else
+      //~ This hasn't been defined by Adobe yet, so assume it looks
+      //~ just like the Win dictionary until they say otherwise.
+      if (actionObj->dictLookup("Unix", &obj1)->isDict()) {
+	obj1.dictLookup("F", &obj2);
+	fileName = getFileSpecName(&obj2);
+	obj2.free();
+	if (obj1.dictLookup("P", &obj2)->isString()) {
+	  params = obj2.getString()->copy();
+	}
+	obj2.free();
+      } else {
+	error(-1, "Bad launch-type link action");
+      }
+#endif
+    }
+    obj1.free();
+  }
+}
+
+LinkLaunch::~LinkLaunch() {
+  if (fileName)
+    delete fileName;
+  if (params)
+    delete params;
+}
+
+//------------------------------------------------------------------------
+// LinkURI
+//------------------------------------------------------------------------
+
+LinkURI::LinkURI(Object *uriObj, GString *baseURI) {
+  GString *uri2;
+  int n;
+  char c;
+
+  uri = NULL;
+  if (uriObj->isString()) {
+    uri2 = uriObj->getString()->copy();
+    if (baseURI && baseURI->getLength() > 0) {
+      n = strcspn(uri2->getCString(), "/:");
+      if (n == uri2->getLength() || uri2->getChar(n) == '/') {
+	uri = baseURI->copy();
+	c = uri->getChar(uri->getLength() - 1);
+	if (c == '/' || c == '?') {
+	  if (uri2->getChar(0) == '/') {
+	    uri2->del(0);
+	  }
+	} else {
+	  if (uri2->getChar(0) != '/') {
+	    uri->append('/');
+	  }
+	}
+	uri->append(uri2);
+	delete uri2;
+      } else {
+	uri = uri2;
+      }
+    } else {
+      uri = uri2;
+    }
+  } else {
+    error(-1, "Illegal URI-type link");
+  }
+}
+
+LinkURI::~LinkURI() {
+  if (uri)
+    delete uri;
+}
+
+//------------------------------------------------------------------------
+// LinkNamed
+//------------------------------------------------------------------------
+
+LinkNamed::LinkNamed(Object *nameObj) {
+  name = NULL;
+  if (nameObj->isName()) {
+    name = new GString(nameObj->getName());
+  }
+}
+
+LinkNamed::~LinkNamed() {
+  if (name) {
+    delete name;
+  }
+}
+
+//------------------------------------------------------------------------
+// LinkMovie
+//------------------------------------------------------------------------
+
+LinkMovie::LinkMovie(Object *annotObj, Object *titleObj) {
+  annotRef.num = -1;
+  title = NULL;
+  if (annotObj->isRef()) {
+    annotRef = annotObj->getRef();
+  } else if (titleObj->isString()) {
+    title = titleObj->getString()->copy();
+  } else {
+    error(-1, "Movie action is missing both the Annot and T keys");
+  }
+}
+
+LinkMovie::~LinkMovie() {
+  if (title) {
+    delete title;
+  }
+}
+
+//------------------------------------------------------------------------
+// LinkUnknown
+//------------------------------------------------------------------------
+
+LinkUnknown::LinkUnknown(char *actionA) {
+  action = new GString(actionA);
+}
+
+LinkUnknown::~LinkUnknown() {
+  delete action;
+}
+
+//------------------------------------------------------------------------
+// Link
+//------------------------------------------------------------------------
+
+Link::Link(Dict *dict, GString *baseURI) {
+  Object obj1, obj2;
+  double t;
+
+  action = NULL;
+  ok = gFalse;
+
+  // get rectangle
+  if (!dict->lookup("Rect", &obj1)->isArray()) {
+    error(-1, "Annotation rectangle is wrong type");
+    goto err2;
+  }
+  if (!obj1.arrayGet(0, &obj2)->isNum()) {
+    error(-1, "Bad annotation rectangle");
+    goto err1;
+  }
+  x1 = obj2.getNum();
+  obj2.free();
+  if (!obj1.arrayGet(1, &obj2)->isNum()) {
+    error(-1, "Bad annotation rectangle");
+    goto err1;
+  }
+  y1 = obj2.getNum();
+  obj2.free();
+  if (!obj1.arrayGet(2, &obj2)->isNum()) {
+    error(-1, "Bad annotation rectangle");
+    goto err1;
+  }
+  x2 = obj2.getNum();
+  obj2.free();
+  if (!obj1.arrayGet(3, &obj2)->isNum()) {
+    error(-1, "Bad annotation rectangle");
+    goto err1;
+  }
+  y2 = obj2.getNum();
+  obj2.free();
+  obj1.free();
+  if (x1 > x2) {
+    t = x1;
+    x1 = x2;
+    x2 = t;
+  }
+  if (y1 > y2) {
+    t = y1;
+    y1 = y2;
+    y2 = t;
+  }
+
+  // look for destination
+  if (!dict->lookup("Dest", &obj1)->isNull()) {
+    action = LinkAction::parseDest(&obj1);
+
+  // look for action
+  } else {
+    obj1.free();
+    if (dict->lookup("A", &obj1)->isDict()) {
+      action = LinkAction::parseAction(&obj1, baseURI);
+    }
+  }
+  obj1.free();
+
+  // check for bad action
+  if (action) {
+    ok = gTrue;
+  }
+
+  return;
+
+ err1:
+  obj2.free();
+ err2:
+  obj1.free();
+}
+
+Link::~Link() {
+  if (action) {
+    delete action;
+  }
+}
+
+//------------------------------------------------------------------------
+// Links
+//------------------------------------------------------------------------
+
+Links::Links(Object *annots, GString *baseURI) {
+  Link *link;
+  Object obj1, obj2;
+  int size;
+  int i;
+
+  links = NULL;
+  size = 0;
+  numLinks = 0;
+
+  if (annots->isArray()) {
+    for (i = 0; i < annots->arrayGetLength(); ++i) {
+      if (annots->arrayGet(i, &obj1)->isDict()) {
+	if (obj1.dictLookup("Subtype", &obj2)->isName("Link")) {
+	  link = new Link(obj1.getDict(), baseURI);
+	  if (link->isOk()) {
+	    if (numLinks >= size) {
+	      size += 16;
+	      links = (Link **)greallocn(links, size, sizeof(Link *));
+	    }
+	    links[numLinks++] = link;
+	  } else {
+	    delete link;
+	  }
+	}
+	obj2.free();
+      }
+      obj1.free();
+    }
+  }
+}
+
+Links::~Links() {
+  int i;
+
+  for (i = 0; i < numLinks; ++i)
+    delete links[i];
+  gfree(links);
+}
+
+LinkAction *Links::find(double x, double y) {
+  int i;
+
+  for (i = numLinks - 1; i >= 0; --i) {
+    if (links[i]->inRect(x, y)) {
+      return links[i]->getAction();
+    }
+  }
+  return NULL;
+}
+
+GBool Links::onLink(double x, double y) {
+  int i;
+
+  for (i = 0; i < numLinks; ++i) {
+    if (links[i]->inRect(x, y))
+      return gTrue;
+  }
+  return gFalse;
+}
diff --git a/xpdf/Link.h b/xpdf/Link.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Link.h
@@ -0,0 +1,369 @@
+//========================================================================
+//
+// Link.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef LINK_H
+#define LINK_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "Object.h"
+
+class GString;
+class Array;
+class Dict;
+
+//------------------------------------------------------------------------
+// LinkAction
+//------------------------------------------------------------------------
+
+enum LinkActionKind {
+  actionGoTo,			// go to destination
+  actionGoToR,			// go to destination in new file
+  actionLaunch,			// launch app (or open document)
+  actionURI,			// URI
+  actionNamed,			// named action
+  actionMovie,			// movie action
+  actionUnknown			// anything else
+};
+
+class LinkAction {
+public:
+
+  // Destructor.
+  virtual ~LinkAction() {}
+
+  // Was the LinkAction created successfully?
+  virtual GBool isOk() = 0;
+
+  // Check link action type.
+  virtual LinkActionKind getKind() = 0;
+
+  // Parse a destination (old-style action) name, string, or array.
+  static LinkAction *parseDest(Object *obj);
+
+  // Parse an action dictionary.
+  static LinkAction *parseAction(Object *obj, GString *baseURI = NULL);
+
+  // Extract a file name from a file specification (string or
+  // dictionary).
+  static GString *getFileSpecName(Object *fileSpecObj);
+};
+
+//------------------------------------------------------------------------
+// LinkDest
+//------------------------------------------------------------------------
+
+enum LinkDestKind {
+  destXYZ,
+  destFit,
+  destFitH,
+  destFitV,
+  destFitR,
+  destFitB,
+  destFitBH,
+  destFitBV
+};
+
+class LinkDest {
+public:
+
+  // Build a LinkDest from the array.
+  LinkDest(Array *a);
+
+  // Copy a LinkDest.
+  LinkDest *copy() { return new LinkDest(this); }
+
+  // Was the LinkDest created successfully?
+  GBool isOk() { return ok; }
+
+  // Accessors.
+  LinkDestKind getKind() { return kind; }
+  GBool isPageRef() { return pageIsRef; }
+  int getPageNum() { return pageNum; }
+  Ref getPageRef() { return pageRef; }
+  double getLeft() { return left; }
+  double getBottom() { return bottom; }
+  double getRight() { return right; }
+  double getTop() { return top; }
+  double getZoom() { return zoom; }
+  GBool getChangeLeft() { return changeLeft; }
+  GBool getChangeTop() { return changeTop; }
+  GBool getChangeZoom() { return changeZoom; }
+
+private:
+
+  LinkDestKind kind;		// destination type
+  GBool pageIsRef;		// is the page a reference or number?
+  union {
+    Ref pageRef;		// reference to page
+    int pageNum;		// one-relative page number
+  };
+  double left, bottom;		// position
+  double right, top;
+  double zoom;			// zoom factor
+  GBool changeLeft, changeTop;	// for destXYZ links, which position
+  GBool changeZoom;		//   components to change
+  GBool ok;			// set if created successfully
+
+  LinkDest(LinkDest *dest);
+};
+
+//------------------------------------------------------------------------
+// LinkGoTo
+//------------------------------------------------------------------------
+
+class LinkGoTo: public LinkAction {
+public:
+
+  // Build a LinkGoTo from a destination (dictionary, name, or string).
+  LinkGoTo(Object *destObj);
+
+  // Destructor.
+  virtual ~LinkGoTo();
+
+  // Was the LinkGoTo created successfully?
+  virtual GBool isOk() { return dest || namedDest; }
+
+  // Accessors.
+  virtual LinkActionKind getKind() { return actionGoTo; }
+  LinkDest *getDest() { return dest; }
+  GString *getNamedDest() { return namedDest; }
+
+private:
+
+  LinkDest *dest;		// regular destination (NULL for remote
+				//   link with bad destination)
+  GString *namedDest;		// named destination (only one of dest and
+				//   and namedDest may be non-NULL)
+};
+
+//------------------------------------------------------------------------
+// LinkGoToR
+//------------------------------------------------------------------------
+
+class LinkGoToR: public LinkAction {
+public:
+
+  // Build a LinkGoToR from a file spec (dictionary) and destination
+  // (dictionary, name, or string).
+  LinkGoToR(Object *fileSpecObj, Object *destObj);
+
+  // Destructor.
+  virtual ~LinkGoToR();
+
+  // Was the LinkGoToR created successfully?
+  virtual GBool isOk() { return fileName && (dest || namedDest); }
+
+  // Accessors.
+  virtual LinkActionKind getKind() { return actionGoToR; }
+  GString *getFileName() { return fileName; }
+  LinkDest *getDest() { return dest; }
+  GString *getNamedDest() { return namedDest; }
+
+private:
+
+  GString *fileName;		// file name
+  LinkDest *dest;		// regular destination (NULL for remote
+				//   link with bad destination)
+  GString *namedDest;		// named destination (only one of dest and
+				//   and namedDest may be non-NULL)
+};
+
+//------------------------------------------------------------------------
+// LinkLaunch
+//------------------------------------------------------------------------
+
+class LinkLaunch: public LinkAction {
+public:
+
+  // Build a LinkLaunch from an action dictionary.
+  LinkLaunch(Object *actionObj);
+
+  // Destructor.
+  virtual ~LinkLaunch();
+
+  // Was the LinkLaunch created successfully?
+  virtual GBool isOk() { return fileName != NULL; }
+
+  // Accessors.
+  virtual LinkActionKind getKind() { return actionLaunch; }
+  GString *getFileName() { return fileName; }
+  GString *getParams() { return params; }
+
+private:
+
+  GString *fileName;		// file name
+  GString *params;		// parameters
+};
+
+//------------------------------------------------------------------------
+// LinkURI
+//------------------------------------------------------------------------
+
+class LinkURI: public LinkAction {
+public:
+
+  // Build a LinkURI given the URI (string) and base URI.
+  LinkURI(Object *uriObj, GString *baseURI);
+
+  // Destructor.
+  virtual ~LinkURI();
+
+  // Was the LinkURI created successfully?
+  virtual GBool isOk() { return uri != NULL; }
+
+  // Accessors.
+  virtual LinkActionKind getKind() { return actionURI; }
+  GString *getURI() { return uri; }
+
+private:
+
+  GString *uri;			// the URI
+};
+
+//------------------------------------------------------------------------
+// LinkNamed
+//------------------------------------------------------------------------
+
+class LinkNamed: public LinkAction {
+public:
+
+  // Build a LinkNamed given the action name.
+  LinkNamed(Object *nameObj);
+
+  virtual ~LinkNamed();
+
+  virtual GBool isOk() { return name != NULL; }
+
+  virtual LinkActionKind getKind() { return actionNamed; }
+  GString *getName() { return name; }
+
+private:
+
+  GString *name;
+};
+
+//------------------------------------------------------------------------
+// LinkMovie
+//------------------------------------------------------------------------
+
+class LinkMovie: public LinkAction {
+public:
+
+  LinkMovie(Object *annotObj, Object *titleObj);
+
+  virtual ~LinkMovie();
+
+  virtual GBool isOk() { return annotRef.num >= 0 || title != NULL; }
+
+  virtual LinkActionKind getKind() { return actionMovie; }
+  GBool hasAnnotRef() { return annotRef.num >= 0; }
+  Ref *getAnnotRef() { return &annotRef; }
+  GString *getTitle() { return title; }
+
+private:
+
+  Ref annotRef;
+  GString *title;
+};
+
+//------------------------------------------------------------------------
+// LinkUnknown
+//------------------------------------------------------------------------
+
+class LinkUnknown: public LinkAction {
+public:
+
+  // Build a LinkUnknown with the specified action type.
+  LinkUnknown(char *actionA);
+
+  // Destructor.
+  virtual ~LinkUnknown();
+
+  // Was the LinkUnknown create successfully?
+  virtual GBool isOk() { return action != NULL; }
+
+  // Accessors.
+  virtual LinkActionKind getKind() { return actionUnknown; }
+  GString *getAction() { return action; }
+
+private:
+
+  GString *action;		// action subtype
+};
+
+//------------------------------------------------------------------------
+// Link
+//------------------------------------------------------------------------
+
+class Link {
+public:
+
+  // Construct a link, given its dictionary.
+  Link(Dict *dict, GString *baseURI);
+
+  // Destructor.
+  ~Link();
+
+  // Was the link created successfully?
+  GBool isOk() { return ok; }
+
+  // Check if point is inside the link rectangle.
+  GBool inRect(double x, double y)
+    { return x1 <= x && x <= x2 && y1 <= y && y <= y2; }
+
+  // Get action.
+  LinkAction *getAction() { return action; }
+
+  // Get the link rectangle.
+  void getRect(double *xa1, double *ya1, double *xa2, double *ya2)
+    { *xa1 = x1; *ya1 = y1; *xa2 = x2; *ya2 = y2; }
+
+private:
+
+  double x1, y1;		// lower left corner
+  double x2, y2;		// upper right corner
+  LinkAction *action;		// action
+  GBool ok;			// is link valid?
+};
+
+//------------------------------------------------------------------------
+// Links
+//------------------------------------------------------------------------
+
+class Links {
+public:
+
+  // Extract links from array of annotations.
+  Links(Object *annots, GString *baseURI);
+
+  // Destructor.
+  ~Links();
+
+  // Iterate through list of links.
+  int getNumLinks() { return numLinks; }
+  Link *getLink(int i) { return links[i]; }
+
+  // If point <x>,<y> is in a link, return the associated action;
+  // else return NULL.
+  LinkAction *find(double x, double y);
+
+  // Return true if <x>,<y> is in a link.
+  GBool onLink(double x, double y);
+
+private:
+
+  Link **links;
+  int numLinks;
+};
+
+#endif
diff --git a/xpdf/NameToCharCode.cc b/xpdf/NameToCharCode.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/NameToCharCode.cc
@@ -0,0 +1,116 @@
+//========================================================================
+//
+// NameToCharCode.cc
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <string.h>
+#include "gmem.h"
+#include "NameToCharCode.h"
+
+//------------------------------------------------------------------------
+
+struct NameToCharCodeEntry {
+  char *name;
+  CharCode c;
+};
+
+//------------------------------------------------------------------------
+
+NameToCharCode::NameToCharCode() {
+  int i;
+
+  size = 31;
+  len = 0;
+  tab = (NameToCharCodeEntry *)gmallocn(size, sizeof(NameToCharCodeEntry));
+  for (i = 0; i < size; ++i) {
+    tab[i].name = NULL;
+  }
+}
+
+NameToCharCode::~NameToCharCode() {
+  int i;
+
+  for (i = 0; i < size; ++i) {
+    if (tab[i].name) {
+      gfree(tab[i].name);
+    }
+  }
+  gfree(tab);
+}
+
+void NameToCharCode::add(char *name, CharCode c) {
+  NameToCharCodeEntry *oldTab;
+  int h, i, oldSize;
+
+  // expand the table if necessary
+  if (len >= size / 2) {
+    oldSize = size;
+    oldTab = tab;
+    size = 2*size + 1;
+    tab = (NameToCharCodeEntry *)gmallocn(size, sizeof(NameToCharCodeEntry));
+    for (h = 0; h < size; ++h) {
+      tab[h].name = NULL;
+    }
+    for (i = 0; i < oldSize; ++i) {
+      if (oldTab[i].name) {
+	h = hash(oldTab[i].name);
+	while (tab[h].name) {
+	  if (++h == size) {
+	    h = 0;
+	  }
+	}
+	tab[h] = oldTab[i];
+      }
+    }
+    gfree(oldTab);
+  }
+
+  // add the new name
+  h = hash(name);
+  while (tab[h].name && strcmp(tab[h].name, name)) {
+    if (++h == size) {
+      h = 0;
+    }
+  }
+  if (!tab[h].name) {
+    tab[h].name = copyString(name);
+  }
+  tab[h].c = c;
+
+  ++len;
+}
+
+CharCode NameToCharCode::lookup(char *name) {
+  int h;
+
+  h = hash(name);
+  while (tab[h].name) {
+    if (!strcmp(tab[h].name, name)) {
+      return tab[h].c;
+    }
+    if (++h == size) {
+      h = 0;
+    }
+  }
+  return 0;
+}
+
+int NameToCharCode::hash(char *name) {
+  char *p;
+  unsigned int h;
+
+  h = 0;
+  for (p = name; *p; ++p) {
+    h = 17 * h + (int)(*p & 0xff);
+  }
+  return (int)(h % size);
+}
diff --git a/xpdf/NameToCharCode.h b/xpdf/NameToCharCode.h
new file mode 100644
--- /dev/null
+++ b/xpdf/NameToCharCode.h
@@ -0,0 +1,42 @@
+//========================================================================
+//
+// NameToCharCode.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef NAMETOCHARCODE_H
+#define NAMETOCHARCODE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "CharTypes.h"
+
+struct NameToCharCodeEntry;
+
+//------------------------------------------------------------------------
+
+class NameToCharCode {
+public:
+
+  NameToCharCode();
+  ~NameToCharCode();
+
+  void add(char *name, CharCode c);
+  CharCode lookup(char *name);
+
+private:
+
+  int hash(char *name);
+
+  NameToCharCodeEntry *tab;
+  int size;
+  int len;
+};
+
+#endif
diff --git a/xpdf/NameToUnicodeTable.h b/xpdf/NameToUnicodeTable.h
new file mode 100644
--- /dev/null
+++ b/xpdf/NameToUnicodeTable.h
@@ -0,0 +1,1097 @@
+//========================================================================
+//
+// NameToUnicodeTable.h
+//
+// Copyright 2001-2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+static struct {
+  Unicode u;
+  char *name;
+} nameToUnicodeTab[] = {
+  {0x0021, "!"},
+  {0x0023, "#"},
+  {0x0024, "$"},
+  {0x0025, "%"},
+  {0x0026, "&"},
+  {0x0027, "'"},
+  {0x0028, "("},
+  {0x0029, ")"},
+  {0x002a, "*"},
+  {0x002b, "+"},
+  {0x002c, ","},
+  {0x002d, "-"},
+  {0x002e, "."},
+  {0x002f, "/"},
+  {0x0030, "0"},
+  {0x0031, "1"},
+  {0x0032, "2"},
+  {0x0033, "3"},
+  {0x0034, "4"},
+  {0x0035, "5"},
+  {0x0036, "6"},
+  {0x0037, "7"},
+  {0x0038, "8"},
+  {0x0039, "9"},
+  {0x003a, ":"},
+  {0x003b, ";"},
+  {0x003c, "<"},
+  {0x003d, "="},
+  {0x003e, ">"},
+  {0x003f, "?"},
+  {0x0040, "@"},
+  {0x0041, "A"},
+  {0x00c6, "AE"},
+  {0x01fc, "AEacute"},
+  {0xf7e6, "AEsmall"},
+  {0x00c1, "Aacute"},
+  {0xf7e1, "Aacutesmall"},
+  {0x0102, "Abreve"},
+  {0x00c2, "Acircumflex"},
+  {0xf7e2, "Acircumflexsmall"},
+  {0xf6c9, "Acute"},
+  {0xf7b4, "Acutesmall"},
+  {0x00c4, "Adieresis"},
+  {0xf7e4, "Adieresissmall"},
+  {0x00c0, "Agrave"},
+  {0xf7e0, "Agravesmall"},
+  {0x0391, "Alpha"},
+  {0x0386, "Alphatonos"},
+  {0x0100, "Amacron"},
+  {0x0104, "Aogonek"},
+  {0x00c5, "Aring"},
+  {0x01fa, "Aringacute"},
+  {0xf7e5, "Aringsmall"},
+  {0xf761, "Asmall"},
+  {0x00c3, "Atilde"},
+  {0xf7e3, "Atildesmall"},
+  {0x0042, "B"},
+  {0x0392, "Beta"},
+  {0xf6f4, "Brevesmall"},
+  {0xf762, "Bsmall"},
+  {0x0043, "C"},
+  {0x0106, "Cacute"},
+  {0xf6ca, "Caron"},
+  {0xf6f5, "Caronsmall"},
+  {0x010c, "Ccaron"},
+  {0x00c7, "Ccedilla"},
+  {0xf7e7, "Ccedillasmall"},
+  {0x0108, "Ccircumflex"},
+  {0x010a, "Cdotaccent"},
+  {0xf7b8, "Cedillasmall"},
+  {0x03a7, "Chi"},
+  {0xf6f6, "Circumflexsmall"},
+  {0xf763, "Csmall"},
+  {0x0044, "D"},
+  {0x010e, "Dcaron"},
+  {0x0110, "Dcroat"},
+  {0x2206, "Delta"},
+  {0xf6cb, "Dieresis"},
+  {0xf6cc, "DieresisAcute"},
+  {0xf6cd, "DieresisGrave"},
+  {0xf7a8, "Dieresissmall"},
+  {0xf6f7, "Dotaccentsmall"},
+  {0xf764, "Dsmall"},
+  {0x0045, "E"},
+  {0x00c9, "Eacute"},
+  {0xf7e9, "Eacutesmall"},
+  {0x0114, "Ebreve"},
+  {0x011a, "Ecaron"},
+  {0x00ca, "Ecircumflex"},
+  {0xf7ea, "Ecircumflexsmall"},
+  {0x00cb, "Edieresis"},
+  {0xf7eb, "Edieresissmall"},
+  {0x0116, "Edotaccent"},
+  {0x00c8, "Egrave"},
+  {0xf7e8, "Egravesmall"},
+  {0x0112, "Emacron"},
+  {0x014a, "Eng"},
+  {0x0118, "Eogonek"},
+  {0x0395, "Epsilon"},
+  {0x0388, "Epsilontonos"},
+  {0xf765, "Esmall"},
+  {0x0397, "Eta"},
+  {0x0389, "Etatonos"},
+  {0x00d0, "Eth"},
+  {0xf7f0, "Ethsmall"},
+  {0x20ac, "Euro"},
+  {0x0046, "F"},
+  {0xf766, "Fsmall"},
+  {0x0047, "G"},
+  {0x0393, "Gamma"},
+  {0x011e, "Gbreve"},
+  {0x01e6, "Gcaron"},
+  {0x011c, "Gcircumflex"},
+  {0x0122, "Gcommaaccent"},
+  {0x0120, "Gdotaccent"},
+  {0xf6ce, "Grave"},
+  {0xf760, "Gravesmall"},
+  {0xf767, "Gsmall"},
+  {0x0048, "H"},
+  {0x25cf, "H18533"},
+  {0x25aa, "H18543"},
+  {0x25ab, "H18551"},
+  {0x25a1, "H22073"},
+  {0x0126, "Hbar"},
+  {0x0124, "Hcircumflex"},
+  {0xf768, "Hsmall"},
+  {0xf6cf, "Hungarumlaut"},
+  {0xf6f8, "Hungarumlautsmall"},
+  {0x0049, "I"},
+  {0x0132, "IJ"},
+  {0x00cd, "Iacute"},
+  {0xf7ed, "Iacutesmall"},
+  {0x012c, "Ibreve"},
+  {0x00ce, "Icircumflex"},
+  {0xf7ee, "Icircumflexsmall"},
+  {0x00cf, "Idieresis"},
+  {0xf7ef, "Idieresissmall"},
+  {0x0130, "Idotaccent"},
+  {0x2111, "Ifraktur"},
+  {0x00cc, "Igrave"},
+  {0xf7ec, "Igravesmall"},
+  {0x012a, "Imacron"},
+  {0x012e, "Iogonek"},
+  {0x0399, "Iota"},
+  {0x03aa, "Iotadieresis"},
+  {0x038a, "Iotatonos"},
+  {0xf769, "Ismall"},
+  {0x0128, "Itilde"},
+  {0x004a, "J"},
+  {0x0134, "Jcircumflex"},
+  {0xf76a, "Jsmall"},
+  {0x004b, "K"},
+  {0x039a, "Kappa"},
+  {0x0136, "Kcommaaccent"},
+  {0xf76b, "Ksmall"},
+  {0x004c, "L"},
+  {0xf6bf, "LL"},
+  {0x0139, "Lacute"},
+  {0x039b, "Lambda"},
+  {0x013d, "Lcaron"},
+  {0x013b, "Lcommaaccent"},
+  {0x013f, "Ldot"},
+  {0x0141, "Lslash"},
+  {0xf6f9, "Lslashsmall"},
+  {0xf76c, "Lsmall"},
+  {0x004d, "M"},
+  {0xf6d0, "Macron"},
+  {0xf7af, "Macronsmall"},
+  {0xf76d, "Msmall"},
+  {0x039c, "Mu"},
+  {0x004e, "N"},
+  {0x0143, "Nacute"},
+  {0x0147, "Ncaron"},
+  {0x0145, "Ncommaaccent"},
+  {0xf76e, "Nsmall"},
+  {0x00d1, "Ntilde"},
+  {0xf7f1, "Ntildesmall"},
+  {0x039d, "Nu"},
+  {0x004f, "O"},
+  {0x0152, "OE"},
+  {0xf6fa, "OEsmall"},
+  {0x00d3, "Oacute"},
+  {0xf7f3, "Oacutesmall"},
+  {0x014e, "Obreve"},
+  {0x00d4, "Ocircumflex"},
+  {0xf7f4, "Ocircumflexsmall"},
+  {0x00d6, "Odieresis"},
+  {0xf7f6, "Odieresissmall"},
+  {0xf6fb, "Ogoneksmall"},
+  {0x00d2, "Ograve"},
+  {0xf7f2, "Ogravesmall"},
+  {0x01a0, "Ohorn"},
+  {0x0150, "Ohungarumlaut"},
+  {0x014c, "Omacron"},
+  {0x2126, "Omega"},
+  {0x038f, "Omegatonos"},
+  {0x039f, "Omicron"},
+  {0x038c, "Omicrontonos"},
+  {0x00d8, "Oslash"},
+  {0x01fe, "Oslashacute"},
+  {0xf7f8, "Oslashsmall"},
+  {0xf76f, "Osmall"},
+  {0x00d5, "Otilde"},
+  {0xf7f5, "Otildesmall"},
+  {0x0050, "P"},
+  {0x03a6, "Phi"},
+  {0x03a0, "Pi"},
+  {0x03a8, "Psi"},
+  {0xf770, "Psmall"},
+  {0x0051, "Q"},
+  {0xf771, "Qsmall"},
+  {0x0052, "R"},
+  {0x0154, "Racute"},
+  {0x0158, "Rcaron"},
+  {0x0156, "Rcommaaccent"},
+  {0x211c, "Rfraktur"},
+  {0x03a1, "Rho"},
+  {0xf6fc, "Ringsmall"},
+  {0xf772, "Rsmall"},
+  {0x0053, "S"},
+  {0x250c, "SF010000"},
+  {0x2514, "SF020000"},
+  {0x2510, "SF030000"},
+  {0x2518, "SF040000"},
+  {0x253c, "SF050000"},
+  {0x252c, "SF060000"},
+  {0x2534, "SF070000"},
+  {0x251c, "SF080000"},
+  {0x2524, "SF090000"},
+  {0x2500, "SF100000"},
+  {0x2502, "SF110000"},
+  {0x2561, "SF190000"},
+  {0x2562, "SF200000"},
+  {0x2556, "SF210000"},
+  {0x2555, "SF220000"},
+  {0x2563, "SF230000"},
+  {0x2551, "SF240000"},
+  {0x2557, "SF250000"},
+  {0x255d, "SF260000"},
+  {0x255c, "SF270000"},
+  {0x255b, "SF280000"},
+  {0x255e, "SF360000"},
+  {0x255f, "SF370000"},
+  {0x255a, "SF380000"},
+  {0x2554, "SF390000"},
+  {0x2569, "SF400000"},
+  {0x2566, "SF410000"},
+  {0x2560, "SF420000"},
+  {0x2550, "SF430000"},
+  {0x256c, "SF440000"},
+  {0x2567, "SF450000"},
+  {0x2568, "SF460000"},
+  {0x2564, "SF470000"},
+  {0x2565, "SF480000"},
+  {0x2559, "SF490000"},
+  {0x2558, "SF500000"},
+  {0x2552, "SF510000"},
+  {0x2553, "SF520000"},
+  {0x256b, "SF530000"},
+  {0x256a, "SF540000"},
+  {0x015a, "Sacute"},
+  {0x0160, "Scaron"},
+  {0xf6fd, "Scaronsmall"},
+  {0x015e, "Scedilla"},
+  {0x015c, "Scircumflex"},
+  {0x0218, "Scommaaccent"},
+  {0x03a3, "Sigma"},
+  {0xf773, "Ssmall"},
+  {0x0054, "T"},
+  {0x03a4, "Tau"},
+  {0x0166, "Tbar"},
+  {0x0164, "Tcaron"},
+  {0x0162, "Tcommaaccent"},
+  {0x0398, "Theta"},
+  {0x00de, "Thorn"},
+  {0xf7fe, "Thornsmall"},
+  {0xf6fe, "Tildesmall"},
+  {0xf774, "Tsmall"},
+  {0x0055, "U"},
+  {0x00da, "Uacute"},
+  {0xf7fa, "Uacutesmall"},
+  {0x016c, "Ubreve"},
+  {0x00db, "Ucircumflex"},
+  {0xf7fb, "Ucircumflexsmall"},
+  {0x00dc, "Udieresis"},
+  {0xf7fc, "Udieresissmall"},
+  {0x00d9, "Ugrave"},
+  {0xf7f9, "Ugravesmall"},
+  {0x01af, "Uhorn"},
+  {0x0170, "Uhungarumlaut"},
+  {0x016a, "Umacron"},
+  {0x0172, "Uogonek"},
+  {0x03a5, "Upsilon"},
+  {0x03d2, "Upsilon1"},
+  {0x03ab, "Upsilondieresis"},
+  {0x038e, "Upsilontonos"},
+  {0x016e, "Uring"},
+  {0xf775, "Usmall"},
+  {0x0168, "Utilde"},
+  {0x0056, "V"},
+  {0xf776, "Vsmall"},
+  {0x0057, "W"},
+  {0x1e82, "Wacute"},
+  {0x0174, "Wcircumflex"},
+  {0x1e84, "Wdieresis"},
+  {0x1e80, "Wgrave"},
+  {0xf777, "Wsmall"},
+  {0x0058, "X"},
+  {0x039e, "Xi"},
+  {0xf778, "Xsmall"},
+  {0x0059, "Y"},
+  {0x00dd, "Yacute"},
+  {0xf7fd, "Yacutesmall"},
+  {0x0176, "Ycircumflex"},
+  {0x0178, "Ydieresis"},
+  {0xf7ff, "Ydieresissmall"},
+  {0x1ef2, "Ygrave"},
+  {0xf779, "Ysmall"},
+  {0x005a, "Z"},
+  {0x0179, "Zacute"},
+  {0x017d, "Zcaron"},
+  {0xf6ff, "Zcaronsmall"},
+  {0x017b, "Zdotaccent"},
+  {0x0396, "Zeta"},
+  {0xf77a, "Zsmall"},
+  {0x0022, "\""},
+  {0x005c, "\\"},
+  {0x005d, "]"},
+  {0x005e, "^"},
+  {0x005f, "_"},
+  {0x0060, "`"},
+  {0x0061, "a"},
+  {0x00e1, "aacute"},
+  {0x0103, "abreve"},
+  {0x00e2, "acircumflex"},
+  {0x00b4, "acute"},
+  {0x0301, "acutecomb"},
+  {0x00e4, "adieresis"},
+  {0x00e6, "ae"},
+  {0x01fd, "aeacute"},
+  {0x2015, "afii00208"},
+  {0x0410, "afii10017"},
+  {0x0411, "afii10018"},
+  {0x0412, "afii10019"},
+  {0x0413, "afii10020"},
+  {0x0414, "afii10021"},
+  {0x0415, "afii10022"},
+  {0x0401, "afii10023"},
+  {0x0416, "afii10024"},
+  {0x0417, "afii10025"},
+  {0x0418, "afii10026"},
+  {0x0419, "afii10027"},
+  {0x041a, "afii10028"},
+  {0x041b, "afii10029"},
+  {0x041c, "afii10030"},
+  {0x041d, "afii10031"},
+  {0x041e, "afii10032"},
+  {0x041f, "afii10033"},
+  {0x0420, "afii10034"},
+  {0x0421, "afii10035"},
+  {0x0422, "afii10036"},
+  {0x0423, "afii10037"},
+  {0x0424, "afii10038"},
+  {0x0425, "afii10039"},
+  {0x0426, "afii10040"},
+  {0x0427, "afii10041"},
+  {0x0428, "afii10042"},
+  {0x0429, "afii10043"},
+  {0x042a, "afii10044"},
+  {0x042b, "afii10045"},
+  {0x042c, "afii10046"},
+  {0x042d, "afii10047"},
+  {0x042e, "afii10048"},
+  {0x042f, "afii10049"},
+  {0x0490, "afii10050"},
+  {0x0402, "afii10051"},
+  {0x0403, "afii10052"},
+  {0x0404, "afii10053"},
+  {0x0405, "afii10054"},
+  {0x0406, "afii10055"},
+  {0x0407, "afii10056"},
+  {0x0408, "afii10057"},
+  {0x0409, "afii10058"},
+  {0x040a, "afii10059"},
+  {0x040b, "afii10060"},
+  {0x040c, "afii10061"},
+  {0x040e, "afii10062"},
+  {0xf6c4, "afii10063"},
+  {0xf6c5, "afii10064"},
+  {0x0430, "afii10065"},
+  {0x0431, "afii10066"},
+  {0x0432, "afii10067"},
+  {0x0433, "afii10068"},
+  {0x0434, "afii10069"},
+  {0x0435, "afii10070"},
+  {0x0451, "afii10071"},
+  {0x0436, "afii10072"},
+  {0x0437, "afii10073"},
+  {0x0438, "afii10074"},
+  {0x0439, "afii10075"},
+  {0x043a, "afii10076"},
+  {0x043b, "afii10077"},
+  {0x043c, "afii10078"},
+  {0x043d, "afii10079"},
+  {0x043e, "afii10080"},
+  {0x043f, "afii10081"},
+  {0x0440, "afii10082"},
+  {0x0441, "afii10083"},
+  {0x0442, "afii10084"},
+  {0x0443, "afii10085"},
+  {0x0444, "afii10086"},
+  {0x0445, "afii10087"},
+  {0x0446, "afii10088"},
+  {0x0447, "afii10089"},
+  {0x0448, "afii10090"},
+  {0x0449, "afii10091"},
+  {0x044a, "afii10092"},
+  {0x044b, "afii10093"},
+  {0x044c, "afii10094"},
+  {0x044d, "afii10095"},
+  {0x044e, "afii10096"},
+  {0x044f, "afii10097"},
+  {0x0491, "afii10098"},
+  {0x0452, "afii10099"},
+  {0x0453, "afii10100"},
+  {0x0454, "afii10101"},
+  {0x0455, "afii10102"},
+  {0x0456, "afii10103"},
+  {0x0457, "afii10104"},
+  {0x0458, "afii10105"},
+  {0x0459, "afii10106"},
+  {0x045a, "afii10107"},
+  {0x045b, "afii10108"},
+  {0x045c, "afii10109"},
+  {0x045e, "afii10110"},
+  {0x040f, "afii10145"},
+  {0x0462, "afii10146"},
+  {0x0472, "afii10147"},
+  {0x0474, "afii10148"},
+  {0xf6c6, "afii10192"},
+  {0x045f, "afii10193"},
+  {0x0463, "afii10194"},
+  {0x0473, "afii10195"},
+  {0x0475, "afii10196"},
+  {0xf6c7, "afii10831"},
+  {0xf6c8, "afii10832"},
+  {0x04d9, "afii10846"},
+  {0x200e, "afii299"},
+  {0x200f, "afii300"},
+  {0x200d, "afii301"},
+  {0x066a, "afii57381"},
+  {0x060c, "afii57388"},
+  {0x0660, "afii57392"},
+  {0x0661, "afii57393"},
+  {0x0662, "afii57394"},
+  {0x0663, "afii57395"},
+  {0x0664, "afii57396"},
+  {0x0665, "afii57397"},
+  {0x0666, "afii57398"},
+  {0x0667, "afii57399"},
+  {0x0668, "afii57400"},
+  {0x0669, "afii57401"},
+  {0x061b, "afii57403"},
+  {0x061f, "afii57407"},
+  {0x0621, "afii57409"},
+  {0x0622, "afii57410"},
+  {0x0623, "afii57411"},
+  {0x0624, "afii57412"},
+  {0x0625, "afii57413"},
+  {0x0626, "afii57414"},
+  {0x0627, "afii57415"},
+  {0x0628, "afii57416"},
+  {0x0629, "afii57417"},
+  {0x062a, "afii57418"},
+  {0x062b, "afii57419"},
+  {0x062c, "afii57420"},
+  {0x062d, "afii57421"},
+  {0x062e, "afii57422"},
+  {0x062f, "afii57423"},
+  {0x0630, "afii57424"},
+  {0x0631, "afii57425"},
+  {0x0632, "afii57426"},
+  {0x0633, "afii57427"},
+  {0x0634, "afii57428"},
+  {0x0635, "afii57429"},
+  {0x0636, "afii57430"},
+  {0x0637, "afii57431"},
+  {0x0638, "afii57432"},
+  {0x0639, "afii57433"},
+  {0x063a, "afii57434"},
+  {0x0640, "afii57440"},
+  {0x0641, "afii57441"},
+  {0x0642, "afii57442"},
+  {0x0643, "afii57443"},
+  {0x0644, "afii57444"},
+  {0x0645, "afii57445"},
+  {0x0646, "afii57446"},
+  {0x0648, "afii57448"},
+  {0x0649, "afii57449"},
+  {0x064a, "afii57450"},
+  {0x064b, "afii57451"},
+  {0x064c, "afii57452"},
+  {0x064d, "afii57453"},
+  {0x064e, "afii57454"},
+  {0x064f, "afii57455"},
+  {0x0650, "afii57456"},
+  {0x0651, "afii57457"},
+  {0x0652, "afii57458"},
+  {0x0647, "afii57470"},
+  {0x06a4, "afii57505"},
+  {0x067e, "afii57506"},
+  {0x0686, "afii57507"},
+  {0x0698, "afii57508"},
+  {0x06af, "afii57509"},
+  {0x0679, "afii57511"},
+  {0x0688, "afii57512"},
+  {0x0691, "afii57513"},
+  {0x06ba, "afii57514"},
+  {0x06d2, "afii57519"},
+  {0x06d5, "afii57534"},
+  {0x20aa, "afii57636"},
+  {0x05be, "afii57645"},
+  {0x05c3, "afii57658"},
+  {0x05d0, "afii57664"},
+  {0x05d1, "afii57665"},
+  {0x05d2, "afii57666"},
+  {0x05d3, "afii57667"},
+  {0x05d4, "afii57668"},
+  {0x05d5, "afii57669"},
+  {0x05d6, "afii57670"},
+  {0x05d7, "afii57671"},
+  {0x05d8, "afii57672"},
+  {0x05d9, "afii57673"},
+  {0x05da, "afii57674"},
+  {0x05db, "afii57675"},
+  {0x05dc, "afii57676"},
+  {0x05dd, "afii57677"},
+  {0x05de, "afii57678"},
+  {0x05df, "afii57679"},
+  {0x05e0, "afii57680"},
+  {0x05e1, "afii57681"},
+  {0x05e2, "afii57682"},
+  {0x05e3, "afii57683"},
+  {0x05e4, "afii57684"},
+  {0x05e5, "afii57685"},
+  {0x05e6, "afii57686"},
+  {0x05e7, "afii57687"},
+  {0x05e8, "afii57688"},
+  {0x05e9, "afii57689"},
+  {0x05ea, "afii57690"},
+  {0xfb2a, "afii57694"},
+  {0xfb2b, "afii57695"},
+  {0xfb4b, "afii57700"},
+  {0xfb1f, "afii57705"},
+  {0x05f0, "afii57716"},
+  {0x05f1, "afii57717"},
+  {0x05f2, "afii57718"},
+  {0xfb35, "afii57723"},
+  {0x05b4, "afii57793"},
+  {0x05b5, "afii57794"},
+  {0x05b6, "afii57795"},
+  {0x05bb, "afii57796"},
+  {0x05b8, "afii57797"},
+  {0x05b7, "afii57798"},
+  {0x05b0, "afii57799"},
+  {0x05b2, "afii57800"},
+  {0x05b1, "afii57801"},
+  {0x05b3, "afii57802"},
+  {0x05c2, "afii57803"},
+  {0x05c1, "afii57804"},
+  {0x05b9, "afii57806"},
+  {0x05bc, "afii57807"},
+  {0x05bd, "afii57839"},
+  {0x05bf, "afii57841"},
+  {0x05c0, "afii57842"},
+  {0x02bc, "afii57929"},
+  {0x2105, "afii61248"},
+  {0x2113, "afii61289"},
+  {0x2116, "afii61352"},
+  {0x202c, "afii61573"},
+  {0x202d, "afii61574"},
+  {0x202e, "afii61575"},
+  {0x200c, "afii61664"},
+  {0x066d, "afii63167"},
+  {0x02bd, "afii64937"},
+  {0x00e0, "agrave"},
+  {0x2135, "aleph"},
+  {0x03b1, "alpha"},
+  {0x03ac, "alphatonos"},
+  {0x0101, "amacron"},
+  {0x0026, "ampersand"},
+  {0xf726, "ampersandsmall"},
+  {0x2220, "angle"},
+  {0x2329, "angleleft"},
+  {0x232a, "angleright"},
+  {0x0387, "anoteleia"},
+  {0x0105, "aogonek"},
+  {0x2248, "approxequal"},
+  {0x00e5, "aring"},
+  {0x01fb, "aringacute"},
+  {0x2194, "arrowboth"},
+  {0x21d4, "arrowdblboth"},
+  {0x21d3, "arrowdbldown"},
+  {0x21d0, "arrowdblleft"},
+  {0x21d2, "arrowdblright"},
+  {0x21d1, "arrowdblup"},
+  {0x2193, "arrowdown"},
+  {0xf8e7, "arrowhorizex"},
+  {0x2190, "arrowleft"},
+  {0x2192, "arrowright"},
+  {0x2191, "arrowup"},
+  {0x2195, "arrowupdn"},
+  {0x21a8, "arrowupdnbse"},
+  {0xf8e6, "arrowvertex"},
+  {0x005e, "asciicircum"},
+  {0x007e, "asciitilde"},
+  {0x002a, "asterisk"},
+  {0x2217, "asteriskmath"},
+  {0xf6e9, "asuperior"},
+  {0x0040, "at"},
+  {0x00e3, "atilde"},
+  {0x0062, "b"},
+  {0x005c, "backslash"},
+  {0x007c, "bar"},
+  {0x03b2, "beta"},
+  {0x2588, "block"},
+  {0xf8f4, "braceex"},
+  {0x007b, "braceleft"},
+  {0xf8f3, "braceleftbt"},
+  {0xf8f2, "braceleftmid"},
+  {0xf8f1, "bracelefttp"},
+  {0x007d, "braceright"},
+  {0xf8fe, "bracerightbt"},
+  {0xf8fd, "bracerightmid"},
+  {0xf8fc, "bracerighttp"},
+  {0x005b, "bracketleft"},
+  {0xf8f0, "bracketleftbt"},
+  {0xf8ef, "bracketleftex"},
+  {0xf8ee, "bracketlefttp"},
+  {0x005d, "bracketright"},
+  {0xf8fb, "bracketrightbt"},
+  {0xf8fa, "bracketrightex"},
+  {0xf8f9, "bracketrighttp"},
+  {0x02d8, "breve"},
+  {0x00a6, "brokenbar"},
+  {0xf6ea, "bsuperior"},
+  {0x2022, "bullet"},
+  {0x0063, "c"},
+  {0x0107, "cacute"},
+  {0x02c7, "caron"},
+  {0x21b5, "carriagereturn"},
+  {0x010d, "ccaron"},
+  {0x00e7, "ccedilla"},
+  {0x0109, "ccircumflex"},
+  {0x010b, "cdotaccent"},
+  {0x00b8, "cedilla"},
+  {0x00a2, "cent"},
+  {0xf6df, "centinferior"},
+  {0xf7a2, "centoldstyle"},
+  {0xf6e0, "centsuperior"},
+  {0x03c7, "chi"},
+  {0x25cb, "circle"},
+  {0x2297, "circlemultiply"},
+  {0x2295, "circleplus"},
+  {0x02c6, "circumflex"},
+  {0x2663, "club"},
+  {0x003a, "colon"},
+  {0x20a1, "colonmonetary"},
+  {0x002c, "comma"},
+  {0xf6c3, "commaaccent"},
+  {0xf6e1, "commainferior"},
+  {0xf6e2, "commasuperior"},
+  {0x2245, "congruent"},
+  {0x00a9, "copyright"},
+  {0x00a9, "copyrightsans"},
+  {0x00a9, "copyrightserif"},
+  {0x00a4, "currency"},
+  {0xf6d1, "cyrBreve"},
+  {0xf6d2, "cyrFlex"},
+  {0xf6d4, "cyrbreve"},
+  {0xf6d5, "cyrflex"},
+  {0x0064, "d"},
+  {0x2020, "dagger"},
+  {0x2021, "daggerdbl"},
+  {0xf6d3, "dblGrave"},
+  {0xf6d6, "dblgrave"},
+  {0x010f, "dcaron"},
+  {0x0111, "dcroat"},
+  {0x00b0, "degree"},
+  {0x03b4, "delta"},
+  {0x2666, "diamond"},
+  {0x00a8, "dieresis"},
+  {0xf6d7, "dieresisacute"},
+  {0xf6d8, "dieresisgrave"},
+  {0x0385, "dieresistonos"},
+  {0x00f7, "divide"},
+  {0x2593, "dkshade"},
+  {0x2584, "dnblock"},
+  {0x0024, "dollar"},
+  {0xf6e3, "dollarinferior"},
+  {0xf724, "dollaroldstyle"},
+  {0xf6e4, "dollarsuperior"},
+  {0x20ab, "dong"},
+  {0x02d9, "dotaccent"},
+  {0x0323, "dotbelowcomb"},
+  {0x0131, "dotlessi"},
+  {0xf6be, "dotlessj"},
+  {0x22c5, "dotmath"},
+  {0xf6eb, "dsuperior"},
+  {0x0065, "e"},
+  {0x00e9, "eacute"},
+  {0x0115, "ebreve"},
+  {0x011b, "ecaron"},
+  {0x00ea, "ecircumflex"},
+  {0x00eb, "edieresis"},
+  {0x0117, "edotaccent"},
+  {0x00e8, "egrave"},
+  {0x0038, "eight"},
+  {0x2088, "eightinferior"},
+  {0xf738, "eightoldstyle"},
+  {0x2078, "eightsuperior"},
+  {0x2208, "element"},
+  {0x2026, "ellipsis"},
+  {0x0113, "emacron"},
+  {0x2014, "emdash"},
+  {0x2205, "emptyset"},
+  {0x2013, "endash"},
+  {0x014b, "eng"},
+  {0x0119, "eogonek"},
+  {0x03b5, "epsilon"},
+  {0x03ad, "epsilontonos"},
+  {0x003d, "equal"},
+  {0x2261, "equivalence"},
+  {0x212e, "estimated"},
+  {0xf6ec, "esuperior"},
+  {0x03b7, "eta"},
+  {0x03ae, "etatonos"},
+  {0x00f0, "eth"},
+  {0x0021, "exclam"},
+  {0x203c, "exclamdbl"},
+  {0x00a1, "exclamdown"},
+  {0xf7a1, "exclamdownsmall"},
+  {0x0021, "exclamleft"},
+  {0xf721, "exclamsmall"},
+  {0x2203, "existential"},
+  {0x0066, "f"},
+  {0x2640, "female"},
+  {0xfb00, "ff"},
+  {0xfb03, "ffi"},
+  {0xfb04, "ffl"},
+  {0xfb01, "fi"},
+  {0x2012, "figuredash"},
+  {0x25a0, "filledbox"},
+  {0x25ac, "filledrect"},
+  {0x0035, "five"},
+  {0x215d, "fiveeighths"},
+  {0x2085, "fiveinferior"},
+  {0xf735, "fiveoldstyle"},
+  {0x2075, "fivesuperior"},
+  {0xfb02, "fl"},
+  {0x0192, "florin"},
+  {0x0034, "four"},
+  {0x2084, "fourinferior"},
+  {0xf734, "fouroldstyle"},
+  {0x2074, "foursuperior"},
+  {0x2044, "fraction"},
+  {0x20a3, "franc"},
+  {0x0067, "g"},
+  {0x03b3, "gamma"},
+  {0x011f, "gbreve"},
+  {0x01e7, "gcaron"},
+  {0x011d, "gcircumflex"},
+  {0x0123, "gcommaaccent"},
+  {0x0121, "gdotaccent"},
+  {0x00df, "germandbls"},
+  {0x2207, "gradient"},
+  {0x0060, "grave"},
+  {0x0300, "gravecomb"},
+  {0x003e, "greater"},
+  {0x2265, "greaterequal"},
+  {0x00ab, "guillemotleft"},
+  {0x00bb, "guillemotright"},
+  {0x2039, "guilsinglleft"},
+  {0x203a, "guilsinglright"},
+  {0x0068, "h"},
+  {0x0127, "hbar"},
+  {0x0125, "hcircumflex"},
+  {0x2665, "heart"},
+  {0x0309, "hookabovecomb"},
+  {0x2302, "house"},
+  {0x02dd, "hungarumlaut"},
+  {0x002d, "hyphen"},
+  {0xf6e5, "hypheninferior"},
+  {0xf6e6, "hyphensuperior"},
+  {0x0069, "i"},
+  {0x00ed, "iacute"},
+  {0x012d, "ibreve"},
+  {0x00ee, "icircumflex"},
+  {0x00ef, "idieresis"},
+  {0x00ec, "igrave"},
+  {0x0133, "ij"},
+  {0x012b, "imacron"},
+  {0x221e, "infinity"},
+  {0x222b, "integral"},
+  {0x2321, "integralbt"},
+  {0xf8f5, "integralex"},
+  {0x2320, "integraltp"},
+  {0x2229, "intersection"},
+  {0x25d8, "invbullet"},
+  {0x25d9, "invcircle"},
+  {0x263b, "invsmileface"},
+  {0x012f, "iogonek"},
+  {0x03b9, "iota"},
+  {0x03ca, "iotadieresis"},
+  {0x0390, "iotadieresistonos"},
+  {0x03af, "iotatonos"},
+  {0xf6ed, "isuperior"},
+  {0x0129, "itilde"},
+  {0x006a, "j"},
+  {0x0135, "jcircumflex"},
+  {0x006b, "k"},
+  {0x03ba, "kappa"},
+  {0x0137, "kcommaaccent"},
+  {0x0138, "kgreenlandic"},
+  {0x006c, "l"},
+  {0x013a, "lacute"},
+  {0x03bb, "lambda"},
+  {0x013e, "lcaron"},
+  {0x013c, "lcommaaccent"},
+  {0x0140, "ldot"},
+  {0x003c, "less"},
+  {0x2264, "lessequal"},
+  {0x258c, "lfblock"},
+  {0x20a4, "lira"},
+  {0xf6c0, "ll"},
+  {0x2227, "logicaland"},
+  {0x00ac, "logicalnot"},
+  {0x2228, "logicalor"},
+  {0x017f, "longs"},
+  {0x25ca, "lozenge"},
+  {0x0142, "lslash"},
+  {0xf6ee, "lsuperior"},
+  {0x2591, "ltshade"},
+  {0x006d, "m"},
+  {0x00af, "macron"},
+  {0x2642, "male"},
+  {0x2212, "minus"},
+  {0x2032, "minute"},
+  {0xf6ef, "msuperior"},
+  {0x00b5, "mu"},
+  {0x00d7, "multiply"},
+  {0x266a, "musicalnote"},
+  {0x266b, "musicalnotedbl"},
+  {0x006e, "n"},
+  {0x0144, "nacute"},
+  {0x0149, "napostrophe"},
+  {0x00a0, "nbspace"},
+  {0x0148, "ncaron"},
+  {0x0146, "ncommaaccent"},
+  {0x0039, "nine"},
+  {0x2089, "nineinferior"},
+  {0xf739, "nineoldstyle"},
+  {0x2079, "ninesuperior"},
+  {0x00a0, "nonbreakingspace"},
+  {0x2209, "notelement"},
+  {0x2260, "notequal"},
+  {0x2284, "notsubset"},
+  {0x207f, "nsuperior"},
+  {0x00f1, "ntilde"},
+  {0x03bd, "nu"},
+  {0x0023, "numbersign"},
+  {0x006f, "o"},
+  {0x00f3, "oacute"},
+  {0x014f, "obreve"},
+  {0x00f4, "ocircumflex"},
+  {0x00f6, "odieresis"},
+  {0x0153, "oe"},
+  {0x02db, "ogonek"},
+  {0x00f2, "ograve"},
+  {0x01a1, "ohorn"},
+  {0x0151, "ohungarumlaut"},
+  {0x014d, "omacron"},
+  {0x03c9, "omega"},
+  {0x03d6, "omega1"},
+  {0x03ce, "omegatonos"},
+  {0x03bf, "omicron"},
+  {0x03cc, "omicrontonos"},
+  {0x0031, "one"},
+  {0x2024, "onedotenleader"},
+  {0x215b, "oneeighth"},
+  {0xf6dc, "onefitted"},
+  {0x00bd, "onehalf"},
+  {0x2081, "oneinferior"},
+  {0xf731, "oneoldstyle"},
+  {0x00bc, "onequarter"},
+  {0x00b9, "onesuperior"},
+  {0x2153, "onethird"},
+  {0x25e6, "openbullet"},
+  {0x00aa, "ordfeminine"},
+  {0x00ba, "ordmasculine"},
+  {0x221f, "orthogonal"},
+  {0x00f8, "oslash"},
+  {0x01ff, "oslashacute"},
+  {0xf6f0, "osuperior"},
+  {0x00f5, "otilde"},
+  {0x0070, "p"},
+  {0x00b6, "paragraph"},
+  {0x0028, "parenleft"},
+  {0xf8ed, "parenleftbt"},
+  {0xf8ec, "parenleftex"},
+  {0x208d, "parenleftinferior"},
+  {0x207d, "parenleftsuperior"},
+  {0xf8eb, "parenlefttp"},
+  {0x0029, "parenright"},
+  {0xf8f8, "parenrightbt"},
+  {0xf8f7, "parenrightex"},
+  {0x208e, "parenrightinferior"},
+  {0x207e, "parenrightsuperior"},
+  {0xf8f6, "parenrighttp"},
+  {0x2202, "partialdiff"},
+  {0x0025, "percent"},
+  {0x002e, "period"},
+  {0x00b7, "periodcentered"},
+  {0xf6e7, "periodinferior"},
+  {0xf6e8, "periodsuperior"},
+  {0x22a5, "perpendicular"},
+  {0x2030, "perthousand"},
+  {0x20a7, "peseta"},
+  {0x03c6, "phi"},
+  {0x03d5, "phi1"},
+  {0x03c0, "pi"},
+  {0x002b, "plus"},
+  {0x00b1, "plusminus"},
+  {0x211e, "prescription"},
+  {0x220f, "product"},
+  {0x2282, "propersubset"},
+  {0x2283, "propersuperset"},
+  {0x221d, "proportional"},
+  {0x03c8, "psi"},
+  {0x0071, "q"},
+  {0x003f, "question"},
+  {0x00bf, "questiondown"},
+  {0xf7bf, "questiondownsmall"},
+  {0xf73f, "questionsmall"},
+  {0x0022, "quotedbl"},
+  {0x201e, "quotedblbase"},
+  {0x201c, "quotedblleft"},
+  {0x201d, "quotedblright"},
+  {0x2018, "quoteleft"},
+  {0x201b, "quotereversed"},
+  {0x2019, "quoteright"},
+  {0x201a, "quotesinglbase"},
+  {0x0027, "quotesingle"},
+  {0x0072, "r"},
+  {0x0155, "racute"},
+  {0x221a, "radical"},
+  {0xf8e5, "radicalex"},
+  {0x0159, "rcaron"},
+  {0x0157, "rcommaaccent"},
+  {0x2286, "reflexsubset"},
+  {0x2287, "reflexsuperset"},
+  {0x00ae, "registered"},
+  {0x00ae, "registersans"},
+  {0x00ae, "registerserif"},
+  {0x2310, "revlogicalnot"},
+  {0x03c1, "rho"},
+  {0x02da, "ring"},
+  {0xf6f1, "rsuperior"},
+  {0x2590, "rtblock"},
+  {0xf6dd, "rupiah"},
+  {0x0073, "s"},
+  {0x015b, "sacute"},
+  {0x0161, "scaron"},
+  {0x015f, "scedilla"},
+  {0x015d, "scircumflex"},
+  {0x0219, "scommaaccent"},
+  {0x2033, "second"},
+  {0x00a7, "section"},
+  {0x003b, "semicolon"},
+  {0x0037, "seven"},
+  {0x215e, "seveneighths"},
+  {0x2087, "seveninferior"},
+  {0xf737, "sevenoldstyle"},
+  {0x2077, "sevensuperior"},
+  {0x2592, "shade"},
+  {0x03c3, "sigma"},
+  {0x03c2, "sigma1"},
+  {0x223c, "similar"},
+  {0x0036, "six"},
+  {0x2086, "sixinferior"},
+  {0xf736, "sixoldstyle"},
+  {0x2076, "sixsuperior"},
+  {0x002f, "slash"},
+  {0x263a, "smileface"},
+  {0x0020, "space"},
+  {0x2660, "spade"},
+  {0xf6f2, "ssuperior"},
+  {0x00a3, "sterling"},
+  {0x220b, "suchthat"},
+  {0x2211, "summation"},
+  {0x263c, "sun"},
+  {0x0074, "t"},
+  {0x03c4, "tau"},
+  {0x0167, "tbar"},
+  {0x0165, "tcaron"},
+  {0x0163, "tcommaaccent"},
+  {0x2234, "therefore"},
+  {0x03b8, "theta"},
+  {0x03d1, "theta1"},
+  {0x00fe, "thorn"},
+  {0x0033, "three"},
+  {0x215c, "threeeighths"},
+  {0x2083, "threeinferior"},
+  {0xf733, "threeoldstyle"},
+  {0x00be, "threequarters"},
+  {0xf6de, "threequartersemdash"},
+  {0x00b3, "threesuperior"},
+  {0x02dc, "tilde"},
+  {0x0303, "tildecomb"},
+  {0x0384, "tonos"},
+  {0x2122, "trademark"},
+  {0x2122, "trademarksans"},
+  {0x2122, "trademarkserif"},
+  {0x25bc, "triagdn"},
+  {0x25c4, "triaglf"},
+  {0x25ba, "triagrt"},
+  {0x25b2, "triagup"},
+  {0xf6f3, "tsuperior"},
+  {0x0032, "two"},
+  {0x2025, "twodotenleader"},
+  {0x2082, "twoinferior"},
+  {0xf732, "twooldstyle"},
+  {0x00b2, "twosuperior"},
+  {0x2154, "twothirds"},
+  {0x0075, "u"},
+  {0x00fa, "uacute"},
+  {0x016d, "ubreve"},
+  {0x00fb, "ucircumflex"},
+  {0x00fc, "udieresis"},
+  {0x00f9, "ugrave"},
+  {0x01b0, "uhorn"},
+  {0x0171, "uhungarumlaut"},
+  {0x016b, "umacron"},
+  {0x005f, "underscore"},
+  {0x2017, "underscoredbl"},
+  {0x222a, "union"},
+  {0x2200, "universal"},
+  {0x0173, "uogonek"},
+  {0x2580, "upblock"},
+  {0x03c5, "upsilon"},
+  {0x03cb, "upsilondieresis"},
+  {0x03b0, "upsilondieresistonos"},
+  {0x03cd, "upsilontonos"},
+  {0x016f, "uring"},
+  {0x0169, "utilde"},
+  {0x0076, "v"},
+  {0x0077, "w"},
+  {0x1e83, "wacute"},
+  {0x0175, "wcircumflex"},
+  {0x1e85, "wdieresis"},
+  {0x2118, "weierstrass"},
+  {0x1e81, "wgrave"},
+  {0x0078, "x"},
+  {0x03be, "xi"},
+  {0x0079, "y"},
+  {0x00fd, "yacute"},
+  {0x0177, "ycircumflex"},
+  {0x00ff, "ydieresis"},
+  {0x00a5, "yen"},
+  {0x1ef3, "ygrave"},
+  {0x007a, "z"},
+  {0x017a, "zacute"},
+  {0x017e, "zcaron"},
+  {0x017c, "zdotaccent"},
+  {0x0030, "zero"},
+  {0x2080, "zeroinferior"},
+  {0xf730, "zerooldstyle"},
+  {0x2070, "zerosuperior"},
+  {0x03b6, "zeta"},
+  {0x007b, "{"},
+  {0x007c, "|"},
+  {0x007d, "}"},
+  {0x007e, "~"},
+  { 0, NULL }
+};
diff --git a/xpdf/Object.cc b/xpdf/Object.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Object.cc
@@ -0,0 +1,231 @@
+//========================================================================
+//
+// Object.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stddef.h>
+#include "Object.h"
+#include "Array.h"
+#include "Dict.h"
+#include "Error.h"
+#include "Stream.h"
+#include "XRef.h"
+
+//------------------------------------------------------------------------
+// Object
+//------------------------------------------------------------------------
+
+char *objTypeNames[numObjTypes] = {
+  "boolean",
+  "integer",
+  "real",
+  "string",
+  "name",
+  "null",
+  "array",
+  "dictionary",
+  "stream",
+  "ref",
+  "cmd",
+  "error",
+  "eof",
+  "none"
+};
+
+#ifdef DEBUG_MEM
+int Object::numAlloc[numObjTypes] =
+  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+#endif
+
+Object *Object::initArray(XRef *xref) {
+  initObj(objArray);
+  array = new Array(xref);
+  return this;
+}
+
+Object *Object::initDict(XRef *xref) {
+  initObj(objDict);
+  dict = new Dict(xref);
+  return this;
+}
+
+Object *Object::initDict(Dict *dictA) {
+  initObj(objDict);
+  dict = dictA;
+  dict->incRef();
+  return this;
+}
+
+Object *Object::initStream(Stream *streamA) {
+  initObj(objStream);
+  stream = streamA;
+  return this;
+}
+
+Object *Object::copy(Object *obj) {
+  *obj = *this;
+  switch (type) {
+  case objString:
+    obj->string = string->copy();
+    break;
+  case objName:
+    obj->name = copyString(name);
+    break;
+  case objArray:
+    array->incRef();
+    break;
+  case objDict:
+    dict->incRef();
+    break;
+  case objStream:
+    stream->incRef();
+    break;
+  case objCmd:
+    obj->cmd = copyString(cmd);
+    break;
+  default:
+    break;
+  }
+#ifdef DEBUG_MEM
+  ++numAlloc[type];
+#endif
+  return obj;
+}
+
+Object *Object::fetch(XRef *xref, Object *obj) {
+  return (type == objRef && xref) ?
+         xref->fetch(ref.num, ref.gen, obj) : copy(obj);
+}
+
+void Object::free() {
+  switch (type) {
+  case objString:
+    delete string;
+    break;
+  case objName:
+    gfree(name);
+    break;
+  case objArray:
+    if (!array->decRef()) {
+      delete array;
+    }
+    break;
+  case objDict:
+    if (!dict->decRef()) {
+      delete dict;
+    }
+    break;
+  case objStream:
+    if (!stream->decRef()) {
+      delete stream;
+    }
+    break;
+  case objCmd:
+    gfree(cmd);
+    break;
+  default:
+    break;
+  }
+#ifdef DEBUG_MEM
+  --numAlloc[type];
+#endif
+  type = objNone;
+}
+
+char *Object::getTypeName() {
+  return objTypeNames[type];
+}
+
+void Object::print(FILE *f) {
+  Object obj;
+  int i;
+
+  switch (type) {
+  case objBool:
+    fprintf(f, "%s", booln ? "true" : "false");
+    break;
+  case objInt:
+    fprintf(f, "%d", intg);
+    break;
+  case objReal:
+    fprintf(f, "%g", real);
+    break;
+  case objString:
+    fprintf(f, "(");
+    fwrite(string->getCString(), 1, string->getLength(), f);
+    fprintf(f, ")");
+    break;
+  case objName:
+    fprintf(f, "/%s", name);
+    break;
+  case objNull:
+    fprintf(f, "null");
+    break;
+  case objArray:
+    fprintf(f, "[");
+    for (i = 0; i < arrayGetLength(); ++i) {
+      if (i > 0)
+	fprintf(f, " ");
+      arrayGetNF(i, &obj);
+      obj.print(f);
+      obj.free();
+    }
+    fprintf(f, "]");
+    break;
+  case objDict:
+    fprintf(f, "<<");
+    for (i = 0; i < dictGetLength(); ++i) {
+      fprintf(f, " /%s ", dictGetKey(i));
+      dictGetValNF(i, &obj);
+      obj.print(f);
+      obj.free();
+    }
+    fprintf(f, " >>");
+    break;
+  case objStream:
+    fprintf(f, "<stream>");
+    break;
+  case objRef:
+    fprintf(f, "%d %d R", ref.num, ref.gen);
+    break;
+  case objCmd:
+    fprintf(f, "%s", cmd);
+    break;
+  case objError:
+    fprintf(f, "<error>");
+    break;
+  case objEOF:
+    fprintf(f, "<EOF>");
+    break;
+  case objNone:
+    fprintf(f, "<none>");
+    break;
+  }
+}
+
+void Object::memCheck(FILE *f) {
+#ifdef DEBUG_MEM
+  int i;
+  int t;
+
+  t = 0;
+  for (i = 0; i < numObjTypes; ++i)
+    t += numAlloc[i];
+  if (t > 0) {
+    fprintf(f, "Allocated objects:\n");
+    for (i = 0; i < numObjTypes; ++i) {
+      if (numAlloc[i] > 0)
+	fprintf(f, "  %-20s: %6d\n", objTypeNames[i], numAlloc[i]);
+    }
+  }
+#endif
+}
diff --git a/xpdf/Object.h b/xpdf/Object.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Object.h
@@ -0,0 +1,303 @@
+//========================================================================
+//
+// Object.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef OBJECT_H
+#define OBJECT_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "gtypes.h"
+#include "gmem.h"
+#include "GString.h"
+
+class XRef;
+class Array;
+class Dict;
+class Stream;
+
+//------------------------------------------------------------------------
+// Ref
+//------------------------------------------------------------------------
+
+struct Ref {
+  int num;			// object number
+  int gen;			// generation number
+};
+
+//------------------------------------------------------------------------
+// object types
+//------------------------------------------------------------------------
+
+enum ObjType {
+  // simple objects
+  objBool,			// boolean
+  objInt,			// integer
+  objReal,			// real
+  objString,			// string
+  objName,			// name
+  objNull,			// null
+
+  // complex objects
+  objArray,			// array
+  objDict,			// dictionary
+  objStream,			// stream
+  objRef,			// indirect reference
+
+  // special objects
+  objCmd,			// command name
+  objError,			// error return from Lexer
+  objEOF,			// end of file return from Lexer
+  objNone			// uninitialized object
+};
+
+#define numObjTypes 14		// total number of object types
+
+//------------------------------------------------------------------------
+// Object
+//------------------------------------------------------------------------
+
+#ifdef DEBUG_MEM
+#define initObj(t) ++numAlloc[type = t]
+#else
+#define initObj(t) type = t
+#endif
+
+class Object {
+public:
+
+  // Default constructor.
+  Object():
+    type(objNone) {}
+
+  // Initialize an object.
+  Object *initBool(GBool boolnA)
+    { initObj(objBool); booln = boolnA; return this; }
+  Object *initInt(int intgA)
+    { initObj(objInt); intg = intgA; return this; }
+  Object *initReal(double realA)
+    { initObj(objReal); real = realA; return this; }
+  Object *initString(GString *stringA)
+    { initObj(objString); string = stringA; return this; }
+  Object *initName(char *nameA)
+    { initObj(objName); name = copyString(nameA); return this; }
+  Object *initNull()
+    { initObj(objNull); return this; }
+  Object *initArray(XRef *xref);
+  Object *initDict(XRef *xref);
+  Object *initDict(Dict *dictA);
+  Object *initStream(Stream *streamA);
+  Object *initRef(int numA, int genA)
+    { initObj(objRef); ref.num = numA; ref.gen = genA; return this; }
+  Object *initCmd(char *cmdA)
+    { initObj(objCmd); cmd = copyString(cmdA); return this; }
+  Object *initError()
+    { initObj(objError); return this; }
+  Object *initEOF()
+    { initObj(objEOF); return this; }
+
+  // Copy an object.
+  Object *copy(Object *obj);
+
+  // If object is a Ref, fetch and return the referenced object.
+  // Otherwise, return a copy of the object.
+  Object *fetch(XRef *xref, Object *obj);
+
+  // Free object contents.
+  void free();
+
+  // Type checking.
+  ObjType getType() { return type; }
+  GBool isBool() { return type == objBool; }
+  GBool isInt() { return type == objInt; }
+  GBool isReal() { return type == objReal; }
+  GBool isNum() { return type == objInt || type == objReal; }
+  GBool isString() { return type == objString; }
+  GBool isName() { return type == objName; }
+  GBool isNull() { return type == objNull; }
+  GBool isArray() { return type == objArray; }
+  GBool isDict() { return type == objDict; }
+  GBool isStream() { return type == objStream; }
+  GBool isRef() { return type == objRef; }
+  GBool isCmd() { return type == objCmd; }
+  GBool isError() { return type == objError; }
+  GBool isEOF() { return type == objEOF; }
+  GBool isNone() { return type == objNone; }
+
+  // Special type checking.
+  GBool isName(char *nameA)
+    { return type == objName && !strcmp(name, nameA); }
+  GBool isDict(char *dictType);
+  GBool isStream(char *dictType);
+  GBool isCmd(char *cmdA)
+    { return type == objCmd && !strcmp(cmd, cmdA); }
+
+  // Accessors.  NB: these assume object is of correct type.
+  GBool getBool() { return booln; }
+  int getInt() { return intg; }
+  double getReal() { return real; }
+  double getNum() { return type == objInt ? (double)intg : real; }
+  GString *getString() { return string; }
+  char *getName() { return name; }
+  Array *getArray() { return array; }
+  Dict *getDict() { return dict; }
+  Stream *getStream() { return stream; }
+  Ref getRef() { return ref; }
+  int getRefNum() { return ref.num; }
+  int getRefGen() { return ref.gen; }
+  char *getCmd() { return cmd; }
+
+  // Array accessors.
+  int arrayGetLength();
+  void arrayAdd(Object *elem);
+  Object *arrayGet(int i, Object *obj);
+  Object *arrayGetNF(int i, Object *obj);
+
+  // Dict accessors.
+  int dictGetLength();
+  void dictAdd(char *key, Object *val);
+  GBool dictIs(char *dictType);
+  Object *dictLookup(char *key, Object *obj);
+  Object *dictLookupNF(char *key, Object *obj);
+  char *dictGetKey(int i);
+  Object *dictGetVal(int i, Object *obj);
+  Object *dictGetValNF(int i, Object *obj);
+
+  // Stream accessors.
+  GBool streamIs(char *dictType);
+  void streamReset();
+  void streamClose();
+  int streamGetChar();
+  int streamLookChar();
+  char *streamGetLine(char *buf, int size);
+  Guint streamGetPos();
+  void streamSetPos(Guint pos, int dir = 0);
+  Dict *streamGetDict();
+
+  // Output.
+  char *getTypeName();
+  void print(FILE *f = stdout);
+
+  // Memory testing.
+  static void memCheck(FILE *f);
+
+private:
+
+  ObjType type;			// object type
+  union {			// value for each type:
+    GBool booln;		//   boolean
+    int intg;			//   integer
+    double real;		//   real
+    GString *string;		//   string
+    char *name;			//   name
+    Array *array;		//   array
+    Dict *dict;			//   dictionary
+    Stream *stream;		//   stream
+    Ref ref;			//   indirect reference
+    char *cmd;			//   command
+  };
+
+#ifdef DEBUG_MEM
+  static int			// number of each type of object
+    numAlloc[numObjTypes];	//   currently allocated
+#endif
+};
+
+//------------------------------------------------------------------------
+// Array accessors.
+//------------------------------------------------------------------------
+
+#include "Array.h"
+
+inline int Object::arrayGetLength()
+  { return array->getLength(); }
+
+inline void Object::arrayAdd(Object *elem)
+  { array->add(elem); }
+
+inline Object *Object::arrayGet(int i, Object *obj)
+  { return array->get(i, obj); }
+
+inline Object *Object::arrayGetNF(int i, Object *obj)
+  { return array->getNF(i, obj); }
+
+//------------------------------------------------------------------------
+// Dict accessors.
+//------------------------------------------------------------------------
+
+#include "Dict.h"
+
+inline int Object::dictGetLength()
+  { return dict->getLength(); }
+
+inline void Object::dictAdd(char *key, Object *val)
+  { dict->add(key, val); }
+
+inline GBool Object::dictIs(char *dictType)
+  { return dict->is(dictType); }
+
+inline GBool Object::isDict(char *dictType)
+  { return type == objDict && dictIs(dictType); }
+
+inline Object *Object::dictLookup(char *key, Object *obj)
+  { return dict->lookup(key, obj); }
+
+inline Object *Object::dictLookupNF(char *key, Object *obj)
+  { return dict->lookupNF(key, obj); }
+
+inline char *Object::dictGetKey(int i)
+  { return dict->getKey(i); }
+
+inline Object *Object::dictGetVal(int i, Object *obj)
+  { return dict->getVal(i, obj); }
+
+inline Object *Object::dictGetValNF(int i, Object *obj)
+  { return dict->getValNF(i, obj); }
+
+//------------------------------------------------------------------------
+// Stream accessors.
+//------------------------------------------------------------------------
+
+#include "Stream.h"
+
+inline GBool Object::streamIs(char *dictType)
+  { return stream->getDict()->is(dictType); }
+
+inline GBool Object::isStream(char *dictType)
+  { return type == objStream && streamIs(dictType); }
+
+inline void Object::streamReset()
+  { stream->reset(); }
+
+inline void Object::streamClose()
+  { stream->close(); }
+
+inline int Object::streamGetChar()
+  { return stream->getChar(); }
+
+inline int Object::streamLookChar()
+  { return stream->lookChar(); }
+
+inline char *Object::streamGetLine(char *buf, int size)
+  { return stream->getLine(buf, size); }
+
+inline Guint Object::streamGetPos()
+  { return stream->getPos(); }
+
+inline void Object::streamSetPos(Guint pos, int dir)
+  { stream->setPos(pos, dir); }
+
+inline Dict *Object::streamGetDict()
+  { return stream->getDict(); }
+
+#endif
diff --git a/xpdf/Outline.cc b/xpdf/Outline.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Outline.cc
@@ -0,0 +1,151 @@
+//========================================================================
+//
+// Outline.cc
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include "gmem.h"
+#include "GString.h"
+#include "GList.h"
+#include "Link.h"
+#include "PDFDocEncoding.h"
+#include "Outline.h"
+
+//------------------------------------------------------------------------
+
+Outline::Outline(Object *outlineObj, XRef *xref) {
+  Object first, last;
+
+  items = NULL;
+  if (!outlineObj->isDict()) {
+    return;
+  }
+  items = OutlineItem::readItemList(outlineObj->dictLookupNF("First", &first),
+				    outlineObj->dictLookupNF("Last", &last),
+				    xref);
+  first.free();
+  last.free();
+}
+
+Outline::~Outline() {
+  if (items) {
+    deleteGList(items, OutlineItem);
+  }
+}
+
+//------------------------------------------------------------------------
+
+OutlineItem::OutlineItem(Dict *dict, XRef *xrefA) {
+  Object obj1;
+  GString *s;
+  int i;
+
+  xref = xrefA;
+  title = NULL;
+  action = NULL;
+  kids = NULL;
+
+  if (dict->lookup("Title", &obj1)->isString()) {
+    s = obj1.getString();
+    if ((s->getChar(0) & 0xff) == 0xfe &&
+	(s->getChar(1) & 0xff) == 0xff) {
+      titleLen = (s->getLength() - 2) / 2;
+      title = (Unicode *)gmallocn(titleLen, sizeof(Unicode));
+      for (i = 0; i < titleLen; ++i) {
+	title[i] = ((s->getChar(2 + 2*i) & 0xff) << 8) |
+	           (s->getChar(3 + 2*i) & 0xff);
+      }
+    } else {
+      titleLen = s->getLength();
+      title = (Unicode *)gmallocn(titleLen, sizeof(Unicode));
+      for (i = 0; i < titleLen; ++i) {
+	title[i] = pdfDocEncoding[s->getChar(i) & 0xff];
+      }
+    }
+  } else {
+    titleLen = 0;
+  }
+  obj1.free();
+
+  if (!dict->lookup("Dest", &obj1)->isNull()) {
+    action = LinkAction::parseDest(&obj1);
+  } else {
+    obj1.free();
+    if (!dict->lookup("A", &obj1)->isNull()) {
+      action = LinkAction::parseAction(&obj1);
+    }
+  }
+  obj1.free();
+
+  dict->lookupNF("First", &firstRef);
+  dict->lookupNF("Last", &lastRef);
+  dict->lookupNF("Next", &nextRef);
+
+  startsOpen = gFalse;
+  if (dict->lookup("Count", &obj1)->isInt()) {
+    if (obj1.getInt() > 0) {
+      startsOpen = gTrue;
+    }
+  }
+  obj1.free();
+}
+
+OutlineItem::~OutlineItem() {
+  close();
+  if (title) {
+    gfree(title);
+  }
+  if (action) {
+    delete action;
+  }
+  firstRef.free();
+  lastRef.free();
+  nextRef.free();
+}
+
+GList *OutlineItem::readItemList(Object *firstItemRef, Object *lastItemRef,
+				 XRef *xrefA) {
+  GList *items;
+  OutlineItem *item;
+  Object obj;
+  Object *p;
+
+  items = new GList();
+  p = firstItemRef;
+  while (p->isRef()) {
+    if (!p->fetch(xrefA, &obj)->isDict()) {
+      obj.free();
+      break;
+    }
+    item = new OutlineItem(obj.getDict(), xrefA);
+    obj.free();
+    items->append(item);
+    if (p->getRef().num == lastItemRef->getRef().num &&
+	p->getRef().gen == lastItemRef->getRef().gen) {
+      break;
+    }
+    p = &item->nextRef;
+  }
+  return items;
+}
+
+void OutlineItem::open() {
+  if (!kids) {
+    kids = readItemList(&firstRef, &lastRef, xref);
+  }
+}
+
+void OutlineItem::close() {
+  if (kids) {
+    deleteGList(kids, OutlineItem);
+    kids = NULL;
+  }
+}
diff --git a/xpdf/Outline.h b/xpdf/Outline.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Outline.h
@@ -0,0 +1,76 @@
+//========================================================================
+//
+// Outline.h
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef OUTLINE_H
+#define OUTLINE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "Object.h"
+#include "CharTypes.h"
+
+class GString;
+class GList;
+class XRef;
+class LinkAction;
+
+//------------------------------------------------------------------------
+
+class Outline {
+public:
+
+  Outline(Object *outlineObj, XRef *xref);
+  ~Outline();
+
+  GList *getItems() { return items; }
+
+private:
+
+  GList *items;			// NULL if document has no outline
+				//   [OutlineItem]
+};
+
+//------------------------------------------------------------------------
+
+class OutlineItem {
+public:
+
+  OutlineItem(Dict *dict, XRef *xrefA);
+  ~OutlineItem();
+
+  static GList *readItemList(Object *firstItemRef, Object *lastItemRef,
+			     XRef *xrefA);
+
+  void open();
+  void close();
+
+  Unicode *getTitle() { return title; }
+  int getTitleLength() { return titleLen; }
+  LinkAction *getAction() { return action; }
+  GBool isOpen() { return startsOpen; }
+  GBool hasKids() { return firstRef.isRef(); }
+  GList *getKids() { return kids; }
+
+private:
+
+  XRef *xref;
+  Unicode *title;
+  int titleLen;
+  LinkAction *action;
+  Object firstRef;
+  Object lastRef;
+  Object nextRef;
+  GBool startsOpen;
+  GList *kids;			// NULL unless this item is open [OutlineItem]
+};
+
+#endif
diff --git a/xpdf/OutputDev.cc b/xpdf/OutputDev.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/OutputDev.cc
@@ -0,0 +1,131 @@
+//========================================================================
+//
+// OutputDev.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stddef.h>
+#include "Object.h"
+#include "Stream.h"
+#include "GfxState.h"
+#include "OutputDev.h"
+
+//------------------------------------------------------------------------
+// OutputDev
+//------------------------------------------------------------------------
+
+void OutputDev::setDefaultCTM(double *ctm) {
+  int i;
+  double det;
+
+  for (i = 0; i < 6; ++i) {
+    defCTM[i] = ctm[i];
+  }
+  det = 1 / (defCTM[0] * defCTM[3] - defCTM[1] * defCTM[2]);
+  defICTM[0] = defCTM[3] * det;
+  defICTM[1] = -defCTM[1] * det;
+  defICTM[2] = -defCTM[2] * det;
+  defICTM[3] = defCTM[0] * det;
+  defICTM[4] = (defCTM[2] * defCTM[5] - defCTM[3] * defCTM[4]) * det;
+  defICTM[5] = (defCTM[1] * defCTM[4] - defCTM[0] * defCTM[5]) * det;
+}
+
+void OutputDev::cvtDevToUser(double dx, double dy, double *ux, double *uy) {
+  *ux = defICTM[0] * dx + defICTM[2] * dy + defICTM[4];
+  *uy = defICTM[1] * dx + defICTM[3] * dy + defICTM[5];
+}
+
+void OutputDev::cvtUserToDev(double ux, double uy, int *dx, int *dy) {
+  *dx = (int)(defCTM[0] * ux + defCTM[2] * uy + defCTM[4] + 0.5);
+  *dy = (int)(defCTM[1] * ux + defCTM[3] * uy + defCTM[5] + 0.5);
+}
+
+void OutputDev::updateAll(GfxState *state) {
+  updateLineDash(state);
+  updateFlatness(state);
+  updateLineJoin(state);
+  updateLineCap(state);
+  updateMiterLimit(state);
+  updateLineWidth(state);
+  updateStrokeAdjust(state);
+  updateFillColorSpace(state);
+  updateFillColor(state);
+  updateStrokeColorSpace(state);
+  updateStrokeColor(state);
+  updateBlendMode(state);
+  updateFillOpacity(state);
+  updateStrokeOpacity(state);
+  updateFillOverprint(state);
+  updateStrokeOverprint(state);
+  updateTransfer(state);
+  updateFont(state);
+}
+
+GBool OutputDev::beginType3Char(GfxState *state, double x, double y,
+				double dx, double dy,
+				CharCode code, Unicode *u, int uLen) {
+  return gFalse;
+}
+
+void OutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
+			      int width, int height, GBool invert,
+			      GBool inlineImg) {
+  int i, j;
+
+  if (inlineImg) {
+    str->reset();
+    j = height * ((width + 7) / 8);
+    for (i = 0; i < j; ++i)
+      str->getChar();
+    str->close();
+  }
+}
+
+void OutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
+			  int width, int height, GfxImageColorMap *colorMap,
+			  int *maskColors, GBool inlineImg) {
+  int i, j;
+
+  if (inlineImg) {
+    str->reset();
+    j = height * ((width * colorMap->getNumPixelComps() *
+		   colorMap->getBits() + 7) / 8);
+    for (i = 0; i < j; ++i)
+      str->getChar();
+    str->close();
+  }
+}
+
+void OutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
+				int width, int height,
+				GfxImageColorMap *colorMap,
+				Stream *maskStr,
+				int maskWidth, int maskHeight,
+				GBool maskInvert) {
+  drawImage(state, ref, str, width, height, colorMap, NULL, gFalse);
+}
+
+void OutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
+				    int width, int height,
+				    GfxImageColorMap *colorMap,
+				    Stream *maskStr,
+				    int maskWidth, int maskHeight,
+				    GfxImageColorMap *maskColorMap) {
+  drawImage(state, ref, str, width, height, colorMap, NULL, gFalse);
+}
+
+#if OPI_SUPPORT
+void OutputDev::opiBegin(GfxState *state, Dict *opiDict) {
+}
+
+void OutputDev::opiEnd(GfxState *state, Dict *opiDict) {
+}
+#endif
diff --git a/xpdf/OutputDev.h b/xpdf/OutputDev.h
new file mode 100644
--- /dev/null
+++ b/xpdf/OutputDev.h
@@ -0,0 +1,250 @@
+//========================================================================
+//
+// OutputDev.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef OUTPUTDEV_H
+#define OUTPUTDEV_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "CharTypes.h"
+
+class GString;
+class GfxState;
+struct GfxColor;
+class GfxColorSpace;
+class GfxImageColorMap;
+class GfxFunctionShading;
+class GfxAxialShading;
+class GfxRadialShading;
+class Stream;
+class Links;
+class Link;
+class Catalog;
+class Page;
+class Function;
+
+//------------------------------------------------------------------------
+// OutputDev
+//------------------------------------------------------------------------
+
+class OutputDev {
+public:
+
+  // Constructor.
+  OutputDev() {}
+
+  // Destructor.
+  virtual ~OutputDev() {}
+
+  //----- get info about output device
+
+  // Does this device use upside-down coordinates?
+  // (Upside-down means (0,0) is the top left corner of the page.)
+  virtual GBool upsideDown() = 0;
+
+  // Does this device use drawChar() or drawString()?
+  virtual GBool useDrawChar() = 0;
+
+  // Does this device use tilingPatternFill()?  If this returns false,
+  // tiling pattern fills will be reduced to a series of other drawing
+  // operations.
+  virtual GBool useTilingPatternFill() { return gFalse; }
+
+  // Does this device use functionShadedFill(), axialShadedFill(), and
+  // radialShadedFill()?  If this returns false, these shaded fills
+  // will be reduced to a series of other drawing operations.
+  virtual GBool useShadedFills() { return gFalse; }
+
+  // Does this device use drawForm()?  If this returns false,
+  // form-type XObjects will be interpreted (i.e., unrolled).
+  virtual GBool useDrawForm() { return gFalse; }
+
+  // Does this device use beginType3Char/endType3Char?  Otherwise,
+  // text in Type 3 fonts will be drawn with drawChar/drawString.
+  virtual GBool interpretType3Chars() = 0;
+
+  // Does this device need non-text content?
+  virtual GBool needNonText() { return gTrue; }
+
+  //----- initialization and control
+
+  // Set default transform matrix.
+  virtual void setDefaultCTM(double *ctm);
+
+  // Check to see if a page slice should be displayed.  If this
+  // returns false, the page display is aborted.  Typically, an
+  // OutputDev will use some alternate means to display the page
+  // before returning false.
+  virtual GBool checkPageSlice(Page *page, double hDPI, double vDPI,
+			       int rotate, GBool useMediaBox, GBool crop,
+			       int sliceX, int sliceY, int sliceW, int sliceH,
+			       GBool printing, Catalog *catalog,
+			       GBool (*abortCheckCbk)(void *data) = NULL,
+			       void *abortCheckCbkData = NULL)
+    { return gTrue; }
+
+  // Start a page.
+  virtual void startPage(int pageNum, GfxState *state) {}
+
+  // End a page.
+  virtual void endPage() {}
+
+  // Dump page contents to display.
+  virtual void dump() {}
+
+  //----- coordinate conversion
+
+  // Convert between device and user coordinates.
+  virtual void cvtDevToUser(double dx, double dy, double *ux, double *uy);
+  virtual void cvtUserToDev(double ux, double uy, int *dx, int *dy);
+
+  double *getDefCTM() { return defCTM; }
+  double *getDefICTM() { return defICTM; }
+
+  //----- save/restore graphics state
+  virtual void saveState(GfxState *state) {}
+  virtual void restoreState(GfxState *state) {}
+
+  //----- update graphics state
+  virtual void updateAll(GfxState *state);
+  virtual void updateCTM(GfxState *state, double m11, double m12,
+			 double m21, double m22, double m31, double m32) {}
+  virtual void updateLineDash(GfxState *state) {}
+  virtual void updateFlatness(GfxState *state) {}
+  virtual void updateLineJoin(GfxState *state) {}
+  virtual void updateLineCap(GfxState *state) {}
+  virtual void updateMiterLimit(GfxState *state) {}
+  virtual void updateLineWidth(GfxState *state) {}
+  virtual void updateStrokeAdjust(GfxState *state) {}
+  virtual void updateFillColorSpace(GfxState *state) {}
+  virtual void updateStrokeColorSpace(GfxState *state) {}
+  virtual void updateFillColor(GfxState *state) {}
+  virtual void updateStrokeColor(GfxState *state) {}
+  virtual void updateBlendMode(GfxState *state) {}
+  virtual void updateFillOpacity(GfxState *state) {}
+  virtual void updateStrokeOpacity(GfxState *state) {}
+  virtual void updateFillOverprint(GfxState *state) {}
+  virtual void updateStrokeOverprint(GfxState *state) {}
+  virtual void updateTransfer(GfxState *state) {}
+
+  //----- update text state
+  virtual void updateFont(GfxState *state) {}
+  virtual void updateTextMat(GfxState *state) {}
+  virtual void updateCharSpace(GfxState *state) {}
+  virtual void updateRender(GfxState *state) {}
+  virtual void updateRise(GfxState *state) {}
+  virtual void updateWordSpace(GfxState *state) {}
+  virtual void updateHorizScaling(GfxState *state) {}
+  virtual void updateTextPos(GfxState *state) {}
+  virtual void updateTextShift(GfxState *state, double shift) {}
+
+  //----- path painting
+  virtual void stroke(GfxState *state) {}
+  virtual void fill(GfxState *state) {}
+  virtual void eoFill(GfxState *state) {}
+  virtual void tilingPatternFill(GfxState *state, Object *str,
+				 int paintType, Dict *resDict,
+				 double *mat, double *bbox,
+				 int x0, int y0, int x1, int y1,
+				 double xStep, double yStep) {}
+  virtual GBool functionShadedFill(GfxState *state,
+				   GfxFunctionShading *shading)
+    { return gFalse; }
+  virtual GBool axialShadedFill(GfxState *state, GfxAxialShading *shading)
+    { return gFalse; }
+  virtual GBool radialShadedFill(GfxState *state, GfxRadialShading *shading)
+    { return gFalse; }
+
+  //----- path clipping
+  virtual void clip(GfxState *state) {}
+  virtual void eoClip(GfxState *state) {}
+  virtual void clipToStrokePath(GfxState *state) {}
+
+  //----- text drawing
+  virtual void beginStringOp(GfxState *state) {}
+  virtual void endStringOp(GfxState *state) {}
+  virtual void beginString(GfxState *state, GString *s) {}
+  virtual void endString(GfxState *state) {}
+  virtual void drawChar(GfxState *state, double x, double y,
+			double dx, double dy,
+			double originX, double originY,
+			CharCode code, int nBytes, Unicode *u, int uLen) {}
+  virtual void drawString(GfxState *state, GString *s) {}
+  virtual GBool beginType3Char(GfxState *state, double x, double y,
+			       double dx, double dy,
+			       CharCode code, Unicode *u, int uLen);
+  virtual void endType3Char(GfxState *state) {}
+  virtual void endTextObject(GfxState *state) {}
+
+  //----- image drawing
+  virtual void drawImageMask(GfxState *state, Object *ref, Stream *str,
+			     int width, int height, GBool invert,
+			     GBool inlineImg);
+  virtual void drawImage(GfxState *state, Object *ref, Stream *str,
+			 int width, int height, GfxImageColorMap *colorMap,
+			 int *maskColors, GBool inlineImg);
+  virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
+			       int width, int height,
+			       GfxImageColorMap *colorMap,
+			       Stream *maskStr, int maskWidth, int maskHeight,
+			       GBool maskInvert);
+  virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
+				   int width, int height,
+				   GfxImageColorMap *colorMap,
+				   Stream *maskStr,
+				   int maskWidth, int maskHeight,
+				   GfxImageColorMap *maskColorMap);
+
+#if OPI_SUPPORT
+  //----- OPI functions
+  virtual void opiBegin(GfxState *state, Dict *opiDict);
+  virtual void opiEnd(GfxState *state, Dict *opiDict);
+#endif
+
+  //----- Type 3 font operators
+  virtual void type3D0(GfxState *state, double wx, double wy) {}
+  virtual void type3D1(GfxState *state, double wx, double wy,
+		       double llx, double lly, double urx, double ury) {}
+
+  //----- form XObjects
+  virtual void drawForm(Ref id) {}
+
+  //----- PostScript XObjects
+  virtual void psXObject(Stream *psStream, Stream *level1Stream) {}
+
+  //----- transparency groups and soft masks
+  virtual void beginTransparencyGroup(GfxState *state, double *bbox,
+				      GfxColorSpace *blendingColorSpace,
+				      GBool isolated, GBool knockout,
+				      GBool forSoftMask) {}
+  virtual void endTransparencyGroup(GfxState *state) {}
+  virtual void paintTransparencyGroup(GfxState *state, double *bbox) {}
+  virtual void setSoftMask(GfxState *state, double *bbox, GBool alpha,
+			   Function *transferFunc, GfxColor *backdropColor) {}
+  virtual void clearSoftMask(GfxState *state) {}
+
+  //----- links
+  virtual void processLink(Link *link, Catalog *catalog) {}
+
+#if 1 //~tmp: turn off anti-aliasing temporarily
+  virtual GBool getVectorAntialias() { return gFalse; }
+  virtual void setVectorAntialias(GBool vaa) {}
+#endif
+
+private:
+
+  double defCTM[6];		// default coordinate transform matrix
+  double defICTM[6];		// inverse of default CTM
+};
+
+#endif
diff --git a/xpdf/PDFCore.cc b/xpdf/PDFCore.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/PDFCore.cc
@@ -0,0 +1,2044 @@
+//========================================================================
+//
+// PDFCore.cc
+//
+// Copyright 2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <math.h>
+#include "GString.h"
+#include "GList.h"
+#include "GlobalParams.h"
+#include "Splash.h"
+#include "SplashBitmap.h"
+#include "SplashPattern.h"
+#include "SplashPath.h"
+#include "Error.h"
+#include "ErrorCodes.h"
+#include "PDFDoc.h"
+#include "Link.h"
+#include "TextOutputDev.h"
+#include "CoreOutputDev.h"
+#include "PDFCore.h"
+
+//------------------------------------------------------------------------
+// PDFCorePage
+//------------------------------------------------------------------------
+
+PDFCorePage::PDFCorePage(int pageA, int wA, int hA, int tileWA, int tileHA) {
+  page = pageA;
+  tiles = new GList();
+  w = wA;
+  h = hA;
+  tileW = tileWA;
+  tileH = tileHA;
+  links = NULL;
+  text = NULL;
+}
+
+PDFCorePage::~PDFCorePage() {
+  deleteGList(tiles, PDFCoreTile);
+  if (links) {
+    delete links;
+  }
+  if (text) {
+    delete text;
+  }
+}
+
+//------------------------------------------------------------------------
+// PDFCoreTile
+//------------------------------------------------------------------------
+
+PDFCoreTile::PDFCoreTile(int xDestA, int yDestA) {
+  xMin = 0;
+  yMin = 0;
+  xMax = 0;
+  yMax = 0;
+  xDest = xDestA;
+  yDest = yDestA;
+  bitmap = NULL;
+}
+
+PDFCoreTile::~PDFCoreTile() {
+  if (bitmap) {
+    delete bitmap;
+  }
+}
+
+
+//------------------------------------------------------------------------
+// PDFCore
+//------------------------------------------------------------------------
+
+PDFCore::PDFCore(SplashColorMode colorModeA, int bitmapRowPadA,
+		 GBool reverseVideoA, SplashColorPtr paperColorA,
+		 GBool incrementalUpdate) {
+  int i;
+
+  doc = NULL;
+  continuousMode = globalParams->getContinuousView();
+  drawAreaWidth = drawAreaHeight = 0;
+  maxPageW = totalDocH = 0;
+  pageY = NULL;
+  topPage = 0;
+  scrollX = scrollY = 0;
+  zoom = defZoom;
+  dpi = 0;
+  rotate = 0;
+
+  selectPage = 0;
+  selectULX = selectLRX = 0;
+  selectULY = selectLRY = 0;
+  dragging = gFalse;
+  lastDragLeft = lastDragTop = gTrue;
+
+  historyCur = pdfHistorySize - 1;
+  historyBLen = historyFLen = 0;
+  for (i = 0; i < pdfHistorySize; ++i) {
+    history[i].fileName = NULL;
+  }
+
+
+  pages = new GList();
+  curTile = NULL;
+
+  splashColorCopy(paperColor, paperColorA);
+  out = new CoreOutputDev(colorModeA, bitmapRowPadA,
+			  reverseVideoA, paperColorA, incrementalUpdate,
+			  &redrawCbk, this);
+  out->startDoc(NULL);
+}
+
+PDFCore::~PDFCore() {
+  int i;
+
+  if (doc) {
+    delete doc;
+  }
+  for (i = 0; i < pdfHistorySize; ++i) {
+    if (history[i].fileName) {
+      delete history[i].fileName;
+    }
+  }
+  gfree(pageY);
+  deleteGList(pages, PDFCorePage);
+  delete out;
+}
+
+int PDFCore::loadFile(GString *fileName, GString *ownerPassword,
+		      GString *userPassword) {
+  int err;
+
+  setBusyCursor(gTrue);
+  err = loadFile2(new PDFDoc(fileName->copy(), ownerPassword, userPassword,
+			     this));
+  setBusyCursor(gFalse);
+  return err;
+}
+
+#ifdef WIN32
+int PDFCore::loadFile(wchar_t *fileName, int fileNameLen,
+		      GString *ownerPassword, GString *userPassword) {
+  int err;
+
+  setBusyCursor(gTrue);
+  err = loadFile2(new PDFDoc(fileName, fileNameLen,
+			     ownerPassword, userPassword, this));
+  setBusyCursor(gFalse);
+  return err;
+}
+#endif
+
+int PDFCore::loadFile(BaseStream *stream, GString *ownerPassword,
+		      GString *userPassword) {
+  int err;
+
+  setBusyCursor(gTrue);
+  err = loadFile2(new PDFDoc(stream, ownerPassword, userPassword, this));
+  setBusyCursor(gFalse);
+  return err;
+}
+
+void PDFCore::loadDoc(PDFDoc *docA) {
+  setBusyCursor(gTrue);
+  loadFile2(docA);
+  setBusyCursor(gFalse);
+}
+
+int PDFCore::loadFile2(PDFDoc *newDoc) {
+  int err;
+  double w, h, t;
+  int i;
+
+  // open the PDF file
+  if (!newDoc->isOk()) {
+    err = newDoc->getErrorCode();
+    delete newDoc;
+    return err;
+  }
+
+  // replace old document
+  if (doc) {
+    delete doc;
+  }
+  doc = newDoc;
+  if (out) {
+    out->startDoc(doc->getXRef());
+  }
+
+  // nothing displayed yet
+  topPage = -99;
+  while (pages->getLength() > 0) {
+    delete (PDFCorePage *)pages->del(0);
+  }
+
+  // compute the max unscaled page size
+  maxUnscaledPageW = maxUnscaledPageH = 0;
+  for (i = 1; i <= doc->getNumPages(); ++i) {
+    w = doc->getPageCropWidth(i);
+    h = doc->getPageCropHeight(i);
+    if (doc->getPageRotate(i) == 90 || doc->getPageRotate(i) == 270) {
+      t = w; w = h; h = t;
+    }
+    if (w > maxUnscaledPageW) {
+      maxUnscaledPageW = w;
+    }
+    if (h > maxUnscaledPageH) {
+      maxUnscaledPageH = h;
+    }
+  }
+
+  return errNone;
+}
+
+void PDFCore::clear() {
+  if (!doc) {
+    return;
+  }
+
+  // no document
+  delete doc;
+  doc = NULL;
+  out->clear();
+
+  // no page displayed
+  topPage = -99;
+  while (pages->getLength() > 0) {
+    delete (PDFCorePage *)pages->del(0);
+  }
+
+  // redraw
+  scrollX = scrollY = 0;
+  redrawWindow(0, 0, drawAreaWidth, drawAreaHeight, gTrue);
+  updateScrollbars();
+}
+
+PDFDoc *PDFCore::takeDoc(GBool redraw) {
+  PDFDoc *docA;
+
+  if (!doc) {
+    return NULL;
+  }
+
+  // no document
+  docA = doc;
+  doc = NULL;
+  out->clear();
+
+  // no page displayed
+  topPage = -99;
+  while (pages->getLength() > 0) {
+    delete (PDFCorePage *)pages->del(0);
+  }
+
+  // redraw
+  scrollX = scrollY = 0;
+  if (redraw) {
+    redrawWindow(0, 0, drawAreaWidth, drawAreaHeight, gTrue);
+    updateScrollbars();
+  }
+
+  return docA;
+}
+
+void PDFCore::displayPage(int topPageA, double zoomA, int rotateA,
+			  GBool scrollToTop, GBool addToHist) {
+  int scrollXA, scrollYA;
+
+  scrollXA = scrollX;
+  if (continuousMode) {
+    scrollYA = -1;
+  } else if (scrollToTop) {
+    scrollYA = 0;
+  } else {
+    scrollYA = scrollY;
+  }
+  if (zoomA != zoom) {
+    scrollXA = 0;
+    scrollYA = continuousMode ? -1 : 0;
+  }
+
+  dragging = gFalse;
+  lastDragLeft = lastDragTop = gTrue;
+
+  update(topPageA, scrollXA, scrollYA, zoomA, rotateA, gTrue, addToHist);
+}
+
+void PDFCore::displayDest(LinkDest *dest, double zoomA, int rotateA,
+			  GBool addToHist) {
+  Ref pageRef;
+  int topPageA;
+  int dx, dy, scrollXA, scrollYA;
+
+  if (dest->isPageRef()) {
+    pageRef = dest->getPageRef();
+    topPageA = doc->findPage(pageRef.num, pageRef.gen);
+  } else {
+    topPageA = dest->getPageNum();
+  }
+  if (topPageA <= 0 || topPageA > doc->getNumPages()) {
+    topPageA = 1;
+  }
+  scrollXA = scrollX;
+  scrollYA = continuousMode ? -1 : scrollY;
+  switch (dest->getKind()) {
+  case destXYZ:
+    cvtUserToDev(topPageA, dest->getLeft(), dest->getTop(), &dx, &dy);
+    scrollXA = dest->getChangeLeft() ? dx : scrollX;
+    if (dest->getChangeTop()) {
+      scrollYA = dy;
+    } else {
+      if (topPage <= 0) {
+	scrollYA = 0;
+      } else if (continuousMode) {
+	scrollYA = scrollY - pageY[topPage - 1];
+      } else {
+	scrollYA = scrollY;
+      }
+    }
+    if (continuousMode && topPage > 0) {
+      scrollYA += pageY[topPageA - 1];
+    }
+    //~ what is the zoom parameter?
+    break;
+  case destFit:
+  case destFitB:
+    //~ do fit
+    scrollXA = 0;
+    scrollYA = continuousMode ? -1 : 0;
+    break;
+  case destFitH:
+  case destFitBH:
+    //~ do fit
+    cvtUserToDev(topPageA, 0, dest->getTop(), &dx, &dy);
+    if (continuousMode && topPage > 0) {
+      dy += pageY[topPageA - 1];
+    }
+    scrollXA = 0;
+    scrollYA = dy;
+    break;
+  case destFitV:
+  case destFitBV:
+    //~ do fit
+    cvtUserToDev(topPageA, dest->getLeft(), 0, &dx, &dy);
+    scrollXA = dx;
+    scrollYA = continuousMode ? -1 : 0;
+    break;
+  case destFitR:
+    //~ do fit
+    cvtUserToDev(topPageA, dest->getLeft(), dest->getTop(), &dx, &dy);
+    if (continuousMode && topPage > 0) {
+      dy += pageY[topPageA - 1];
+    }
+    scrollXA = dx;
+    scrollYA = dy;
+    break;
+  }
+  update(topPageA, scrollXA, scrollYA, zoom, rotate, gFalse,
+	 addToHist && topPageA != topPage);
+}
+
+void PDFCore::update(int topPageA, int scrollXA, int scrollYA,
+		     double zoomA, int rotateA, GBool force, GBool addToHist) {
+  double hDPI, vDPI, dpiA, uw, uh, ut;
+  int w, h, t, x0, x1, y0, y1, x, y;
+  int rot;
+  int pg0, pg1;
+  PDFCoreTile *tile;
+  PDFCorePage *page;
+  PDFHistory *hist;
+  SplashColor xorColor;
+  GBool needUpdate;
+  int i, j;
+
+  // check for document and valid page number
+  if (!doc) {
+    // save the new settings
+    zoom = zoomA;
+    rotate = rotateA;
+    return;
+  }
+  if (topPageA <= 0 || topPageA > doc->getNumPages()) {
+    return;
+  }
+
+  needUpdate = gFalse;
+
+  // check for changes to the PDF file
+  if ((force || (!continuousMode && topPage != topPageA)) &&
+      checkForNewFile()) {
+    if (loadFile(doc->getFileName()) == errNone) {
+      if (topPageA > doc->getNumPages()) {
+	topPageA = doc->getNumPages();
+      }
+      needUpdate = gTrue;
+    }
+  }
+
+  // compute the DPI
+  if (continuousMode) {
+    uw = maxUnscaledPageW;
+    uh = maxUnscaledPageH;
+    rot = rotateA;
+  } else {
+    uw = doc->getPageCropWidth(topPageA);
+    uh = doc->getPageCropHeight(topPageA);
+    rot = rotateA + doc->getPageRotate(topPageA);
+    if (rot >= 360) {
+      rot -= 360;
+    } else if (rot < 0) {
+      rot += 360;
+    }
+  }
+  if (rot == 90 || rot == 270) {
+    ut = uw; uw = uh; uh = ut;
+  }
+  if (zoomA == zoomPage) {
+    hDPI = (drawAreaWidth / uw) * 72;
+    if (continuousMode) {
+      vDPI = ((drawAreaHeight - continuousModePageSpacing) / uh) * 72;
+    } else {
+      vDPI = (drawAreaHeight / uh) * 72;
+    }
+    dpiA = (hDPI < vDPI) ? hDPI : vDPI;
+  } else if (zoomA == zoomWidth) {
+    dpiA = (drawAreaWidth / uw) * 72;
+  } else {
+    dpiA = 0.01 * zoomA * 72;
+  }
+  // this can happen if the window hasn't been sized yet
+  if (dpiA <= 0) {
+    dpiA = 1;
+  }
+
+  // if the display properties have changed, create a new PDFCorePage
+  // object
+  if (force || pages->getLength() == 0 ||
+      (!continuousMode && topPageA != topPage) ||
+      zoomA != zoom || dpiA != dpi || rotateA != rotate) {
+    needUpdate = gTrue;
+    setSelection(0, 0, 0, 0, 0);
+    while (pages->getLength() > 0) {
+      delete (PDFCorePage *)pages->del(0);
+    }
+    zoom = zoomA;
+    rotate = rotateA;
+    dpi = dpiA;
+    if (continuousMode) {
+      maxPageW = totalDocH = 0;
+      pageY = (int *)greallocn(pageY, doc->getNumPages(), sizeof(int));
+      for (i = 1; i <= doc->getNumPages(); ++i) {
+	pageY[i-1] = totalDocH;
+	w = (int)((doc->getPageCropWidth(i) * dpi) / 72 + 0.5);
+	h = (int)((doc->getPageCropHeight(i) * dpi) / 72 + 0.5);
+	rot = rotate + doc->getPageRotate(i);
+	if (rot >= 360) {
+	  rot -= 360;
+	} else if (rot < 0) {
+	  rot += 360;
+	}
+	if (rot == 90 || rot == 270) {
+	  t = w; w = h; h = t;
+	}
+	if (w > maxPageW) {
+	  maxPageW = w;
+	}
+	totalDocH += h;
+	if (i < doc->getNumPages()) {
+	  totalDocH += continuousModePageSpacing;
+	}
+      }
+    } else {
+      rot = rotate + doc->getPageRotate(topPageA);
+      if (rot >= 360) {
+	rot -= 360;
+      } else if (rot < 0) {
+	rot += 360;
+      }
+      addPage(topPageA, rot);
+    }
+  } else {
+    // erase the selection
+    if (selectULX != selectLRX && selectULY != selectLRY) {
+      xorColor[0] = xorColor[1] = xorColor[2] = 0xff;
+      xorRectangle(selectPage, selectULX, selectULY, selectLRX, selectLRY,
+		   new SplashSolidColor(xorColor));
+    }
+  }
+  if (continuousMode) {
+    page = NULL; // make gcc happy
+  } else {
+    page = (PDFCorePage *)pages->get(0);
+  }
+  topPage = topPageA;
+
+  // adjust the scroll position
+  scrollX = scrollXA;
+  if (continuousMode && scrollYA < 0) {
+    scrollY = pageY[topPage - 1];
+  } else {
+    scrollY = scrollYA;
+  }
+  w = continuousMode ? maxPageW : page->w;
+  if (scrollX > w - drawAreaWidth) {
+    scrollX = w - drawAreaWidth;
+  }
+  if (scrollX < 0) {
+    scrollX = 0;
+  }
+  h = continuousMode ? totalDocH : page->h;
+  if (scrollY > h - drawAreaHeight) {
+    scrollY = h - drawAreaHeight;
+  }
+  if (scrollY < 0) {
+    scrollY = 0;
+  }
+
+  // find topPage, and the first and last pages to be rasterized
+  if (continuousMode) {
+    //~ should use a binary search
+    for (i = 2; i <= doc->getNumPages(); ++i) {
+      if (pageY[i-1] > scrollY - drawAreaHeight / 2) {
+	break;
+      }
+    }
+    pg0 = i - 1;
+    for (i = pg0 + 1; i <= doc->getNumPages(); ++i) {
+      if (pageY[i-1] > scrollY) {
+	break;
+      }
+    }
+    topPage = i - 1;
+    for (i = topPage + 1; i <= doc->getNumPages(); ++i) {
+      if (pageY[i-1] > scrollY + drawAreaHeight + drawAreaHeight / 2) {
+	break;
+      }
+    }
+    pg1 = i - 1;
+
+    // delete pages that are no longer needed and insert new pages
+    // objects that are needed
+    while (pages->getLength() > 0 &&
+	   ((PDFCorePage *)pages->get(0))->page < pg0) {
+      delete (PDFCorePage *)pages->del(0);
+    }
+    i = pages->getLength() - 1;
+    while (i > 0 && ((PDFCorePage *)pages->get(i))->page > pg1) {
+      delete (PDFCorePage *)pages->del(i--);
+    }
+    j = pages->getLength() > 0 ? ((PDFCorePage *)pages->get(0))->page - 1
+                               : pg1;
+    for (i = pg0; i <= j; ++i) {
+      rot = rotate + doc->getPageRotate(i);
+      if (rot >= 360) {
+	rot -= 360;
+      } else if (rot < 0) {
+	rot += 360;
+      }
+      addPage(i, rot);
+    }      
+    j = ((PDFCorePage *)pages->get(pages->getLength() - 1))->page;
+    for (i = j + 1; i <= pg1; ++i) {
+      rot = rotate + doc->getPageRotate(i);
+      if (rot >= 360) {
+	rot -= 360;
+      } else if (rot < 0) {
+	rot += 360;
+      }
+      addPage(i, rot);
+    }
+
+  } else {
+    pg0 = pg1 = topPage;
+  }
+
+  // delete tiles that are no longer needed
+  for (i = 0; i < pages->getLength(); ++i) {
+    page = (PDFCorePage *)pages->get(i);
+    j = 0;
+    while (j < page->tiles->getLength()) {
+      tile = (PDFCoreTile *)page->tiles->get(j);
+      if (continuousMode) {
+	y0 = pageY[page->page - 1] + tile->yMin;
+	y1 = pageY[page->page - 1] + tile->yMax;
+      } else {
+	y0 = tile->yMin;
+	y1 = tile->yMax;
+      }
+      if (tile->xMax < scrollX - drawAreaWidth / 2 ||
+	  tile->xMin > scrollX + drawAreaWidth + drawAreaWidth / 2 ||
+	  y1 < scrollY - drawAreaHeight / 2 ||
+	  y0 > scrollY + drawAreaHeight + drawAreaHeight / 2) {
+	delete (PDFCoreTile *)page->tiles->del(j);
+      } else {
+	++j;
+      }
+    }
+  }
+
+  // update page positions
+  for (i = 0; i < pages->getLength(); ++i) {
+    page = (PDFCorePage *)pages->get(i);
+    page->xDest = -scrollX;
+    if (continuousMode) {
+      page->yDest = pageY[page->page - 1] - scrollY;
+    } else {
+      page->yDest = -scrollY;
+    }
+    if (continuousMode) {
+      if (page->w < maxPageW) {
+	page->xDest += (maxPageW - page->w) / 2;
+      }
+      if (maxPageW < drawAreaWidth) {
+	page->xDest += (drawAreaWidth - maxPageW) / 2;
+      }
+    } else if (page->w < drawAreaWidth) {
+      page->xDest += (drawAreaWidth - page->w) / 2;
+    }
+    if (continuousMode && totalDocH < drawAreaHeight) {
+      page->yDest += (drawAreaHeight - totalDocH) / 2;
+    } else if (!continuousMode && page->h < drawAreaHeight) {
+      page->yDest += (drawAreaHeight - page->h) / 2;
+    }
+  }
+
+  // rasterize any new tiles
+  for (i = 0; i < pages->getLength(); ++i) {
+    page = (PDFCorePage *)pages->get(i);
+    x0 = page->xDest;
+    x1 = x0 + page->w - 1;
+    if (x0 < -drawAreaWidth / 2) {
+      x0 = -drawAreaWidth / 2;
+    }
+    if (x1 > drawAreaWidth + drawAreaWidth / 2) {
+      x1 = drawAreaWidth + drawAreaWidth / 2;
+    }
+    x0 = ((x0 - page->xDest) / page->tileW) * page->tileW;
+    x1 = ((x1 - page->xDest) / page->tileW) * page->tileW;
+    y0 = page->yDest;
+    y1 = y0 + page->h - 1;
+    if (y0 < -drawAreaHeight / 2) {
+      y0 = -drawAreaHeight / 2;
+    }
+    if (y1 > drawAreaHeight + drawAreaHeight / 2) {
+      y1 = drawAreaHeight + drawAreaHeight / 2;
+    }
+    y0 = ((y0 - page->yDest) / page->tileH) * page->tileH;
+    y1 = ((y1 - page->yDest) / page->tileH) * page->tileH;
+    for (y = y0; y <= y1; y += page->tileH) {
+      for (x = x0; x <= x1; x += page->tileW) {
+	needTile(page, x, y);
+      }
+    }
+  }
+
+  // update tile positions
+  for (i = 0; i < pages->getLength(); ++i) {
+    page = (PDFCorePage *)pages->get(i);
+    for (j = 0; j < page->tiles->getLength(); ++j) {
+      tile = (PDFCoreTile *)page->tiles->get(j);
+      tile->xDest = tile->xMin - scrollX;
+      if (continuousMode) {
+	tile->yDest = tile->yMin + pageY[page->page - 1] - scrollY;
+      } else {
+	tile->yDest = tile->yMin - scrollY;
+      }
+      if (continuousMode) {
+	if (page->w < maxPageW) {
+	  tile->xDest += (maxPageW - page->w) / 2;
+	}
+	if (maxPageW < drawAreaWidth) {
+	  tile->xDest += (drawAreaWidth - maxPageW) / 2;
+	}
+      } else if (page->w < drawAreaWidth) {
+	tile->xDest += (drawAreaWidth - page->w) / 2;
+      }
+      if (continuousMode && totalDocH < drawAreaHeight) {
+	tile->yDest += (drawAreaHeight - totalDocH) / 2;
+      } else if (!continuousMode && page->h < drawAreaHeight) {
+	tile->yDest += (drawAreaHeight - page->h) / 2;
+      }
+    }
+  }
+
+  // redraw the selection
+  if (selectULX != selectLRX && selectULY != selectLRY) {
+    xorColor[0] = xorColor[1] = xorColor[2] = 0xff;
+    xorRectangle(selectPage, selectULX, selectULY, selectLRX, selectLRY,
+		 new SplashSolidColor(xorColor));
+  }
+
+  // redraw the window
+  redrawWindow(0, 0, drawAreaWidth, drawAreaHeight, needUpdate);
+  updateScrollbars();
+
+  // add to history
+  if (addToHist) {
+    if (++historyCur == pdfHistorySize) {
+      historyCur = 0;
+    }
+    hist = &history[historyCur];
+    if (hist->fileName) {
+      delete hist->fileName;
+    }
+    if (doc->getFileName()) {
+      hist->fileName = doc->getFileName()->copy();
+    } else {
+      hist->fileName = NULL;
+    }
+    hist->page = topPage;
+    if (historyBLen < pdfHistorySize) {
+      ++historyBLen;
+    }
+    historyFLen = 0;
+  }
+}
+
+void PDFCore::addPage(int pg, int rot) {
+  PDFCorePage *page;
+  int w, h, t, tileW, tileH, i;
+
+  w = (int)((doc->getPageCropWidth(pg) * dpi) / 72 + 0.5);
+  h = (int)((doc->getPageCropHeight(pg) * dpi) / 72 + 0.5);
+  if (rot == 90 || rot == 270) {
+    t = w; w = h; h = t;
+  }
+  tileW = 2 * drawAreaWidth;
+  if (tileW < 1500) {
+    tileW = 1500;
+  }
+  if (tileW > w) {
+    tileW = w;
+  }
+  tileH = 2 * drawAreaHeight;
+  if (tileH < 1500) {
+    tileH = 1500;
+  }
+  if (tileH > h) {
+    tileH = h;
+  }
+  page = new PDFCorePage(pg, w, h, tileW, tileH);
+  for (i = 0;
+       i < pages->getLength() && pg > ((PDFCorePage *)pages->get(i))->page;
+       ++i) ;
+  pages->insert(i, page);
+}
+
+void PDFCore::needTile(PDFCorePage *page, int x, int y) {
+  PDFCoreTile *tile;
+  TextOutputDev *textOut;
+  int xDest, yDest, sliceW, sliceH;
+  int i;
+
+  for (i = 0; i < page->tiles->getLength(); ++i) {
+    tile = (PDFCoreTile *)page->tiles->get(i);
+    if (x == tile->xMin && y == tile->yMin) {
+      return;
+    }
+  }
+
+  setBusyCursor(gTrue);
+
+  sliceW = page->tileW;
+  if (x + sliceW > page->w) {
+    sliceW = page->w - x;
+  }
+  sliceH = page->tileH;
+  if (y + sliceH > page->h) {
+    sliceH = page->h - y;
+  }
+
+  xDest = x - scrollX;
+  if (continuousMode) {
+    yDest = y + pageY[page->page - 1] - scrollY;
+  } else {
+    yDest = y - scrollY;
+  }
+  if (continuousMode) {
+    if (page->w < maxPageW) {
+      xDest += (maxPageW - page->w) / 2;
+    }
+    if (maxPageW < drawAreaWidth) {
+      xDest += (drawAreaWidth - maxPageW) / 2;
+    }
+  } else if (page->w < drawAreaWidth) {
+    xDest += (drawAreaWidth - page->w) / 2;
+  }
+  if (continuousMode && totalDocH < drawAreaHeight) {
+    yDest += (drawAreaHeight - totalDocH) / 2;
+  } else if (!continuousMode && page->h < drawAreaHeight) {
+    yDest += (drawAreaHeight - page->h) / 2;
+  }
+  curTile = tile = newTile(xDest, yDest);
+  curPage = page;
+  tile->xMin = x;
+  tile->yMin = y;
+  tile->xMax = x + sliceW;
+  tile->yMax = y + sliceH;
+  tile->edges = 0;
+  if (tile->xMin == 0) {
+    tile->edges |= pdfCoreTileLeftEdge;
+  }
+  if (tile->xMax == page->w) {
+    tile->edges |= pdfCoreTileRightEdge;
+  }
+  if (continuousMode) {
+    if (tile->yMin == 0) {
+      tile->edges |= pdfCoreTileTopSpace;
+      if (page->page == 1) {
+	tile->edges |= pdfCoreTileTopEdge;
+      }
+    }
+    if (tile->yMax == page->h) {
+      tile->edges |= pdfCoreTileBottomSpace;
+      if (page->page == doc->getNumPages()) {
+	tile->edges |= pdfCoreTileBottomEdge;
+      }
+    }
+  } else {
+    if (tile->yMin == 0) {
+      tile->edges |= pdfCoreTileTopEdge;
+    }
+    if (tile->yMax == page->h) {
+      tile->edges |= pdfCoreTileBottomEdge;
+    }
+  }
+  doc->displayPageSlice(out, page->page, dpi, dpi, rotate,
+			gFalse, gTrue, gFalse, x, y, sliceW, sliceH);
+  tile->bitmap = out->takeBitmap();
+  memcpy(tile->ctm, out->getDefCTM(), 6 * sizeof(double));
+  memcpy(tile->ictm, out->getDefICTM(), 6 * sizeof(double));
+  if (!page->links) {
+    page->links = doc->getLinks(page->page);
+  }
+  if (!page->text) {
+    if ((textOut = new TextOutputDev(NULL, gTrue, gFalse, gFalse))) {
+      doc->displayPage(textOut, page->page, dpi, dpi, rotate,
+		       gFalse, gTrue, gFalse);
+      page->text = textOut->takeText();
+      delete textOut;
+    }
+  }
+  page->tiles->append(tile);
+  curTile = NULL;
+  curPage = NULL;
+
+  setBusyCursor(gFalse);
+}
+
+GBool PDFCore::gotoNextPage(int inc, GBool top) {
+  int pg, scrollYA;
+
+  if (!doc || doc->getNumPages() == 0 || topPage >= doc->getNumPages()) {
+    return gFalse;
+  }
+  if ((pg = topPage + inc) > doc->getNumPages()) {
+    pg = doc->getNumPages();
+  }
+  if (continuousMode) {
+    scrollYA = -1;
+  } else if (top) {
+    scrollYA = 0;
+  } else {
+    scrollYA = scrollY;
+  }
+  update(pg, scrollX, scrollYA, zoom, rotate, gFalse, gTrue);
+  return gTrue;
+}
+
+GBool PDFCore::gotoPrevPage(int dec, GBool top, GBool bottom) {
+  int pg, scrollYA;
+
+  if (!doc || doc->getNumPages() == 0 || topPage <= 1) {
+    return gFalse;
+  }
+  if ((pg = topPage - dec) < 1) {
+    pg = 1;
+  }
+  if (continuousMode) {
+    scrollYA = -1;
+  } else if (top) {
+    scrollYA = 0;
+  } else if (bottom) {
+    scrollYA = ((PDFCorePage *)pages->get(0))->h - drawAreaHeight;
+    if (scrollYA < 0) {
+      scrollYA = 0;
+    }
+  } else {
+    scrollYA = scrollY;
+  }
+  update(pg, scrollX, scrollYA, zoom, rotate, gFalse, gTrue);
+  return gTrue;
+}
+
+GBool PDFCore::gotoNamedDestination(GString *dest) {
+  LinkDest *d;
+
+  if (!doc) {
+    return gFalse;
+  }
+  if (!(d = doc->findDest(dest))) {
+    return gFalse;
+  }
+  displayDest(d, zoom, rotate, gTrue);
+  delete d;
+  return gTrue;
+}
+
+GBool PDFCore::goForward() {
+  int pg;
+
+  if (historyFLen == 0) {
+    return gFalse;
+  }
+  if (++historyCur == pdfHistorySize) {
+    historyCur = 0;
+  }
+  --historyFLen;
+  ++historyBLen;
+  if (!doc || history[historyCur].fileName->cmp(doc->getFileName()) != 0) {
+    if (loadFile(history[historyCur].fileName) != errNone) {
+      return gFalse;
+    }
+  }
+  pg = history[historyCur].page;
+  update(pg, scrollX, continuousMode ? -1 : scrollY,
+	 zoom, rotate, gFalse, gFalse);
+  return gTrue;
+}
+
+GBool PDFCore::goBackward() {
+  int pg;
+
+  if (historyBLen <= 1) {
+    return gFalse;
+  }
+  if (--historyCur < 0) {
+    historyCur = pdfHistorySize - 1;
+  }
+  --historyBLen;
+  ++historyFLen;
+  if (!doc || history[historyCur].fileName->cmp(doc->getFileName()) != 0) {
+    if (loadFile(history[historyCur].fileName) != errNone) {
+      return gFalse;
+    }
+  }
+  pg = history[historyCur].page;
+  update(pg, scrollX, continuousMode ? -1 : scrollY,
+	 zoom, rotate, gFalse, gFalse);
+  return gTrue;
+}
+
+void PDFCore::scrollLeft(int nCols) {
+  scrollTo(scrollX - nCols, scrollY);
+}
+
+void PDFCore::scrollRight(int nCols) {
+  scrollTo(scrollX + nCols, scrollY);
+}
+
+void PDFCore::scrollUp(int nLines) {
+  scrollTo(scrollX, scrollY - nLines);
+}
+
+void PDFCore::scrollUpPrevPage(int nLines) {
+  if (!continuousMode && scrollY == 0) {
+    gotoPrevPage(1, gFalse, gTrue);
+  } else {
+    scrollTo(scrollX, scrollY - nLines);
+  }
+}
+
+void PDFCore::scrollDown(int nLines) {
+  scrollTo(scrollX, scrollY + nLines);
+}
+
+void PDFCore::scrollDownNextPage(int nLines) {
+  if (!continuousMode &&
+      scrollY >= ((PDFCorePage *)pages->get(0))->h - drawAreaHeight) {
+    gotoNextPage(1, gTrue);
+  } else {
+    scrollTo(scrollX, scrollY + nLines);
+  }
+}
+
+void PDFCore::scrollPageUp() {
+  if (!continuousMode && scrollY == 0) {
+    gotoPrevPage(1, gFalse, gTrue);
+  } else {
+    scrollTo(scrollX, scrollY - drawAreaHeight);
+  }
+}
+
+void PDFCore::scrollPageDown() {
+  if (!continuousMode &&
+      scrollY >= ((PDFCorePage *)pages->get(0))->h - drawAreaHeight) {
+    gotoNextPage(1, gTrue);
+  } else {
+    scrollTo(scrollX, scrollY + drawAreaHeight);
+  }
+}
+
+void PDFCore::scrollTo(int x, int y) {
+  update(topPage, x, y < 0 ? 0 : y, zoom, rotate, gFalse, gFalse);
+}
+
+void PDFCore::scrollToLeftEdge() {
+  update(topPage, 0, scrollY, zoom, rotate, gFalse, gFalse);
+}
+
+void PDFCore::scrollToRightEdge() {
+  PDFCorePage *page;
+
+  page = (PDFCorePage *)pages->get(0);
+  update(topPage, page->w - drawAreaWidth, scrollY,
+	 zoom, rotate, gFalse, gFalse);
+}
+
+void PDFCore::scrollToTopEdge() {
+  int y;
+
+  y = continuousMode ? pageY[topPage - 1] : 0;
+  update(topPage, scrollX, y, zoom, rotate, gFalse, gFalse);
+}
+
+void PDFCore::scrollToBottomEdge() {
+  PDFCorePage *page;
+  int y, i;
+
+  for (i = pages->getLength() - 1; i > 0; --i) {
+    page = (PDFCorePage *)pages->get(i);
+    if (page->yDest < drawAreaHeight) {
+      break;
+    }
+  }
+  page = (PDFCorePage *)pages->get(i);
+  if (continuousMode) {
+    y = pageY[page->page - 1] + page->h - drawAreaHeight;
+  } else {
+    y = page->h - drawAreaHeight;
+  }
+  update(topPage, scrollX, y, zoom, rotate, gFalse, gFalse);
+}
+
+void PDFCore::scrollToTopLeft() {
+  int y;
+
+  y = continuousMode ? pageY[topPage - 1] : 0;
+  update(topPage, 0, y, zoom, rotate, gFalse, gFalse);
+}
+
+void PDFCore::scrollToBottomRight() {
+  PDFCorePage *page;
+  int x, y, i;
+
+  for (i = pages->getLength() - 1; i > 0; --i) {
+    page = (PDFCorePage *)pages->get(i);
+    if (page->yDest < drawAreaHeight) {
+      break;
+    }
+  }
+  page = (PDFCorePage *)pages->get(i);
+  x = page->w - drawAreaWidth;
+  if (continuousMode) {
+    y = pageY[page->page - 1] + page->h - drawAreaHeight;
+  } else {
+    y = page->h - drawAreaHeight;
+  }
+  update(topPage, x, y, zoom, rotate, gFalse, gFalse);
+}
+
+void PDFCore::zoomToRect(int pg, double ulx, double uly,
+			 double lrx, double lry) {
+  int x0, y0, x1, y1, u, sx, sy;
+  double rx, ry, newZoom, t;
+  PDFCorePage *p;
+
+  cvtUserToDev(pg, ulx, uly, &x0, &y0);
+  cvtUserToDev(pg, lrx, lry, &x1, &y1);
+  if (x0 > x1) {
+    u = x0; x0 = x1; x1 = u;
+  }
+  if (y0 > y1) {
+    u = y0; y0 = y1; y1 = u;
+  }
+  rx = (double)drawAreaWidth / (double)(x1 - x0);
+  ry = (double)drawAreaHeight / (double)(y1 - y0);
+  if (rx < ry) {
+    newZoom = rx * (dpi / (0.01 * 72));
+    sx = (int)(rx * x0);
+    t = (drawAreaHeight * (x1 - x0)) / drawAreaWidth;
+    sy = (int)(rx * (y0 + y1 - t) / 2);
+    if (continuousMode) {
+      if ((p = findPage(pg)) && p->w < maxPageW) {
+	sx += (int)(0.5 * rx * (maxPageW - p->w));
+      }
+      u = (pg - 1) * continuousModePageSpacing;
+      sy += (int)(rx * (pageY[pg - 1] - u)) + u;
+    }
+  } else {
+    newZoom = ry * (dpi / (0.01 * 72));
+    t = (drawAreaWidth * (y1 - y0)) / drawAreaHeight;
+    sx = (int)(ry * (x0 + x1 - t) / 2);
+    sy = (int)(ry * y0);
+    if (continuousMode) {
+      if ((p = findPage(pg)) && p->w < maxPageW) {
+	sx += (int)(0.5 * rx * (maxPageW - p->w));
+      }
+      u = (pg - 1) * continuousModePageSpacing;
+      sy += (int)(ry * (pageY[pg - 1] - u)) + u;
+    }
+  }
+  update(pg, sx, sy, newZoom, rotate, gFalse, gFalse);
+}
+
+void PDFCore::zoomCentered(double zoomA) {
+  int sx, sy, rot, hAdjust, vAdjust, i;
+  double dpi1, dpi2, pageW, pageH;
+  PDFCorePage *page;
+
+  if (zoomA == zoomPage) {
+    if (continuousMode) {
+      pageW = (rotate == 90 || rotate == 270) ? maxUnscaledPageH
+	                                      : maxUnscaledPageW;
+      pageH = (rotate == 90 || rotate == 270) ? maxUnscaledPageW
+	                                      : maxUnscaledPageH;
+      dpi1 = 72.0 * (double)drawAreaWidth / pageW;
+      dpi2 = 72.0 * (double)(drawAreaHeight - continuousModePageSpacing) /
+	     pageH;
+      if (dpi2 < dpi1) {
+	dpi1 = dpi2;
+      }
+    } else {
+      // in single-page mode, sx=sy=0 -- so dpi1 is irrelevant
+      dpi1 = dpi;
+    }
+    sx = 0;
+
+  } else if (zoomA == zoomWidth) {
+    if (continuousMode) {
+      pageW = (rotate == 90 || rotate == 270) ? maxUnscaledPageH
+	                                      : maxUnscaledPageW;
+    } else {
+      rot = rotate + doc->getPageRotate(topPage);
+      if (rot >= 360) {
+	rot -= 360;
+      } else if (rot < 0) {
+	rot += 360;
+      }
+      pageW = (rot == 90 || rot == 270) ? doc->getPageCropHeight(topPage)
+	                                : doc->getPageCropWidth(topPage);
+    }
+    dpi1 = 72.0 * (double)drawAreaWidth / pageW;
+    sx = 0;
+
+  } else if (zoomA <= 0) {
+    return;
+
+  } else {
+    dpi1 = 72.0 * zoomA / 100.0;
+    if ((page = (PDFCorePage *)pages->get(0)) && page->xDest > 0) {
+      hAdjust = page->xDest;
+    } else {
+      hAdjust = 0;
+    }
+    sx = (int)((scrollX - hAdjust + drawAreaWidth / 2) * (dpi1 / dpi)) -
+         drawAreaWidth / 2;
+    if (sx < 0) {
+      sx = 0;
+    }
+  }
+
+  if (continuousMode) {
+    // we can't just multiply scrollY by dpi1/dpi -- the rounding
+    // errors add up (because the pageY values are integers) -- so
+    // we compute the pageY values at the new zoom level instead
+    sy = 0;
+    for (i = 1; i < topPage; ++i) {
+      rot = rotate + doc->getPageRotate(i);
+      if (rot >= 360) {
+	rot -= 360;
+      } else if (rot < 0) {
+	rot += 360;
+      }
+      if (rot == 90 || rot == 270) {
+	sy += (int)((doc->getPageCropWidth(i) * dpi1) / 72 + 0.5);
+      } else {
+	sy += (int)((doc->getPageCropHeight(i) * dpi1) / 72 + 0.5);
+      }
+    }
+    vAdjust = (topPage - 1) * continuousModePageSpacing;
+    sy = sy + (int)((scrollY - pageY[topPage - 1] + drawAreaHeight / 2)
+		    * (dpi1 / dpi))
+         + vAdjust - drawAreaHeight / 2;
+  } else {
+    sy = (int)((scrollY + drawAreaHeight / 2) * (dpi1 / dpi))
+         - drawAreaHeight / 2;
+  }
+
+  update(topPage, sx, sy, zoomA, rotate, gFalse, gFalse);
+}
+
+// Zoom so that the current page(s) fill the window width.  Maintain
+// the vertical center.
+void PDFCore::zoomToCurrentWidth() {
+  double w, maxW, dpi1;
+  int sx, sy, vAdjust, rot, i;
+
+  // compute the maximum page width of visible pages
+  rot = rotate + doc->getPageRotate(topPage);
+  if (rot >= 360) {
+    rot -= 360;
+  } else if (rot < 0) {
+    rot += 360;
+  }
+  if (rot == 90 || rot == 270) {
+    maxW = doc->getPageCropHeight(topPage);
+  } else {
+    maxW = doc->getPageCropWidth(topPage);
+  }
+  if (continuousMode) {
+    for (i = topPage + 1;
+	 i < doc->getNumPages() && pageY[i-1] < scrollY + drawAreaHeight;
+	 ++i) {
+      rot = rotate + doc->getPageRotate(i);
+      if (rot >= 360) {
+	rot -= 360;
+      } else if (rot < 0) {
+	rot += 360;
+      }
+      if (rot == 90 || rot == 270) {
+	w = doc->getPageCropHeight(i);
+      } else {
+	w = doc->getPageCropWidth(i);
+      }
+      if (w > maxW) {
+	maxW = w;
+      }
+    }
+  }
+
+  // compute the resolution
+  dpi1 = (drawAreaWidth / maxW) * 72;
+
+  // compute the horizontal scroll position
+  if (continuousMode) {
+    sx = ((int)(maxPageW * dpi1 / dpi) - drawAreaWidth) / 2;
+  } else {
+    sx = 0;
+  }
+
+  // compute the vertical scroll position
+  if (continuousMode) {
+    // we can't just multiply scrollY by dpi1/dpi -- the rounding
+    // errors add up (because the pageY values are integers) -- so
+    // we compute the pageY values at the new zoom level instead
+    sy = 0;
+    for (i = 1; i < topPage; ++i) {
+      rot = rotate + doc->getPageRotate(i);
+      if (rot >= 360) {
+	rot -= 360;
+      } else if (rot < 0) {
+	rot += 360;
+      }
+      if (rot == 90 || rot == 270) {
+	sy += (int)((doc->getPageCropWidth(i) * dpi1) / 72 + 0.5);
+      } else {
+	sy += (int)((doc->getPageCropHeight(i) * dpi1) / 72 + 0.5);
+      }
+    }
+    vAdjust = (topPage - 1) * continuousModePageSpacing;
+    sy = sy + (int)((scrollY - pageY[topPage - 1] + drawAreaHeight / 2)
+		    * (dpi1 / dpi))
+         + vAdjust - drawAreaHeight / 2;
+  } else {
+    sy = (int)((scrollY + drawAreaHeight / 2) * (dpi1 / dpi))
+         - drawAreaHeight / 2;
+  }
+
+  update(topPage, sx, sy, (dpi1 * 100) / 72, rotate, gFalse, gFalse);
+}
+
+void PDFCore::setContinuousMode(GBool cm) {
+  if (continuousMode != cm) {
+    continuousMode = cm;
+    update(topPage, scrollX, -1, zoom, rotate, gTrue, gFalse);
+  }
+}
+
+void PDFCore::setSelection(int newSelectPage,
+			   int newSelectULX, int newSelectULY,
+			   int newSelectLRX, int newSelectLRY) {
+  int x0, y0, x1, y1, py;
+  GBool haveSel, newHaveSel;
+  GBool needRedraw, needScroll;
+  GBool moveLeft, moveRight, moveTop, moveBottom;
+  SplashColor xorColor;
+  PDFCorePage *page;
+
+
+  haveSel = selectULX != selectLRX && selectULY != selectLRY;
+  newHaveSel = newSelectULX != newSelectLRX && newSelectULY != newSelectLRY;
+
+  // erase old selection on off-screen bitmap
+  needRedraw = gFalse;
+  if (haveSel) {
+    xorColor[0] = xorColor[1] = xorColor[2] = 0xff;
+    xorRectangle(selectPage, selectULX, selectULY, selectLRX, selectLRY,
+		 new SplashSolidColor(xorColor));
+    needRedraw = gTrue;
+  }
+
+  // draw new selection on off-screen bitmap
+  if (newHaveSel) {
+    xorColor[0] = xorColor[1] = xorColor[2] = 0xff;
+    xorRectangle(newSelectPage, newSelectULX, newSelectULY,
+		 newSelectLRX, newSelectLRY,
+		 new SplashSolidColor(xorColor));
+    needRedraw = gTrue;
+  }
+
+  // check which edges moved
+  if (!haveSel || newSelectPage != selectPage) {
+    moveLeft = moveTop = moveRight = moveBottom = gTrue;
+  } else {
+    moveLeft = newSelectULX != selectULX;
+    moveTop = newSelectULY != selectULY;
+    moveRight = newSelectLRX != selectLRX;
+    moveBottom = newSelectLRY != selectLRY;
+  }
+
+  // redraw currently visible part of bitmap
+  if (needRedraw) {
+    if (!haveSel) {
+      page = findPage(newSelectPage);
+      x0 = newSelectULX;
+      y0 = newSelectULY;
+      x1 = newSelectLRX;
+      y1 = newSelectLRY;
+      redrawWindow(page->xDest + x0, page->yDest + y0,
+		   x1 - x0 + 1, y1 - y0 + 1, gFalse);
+    } else if (!newHaveSel) {
+      if ((page = findPage(selectPage))) {
+	x0 = selectULX;
+	y0 = selectULY;
+	x1 = selectLRX;
+	y1 = selectLRY;
+	redrawWindow(page->xDest + x0, page->yDest + y0,
+		     x1 - x0 + 1, y1 - y0 + 1, gFalse);
+      }
+    } else {
+      page = findPage(newSelectPage);
+      if (moveLeft) {
+	x0 = newSelectULX < selectULX ? newSelectULX : selectULX;
+	y0 = newSelectULY < selectULY ? newSelectULY : selectULY;
+	x1 = newSelectULX > selectULX ? newSelectULX : selectULX;
+	y1 = newSelectLRY > selectLRY ? newSelectLRY : selectLRY;
+	redrawWindow(page->xDest + x0, page->yDest + y0,
+		     x1 - x0 + 1, y1 - y0 + 1, gFalse);
+      }
+      if (moveRight) {
+	x0 = newSelectLRX < selectLRX ? newSelectLRX : selectLRX;
+	y0 = newSelectULY < selectULY ? newSelectULY : selectULY;
+	x1 = newSelectLRX > selectLRX ? newSelectLRX : selectLRX;
+	y1 = newSelectLRY > selectLRY ? newSelectLRY : selectLRY;
+	redrawWindow(page->xDest + x0, page->yDest + y0,
+		     x1 - x0 + 1, y1 - y0 + 1, gFalse);
+      }
+      if (moveTop) {
+	x0 = newSelectULX < selectULX ? newSelectULX : selectULX;
+	y0 = newSelectULY < selectULY ? newSelectULY : selectULY;
+	x1 = newSelectLRX > selectLRX ? newSelectLRX : selectLRX;
+	y1 = newSelectULY > selectULY ? newSelectULY : selectULY;
+	redrawWindow(page->xDest + x0, page->yDest + y0,
+		     x1 - x0 + 1, y1 - y0 + 1, gFalse);
+      }
+      if (moveBottom) {
+	x0 = newSelectULX < selectULX ? newSelectULX : selectULX;
+	y0 = newSelectLRY < selectLRY ? newSelectLRY : selectLRY;
+	x1 = newSelectLRX > selectLRX ? newSelectLRX : selectLRX;
+	y1 = newSelectLRY > selectLRY ? newSelectLRY : selectLRY;
+	redrawWindow(page->xDest + x0, page->yDest + y0,
+		     x1 - x0 + 1, y1 - y0 + 1, gFalse);
+      }
+    }
+  }
+
+  // switch to new selection coords
+  selectPage = newSelectPage;
+  selectULX = newSelectULX;
+  selectULY = newSelectULY;
+  selectLRX = newSelectLRX;
+  selectLRY = newSelectLRY;
+
+  // scroll if necessary
+  if (newHaveSel) {
+    page = findPage(selectPage);
+    needScroll = gFalse;
+    x0 = scrollX;
+    y0 = scrollY;
+    if (moveLeft && page->xDest + selectULX < 0) {
+      x0 += page->xDest + selectULX;
+      needScroll = gTrue;
+    } else if (moveRight && page->xDest + selectLRX >= drawAreaWidth) {
+      x0 += page->xDest + selectLRX - drawAreaWidth;
+      needScroll = gTrue;
+    } else if (moveLeft && page->xDest + selectULX >= drawAreaWidth) {
+      x0 += page->xDest + selectULX - drawAreaWidth;
+      needScroll = gTrue;
+    } else if (moveRight && page->xDest + selectLRX < 0) {
+      x0 += page->xDest + selectLRX;
+      needScroll = gTrue;
+    }
+    py = continuousMode ? pageY[selectPage - 1] : 0;
+    if (moveTop && py + selectULY < y0) {
+      y0 = py + selectULY;
+      needScroll = gTrue;
+    } else if (moveBottom && py + selectLRY >= y0 + drawAreaHeight) {
+      y0 = py + selectLRY - drawAreaHeight;
+      needScroll = gTrue;
+    } else if (moveTop && py + selectULY >= y0 + drawAreaHeight) {
+      y0 = py + selectULY - drawAreaHeight;
+      needScroll = gTrue;
+    } else if (moveBottom && py + selectLRY < y0) {
+      y0 = py + selectLRY;
+      needScroll = gTrue;
+    }
+    if (needScroll) {
+      scrollTo(x0, y0);
+    }
+  }
+}
+
+void PDFCore::moveSelection(int pg, int x, int y) {
+  int newSelectULX, newSelectULY, newSelectLRX, newSelectLRY;
+
+  // don't allow selections to span multiple pages
+  if (pg != selectPage) {
+    return;
+  }
+
+  // move appropriate edges of selection
+  if (lastDragLeft) {
+    if (x < selectLRX) {
+      newSelectULX = x;
+      newSelectLRX = selectLRX;
+    } else {
+      newSelectULX = selectLRX;
+      newSelectLRX = x;
+      lastDragLeft = gFalse;
+    }
+  } else {
+    if (x > selectULX) {
+      newSelectULX = selectULX;
+      newSelectLRX = x;
+    } else {
+      newSelectULX = x;
+      newSelectLRX = selectULX;
+      lastDragLeft = gTrue;
+    }
+  }
+  if (lastDragTop) {
+    if (y < selectLRY) {
+      newSelectULY = y;
+      newSelectLRY = selectLRY;
+    } else {
+      newSelectULY = selectLRY;
+      newSelectLRY = y;
+      lastDragTop = gFalse;
+    }
+  } else {
+    if (y > selectULY) {
+      newSelectULY = selectULY;
+      newSelectLRY = y;
+    } else {
+      newSelectULY = y;
+      newSelectLRY = selectULY;
+      lastDragTop = gTrue;
+    }
+  }
+
+  // redraw the selection
+  setSelection(selectPage, newSelectULX, newSelectULY,
+	       newSelectLRX, newSelectLRY);
+}
+
+void PDFCore::xorRectangle(int pg, int x0, int y0, int x1, int y1,
+			   SplashPattern *pattern, PDFCoreTile *oneTile) {
+  Splash *splash;
+  SplashPath *path;
+  PDFCorePage *page;
+  PDFCoreTile *tile;
+  SplashCoord xx0, yy0, xx1, yy1;
+  int xi, yi, wi, hi;
+  int i;
+
+  if ((page = findPage(pg))) {
+    for (i = 0; i < page->tiles->getLength(); ++i) {
+      tile = (PDFCoreTile *)page->tiles->get(i);
+      if (!oneTile || tile == oneTile) {
+	splash = new Splash(tile->bitmap, gFalse);
+	splash->setFillPattern(pattern->copy());
+	xx0 = (SplashCoord)(x0 - tile->xMin);
+	yy0 = (SplashCoord)(y0 - tile->yMin);
+	xx1 = (SplashCoord)(x1 - tile->xMin);
+	yy1 = (SplashCoord)(y1 - tile->yMin);
+	path = new SplashPath();
+	path->moveTo(xx0, yy0);
+	path->lineTo(xx1, yy0);
+	path->lineTo(xx1, yy1);
+	path->lineTo(xx0, yy1);
+	path->close();
+	splash->xorFill(path, gTrue);
+	delete path;
+	delete splash;
+	xi = x0 - tile->xMin;
+	wi = x1 - x0;
+	if (xi < 0) {
+	  wi += xi;
+	  xi = 0;
+	}
+	if (xi + wi > tile->bitmap->getWidth()) {
+	  wi = tile->bitmap->getWidth() - xi;
+	}
+	yi = y0 - tile->yMin;
+	hi = y1 - y0;
+	if (yi < 0) {
+	  hi += yi;
+	  yi = 0;
+	}
+	if (yi + hi > tile->bitmap->getHeight()) {
+	  hi = tile->bitmap->getHeight() - yi;
+	}
+	updateTileData(tile, xi, yi, wi, hi, gTrue);
+      }
+    }
+  }
+  delete pattern;
+}
+
+GBool PDFCore::getSelection(int *pg, double *ulx, double *uly,
+			    double *lrx, double *lry) {
+  if (selectULX == selectLRX || selectULY == selectLRY) {
+    return gFalse;
+  }
+  *pg = selectPage;
+  cvtDevToUser(selectPage, selectULX, selectULY, ulx, uly);
+  cvtDevToUser(selectPage, selectLRX, selectLRY, lrx, lry);
+  return gTrue;
+}
+
+GString *PDFCore::extractText(int pg, double xMin, double yMin,
+			      double xMax, double yMax) {
+  PDFCorePage *page;
+  TextOutputDev *textOut;
+  int x0, y0, x1, y1, t;
+  GString *s;
+
+  if (!doc->okToCopy()) {
+    return NULL;
+  }
+  if ((page = findPage(pg))) {
+    cvtUserToDev(pg, xMin, yMin, &x0, &y0);
+    cvtUserToDev(pg, xMax, yMax, &x1, &y1);
+    if (x0 > x1) {
+      t = x0; x0 = x1; x1 = t;
+    }
+    if (y0 > y1) {
+      t = y0; y0 = y1; y1 = t;
+    }
+    s = page->text->getText(x0, y0, x1, y1);
+  } else {
+    textOut = new TextOutputDev(NULL, gTrue, gFalse, gFalse);
+    if (textOut->isOk()) {
+      doc->displayPage(textOut, pg, dpi, dpi, rotate, gFalse, gTrue, gFalse);
+      textOut->cvtUserToDev(xMin, yMin, &x0, &y0);
+      textOut->cvtUserToDev(xMax, yMax, &x1, &y1);
+      if (x0 > x1) {
+	t = x0; x0 = x1; x1 = t;
+      }
+      if (y0 > y1) {
+	t = y0; y0 = y1; y1 = t;
+      }
+      s = textOut->getText(x0, y0, x1, y1);
+    } else {
+      s = new GString();
+    }
+    delete textOut;
+  }
+  return s;
+}
+
+GBool PDFCore::find(char *s, GBool caseSensitive, GBool next, GBool backward,
+		    GBool onePageOnly) {
+  Unicode *u;
+  int len, i;
+  GBool ret;
+
+  // convert to Unicode
+  len = strlen(s);
+  u = (Unicode *)gmallocn(len, sizeof(Unicode));
+  for (i = 0; i < len; ++i) {
+    u[i] = (Unicode)(s[i] & 0xff);
+  }
+
+  ret = findU(u, len, caseSensitive, next, backward, onePageOnly);
+
+  gfree(u);
+  return ret;
+}
+
+GBool PDFCore::findU(Unicode *u, int len, GBool caseSensitive,
+		     GBool next, GBool backward, GBool onePageOnly) {
+  TextOutputDev *textOut;
+  double xMin, yMin, xMax, yMax;
+  PDFCorePage *page;
+  PDFCoreTile *tile;
+  int pg;
+  GBool startAtTop, startAtLast, stopAtLast;
+
+  // check for zero-length string
+  if (len == 0) {
+    return gFalse;
+  }
+
+  setBusyCursor(gTrue);
+
+  // search current page starting at previous result, current
+  // selection, or top/bottom of page
+  startAtTop = startAtLast = gFalse;
+  xMin = yMin = xMax = yMax = 0;
+  pg = topPage;
+  if (next) {
+    startAtLast = gTrue;
+  } else if (selectULX != selectLRX && selectULY != selectLRY) {
+    pg = selectPage;
+    if (backward) {
+      xMin = selectULX - 1;
+      yMin = selectULY - 1;
+    } else {
+      xMin = selectULX + 1;
+      yMin = selectULY + 1;
+    }
+  } else {
+    startAtTop = gTrue;
+  }
+  if (!(page = findPage(pg))) {
+    displayPage(pg, zoom, rotate, gTrue, gFalse);
+    page = findPage(pg);
+  }
+  if (page->text->findText(u, len, startAtTop, gTrue, startAtLast, gFalse,
+			   caseSensitive, backward,
+			   &xMin, &yMin, &xMax, &yMax)) {
+    goto found;
+  }
+
+  if (!onePageOnly) {
+
+    // search following/previous pages
+    textOut = new TextOutputDev(NULL, gTrue, gFalse, gFalse);
+    if (!textOut->isOk()) {
+      delete textOut;
+      goto notFound;
+    }
+    for (pg = backward ? pg - 1 : pg + 1;
+	 backward ? pg >= 1 : pg <= doc->getNumPages();
+	 pg += backward ? -1 : 1) {
+      doc->displayPage(textOut, pg, 72, 72, 0, gFalse, gTrue, gFalse);
+      if (textOut->findText(u, len, gTrue, gTrue, gFalse, gFalse,
+			    caseSensitive, backward,
+			    &xMin, &yMin, &xMax, &yMax)) {
+	delete textOut;
+	goto foundPage;
+      }
+    }
+
+    // search previous/following pages
+    for (pg = backward ? doc->getNumPages() : 1;
+	 backward ? pg > topPage : pg < topPage;
+	 pg += backward ? -1 : 1) {
+      doc->displayPage(textOut, pg, 72, 72, 0, gFalse, gTrue, gFalse);
+      if (textOut->findText(u, len, gTrue, gTrue, gFalse, gFalse,
+			    caseSensitive, backward,
+			    &xMin, &yMin, &xMax, &yMax)) {
+	delete textOut;
+	goto foundPage;
+      }
+    }
+    delete textOut;
+
+  }
+
+  // search current page ending at previous result, current selection,
+  // or bottom/top of page
+  if (!startAtTop) {
+    xMin = yMin = xMax = yMax = 0;
+    if (next) {
+      stopAtLast = gTrue;
+    } else {
+      stopAtLast = gFalse;
+      xMax = selectLRX;
+      yMax = selectLRY;
+    }
+    if (page->text->findText(u, len, gTrue, gFalse, gFalse, stopAtLast,
+			     caseSensitive, backward,
+			     &xMin, &yMin, &xMax, &yMax)) {
+      goto found;
+    }
+  }
+
+  // not found
+ notFound:
+  setBusyCursor(gFalse);
+  return gFalse;
+
+  // found on a different page
+ foundPage:
+  update(pg, scrollX, continuousMode ? -1 : 0, zoom, rotate, gFalse, gTrue);
+  page = findPage(pg);
+  if (!page->text->findText(u, len, gTrue, gTrue, gFalse, gFalse,
+			    caseSensitive, backward,
+			    &xMin, &yMin, &xMax, &yMax)) {
+    // this can happen if coalescing is bad
+    goto notFound;
+  }
+
+  // found: change the selection
+ found:
+  tile = (PDFCoreTile *)page->tiles->get(0);
+  setSelection(pg, (int)floor(xMin), (int)floor(yMin),
+	       (int)ceil(xMax), (int)ceil(yMax));
+
+  setBusyCursor(gFalse);
+  return gTrue;
+}
+
+
+GBool PDFCore::cvtWindowToUser(int xw, int yw,
+			       int *pg, double *xu, double *yu) {
+  PDFCorePage *page;
+  PDFCoreTile *tile;
+  int i;
+
+  for (i = 0; i < pages->getLength(); ++i) {
+    page = (PDFCorePage *)pages->get(i);
+    if (xw >= page->xDest && xw < page->xDest + page->w &&
+	yw >= page->yDest && yw < page->yDest + page->h) {
+      tile = (PDFCoreTile *)page->tiles->get(0);
+      *pg = page->page;
+      xw -= tile->xDest;
+      yw -= tile->yDest;
+      *xu = tile->ictm[0] * xw + tile->ictm[2] * yw + tile->ictm[4];
+      *yu = tile->ictm[1] * xw + tile->ictm[3] * yw + tile->ictm[5];
+      return gTrue;
+    }
+  }
+  *pg = 0;
+  *xu = *yu = 0;
+  return gFalse;
+}
+
+GBool PDFCore::cvtWindowToDev(int xw, int yw, int *pg, int *xd, int *yd) {
+  PDFCorePage *page;
+  int i;
+
+  for (i = 0; i < pages->getLength(); ++i) {
+    page = (PDFCorePage *)pages->get(i);
+    if (xw >= page->xDest && xw < page->xDest + page->w &&
+	yw >= page->yDest && yw < page->yDest + page->h) {
+      *pg = page->page;
+      *xd = xw - page->xDest;
+      *yd = yw - page->yDest;
+      return gTrue;
+    }
+  }
+  *pg = 0;
+  *xd = *yd = 0;
+  return gFalse;
+}
+
+void PDFCore::cvtUserToWindow(int pg, double xu, double yu, int *xw, int *yw) {
+  PDFCorePage *page;
+  PDFCoreTile *tile;
+
+  if ((page = findPage(pg)) &&
+      page->tiles->getLength() > 0) {
+    tile = (PDFCoreTile *)page->tiles->get(0);
+  } else if (curTile && curPage->page == pg) {
+    tile = curTile;
+  } else {
+    tile = NULL;
+  }
+  if (tile) {
+    *xw = tile->xDest + (int)(tile->ctm[0] * xu + tile->ctm[2] * yu +
+			      tile->ctm[4] + 0.5);
+    *yw = tile->yDest + (int)(tile->ctm[1] * xu + tile->ctm[3] * yu +
+			      tile->ctm[5] + 0.5);
+  } else {
+    // this should never happen
+    *xw = *yw = 0;
+  }
+}
+
+void PDFCore::cvtUserToDev(int pg, double xu, double yu, int *xd, int *yd) {
+  PDFCorePage *page;
+  PDFCoreTile *tile;
+  double ctm[6];
+
+  if ((page = findPage(pg)) &&
+      page->tiles->getLength() > 0) {
+    tile = (PDFCoreTile *)page->tiles->get(0);
+  } else if (curTile && curPage->page == pg) {
+    tile = curTile;
+  } else {
+    tile = NULL;
+  }
+  if (tile) {
+    *xd = (int)(tile->xMin + tile->ctm[0] * xu +
+		tile->ctm[2] * yu + tile->ctm[4] + 0.5);
+    *yd = (int)(tile->yMin + tile->ctm[1] * xu +
+		tile->ctm[3] * yu + tile->ctm[5] + 0.5);
+  } else {
+    doc->getCatalog()->getPage(pg)->getDefaultCTM(ctm, dpi, dpi, rotate,
+						  gFalse, out->upsideDown());
+    *xd = (int)(ctm[0] * xu + ctm[2] * yu + ctm[4] + 0.5);
+    *yd = (int)(ctm[1] * xu + ctm[3] * yu + ctm[5] + 0.5);
+  }
+}
+
+void PDFCore::cvtDevToWindow(int pg, int xd, int yd, int *xw, int *yw) {
+  PDFCorePage *page;
+
+  if ((page = findPage(pg))) {
+    *xw = page->xDest + xd;
+    *yw = page->yDest + yd;
+  } else {
+    // this should never happen
+    *xw = *yw = 0;
+  }
+}
+
+void PDFCore::cvtDevToUser(int pg, int xd, int yd, double *xu, double *yu) {
+  PDFCorePage *page;
+  PDFCoreTile *tile;
+
+  if ((page = findPage(pg)) &&
+      page->tiles->getLength() > 0) {
+    tile = (PDFCoreTile *)page->tiles->get(0);
+  } else if (curTile && curPage->page == pg) {
+    tile = curTile;
+  } else {
+    tile = NULL;
+  }
+  if (tile) {
+    xd -= tile->xMin;
+    yd -= tile->yMin;
+    *xu = tile->ictm[0] * xd + tile->ictm[2] * yd + tile->ictm[4];
+    *yu = tile->ictm[1] * xd + tile->ictm[3] * yd + tile->ictm[5];
+  } else {
+    // this should never happen
+    *xu = *yu = 0;
+  }
+}
+
+void PDFCore::setReverseVideo(GBool reverseVideoA) {
+  out->setReverseVideo(reverseVideoA);
+  update(topPage, scrollX, scrollY, zoom, rotate, gTrue, gFalse);
+}
+
+LinkAction *PDFCore::findLink(int pg, double x, double y) {
+  PDFCorePage *page;
+
+  if ((page = findPage(pg))) {
+    return page->links ? page->links->find(x, y) : (LinkAction *)NULL;
+  }
+  return NULL;
+}
+
+PDFCorePage *PDFCore::findPage(int pg) {
+  PDFCorePage *page;
+  int i;
+
+  for (i = 0; i < pages->getLength(); ++i) {
+    page = (PDFCorePage *)pages->get(i);
+    if (page->page == pg) {
+      return page;
+    }
+  }
+  return NULL;
+}
+
+void PDFCore::redrawCbk(void *data, int x0, int y0, int x1, int y1,
+			GBool composited) {
+  PDFCore *core = (PDFCore *)data;
+
+  core->curTile->bitmap = core->out->getBitmap();
+
+  // the default CTM is set by the Gfx constructor; tile->ctm is
+  // needed by the coordinate conversion functions (which may be
+  // called during redraw)
+  memcpy(core->curTile->ctm, core->out->getDefCTM(), 6 * sizeof(double));
+  memcpy(core->curTile->ictm, core->out->getDefICTM(), 6 * sizeof(double));
+
+  // the bitmap created by Gfx and SplashOutputDev can be a slightly
+  // different size due to rounding errors
+  if (x1 >= core->curTile->xMax) {
+    x1 = core->curTile->xMax - 1;
+  }
+  if (y1 >= core->curTile->yMax) {
+    y1 = core->curTile->yMax - 1;
+  }
+
+  core->clippedRedrawRect(core->curTile, x0, y0,
+			  core->curTile->xDest + x0, core->curTile->yDest + y0,
+			  x1 - x0 + 1, y1 - y0 + 1,
+			  0, 0, core->drawAreaWidth, core->drawAreaHeight,
+			  gTrue, composited);
+}
+
+void PDFCore::redrawWindow(int x, int y, int width, int height,
+			   GBool needUpdate) {
+  PDFCorePage *page;
+  PDFCoreTile *tile;
+  int xDest, yDest, w, i, j;
+
+  if (pages->getLength() == 0) {
+    redrawRect(NULL, 0, 0, x, y, width, height, gTrue);
+    return;
+  }
+
+  for (i = 0; i < pages->getLength(); ++i) {
+    page = (PDFCorePage *)pages->get(i);
+    for (j = 0; j < page->tiles->getLength(); ++j) {
+      tile = (PDFCoreTile *)page->tiles->get(j);
+      if (tile->edges & pdfCoreTileTopEdge) {
+	if (tile->edges & pdfCoreTileLeftEdge) {
+	  xDest = 0;
+	} else {
+	  xDest = tile->xDest;
+	}
+	if (tile->edges & pdfCoreTileRightEdge) {
+	  w = drawAreaWidth - xDest;
+	} else {
+	  w = tile->xDest + (tile->xMax - tile->xMin) - xDest;
+	}
+	clippedRedrawRect(NULL, 0, 0,
+			  xDest, 0, w, tile->yDest,
+			  x, y, width, height, gFalse);
+      }
+      if (tile->edges & pdfCoreTileBottomEdge) {
+	if (tile->edges & pdfCoreTileLeftEdge) {
+	  xDest = 0;
+	} else {
+	  xDest = tile->xDest;
+	}
+	if (tile->edges & pdfCoreTileRightEdge) {
+	  w = drawAreaWidth - xDest;
+	} else {
+	  w = tile->xDest + (tile->xMax - tile->xMin) - xDest;
+	}
+	yDest = tile->yDest + (tile->yMax - tile->yMin);
+	clippedRedrawRect(NULL, 0, 0,
+			  xDest, yDest, w, drawAreaHeight - yDest,
+			  x, y, width, height, gFalse);
+      } else if ((tile->edges & pdfCoreTileBottomSpace) &&
+		 i+1 < pages->getLength()) {
+	if (tile->edges & pdfCoreTileLeftEdge) {
+	  xDest = 0;
+	} else {
+	  xDest = tile->xDest;
+	}
+	if (tile->edges & pdfCoreTileRightEdge) {
+	  w = drawAreaWidth - xDest;
+	} else {
+	  w = tile->xDest + (tile->xMax - tile->xMin) - xDest;
+	}
+	yDest = tile->yDest + (tile->yMax - tile->yMin);
+	clippedRedrawRect(NULL, 0, 0,
+			  xDest, yDest,
+			  w, ((PDFCorePage *)pages->get(i+1))->yDest - yDest,
+			  x, y, width, height, gFalse);
+      }
+      if (tile->edges & pdfCoreTileLeftEdge) {
+	clippedRedrawRect(NULL, 0, 0,
+			  0, tile->yDest,
+			  tile->xDest, tile->yMax - tile->yMin,
+			  x, y, width, height, gFalse);
+      }
+      if (tile->edges & pdfCoreTileRightEdge) {
+	xDest = tile->xDest + (tile->xMax - tile->xMin);
+	clippedRedrawRect(NULL, 0, 0,
+			  xDest, tile->yDest,
+			  drawAreaWidth - xDest, tile->yMax - tile->yMin,
+			  x, y, width, height, gFalse);
+      }
+      clippedRedrawRect(tile, 0, 0, tile->xDest, tile->yDest,
+			tile->bitmap->getWidth(), tile->bitmap->getHeight(),
+			x, y, width, height, needUpdate);
+    }
+  }
+}
+
+PDFCoreTile *PDFCore::newTile(int xDestA, int yDestA) {
+  return new PDFCoreTile(xDestA, yDestA);
+}
+
+void PDFCore::updateTileData(PDFCoreTile *tileA, int xSrc, int ySrc,
+			     int width, int height, GBool composited) {
+}
+
+void PDFCore::clippedRedrawRect(PDFCoreTile *tile, int xSrc, int ySrc,
+				int xDest, int yDest, int width, int height,
+				int xClip, int yClip, int wClip, int hClip,
+				GBool needUpdate, GBool composited) {
+  if (tile && needUpdate) {
+    updateTileData(tile, xSrc, ySrc, width, height, composited);
+  }
+  if (xDest < xClip) {
+    xSrc += xClip - xDest;
+    width -= xClip - xDest;
+    xDest = xClip;
+  }
+  if (xDest + width > xClip + wClip) {
+    width = xClip + wClip - xDest;
+  }
+  if (yDest < yClip) {
+    ySrc += yClip - yDest;
+    height -= yClip - yDest;
+    yDest = yClip;
+  }
+  if (yDest + height > yClip + hClip) {
+    height = yClip + hClip - yDest;
+  }
+  if (width > 0 && height > 0) {
+    redrawRect(tile, xSrc, ySrc, xDest, yDest, width, height, composited);
+  }
+}
diff --git a/xpdf/PDFCore.h b/xpdf/PDFCore.h
new file mode 100644
--- /dev/null
+++ b/xpdf/PDFCore.h
@@ -0,0 +1,321 @@
+//========================================================================
+//
+// PDFCore.h
+//
+// Copyright 2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef PDFCORE_H
+#define PDFCORE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <stdlib.h>
+#include "SplashTypes.h"
+#include "CharTypes.h"
+
+class GString;
+class GList;
+class SplashBitmap;
+class SplashPattern;
+class BaseStream;
+class PDFDoc;
+class Links;
+class LinkDest;
+class LinkAction;
+class TextPage;
+class HighlightFile;
+class CoreOutputDev;
+class PDFCore;
+
+//------------------------------------------------------------------------
+// zoom factor
+//------------------------------------------------------------------------
+
+#define zoomPage  -1
+#define zoomWidth -2
+#define defZoom   125
+
+//------------------------------------------------------------------------
+
+
+//------------------------------------------------------------------------
+
+// Number of pixels of matte color between pages in continuous mode.
+#define continuousModePageSpacing 3
+
+//------------------------------------------------------------------------
+// PDFCorePage
+//------------------------------------------------------------------------
+
+class PDFCorePage {
+public:
+
+  PDFCorePage(int pageA, int wA, int hA, int tileWA, int tileHA);
+  ~PDFCorePage();
+
+  int page;
+  GList *tiles;			// cached tiles [PDFCoreTile]
+  int xDest, yDest;		// position of upper-left corner
+				//   in the drawing area
+  int w, h;			// size of whole page bitmap
+  int tileW, tileH;		// size of tiles
+  Links *links;			// hyperlinks for this page
+  TextPage *text;		// extracted text
+};
+
+//------------------------------------------------------------------------
+// PDFCoreTile
+//------------------------------------------------------------------------
+
+class PDFCoreTile {
+public:
+
+  PDFCoreTile(int xDestA, int yDestA);
+  virtual ~PDFCoreTile();
+
+  int xMin, yMin, xMax, yMax;
+  int xDest, yDest;
+  Guint edges;
+  SplashBitmap *bitmap;
+  double ctm[6];		// coordinate transform matrix:
+				//   default user space -> device space
+  double ictm[6];		// inverse CTM
+};
+
+#define pdfCoreTileTopEdge      0x01
+#define pdfCoreTileBottomEdge   0x02
+#define pdfCoreTileLeftEdge     0x04
+#define pdfCoreTileRightEdge    0x08
+#define pdfCoreTileTopSpace     0x10
+#define pdfCoreTileBottomSpace  0x20
+
+//------------------------------------------------------------------------
+// PDFHistory
+//------------------------------------------------------------------------
+
+struct PDFHistory {
+  GString *fileName;
+  int page;
+};
+
+#define pdfHistorySize 50
+
+
+//------------------------------------------------------------------------
+// PDFCore
+//------------------------------------------------------------------------
+
+class PDFCore {
+public:
+
+  PDFCore(SplashColorMode colorModeA, int bitmapRowPadA,
+	  GBool reverseVideoA, SplashColorPtr paperColorA,
+	  GBool incrementalUpdate);
+  virtual ~PDFCore();
+
+  //----- loadFile / displayPage / displayDest
+
+  // Load a new file.  Returns pdfOk or error code.
+  virtual int loadFile(GString *fileName, GString *ownerPassword = NULL,
+		       GString *userPassword = NULL);
+
+#ifdef WIN32
+  // Load a new file.  Returns pdfOk or error code.
+  virtual int loadFile(wchar_t *fileName, int fileNameLen,
+		       GString *ownerPassword = NULL,
+		       GString *userPassword = NULL);
+#endif
+
+  // Load a new file, via a Stream instead of a file name.  Returns
+  // pdfOk or error code.
+  virtual int loadFile(BaseStream *stream, GString *ownerPassword = NULL,
+		       GString *userPassword = NULL);
+
+  // Load an already-created PDFDoc object.
+  virtual void loadDoc(PDFDoc *docA);
+
+  // Clear out the current document, if any.
+  virtual void clear();
+
+  // Same as clear(), but returns the PDFDoc object instead of
+  // deleting it.
+  virtual PDFDoc *takeDoc(GBool redraw);
+
+  // Display (or redisplay) the specified page.  If <scrollToTop> is
+  // set, the window is vertically scrolled to the top; otherwise, no
+  // scrolling is done.  If <addToHist> is set, this page change is
+  // added to the history list.
+  virtual void displayPage(int topPageA, double zoomA, int rotateA,
+			   GBool scrollToTop, GBool addToHist);
+
+  // Display a link destination.
+  virtual void displayDest(LinkDest *dest, double zoomA, int rotateA,
+			   GBool addToHist);
+
+  // Update the display, given the specified parameters.
+  virtual void update(int topPageA, int scrollXA, int scrollYA,
+		      double zoomA, int rotateA, GBool force, GBool addToHist);
+
+  //----- page/position changes
+
+  virtual GBool gotoNextPage(int inc, GBool top);
+  virtual GBool gotoPrevPage(int dec, GBool top, GBool bottom);
+  virtual GBool gotoNamedDestination(GString *dest);
+  virtual GBool goForward();
+  virtual GBool goBackward();
+  virtual void scrollLeft(int nCols = 16);
+  virtual void scrollRight(int nCols = 16);
+  virtual void scrollUp(int nLines = 16);
+  virtual void scrollUpPrevPage(int nLines = 16);
+  virtual void scrollDown(int nLines = 16);
+  virtual void scrollDownNextPage(int nLines = 16);
+  virtual void scrollPageUp();
+  virtual void scrollPageDown();
+  virtual void scrollTo(int x, int y);
+  virtual void scrollToLeftEdge();
+  virtual void scrollToRightEdge();
+  virtual void scrollToTopEdge();
+  virtual void scrollToBottomEdge();
+  virtual void scrollToTopLeft();
+  virtual void scrollToBottomRight();
+  virtual void zoomToRect(int pg, double ulx, double uly,
+			  double lrx, double lry);
+  virtual void zoomCentered(double zoomA);
+  virtual void zoomToCurrentWidth();
+  virtual void setContinuousMode(GBool cm);
+
+  //----- selection
+
+  // Current selected region.
+  void setSelection(int newSelectPage,
+		    int newSelectULX, int newSelectULY,
+		    int newSelectLRX, int newSelectLRY);
+  void moveSelection(int pg, int x, int y);
+  GBool getSelection(int *pg, double *ulx, double *uly,
+		     double *lrx, double *lry);
+
+  // Text extraction.
+  GString *extractText(int pg, double xMin, double yMin,
+		       double xMax, double yMax);
+
+  //----- find
+
+  virtual GBool find(char *s, GBool caseSensitive, GBool next, GBool backward,
+		     GBool onePageOnly);
+  virtual GBool findU(Unicode *u, int len, GBool caseSensitive,
+		      GBool next, GBool backward, GBool onePageOnly);
+
+
+  //----- coordinate conversion
+
+  // user space: per-pace, as defined by PDF file; unit = point
+  // device space: (0,0) is upper-left corner of a page; unit = pixel
+  // window space: (0,0) is upper-left corner of drawing area; unit = pixel
+
+  GBool cvtWindowToUser(int xw, int yw, int *pg, double *xu, double *yu);
+  GBool cvtWindowToDev(int xw, int yw, int *pg, int *xd, int *yd);
+  void cvtUserToWindow(int pg, double xy, double yu, int *xw, int *yw);
+  void cvtUserToDev(int pg, double xu, double yu, int *xd, int *yd);
+  void cvtDevToWindow(int pg, int xd, int yd, int *xw, int *yw);
+  void cvtDevToUser(int pg, int xd, int yd, double *xu, double *yu);
+
+  //----- misc access
+
+  PDFDoc *getDoc() { return doc; }
+  int getPageNum() { return topPage; }
+  double getZoom() { return zoom; }
+  double getZoomDPI() { return dpi; }
+  int getRotate() { return rotate; }
+  GBool getContinuousMode() { return continuousMode; }
+  virtual void setReverseVideo(GBool reverseVideoA);
+  GBool canGoBack() { return historyBLen > 1; }
+  GBool canGoForward() { return historyFLen > 0; }
+  int getScrollX() { return scrollX; }
+  int getScrollY() { return scrollY; }
+  int getDrawAreaWidth() { return drawAreaWidth; }
+  int getDrawAreaHeight() { return drawAreaHeight; }
+  virtual void setBusyCursor(GBool busy) = 0;
+  LinkAction *findLink(int pg, double x, double y);
+
+protected:
+
+  int loadFile2(PDFDoc *newDoc);
+  void addPage(int pg, int rot);
+  void needTile(PDFCorePage *page, int x, int y);
+  void xorRectangle(int pg, int x0, int y0, int x1, int y1,
+		    SplashPattern *pattern, PDFCoreTile *oneTile = NULL);
+  int loadHighlightFile(HighlightFile *hf, SplashColorPtr color,
+			SplashColorPtr selectColor, GBool selectable);
+  PDFCorePage *findPage(int pg);
+  static void redrawCbk(void *data, int x0, int y0, int x1, int y1,
+			GBool composited);
+  void redrawWindow(int x, int y, int width, int height,
+		    GBool needUpdate);
+  virtual PDFCoreTile *newTile(int xDestA, int yDestA);
+  virtual void updateTileData(PDFCoreTile *tileA, int xSrc, int ySrc,
+			      int width, int height, GBool composited);
+  virtual void redrawRect(PDFCoreTile *tileA, int xSrc, int ySrc,
+			  int xDest, int yDest, int width, int height,
+			  GBool composited) = 0;
+  void clippedRedrawRect(PDFCoreTile *tile, int xSrc, int ySrc,
+			 int xDest, int yDest, int width, int height,
+			 int xClip, int yClip, int wClip, int hClip,
+			 GBool needUpdate, GBool composited = gTrue);
+  virtual void updateScrollbars() = 0;
+  virtual GBool checkForNewFile() { return gFalse; }
+
+  PDFDoc *doc;			// current PDF file
+  GBool continuousMode;		// false for single-page mode, true for
+				//   continuous mode
+  int drawAreaWidth,		// size of the PDF display area
+      drawAreaHeight;
+  double maxUnscaledPageW,	// maximum unscaled page size
+         maxUnscaledPageH;
+  int maxPageW;			// maximum page width (only used in
+				//   continuous mode)
+  int totalDocH;		// total document height (only used in
+				//   continuous mode)
+  int *pageY;			// top coordinates for each page (only used
+				//   in continuous mode)
+  int topPage;			// page at top of window
+  int scrollX, scrollY;		// offset from top left corner of topPage
+				//   to top left corner of window
+  double zoom;			// current zoom level, in percent of 72 dpi
+  double dpi;			// current zoom level, in DPI
+  int rotate;			// current page rotation
+
+  int selectPage;		// page number of current selection
+  int selectULX,		// coordinates of current selection,
+      selectULY,		//   in device space -- (ULX==LRX || ULY==LRY)
+      selectLRX,		//   means there is no selection
+      selectLRY;
+  GBool dragging;		// set while selection is being dragged
+  GBool lastDragLeft;		// last dragged selection edge was left/right
+  GBool lastDragTop;		// last dragged selection edge was top/bottom
+
+  PDFHistory			// page history queue
+    history[pdfHistorySize];
+  int historyCur;               // currently displayed page
+  int historyBLen;              // number of valid entries backward from
+                                //   current entry
+  int historyFLen;              // number of valid entries forward from
+                                //   current entry
+
+
+  GList *pages;			// cached pages [PDFCorePage]
+  PDFCoreTile *curTile;		// tile currently being rasterized
+  PDFCorePage *curPage;		// page to which curTile belongs
+
+  SplashColor paperColor;
+  CoreOutputDev *out;
+
+  friend class PDFCoreTile;
+};
+
+#endif
diff --git a/xpdf/PDFDoc.cc b/xpdf/PDFDoc.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/PDFDoc.cc
@@ -0,0 +1,402 @@
+//========================================================================
+//
+// PDFDoc.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#ifdef WIN32
+#  include <windows.h>
+#endif
+#include "GString.h"
+#include "config.h"
+#include "GlobalParams.h"
+#include "Page.h"
+#include "Catalog.h"
+#include "Stream.h"
+#include "XRef.h"
+#include "Link.h"
+#include "OutputDev.h"
+#include "Error.h"
+#include "ErrorCodes.h"
+#include "Lexer.h"
+#include "Parser.h"
+#include "SecurityHandler.h"
+#ifndef DISABLE_OUTLINE
+#include "Outline.h"
+#endif
+#include "PDFDoc.h"
+
+//------------------------------------------------------------------------
+
+#define headerSearchSize 1024	// read this many bytes at beginning of
+				//   file to look for '%PDF'
+
+//------------------------------------------------------------------------
+// PDFDoc
+//------------------------------------------------------------------------
+
+PDFDoc::PDFDoc(GString *fileNameA, GString *ownerPassword,
+	       GString *userPassword, void *guiDataA) {
+  Object obj;
+  GString *fileName1, *fileName2;
+
+  ok = gFalse;
+  errCode = errNone;
+
+  guiData = guiDataA;
+
+  file = NULL;
+  str = NULL;
+  xref = NULL;
+  catalog = NULL;
+#ifndef DISABLE_OUTLINE
+  outline = NULL;
+#endif
+
+  fileName = fileNameA;
+  fileName1 = fileName;
+
+
+  // try to open file
+  fileName2 = NULL;
+#ifdef VMS
+  if (!(file = fopen(fileName1->getCString(), "rb", "ctx=stm"))) {
+    error(-1, "Couldn't open file '%s'", fileName1->getCString());
+    errCode = errOpenFile;
+    return;
+  }
+#else
+  if (!(file = fopen(fileName1->getCString(), "rb"))) {
+    fileName2 = fileName->copy();
+    fileName2->lowerCase();
+    if (!(file = fopen(fileName2->getCString(), "rb"))) {
+      fileName2->upperCase();
+      if (!(file = fopen(fileName2->getCString(), "rb"))) {
+	error(-1, "Couldn't open file '%s'", fileName->getCString());
+	delete fileName2;
+	errCode = errOpenFile;
+	return;
+      }
+    }
+    delete fileName2;
+  }
+#endif
+
+  // create stream
+  obj.initNull();
+  str = new FileStream(file, 0, gFalse, 0, &obj);
+
+  ok = setup(ownerPassword, userPassword);
+}
+
+#ifdef WIN32
+PDFDoc::PDFDoc(wchar_t *fileNameA, int fileNameLen, GString *ownerPassword,
+	       GString *userPassword, void *guiDataA) {
+  OSVERSIONINFO version;
+  wchar_t fileName2[_MAX_PATH + 1];
+  Object obj;
+  int i;
+
+  ok = gFalse;
+  errCode = errNone;
+
+  guiData = guiDataA;
+
+  file = NULL;
+  str = NULL;
+  xref = NULL;
+  catalog = NULL;
+#ifndef DISABLE_OUTLINE
+  outline = NULL;
+#endif
+
+  //~ file name should be stored in Unicode (?)
+  fileName = new GString();
+  for (i = 0; i < fileNameLen; ++i) {
+    fileName->append((char)fileNameA[i]);
+  }
+
+  // zero-terminate the file name string
+  for (i = 0; i < fileNameLen && i < _MAX_PATH; ++i) {
+    fileName2[i] = fileNameA[i];
+  }
+  fileName2[i] = 0;
+
+  // try to open file
+  // NB: _wfopen is only available in NT
+  version.dwOSVersionInfoSize = sizeof(version);
+  GetVersionEx(&version);
+  if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) {
+    file = _wfopen(fileName2, L"rb");
+  } else {
+    file = fopen(fileName->getCString(), "rb");
+  }
+  if (!file) {
+    error(-1, "Couldn't open file '%s'", fileName->getCString());
+    errCode = errOpenFile;
+    return;
+  }
+
+  // create stream
+  obj.initNull();
+  str = new FileStream(file, 0, gFalse, 0, &obj);
+
+  ok = setup(ownerPassword, userPassword);
+}
+#endif
+
+PDFDoc::PDFDoc(BaseStream *strA, GString *ownerPassword,
+	       GString *userPassword, void *guiDataA) {
+  ok = gFalse;
+  errCode = errNone;
+  guiData = guiDataA;
+  if (strA->getFileName()) {
+    fileName = strA->getFileName()->copy();
+  } else {
+    fileName = NULL;
+  }
+  file = NULL;
+  str = strA;
+  xref = NULL;
+  catalog = NULL;
+#ifndef DISABLE_OUTLINE
+  outline = NULL;
+#endif
+  ok = setup(ownerPassword, userPassword);
+}
+
+GBool PDFDoc::setup(GString *ownerPassword, GString *userPassword) {
+  str->reset();
+
+  // check header
+  checkHeader();
+
+  // read xref table
+  xref = new XRef(str);
+  if (!xref->isOk()) {
+    error(-1, "Couldn't read xref table");
+    errCode = xref->getErrorCode();
+    return gFalse;
+  }
+
+  // check for encryption
+  if (!checkEncryption(ownerPassword, userPassword)) {
+    errCode = errEncrypted;
+    return gFalse;
+  }
+
+  // read catalog
+  catalog = new Catalog(xref);
+  if (!catalog->isOk()) {
+    error(-1, "Couldn't read page catalog");
+    errCode = errBadCatalog;
+    return gFalse;
+  }
+
+#ifndef DISABLE_OUTLINE
+  // read outline
+  outline = new Outline(catalog->getOutline(), xref);
+#endif
+
+  // done
+  return gTrue;
+}
+
+PDFDoc::~PDFDoc() {
+#ifndef DISABLE_OUTLINE
+  if (outline) {
+    delete outline;
+  }
+#endif
+  if (catalog) {
+    delete catalog;
+  }
+  if (xref) {
+    delete xref;
+  }
+  if (str) {
+    delete str;
+  }
+  if (file) {
+    fclose(file);
+  }
+  if (fileName) {
+    delete fileName;
+  }
+}
+
+// Check for a PDF header on this stream.  Skip past some garbage
+// if necessary.
+void PDFDoc::checkHeader() {
+  char hdrBuf[headerSearchSize+1];
+  char *p;
+  int i;
+
+  pdfVersion = 0;
+  for (i = 0; i < headerSearchSize; ++i) {
+    hdrBuf[i] = str->getChar();
+  }
+  hdrBuf[headerSearchSize] = '\0';
+  for (i = 0; i < headerSearchSize - 5; ++i) {
+    if (!strncmp(&hdrBuf[i], "%PDF-", 5)) {
+      break;
+    }
+  }
+  if (i >= headerSearchSize - 5) {
+    error(-1, "May not be a PDF file (continuing anyway)");
+    return;
+  }
+  str->moveStart(i);
+  if (!(p = strtok(&hdrBuf[i+5], " \t\n\r"))) {
+    error(-1, "May not be a PDF file (continuing anyway)");
+    return;
+  }
+  pdfVersion = atof(p);
+  if (!(hdrBuf[i+5] >= '0' && hdrBuf[i+5] <= '9') ||
+      pdfVersion > supportedPDFVersionNum + 0.0001) {
+    error(-1, "PDF version %s -- xpdf supports version %s"
+	  " (continuing anyway)", p, supportedPDFVersionStr);
+  }
+}
+
+GBool PDFDoc::checkEncryption(GString *ownerPassword, GString *userPassword) {
+  Object encrypt;
+  GBool encrypted;
+  SecurityHandler *secHdlr;
+  GBool ret;
+
+  xref->getTrailerDict()->dictLookup("Encrypt", &encrypt);
+  if ((encrypted = encrypt.isDict())) {
+    if ((secHdlr = SecurityHandler::make(this, &encrypt))) {
+      if (secHdlr->checkEncryption(ownerPassword, userPassword)) {
+	// authorization succeeded
+       	xref->setEncryption(secHdlr->getPermissionFlags(),
+			    secHdlr->getOwnerPasswordOk(),
+			    secHdlr->getFileKey(),
+			    secHdlr->getFileKeyLength(),
+			    secHdlr->getEncVersion(),
+			    secHdlr->getEncAlgorithm());
+	ret = gTrue;
+      } else {
+	// authorization failed
+	ret = gFalse;
+      }
+      delete secHdlr;
+    } else {
+      // couldn't find the matching security handler
+      ret = gFalse;
+    }
+  } else {
+    // document is not encrypted
+    ret = gTrue;
+  }
+  encrypt.free();
+  return ret;
+}
+
+void PDFDoc::displayPage(OutputDev *out, int page,
+			 double hDPI, double vDPI, int rotate,
+			 GBool useMediaBox, GBool crop, GBool printing,
+			 GBool (*abortCheckCbk)(void *data),
+			 void *abortCheckCbkData) {
+  if (globalParams->getPrintCommands()) {
+    printf("***** page %d *****\n", page);
+  }
+  catalog->getPage(page)->display(out, hDPI, vDPI,
+				  rotate, useMediaBox, crop, printing, catalog,
+				  abortCheckCbk, abortCheckCbkData);
+}
+
+void PDFDoc::displayPages(OutputDev *out, int firstPage, int lastPage,
+			  double hDPI, double vDPI, int rotate,
+			  GBool useMediaBox, GBool crop, GBool printing,
+			  GBool (*abortCheckCbk)(void *data),
+			  void *abortCheckCbkData) {
+  int page;
+
+  for (page = firstPage; page <= lastPage; ++page) {
+    displayPage(out, page, hDPI, vDPI, rotate, useMediaBox, crop, printing,
+		abortCheckCbk, abortCheckCbkData);
+  }
+}
+
+void PDFDoc::displayPageSlice(OutputDev *out, int page,
+			      double hDPI, double vDPI, int rotate,
+			      GBool useMediaBox, GBool crop, GBool printing,
+			      int sliceX, int sliceY, int sliceW, int sliceH,
+			      GBool (*abortCheckCbk)(void *data),
+			      void *abortCheckCbkData) {
+  catalog->getPage(page)->displaySlice(out, hDPI, vDPI,
+				       rotate, useMediaBox, crop,
+				       sliceX, sliceY, sliceW, sliceH,
+				       printing, catalog,
+				       abortCheckCbk, abortCheckCbkData);
+}
+
+Links *PDFDoc::getLinks(int page) {
+  return catalog->getPage(page)->getLinks(catalog);
+}
+
+void PDFDoc::processLinks(OutputDev *out, int page) {
+  catalog->getPage(page)->processLinks(out, catalog);
+}
+
+GBool PDFDoc::isLinearized() {
+  Parser *parser;
+  Object obj1, obj2, obj3, obj4, obj5;
+  GBool lin;
+
+  lin = gFalse;
+  obj1.initNull();
+  parser = new Parser(xref,
+	     new Lexer(xref,
+	       str->makeSubStream(str->getStart(), gFalse, 0, &obj1)),
+	     gTrue);
+  parser->getObj(&obj1);
+  parser->getObj(&obj2);
+  parser->getObj(&obj3);
+  parser->getObj(&obj4);
+  if (obj1.isInt() && obj2.isInt() && obj3.isCmd("obj") &&
+      obj4.isDict()) {
+    obj4.dictLookup("Linearized", &obj5);
+    if (obj5.isNum() && obj5.getNum() > 0) {
+      lin = gTrue;
+    }
+    obj5.free();
+  }
+  obj4.free();
+  obj3.free();
+  obj2.free();
+  obj1.free();
+  delete parser;
+  return lin;
+}
+
+GBool PDFDoc::saveAs(GString *name) {
+  FILE *f;
+  int c;
+
+  if (!(f = fopen(name->getCString(), "wb"))) {
+    error(-1, "Couldn't open file '%s'", name->getCString());
+    return gFalse;
+  }
+  str->reset();
+  while ((c = str->getChar()) != EOF) {
+    fputc(c, f);
+  }
+  str->close();
+  fclose(f);
+  return gTrue;
+}
diff --git a/xpdf/PDFDoc.h b/xpdf/PDFDoc.h
new file mode 100644
--- /dev/null
+++ b/xpdf/PDFDoc.h
@@ -0,0 +1,183 @@
+//========================================================================
+//
+// PDFDoc.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef PDFDOC_H
+#define PDFDOC_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <stdio.h>
+#include "XRef.h"
+#include "Catalog.h"
+#include "Page.h"
+
+class GString;
+class BaseStream;
+class OutputDev;
+class Links;
+class LinkAction;
+class LinkDest;
+class Outline;
+
+//------------------------------------------------------------------------
+// PDFDoc
+//------------------------------------------------------------------------
+
+class PDFDoc {
+public:
+
+  PDFDoc(GString *fileNameA, GString *ownerPassword = NULL,
+	 GString *userPassword = NULL, void *guiDataA = NULL);
+#ifdef WIN32
+  PDFDoc(wchar_t *fileNameA, int fileNameLen, GString *ownerPassword = NULL,
+	 GString *userPassword = NULL, void *guiDataA = NULL);
+#endif
+  PDFDoc(BaseStream *strA, GString *ownerPassword = NULL,
+	 GString *userPassword = NULL, void *guiDataA = NULL);
+  ~PDFDoc();
+
+  // Was PDF document successfully opened?
+  GBool isOk() { return ok; }
+
+  // Get the error code (if isOk() returns false).
+  int getErrorCode() { return errCode; }
+
+  // Get file name.
+  GString *getFileName() { return fileName; }
+
+  // Get the xref table.
+  XRef *getXRef() { return xref; }
+
+  // Get catalog.
+  Catalog *getCatalog() { return catalog; }
+
+  // Get base stream.
+  BaseStream *getBaseStream() { return str; }
+
+  // Get page parameters.
+  double getPageMediaWidth(int page)
+    { return catalog->getPage(page)->getMediaWidth(); }
+  double getPageMediaHeight(int page)
+    { return catalog->getPage(page)->getMediaHeight(); }
+  double getPageCropWidth(int page)
+    { return catalog->getPage(page)->getCropWidth(); }
+  double getPageCropHeight(int page)
+    { return catalog->getPage(page)->getCropHeight(); }
+  int getPageRotate(int page)
+    { return catalog->getPage(page)->getRotate(); }
+
+  // Get number of pages.
+  int getNumPages() { return catalog->getNumPages(); }
+
+  // Return the contents of the metadata stream, or NULL if there is
+  // no metadata.
+  GString *readMetadata() { return catalog->readMetadata(); }
+
+  // Return the structure tree root object.
+  Object *getStructTreeRoot() { return catalog->getStructTreeRoot(); }
+
+  // Display a page.
+  void displayPage(OutputDev *out, int page,
+		   double hDPI, double vDPI, int rotate,
+		   GBool useMediaBox, GBool crop, GBool printing,
+		   GBool (*abortCheckCbk)(void *data) = NULL,
+		   void *abortCheckCbkData = NULL);
+
+  // Display a range of pages.
+  void displayPages(OutputDev *out, int firstPage, int lastPage,
+		    double hDPI, double vDPI, int rotate,
+		    GBool useMediaBox, GBool crop, GBool printing,
+		    GBool (*abortCheckCbk)(void *data) = NULL,
+		    void *abortCheckCbkData = NULL);
+
+  // Display part of a page.
+  void displayPageSlice(OutputDev *out, int page,
+			double hDPI, double vDPI, int rotate,
+			GBool useMediaBox, GBool crop, GBool printing,
+			int sliceX, int sliceY, int sliceW, int sliceH,
+			GBool (*abortCheckCbk)(void *data) = NULL,
+			void *abortCheckCbkData = NULL);
+
+  // Find a page, given its object ID.  Returns page number, or 0 if
+  // not found.
+  int findPage(int num, int gen) { return catalog->findPage(num, gen); }
+
+  // Returns the links for the current page, transferring ownership to
+  // the caller.
+  Links *getLinks(int page);
+
+  // Find a named destination.  Returns the link destination, or
+  // NULL if <name> is not a destination.
+  LinkDest *findDest(GString *name)
+    { return catalog->findDest(name); }
+
+  // Process the links for a page.
+  void processLinks(OutputDev *out, int page);
+
+#ifndef DISABLE_OUTLINE
+  // Return the outline object.
+  Outline *getOutline() { return outline; }
+#endif
+
+  // Is the file encrypted?
+  GBool isEncrypted() { return xref->isEncrypted(); }
+
+  // Check various permissions.
+  GBool okToPrint(GBool ignoreOwnerPW = gFalse)
+    { return xref->okToPrint(ignoreOwnerPW); }
+  GBool okToChange(GBool ignoreOwnerPW = gFalse)
+    { return xref->okToChange(ignoreOwnerPW); }
+  GBool okToCopy(GBool ignoreOwnerPW = gFalse)
+    { return xref->okToCopy(ignoreOwnerPW); }
+  GBool okToAddNotes(GBool ignoreOwnerPW = gFalse)
+    { return xref->okToAddNotes(ignoreOwnerPW); }
+
+  // Is this document linearized?
+  GBool isLinearized();
+
+  // Return the document's Info dictionary (if any).
+  Object *getDocInfo(Object *obj) { return xref->getDocInfo(obj); }
+  Object *getDocInfoNF(Object *obj) { return xref->getDocInfoNF(obj); }
+
+  // Return the PDF version specified by the file.
+  double getPDFVersion() { return pdfVersion; }
+
+  // Save this file with another name.
+  GBool saveAs(GString *name);
+
+  // Return a pointer to the GUI (XPDFCore or WinPDFCore object).
+  void *getGUIData() { return guiData; }
+
+
+private:
+
+  GBool setup(GString *ownerPassword, GString *userPassword);
+  void checkHeader();
+  GBool checkEncryption(GString *ownerPassword, GString *userPassword);
+
+  GString *fileName;
+  FILE *file;
+  BaseStream *str;
+  void *guiData;
+  double pdfVersion;
+  XRef *xref;
+  Catalog *catalog;
+#ifndef DISABLE_OUTLINE
+  Outline *outline;
+#endif
+
+
+  GBool ok;
+  int errCode;
+};
+
+#endif
diff --git a/xpdf/PDFDocEncoding.cc b/xpdf/PDFDocEncoding.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/PDFDocEncoding.cc
@@ -0,0 +1,44 @@
+//========================================================================
+//
+// PDFDocEncoding.h
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include "PDFDocEncoding.h"
+
+Unicode pdfDocEncoding[256] = {
+  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 00
+  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
+  0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, // 10
+  0x02d8, 0x02c7, 0x02c6, 0x02d9, 0x02dd, 0x02db, 0x02da, 0x02dc,
+  0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, // 20
+  0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+  0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, // 30
+  0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+  0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, // 40
+  0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+  0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, // 50
+  0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+  0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, // 60
+  0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+  0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, // 70
+  0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x0000,
+  0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x0192, 0x2044, // 80
+  0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018,
+  0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x0141, 0x0152, 0x0160, // 90
+  0x0178, 0x017d, 0x0131, 0x0142, 0x0153, 0x0161, 0x017e, 0x0000,
+  0x20ac, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7, // a0
+  0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x0000, 0x00ae, 0x00af,
+  0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7, // b0
+  0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+  0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, // c0
+  0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+  0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7, // d0
+  0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+  0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7, // e0
+  0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+  0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7, // f0
+  0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
+};
diff --git a/xpdf/PDFDocEncoding.h b/xpdf/PDFDocEncoding.h
new file mode 100644
--- /dev/null
+++ b/xpdf/PDFDocEncoding.h
@@ -0,0 +1,16 @@
+//========================================================================
+//
+// PDFDocEncoding.h
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef PDFDOCENCODING_H
+#define PDFDOCENCODING_H
+
+#include "CharTypes.h"
+
+extern Unicode pdfDocEncoding[256];
+
+#endif
diff --git a/xpdf/PSOutputDev.cc b/xpdf/PSOutputDev.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/PSOutputDev.cc
@@ -0,0 +1,6222 @@
+//========================================================================
+//
+// PSOutputDev.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <signal.h>
+#include <math.h>
+#include "GString.h"
+#include "GList.h"
+#include "config.h"
+#include "GlobalParams.h"
+#include "Object.h"
+#include "Error.h"
+#include "Function.h"
+#include "Gfx.h"
+#include "GfxState.h"
+#include "GfxFont.h"
+#include "UnicodeMap.h"
+#include "FoFiType1C.h"
+#include "FoFiTrueType.h"
+#include "Catalog.h"
+#include "Page.h"
+#include "Stream.h"
+#include "Annot.h"
+#include "XRef.h"
+#include "PreScanOutputDev.h"
+#if HAVE_SPLASH
+#  include "Splash.h"
+#  include "SplashBitmap.h"
+#  include "SplashOutputDev.h"
+#endif
+#include "PSOutputDev.h"
+
+#ifdef MACOS
+// needed for setting type/creator of MacOS files
+#include "ICSupport.h"
+#endif
+
+// the MSVC math.h doesn't define this
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+//------------------------------------------------------------------------
+
+// Resolution at which pages with transparency will be rasterized.
+#define splashDPI 300
+
+//------------------------------------------------------------------------
+// PostScript prolog and setup
+//------------------------------------------------------------------------
+
+// The '~' escapes mark prolog code that is emitted only in certain
+// levels:
+//
+//   ~[123][sn]
+//      ^   ^----- s=psLevel*Sep, n=psLevel*
+//      +----- 1=psLevel1*, 2=psLevel2*, 3=psLevel3*
+
+static char *prolog[] = {
+  "/xpdf 75 dict def xpdf begin",
+  "% PDF special state",
+  "/pdfDictSize 15 def",
+  "~1sn",
+  "/pdfStates 64 array def",
+  "  0 1 63 {",
+  "    pdfStates exch pdfDictSize dict",
+  "    dup /pdfStateIdx 3 index put",
+  "    put",
+  "  } for",
+  "~123sn",
+  "/pdfSetup {",
+  "  3 1 roll 2 array astore",
+  "  /setpagedevice where {",
+  "    pop 3 dict begin",
+  "      /PageSize exch def",
+  "      /ImagingBBox null def",
+  "      /Policies 1 dict dup begin /PageSize 3 def end def",
+  "      { /Duplex true def } if",
+  "    currentdict end setpagedevice",
+  "  } {",
+  "    pop pop",
+  "  } ifelse",
+  "} def",
+  "~1sn",
+  "/pdfOpNames [",
+  "  /pdfFill /pdfStroke /pdfLastFill /pdfLastStroke",
+  "  /pdfTextMat /pdfFontSize /pdfCharSpacing /pdfTextRender",
+  "  /pdfTextRise /pdfWordSpacing /pdfHorizScaling /pdfTextClipPath",
+  "] def",
+  "~123sn",
+  "/pdfStartPage {",
+  "~1sn",
+  "  pdfStates 0 get begin",
+  "~23sn",
+  "  pdfDictSize dict begin",
+  "~23n",
+  "  /pdfFillCS [] def",
+  "  /pdfFillXform {} def",
+  "  /pdfStrokeCS [] def",
+  "  /pdfStrokeXform {} def",
+  "~1n",
+  "  /pdfFill 0 def",
+  "  /pdfStroke 0 def",
+  "~1s",
+  "  /pdfFill [0 0 0 1] def",
+  "  /pdfStroke [0 0 0 1] def",
+  "~23sn",
+  "  /pdfFill [0] def",
+  "  /pdfStroke [0] def",
+  "  /pdfFillOP false def",
+  "  /pdfStrokeOP false def",
+  "~123sn",
+  "  /pdfLastFill false def",
+  "  /pdfLastStroke false def",
+  "  /pdfTextMat [1 0 0 1 0 0] def",
+  "  /pdfFontSize 0 def",
+  "  /pdfCharSpacing 0 def",
+  "  /pdfTextRender 0 def",
+  "  /pdfTextRise 0 def",
+  "  /pdfWordSpacing 0 def",
+  "  /pdfHorizScaling 1 def",
+  "  /pdfTextClipPath [] def",
+  "} def",
+  "/pdfEndPage { end } def",
+  "~23s",
+  "% separation convention operators",
+  "/findcmykcustomcolor where {",
+  "  pop",
+  "}{",
+  "  /findcmykcustomcolor { 5 array astore } def",
+  "} ifelse",
+  "/setcustomcolor where {",
+  "  pop",
+  "}{",
+  "  /setcustomcolor {",
+  "    exch",
+  "    [ exch /Separation exch dup 4 get exch /DeviceCMYK exch",
+  "      0 4 getinterval cvx",
+  "      [ exch /dup load exch { mul exch dup } /forall load",
+  "        /pop load dup ] cvx",
+  "    ] setcolorspace setcolor",
+  "  } def",
+  "} ifelse",
+  "/customcolorimage where {",
+  "  pop",
+  "}{",
+  "  /customcolorimage {",
+  "    gsave",
+  "    [ exch /Separation exch dup 4 get exch /DeviceCMYK exch",
+  "      0 4 getinterval",
+  "      [ exch /dup load exch { mul exch dup } /forall load",
+  "        /pop load dup ] cvx",
+  "    ] setcolorspace",
+  "    10 dict begin",
+  "      /ImageType 1 def",
+  "      /DataSource exch def",
+  "      /ImageMatrix exch def",
+  "      /BitsPerComponent exch def",
+  "      /Height exch def",
+  "      /Width exch def",
+  "      /Decode [1 0] def",
+  "    currentdict end",
+  "    image",
+  "    grestore",
+  "  } def",
+  "} ifelse",
+  "~123sn",
+  "% PDF color state",
+  "~1n",
+  "/g { dup /pdfFill exch def setgray",
+  "     /pdfLastFill true def /pdfLastStroke false def } def",
+  "/G { dup /pdfStroke exch def setgray",
+  "     /pdfLastStroke true def /pdfLastFill false def } def",
+  "/fCol {",
+  "  pdfLastFill not {",
+  "    pdfFill setgray",
+  "    /pdfLastFill true def /pdfLastStroke false def",
+  "  } if",
+  "} def",
+  "/sCol {",
+  "  pdfLastStroke not {",
+  "    pdfStroke setgray",
+  "    /pdfLastStroke true def /pdfLastFill false def",
+  "  } if",
+  "} def",
+  "~1s",
+  "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor",
+  "     /pdfLastFill true def /pdfLastStroke false def } def",
+  "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor",
+  "     /pdfLastStroke true def /pdfLastFill false def } def",
+  "/fCol {",
+  "  pdfLastFill not {",
+  "    pdfFill aload pop setcmykcolor",
+  "    /pdfLastFill true def /pdfLastStroke false def",
+  "  } if",
+  "} def",
+  "/sCol {",
+  "  pdfLastStroke not {",
+  "    pdfStroke aload pop setcmykcolor",
+  "    /pdfLastStroke true def /pdfLastFill false def",
+  "  } if",
+  "} def",
+  "~23n",
+  "/cs { /pdfFillXform exch def dup /pdfFillCS exch def",
+  "      setcolorspace } def",
+  "/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def",
+  "      setcolorspace } def",
+  "/sc { pdfLastFill not { pdfFillCS setcolorspace } if",
+  "      dup /pdfFill exch def aload pop pdfFillXform setcolor",
+  "     /pdfLastFill true def /pdfLastStroke false def } def",
+  "/SC { pdfLastStroke not { pdfStrokeCS setcolorspace } if",
+  "      dup /pdfStroke exch def aload pop pdfStrokeXform setcolor",
+  "     /pdfLastStroke true def /pdfLastFill false def } def",
+  "/op { /pdfFillOP exch def",
+  "      pdfLastFill { pdfFillOP setoverprint } if } def",
+  "/OP { /pdfStrokeOP exch def",
+  "      pdfLastStroke { pdfStrokeOP setoverprint } if } def",
+  "/fCol {",
+  "  pdfLastFill not {",
+  "    pdfFillCS setcolorspace",
+  "    pdfFill aload pop pdfFillXform setcolor",
+  "    pdfFillOP setoverprint",
+  "    /pdfLastFill true def /pdfLastStroke false def",
+  "  } if",
+  "} def",
+  "/sCol {",
+  "  pdfLastStroke not {",
+  "    pdfStrokeCS setcolorspace",
+  "    pdfStroke aload pop pdfStrokeXform setcolor",
+  "    pdfStrokeOP setoverprint",
+  "    /pdfLastStroke true def /pdfLastFill false def",
+  "  } if",
+  "} def",
+  "~23s",
+  "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor",
+  "     /pdfLastFill true def /pdfLastStroke false def } def",
+  "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor",
+  "     /pdfLastStroke true def /pdfLastFill false def } def",
+  "/ck { 6 copy 6 array astore /pdfFill exch def",
+  "      findcmykcustomcolor exch setcustomcolor",
+  "      /pdfLastFill true def /pdfLastStroke false def } def",
+  "/CK { 6 copy 6 array astore /pdfStroke exch def",
+  "      findcmykcustomcolor exch setcustomcolor",
+  "      /pdfLastStroke true def /pdfLastFill false def } def",
+  "/op { /pdfFillOP exch def",
+  "      pdfLastFill { pdfFillOP setoverprint } if } def",
+  "/OP { /pdfStrokeOP exch def",
+  "      pdfLastStroke { pdfStrokeOP setoverprint } if } def",
+  "/fCol {",
+  "  pdfLastFill not {",
+  "    pdfFill aload length 4 eq {",
+  "      setcmykcolor",
+  "    }{",
+  "      findcmykcustomcolor exch setcustomcolor",
+  "    } ifelse",
+  "    pdfFillOP setoverprint",
+  "    /pdfLastFill true def /pdfLastStroke false def",
+  "  } if",
+  "} def",
+  "/sCol {",
+  "  pdfLastStroke not {",
+  "    pdfStroke aload length 4 eq {",
+  "      setcmykcolor",
+  "    }{",
+  "      findcmykcustomcolor exch setcustomcolor",
+  "    } ifelse",
+  "    pdfStrokeOP setoverprint",
+  "    /pdfLastStroke true def /pdfLastFill false def",
+  "  } if",
+  "} def",
+  "~123sn",
+  "% build a font",
+  "/pdfMakeFont {",
+  "  4 3 roll findfont",
+  "  4 2 roll matrix scale makefont",
+  "  dup length dict begin",
+  "    { 1 index /FID ne { def } { pop pop } ifelse } forall",
+  "    /Encoding exch def",
+  "    currentdict",
+  "  end",
+  "  definefont pop",
+  "} def",
+  "/pdfMakeFont16 {",
+  "  exch findfont",
+  "  dup length dict begin",
+  "    { 1 index /FID ne { def } { pop pop } ifelse } forall",
+  "    /WMode exch def",
+  "    currentdict",
+  "  end",
+  "  definefont pop",
+  "} def",
+  "~3sn",
+  "/pdfMakeFont16L3 {",
+  "  1 index /CIDFont resourcestatus {",
+  "    pop pop 1 index /CIDFont findresource /CIDFontType known",
+  "  } {",
+  "    false",
+  "  } ifelse",
+  "  {",
+  "    0 eq { /Identity-H } { /Identity-V } ifelse",
+  "    exch 1 array astore composefont pop",
+  "  } {",
+  "    pdfMakeFont16",
+  "  } ifelse",
+  "} def",
+  "~123sn",
+  "% graphics state operators",
+  "~1sn",
+  "/q {",
+  "  gsave",
+  "  pdfOpNames length 1 sub -1 0 { pdfOpNames exch get load } for",
+  "  pdfStates pdfStateIdx 1 add get begin",
+  "  pdfOpNames { exch def } forall",
+  "} def",
+  "/Q { end grestore } def",
+  "~23sn",
+  "/q { gsave pdfDictSize dict begin } def",
+  "/Q {",
+  "  end grestore",
+  "  /pdfLastFill where {",
+  "    pop",
+  "    pdfLastFill {",
+  "      pdfFillOP setoverprint",
+  "    } {",
+  "      pdfStrokeOP setoverprint",
+  "    } ifelse",
+  "  } if",
+  "} def",
+  "~123sn",
+  "/cm { concat } def",
+  "/d { setdash } def",
+  "/i { setflat } def",
+  "/j { setlinejoin } def",
+  "/J { setlinecap } def",
+  "/M { setmiterlimit } def",
+  "/w { setlinewidth } def",
+  "% path segment operators",
+  "/m { moveto } def",
+  "/l { lineto } def",
+  "/c { curveto } def",
+  "/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto",
+  "      neg 0 rlineto closepath } def",
+  "/h { closepath } def",
+  "% path painting operators",
+  "/S { sCol stroke } def",
+  "/Sf { fCol stroke } def",
+  "/f { fCol fill } def",
+  "/f* { fCol eofill } def",
+  "% clipping operators",
+  "/W { clip newpath } def",
+  "/W* { eoclip newpath } def",
+  "/Ws { strokepath clip newpath } def",
+  "% text state operators",
+  "/Tc { /pdfCharSpacing exch def } def",
+  "/Tf { dup /pdfFontSize exch def",
+  "      dup pdfHorizScaling mul exch matrix scale",
+  "      pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put",
+  "      exch findfont exch makefont setfont } def",
+  "/Tr { /pdfTextRender exch def } def",
+  "/Ts { /pdfTextRise exch def } def",
+  "/Tw { /pdfWordSpacing exch def } def",
+  "/Tz { /pdfHorizScaling exch def } def",
+  "% text positioning operators",
+  "/Td { pdfTextMat transform moveto } def",
+  "/Tm { /pdfTextMat exch def } def",
+  "% text string operators",
+  "/cshow where {",
+  "  pop",
+  "  /cshow2 {",
+  "    dup {",
+  "      pop pop",
+  "      1 string dup 0 3 index put 3 index exec",
+  "    } exch cshow",
+  "    pop pop",
+  "  } def",
+  "}{",
+  "  /cshow2 {",
+  "    currentfont /FontType get 0 eq {",
+  "      0 2 2 index length 1 sub {",
+  "        2 copy get exch 1 add 2 index exch get",
+  "        2 copy exch 256 mul add",
+  "        2 string dup 0 6 5 roll put dup 1 5 4 roll put",
+  "        3 index exec",
+  "      } for",
+  "    } {",
+  "      dup {",
+  "        1 string dup 0 3 index put 3 index exec",
+  "      } forall",
+  "    } ifelse",
+  "    pop pop",
+  "  } def",
+  "} ifelse",
+  "/awcp {", // awidthcharpath
+  "  exch {",
+  "    false charpath",
+  "    5 index 5 index rmoveto",
+  "    6 index eq { 7 index 7 index rmoveto } if",
+  "  } exch cshow2",
+  "  6 {pop} repeat",
+  "} def",
+  "/Tj {",
+  "  fCol",  // because stringwidth has to draw Type 3 chars
+  "  1 index stringwidth pdfTextMat idtransform pop",
+  "  sub 1 index length dup 0 ne { div } { pop pop 0 } ifelse",
+  "  pdfWordSpacing pdfHorizScaling mul 0 pdfTextMat dtransform 32",
+  "  4 3 roll pdfCharSpacing pdfHorizScaling mul add 0",
+  "  pdfTextMat dtransform",
+  "  6 5 roll Tj1",
+  "} def",
+  "/Tj16 {",
+  "  fCol",  // because stringwidth has to draw Type 3 chars
+  "  2 index stringwidth pdfTextMat idtransform pop",
+  "  sub exch div",
+  "  pdfWordSpacing pdfHorizScaling mul 0 pdfTextMat dtransform 32",
+  "  4 3 roll pdfCharSpacing pdfHorizScaling mul add 0",
+  "  pdfTextMat dtransform",
+  "  6 5 roll Tj1",
+  "} def",
+  "/Tj16V {",
+  "  fCol",  // because stringwidth has to draw Type 3 chars
+  "  2 index stringwidth pdfTextMat idtransform exch pop",
+  "  sub exch div",
+  "  0 pdfWordSpacing pdfTextMat dtransform 32",
+  "  4 3 roll pdfCharSpacing add 0 exch",
+  "  pdfTextMat dtransform",
+  "  6 5 roll Tj1",
+  "} def",
+  "/Tj1 {",
+  "  0 pdfTextRise pdfTextMat dtransform rmoveto",
+  "  currentpoint 8 2 roll",
+  "  pdfTextRender 1 and 0 eq {",
+  "    6 copy awidthshow",
+  "  } if",
+  "  pdfTextRender 3 and dup 1 eq exch 2 eq or {",
+  "    7 index 7 index moveto",
+  "    6 copy",
+  "    currentfont /FontType get 3 eq { fCol } { sCol } ifelse",
+  "    false awcp currentpoint stroke moveto",
+  "  } if",
+  "  pdfTextRender 4 and 0 ne {",
+  "    8 6 roll moveto",
+  "    false awcp",
+  "    /pdfTextClipPath [ pdfTextClipPath aload pop",
+  "      {/moveto cvx}",
+  "      {/lineto cvx}",
+  "      {/curveto cvx}",
+  "      {/closepath cvx}",
+  "    pathforall ] def",
+  "    currentpoint newpath moveto",
+  "  } {",
+  "    8 {pop} repeat",
+  "  } ifelse",
+  "  0 pdfTextRise neg pdfTextMat dtransform rmoveto",
+  "} def",
+  "/TJm { pdfFontSize 0.001 mul mul neg 0",
+  "       pdfTextMat dtransform rmoveto } def",
+  "/TJmV { pdfFontSize 0.001 mul mul neg 0 exch",
+  "        pdfTextMat dtransform rmoveto } def",
+  "/Tclip { pdfTextClipPath cvx exec clip newpath",
+  "         /pdfTextClipPath [] def } def",
+  "~1ns",
+  "% Level 1 image operators",
+  "~1n",
+  "/pdfIm1 {",
+  "  /pdfImBuf1 4 index string def",
+  "  { currentfile pdfImBuf1 readhexstring pop } image",
+  "} def",
+  "~1s",
+  "/pdfIm1Sep {",
+  "  /pdfImBuf1 4 index string def",
+  "  /pdfImBuf2 4 index string def",
+  "  /pdfImBuf3 4 index string def",
+  "  /pdfImBuf4 4 index string def",
+  "  { currentfile pdfImBuf1 readhexstring pop }",
+  "  { currentfile pdfImBuf2 readhexstring pop }",
+  "  { currentfile pdfImBuf3 readhexstring pop }",
+  "  { currentfile pdfImBuf4 readhexstring pop }",
+  "  true 4 colorimage",
+  "} def",
+  "~1ns",
+  "/pdfImM1 {",
+  "  fCol /pdfImBuf1 4 index 7 add 8 idiv string def",
+  "  { currentfile pdfImBuf1 readhexstring pop } imagemask",
+  "} def",
+  "/pdfImM1a {",
+  "  { 2 copy get exch 1 add exch } imagemask",
+  "  pop pop",
+  "} def",
+  "~23sn",
+  "% Level 2 image operators",
+  "/pdfImBuf 100 string def",
+  "/pdfIm {",
+  "  image",
+  "  { currentfile pdfImBuf readline",
+  "    not { pop exit } if",
+  "    (%-EOD-) eq { exit } if } loop",
+  "} def",
+  "~23s",
+  "/pdfImSep {",
+  "  findcmykcustomcolor exch",
+  "  dup /Width get /pdfImBuf1 exch string def",
+  "  dup /Decode get aload pop 1 index sub /pdfImDecodeRange exch def",
+  "  /pdfImDecodeLow exch def",
+  "  begin Width Height BitsPerComponent ImageMatrix DataSource end",
+  "  /pdfImData exch def",
+  "  { pdfImData pdfImBuf1 readstring pop",
+  "    0 1 2 index length 1 sub {",
+  "      1 index exch 2 copy get",
+  "      pdfImDecodeRange mul 255 div pdfImDecodeLow add round cvi",
+  "      255 exch sub put",
+  "    } for }",
+  "  6 5 roll customcolorimage",
+  "  { currentfile pdfImBuf readline",
+  "    not { pop exit } if",
+  "    (%-EOD-) eq { exit } if } loop",
+  "} def",
+  "~23sn",
+  "/pdfImM {",
+  "  fCol imagemask",
+  "  { currentfile pdfImBuf readline",
+  "    not { pop exit } if",
+  "    (%-EOD-) eq { exit } if } loop",
+  "} def",
+  "/pr { 2 index 2 index 3 2 roll putinterval 4 add } def",
+  "/pdfImClip {",
+  "  gsave",
+  "  0 2 4 index length 1 sub {",
+  "    dup 4 index exch 2 copy",
+  "    get 5 index div put",
+  "    1 add 3 index exch 2 copy",
+  "    get 3 index div put",
+  "  } for",
+  "  pop pop rectclip",
+  "} def",
+  "/pdfImClipEnd { grestore } def",
+  "~23sn",
+  "% shading operators",
+  "/colordelta {",
+  "  false 0 1 3 index length 1 sub {",
+  "    dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {",
+  "      pop true",
+  "    } if",
+  "  } for",
+  "  exch pop exch pop",
+  "} def",
+  "/funcCol { func n array astore } def",
+  "/funcSH {",
+  "  dup 0 eq {",
+  "    true",
+  "  } {",
+  "    dup 6 eq {",
+  "      false",
+  "    } {",
+  "      4 index 4 index funcCol dup",
+  "      6 index 4 index funcCol dup",
+  "      3 1 roll colordelta 3 1 roll",
+  "      5 index 5 index funcCol dup",
+  "      3 1 roll colordelta 3 1 roll",
+  "      6 index 8 index funcCol dup",
+  "      3 1 roll colordelta 3 1 roll",
+  "      colordelta or or or",
+  "    } ifelse",
+  "  } ifelse",
+  "  {",
+  "    1 add",
+  "    4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch",
+  "    6 index 6 index 4 index 4 index 4 index funcSH",
+  "    2 index 6 index 6 index 4 index 4 index funcSH",
+  "    6 index 2 index 4 index 6 index 4 index funcSH",
+  "    5 3 roll 3 2 roll funcSH pop pop",
+  "  } {",
+  "    pop 3 index 2 index add 0.5 mul 3 index  2 index add 0.5 mul",
+  "~23n",
+  "    funcCol sc",
+  "~23s",
+  "    funcCol aload pop k",
+  "~23sn",
+  "    dup 4 index exch mat transform m",
+  "    3 index 3 index mat transform l",
+  "    1 index 3 index mat transform l",
+  "    mat transform l pop pop h f*",
+  "  } ifelse",
+  "} def",
+  "/axialCol {",
+  "  dup 0 lt {",
+  "    pop t0",
+  "  } {",
+  "    dup 1 gt {",
+  "      pop t1",
+  "    } {",
+  "      dt mul t0 add",
+  "    } ifelse",
+  "  } ifelse",
+  "  func n array astore",
+  "} def",
+  "/axialSH {",
+  "  dup 0 eq {",
+  "    true",
+  "  } {",
+  "    dup 8 eq {",
+  "      false",
+  "    } {",
+  "      2 index axialCol 2 index axialCol colordelta",
+  "    } ifelse",
+  "  } ifelse",
+  "  {",
+  "    1 add 3 1 roll 2 copy add 0.5 mul",
+  "    dup 4 3 roll exch 4 index axialSH",
+  "    exch 3 2 roll axialSH",
+  "  } {",
+  "    pop 2 copy add 0.5 mul",
+  "~23n",
+  "    axialCol sc",
+  "~23s",
+  "    axialCol aload pop k",
+  "~23sn",
+  "    exch dup dx mul x0 add exch dy mul y0 add",
+  "    3 2 roll dup dx mul x0 add exch dy mul y0 add",
+  "    dx abs dy abs ge {",
+  "      2 copy yMin sub dy mul dx div add yMin m",
+  "      yMax sub dy mul dx div add yMax l",
+  "      2 copy yMax sub dy mul dx div add yMax l",
+  "      yMin sub dy mul dx div add yMin l",
+  "      h f*",
+  "    } {",
+  "      exch 2 copy xMin sub dx mul dy div add xMin exch m",
+  "      xMax sub dx mul dy div add xMax exch l",
+  "      exch 2 copy xMax sub dx mul dy div add xMax exch l",
+  "      xMin sub dx mul dy div add xMin exch l",
+  "      h f*",
+  "    } ifelse",
+  "  } ifelse",
+  "} def",
+  "/radialCol {",
+  "  dup t0 lt {",
+  "    pop t0",
+  "  } {",
+  "    dup t1 gt {",
+  "      pop t1",
+  "    } if",
+  "  } ifelse",
+  "  func n array astore",
+  "} def",
+  "/radialSH {",
+  "  dup 0 eq {",
+  "    true",
+  "  } {",
+  "    dup 8 eq {",
+  "      false",
+  "    } {",
+  "      2 index dt mul t0 add radialCol",
+  "      2 index dt mul t0 add radialCol colordelta",
+  "    } ifelse",
+  "  } ifelse",
+  "  {",
+  "    1 add 3 1 roll 2 copy add 0.5 mul",
+  "    dup 4 3 roll exch 4 index radialSH",
+  "    exch 3 2 roll radialSH",
+  "  } {",
+  "    pop 2 copy add 0.5 mul dt mul t0 add",
+  "~23n",
+  "    radialCol sc",
+  "~23s",
+  "    radialCol aload pop k",
+  "~23sn",
+  "    encl {",
+  "      exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      0 360 arc h",
+  "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      360 0 arcn h f",
+  "    } {",
+  "      2 copy",
+  "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      a1 a2 arcn",
+  "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      a2 a1 arcn h",
+  "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      a1 a2 arc",
+  "      dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add",
+  "      a2 a1 arc h f",
+  "    } ifelse",
+  "  } ifelse",
+  "} def",
+  "~123sn",
+  "end",
+  NULL
+};
+
+static char *cmapProlog[] = {
+  "/CIDInit /ProcSet findresource begin",
+  "10 dict begin",
+  "  begincmap",
+  "  /CMapType 1 def",
+  "  /CMapName /Identity-H def",
+  "  /CIDSystemInfo 3 dict dup begin",
+  "    /Registry (Adobe) def",
+  "    /Ordering (Identity) def",
+  "    /Supplement 0 def",
+  "  end def",
+  "  1 begincodespacerange",
+  "    <0000> <ffff>",
+  "  endcodespacerange",
+  "  0 usefont",
+  "  1 begincidrange",
+  "    <0000> <ffff> 0",
+  "  endcidrange",
+  "  endcmap",
+  "  currentdict CMapName exch /CMap defineresource pop",
+  "end",
+  "10 dict begin",
+  "  begincmap",
+  "  /CMapType 1 def",
+  "  /CMapName /Identity-V def",
+  "  /CIDSystemInfo 3 dict dup begin",
+  "    /Registry (Adobe) def",
+  "    /Ordering (Identity) def",
+  "    /Supplement 0 def",
+  "  end def",
+  "  /WMode 1 def",
+  "  1 begincodespacerange",
+  "    <0000> <ffff>",
+  "  endcodespacerange",
+  "  0 usefont",
+  "  1 begincidrange",
+  "    <0000> <ffff> 0",
+  "  endcidrange",
+  "  endcmap",
+  "  currentdict CMapName exch /CMap defineresource pop",
+  "end",
+  "end",
+  NULL
+};
+
+//------------------------------------------------------------------------
+// Fonts
+//------------------------------------------------------------------------
+
+struct PSSubstFont {
+  char *psName;			// PostScript name
+  double mWidth;		// width of 'm' character
+};
+
+static char *psFonts[] = {
+  "Courier",
+  "Courier-Bold",
+  "Courier-Oblique",
+  "Courier-BoldOblique",
+  "Helvetica",
+  "Helvetica-Bold",
+  "Helvetica-Oblique",
+  "Helvetica-BoldOblique",
+  "Symbol",
+  "Times-Roman",
+  "Times-Bold",
+  "Times-Italic",
+  "Times-BoldItalic",
+  "ZapfDingbats",
+  NULL
+};
+
+static PSSubstFont psSubstFonts[] = {
+  {"Helvetica",             0.833},
+  {"Helvetica-Oblique",     0.833},
+  {"Helvetica-Bold",        0.889},
+  {"Helvetica-BoldOblique", 0.889},
+  {"Times-Roman",           0.788},
+  {"Times-Italic",          0.722},
+  {"Times-Bold",            0.833},
+  {"Times-BoldItalic",      0.778},
+  {"Courier",               0.600},
+  {"Courier-Oblique",       0.600},
+  {"Courier-Bold",          0.600},
+  {"Courier-BoldOblique",   0.600}
+};
+
+// Info for 8-bit fonts
+struct PSFont8Info {
+  Ref fontID;
+  Gushort *codeToGID;		// code-to-GID mapping for TrueType fonts
+};
+
+// Encoding info for substitute 16-bit font
+struct PSFont16Enc {
+  Ref fontID;
+  GString *enc;
+};
+
+//------------------------------------------------------------------------
+// process colors
+//------------------------------------------------------------------------
+
+#define psProcessCyan     1
+#define psProcessMagenta  2
+#define psProcessYellow   4
+#define psProcessBlack    8
+#define psProcessCMYK    15
+
+//------------------------------------------------------------------------
+// PSOutCustomColor
+//------------------------------------------------------------------------
+
+class PSOutCustomColor {
+public:
+
+  PSOutCustomColor(double cA, double mA,
+		   double yA, double kA, GString *nameA);
+  ~PSOutCustomColor();
+
+  double c, m, y, k;
+  GString *name;
+  PSOutCustomColor *next;
+};
+
+PSOutCustomColor::PSOutCustomColor(double cA, double mA,
+				   double yA, double kA, GString *nameA) {
+  c = cA;
+  m = mA;
+  y = yA;
+  k = kA;
+  name = nameA;
+  next = NULL;
+}
+
+PSOutCustomColor::~PSOutCustomColor() {
+  delete name;
+}
+
+//------------------------------------------------------------------------
+
+struct PSOutImgClipRect {
+  int x0, x1, y0, y1;
+};
+
+//------------------------------------------------------------------------
+// DeviceNRecoder
+//------------------------------------------------------------------------
+
+class DeviceNRecoder: public FilterStream {
+public:
+
+  DeviceNRecoder(Stream *strA, int widthA, int heightA,
+		 GfxImageColorMap *colorMapA);
+  virtual ~DeviceNRecoder();
+  virtual StreamKind getKind() { return strWeird; }
+  virtual void reset();
+  virtual int getChar()
+    { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx++]; }
+  virtual int lookChar()
+    { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx]; }
+  virtual GString *getPSFilter(int psLevel, char *indent) { return NULL; }
+  virtual GBool isBinary(GBool last = gTrue) { return gTrue; }
+  virtual GBool isEncoder() { return gTrue; }
+
+private:
+
+  GBool fillBuf();
+
+  int width, height;
+  GfxImageColorMap *colorMap;
+  Function *func;
+  ImageStream *imgStr;
+  int buf[gfxColorMaxComps];
+  int pixelIdx;
+  int bufIdx;
+  int bufSize;
+};
+
+DeviceNRecoder::DeviceNRecoder(Stream *strA, int widthA, int heightA,
+			       GfxImageColorMap *colorMapA):
+    FilterStream(strA) {
+  width = widthA;
+  height = heightA;
+  colorMap = colorMapA;
+  imgStr = NULL;
+  pixelIdx = 0;
+  bufIdx = gfxColorMaxComps;
+  bufSize = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
+              getAlt()->getNComps();
+  func = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
+           getTintTransformFunc();
+}
+
+DeviceNRecoder::~DeviceNRecoder() {
+  if (imgStr) {
+    delete imgStr;
+  }
+}
+
+void DeviceNRecoder::reset() {
+  imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
+			   colorMap->getBits());
+  imgStr->reset();
+}
+
+GBool DeviceNRecoder::fillBuf() {
+  Guchar pixBuf[gfxColorMaxComps];
+  GfxColor color;
+  double x[gfxColorMaxComps], y[gfxColorMaxComps];
+  int i;
+
+  if (pixelIdx >= width * height) {
+    return gFalse;
+  }
+  imgStr->getPixel(pixBuf);
+  colorMap->getColor(pixBuf, &color);
+  for (i = 0;
+       i < ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->getNComps();
+       ++i) {
+    x[i] = colToDbl(color.c[i]);
+  }
+  func->transform(x, y);
+  for (i = 0; i < bufSize; ++i) {
+    buf[i] = (int)(y[i] * 255 + 0.5);
+  }
+  bufIdx = 0;
+  ++pixelIdx;
+  return gTrue;
+}
+
+//------------------------------------------------------------------------
+// PSOutputDev
+//------------------------------------------------------------------------
+
+extern "C" {
+typedef void (*SignalFunc)(int);
+}
+
+static void outputToFile(void *stream, char *data, int len) {
+  fwrite(data, 1, len, (FILE *)stream);
+}
+
+PSOutputDev::PSOutputDev(char *fileName, XRef *xrefA, Catalog *catalog,
+			 int firstPage, int lastPage, PSOutMode modeA,
+			 int imgLLXA, int imgLLYA, int imgURXA, int imgURYA,
+			 GBool manualCtrlA) {
+  FILE *f;
+  PSFileType fileTypeA;
+
+  underlayCbk = NULL;
+  underlayCbkData = NULL;
+  overlayCbk = NULL;
+  overlayCbkData = NULL;
+
+  fontIDs = NULL;
+  fontFileIDs = NULL;
+  fontFileNames = NULL;
+  font8Info = NULL;
+  font16Enc = NULL;
+  imgIDs = NULL;
+  formIDs = NULL;
+  xobjStack = NULL;
+  embFontList = NULL;
+  customColors = NULL;
+  haveTextClip = gFalse;
+  t3String = NULL;
+
+  // open file or pipe
+  if (!strcmp(fileName, "-")) {
+    fileTypeA = psStdout;
+    f = stdout;
+  } else if (fileName[0] == '|') {
+    fileTypeA = psPipe;
+#ifdef HAVE_POPEN
+#ifndef WIN32
+    signal(SIGPIPE, (SignalFunc)SIG_IGN);
+#endif
+    if (!(f = popen(fileName + 1, "w"))) {
+      error(-1, "Couldn't run print command '%s'", fileName);
+      ok = gFalse;
+      return;
+    }
+#else
+    error(-1, "Print commands are not supported ('%s')", fileName);
+    ok = gFalse;
+    return;
+#endif
+  } else {
+    fileTypeA = psFile;
+    if (!(f = fopen(fileName, "w"))) {
+      error(-1, "Couldn't open PostScript file '%s'", fileName);
+      ok = gFalse;
+      return;
+    }
+  }
+
+  init(outputToFile, f, fileTypeA,
+       xrefA, catalog, firstPage, lastPage, modeA,
+       imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA);
+}
+
+PSOutputDev::PSOutputDev(PSOutputFunc outputFuncA, void *outputStreamA,
+			 XRef *xrefA, Catalog *catalog,
+			 int firstPage, int lastPage, PSOutMode modeA,
+			 int imgLLXA, int imgLLYA, int imgURXA, int imgURYA,
+			 GBool manualCtrlA) {
+  underlayCbk = NULL;
+  underlayCbkData = NULL;
+  overlayCbk = NULL;
+  overlayCbkData = NULL;
+
+  fontIDs = NULL;
+  fontFileIDs = NULL;
+  fontFileNames = NULL;
+  font8Info = NULL;
+  font16Enc = NULL;
+  imgIDs = NULL;
+  formIDs = NULL;
+  xobjStack = NULL;
+  embFontList = NULL;
+  customColors = NULL;
+  haveTextClip = gFalse;
+  t3String = NULL;
+
+  init(outputFuncA, outputStreamA, psGeneric,
+       xrefA, catalog, firstPage, lastPage, modeA,
+       imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA);
+}
+
+void PSOutputDev::init(PSOutputFunc outputFuncA, void *outputStreamA,
+		       PSFileType fileTypeA, XRef *xrefA, Catalog *catalog,
+		       int firstPage, int lastPage, PSOutMode modeA,
+		       int imgLLXA, int imgLLYA, int imgURXA, int imgURYA,
+		       GBool manualCtrlA) {
+  Page *page;
+  PDFRectangle *box;
+
+  // initialize
+  ok = gTrue;
+  outputFunc = outputFuncA;
+  outputStream = outputStreamA;
+  fileType = fileTypeA;
+  xref = xrefA;
+  level = globalParams->getPSLevel();
+  mode = modeA;
+  paperWidth = globalParams->getPSPaperWidth();
+  paperHeight = globalParams->getPSPaperHeight();
+  imgLLX = imgLLXA;
+  imgLLY = imgLLYA;
+  imgURX = imgURXA;
+  imgURY = imgURYA;
+  if (imgLLX == 0 && imgURX == 0 && imgLLY == 0 && imgURY == 0) {
+    globalParams->getPSImageableArea(&imgLLX, &imgLLY, &imgURX, &imgURY);
+  }
+  if (paperWidth < 0 || paperHeight < 0) {
+    // this check is needed in case the document has zero pages
+    if (firstPage > 0 && firstPage <= catalog->getNumPages()) {
+      page = catalog->getPage(firstPage);
+      paperWidth = (int)ceil(page->getMediaWidth());
+      paperHeight = (int)ceil(page->getMediaHeight());
+    } else {
+      paperWidth = 1;
+      paperHeight = 1;
+    }
+    imgLLX = imgLLY = 0;
+    imgURX = paperWidth;
+    imgURY = paperHeight;
+  }
+  preload = globalParams->getPSPreload();
+  manualCtrl = manualCtrlA;
+  if (mode == psModeForm) {
+    lastPage = firstPage;
+  }
+  processColors = 0;
+  inType3Char = gFalse;
+
+#if OPI_SUPPORT
+  // initialize OPI nesting levels
+  opi13Nest = 0;
+  opi20Nest = 0;
+#endif
+
+  tx0 = ty0 = -1;
+  xScale0 = yScale0 = 0;
+  rotate0 = -1;
+  clipLLX0 = clipLLY0 = 0;
+  clipURX0 = clipURY0 = -1;
+
+  // initialize fontIDs, fontFileIDs, and fontFileNames lists
+  fontIDSize = 64;
+  fontIDLen = 0;
+  fontIDs = (Ref *)gmallocn(fontIDSize, sizeof(Ref));
+  fontFileIDSize = 64;
+  fontFileIDLen = 0;
+  fontFileIDs = (Ref *)gmallocn(fontFileIDSize, sizeof(Ref));
+  fontFileNameSize = 64;
+  fontFileNameLen = 0;
+  fontFileNames = (GString **)gmallocn(fontFileNameSize, sizeof(GString *));
+  nextTrueTypeNum = 0;
+  font8InfoLen = 0;
+  font8InfoSize = 0;
+  font16EncLen = 0;
+  font16EncSize = 0;
+  imgIDLen = 0;
+  imgIDSize = 0;
+  formIDLen = 0;
+  formIDSize = 0;
+
+  xobjStack = new GList();
+  numSaves = 0;
+  numTilingPatterns = 0;
+  nextFunc = 0;
+
+  // initialize embedded font resource comment list
+  embFontList = new GString();
+
+  if (!manualCtrl) {
+    // this check is needed in case the document has zero pages
+    if (firstPage > 0 && firstPage <= catalog->getNumPages()) {
+      writeHeader(firstPage, lastPage,
+		  catalog->getPage(firstPage)->getMediaBox(),
+		  catalog->getPage(firstPage)->getCropBox(),
+		  catalog->getPage(firstPage)->getRotate());
+    } else {
+      box = new PDFRectangle(0, 0, 1, 1);
+      writeHeader(firstPage, lastPage, box, box, 0);
+      delete box;
+    }
+    if (mode != psModeForm) {
+      writePS("%%BeginProlog\n");
+    }
+    writeXpdfProcset();
+    if (mode != psModeForm) {
+      writePS("%%EndProlog\n");
+      writePS("%%BeginSetup\n");
+    }
+    writeDocSetup(catalog, firstPage, lastPage);
+    if (mode != psModeForm) {
+      writePS("%%EndSetup\n");
+    }
+  }
+
+  // initialize sequential page number
+  seqPage = 1;
+}
+
+PSOutputDev::~PSOutputDev() {
+  PSOutCustomColor *cc;
+  int i;
+
+  if (ok) {
+    if (!manualCtrl) {
+      writePS("%%Trailer\n");
+      writeTrailer();
+      if (mode != psModeForm) {
+	writePS("%%EOF\n");
+      }
+    }
+    if (fileType == psFile) {
+#ifdef MACOS
+      ICS_MapRefNumAndAssign((short)((FILE *)outputStream)->handle);
+#endif
+      fclose((FILE *)outputStream);
+    }
+#ifdef HAVE_POPEN
+    else if (fileType == psPipe) {
+      pclose((FILE *)outputStream);
+#ifndef WIN32
+      signal(SIGPIPE, (SignalFunc)SIG_DFL);
+#endif
+    }
+#endif
+  }
+  if (embFontList) {
+    delete embFontList;
+  }
+  if (fontIDs) {
+    gfree(fontIDs);
+  }
+  if (fontFileIDs) {
+    gfree(fontFileIDs);
+  }
+  if (fontFileNames) {
+    for (i = 0; i < fontFileNameLen; ++i) {
+      delete fontFileNames[i];
+    }
+    gfree(fontFileNames);
+  }
+  if (font8Info) {
+    for (i = 0; i < font8InfoLen; ++i) {
+      gfree(font8Info[i].codeToGID);
+    }
+    gfree(font8Info);
+  }
+  if (font16Enc) {
+    for (i = 0; i < font16EncLen; ++i) {
+      delete font16Enc[i].enc;
+    }
+    gfree(font16Enc);
+  }
+  gfree(imgIDs);
+  gfree(formIDs);
+  if (xobjStack) {
+    delete xobjStack;
+  }
+  while (customColors) {
+    cc = customColors;
+    customColors = cc->next;
+    delete cc;
+  }
+}
+
+void PSOutputDev::writeHeader(int firstPage, int lastPage,
+			      PDFRectangle *mediaBox, PDFRectangle *cropBox,
+			      int pageRotate) {
+  Object info, obj1;
+  double x1, y1, x2, y2;
+
+  switch (mode) {
+  case psModePS:
+    writePS("%!PS-Adobe-3.0\n");
+    break;
+  case psModeEPS:
+    writePS("%!PS-Adobe-3.0 EPSF-3.0\n");
+    break;
+  case psModeForm:
+    writePS("%!PS-Adobe-3.0 Resource-Form\n");
+    break;
+  }
+
+  writePSFmt("% Produced by xpdf/pdftops {0:s}\n", xpdfVersion);
+  xref->getDocInfo(&info);
+  if (info.isDict() && info.dictLookup("Creator", &obj1)->isString()) {
+    writePS("%%Creator: ");
+    writePSTextLine(obj1.getString());
+  }
+  obj1.free();
+  if (info.isDict() && info.dictLookup("Title", &obj1)->isString()) {
+    writePS("%%Title: ");
+    writePSTextLine(obj1.getString());
+  }
+  obj1.free();
+  info.free();
+  writePSFmt("%%LanguageLevel: {0:d}\n",
+	     (level == psLevel1 || level == psLevel1Sep) ? 1 :
+	     (level == psLevel2 || level == psLevel2Sep) ? 2 : 3);
+  if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) {
+    writePS("%%DocumentProcessColors: (atend)\n");
+    writePS("%%DocumentCustomColors: (atend)\n");
+  }
+  writePS("%%DocumentSuppliedResources: (atend)\n");
+
+  switch (mode) {
+  case psModePS:
+    writePSFmt("%%DocumentMedia: plain {0:d} {1:d} 0 () ()\n",
+	       paperWidth, paperHeight);
+    writePSFmt("%%BoundingBox: 0 0 {0:d} {1:d}\n", paperWidth, paperHeight);
+    writePSFmt("%%Pages: {0:d}\n", lastPage - firstPage + 1);
+    writePS("%%EndComments\n");
+    writePS("%%BeginDefaults\n");
+    writePS("%%PageMedia: plain\n");
+    writePS("%%EndDefaults\n");
+    break;
+  case psModeEPS:
+    epsX1 = cropBox->x1;
+    epsY1 = cropBox->y1;
+    epsX2 = cropBox->x2;
+    epsY2 = cropBox->y2;
+    if (pageRotate == 0 || pageRotate == 180) {
+      x1 = epsX1;
+      y1 = epsY1;
+      x2 = epsX2;
+      y2 = epsY2;
+    } else { // pageRotate == 90 || pageRotate == 270
+      x1 = 0;
+      y1 = 0;
+      x2 = epsY2 - epsY1;
+      y2 = epsX2 - epsX1;
+    }
+    writePSFmt("%%BoundingBox: {0:d} {1:d} {2:d} {3:d}\n",
+	       (int)floor(x1), (int)floor(y1), (int)ceil(x2), (int)ceil(y2));
+    if (floor(x1) != ceil(x1) || floor(y1) != ceil(y1) ||
+	floor(x2) != ceil(x2) || floor(y2) != ceil(y2)) {
+      writePSFmt("%%HiResBoundingBox: {0:.4g} {1:.4g} {2:.4g} {3:.4g}\n",
+		 x1, y1, x2, y2);
+    }
+    writePS("%%EndComments\n");
+    break;
+  case psModeForm:
+    writePS("%%EndComments\n");
+    writePS("32 dict dup begin\n");
+    writePSFmt("/BBox [{0:d} {1:d} {2:d} {3:d}] def\n",
+	       (int)floor(mediaBox->x1), (int)floor(mediaBox->y1),
+	       (int)ceil(mediaBox->x2), (int)ceil(mediaBox->y2));
+    writePS("/FormType 1 def\n");
+    writePS("/Matrix [1 0 0 1 0 0] def\n");
+    break;
+  }
+}
+
+void PSOutputDev::writeXpdfProcset() {
+  GBool lev1, lev2, lev3, sep, nonSep;
+  char **p;
+  char *q;
+
+  writePSFmt("%%BeginResource: procset xpdf {0:s} 0\n", xpdfVersion);
+  writePSFmt("%%Copyright: {0:s}\n", xpdfCopyright);
+  lev1 = lev2 = lev3 = sep = nonSep = gTrue;
+  for (p = prolog; *p; ++p) {
+    if ((*p)[0] == '~') {
+      lev1 = lev2 = lev3 = sep = nonSep = gFalse;
+      for (q = *p + 1; *q; ++q) {
+	switch (*q) {
+	case '1': lev1 = gTrue; break;
+	case '2': lev2 = gTrue; break;
+	case '3': lev3 = gTrue; break;
+	case 's': sep = gTrue; break;
+	case 'n': nonSep = gTrue; break;
+	}
+      }
+    } else if ((level == psLevel1 && lev1 && nonSep) ||
+	       (level == psLevel1Sep && lev1 && sep) ||
+	       (level == psLevel2 && lev2 && nonSep) ||
+	       (level == psLevel2Sep && lev2 && sep) ||
+	       (level == psLevel3 && lev3 && nonSep) ||
+	       (level == psLevel3Sep && lev3 && sep)) {
+      writePSFmt("{0:s}\n", *p);
+    }
+  }
+  writePS("%%EndResource\n");
+
+  if (level >= psLevel3) {
+    for (p = cmapProlog; *p; ++p) {
+      writePSFmt("{0:s}\n", *p);
+    }
+  }
+}
+
+void PSOutputDev::writeDocSetup(Catalog *catalog,
+				int firstPage, int lastPage) {
+  Page *page;
+  Dict *resDict;
+  Annots *annots;
+  Object obj1, obj2;
+  int pg, i;
+
+  if (mode == psModeForm) {
+    // swap the form and xpdf dicts
+    writePS("xpdf end begin dup begin\n");
+  } else {
+    writePS("xpdf begin\n");
+  }
+  for (pg = firstPage; pg <= lastPage; ++pg) {
+    page = catalog->getPage(pg);
+    if ((resDict = page->getResourceDict())) {
+      setupResources(resDict);
+    }
+    annots = new Annots(xref, catalog, page->getAnnots(&obj1));
+    obj1.free();
+    for (i = 0; i < annots->getNumAnnots(); ++i) {
+      if (annots->getAnnot(i)->getAppearance(&obj1)->isStream()) {
+	obj1.streamGetDict()->lookup("Resources", &obj2);
+	if (obj2.isDict()) {
+	  setupResources(obj2.getDict());
+	}
+	obj2.free();
+      }
+      obj1.free();
+    }
+    delete annots;
+  }
+  if (mode != psModeForm) {
+    if (mode != psModeEPS && !manualCtrl) {
+      writePSFmt("{0:d} {1:d} {2:s} pdfSetup\n",
+		 paperWidth, paperHeight,
+		 globalParams->getPSDuplex() ? "true" : "false");
+    }
+#if OPI_SUPPORT
+    if (globalParams->getPSOPI()) {
+      writePS("/opiMatrix matrix currentmatrix def\n");
+    }
+#endif
+  }
+}
+
+void PSOutputDev::writePageTrailer() {
+  if (mode != psModeForm) {
+    writePS("pdfEndPage\n");
+  }
+}
+
+void PSOutputDev::writeTrailer() {
+  PSOutCustomColor *cc;
+
+  if (mode == psModeForm) {
+    writePS("/Foo exch /Form defineresource pop\n");
+  } else {
+    writePS("end\n");
+    writePS("%%DocumentSuppliedResources:\n");
+    writePS(embFontList->getCString());
+    if (level == psLevel1Sep || level == psLevel2Sep ||
+	level == psLevel3Sep) {
+      writePS("%%DocumentProcessColors:");
+      if (processColors & psProcessCyan) {
+	writePS(" Cyan");
+	 }
+      if (processColors & psProcessMagenta) {
+	writePS(" Magenta");
+      }
+      if (processColors & psProcessYellow) {
+	writePS(" Yellow");
+      }
+      if (processColors & psProcessBlack) {
+	writePS(" Black");
+      }
+      writePS("\n");
+      writePS("%%DocumentCustomColors:");
+      for (cc = customColors; cc; cc = cc->next) {
+	writePSFmt(" ({0:s})", cc->name->getCString());
+      }
+      writePS("\n");
+      writePS("%%CMYKCustomColor:\n");
+      for (cc = customColors; cc; cc = cc->next) {
+	writePSFmt("%%+ {0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t})\n",
+		   cc->c, cc->m, cc->y, cc->k, cc->name);
+      }
+    }
+  }
+}
+
+void PSOutputDev::setupResources(Dict *resDict) {
+  Object xObjDict, xObjRef, xObj, patDict, patRef, pat, resObj;
+  Ref ref0, ref1;
+  GBool skip;
+  int i, j;
+
+  setupFonts(resDict);
+  setupImages(resDict);
+  setupForms(resDict);
+
+  //----- recursively scan XObjects
+  resDict->lookup("XObject", &xObjDict);
+  if (xObjDict.isDict()) {
+    for (i = 0; i < xObjDict.dictGetLength(); ++i) {
+
+      // avoid infinite recursion on XObjects
+      skip = gFalse;
+      if ((xObjDict.dictGetValNF(i, &xObjRef)->isRef())) {
+	ref0 = xObjRef.getRef();
+	for (j = 0; j < xobjStack->getLength(); ++j) {
+	  ref1 = *(Ref *)xobjStack->get(j);
+	  if (ref1.num == ref0.num && ref1.gen == ref0.gen) {
+	    skip = gTrue;
+	    break;
+	  }
+	}
+	if (!skip) {
+	  xobjStack->append(&ref0);
+	}
+      }
+      if (!skip) {
+
+	// process the XObject's resource dictionary
+	xObjDict.dictGetVal(i, &xObj);
+	if (xObj.isStream()) {
+	  xObj.streamGetDict()->lookup("Resources", &resObj);
+	  if (resObj.isDict()) {
+	    setupResources(resObj.getDict());
+	  }
+	  resObj.free();
+	}
+	xObj.free();
+      }
+
+      if (xObjRef.isRef() && !skip) {
+	xobjStack->del(xobjStack->getLength() - 1);
+      }
+      xObjRef.free();
+    }
+  }
+  xObjDict.free();
+
+  //----- recursively scan Patterns
+  resDict->lookup("Pattern", &patDict);
+  if (patDict.isDict()) {
+    inType3Char = gTrue;
+    for (i = 0; i < patDict.dictGetLength(); ++i) {
+
+      // avoid infinite recursion on Patterns
+      skip = gFalse;
+      if ((patDict.dictGetValNF(i, &patRef)->isRef())) {
+	ref0 = patRef.getRef();
+	for (j = 0; j < xobjStack->getLength(); ++j) {
+	  ref1 = *(Ref *)xobjStack->get(j);
+	  if (ref1.num == ref0.num && ref1.gen == ref0.gen) {
+	    skip = gTrue;
+	    break;
+	  }
+	}
+	if (!skip) {
+	  xobjStack->append(&ref0);
+	}
+      }
+      if (!skip) {
+
+	// process the Pattern's resource dictionary
+	patDict.dictGetVal(i, &pat);
+	if (pat.isStream()) {
+	  pat.streamGetDict()->lookup("Resources", &resObj);
+	  if (resObj.isDict()) {
+	    setupResources(resObj.getDict());
+	  }
+	  resObj.free();
+	}
+	pat.free();
+      }
+
+      if (patRef.isRef() && !skip) {
+	xobjStack->del(xobjStack->getLength() - 1);
+      }
+      patRef.free();
+    }
+    inType3Char = gFalse;
+  }
+  patDict.free();
+}
+
+void PSOutputDev::setupFonts(Dict *resDict) {
+  Object obj1, obj2;
+  Ref r;
+  GfxFontDict *gfxFontDict;
+  GfxFont *font;
+  int i;
+
+  gfxFontDict = NULL;
+  resDict->lookupNF("Font", &obj1);
+  if (obj1.isRef()) {
+    obj1.fetch(xref, &obj2);
+    if (obj2.isDict()) {
+      r = obj1.getRef();
+      gfxFontDict = new GfxFontDict(xref, &r, obj2.getDict());
+    }
+    obj2.free();
+  } else if (obj1.isDict()) {
+    gfxFontDict = new GfxFontDict(xref, NULL, obj1.getDict());
+  }
+  if (gfxFontDict) {
+    for (i = 0; i < gfxFontDict->getNumFonts(); ++i) {
+      if ((font = gfxFontDict->getFont(i))) {
+	setupFont(font, resDict);
+      }
+    }
+    delete gfxFontDict;
+  }
+  obj1.free();
+}
+
+void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) {
+  Ref fontFileID;
+  GString *name;
+  PSFontParam *fontParam;
+  GString *psName;
+  char buf[16];
+  GBool subst;
+  UnicodeMap *uMap;
+  char *charName;
+  double xs, ys;
+  int code;
+  double w1, w2;
+  double *fm;
+  int i, j;
+
+  // check if font is already set up
+  for (i = 0; i < fontIDLen; ++i) {
+    if (fontIDs[i].num == font->getID()->num &&
+	fontIDs[i].gen == font->getID()->gen) {
+      return;
+    }
+  }
+
+  // add entry to fontIDs list
+  if (fontIDLen >= fontIDSize) {
+    fontIDSize += 64;
+    fontIDs = (Ref *)greallocn(fontIDs, fontIDSize, sizeof(Ref));
+  }
+  fontIDs[fontIDLen++] = *font->getID();
+
+  xs = ys = 1;
+  subst = gFalse;
+
+  // check for resident 8-bit font
+  if (font->getName() &&
+      (fontParam = globalParams->getPSFont(font->getName()))) {
+    psName = new GString(fontParam->psFontName->getCString());
+
+  // check for embedded Type 1 font
+  } else if (globalParams->getPSEmbedType1() &&
+	     font->getType() == fontType1 &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    psName = filterPSName(font->getEmbeddedFontName());
+    setupEmbeddedType1Font(&fontFileID, psName);
+
+  // check for embedded Type 1C font
+  } else if (globalParams->getPSEmbedType1() &&
+	     font->getType() == fontType1C &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    // use the PDF font name because the embedded font name might
+    // not include the subset prefix
+    psName = filterPSName(font->getOrigName());
+    setupEmbeddedType1CFont(font, &fontFileID, psName);
+
+  // check for embedded OpenType - Type 1C font
+  } else if (globalParams->getPSEmbedType1() &&
+	     font->getType() == fontType1COT &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    // use the PDF font name because the embedded font name might
+    // not include the subset prefix
+    psName = filterPSName(font->getOrigName());
+    setupEmbeddedOpenTypeT1CFont(font, &fontFileID, psName);
+
+  // check for external Type 1 font file
+  } else if (globalParams->getPSEmbedType1() &&
+	     font->getType() == fontType1 &&
+	     font->getExtFontFile()) {
+    // this assumes that the PS font name matches the PDF font name
+    psName = font->getName()->copy();
+    setupExternalType1Font(font->getExtFontFile(), psName);
+
+  // check for embedded TrueType font
+  } else if (globalParams->getPSEmbedTrueType() &&
+	     (font->getType() == fontTrueType ||
+	      font->getType() == fontTrueTypeOT) &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    psName = filterPSName(font->getEmbeddedFontName());
+    setupEmbeddedTrueTypeFont(font, &fontFileID, psName);
+
+  // check for external TrueType font file
+  } else if (globalParams->getPSEmbedTrueType() &&
+	     font->getType() == fontTrueType &&
+	     font->getExtFontFile()) {
+    psName = filterPSName(font->getName());
+    setupExternalTrueTypeFont(font, psName);
+
+  // check for embedded CID PostScript font
+  } else if (globalParams->getPSEmbedCIDPostScript() &&
+	     font->getType() == fontCIDType0C &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    psName = filterPSName(font->getEmbeddedFontName());
+    setupEmbeddedCIDType0Font(font, &fontFileID, psName);
+
+  // check for embedded CID TrueType font
+  } else if (globalParams->getPSEmbedCIDTrueType() &&
+	     (font->getType() == fontCIDType2 ||
+	      font->getType() == fontCIDType2OT) &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    psName = filterPSName(font->getEmbeddedFontName());
+    //~ should check to see if font actually uses vertical mode
+    setupEmbeddedCIDTrueTypeFont(font, &fontFileID, psName, gTrue);
+
+  // check for embedded OpenType - CID CFF font
+  } else if (globalParams->getPSEmbedCIDPostScript() &&
+	     font->getType() == fontCIDType0COT &&
+	     font->getEmbeddedFontID(&fontFileID)) {
+    psName = filterPSName(font->getEmbeddedFontName());
+    setupEmbeddedOpenTypeCFFFont(font, &fontFileID, psName);
+
+  // check for Type 3 font
+  } else if (font->getType() == fontType3) {
+    psName = GString::format("T3_{0:d}_{1:d}",
+			     font->getID()->num, font->getID()->gen);
+    setupType3Font(font, psName, parentResDict);
+
+  // do 8-bit font substitution
+  } else if (!font->isCIDFont()) {
+    subst = gTrue;
+    name = font->getName();
+    psName = NULL;
+    if (name) {
+      for (i = 0; psFonts[i]; ++i) {
+	if (name->cmp(psFonts[i]) == 0) {
+	  psName = new GString(psFonts[i]);
+	  break;
+	}
+      }
+    }
+    if (!psName) {
+      if (font->isFixedWidth()) {
+	i = 8;
+      } else if (font->isSerif()) {
+	i = 4;
+      } else {
+	i = 0;
+      }
+      if (font->isBold()) {
+	i += 2;
+      }
+      if (font->isItalic()) {
+	i += 1;
+      }
+      psName = new GString(psSubstFonts[i].psName);
+      for (code = 0; code < 256; ++code) {
+	if ((charName = ((Gfx8BitFont *)font)->getCharName(code)) &&
+	    charName[0] == 'm' && charName[1] == '\0') {
+	  break;
+	}
+      }
+      if (code < 256) {
+	w1 = ((Gfx8BitFont *)font)->getWidth(code);
+      } else {
+	w1 = 0;
+      }
+      w2 = psSubstFonts[i].mWidth;
+      xs = w1 / w2;
+      if (xs < 0.1) {
+	xs = 1;
+      }
+      if (font->getType() == fontType3) {
+	// This is a hack which makes it possible to substitute for some
+	// Type 3 fonts.  The problem is that it's impossible to know what
+	// the base coordinate system used in the font is without actually
+	// rendering the font.
+	ys = xs;
+	fm = font->getFontMatrix();
+	if (fm[0] != 0) {
+	  ys *= fm[3] / fm[0];
+	}
+      } else {
+	ys = 1;
+      }
+    }
+
+  // do 16-bit font substitution
+  } else if ((fontParam = globalParams->
+	        getPSFont16(font->getName(),
+			    ((GfxCIDFont *)font)->getCollection(),
+			    font->getWMode()))) {
+    subst = gTrue;
+    psName = fontParam->psFontName->copy();
+    if (font16EncLen >= font16EncSize) {
+      font16EncSize += 16;
+      font16Enc = (PSFont16Enc *)greallocn(font16Enc,
+					   font16EncSize, sizeof(PSFont16Enc));
+    }
+    font16Enc[font16EncLen].fontID = *font->getID();
+    font16Enc[font16EncLen].enc = fontParam->encoding->copy();
+    if ((uMap = globalParams->getUnicodeMap(font16Enc[font16EncLen].enc))) {
+      uMap->decRefCnt();
+      ++font16EncLen;
+    } else {
+      error(-1, "Couldn't find Unicode map for 16-bit font encoding '%s'",
+	    font16Enc[font16EncLen].enc->getCString());
+    }
+
+  // give up - can't do anything with this font
+  } else {
+    error(-1, "Couldn't find a font to substitute for '%s' ('%s' character collection)",
+	  font->getName() ? font->getName()->getCString() : "(unnamed)",
+	  ((GfxCIDFont *)font)->getCollection()
+	    ? ((GfxCIDFont *)font)->getCollection()->getCString()
+	    : "(unknown)");
+    return;
+  }
+
+  // generate PostScript code to set up the font
+  if (font->isCIDFont()) {
+    if (level == psLevel3 || level == psLevel3Sep) {
+      writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16L3\n",
+		 font->getID()->num, font->getID()->gen, psName,
+		 font->getWMode());
+    } else {
+      writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16\n",
+		 font->getID()->num, font->getID()->gen, psName,
+		 font->getWMode());
+    }
+  } else {
+    writePSFmt("/F{0:d}_{1:d} /{2:t} {3:.4g} {4:.4g}\n",
+	       font->getID()->num, font->getID()->gen, psName, xs, ys);
+    for (i = 0; i < 256; i += 8) {
+      writePS((char *)((i == 0) ? "[ " : "  "));
+      for (j = 0; j < 8; ++j) {
+	if (font->getType() == fontTrueType &&
+	    !subst &&
+	    !((Gfx8BitFont *)font)->getHasEncoding()) {
+	  sprintf(buf, "c%02x", i+j);
+	  charName = buf;
+	} else {
+	  charName = ((Gfx8BitFont *)font)->getCharName(i+j);
+	  // this is a kludge for broken PDF files that encode char 32
+	  // as .notdef
+	  if (i+j == 32 && charName && !strcmp(charName, ".notdef")) {
+	    charName = "space";
+	  }
+	}
+	writePS("/");
+	writePSName(charName ? charName : (char *)".notdef");
+	// the empty name is legal in PDF and PostScript, but PostScript
+	// uses a double-slash (//...) for "immediately evaluated names",
+	// so we need to add a space character here
+	if (charName && !charName[0]) {
+	  writePS(" ");
+	}
+      }
+      writePS((i == 256-8) ? (char *)"]\n" : (char *)"\n");
+    }
+    writePS("pdfMakeFont\n");
+  }
+
+  delete psName;
+}
+
+void PSOutputDev::setupEmbeddedType1Font(Ref *id, GString *psName) {
+  static char hexChar[17] = "0123456789abcdef";
+  Object refObj, strObj, obj1, obj2, obj3;
+  Dict *dict;
+  int length1, length2, length3;
+  int c;
+  int start[4];
+  GBool binMode;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen)
+      return;
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // get the font stream and info
+  refObj.initRef(id->num, id->gen);
+  refObj.fetch(xref, &strObj);
+  refObj.free();
+  if (!strObj.isStream()) {
+    error(-1, "Embedded font file object is not a stream");
+    goto err1;
+  }
+  if (!(dict = strObj.streamGetDict())) {
+    error(-1, "Embedded font stream is missing its dictionary");
+    goto err1;
+  }
+  dict->lookup("Length1", &obj1);
+  dict->lookup("Length2", &obj2);
+  dict->lookup("Length3", &obj3);
+  if (!obj1.isInt() || !obj2.isInt() || !obj3.isInt()) {
+    error(-1, "Missing length fields in embedded font stream dictionary");
+    obj1.free();
+    obj2.free();
+    obj3.free();
+    goto err1;
+  }
+  length1 = obj1.getInt();
+  length2 = obj2.getInt();
+  length3 = obj3.getInt();
+  obj1.free();
+  obj2.free();
+  obj3.free();
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // copy ASCII portion of font
+  strObj.streamReset();
+  for (i = 0; i < length1 && (c = strObj.streamGetChar()) != EOF; ++i) {
+    writePSChar(c);
+  }
+
+  // figure out if encrypted portion is binary or ASCII
+  binMode = gFalse;
+  for (i = 0; i < 4; ++i) {
+    start[i] = strObj.streamGetChar();
+    if (start[i] == EOF) {
+      error(-1, "Unexpected end of file in embedded font stream");
+      goto err1;
+    }
+    if (!((start[i] >= '0' && start[i] <= '9') ||
+	  (start[i] >= 'A' && start[i] <= 'F') ||
+	  (start[i] >= 'a' && start[i] <= 'f')))
+      binMode = gTrue;
+  }
+
+  // convert binary data to ASCII
+  if (binMode) {
+    for (i = 0; i < 4; ++i) {
+      writePSChar(hexChar[(start[i] >> 4) & 0x0f]);
+      writePSChar(hexChar[start[i] & 0x0f]);
+    }
+#if 0 // this causes trouble for various PostScript printers
+    // if Length2 is incorrect (too small), font data gets chopped, so
+    // we take a few extra characters from the trailer just in case
+    length2 += length3 >= 8 ? 8 : length3;
+#endif
+    while (i < length2) {
+      if ((c = strObj.streamGetChar()) == EOF) {
+	break;
+      }
+      writePSChar(hexChar[(c >> 4) & 0x0f]);
+      writePSChar(hexChar[c & 0x0f]);
+      if (++i % 32 == 0) {
+	writePSChar('\n');
+      }
+    }
+    if (i % 32 > 0) {
+      writePSChar('\n');
+    }
+
+  // already in ASCII format -- just copy it
+  } else {
+    for (i = 0; i < 4; ++i) {
+      writePSChar(start[i]);
+    }
+    for (i = 4; i < length2; ++i) {
+      if ((c = strObj.streamGetChar()) == EOF) {
+	break;
+      }
+      writePSChar(c);
+    }
+  }
+
+  // write padding and "cleartomark"
+  for (i = 0; i < 8; ++i) {
+    writePS("00000000000000000000000000000000"
+	    "00000000000000000000000000000000\n");
+  }
+  writePS("cleartomark\n");
+
+  // ending comment
+  writePS("%%EndResource\n");
+
+ err1:
+  strObj.streamClose();
+  strObj.free();
+}
+
+//~ This doesn't handle .pfb files or binary eexec data (which only
+//~ happens in pfb files?).
+void PSOutputDev::setupExternalType1Font(GString *fileName, GString *psName) {
+  FILE *fontFile;
+  int c;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileNameLen; ++i) {
+    if (!fontFileNames[i]->cmp(fileName)) {
+      return;
+    }
+  }
+
+  // add entry to fontFileNames list
+  if (fontFileNameLen >= fontFileNameSize) {
+    fontFileNameSize += 64;
+    fontFileNames = (GString **)greallocn(fontFileNames,
+					  fontFileNameSize, sizeof(GString *));
+  }
+  fontFileNames[fontFileNameLen++] = fileName->copy();
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // copy the font file
+  if (!(fontFile = fopen(fileName->getCString(), "rb"))) {
+    error(-1, "Couldn't open external font file");
+    return;
+  }
+  while ((c = fgetc(fontFile)) != EOF) {
+    writePSChar(c);
+  }
+  fclose(fontFile);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id,
+					  GString *psName) {
+  char *fontBuf;
+  int fontLen;
+  FoFiType1C *ffT1C;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen)
+      return;
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 1 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffT1C = FoFiType1C::make(fontBuf, fontLen))) {
+    ffT1C->convertToType1(psName->getCString(), NULL, gTrue,
+			  outputFunc, outputStream);
+    delete ffT1C;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id,
+					       GString *psName) {
+  char *fontBuf;
+  int fontLen;
+  FoFiTrueType *ffTT;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen)
+      return;
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 1 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
+    if (ffTT->isOpenTypeCFF()) {
+      ffTT->convertToType1(psName->getCString(), NULL, gTrue,
+			   outputFunc, outputStream);
+    }
+    delete ffTT;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id,
+					    GString *psName) {
+  char *fontBuf;
+  int fontLen;
+  FoFiTrueType *ffTT;
+  Gushort *codeToGID;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen) {
+      psName->appendf("_{0:d}", nextTrueTypeNum++);
+      break;
+    }
+  }
+
+  // add entry to fontFileIDs list
+  if (i == fontFileIDLen) {
+    if (fontFileIDLen >= fontFileIDSize) {
+      fontFileIDSize += 64;
+      fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+    }
+    fontFileIDs[fontFileIDLen++] = *id;
+  }
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 42 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
+    codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT);
+    ffTT->convertToType42(psName->getCString(),
+			  ((Gfx8BitFont *)font)->getHasEncoding()
+			    ? ((Gfx8BitFont *)font)->getEncoding()
+			    : (char **)NULL,
+			  codeToGID, outputFunc, outputStream);
+    if (codeToGID) {
+      if (font8InfoLen >= font8InfoSize) {
+	font8InfoSize += 16;
+	font8Info = (PSFont8Info *)greallocn(font8Info,
+					     font8InfoSize,
+					     sizeof(PSFont8Info));
+      }
+      font8Info[font8InfoLen].fontID = *font->getID();
+      font8Info[font8InfoLen].codeToGID = codeToGID;
+      ++font8InfoLen;
+    }
+    delete ffTT;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupExternalTrueTypeFont(GfxFont *font, GString *psName) {
+  GString *fileName;
+  char *fontBuf;
+  int fontLen;
+  FoFiTrueType *ffTT;
+  Gushort *codeToGID;
+  int i;
+
+  // check if font is already embedded
+  fileName = font->getExtFontFile();
+  for (i = 0; i < fontFileNameLen; ++i) {
+    if (!fontFileNames[i]->cmp(fileName)) {
+      psName->appendf("_{0:d}", nextTrueTypeNum++);
+      break;
+    }
+  }
+
+  // add entry to fontFileNames list
+  if (i == fontFileNameLen) {
+    if (fontFileNameLen >= fontFileNameSize) {
+      fontFileNameSize += 64;
+      fontFileNames =
+	(GString **)greallocn(fontFileNames,
+			      fontFileNameSize, sizeof(GString *));
+    }
+    fontFileNames[fontFileNameLen++] = fileName->copy();
+  }
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 42 font
+  fontBuf = font->readExtFontFile(&fontLen);
+  if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
+    codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT);
+    ffTT->convertToType42(psName->getCString(),
+			  ((Gfx8BitFont *)font)->getHasEncoding()
+			    ? ((Gfx8BitFont *)font)->getEncoding()
+			    : (char **)NULL,
+			  codeToGID, outputFunc, outputStream);
+    if (codeToGID) {
+      if (font8InfoLen >= font8InfoSize) {
+	font8InfoSize += 16;
+	font8Info = (PSFont8Info *)greallocn(font8Info,
+					     font8InfoSize,
+					     sizeof(PSFont8Info));
+      }
+      font8Info[font8InfoLen].fontID = *font->getID();
+      font8Info[font8InfoLen].codeToGID = codeToGID;
+      ++font8InfoLen;
+    }
+    delete ffTT;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id,
+					    GString *psName) {
+  char *fontBuf;
+  int fontLen;
+  FoFiType1C *ffT1C;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen)
+      return;
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 0 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffT1C = FoFiType1C::make(fontBuf, fontLen))) {
+    if (globalParams->getPSLevel() >= psLevel3) {
+      // Level 3: use a CID font
+      ffT1C->convertToCIDType0(psName->getCString(), outputFunc, outputStream);
+    } else {
+      // otherwise: use a non-CID composite font
+      ffT1C->convertToType0(psName->getCString(), outputFunc, outputStream);
+    }
+    delete ffT1C;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id,
+					       GString *psName,
+					       GBool needVerticalMetrics) {
+  char *fontBuf;
+  int fontLen;
+  FoFiTrueType *ffTT;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen) {
+      psName->appendf("_{0:d}", nextTrueTypeNum++);
+      break;
+    }
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 0 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
+    if (globalParams->getPSLevel() >= psLevel3) {
+      // Level 3: use a CID font
+      ffTT->convertToCIDType2(psName->getCString(),
+			      ((GfxCIDFont *)font)->getCIDToGID(),
+			      ((GfxCIDFont *)font)->getCIDToGIDLen(),
+			      needVerticalMetrics,
+			      outputFunc, outputStream);
+    } else {
+      // otherwise: use a non-CID composite font
+      ffTT->convertToType0(psName->getCString(),
+			   ((GfxCIDFont *)font)->getCIDToGID(),
+			   ((GfxCIDFont *)font)->getCIDToGIDLen(),
+			   needVerticalMetrics,
+			   outputFunc, outputStream);
+    }
+    delete ffTT;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id,
+					       GString *psName) {
+  char *fontBuf;
+  int fontLen;
+  FoFiTrueType *ffTT;
+  int i;
+
+  // check if font is already embedded
+  for (i = 0; i < fontFileIDLen; ++i) {
+    if (fontFileIDs[i].num == id->num &&
+	fontFileIDs[i].gen == id->gen)
+      return;
+  }
+
+  // add entry to fontFileIDs list
+  if (fontFileIDLen >= fontFileIDSize) {
+    fontFileIDSize += 64;
+    fontFileIDs = (Ref *)greallocn(fontFileIDs, fontFileIDSize, sizeof(Ref));
+  }
+  fontFileIDs[fontFileIDLen++] = *id;
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // convert it to a Type 0 font
+  fontBuf = font->readEmbFontFile(xref, &fontLen);
+  if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) {
+    if (ffTT->isOpenTypeCFF()) {
+      if (globalParams->getPSLevel() >= psLevel3) {
+	// Level 3: use a CID font
+	ffTT->convertToCIDType0(psName->getCString(),
+				outputFunc, outputStream);
+      } else {
+	// otherwise: use a non-CID composite font
+	ffTT->convertToType0(psName->getCString(), outputFunc, outputStream);
+      }
+    }
+    delete ffTT;
+  }
+  gfree(fontBuf);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupType3Font(GfxFont *font, GString *psName,
+				 Dict *parentResDict) {
+  Dict *resDict;
+  Dict *charProcs;
+  Object charProc;
+  Gfx *gfx;
+  PDFRectangle box;
+  double *m;
+  GString *buf;
+  int i;
+
+  // set up resources used by font
+  if ((resDict = ((Gfx8BitFont *)font)->getResources())) {
+    inType3Char = gTrue;
+    setupResources(resDict);
+    inType3Char = gFalse;
+  } else {
+    resDict = parentResDict;
+  }
+
+  // beginning comment
+  writePSFmt("%%BeginResource: font {0:t}\n", psName);
+  embFontList->append("%%+ font ");
+  embFontList->append(psName->getCString());
+  embFontList->append("\n");
+
+  // font dictionary
+  writePS("8 dict begin\n");
+  writePS("/FontType 3 def\n");
+  m = font->getFontMatrix();
+  writePSFmt("/FontMatrix [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] def\n",
+	     m[0], m[1], m[2], m[3], m[4], m[5]);
+  m = font->getFontBBox();
+  writePSFmt("/FontBBox [{0:.4g} {1:.4g} {2:.4g} {3:.4g}] def\n",
+	     m[0], m[1], m[2], m[3]);
+  writePS("/Encoding 256 array def\n");
+  writePS("  0 1 255 { Encoding exch /.notdef put } for\n");
+  writePS("/BuildGlyph {\n");
+  writePS("  exch /CharProcs get exch\n");
+  writePS("  2 copy known not { pop /.notdef } if\n");
+  writePS("  get exec\n");
+  writePS("} bind def\n");
+  writePS("/BuildChar {\n");
+  writePS("  1 index /Encoding get exch get\n");
+  writePS("  1 index /BuildGlyph get exec\n");
+  writePS("} bind def\n");
+  if ((charProcs = ((Gfx8BitFont *)font)->getCharProcs())) {
+    writePSFmt("/CharProcs {0:d} dict def\n", charProcs->getLength());
+    writePS("CharProcs begin\n");
+    box.x1 = m[0];
+    box.y1 = m[1];
+    box.x2 = m[2];
+    box.y2 = m[3];
+    gfx = new Gfx(xref, this, resDict, &box, NULL);
+    inType3Char = gTrue;
+    for (i = 0; i < charProcs->getLength(); ++i) {
+      t3Cacheable = gFalse;
+      t3NeedsRestore = gFalse;
+      writePS("/");
+      writePSName(charProcs->getKey(i));
+      writePS(" {\n");
+      gfx->display(charProcs->getVal(i, &charProc));
+      charProc.free();
+      if (t3String) {
+	if (t3Cacheable) {
+	  buf = GString::format("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} setcachedevice\n",
+				t3WX, t3WY, t3LLX, t3LLY, t3URX, t3URY);
+	} else {
+	  buf = GString::format("{0:.4g} {1:.4g} setcharwidth\n", t3WX, t3WY);
+	}
+	(*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+	delete buf;
+	(*outputFunc)(outputStream, t3String->getCString(),
+		      t3String->getLength());
+	delete t3String;
+	t3String = NULL;
+      }
+      if (t3NeedsRestore) {
+	(*outputFunc)(outputStream, "Q\n", 2);
+      }
+      writePS("} def\n");
+    }
+    inType3Char = gFalse;
+    delete gfx;
+    writePS("end\n");
+  }
+  writePS("currentdict end\n");
+  writePSFmt("/{0:t} exch definefont pop\n", psName);
+
+  // ending comment
+  writePS("%%EndResource\n");
+}
+
+void PSOutputDev::setupImages(Dict *resDict) {
+  Object xObjDict, xObj, xObjRef, subtypeObj;
+  int i;
+
+  if (!(mode == psModeForm || inType3Char || preload)) {
+    return;
+  }
+
+  resDict->lookup("XObject", &xObjDict);
+  if (xObjDict.isDict()) {
+    for (i = 0; i < xObjDict.dictGetLength(); ++i) {
+      xObjDict.dictGetValNF(i, &xObjRef);
+      xObjDict.dictGetVal(i, &xObj);
+      if (xObj.isStream()) {
+	xObj.streamGetDict()->lookup("Subtype", &subtypeObj);
+	if (subtypeObj.isName("Image")) {
+	  if (xObjRef.isRef()) {
+	    setupImage(xObjRef.getRef(), xObj.getStream());
+	  } else {
+	    error(-1, "Image in resource dict is not an indirect reference");
+	  }
+	}
+	subtypeObj.free();
+      }
+      xObj.free();
+      xObjRef.free();
+    }
+  }
+  xObjDict.free();
+}
+
+void PSOutputDev::setupImage(Ref id, Stream *str) {
+  GBool useRLE, useCompressed, useASCIIHex;
+  GString *s;
+  int c;
+  int size, line, col, i;
+
+  // check if image is already setup
+  for (i = 0; i < imgIDLen; ++i) {
+    if (imgIDs[i].num == id.num && imgIDs[i].gen == id.gen) {
+      return;
+    }
+  }
+
+  // add entry to imgIDs list
+  if (imgIDLen >= imgIDSize) {
+    if (imgIDSize == 0) {
+      imgIDSize = 64;
+    } else {
+      imgIDSize *= 2;
+    }
+    imgIDs = (Ref *)greallocn(imgIDs, imgIDSize, sizeof(Ref));
+  }
+  imgIDs[imgIDLen++] = id;
+
+  // filters
+  //~ this does not correctly handle the DeviceN color space
+  //~   -- need to use DeviceNRecoder
+  if (level < psLevel2) {
+    useRLE = gFalse;
+    useCompressed = gFalse;
+    useASCIIHex = gTrue;
+  } else {
+    s = str->getPSFilter(level < psLevel3 ? 2 : 3, "");
+    if (s) {
+      useRLE = gFalse;
+      useCompressed = gTrue;
+      delete s;
+    } else {
+      useRLE = gTrue;
+      useCompressed = gFalse;
+    }
+    useASCIIHex = level == psLevel1 || level == psLevel1Sep ||
+                  globalParams->getPSASCIIHex();
+  }
+  if (useCompressed) {
+    str = str->getUndecodedStream();
+  }
+  if (useRLE) {
+    str = new RunLengthEncoder(str);
+  }
+  if (useASCIIHex) {
+    str = new ASCIIHexEncoder(str);
+  } else {
+    str = new ASCII85Encoder(str);
+  }
+
+  // compute image data size
+  str->reset();
+  col = size = 0;
+  do {
+    do {
+      c = str->getChar();
+    } while (c == '\n' || c == '\r');
+    if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+      break;
+    }
+    if (c == 'z') {
+      ++col;
+    } else {
+      ++col;
+      for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
+	do {
+	  c = str->getChar();
+	} while (c == '\n' || c == '\r');
+	if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	  break;
+	}
+	++col;
+      }
+    }
+    if (col > 225) {
+      ++size;
+      col = 0;
+    }
+  } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
+  // add one entry for the final line of data; add another entry
+  // because the RunLengthDecode filter may read past the end
+  ++size;
+  if (useRLE) {
+    ++size;
+  }
+  writePSFmt("{0:d} array dup /ImData_{1:d}_{2:d} exch def\n",
+	     size, id.num, id.gen);
+  str->close();
+
+  // write the data into the array
+  str->reset();
+  line = col = 0;
+  writePS((char *)(useASCIIHex ? "dup 0 <" : "dup 0 <~"));
+  do {
+    do {
+      c = str->getChar();
+    } while (c == '\n' || c == '\r');
+    if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+      break;
+    }
+    if (c == 'z') {
+      writePSChar(c);
+      ++col;
+    } else {
+      writePSChar(c);
+      ++col;
+      for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
+	do {
+	  c = str->getChar();
+	} while (c == '\n' || c == '\r');
+	if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	  break;
+	}
+	writePSChar(c);
+	++col;
+      }
+    }
+    // each line is: "dup nnnnn <~...data...~> put<eol>"
+    // so max data length = 255 - 20 = 235
+    // chunks are 1 or 4 bytes each, so we have to stop at 232
+    // but make it 225 just to be safe
+    if (col > 225) {
+      writePS((char *)(useASCIIHex ? "> put\n" : "~> put\n"));
+      ++line;
+      writePSFmt((char *)(useASCIIHex ? "dup {0:d} <" : "dup {0:d} <~"), line);
+      col = 0;
+    }
+  } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
+  writePS((char *)(useASCIIHex ? "> put\n" : "~> put\n"));
+  if (useRLE) {
+    ++line;
+    writePSFmt("{0:d} <> put\n", line);
+  } else {
+    writePS("pop\n");
+  }
+  str->close();
+
+  delete str;
+}
+
+void PSOutputDev::setupForms(Dict *resDict) {
+  Object xObjDict, xObj, xObjRef, subtypeObj;
+  int i;
+
+  if (!preload) {
+    return;
+  }
+
+  resDict->lookup("XObject", &xObjDict);
+  if (xObjDict.isDict()) {
+    for (i = 0; i < xObjDict.dictGetLength(); ++i) {
+      xObjDict.dictGetValNF(i, &xObjRef);
+      xObjDict.dictGetVal(i, &xObj);
+      if (xObj.isStream()) {
+	xObj.streamGetDict()->lookup("Subtype", &subtypeObj);
+	if (subtypeObj.isName("Form")) {
+	  if (xObjRef.isRef()) {
+	    setupForm(xObjRef.getRef(), &xObj);
+	  } else {
+	    error(-1, "Form in resource dict is not an indirect reference");
+	  }
+	}
+	subtypeObj.free();
+      }
+      xObj.free();
+      xObjRef.free();
+    }
+  }
+  xObjDict.free();
+}
+
+void PSOutputDev::setupForm(Ref id, Object *strObj) {
+  Dict *dict, *resDict;
+  Object matrixObj, bboxObj, resObj, obj1;
+  double m[6], bbox[4];
+  PDFRectangle box;
+  Gfx *gfx;
+  int i;
+
+  // check if form is already defined
+  for (i = 0; i < formIDLen; ++i) {
+    if (formIDs[i].num == id.num && formIDs[i].gen == id.gen) {
+      return;
+    }
+  }
+
+  // add entry to formIDs list
+  if (formIDLen >= formIDSize) {
+    if (formIDSize == 0) {
+      formIDSize = 64;
+    } else {
+      formIDSize *= 2;
+    }
+    formIDs = (Ref *)greallocn(formIDs, formIDSize, sizeof(Ref));
+  }
+  formIDs[formIDLen++] = id;
+
+  dict = strObj->streamGetDict();
+
+  // get bounding box
+  dict->lookup("BBox", &bboxObj);
+  if (!bboxObj.isArray()) {
+    bboxObj.free();
+    error(-1, "Bad form bounding box");
+    return;
+  }
+  for (i = 0; i < 4; ++i) {
+    bboxObj.arrayGet(i, &obj1);
+    bbox[i] = obj1.getNum();
+    obj1.free();
+  }
+  bboxObj.free();
+
+  // get matrix
+  dict->lookup("Matrix", &matrixObj);
+  if (matrixObj.isArray()) {
+    for (i = 0; i < 6; ++i) {
+      matrixObj.arrayGet(i, &obj1);
+      m[i] = obj1.getNum();
+      obj1.free();
+    }
+  } else {
+    m[0] = 1; m[1] = 0;
+    m[2] = 0; m[3] = 1;
+    m[4] = 0; m[5] = 0;
+  }
+  matrixObj.free();
+
+  // get resources
+  dict->lookup("Resources", &resObj);
+  resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL;
+
+  writePSFmt("/f_{0:d}_{1:d} {{\n", id.num, id.gen);
+  writePS("q\n");
+  writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] cm\n",
+	     m[0], m[1], m[2], m[3], m[4], m[5]);
+
+  box.x1 = bbox[0];
+  box.y1 = bbox[1];
+  box.x2 = bbox[2];
+  box.y2 = bbox[3];
+  gfx = new Gfx(xref, this, resDict, &box, &box);
+  gfx->display(strObj);
+  delete gfx;
+
+  writePS("Q\n");
+  writePS("} def\n");
+
+  resObj.free();
+}
+
+GBool PSOutputDev::checkPageSlice(Page *page, double hDPI, double vDPI,
+				  int rotateA, GBool useMediaBox, GBool crop,
+				  int sliceX, int sliceY,
+				  int sliceW, int sliceH,
+				  GBool printing, Catalog *catalog,
+				  GBool (*abortCheckCbk)(void *data),
+				  void *abortCheckCbkData) {
+#if HAVE_SPLASH
+  PreScanOutputDev *scan;
+  GBool rasterize;
+  SplashOutputDev *splashOut;
+  SplashColor paperColor;
+  PDFRectangle box;
+  GfxState *state;
+  SplashBitmap *bitmap;
+  Stream *str0, *str;
+  Object obj;
+  Guchar *p;
+  Guchar col[4];
+  double m0, m1, m2, m3, m4, m5;
+  int c, w, h, x, y, comp, i;
+
+  scan = new PreScanOutputDev();
+  page->displaySlice(scan, 72, 72, rotateA, useMediaBox, crop,
+		     sliceX, sliceY, sliceW, sliceH,
+		     printing, catalog, abortCheckCbk, abortCheckCbkData);
+  rasterize = scan->usesTransparency();
+  delete scan;
+  if (!rasterize) {
+    return gTrue;
+  }
+
+  // rasterize the page
+  if (level == psLevel1) {
+    paperColor[0] = 0xff;
+    splashOut = new SplashOutputDev(splashModeMono8, 1, gFalse,
+				    paperColor, gTrue, gFalse);
+#if SPLASH_CMYK
+  } else if (level == psLevel1Sep) {
+    paperColor[0] = paperColor[1] = paperColor[2] = paperColor[3] = 0;
+    splashOut = new SplashOutputDev(splashModeCMYK8, 1, gFalse,
+				    paperColor, gTrue, gFalse);
+#endif
+  } else {
+    paperColor[0] = paperColor[1] = paperColor[2] = 0xff;
+    splashOut = new SplashOutputDev(splashModeRGB8, 1, gFalse,
+				    paperColor, gTrue, gFalse);
+  }
+  splashOut->startDoc(xref);
+  page->displaySlice(splashOut, splashDPI, splashDPI, rotateA,
+		     useMediaBox, crop,
+		     sliceX, sliceY, sliceW, sliceH,
+		     printing, catalog, abortCheckCbk, abortCheckCbkData);
+
+  // start the PS page
+  page->makeBox(splashDPI, splashDPI, rotateA, useMediaBox, gFalse,
+		sliceX, sliceY, sliceW, sliceH, &box, &crop);
+  rotateA += page->getRotate();
+  if (rotateA >= 360) {
+    rotateA -= 360;
+  } else if (rotateA < 0) {
+    rotateA += 360;
+  }
+  state = new GfxState(splashDPI, splashDPI, &box, rotateA, gFalse);
+  startPage(page->getNum(), state);
+  delete state;
+  switch (rotateA) {
+  case 0:
+  default:  // this should never happen
+    m0 = box.x2 - box.x1;
+    m1 = 0;
+    m2 = 0;
+    m3 = box.y2 - box.y1;
+    m4 = box.x1;
+    m5 = box.y1;
+    break;
+  case 90:
+    m0 = 0;
+    m1 = box.y2 - box.y1;
+    m2 = -(box.x2 - box.x1);
+    m3 = 0;
+    m4 = box.x2;
+    m5 = box.y1;
+    break;
+  case 180:
+    m0 = -(box.x2 - box.x1);
+    m1 = 0;
+    m2 = 0;
+    m3 = -(box.y2 - box.y1);
+    m4 = box.x2;
+    m5 = box.y2;
+    break;
+  case 270:
+    m0 = 0;
+    m1 = -(box.y2 - box.y1);
+    m2 = box.x2 - box.x1;
+    m3 = 0;
+    m4 = box.x1;
+    m5 = box.y2;
+    break;
+  }
+
+  //~ need to add the process colors
+
+  // draw the rasterized image
+  bitmap = splashOut->getBitmap();
+  w = bitmap->getWidth();
+  h = bitmap->getHeight();
+  writePS("gsave\n");
+  writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] concat\n",
+	     m0, m1, m2, m3, m4, m5);
+  switch (level) {
+  case psLevel1:
+    writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1\n",
+	       w, h, w, -h, h);
+    p = bitmap->getDataPtr();
+    i = 0;
+    for (y = 0; y < h; ++y) {
+      for (x = 0; x < w; ++x) {
+	writePSFmt("{0:02x}", *p++);
+	if (++i == 32) {
+	  writePSChar('\n');
+	  i = 0;
+	}
+      }
+    }
+    if (i != 0) {
+      writePSChar('\n');
+    }
+    break;
+  case psLevel1Sep:
+    writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1Sep\n",
+	       w, h, w, -h, h);
+    p = bitmap->getDataPtr();
+    i = 0;
+    col[0] = col[1] = col[2] = col[3] = 0;
+    for (y = 0; y < h; ++y) {
+      for (comp = 0; comp < 4; ++comp) {
+	for (x = 0; x < w; ++x) {
+	  writePSFmt("{0:02x}", p[4*x + comp]);
+	  col[comp] |= p[4*x + comp];
+	  if (++i == 32) {
+	    writePSChar('\n');
+	    i = 0;
+	  }
+	}
+      }
+      p += bitmap->getRowSize();
+    }
+    if (i != 0) {
+      writePSChar('\n');
+    }
+    if (col[0]) {
+      processColors |= psProcessCyan;
+    }
+    if (col[1]) {
+      processColors |= psProcessMagenta;
+    }
+    if (col[2]) {
+      processColors |= psProcessYellow;
+    }
+    if (col[3]) {
+      processColors |= psProcessBlack;
+    }
+    break;
+  case psLevel2:
+  case psLevel2Sep:
+  case psLevel3:
+  case psLevel3Sep:
+    writePS("/DeviceRGB setcolorspace\n");
+    writePS("<<\n  /ImageType 1\n");
+    writePSFmt("  /Width {0:d}\n", bitmap->getWidth());
+    writePSFmt("  /Height {0:d}\n", bitmap->getHeight());
+    writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n", w, -h, h);
+    writePS("  /BitsPerComponent 8\n");
+    writePS("  /Decode [0 1 0 1 0 1]\n");
+    writePS("  /DataSource currentfile\n");
+    if (globalParams->getPSASCIIHex()) {
+      writePS("    /ASCIIHexDecode filter\n");
+    } else {
+      writePS("    /ASCII85Decode filter\n");
+    }
+    writePS("    /RunLengthDecode filter\n");
+    writePS(">>\n");
+    writePS("image\n");
+    obj.initNull();
+    str0 = new MemStream((char *)bitmap->getDataPtr(), 0, w * h * 3, &obj);
+    str = new RunLengthEncoder(str0);
+    if (globalParams->getPSASCIIHex()) {
+      str = new ASCIIHexEncoder(str);
+    } else {
+      str = new ASCII85Encoder(str);
+    }
+    str->reset();
+    while ((c = str->getChar()) != EOF) {
+      writePSChar(c);
+    }
+    str->close();
+    delete str;
+    delete str0;
+    processColors |= psProcessCMYK;
+    break;
+  }
+  delete splashOut;
+  writePS("grestore\n");
+
+  // finish the PS page
+  endPage();
+
+  return gFalse;
+#else
+  return gTrue;
+#endif
+}
+
+void PSOutputDev::startPage(int pageNum, GfxState *state) {
+  int x1, y1, x2, y2, width, height;
+  int imgWidth, imgHeight, imgWidth2, imgHeight2;
+  GBool landscape;
+
+
+  if (mode == psModePS) {
+    writePSFmt("%%Page: {0:d} {1:d}\n", pageNum, seqPage);
+    writePS("%%BeginPageSetup\n");
+  }
+
+  // underlays
+  if (underlayCbk) {
+    (*underlayCbk)(this, underlayCbkData);
+  }
+  if (overlayCbk) {
+    saveState(NULL);
+  }
+
+  switch (mode) {
+
+  case psModePS:
+    // rotate, translate, and scale page
+    imgWidth = imgURX - imgLLX;
+    imgHeight = imgURY - imgLLY;
+    x1 = (int)floor(state->getX1());
+    y1 = (int)floor(state->getY1());
+    x2 = (int)ceil(state->getX2());
+    y2 = (int)ceil(state->getY2());
+    width = x2 - x1;
+    height = y2 - y1;
+    tx = ty = 0;
+    // rotation and portrait/landscape mode
+    if (rotate0 >= 0) {
+      rotate = (360 - rotate0) % 360;
+      landscape = gFalse;
+    } else {
+      rotate = (360 - state->getRotate()) % 360;
+      if (rotate == 0 || rotate == 180) {
+	if (width > height && width > imgWidth) {
+	  rotate += 90;
+	  landscape = gTrue;
+	} else {
+	  landscape = gFalse;
+	}
+      } else { // rotate == 90 || rotate == 270
+	if (height > width && height > imgWidth) {
+	  rotate = 270 - rotate;
+	  landscape = gTrue;
+	} else {
+	  landscape = gFalse;
+	}
+      }
+    }
+    writePSFmt("%%PageOrientation: {0:s}\n",
+	       landscape ? "Landscape" : "Portrait");
+    writePS("pdfStartPage\n");
+    if (rotate == 0) {
+      imgWidth2 = imgWidth;
+      imgHeight2 = imgHeight;
+    } else if (rotate == 90) {
+      writePS("90 rotate\n");
+      ty = -imgWidth;
+      imgWidth2 = imgHeight;
+      imgHeight2 = imgWidth;
+    } else if (rotate == 180) {
+      writePS("180 rotate\n");
+      imgWidth2 = imgWidth;
+      imgHeight2 = imgHeight;
+      tx = -imgWidth;
+      ty = -imgHeight;
+    } else { // rotate == 270
+      writePS("270 rotate\n");
+      tx = -imgHeight;
+      imgWidth2 = imgHeight;
+      imgHeight2 = imgWidth;
+    }
+    // shrink or expand
+    if (xScale0 > 0 && yScale0 > 0) {
+      xScale = xScale0;
+      yScale = yScale0;
+    } else if ((globalParams->getPSShrinkLarger() &&
+	 (width > imgWidth2 || height > imgHeight2)) ||
+	(globalParams->getPSExpandSmaller() &&
+	 (width < imgWidth2 && height < imgHeight2))) {
+      xScale = (double)imgWidth2 / (double)width;
+      yScale = (double)imgHeight2 / (double)height;
+      if (yScale < xScale) {
+	xScale = yScale;
+      } else {
+	yScale = xScale;
+      }
+    } else {
+      xScale = yScale = 1;
+    }
+    // deal with odd bounding boxes or clipping
+    if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
+      tx -= xScale * clipLLX0;
+      ty -= yScale * clipLLY0;
+    } else {
+      tx -= xScale * x1;
+      ty -= yScale * y1;
+    }
+    // center
+    if (tx0 >= 0 && ty0 >= 0) {
+      tx += rotate == 0 ? tx0 : ty0;
+      ty += rotate == 0 ? ty0 : -tx0;
+    } else if (globalParams->getPSCenter()) {
+      if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
+	tx += (imgWidth2 - xScale * (clipURX0 - clipLLX0)) / 2;
+	ty += (imgHeight2 - yScale * (clipURY0 - clipLLY0)) / 2;
+      } else {
+	tx += (imgWidth2 - xScale * width) / 2;
+	ty += (imgHeight2 - yScale * height) / 2;
+      }
+    }
+    tx += rotate == 0 ? imgLLX : imgLLY;
+    ty += rotate == 0 ? imgLLY : -imgLLX;
+    if (tx != 0 || ty != 0) {
+      writePSFmt("{0:.4g} {1:.4g} translate\n", tx, ty);
+    }
+    if (xScale != 1 || yScale != 1) {
+      writePSFmt("{0:.4f} {0:.4f} scale\n", xScale);
+    }
+    if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) {
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} re W\n",
+		 clipLLX0, clipLLY0, clipURX0 - clipLLX0, clipURY0 - clipLLY0);
+    } else {
+      writePSFmt("{0:d} {1:d} {2:d} {3:d} re W\n", x1, y1, x2 - x1, y2 - y1);
+    }
+
+    writePS("%%EndPageSetup\n");
+    ++seqPage;
+    break;
+
+  case psModeEPS:
+    writePS("pdfStartPage\n");
+    tx = ty = 0;
+    rotate = (360 - state->getRotate()) % 360;
+    if (rotate == 0) {
+    } else if (rotate == 90) {
+      writePS("90 rotate\n");
+      tx = -epsX1;
+      ty = -epsY2;
+    } else if (rotate == 180) {
+      writePS("180 rotate\n");
+      tx = -(epsX1 + epsX2);
+      ty = -(epsY1 + epsY2);
+    } else { // rotate == 270
+      writePS("270 rotate\n");
+      tx = -epsX2;
+      ty = -epsY1;
+    }
+    if (tx != 0 || ty != 0) {
+      writePSFmt("{0:.4g} {1:.4g} translate\n", tx, ty);
+    }
+    xScale = yScale = 1;
+    break;
+
+  case psModeForm:
+    writePS("/PaintProc {\n");
+    writePS("begin xpdf begin\n");
+    writePS("pdfStartPage\n");
+    tx = ty = 0;
+    xScale = yScale = 1;
+    rotate = 0;
+    break;
+  }
+}
+
+void PSOutputDev::endPage() {
+  if (overlayCbk) {
+    restoreState(NULL);
+    (*overlayCbk)(this, overlayCbkData);
+  }
+
+
+  if (mode == psModeForm) {
+    writePS("pdfEndPage\n");
+    writePS("end end\n");
+    writePS("} def\n");
+    writePS("end end\n");
+  } else {
+    if (!manualCtrl) {
+      writePS("showpage\n");
+    }
+    writePS("%%PageTrailer\n");
+    writePageTrailer();
+  }
+}
+
+void PSOutputDev::saveState(GfxState *state) {
+  writePS("q\n");
+  ++numSaves;
+}
+
+void PSOutputDev::restoreState(GfxState *state) {
+  writePS("Q\n");
+  --numSaves;
+}
+
+void PSOutputDev::updateCTM(GfxState *state, double m11, double m12,
+			    double m21, double m22, double m31, double m32) {
+  writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] cm\n",
+	     m11, m12, m21, m22, m31, m32);
+}
+
+void PSOutputDev::updateLineDash(GfxState *state) {
+  double *dash;
+  double start;
+  int length, i;
+
+  state->getLineDash(&dash, &length, &start);
+  writePS("[");
+  for (i = 0; i < length; ++i) {
+    writePSFmt("{0:.4g}{1:w}",
+	       dash[i] < 0 ? 0 : dash[i],
+	       (i == length-1) ? 0 : 1);
+  }
+  writePSFmt("] {0:.4g} d\n", start);
+}
+
+void PSOutputDev::updateFlatness(GfxState *state) {
+  writePSFmt("{0:d} i\n", state->getFlatness());
+}
+
+void PSOutputDev::updateLineJoin(GfxState *state) {
+  writePSFmt("{0:d} j\n", state->getLineJoin());
+}
+
+void PSOutputDev::updateLineCap(GfxState *state) {
+  writePSFmt("{0:d} J\n", state->getLineCap());
+}
+
+void PSOutputDev::updateMiterLimit(GfxState *state) {
+  writePSFmt("{0:.4g} M\n", state->getMiterLimit());
+}
+
+void PSOutputDev::updateLineWidth(GfxState *state) {
+  writePSFmt("{0:.4g} w\n", state->getLineWidth());
+}
+
+void PSOutputDev::updateFillColorSpace(GfxState *state) {
+  switch (level) {
+  case psLevel1:
+  case psLevel1Sep:
+    break;
+  case psLevel2:
+  case psLevel3:
+    if (state->getFillColorSpace()->getMode() != csPattern) {
+      dumpColorSpaceL2(state->getFillColorSpace(), gTrue, gFalse, gFalse);
+      writePS(" cs\n");
+    }
+    break;
+  case psLevel2Sep:
+  case psLevel3Sep:
+    break;
+  }
+}
+
+void PSOutputDev::updateStrokeColorSpace(GfxState *state) {
+  switch (level) {
+  case psLevel1:
+  case psLevel1Sep:
+    break;
+  case psLevel2:
+  case psLevel3:
+    if (state->getStrokeColorSpace()->getMode() != csPattern) {
+      dumpColorSpaceL2(state->getStrokeColorSpace(), gTrue, gFalse, gFalse);
+      writePS(" CS\n");
+    }
+    break;
+  case psLevel2Sep:
+  case psLevel3Sep:
+    break;
+  }
+}
+
+void PSOutputDev::updateFillColor(GfxState *state) {
+  GfxColor color;
+  GfxColor *colorPtr;
+  GfxGray gray;
+  GfxCMYK cmyk;
+  GfxSeparationColorSpace *sepCS;
+  double c, m, y, k;
+  int i;
+
+  switch (level) {
+  case psLevel1:
+    state->getFillGray(&gray);
+    writePSFmt("{0:.4g} g\n", colToDbl(gray));
+    break;
+  case psLevel1Sep:
+    state->getFillCMYK(&cmyk);
+    c = colToDbl(cmyk.c);
+    m = colToDbl(cmyk.m);
+    y = colToDbl(cmyk.y);
+    k = colToDbl(cmyk.k);
+    writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} k\n", c, m, y, k);
+    addProcessColor(c, m, y, k);
+    break;
+  case psLevel2:
+  case psLevel3:
+    if (state->getFillColorSpace()->getMode() != csPattern) {
+      colorPtr = state->getFillColor();
+      writePS("[");
+      for (i = 0; i < state->getFillColorSpace()->getNComps(); ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePSFmt("{0:.4g}", colToDbl(colorPtr->c[i]));
+      }
+      writePS("] sc\n");
+    }
+    break;
+  case psLevel2Sep:
+  case psLevel3Sep:
+    if (state->getFillColorSpace()->getMode() == csSeparation) {
+      sepCS = (GfxSeparationColorSpace *)state->getFillColorSpace();
+      color.c[0] = gfxColorComp1;
+      sepCS->getCMYK(&color, &cmyk);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) ck\n",
+		 colToDbl(state->getFillColor()->c[0]),
+		 colToDbl(cmyk.c), colToDbl(cmyk.m),
+		 colToDbl(cmyk.y), colToDbl(cmyk.k),
+		 sepCS->getName());
+      addCustomColor(sepCS);
+    } else {
+      state->getFillCMYK(&cmyk);
+      c = colToDbl(cmyk.c);
+      m = colToDbl(cmyk.m);
+      y = colToDbl(cmyk.y);
+      k = colToDbl(cmyk.k);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} k\n", c, m, y, k);
+      addProcessColor(c, m, y, k);
+    }
+    break;
+  }
+  t3Cacheable = gFalse;
+}
+
+void PSOutputDev::updateStrokeColor(GfxState *state) {
+  GfxColor color;
+  GfxColor *colorPtr;
+  GfxGray gray;
+  GfxCMYK cmyk;
+  GfxSeparationColorSpace *sepCS;
+  double c, m, y, k;
+  int i;
+
+  switch (level) {
+  case psLevel1:
+    state->getStrokeGray(&gray);
+    writePSFmt("{0:.4g} G\n", colToDbl(gray));
+    break;
+  case psLevel1Sep:
+    state->getStrokeCMYK(&cmyk);
+    c = colToDbl(cmyk.c);
+    m = colToDbl(cmyk.m);
+    y = colToDbl(cmyk.y);
+    k = colToDbl(cmyk.k);
+    writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} K\n", c, m, y, k);
+    addProcessColor(c, m, y, k);
+    break;
+  case psLevel2:
+  case psLevel3:
+    if (state->getStrokeColorSpace()->getMode() != csPattern) {
+      colorPtr = state->getStrokeColor();
+      writePS("[");
+      for (i = 0; i < state->getStrokeColorSpace()->getNComps(); ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePSFmt("{0:.4g}", colToDbl(colorPtr->c[i]));
+      }
+      writePS("] SC\n");
+    }
+    break;
+  case psLevel2Sep:
+  case psLevel3Sep:
+    if (state->getStrokeColorSpace()->getMode() == csSeparation) {
+      sepCS = (GfxSeparationColorSpace *)state->getStrokeColorSpace();
+      color.c[0] = gfxColorComp1;
+      sepCS->getCMYK(&color, &cmyk);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) CK\n",
+		 colToDbl(state->getStrokeColor()->c[0]),
+		 colToDbl(cmyk.c), colToDbl(cmyk.m),
+		 colToDbl(cmyk.y), colToDbl(cmyk.k),
+		 sepCS->getName());
+      addCustomColor(sepCS);
+    } else {
+      state->getStrokeCMYK(&cmyk);
+      c = colToDbl(cmyk.c);
+      m = colToDbl(cmyk.m);
+      y = colToDbl(cmyk.y);
+      k = colToDbl(cmyk.k);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} K\n", c, m, y, k);
+      addProcessColor(c, m, y, k);
+    }
+    break;
+  }
+  t3Cacheable = gFalse;
+}
+
+void PSOutputDev::addProcessColor(double c, double m, double y, double k) {
+  if (c > 0) {
+    processColors |= psProcessCyan;
+  }
+  if (m > 0) {
+    processColors |= psProcessMagenta;
+  }
+  if (y > 0) {
+    processColors |= psProcessYellow;
+  }
+  if (k > 0) {
+    processColors |= psProcessBlack;
+  }
+}
+
+void PSOutputDev::addCustomColor(GfxSeparationColorSpace *sepCS) {
+  PSOutCustomColor *cc;
+  GfxColor color;
+  GfxCMYK cmyk;
+
+  for (cc = customColors; cc; cc = cc->next) {
+    if (!cc->name->cmp(sepCS->getName())) {
+      return;
+    }
+  }
+  color.c[0] = gfxColorComp1;
+  sepCS->getCMYK(&color, &cmyk);
+  cc = new PSOutCustomColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
+			    colToDbl(cmyk.y), colToDbl(cmyk.k),
+			    sepCS->getName()->copy());
+  cc->next = customColors;
+  customColors = cc;
+}
+
+void PSOutputDev::updateFillOverprint(GfxState *state) {
+  if (level >= psLevel2) {
+    writePSFmt("{0:s} op\n", state->getFillOverprint() ? "true" : "false");
+  }
+}
+
+void PSOutputDev::updateStrokeOverprint(GfxState *state) {
+  if (level >= psLevel2) {
+    writePSFmt("{0:s} OP\n", state->getStrokeOverprint() ? "true" : "false");
+  }
+}
+
+void PSOutputDev::updateTransfer(GfxState *state) {
+  Function **funcs;
+  int i;
+
+  funcs = state->getTransfer();
+  if (funcs[0] && funcs[1] && funcs[2] && funcs[3]) {
+    if (level >= psLevel2) {
+      for (i = 0; i < 4; ++i) {
+	cvtFunction(funcs[i]);
+      }
+      writePS("setcolortransfer\n");
+    } else {
+      cvtFunction(funcs[3]);
+      writePS("settransfer\n");
+    }
+  } else if (funcs[0]) {
+    cvtFunction(funcs[0]);
+    writePS("settransfer\n");
+  } else {
+    writePS("{} settransfer\n");
+  }
+}
+
+void PSOutputDev::updateFont(GfxState *state) {
+  if (state->getFont()) {
+    writePSFmt("/F{0:d}_{1:d} {2:.4g} Tf\n",
+	       state->getFont()->getID()->num, state->getFont()->getID()->gen,
+	       fabs(state->getFontSize()) < 0.00001 ? 0.00001
+	                                            : state->getFontSize());
+  }
+}
+
+void PSOutputDev::updateTextMat(GfxState *state) {
+  double *mat;
+
+  mat = state->getTextMat();
+  if (fabs(mat[0] * mat[3] - mat[1] * mat[2]) < 0.00001) {
+    // avoid a singular (or close-to-singular) matrix
+    writePSFmt("[0.00001 0 0 0.00001 {0:.4g} {1:.4g}] Tm\n", mat[4], mat[5]);
+  } else {
+    writePSFmt("[{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] Tm\n",
+	       mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
+  }
+}
+
+void PSOutputDev::updateCharSpace(GfxState *state) {
+  writePSFmt("{0:.4g} Tc\n", state->getCharSpace());
+}
+
+void PSOutputDev::updateRender(GfxState *state) {
+  int rm;
+
+  rm = state->getRender();
+  writePSFmt("{0:d} Tr\n", rm);
+  rm &= 3;
+  if (rm != 0 && rm != 3) {
+    t3Cacheable = gFalse;
+  }
+}
+
+void PSOutputDev::updateRise(GfxState *state) {
+  writePSFmt("{0:.4g} Ts\n", state->getRise());
+}
+
+void PSOutputDev::updateWordSpace(GfxState *state) {
+  writePSFmt("{0:.4g} Tw\n", state->getWordSpace());
+}
+
+void PSOutputDev::updateHorizScaling(GfxState *state) {
+  double h;
+
+  h = state->getHorizScaling();
+  if (fabs(h) < 0.01) {
+    h = 0.01;
+  }
+  writePSFmt("{0:.4g} Tz\n", h);
+}
+
+void PSOutputDev::updateTextPos(GfxState *state) {
+  writePSFmt("{0:.4g} {1:.4g} Td\n", state->getLineX(), state->getLineY());
+}
+
+void PSOutputDev::updateTextShift(GfxState *state, double shift) {
+  if (state->getFont()->getWMode()) {
+    writePSFmt("{0:.4g} TJmV\n", shift);
+  } else {
+    writePSFmt("{0:.4g} TJm\n", shift);
+  }
+}
+
+void PSOutputDev::stroke(GfxState *state) {
+  doPath(state->getPath());
+  if (t3String) {
+    // if we're construct a cacheable Type 3 glyph, we need to do
+    // everything in the fill color
+    writePS("Sf\n");
+  } else {
+    writePS("S\n");
+  }
+}
+
+void PSOutputDev::fill(GfxState *state) {
+  doPath(state->getPath());
+  writePS("f\n");
+}
+
+void PSOutputDev::eoFill(GfxState *state) {
+  doPath(state->getPath());
+  writePS("f*\n");
+}
+
+void PSOutputDev::tilingPatternFill(GfxState *state, Object *str,
+				    int paintType, Dict *resDict,
+				    double *mat, double *bbox,
+				    int x0, int y0, int x1, int y1,
+				    double xStep, double yStep) {
+  PDFRectangle box;
+  Gfx *gfx;
+
+  // define a Type 3 font
+  writePS("8 dict begin\n");
+  writePS("/FontType 3 def\n");
+  writePS("/FontMatrix [1 0 0 1 0 0] def\n");
+  writePSFmt("/FontBBox [{0:.4g} {1:.4g} {2:.4g} {3:.4g}] def\n",
+	     bbox[0], bbox[1], bbox[2], bbox[3]);
+  writePS("/Encoding 256 array def\n");
+  writePS("  0 1 255 { Encoding exch /.notdef put } for\n");
+  writePS("  Encoding 120 /x put\n");
+  writePS("/BuildGlyph {\n");
+  writePS("  exch /CharProcs get exch\n");
+  writePS("  2 copy known not { pop /.notdef } if\n");
+  writePS("  get exec\n");
+  writePS("} bind def\n");
+  writePS("/BuildChar {\n");
+  writePS("  1 index /Encoding get exch get\n");
+  writePS("  1 index /BuildGlyph get exec\n");
+  writePS("} bind def\n");
+  writePS("/CharProcs 1 dict def\n");
+  writePS("CharProcs begin\n");
+  box.x1 = bbox[0];
+  box.y1 = bbox[1];
+  box.x2 = bbox[2];
+  box.y2 = bbox[3];
+  gfx = new Gfx(xref, this, resDict, &box, NULL);
+  writePS("/x {\n");
+  if (paintType == 2) {
+    writePSFmt("{0:.4g} 0 {1:.4g} {2:.4g} {3:.4g} {4:.4g} setcachedevice\n",
+	       xStep, bbox[0], bbox[1], bbox[2], bbox[3]);
+  } else {
+    if (x1 - 1 <= x0) {
+      writePS("1 0 setcharwidth\n");
+    } else {
+      writePSFmt("{0:.4g} 0 setcharwidth\n", xStep);
+    }
+  }
+  inType3Char = gTrue;
+  ++numTilingPatterns;
+  gfx->display(str);
+  --numTilingPatterns;
+  inType3Char = gFalse;
+  writePS("} def\n");
+  delete gfx;
+  writePS("end\n");
+  writePS("currentdict end\n");
+  writePSFmt("/xpdfTile{0:d} exch definefont pop\n", numTilingPatterns);
+
+  // draw the tiles
+  writePSFmt("/xpdfTile{0:d} findfont setfont\n", numTilingPatterns);
+  writePSFmt("gsave [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] concat\n",
+	     mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
+  writePSFmt("{0:d} 1 {1:d} {{ {2:.4g} exch {3:.4g} mul m {4:d} 1 {5:d} {{ pop (x) show }} for }} for\n",
+	     y0, y1 - 1, x0 * xStep, yStep, x0, x1 - 1);
+  writePS("grestore\n");
+}
+
+GBool PSOutputDev::functionShadedFill(GfxState *state,
+				      GfxFunctionShading *shading) {
+  double x0, y0, x1, y1;
+  double *mat;
+  int i;
+
+  if (level == psLevel2Sep || level == psLevel3Sep) {
+    if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
+      return gFalse;
+    }
+    processColors |= psProcessCMYK;
+  }
+
+  shading->getDomain(&x0, &y0, &x1, &y1);
+  mat = shading->getMatrix();
+  writePSFmt("/mat [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g}] def\n",
+	     mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]);
+  writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
+  if (shading->getNFuncs() == 1) {
+    writePS("/func ");
+    cvtFunction(shading->getFunc(0));
+    writePS("def\n");
+  } else {
+    writePS("/func {\n");
+    for (i = 0; i < shading->getNFuncs(); ++i) {
+      if (i < shading->getNFuncs() - 1) {
+	writePS("2 copy\n");
+      }
+      cvtFunction(shading->getFunc(i));
+      writePS("exec\n");
+      if (i < shading->getNFuncs() - 1) {
+	writePS("3 1 roll\n");
+      }
+    }
+    writePS("} def\n");
+  }
+  writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} 0 funcSH\n", x0, y0, x1, y1);
+
+  return gTrue;
+}
+
+GBool PSOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading) {
+  double xMin, yMin, xMax, yMax;
+  double x0, y0, x1, y1, dx, dy, mul;
+  double tMin, tMax, t, t0, t1;
+  int i;
+
+  if (level == psLevel2Sep || level == psLevel3Sep) {
+    if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
+      return gFalse;
+    }
+    processColors |= psProcessCMYK;
+  }
+
+  // get the clip region bbox
+  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
+
+  // compute min and max t values, based on the four corners of the
+  // clip region bbox
+  shading->getCoords(&x0, &y0, &x1, &y1);
+  dx = x1 - x0;
+  dy = y1 - y0;
+  if (fabs(dx) < 0.01 && fabs(dy) < 0.01) {
+    return gTrue;
+  } else {
+    mul = 1 / (dx * dx + dy * dy);
+    tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul;
+    t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul;
+    if (t < tMin) {
+      tMin = t;
+    } else if (t > tMax) {
+      tMax = t;
+    }
+    if (tMin < 0 && !shading->getExtend0()) {
+      tMin = 0;
+    }
+    if (tMax > 1 && !shading->getExtend1()) {
+      tMax = 1;
+    }
+  }
+
+  // get the function domain
+  t0 = shading->getDomain0();
+  t1 = shading->getDomain1();
+
+  // generate the PS code
+  writePSFmt("/t0 {0:.4g} def\n", t0);
+  writePSFmt("/t1 {0:.4g} def\n", t1);
+  writePSFmt("/dt {0:.4g} def\n", t1 - t0);
+  writePSFmt("/x0 {0:.4g} def\n", x0);
+  writePSFmt("/y0 {0:.4g} def\n", y0);
+  writePSFmt("/dx {0:.4g} def\n", x1 - x0);
+  writePSFmt("/x1 {0:.4g} def\n", x1);
+  writePSFmt("/y1 {0:.4g} def\n", y1);
+  writePSFmt("/dy {0:.4g} def\n", y1 - y0);
+  writePSFmt("/xMin {0:.4g} def\n", xMin);
+  writePSFmt("/yMin {0:.4g} def\n", yMin);
+  writePSFmt("/xMax {0:.4g} def\n", xMax);
+  writePSFmt("/yMax {0:.4g} def\n", yMax);
+  writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
+  if (shading->getNFuncs() == 1) {
+    writePS("/func ");
+    cvtFunction(shading->getFunc(0));
+    writePS("def\n");
+  } else {
+    writePS("/func {\n");
+    for (i = 0; i < shading->getNFuncs(); ++i) {
+      if (i < shading->getNFuncs() - 1) {
+	writePS("dup\n");
+      }
+      cvtFunction(shading->getFunc(i));
+      writePS("exec\n");
+      if (i < shading->getNFuncs() - 1) {
+	writePS("exch\n");
+      }
+    }
+    writePS("} def\n");
+  }
+  writePSFmt("{0:.4g} {1:.4g} 0 axialSH\n", tMin, tMax);
+
+  return gTrue;
+}
+
+GBool PSOutputDev::radialShadedFill(GfxState *state,
+				    GfxRadialShading *shading) {
+  double xMin, yMin, xMax, yMax;
+  double x0, y0, r0, x1, y1, r1, t0, t1;
+  double xa, ya, ra;
+  double sz, xz, yz, sMin, sMax, sa, ta;
+  double theta, alpha, a1, a2;
+  GBool enclosed;
+  int i;
+
+  if (level == psLevel2Sep || level == psLevel3Sep) {
+    if (shading->getColorSpace()->getMode() != csDeviceCMYK) {
+      return gFalse;
+    }
+    processColors |= psProcessCMYK;
+  }
+
+  // get the shading info
+  shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
+  t0 = shading->getDomain0();
+  t1 = shading->getDomain1();
+
+  // Compute the point at which r(s) = 0; check for the enclosed
+  // circles case; and compute the angles for the tangent lines.
+  if (r0 == r1) {
+    enclosed = x0 == x1 && y0 == y1;
+    theta = 0;
+    sz = 0; // make gcc happy
+  } else {
+    sz = -r0 / (r1 - r0);
+    xz = x0 + sz * (x1 - x0);
+    yz = y0 + sz * (y1 - y0);
+    enclosed = (xz - x0) * (xz - x0) + (yz - y0) * (yz - y0) <= r0 * r0;
+    theta = asin(r0 / sqrt((x0 - xz) * (x0 - xz) + (y0 - yz) * (y0 - yz)));
+    if (r0 > r1) {
+      theta = -theta;
+    }
+  }
+  if (enclosed) {
+    a1 = 0;
+    a2 = 360;
+  } else {
+    alpha = atan2(y1 - y0, x1 - x0);
+    a1 = (180 / M_PI) * (alpha + theta) + 90;
+    a2 = (180 / M_PI) * (alpha - theta) - 90;
+    while (a2 < a1) {
+      a2 += 360;
+    }
+  }
+
+  // compute the (possibly extended) s range
+  state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
+  if (enclosed) {
+    sMin = 0;
+    sMax = 1;
+  } else {
+    sMin = 1;
+    sMax = 0;
+    // solve for x(s) + r(s) = xMin
+    if ((x1 + r1) - (x0 + r0) != 0) {
+      sa = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // solve for x(s) - r(s) = xMax
+    if ((x1 - r1) - (x0 - r0) != 0) {
+      sa = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // solve for y(s) + r(s) = yMin
+    if ((y1 + r1) - (y0 + r0) != 0) {
+      sa = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // solve for y(s) - r(s) = yMax
+    if ((y1 - r1) - (y0 - r0) != 0) {
+      sa = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0));
+      if (sa < sMin) {
+	sMin = sa;
+      } else if (sa > sMax) {
+	sMax = sa;
+      }
+    }
+    // check against sz
+    if (r0 < r1) {
+      if (sMin < sz) {
+	sMin = sz;
+      }
+    } else if (r0 > r1) {
+      if (sMax > sz) {
+	sMax = sz;
+      }
+    }
+    // check the 'extend' flags
+    if (!shading->getExtend0() && sMin < 0) {
+      sMin = 0;
+    }
+    if (!shading->getExtend1() && sMax > 1) {
+      sMax = 1;
+    }
+  }
+
+  // generate the PS code
+  writePSFmt("/x0 {0:.4g} def\n", x0);
+  writePSFmt("/x1 {0:.4g} def\n", x1);
+  writePSFmt("/dx {0:.4g} def\n", x1 - x0);
+  writePSFmt("/y0 {0:.4g} def\n", y0);
+  writePSFmt("/y1 {0:.4g} def\n", y1);
+  writePSFmt("/dy {0:.4g} def\n", y1 - y0);
+  writePSFmt("/r0 {0:.4g} def\n", r0);
+  writePSFmt("/r1 {0:.4g} def\n", r1);
+  writePSFmt("/dr {0:.4g} def\n", r1 - r0);
+  writePSFmt("/t0 {0:.4g} def\n", t0);
+  writePSFmt("/t1 {0:.4g} def\n", t1);
+  writePSFmt("/dt {0:.4g} def\n", t1 - t0);
+  writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps());
+  writePSFmt("/encl {0:s} def\n", enclosed ? "true" : "false");
+  writePSFmt("/a1 {0:.4g} def\n", a1);
+  writePSFmt("/a2 {0:.4g} def\n", a2);
+  if (shading->getNFuncs() == 1) {
+    writePS("/func ");
+    cvtFunction(shading->getFunc(0));
+    writePS("def\n");
+  } else {
+    writePS("/func {\n");
+    for (i = 0; i < shading->getNFuncs(); ++i) {
+      if (i < shading->getNFuncs() - 1) {
+	writePS("dup\n");
+      }
+      cvtFunction(shading->getFunc(i));
+      writePS("exec\n");
+      if (i < shading->getNFuncs() - 1) {
+	writePS("exch\n");
+      }
+    }
+    writePS("} def\n");
+  }
+  writePSFmt("{0:.4g} {1:.4g} 0 radialSH\n", sMin, sMax);
+
+  // extend the 'enclosed' case
+  if (enclosed) {
+    // extend the smaller circle
+    if ((shading->getExtend0() && r0 <= r1) ||
+	(shading->getExtend1() && r1 < r0)) {
+      if (r0 <= r1) {
+	ta = t0;
+	ra = r0;
+	xa = x0;
+	ya = y0;
+      } else {
+	ta = t1;
+	ra = r1;
+	xa = x1;
+	ya = y1;
+      }
+      if (level == psLevel2Sep || level == psLevel3Sep) {
+	writePSFmt("{0:.4g} radialCol aload pop k\n", ta);
+      } else {
+	writePSFmt("{0:.4g} radialCol sc\n", ta);
+      }
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} 0 360 arc h f*\n", xa, ya, ra);
+    }
+
+    // extend the larger circle
+    if ((shading->getExtend0() && r0 > r1) ||
+	(shading->getExtend1() && r1 >= r0)) {
+      if (r0 > r1) {
+	ta = t0;
+	ra = r0;
+	xa = x0;
+	ya = y0;
+      } else {
+	ta = t1;
+	ra = r1;
+	xa = x1;
+	ya = y1;
+      }
+      if (level == psLevel2Sep || level == psLevel3Sep) {
+	writePSFmt("{0:.4g} radialCol aload pop k\n", ta);
+      } else {
+	writePSFmt("{0:.4g} radialCol sc\n", ta);
+      }
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} 0 360 arc h\n", xa, ya, ra);
+      writePSFmt("{0:.4g} {1:.4g} m {2:.4g} {3:.4g} l {4:.4g} {5:.4g} l {6:.4g} {7:.4g} l h f*\n",
+		 xMin, yMin, xMin, yMax, xMax, yMax, xMax, yMin);
+    }
+  }
+
+  return gTrue;
+}
+
+void PSOutputDev::clip(GfxState *state) {
+  doPath(state->getPath());
+  writePS("W\n");
+}
+
+void PSOutputDev::eoClip(GfxState *state) {
+  doPath(state->getPath());
+  writePS("W*\n");
+}
+
+void PSOutputDev::clipToStrokePath(GfxState *state) {
+  doPath(state->getPath());
+  writePS("Ws\n");
+}
+
+void PSOutputDev::doPath(GfxPath *path) {
+  GfxSubpath *subpath;
+  double x0, y0, x1, y1, x2, y2, x3, y3, x4, y4;
+  int n, m, i, j;
+
+  n = path->getNumSubpaths();
+
+  if (n == 1 && path->getSubpath(0)->getNumPoints() == 5) {
+    subpath = path->getSubpath(0);
+    x0 = subpath->getX(0);
+    y0 = subpath->getY(0);
+    x4 = subpath->getX(4);
+    y4 = subpath->getY(4);
+    if (x4 == x0 && y4 == y0) {
+      x1 = subpath->getX(1);
+      y1 = subpath->getY(1);
+      x2 = subpath->getX(2);
+      y2 = subpath->getY(2);
+      x3 = subpath->getX(3);
+      y3 = subpath->getY(3);
+      if (x0 == x1 && x2 == x3 && y0 == y3 && y1 == y2) {
+	writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} re\n",
+		   x0 < x2 ? x0 : x2, y0 < y1 ? y0 : y1,
+		   fabs(x2 - x0), fabs(y1 - y0));
+	return;
+      } else if (x0 == x3 && x1 == x2 && y0 == y1 && y2 == y3) {
+	writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} re\n",
+		   x0 < x1 ? x0 : x1, y0 < y2 ? y0 : y2,
+		   fabs(x1 - x0), fabs(y2 - y0));
+	return;
+      }
+    }
+  }
+
+  for (i = 0; i < n; ++i) {
+    subpath = path->getSubpath(i);
+    m = subpath->getNumPoints();
+    writePSFmt("{0:.4g} {1:.4g} m\n", subpath->getX(0), subpath->getY(0));
+    j = 1;
+    while (j < m) {
+      if (subpath->getCurve(j)) {
+	writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} c\n",
+		   subpath->getX(j), subpath->getY(j),
+		   subpath->getX(j+1), subpath->getY(j+1),
+		   subpath->getX(j+2), subpath->getY(j+2));
+	j += 3;
+      } else {
+	writePSFmt("{0:.4g} {1:.4g} l\n", subpath->getX(j), subpath->getY(j));
+	++j;
+      }
+    }
+    if (subpath->isClosed()) {
+      writePS("h\n");
+    }
+  }
+}
+
+void PSOutputDev::drawString(GfxState *state, GString *s) {
+  GfxFont *font;
+  int wMode;
+  Gushort *codeToGID;
+  GString *s2;
+  double dx, dy, dx2, dy2, originX, originY;
+  char *p;
+  UnicodeMap *uMap;
+  CharCode code;
+  Unicode u[8];
+  char buf[8];
+  int len, nChars, uLen, n, m, i, j;
+
+  // check for invisible text -- this is used by Acrobat Capture
+  if (state->getRender() == 3) {
+    return;
+  }
+
+  // ignore empty strings
+  if (s->getLength() == 0) {
+    return;
+  }
+
+  // get the font
+  if (!(font = state->getFont())) {
+    return;
+  }
+  wMode = font->getWMode();
+
+  // check for a subtitute 16-bit font
+  uMap = NULL;
+  codeToGID = NULL;
+  if (font->isCIDFont()) {
+    for (i = 0; i < font16EncLen; ++i) {
+      if (font->getID()->num == font16Enc[i].fontID.num &&
+	  font->getID()->gen == font16Enc[i].fontID.gen) {
+	uMap = globalParams->getUnicodeMap(font16Enc[i].enc);
+	break;
+      }
+    }
+
+  // check for a code-to-GID map
+  } else {
+    for (i = 0; i < font8InfoLen; ++i) {
+      if (font->getID()->num == font8Info[i].fontID.num &&
+	  font->getID()->gen == font8Info[i].fontID.gen) {
+	codeToGID = font8Info[i].codeToGID;
+	break;
+      }
+    }
+  }
+
+  // compute width of chars in string, ignoring char spacing and word
+  // spacing -- the Tj operator will adjust for the metrics of the
+  // font that's actually used
+  dx = dy = 0;
+  nChars = 0;
+  p = s->getCString();
+  len = s->getLength();
+  s2 = new GString();
+  while (len > 0) {
+    n = font->getNextChar(p, len, &code,
+			  u, (int)(sizeof(u) / sizeof(Unicode)), &uLen,
+			  &dx2, &dy2, &originX, &originY);
+    if (font->isCIDFont()) {
+      if (uMap) {
+	for (i = 0; i < uLen; ++i) {
+	  m = uMap->mapUnicode(u[i], buf, (int)sizeof(buf));
+	  for (j = 0; j < m; ++j) {
+	    s2->append(buf[j]);
+	  }
+	}
+	//~ this really needs to get the number of chars in the target
+	//~ encoding - which may be more than the number of Unicode
+	//~ chars
+	nChars += uLen;
+      } else {
+	s2->append((char)((code >> 8) & 0xff));
+	s2->append((char)(code & 0xff));
+	++nChars;
+      }
+    } else {
+      if (!codeToGID || codeToGID[code]) {
+	s2->append((char)code);
+      }
+    }
+    dx += dx2;
+    dy += dy2;
+    p += n;
+    len -= n;
+  }
+  dx *= state->getFontSize() * state->getHorizScaling();
+  dy *= state->getFontSize();
+  if (uMap) {
+    uMap->decRefCnt();
+  }
+
+  if (s2->getLength() > 0) {
+    writePSString(s2);
+    if (font->isCIDFont()) {
+      if (wMode) {
+	writePSFmt(" {0:d} {1:.4g} Tj16V\n", nChars, dy);
+      } else {
+	writePSFmt(" {0:d} {1:.4g} Tj16\n", nChars, dx);
+      }
+    } else {
+      writePSFmt(" {0:.4g} Tj\n", dx);
+    }
+  }
+  delete s2;
+
+  if (state->getRender() & 4) {
+    haveTextClip = gTrue;
+  }
+}
+
+void PSOutputDev::endTextObject(GfxState *state) {
+  if (haveTextClip) {
+    writePS("Tclip\n");
+    haveTextClip = gFalse;
+  }
+}
+
+void PSOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
+				int width, int height, GBool invert,
+				GBool inlineImg) {
+  int len;
+
+  len = height * ((width + 7) / 8);
+  switch (level) {
+  case psLevel1:
+  case psLevel1Sep:
+    doImageL1(ref, NULL, invert, inlineImg, str, width, height, len);
+    break;
+  case psLevel2:
+  case psLevel2Sep:
+    doImageL2(ref, NULL, invert, inlineImg, str, width, height, len,
+	      NULL, NULL, 0, 0, gFalse);
+    break;
+  case psLevel3:
+  case psLevel3Sep:
+    doImageL3(ref, NULL, invert, inlineImg, str, width, height, len,
+	      NULL, NULL, 0, 0, gFalse);
+    break;
+  }
+}
+
+void PSOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
+			    int width, int height, GfxImageColorMap *colorMap,
+			    int *maskColors, GBool inlineImg) {
+  int len;
+
+  len = height * ((width * colorMap->getNumPixelComps() *
+		   colorMap->getBits() + 7) / 8);
+  switch (level) {
+  case psLevel1:
+    doImageL1(ref, colorMap, gFalse, inlineImg, str, width, height, len);
+    break;
+  case psLevel1Sep:
+    //~ handle indexed, separation, ... color spaces
+    doImageL1Sep(colorMap, gFalse, inlineImg, str, width, height, len);
+    break;
+  case psLevel2:
+  case psLevel2Sep:
+    doImageL2(ref, colorMap, gFalse, inlineImg, str,
+	      width, height, len, maskColors, NULL, 0, 0, gFalse);
+    break;
+  case psLevel3:
+  case psLevel3Sep:
+    doImageL3(ref, colorMap, gFalse, inlineImg, str,
+	      width, height, len, maskColors, NULL, 0, 0, gFalse);
+    break;
+  }
+  t3Cacheable = gFalse;
+}
+
+void PSOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
+				  int width, int height,
+				  GfxImageColorMap *colorMap,
+				  Stream *maskStr,
+				  int maskWidth, int maskHeight,
+				  GBool maskInvert) {
+  int len;
+
+  len = height * ((width * colorMap->getNumPixelComps() *
+		   colorMap->getBits() + 7) / 8);
+  switch (level) {
+  case psLevel1:
+    doImageL1(ref, colorMap, gFalse, gFalse, str, width, height, len);
+    break;
+  case psLevel1Sep:
+    //~ handle indexed, separation, ... color spaces
+    doImageL1Sep(colorMap, gFalse, gFalse, str, width, height, len);
+    break;
+  case psLevel2:
+  case psLevel2Sep:
+    doImageL2(ref, colorMap, gFalse, gFalse, str, width, height, len,
+	      NULL, maskStr, maskWidth, maskHeight, maskInvert);
+    break;
+  case psLevel3:
+  case psLevel3Sep:
+    doImageL3(ref, colorMap, gFalse, gFalse, str, width, height, len,
+	      NULL, maskStr, maskWidth, maskHeight, maskInvert);
+    break;
+  }
+  t3Cacheable = gFalse;
+}
+
+void PSOutputDev::doImageL1(Object *ref, GfxImageColorMap *colorMap,
+			    GBool invert, GBool inlineImg,
+			    Stream *str, int width, int height, int len) {
+  ImageStream *imgStr;
+  Guchar pixBuf[gfxColorMaxComps];
+  GfxGray gray;
+  int col, x, y, c, i;
+
+  if ((inType3Char || preload) && !colorMap) {
+    if (inlineImg) {
+      // create an array
+      str = new FixedLengthEncoder(str, len);
+      str = new ASCIIHexEncoder(str);
+      str->reset();
+      col = 0;
+      writePS("[<");
+      do {
+	do {
+	  c = str->getChar();
+	} while (c == '\n' || c == '\r');
+	if (c == '>' || c == EOF) {
+	  break;
+	}
+	writePSChar(c);
+	++col;
+	// each line is: "<...data...><eol>"
+	// so max data length = 255 - 4 = 251
+	// but make it 240 just to be safe
+	// chunks are 2 bytes each, so we need to stop on an even col number
+	if (col == 240) {
+	  writePS(">\n<");
+	  col = 0;
+	}
+      } while (c != '>' && c != EOF);
+      writePS(">]\n");
+      writePS("0\n");
+      str->close();
+      delete str;
+    } else {
+      // set up to use the array already created by setupImages()
+      writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen());
+    }
+  }
+
+  // image/imagemask command
+  if ((inType3Char || preload) && !colorMap) {
+    writePSFmt("{0:d} {1:d} {2:s} [{3:d} 0 0 {4:d} 0 {5:d}] pdfImM1a\n",
+	       width, height, invert ? "true" : "false",
+	       width, -height, height);
+  } else if (colorMap) {
+    writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1\n",
+	       width, height,
+	       width, -height, height);
+  } else {
+    writePSFmt("{0:d} {1:d} {2:s} [{3:d} 0 0 {4:d} 0 {5:d}] pdfImM1\n",
+	       width, height, invert ? "true" : "false",
+	       width, -height, height);
+  }
+
+  // image data
+  if (!((inType3Char || preload) && !colorMap)) {
+
+    if (colorMap) {
+
+      // set up to process the data stream
+      imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
+			       colorMap->getBits());
+      imgStr->reset();
+
+      // process the data stream
+      i = 0;
+      for (y = 0; y < height; ++y) {
+
+	// write the line
+	for (x = 0; x < width; ++x) {
+	  imgStr->getPixel(pixBuf);
+	  colorMap->getGray(pixBuf, &gray);
+	  writePSFmt("{0:02x}", colToByte(gray));
+	  if (++i == 32) {
+	    writePSChar('\n');
+	    i = 0;
+	  }
+	}
+      }
+      if (i != 0) {
+	writePSChar('\n');
+      }
+      str->close();
+      delete imgStr;
+
+    // imagemask
+    } else {
+      str->reset();
+      i = 0;
+      for (y = 0; y < height; ++y) {
+	for (x = 0; x < width; x += 8) {
+	  writePSFmt("{0:02x}", str->getChar() & 0xff);
+	  if (++i == 32) {
+	    writePSChar('\n');
+	    i = 0;
+	  }
+	}
+      }
+      if (i != 0) {
+	writePSChar('\n');
+      }
+      str->close();
+    }
+  }
+}
+
+void PSOutputDev::doImageL1Sep(GfxImageColorMap *colorMap,
+			       GBool invert, GBool inlineImg,
+			       Stream *str, int width, int height, int len) {
+  ImageStream *imgStr;
+  Guchar *lineBuf;
+  Guchar pixBuf[gfxColorMaxComps];
+  GfxCMYK cmyk;
+  int x, y, i, comp;
+
+  // width, height, matrix, bits per component
+  writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1Sep\n",
+	     width, height,
+	     width, -height, height);
+
+  // allocate a line buffer
+  lineBuf = (Guchar *)gmalloc(4 * width);
+
+  // set up to process the data stream
+  imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(),
+			   colorMap->getBits());
+  imgStr->reset();
+
+  // process the data stream
+  i = 0;
+  for (y = 0; y < height; ++y) {
+
+    // read the line
+    for (x = 0; x < width; ++x) {
+      imgStr->getPixel(pixBuf);
+      colorMap->getCMYK(pixBuf, &cmyk);
+      lineBuf[4*x+0] = colToByte(cmyk.c);
+      lineBuf[4*x+1] = colToByte(cmyk.m);
+      lineBuf[4*x+2] = colToByte(cmyk.y);
+      lineBuf[4*x+3] = colToByte(cmyk.k);
+      addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
+		      colToDbl(cmyk.y), colToDbl(cmyk.k));
+    }
+
+    // write one line of each color component
+    for (comp = 0; comp < 4; ++comp) {
+      for (x = 0; x < width; ++x) {
+	writePSFmt("{0:02x}", lineBuf[4*x + comp]);
+	if (++i == 32) {
+	  writePSChar('\n');
+	  i = 0;
+	}
+      }
+    }
+  }
+
+  if (i != 0) {
+    writePSChar('\n');
+  }
+
+  str->close();
+  delete imgStr;
+  gfree(lineBuf);
+}
+
+void PSOutputDev::doImageL2(Object *ref, GfxImageColorMap *colorMap,
+			    GBool invert, GBool inlineImg,
+			    Stream *str, int width, int height, int len,
+			    int *maskColors, Stream *maskStr,
+			    int maskWidth, int maskHeight, GBool maskInvert) {
+  Stream *str2;
+  ImageStream *imgStr;
+  Guchar *line;
+  PSOutImgClipRect *rects0, *rects1, *rectsTmp, *rectsOut;
+  int rects0Len, rects1Len, rectsSize, rectsOutLen, rectsOutSize;
+  GBool emitRect, addRect, extendRect;
+  GString *s;
+  int n, numComps;
+  GBool useRLE, useASCII, useASCIIHex, useCompressed;
+  GfxSeparationColorSpace *sepCS;
+  GfxColor color;
+  GfxCMYK cmyk;
+  int c;
+  int col, i, j, x0, x1, y, maskXor;
+
+  // color key masking
+  if (maskColors && colorMap && !inlineImg) {
+    // can't read the stream twice for inline images -- but masking
+    // isn't allowed with inline images anyway
+    numComps = colorMap->getNumPixelComps();
+    imgStr = new ImageStream(str, width, numComps, colorMap->getBits());
+    imgStr->reset();
+    rects0Len = rects1Len = rectsOutLen = 0;
+    rectsSize = rectsOutSize = 64;
+    rects0 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
+    rects1 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
+    rectsOut = (PSOutImgClipRect *)gmallocn(rectsOutSize,
+					    sizeof(PSOutImgClipRect));
+    for (y = 0; y < height; ++y) {
+      if (!(line = imgStr->getLine())) {
+	break;
+      }
+      i = 0;
+      rects1Len = 0;
+      for (x0 = 0; x0 < width; ++x0) {
+	for (j = 0; j < numComps; ++j) {
+	  if (line[x0*numComps+j] < maskColors[2*j] ||
+	      line[x0*numComps+j] > maskColors[2*j+1]) {
+	    break;
+	  }
+	}
+	if (j < numComps) {
+	  break;
+	}
+      }
+      for (x1 = x0; x1 < width; ++x1) {
+	for (j = 0; j < numComps; ++j) {
+	  if (line[x1*numComps+j] < maskColors[2*j] ||
+	      line[x1*numComps+j] > maskColors[2*j+1]) {
+	    break;
+	  }
+	}
+	if (j == numComps) {
+	  break;
+	}
+      }
+      while (x0 < width || i < rects0Len) {
+	emitRect = addRect = extendRect = gFalse;
+	if (x0 >= width) {
+	  emitRect = gTrue;
+	} else if (i >= rects0Len) {
+	  addRect = gTrue;
+	} else if (rects0[i].x0 < x0) {
+	  emitRect = gTrue;
+	} else if (x0 < rects0[i].x0) {
+	  addRect = gTrue;
+	} else if (rects0[i].x1 == x1) {
+	  extendRect = gTrue;
+	} else {
+	  emitRect = addRect = gTrue;
+	}
+	if (emitRect) {
+	  if (rectsOutLen == rectsOutSize) {
+	    rectsOutSize *= 2;
+	    rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
+						     sizeof(PSOutImgClipRect));
+	  }
+	  rectsOut[rectsOutLen].x0 = rects0[i].x0;
+	  rectsOut[rectsOutLen].x1 = rects0[i].x1;
+	  rectsOut[rectsOutLen].y0 = height - y - 1;
+	  rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1;
+	  ++rectsOutLen;
+	  ++i;
+	}
+	if (addRect || extendRect) {
+	  if (rects1Len == rectsSize) {
+	    rectsSize *= 2;
+	    rects0 = (PSOutImgClipRect *)greallocn(rects0, rectsSize,
+						   sizeof(PSOutImgClipRect));
+	    rects1 = (PSOutImgClipRect *)greallocn(rects1, rectsSize,
+						   sizeof(PSOutImgClipRect));
+	  }
+	  rects1[rects1Len].x0 = x0;
+	  rects1[rects1Len].x1 = x1;
+	  if (addRect) {
+	    rects1[rects1Len].y0 = y;
+	  }
+	  if (extendRect) {
+	    rects1[rects1Len].y0 = rects0[i].y0;
+	    ++i;
+	  }
+	  ++rects1Len;
+	  for (x0 = x1; x0 < width; ++x0) {
+	    for (j = 0; j < numComps; ++j) {
+	      if (line[x0*numComps+j] < maskColors[2*j] ||
+		  line[x0*numComps+j] > maskColors[2*j+1]) {
+		break;
+	      }
+	    }
+	    if (j < numComps) {
+	      break;
+	    }
+	  }
+	  for (x1 = x0; x1 < width; ++x1) {
+	    for (j = 0; j < numComps; ++j) {
+	      if (line[x1*numComps+j] < maskColors[2*j] ||
+		  line[x1*numComps+j] > maskColors[2*j+1]) {
+		break;
+	      }
+	    }
+	    if (j == numComps) {
+	      break;
+	    }
+	  }
+	}
+      }
+      rectsTmp = rects0;
+      rects0 = rects1;
+      rects1 = rectsTmp;
+      i = rects0Len;
+      rects0Len = rects1Len;
+      rects1Len = i;
+    }
+    for (i = 0; i < rects0Len; ++i) {
+      if (rectsOutLen == rectsOutSize) {
+	rectsOutSize *= 2;
+	rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
+						 sizeof(PSOutImgClipRect));
+      }
+      rectsOut[rectsOutLen].x0 = rects0[i].x0;
+      rectsOut[rectsOutLen].x1 = rects0[i].x1;
+      rectsOut[rectsOutLen].y0 = height - y - 1;
+      rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1;
+      ++rectsOutLen;
+    }
+    writePSFmt("{0:d} array 0\n", rectsOutLen * 4);
+    for (i = 0; i < rectsOutLen; ++i) {
+      writePSFmt("[{0:d} {1:d} {2:d} {3:d}] pr\n",
+		 rectsOut[i].x0, rectsOut[i].y0,
+		 rectsOut[i].x1 - rectsOut[i].x0,
+		 rectsOut[i].y1 - rectsOut[i].y0);
+    }
+    writePSFmt("pop {0:d} {1:d} pdfImClip\n", width, height);
+    gfree(rectsOut);
+    gfree(rects0);
+    gfree(rects1);
+    delete imgStr;
+    str->close();
+
+  // explicit masking
+  } else if (maskStr) {
+    imgStr = new ImageStream(maskStr, maskWidth, 1, 1);
+    imgStr->reset();
+    rects0Len = rects1Len = rectsOutLen = 0;
+    rectsSize = rectsOutSize = 64;
+    rects0 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
+    rects1 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect));
+    rectsOut = (PSOutImgClipRect *)gmallocn(rectsOutSize,
+					    sizeof(PSOutImgClipRect));
+    maskXor = maskInvert ? 1 : 0;
+    for (y = 0; y < maskHeight; ++y) {
+      if (!(line = imgStr->getLine())) {
+	break;
+      }
+      i = 0;
+      rects1Len = 0;
+      for (x0 = 0; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) ;
+      for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) ;
+      while (x0 < maskWidth || i < rects0Len) {
+	emitRect = addRect = extendRect = gFalse;
+	if (x0 >= maskWidth) {
+	  emitRect = gTrue;
+	} else if (i >= rects0Len) {
+	  addRect = gTrue;
+	} else if (rects0[i].x0 < x0) {
+	  emitRect = gTrue;
+	} else if (x0 < rects0[i].x0) {
+	  addRect = gTrue;
+	} else if (rects0[i].x1 == x1) {
+	  extendRect = gTrue;
+	} else {
+	  emitRect = addRect = gTrue;
+	}
+	if (emitRect) {
+	  if (rectsOutLen == rectsOutSize) {
+	    rectsOutSize *= 2;
+	    rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
+						     sizeof(PSOutImgClipRect));
+	  }
+	  rectsOut[rectsOutLen].x0 = rects0[i].x0;
+	  rectsOut[rectsOutLen].x1 = rects0[i].x1;
+	  rectsOut[rectsOutLen].y0 = maskHeight - y - 1;
+	  rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1;
+	  ++rectsOutLen;
+	  ++i;
+	}
+	if (addRect || extendRect) {
+	  if (rects1Len == rectsSize) {
+	    rectsSize *= 2;
+	    rects0 = (PSOutImgClipRect *)greallocn(rects0, rectsSize,
+						   sizeof(PSOutImgClipRect));
+	    rects1 = (PSOutImgClipRect *)greallocn(rects1, rectsSize,
+						   sizeof(PSOutImgClipRect));
+	  }
+	  rects1[rects1Len].x0 = x0;
+	  rects1[rects1Len].x1 = x1;
+	  if (addRect) {
+	    rects1[rects1Len].y0 = y;
+	  }
+	  if (extendRect) {
+	    rects1[rects1Len].y0 = rects0[i].y0;
+	    ++i;
+	  }
+	  ++rects1Len;
+	  for (x0 = x1; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) ;
+	  for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) ;
+	}
+      }
+      rectsTmp = rects0;
+      rects0 = rects1;
+      rects1 = rectsTmp;
+      i = rects0Len;
+      rects0Len = rects1Len;
+      rects1Len = i;
+    }
+    for (i = 0; i < rects0Len; ++i) {
+      if (rectsOutLen == rectsOutSize) {
+	rectsOutSize *= 2;
+	rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize,
+						 sizeof(PSOutImgClipRect));
+      }
+      rectsOut[rectsOutLen].x0 = rects0[i].x0;
+      rectsOut[rectsOutLen].x1 = rects0[i].x1;
+      rectsOut[rectsOutLen].y0 = maskHeight - y - 1;
+      rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1;
+      ++rectsOutLen;
+    }
+    writePSFmt("{0:d} array 0\n", rectsOutLen * 4);
+    for (i = 0; i < rectsOutLen; ++i) {
+      writePSFmt("[{0:d} {1:d} {2:d} {3:d}] pr\n",
+		 rectsOut[i].x0, rectsOut[i].y0,
+		 rectsOut[i].x1 - rectsOut[i].x0,
+		 rectsOut[i].y1 - rectsOut[i].y0);
+    }
+    writePSFmt("pop {0:d} {1:d} pdfImClip\n", maskWidth, maskHeight);
+    gfree(rectsOut);
+    gfree(rects0);
+    gfree(rects1);
+    delete imgStr;
+    maskStr->close();
+  }
+
+  // color space
+  if (colorMap) {
+    dumpColorSpaceL2(colorMap->getColorSpace(), gFalse, gTrue, gFalse);
+    writePS(" setcolorspace\n");
+  }
+
+  useASCIIHex = globalParams->getPSASCIIHex();
+
+  // set up the image data
+  if (mode == psModeForm || inType3Char || preload) {
+    if (inlineImg) {
+      // create an array
+      str2 = new FixedLengthEncoder(str, len);
+      str2 = new RunLengthEncoder(str2);
+      if (useASCIIHex) {
+	str2 = new ASCIIHexEncoder(str2);
+      } else {
+	str2 = new ASCII85Encoder(str2);
+      }
+      str2->reset();
+      col = 0;
+      writePS((char *)(useASCIIHex ? "[<" : "[<~"));
+      do {
+	do {
+	  c = str2->getChar();
+	} while (c == '\n' || c == '\r');
+	if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	  break;
+	}
+	if (c == 'z') {
+	  writePSChar(c);
+	  ++col;
+	} else {
+	  writePSChar(c);
+	  ++col;
+	  for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
+	    do {
+	      c = str2->getChar();
+	    } while (c == '\n' || c == '\r');
+	    if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	      break;
+	    }
+	    writePSChar(c);
+	    ++col;
+	  }
+	}
+	// each line is: "<~...data...~><eol>"
+	// so max data length = 255 - 6 = 249
+	// chunks are 1 or 5 bytes each, so we have to stop at 245
+	// but make it 240 just to be safe
+	if (col > 240) {
+	  writePS((char *)(useASCIIHex ? ">\n<" : "~>\n<~"));
+	  col = 0;
+	}
+      } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
+      writePS((char *)(useASCIIHex ? ">\n" : "~>\n"));
+      // add an extra entry because the RunLengthDecode filter may
+      // read past the end
+      writePS("<>]\n");
+      writePS("0\n");
+      str2->close();
+      delete str2;
+    } else {
+      // set up to use the array already created by setupImages()
+      writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen());
+    }
+  }
+
+  // image dictionary
+  writePS("<<\n  /ImageType 1\n");
+
+  // width, height, matrix, bits per component
+  writePSFmt("  /Width {0:d}\n", width);
+  writePSFmt("  /Height {0:d}\n", height);
+  writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
+	     width, -height, height);
+  if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
+    writePS("  /BitsPerComponent 8\n");
+  } else {
+    writePSFmt("  /BitsPerComponent {0:d}\n",
+	       colorMap ? colorMap->getBits() : 1);
+  }
+
+  // decode 
+  if (colorMap) {
+    writePS("  /Decode [");
+    if ((level == psLevel2Sep || level == psLevel3Sep) &&
+	colorMap->getColorSpace()->getMode() == csSeparation) {
+      // this matches up with the code in the pdfImSep operator
+      n = (1 << colorMap->getBits()) - 1;
+      writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(0) * n,
+		 colorMap->getDecodeHigh(0) * n);
+    } else if (colorMap->getColorSpace()->getMode() == csDeviceN) {
+      numComps = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
+	           getAlt()->getNComps();
+      for (i = 0; i < numComps; ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePS("0 1");
+      }
+    } else {
+      numComps = colorMap->getNumPixelComps();
+      for (i = 0; i < numComps; ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePSFmt("{0:.4g} {1:.4g}",
+		   colorMap->getDecodeLow(i), colorMap->getDecodeHigh(i));
+      }
+    }
+    writePS("]\n");
+  } else {
+    writePSFmt("  /Decode [{0:d} {1:d}]\n", invert ? 1 : 0, invert ? 0 : 1);
+  }
+
+  // data source
+  if (mode == psModeForm || inType3Char || preload) {
+    writePS("  /DataSource { 2 copy get exch 1 add exch }\n");
+  } else {
+    writePS("  /DataSource currentfile\n");
+  }
+
+  // filters
+  s = str->getPSFilter(level < psLevel2 ? 1 : level < psLevel3 ? 2 : 3,
+		       "    ");
+  if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) ||
+      inlineImg || !s) {
+    useRLE = gTrue;
+    useASCII = !(mode == psModeForm || inType3Char || preload);
+    useCompressed = gFalse;
+  } else {
+    useRLE = gFalse;
+    useASCII = str->isBinary() &&
+               !(mode == psModeForm || inType3Char || preload);
+    useCompressed = gTrue;
+  }
+  if (useASCII) {
+    writePSFmt("    /ASCII{0:s}Decode filter\n",
+	       useASCIIHex ? "Hex" : "85");
+  }
+  if (useRLE) {
+    writePS("    /RunLengthDecode filter\n");
+  }
+  if (useCompressed) {
+    writePS(s->getCString());
+  }
+  if (s) {
+    delete s;
+  }
+
+  if (mode == psModeForm || inType3Char || preload) {
+
+    // end of image dictionary
+    writePSFmt(">>\n{0:s}\n", colorMap ? "image" : "imagemask");
+
+    // get rid of the array and index
+    writePS("pop pop\n");
+
+  } else {
+
+    // cut off inline image streams at appropriate length
+    if (inlineImg) {
+      str = new FixedLengthEncoder(str, len);
+    } else if (useCompressed) {
+      str = str->getUndecodedStream();
+    }
+
+    // recode DeviceN data
+    if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
+      str = new DeviceNRecoder(str, width, height, colorMap);
+    }
+
+    // add RunLengthEncode and ASCIIHex/85 encode filters
+    if (useRLE) {
+      str = new RunLengthEncoder(str);
+    }
+    if (useASCII) {
+      if (useASCIIHex) {
+	str = new ASCIIHexEncoder(str);
+      } else {
+	str = new ASCII85Encoder(str);
+      }
+    }
+
+    // end of image dictionary
+    writePS(">>\n");
+#if OPI_SUPPORT
+    if (opi13Nest) {
+      if (inlineImg) {
+	// this can't happen -- OPI dictionaries are in XObjects
+	error(-1, "Internal: OPI in inline image");
+	n = 0;
+      } else {
+	// need to read the stream to count characters -- the length
+	// is data-dependent (because of ASCII and RLE filters)
+	str->reset();
+	n = 0;
+	while ((c = str->getChar()) != EOF) {
+	  ++n;
+	}
+	str->close();
+      }
+      // +6/7 for "pdfIm\n" / "pdfImM\n"
+      // +8 for newline + trailer
+      n += colorMap ? 14 : 15;
+      writePSFmt("%%BeginData: {0:d} Hex Bytes\n", n);
+    }
+#endif
+    if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap &&
+	colorMap->getColorSpace()->getMode() == csSeparation) {
+      color.c[0] = gfxColorComp1;
+      sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace();
+      sepCS->getCMYK(&color, &cmyk);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n",
+		 colToDbl(cmyk.c), colToDbl(cmyk.m),
+		 colToDbl(cmyk.y), colToDbl(cmyk.k),
+		 sepCS->getName());
+    } else {
+      writePSFmt("{0:s}\n", colorMap ? "pdfIm" : "pdfImM");
+    }
+
+    // copy the stream data
+    str->reset();
+    while ((c = str->getChar()) != EOF) {
+      writePSChar(c);
+    }
+    str->close();
+
+    // add newline and trailer to the end
+    writePSChar('\n');
+    writePS("%-EOD-\n");
+#if OPI_SUPPORT
+    if (opi13Nest) {
+      writePS("%%EndData\n");
+    }
+#endif
+
+    // delete encoders
+    if (useRLE || useASCII || inlineImg) {
+      delete str;
+    }
+  }
+
+  if ((maskColors && colorMap && !inlineImg) || maskStr) {
+    writePS("pdfImClipEnd\n");
+  }
+}
+
+//~ this doesn't currently support OPI
+void PSOutputDev::doImageL3(Object *ref, GfxImageColorMap *colorMap,
+			    GBool invert, GBool inlineImg,
+			    Stream *str, int width, int height, int len,
+			    int *maskColors, Stream *maskStr,
+			    int maskWidth, int maskHeight, GBool maskInvert) {
+  Stream *str2;
+  GString *s;
+  int n, numComps;
+  GBool useRLE, useASCII, useASCIIHex, useCompressed;
+  GBool maskUseRLE, maskUseASCII, maskUseCompressed;
+  GfxSeparationColorSpace *sepCS;
+  GfxColor color;
+  GfxCMYK cmyk;
+  int c;
+  int col, i;
+
+  useASCIIHex = globalParams->getPSASCIIHex();
+  useRLE = useASCII = useCompressed = gFalse; // make gcc happy
+  maskUseRLE = maskUseASCII = maskUseCompressed = gFalse; // make gcc happy
+
+  // color space
+  if (colorMap) {
+    dumpColorSpaceL2(colorMap->getColorSpace(), gFalse, gTrue, gFalse);
+    writePS(" setcolorspace\n");
+  }
+
+  // set up the image data
+  if (mode == psModeForm || inType3Char || preload) {
+    if (inlineImg) {
+      // create an array
+      str2 = new FixedLengthEncoder(str, len);
+      str2 = new RunLengthEncoder(str2);
+      if (useASCIIHex) {
+	str2 = new ASCIIHexEncoder(str2);
+      } else {
+	str2 = new ASCII85Encoder(str2);
+      }
+      str2->reset();
+      col = 0;
+      writePS((char *)(useASCIIHex ? "[<" : "[<~"));
+      do {
+	do {
+	  c = str2->getChar();
+	} while (c == '\n' || c == '\r');
+	if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	  break;
+	}
+	if (c == 'z') {
+	  writePSChar(c);
+	  ++col;
+	} else {
+	  writePSChar(c);
+	  ++col;
+	  for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) {
+	    do {
+	      c = str2->getChar();
+	    } while (c == '\n' || c == '\r');
+	    if (c == (useASCIIHex ? '>' : '~') || c == EOF) {
+	      break;
+	    }
+	    writePSChar(c);
+	    ++col;
+	  }
+	}
+	// each line is: "<~...data...~><eol>"
+	// so max data length = 255 - 6 = 249
+	// chunks are 1 or 5 bytes each, so we have to stop at 245
+	// but make it 240 just to be safe
+	if (col > 240) {
+	  writePS((char *)(useASCIIHex ? ">\n<" : "~>\n<~"));
+	  col = 0;
+	}
+      } while (c != (useASCIIHex ? '>' : '~') && c != EOF);
+      writePS((char *)(useASCIIHex ? ">\n" : "~>\n"));
+      // add an extra entry because the RunLengthDecode filter may
+      // read past the end
+      writePS("<>]\n");
+      writePS("0\n");
+      str2->close();
+      delete str2;
+    } else {
+      // set up to use the array already created by setupImages()
+      writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen());
+    }
+  }
+
+  // explicit masking
+  if (maskStr) {
+    writePS("<<\n  /ImageType 3\n");
+    writePS("  /InterleaveType 3\n");
+    writePS("  /DataDict\n");
+  }
+
+  // image (data) dictionary
+  writePSFmt("<<\n  /ImageType {0:d}\n", (maskColors && colorMap) ? 4 : 1);
+
+  // color key masking
+  if (maskColors && colorMap) {
+    writePS("  /MaskColor [\n");
+    numComps = colorMap->getNumPixelComps();
+    for (i = 0; i < 2 * numComps; i += 2) {
+      writePSFmt("    {0:d} {1:d}\n", maskColors[i], maskColors[i+1]);
+    }
+    writePS("  ]\n");
+  }
+
+  // width, height, matrix, bits per component
+  writePSFmt("  /Width {0:d}\n", width);
+  writePSFmt("  /Height {0:d}\n", height);
+  writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
+	     width, -height, height);
+  if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
+    writePS("  /BitsPerComponent 8\n");
+  } else {
+    writePSFmt("  /BitsPerComponent {0:d}\n",
+	       colorMap ? colorMap->getBits() : 1);
+  }
+
+  // decode 
+  if (colorMap) {
+    writePS("  /Decode [");
+    if ((level == psLevel2Sep || level == psLevel3Sep) &&
+	colorMap->getColorSpace()->getMode() == csSeparation) {
+      // this matches up with the code in the pdfImSep operator
+      n = (1 << colorMap->getBits()) - 1;
+      writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(0) * n,
+		 colorMap->getDecodeHigh(0) * n);
+    } else if (colorMap->getColorSpace()->getMode() == csDeviceN) {
+      numComps = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->
+	           getAlt()->getNComps();
+      for (i = 0; i < numComps; ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePS("0 1");
+      }
+    } else {
+      numComps = colorMap->getNumPixelComps();
+      for (i = 0; i < numComps; ++i) {
+	if (i > 0) {
+	  writePS(" ");
+	}
+	writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(i),
+		   colorMap->getDecodeHigh(i));
+      }
+    }
+    writePS("]\n");
+  } else {
+    writePSFmt("  /Decode [{0:d} {1:d}]\n", invert ? 1 : 0, invert ? 0 : 1);
+  }
+
+  // data source
+  if (mode == psModeForm || inType3Char || preload) {
+    writePS("  /DataSource { 2 copy get exch 1 add exch }\n");
+  } else {
+    writePS("  /DataSource currentfile\n");
+  }
+
+  // filters
+  s = str->getPSFilter(level < psLevel2 ? 1 : level < psLevel3 ? 2 : 3,
+		       "    ");
+  if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) ||
+      inlineImg || !s) {
+    useRLE = gTrue;
+    useASCII = !(mode == psModeForm || inType3Char || preload);
+    useCompressed = gFalse;
+  } else {
+    useRLE = gFalse;
+    useASCII = str->isBinary() &&
+               !(mode == psModeForm || inType3Char || preload);
+    useCompressed = gTrue;
+  }
+  if (useASCII) {
+    writePSFmt("    /ASCII{0:s}Decode filter\n",
+	       useASCIIHex ? "Hex" : "85");
+  }
+  if (useRLE) {
+    writePS("    /RunLengthDecode filter\n");
+  }
+  if (useCompressed) {
+    writePS(s->getCString());
+  }
+  if (s) {
+    delete s;
+  }
+
+  // end of image (data) dictionary
+  writePS(">>\n");
+
+  // explicit masking
+  if (maskStr) {
+    writePS("  /MaskDict\n");
+    writePS("<<\n");
+    writePS("  /ImageType 1\n");
+    writePSFmt("  /Width {0:d}\n", maskWidth);
+    writePSFmt("  /Height {0:d}\n", maskHeight);
+    writePSFmt("  /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n",
+	       maskWidth, -maskHeight, maskHeight);
+    writePS("  /BitsPerComponent 1\n");
+    writePSFmt("  /Decode [{0:d} {1:d}]\n",
+	       maskInvert ? 1 : 0, maskInvert ? 0 : 1);
+
+    // mask data source
+    writePS("  /DataSource currentfile\n");
+    s = maskStr->getPSFilter(3, "    ");
+    if (!s) {
+      maskUseRLE = gTrue;
+      maskUseASCII = gTrue;
+      maskUseCompressed = gFalse;
+    } else {
+      maskUseRLE = gFalse;
+      maskUseASCII = maskStr->isBinary();
+      maskUseCompressed = gTrue;
+    }
+    if (maskUseASCII) {
+      writePSFmt("    /ASCII{0:s}Decode filter\n",
+		 useASCIIHex ? "Hex" : "85");
+    }
+    if (maskUseRLE) {
+      writePS("    /RunLengthDecode filter\n");
+    }
+    if (maskUseCompressed) {
+      writePS(s->getCString());
+    }
+    if (s) {
+      delete s;
+    }
+
+    writePS(">>\n");
+    writePS(">>\n");
+  }
+
+  if (mode == psModeForm || inType3Char || preload) {
+
+    // image command
+    writePSFmt("{0:s}\n", colorMap ? "image" : "imagemask");
+
+  } else {
+
+    if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap &&
+	colorMap->getColorSpace()->getMode() == csSeparation) {
+      color.c[0] = gfxColorComp1;
+      sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace();
+      sepCS->getCMYK(&color, &cmyk);
+      writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n",
+		 colToDbl(cmyk.c), colToDbl(cmyk.m),
+		 colToDbl(cmyk.y), colToDbl(cmyk.k),
+		 sepCS->getName());
+    } else {
+      writePSFmt("{0:s}\n", colorMap ? "pdfIm" : "pdfImM");
+    }
+
+  }
+
+  // explicit masking
+  if (maskStr) {
+
+    if (maskUseCompressed) {
+      maskStr = maskStr->getUndecodedStream();
+    }
+
+    // add RunLengthEncode and ASCIIHex/85 encode filters
+    if (maskUseRLE) {
+      maskStr = new RunLengthEncoder(maskStr);
+    }
+    if (maskUseASCII) {
+      if (useASCIIHex) {
+	maskStr = new ASCIIHexEncoder(maskStr);
+      } else {
+	maskStr = new ASCII85Encoder(maskStr);
+      }
+    }
+
+    // copy the stream data
+    maskStr->reset();
+    while ((c = maskStr->getChar()) != EOF) {
+      writePSChar(c);
+    }
+    maskStr->close();
+    writePSChar('\n');
+
+    // delete encoders
+    if (maskUseRLE || maskUseASCII) {
+      delete maskStr;
+    }
+  }
+
+  // get rid of the array and index
+  if (mode == psModeForm || inType3Char || preload) {
+    writePS("pop pop\n");
+
+  // image data
+  } else {
+
+    // cut off inline image streams at appropriate length
+    if (inlineImg) {
+      str = new FixedLengthEncoder(str, len);
+    } else if (useCompressed) {
+      str = str->getUndecodedStream();
+    }
+
+    // recode DeviceN data
+    if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) {
+      str = new DeviceNRecoder(str, width, height, colorMap);
+    }
+
+    // add RunLengthEncode and ASCIIHex/85 encode filters
+    if (useRLE) {
+      str = new RunLengthEncoder(str);
+    }
+    if (useASCII) {
+      if (useASCIIHex) {
+	str = new ASCIIHexEncoder(str);
+      } else {
+	str = new ASCII85Encoder(str);
+      }
+    }
+
+    // copy the stream data
+    str->reset();
+    while ((c = str->getChar()) != EOF) {
+      writePSChar(c);
+    }
+    str->close();
+
+    // add newline and trailer to the end
+    writePSChar('\n');
+    writePS("%-EOD-\n");
+
+    // delete encoders
+    if (useRLE || useASCII || inlineImg) {
+      delete str;
+    }
+  }
+}
+
+void PSOutputDev::dumpColorSpaceL2(GfxColorSpace *colorSpace,
+				   GBool genXform, GBool updateColors,
+				   GBool map01) {
+  GfxCalGrayColorSpace *calGrayCS;
+  GfxCalRGBColorSpace *calRGBCS;
+  GfxLabColorSpace *labCS;
+  GfxIndexedColorSpace *indexedCS;
+  GfxSeparationColorSpace *separationCS;
+  GfxDeviceNColorSpace *deviceNCS;
+  GfxColorSpace *baseCS;
+  Guchar *lookup, *p;
+  double x[gfxColorMaxComps], y[gfxColorMaxComps];
+  double low[gfxColorMaxComps], range[gfxColorMaxComps];
+  GfxColor color;
+  GfxCMYK cmyk;
+  Function *func;
+  int n, numComps, numAltComps;
+  int byte;
+  int i, j, k;
+
+  switch (colorSpace->getMode()) {
+
+  case csDeviceGray:
+    writePS("/DeviceGray");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessBlack;
+    }
+    break;
+
+  case csCalGray:
+    calGrayCS = (GfxCalGrayColorSpace *)colorSpace;
+    writePS("[/CIEBasedA <<\n");
+    writePSFmt(" /DecodeA {{{0:.4g} exp}} bind\n", calGrayCS->getGamma());
+    writePSFmt(" /MatrixA [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       calGrayCS->getWhiteX(), calGrayCS->getWhiteY(),
+	       calGrayCS->getWhiteZ());
+    writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       calGrayCS->getWhiteX(), calGrayCS->getWhiteY(),
+	       calGrayCS->getWhiteZ());
+    writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       calGrayCS->getBlackX(), calGrayCS->getBlackY(),
+	       calGrayCS->getBlackZ());
+    writePS(">>]");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessBlack;
+    }
+    break;
+
+  case csDeviceRGB:
+    writePS("/DeviceRGB");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessCMYK;
+    }
+    break;
+
+  case csCalRGB:
+    calRGBCS = (GfxCalRGBColorSpace *)colorSpace;
+    writePS("[/CIEBasedABC <<\n");
+    writePSFmt(" /DecodeABC [{{{0:.4g} exp}} bind {{{1:.4g} exp}} bind {{{2:.4g} exp}} bind]\n",
+	       calRGBCS->getGammaR(), calRGBCS->getGammaG(),
+	       calRGBCS->getGammaB());
+    writePSFmt(" /MatrixABC [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} {6:.4g} {7:.4g} {8:.4g}]\n",
+	       calRGBCS->getMatrix()[0], calRGBCS->getMatrix()[1],
+	       calRGBCS->getMatrix()[2], calRGBCS->getMatrix()[3],
+	       calRGBCS->getMatrix()[4], calRGBCS->getMatrix()[5],
+	       calRGBCS->getMatrix()[6], calRGBCS->getMatrix()[7],
+	       calRGBCS->getMatrix()[8]);
+    writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       calRGBCS->getWhiteX(), calRGBCS->getWhiteY(),
+	       calRGBCS->getWhiteZ());
+    writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       calRGBCS->getBlackX(), calRGBCS->getBlackY(),
+	       calRGBCS->getBlackZ());
+    writePS(">>]");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessCMYK;
+    }
+    break;
+
+  case csDeviceCMYK:
+    writePS("/DeviceCMYK");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessCMYK;
+    }
+    break;
+
+  case csLab:
+    labCS = (GfxLabColorSpace *)colorSpace;
+    writePS("[/CIEBasedABC <<\n");
+    if (map01) {
+      writePS(" /RangeABC [0 1 0 1 0 1]\n");
+      writePSFmt(" /DecodeABC [{{100 mul 16 add 116 div}} bind {{{0:.4g} mul {1:.4g} add}} bind {{{2:.4g} mul {3:.4g} add}} bind]\n",
+		 (labCS->getAMax() - labCS->getAMin()) / 500.0,
+		 labCS->getAMin() / 500.0,
+		 (labCS->getBMax() - labCS->getBMin()) / 200.0,
+		 labCS->getBMin() / 200.0);
+    } else {
+      writePSFmt(" /RangeABC [0 100 {0:.4g} {1:.4g} {2:.4g} {3:.4g}]\n",
+		 labCS->getAMin(), labCS->getAMax(),
+		 labCS->getBMin(), labCS->getBMax());
+      writePS(" /DecodeABC [{16 add 116 div} bind {500 div} bind {200 div} bind]\n");
+    }
+    writePS(" /MatrixABC [1 1 1 1 0 0 0 0 -1]\n");
+    writePS(" /DecodeLMN\n");
+    writePS("   [{dup 6 29 div ge {dup dup mul mul}\n");
+    writePSFmt("     {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n",
+	       labCS->getWhiteX());
+    writePS("    {dup 6 29 div ge {dup dup mul mul}\n");
+    writePSFmt("     {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n",
+	       labCS->getWhiteY());
+    writePS("    {dup 6 29 div ge {dup dup mul mul}\n");
+    writePSFmt("     {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind]\n",
+	       labCS->getWhiteZ());
+    writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       labCS->getWhiteX(), labCS->getWhiteY(), labCS->getWhiteZ());
+    writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n",
+	       labCS->getBlackX(), labCS->getBlackY(), labCS->getBlackZ());
+    writePS(">>]");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      processColors |= psProcessCMYK;
+    }
+    break;
+
+  case csICCBased:
+    // there is no transform function to the alternate color space, so
+    // we can use it directly
+    dumpColorSpaceL2(((GfxICCBasedColorSpace *)colorSpace)->getAlt(),
+		     genXform, updateColors, gFalse);
+    break;
+
+  case csIndexed:
+    indexedCS = (GfxIndexedColorSpace *)colorSpace;
+    baseCS = indexedCS->getBase();
+    writePS("[/Indexed ");
+    dumpColorSpaceL2(baseCS, gFalse, gFalse, gTrue);
+    n = indexedCS->getIndexHigh();
+    numComps = baseCS->getNComps();
+    lookup = indexedCS->getLookup();
+    writePSFmt(" {0:d} <\n", n);
+    if (baseCS->getMode() == csDeviceN) {
+      func = ((GfxDeviceNColorSpace *)baseCS)->getTintTransformFunc();
+      baseCS->getDefaultRanges(low, range, indexedCS->getIndexHigh());
+      if (((GfxDeviceNColorSpace *)baseCS)->getAlt()->getMode() == csLab) {
+	labCS = (GfxLabColorSpace *)((GfxDeviceNColorSpace *)baseCS)->getAlt();
+      } else {
+	labCS = NULL;
+      }
+      numAltComps = ((GfxDeviceNColorSpace *)baseCS)->getAlt()->getNComps();
+      p = lookup;
+      for (i = 0; i <= n; i += 8) {
+	writePS("  ");
+	for (j = i; j < i+8 && j <= n; ++j) {
+	  for (k = 0; k < numComps; ++k) {
+	    x[k] = low[k] + (*p++ / 255.0) * range[k];
+	  }
+	  func->transform(x, y);
+	  if (labCS) {
+	    y[0] /= 100.0;
+	    y[1] = (y[1] - labCS->getAMin()) /
+	           (labCS->getAMax() - labCS->getAMin());
+	    y[2] = (y[2] - labCS->getBMin()) /
+	           (labCS->getBMax() - labCS->getBMin());
+	  }
+	  for (k = 0; k < numAltComps; ++k) {
+	    byte = (int)(y[k] * 255 + 0.5);
+	    if (byte < 0) {
+	      byte = 0;
+	    } else if (byte > 255) {
+	      byte = 255;
+	    }
+	    writePSFmt("{0:02x}", byte);
+	  }
+	  if (updateColors) {
+	    color.c[0] = dblToCol(j);
+	    indexedCS->getCMYK(&color, &cmyk);
+	    addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
+			    colToDbl(cmyk.y), colToDbl(cmyk.k));
+	  }
+	}
+	writePS("\n");
+      }
+    } else {
+      for (i = 0; i <= n; i += 8) {
+	writePS("  ");
+	for (j = i; j < i+8 && j <= n; ++j) {
+	  for (k = 0; k < numComps; ++k) {
+	    writePSFmt("{0:02x}", lookup[j * numComps + k]);
+	  }
+	  if (updateColors) {
+	    color.c[0] = dblToCol(j);
+	    indexedCS->getCMYK(&color, &cmyk);
+	    addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m),
+			    colToDbl(cmyk.y), colToDbl(cmyk.k));
+	  }
+	}
+	writePS("\n");
+      }
+    }
+    writePS(">]");
+    if (genXform) {
+      writePS(" {}");
+    }
+    break;
+
+  case csSeparation:
+    separationCS = (GfxSeparationColorSpace *)colorSpace;
+    writePS("[/Separation ");
+    writePSString(separationCS->getName());
+    writePS(" ");
+    dumpColorSpaceL2(separationCS->getAlt(), gFalse, gFalse, gFalse);
+    writePS("\n");
+    cvtFunction(separationCS->getFunc());
+    writePS("]");
+    if (genXform) {
+      writePS(" {}");
+    }
+    if (updateColors) {
+      addCustomColor(separationCS);
+    }
+    break;
+
+  case csDeviceN:
+    // DeviceN color spaces are a Level 3 PostScript feature.
+    deviceNCS = (GfxDeviceNColorSpace *)colorSpace;
+    dumpColorSpaceL2(deviceNCS->getAlt(), gFalse, updateColors, map01);
+    if (genXform) {
+      writePS(" ");
+      cvtFunction(deviceNCS->getTintTransformFunc());
+    }
+    break;
+
+  case csPattern:
+    //~ unimplemented
+    break;
+  }
+}
+
+#if OPI_SUPPORT
+void PSOutputDev::opiBegin(GfxState *state, Dict *opiDict) {
+  Object dict;
+
+  if (globalParams->getPSOPI()) {
+    opiDict->lookup("2.0", &dict);
+    if (dict.isDict()) {
+      opiBegin20(state, dict.getDict());
+      dict.free();
+    } else {
+      dict.free();
+      opiDict->lookup("1.3", &dict);
+      if (dict.isDict()) {
+	opiBegin13(state, dict.getDict());
+      }
+      dict.free();
+    }
+  }
+}
+
+void PSOutputDev::opiBegin20(GfxState *state, Dict *dict) {
+  Object obj1, obj2, obj3, obj4;
+  double width, height, left, right, top, bottom;
+  int w, h;
+  int i;
+
+  writePS("%%BeginOPI: 2.0\n");
+  writePS("%%Distilled\n");
+
+  dict->lookup("F", &obj1);
+  if (getFileSpec(&obj1, &obj2)) {
+    writePSFmt("%%ImageFileName: {0:t}\n", obj2.getString());
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("MainImage", &obj1);
+  if (obj1.isString()) {
+    writePSFmt("%%MainImage: {0:t}\n", obj1.getString());
+  }
+  obj1.free();
+
+  //~ ignoring 'Tags' entry
+  //~ need to use writePSString() and deal with >255-char lines
+
+  dict->lookup("Size", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
+    obj1.arrayGet(0, &obj2);
+    width = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    height = obj2.getNum();
+    obj2.free();
+    writePSFmt("%%ImageDimensions: {0:.4g} {1:.4g}\n", width, height);
+  }
+  obj1.free();
+
+  dict->lookup("CropRect", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 4) {
+    obj1.arrayGet(0, &obj2);
+    left = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    top = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(2, &obj2);
+    right = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(3, &obj2);
+    bottom = obj2.getNum();
+    obj2.free();
+    writePSFmt("%%ImageCropRect: {0:.4g} {1:.4g} {2:.4g} {3:.4g}\n",
+	       left, top, right, bottom);
+  }
+  obj1.free();
+
+  dict->lookup("Overprint", &obj1);
+  if (obj1.isBool()) {
+    writePSFmt("%%ImageOverprint: {0:s}\n", obj1.getBool() ? "true" : "false");
+  }
+  obj1.free();
+
+  dict->lookup("Inks", &obj1);
+  if (obj1.isName()) {
+    writePSFmt("%%ImageInks: {0:s}\n", obj1.getName());
+  } else if (obj1.isArray() && obj1.arrayGetLength() >= 1) {
+    obj1.arrayGet(0, &obj2);
+    if (obj2.isName()) {
+      writePSFmt("%%ImageInks: {0:s} {1:d}",
+		 obj2.getName(), (obj1.arrayGetLength() - 1) / 2);
+      for (i = 1; i+1 < obj1.arrayGetLength(); i += 2) {
+	obj1.arrayGet(i, &obj3);
+	obj1.arrayGet(i+1, &obj4);
+	if (obj3.isString() && obj4.isNum()) {
+	  writePS(" ");
+	  writePSString(obj3.getString());
+	  writePSFmt(" {0:.4g}", obj4.getNum());
+	}
+	obj3.free();
+	obj4.free();
+      }
+      writePS("\n");
+    }
+    obj2.free();
+  }
+  obj1.free();
+
+  writePS("gsave\n");
+
+  writePS("%%BeginIncludedImage\n");
+
+  dict->lookup("IncludedImageDimensions", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
+    obj1.arrayGet(0, &obj2);
+    w = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    h = obj2.getInt();
+    obj2.free();
+    writePSFmt("%%IncludedImageDimensions: {0:d} {1:d}\n", w, h);
+  }
+  obj1.free();
+
+  dict->lookup("IncludedImageQuality", &obj1);
+  if (obj1.isNum()) {
+    writePSFmt("%%IncludedImageQuality: {0:.4g}\n", obj1.getNum());
+  }
+  obj1.free();
+
+  ++opi20Nest;
+}
+
+void PSOutputDev::opiBegin13(GfxState *state, Dict *dict) {
+  Object obj1, obj2;
+  int left, right, top, bottom, samples, bits, width, height;
+  double c, m, y, k;
+  double llx, lly, ulx, uly, urx, ury, lrx, lry;
+  double tllx, tlly, tulx, tuly, turx, tury, tlrx, tlry;
+  double horiz, vert;
+  int i, j;
+
+  writePS("save\n");
+  writePS("/opiMatrix2 matrix currentmatrix def\n");
+  writePS("opiMatrix setmatrix\n");
+
+  dict->lookup("F", &obj1);
+  if (getFileSpec(&obj1, &obj2)) {
+    writePSFmt("%ALDImageFileName: {0:t}\n", obj2.getString());
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("CropRect", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 4) {
+    obj1.arrayGet(0, &obj2);
+    left = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    top = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(2, &obj2);
+    right = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(3, &obj2);
+    bottom = obj2.getInt();
+    obj2.free();
+    writePSFmt("%ALDImageCropRect: {0:d} {1:d} {2:d} {3:d}\n",
+	       left, top, right, bottom);
+  }
+  obj1.free();
+
+  dict->lookup("Color", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 5) {
+    obj1.arrayGet(0, &obj2);
+    c = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    m = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(2, &obj2);
+    y = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(3, &obj2);
+    k = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(4, &obj2);
+    if (obj2.isString()) {
+      writePSFmt("%ALDImageColor: {0:.4g} {1:.4g} {2:.4g} {3:.4g} ",
+		 c, m, y, k);
+      writePSString(obj2.getString());
+      writePS("\n");
+    }
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("ColorType", &obj1);
+  if (obj1.isName()) {
+    writePSFmt("%ALDImageColorType: {0:s}\n", obj1.getName());
+  }
+  obj1.free();
+
+  //~ ignores 'Comments' entry
+  //~ need to handle multiple lines
+
+  dict->lookup("CropFixed", &obj1);
+  if (obj1.isArray()) {
+    obj1.arrayGet(0, &obj2);
+    ulx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    uly = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(2, &obj2);
+    lrx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(3, &obj2);
+    lry = obj2.getNum();
+    obj2.free();
+    writePSFmt("%ALDImageCropFixed: {0:.4g} {1:.4g} {2:.4g} {3:.4g}\n",
+	       ulx, uly, lrx, lry);
+  }
+  obj1.free();
+
+  dict->lookup("GrayMap", &obj1);
+  if (obj1.isArray()) {
+    writePS("%ALDImageGrayMap:");
+    for (i = 0; i < obj1.arrayGetLength(); i += 16) {
+      if (i > 0) {
+	writePS("\n%%+");
+      }
+      for (j = 0; j < 16 && i+j < obj1.arrayGetLength(); ++j) {
+	obj1.arrayGet(i+j, &obj2);
+	writePSFmt(" {0:d}", obj2.getInt());
+	obj2.free();
+      }
+    }
+    writePS("\n");
+  }
+  obj1.free();
+
+  dict->lookup("ID", &obj1);
+  if (obj1.isString()) {
+    writePSFmt("%ALDImageID: {0:t}\n", obj1.getString());
+  }
+  obj1.free();
+
+  dict->lookup("ImageType", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
+    obj1.arrayGet(0, &obj2);
+    samples = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    bits = obj2.getInt();
+    obj2.free();
+    writePSFmt("%ALDImageType: {0:d} {1:d}\n", samples, bits);
+  }
+  obj1.free();
+
+  dict->lookup("Overprint", &obj1);
+  if (obj1.isBool()) {
+    writePSFmt("%ALDImageOverprint: {0:s}\n",
+	       obj1.getBool() ? "true" : "false");
+  }
+  obj1.free();
+
+  dict->lookup("Position", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 8) {
+    obj1.arrayGet(0, &obj2);
+    llx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    lly = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(2, &obj2);
+    ulx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(3, &obj2);
+    uly = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(4, &obj2);
+    urx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(5, &obj2);
+    ury = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(6, &obj2);
+    lrx = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(7, &obj2);
+    lry = obj2.getNum();
+    obj2.free();
+    opiTransform(state, llx, lly, &tllx, &tlly);
+    opiTransform(state, ulx, uly, &tulx, &tuly);
+    opiTransform(state, urx, ury, &turx, &tury);
+    opiTransform(state, lrx, lry, &tlrx, &tlry);
+    writePSFmt("%ALDImagePosition: {0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} {6:.4g} {7:.4g}\n",
+	       tllx, tlly, tulx, tuly, turx, tury, tlrx, tlry);
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("Resolution", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
+    obj1.arrayGet(0, &obj2);
+    horiz = obj2.getNum();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    vert = obj2.getNum();
+    obj2.free();
+    writePSFmt("%ALDImageResoution: {0:.4g} {1:.4g}\n", horiz, vert);
+    obj2.free();
+  }
+  obj1.free();
+
+  dict->lookup("Size", &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 2) {
+    obj1.arrayGet(0, &obj2);
+    width = obj2.getInt();
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    height = obj2.getInt();
+    obj2.free();
+    writePSFmt("%ALDImageDimensions: {0:d} {1:d}\n", width, height);
+  }
+  obj1.free();
+
+  //~ ignoring 'Tags' entry
+  //~ need to use writePSString() and deal with >255-char lines
+
+  dict->lookup("Tint", &obj1);
+  if (obj1.isNum()) {
+    writePSFmt("%ALDImageTint: {0:.4g}\n", obj1.getNum());
+  }
+  obj1.free();
+
+  dict->lookup("Transparency", &obj1);
+  if (obj1.isBool()) {
+    writePSFmt("%ALDImageTransparency: {0:s}\n",
+	       obj1.getBool() ? "true" : "false");
+  }
+  obj1.free();
+
+  writePS("%%BeginObject: image\n");
+  writePS("opiMatrix2 setmatrix\n");
+  ++opi13Nest;
+}
+
+// Convert PDF user space coordinates to PostScript default user space
+// coordinates.  This has to account for both the PDF CTM and the
+// PSOutputDev page-fitting transform.
+void PSOutputDev::opiTransform(GfxState *state, double x0, double y0,
+			       double *x1, double *y1) {
+  double t;
+
+  state->transform(x0, y0, x1, y1);
+  *x1 += tx;
+  *y1 += ty;
+  if (rotate == 90) {
+    t = *x1;
+    *x1 = -*y1;
+    *y1 = t;
+  } else if (rotate == 180) {
+    *x1 = -*x1;
+    *y1 = -*y1;
+  } else if (rotate == 270) {
+    t = *x1;
+    *x1 = *y1;
+    *y1 = -t;
+  }
+  *x1 *= xScale;
+  *y1 *= yScale;
+}
+
+void PSOutputDev::opiEnd(GfxState *state, Dict *opiDict) {
+  Object dict;
+
+  if (globalParams->getPSOPI()) {
+    opiDict->lookup("2.0", &dict);
+    if (dict.isDict()) {
+      writePS("%%EndIncludedImage\n");
+      writePS("%%EndOPI\n");
+      writePS("grestore\n");
+      --opi20Nest;
+      dict.free();
+    } else {
+      dict.free();
+      opiDict->lookup("1.3", &dict);
+      if (dict.isDict()) {
+	writePS("%%EndObject\n");
+	writePS("restore\n");
+	--opi13Nest;
+      }
+      dict.free();
+    }
+  }
+}
+
+GBool PSOutputDev::getFileSpec(Object *fileSpec, Object *fileName) {
+  if (fileSpec->isString()) {
+    fileSpec->copy(fileName);
+    return gTrue;
+  }
+  if (fileSpec->isDict()) {
+    fileSpec->dictLookup("DOS", fileName);
+    if (fileName->isString()) {
+      return gTrue;
+    }
+    fileName->free();
+    fileSpec->dictLookup("Mac", fileName);
+    if (fileName->isString()) {
+      return gTrue;
+    }
+    fileName->free();
+    fileSpec->dictLookup("Unix", fileName);
+    if (fileName->isString()) {
+      return gTrue;
+    }
+    fileName->free();
+    fileSpec->dictLookup("F", fileName);
+    if (fileName->isString()) {
+      return gTrue;
+    }
+    fileName->free();
+  }
+  return gFalse;
+}
+#endif // OPI_SUPPORT
+
+void PSOutputDev::type3D0(GfxState *state, double wx, double wy) {
+  writePSFmt("{0:.4g} {1:.4g} setcharwidth\n", wx, wy);
+  writePS("q\n");
+  t3NeedsRestore = gTrue;
+}
+
+void PSOutputDev::type3D1(GfxState *state, double wx, double wy,
+			  double llx, double lly, double urx, double ury) {
+  t3WX = wx;
+  t3WY = wy;
+  t3LLX = llx;
+  t3LLY = lly;
+  t3URX = urx;
+  t3URY = ury;
+  t3String = new GString();
+  writePS("q\n");
+  t3Cacheable = gTrue;
+  t3NeedsRestore = gTrue;
+}
+
+void PSOutputDev::drawForm(Ref id) {
+  writePSFmt("f_{0:d}_{1:d}\n", id.num, id.gen);
+}
+
+void PSOutputDev::psXObject(Stream *psStream, Stream *level1Stream) {
+  Stream *str;
+  int c;
+
+  if ((level == psLevel1 || level == psLevel1Sep) && level1Stream) {
+    str = level1Stream;
+  } else {
+    str = psStream;
+  }
+  str->reset();
+  while ((c = str->getChar()) != EOF) {
+    writePSChar(c);
+  }
+  str->close();
+}
+
+//~ can nextFunc be reset to 0 -- maybe at the start of each page?
+//~   or maybe at the start of each color space / pattern?
+void PSOutputDev::cvtFunction(Function *func) {
+  SampledFunction *func0;
+  ExponentialFunction *func2;
+  StitchingFunction *func3;
+  PostScriptFunction *func4;
+  int thisFunc, m, n, nSamples, i, j, k;
+
+  switch (func->getType()) {
+
+  case -1:			// identity
+    writePS("{}\n");
+    break;
+
+  case 0:			// sampled
+    func0 = (SampledFunction *)func;
+    thisFunc = nextFunc++;
+    m = func0->getInputSize();
+    n = func0->getOutputSize();
+    nSamples = n;
+    for (i = 0; i < m; ++i) {
+      nSamples *= func0->getSampleSize(i);
+    }
+    writePSFmt("/xpdfSamples{0:d} [\n", thisFunc);
+    for (i = 0; i < nSamples; ++i) {
+      writePSFmt("{0:.4g}\n", func0->getSamples()[i]);
+    }
+    writePS("] def\n");
+    writePSFmt("{{ {0:d} array {1:d} array {2:d} 2 roll\n", 2*m, m, m+2);
+    // [e01] [efrac] x0 x1 ... xm-1
+    for (i = m-1; i >= 0; --i) {
+      // [e01] [efrac] x0 x1 ... xi
+      writePSFmt("{0:.4g} sub {1:.4g} mul {2:.4g} add\n",
+	      func0->getDomainMin(i),
+	      (func0->getEncodeMax(i) - func0->getEncodeMin(i)) /
+	        (func0->getDomainMax(i) - func0->getDomainMin(i)),
+	      func0->getEncodeMin(i));
+      // [e01] [efrac] x0 x1 ... xi-1 xi'
+      writePSFmt("dup 0 lt {{ pop 0 }} {{ dup {0:d} gt {{ pop {1:d} }} if }} ifelse\n",
+		 func0->getSampleSize(i) - 1, func0->getSampleSize(i) - 1);
+      // [e01] [efrac] x0 x1 ... xi-1 xi'
+      writePS("dup floor cvi exch dup ceiling cvi exch 2 index sub\n");
+      // [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi') xi'-floor(xi')
+      writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+3, i);
+      // [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi')
+      writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+3, 2*i+1);
+      // [e01] [efrac] x0 x1 ... xi-1 floor(xi')
+      writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+2, 2*i);
+      // [e01] [efrac] x0 x1 ... xi-1
+    }
+    // [e01] [efrac]
+    for (i = 0; i < n; ++i) {
+      // [e01] [efrac] y(0) ... y(i-1)
+      for (j = 0; j < (1<<m); ++j) {
+	// [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(j-1)
+	writePSFmt("xpdfSamples{0:d}\n", thisFunc);
+	k = m - 1;
+	writePSFmt("{0:d} index {1:d} get\n", i+j+2, 2 * k + ((j >> k) & 1));
+	for (k = m - 2; k >= 0; --k) {
+	  writePSFmt("{0:d} mul {1:d} index {2:d} get add\n",
+		     func0->getSampleSize(k),
+		     i + j + 3,
+		     2 * k + ((j >> k) & 1));
+	}
+	if (n > 1) {
+	  writePSFmt("{0:d} mul {1:d} add ", n, i);
+	}
+	writePS("get\n");
+      }
+      // [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^m-1)
+      for (j = 0; j < m; ++j) {
+	// [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^(m-j)-1)
+	for (k = 0; k < (1 << (m - j)); k += 2) {
+	  // [e01] [efrac] y(0) ... y(i-1) <k/2 s' values> <2^(m-j)-k s values>
+	  writePSFmt("{0:d} index {1:d} get dup\n",
+		     i + k/2 + (1 << (m-j)) - k, j);
+	  writePS("3 2 roll mul exch 1 exch sub 3 2 roll mul add\n");
+	  writePSFmt("{0:d} 1 roll\n", k/2 + (1 << m-j) - k - 1);
+	}
+	// [e01] [efrac] s'(0) s'(1) ... s(2^(m-j-1)-1)
+      }
+      // [e01] [efrac] y(0) ... y(i-1) s
+      writePSFmt("{0:.4g} mul {1:.4g} add\n",
+		 func0->getDecodeMax(i) - func0->getDecodeMin(i),
+		 func0->getDecodeMin(i));
+      writePSFmt("dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
+		 func0->getRangeMin(i), func0->getRangeMin(i),
+		 func0->getRangeMax(i), func0->getRangeMax(i));
+      // [e01] [efrac] y(0) ... y(i-1) y(i)
+    }
+    // [e01] [efrac] y(0) ... y(n-1)
+    writePSFmt("{0:d} {1:d} roll pop pop }}\n", n+2, n);
+    break;
+
+  case 2:			// exponential
+    func2 = (ExponentialFunction *)func;
+    n = func2->getOutputSize();
+    writePSFmt("{{ dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
+	       func2->getDomainMin(0), func2->getDomainMin(0),
+	       func2->getDomainMax(0), func2->getDomainMax(0));
+    // x
+    for (i = 0; i < n; ++i) {
+      // x y(0) .. y(i-1)
+      writePSFmt("{0:d} index {1:.4g} exp {2:.4g} mul {3:.4g} add\n",
+		 i, func2->getE(), func2->getC1()[i] - func2->getC0()[i],
+		 func2->getC0()[i]);
+      if (func2->getHasRange()) {
+	writePSFmt("dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
+		   func2->getRangeMin(i), func2->getRangeMin(i),
+		   func2->getRangeMax(i), func2->getRangeMax(i));
+      }
+    }
+    // x y(0) .. y(n-1)
+    writePSFmt("{0:d} {1:d} roll pop }}\n", n+1, n);
+    break;
+
+  case 3:			// stitching
+    func3 = (StitchingFunction *)func;
+    thisFunc = nextFunc++;
+    for (i = 0; i < func3->getNumFuncs(); ++i) {
+      cvtFunction(func3->getFunc(i));
+      writePSFmt("/xpdfFunc{0:d}_{1:d} exch def\n", thisFunc, i);
+    }
+    writePSFmt("{{ dup {0:.4g} lt {{ pop {1:.4g} }} {{ dup {2:.4g} gt {{ pop {3:.4g} }} if }} ifelse\n",
+	       func3->getDomainMin(0), func3->getDomainMin(0),
+	       func3->getDomainMax(0), func3->getDomainMax(0));
+    for (i = 0; i < func3->getNumFuncs() - 1; ++i) {
+      writePSFmt("dup {0:.4g} lt {{ {1:.4g} sub {2:.4g} mul {3:.4g} add xpdfFunc{4:d}_{5:d} }} {{\n",
+		 func3->getBounds()[i+1],
+		 func3->getBounds()[i],
+		 func3->getScale()[i],
+		 func3->getEncode()[2*i],
+		 thisFunc, i);
+    }
+    writePSFmt("{0:.4g} sub {1:.4g} mul {2:.4g} add xpdfFunc{3:d}_{4:d}\n",
+	       func3->getBounds()[i],
+	       func3->getScale()[i],
+	       func3->getEncode()[2*i],
+	       thisFunc, i);
+    for (i = 0; i < func3->getNumFuncs() - 1; ++i) {
+      writePS("} ifelse\n");
+    }
+    writePS("}\n");
+    break;
+
+  case 4:			// PostScript
+    func4 = (PostScriptFunction *)func;
+    writePS(func4->getCodeString()->getCString());
+    writePS("\n");
+    break;
+  }
+}
+
+void PSOutputDev::writePSChar(char c) {
+  if (t3String) {
+    t3String->append(c);
+  } else {
+    (*outputFunc)(outputStream, &c, 1);
+  }
+}
+
+void PSOutputDev::writePS(char *s) {
+  if (t3String) {
+    t3String->append(s);
+  } else {
+    (*outputFunc)(outputStream, s, strlen(s));
+  }
+}
+
+void PSOutputDev::writePSFmt(const char *fmt, ...) {
+  va_list args;
+  GString *buf;
+
+  va_start(args, fmt);
+  if (t3String) {
+    t3String->appendfv((char *)fmt, args);
+  } else {
+    buf = GString::formatv((char *)fmt, args);
+    (*outputFunc)(outputStream, buf->getCString(), buf->getLength());
+    delete buf;
+  }
+  va_end(args);
+}
+
+void PSOutputDev::writePSString(GString *s) {
+  Guchar *p;
+  int n, line;
+  char buf[8];
+
+  writePSChar('(');
+  line = 1;
+  for (p = (Guchar *)s->getCString(), n = s->getLength(); n; ++p, --n) {
+    if (line >= 64) {
+      writePSChar('\\');
+      writePSChar('\n');
+      line = 0;
+    }
+    if (*p == '(' || *p == ')' || *p == '\\') {
+      writePSChar('\\');
+      writePSChar((char)*p);
+      line += 2;
+    } else if (*p < 0x20 || *p >= 0x80) {
+      sprintf(buf, "\\%03o", *p);
+      writePS(buf);
+      line += 4;
+    } else {
+      writePSChar((char)*p);
+      ++line;
+    }
+  }
+  writePSChar(')');
+}
+
+void PSOutputDev::writePSName(char *s) {
+  char *p;
+  char c;
+
+  p = s;
+  while ((c = *p++)) {
+    if (c <= (char)0x20 || c >= (char)0x7f ||
+	c == '(' || c == ')' || c == '<' || c == '>' ||
+	c == '[' || c == ']' || c == '{' || c == '}' ||
+	c == '/' || c == '%') {
+      writePSFmt("#{0:02x}", c & 0xff);
+    } else {
+      writePSChar(c);
+    }
+  }
+}
+
+GString *PSOutputDev::filterPSName(GString *name) {
+  GString *name2;
+  char buf[8];
+  int i;
+  char c;
+
+  name2 = new GString();
+
+  // ghostscript chokes on names that begin with out-of-limits
+  // numbers, e.g., 1e4foo is handled correctly (as a name), but
+  // 1e999foo generates a limitcheck error
+  c = name->getChar(0);
+  if (c >= '0' && c <= '9') {
+    name2->append('f');
+  }
+
+  for (i = 0; i < name->getLength(); ++i) {
+    c = name->getChar(i);
+    if (c <= (char)0x20 || c >= (char)0x7f ||
+	c == '(' || c == ')' || c == '<' || c == '>' ||
+	c == '[' || c == ']' || c == '{' || c == '}' ||
+	c == '/' || c == '%') {
+      sprintf(buf, "#%02x", c & 0xff);
+      name2->append(buf);
+    } else {
+      name2->append(c);
+    }
+  }
+  return name2;
+}
+
+// Write a DSC-compliant <textline>.
+void PSOutputDev::writePSTextLine(GString *s) {
+  int i, j, step;
+  int c;
+
+  // - DSC comments must be printable ASCII; control chars and
+  //   backslashes have to be escaped (we do cheap Unicode-to-ASCII
+  //   conversion by simply ignoring the high byte)
+  // - lines are limited to 255 chars (we limit to 200 here to allow
+  //   for the keyword, which was emitted by the caller)
+  // - lines that start with a left paren are treated as <text>
+  //   instead of <textline>, so we escape a leading paren
+  if (s->getLength() >= 2 &&
+      (s->getChar(0) & 0xff) == 0xfe &&
+      (s->getChar(1) & 0xff) == 0xff) {
+    i = 3;
+    step = 2;
+  } else {
+    i = 0;
+    step = 1;
+  }
+  for (j = 0; i < s->getLength() && j < 200; i += step) {
+    c = s->getChar(i) & 0xff;
+    if (c == '\\') {
+      writePS("\\\\");
+      j += 2;
+    } else if (c < 0x20 || c > 0x7e || (j == 0 && c == '(')) {
+      writePSFmt("\\{0:03o}", c);
+      j += 4;
+    } else {
+      writePSChar(c);
+      ++j;
+    }
+  }
+  writePS("\n");
+}
diff --git a/xpdf/PSOutputDev.h b/xpdf/PSOutputDev.h
new file mode 100644
--- /dev/null
+++ b/xpdf/PSOutputDev.h
@@ -0,0 +1,395 @@
+//========================================================================
+//
+// PSOutputDev.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef PSOUTPUTDEV_H
+#define PSOUTPUTDEV_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <stddef.h>
+#include "config.h"
+#include "Object.h"
+#include "GlobalParams.h"
+#include "OutputDev.h"
+
+class Function;
+class GfxPath;
+class GfxFont;
+class GfxColorSpace;
+class GfxSeparationColorSpace;
+class PDFRectangle;
+struct PSFont8Info;
+struct PSFont16Enc;
+class PSOutCustomColor;
+
+//------------------------------------------------------------------------
+// PSOutputDev
+//------------------------------------------------------------------------
+
+enum PSOutMode {
+  psModePS,
+  psModeEPS,
+  psModeForm
+};
+
+enum PSFileType {
+  psFile,			// write to file
+  psPipe,			// write to pipe
+  psStdout,			// write to stdout
+  psGeneric			// write to a generic stream
+};
+
+typedef void (*PSOutputFunc)(void *stream, char *data, int len);
+
+class PSOutputDev: public OutputDev {
+public:
+
+  // Open a PostScript output file, and write the prolog.
+  PSOutputDev(char *fileName, XRef *xrefA, Catalog *catalog,
+	      int firstPage, int lastPage, PSOutMode modeA,
+	      int imgLLXA = 0, int imgLLYA = 0,
+	      int imgURXA = 0, int imgURYA = 0,
+	      GBool manualCtrlA = gFalse);
+
+  // Open a PSOutputDev that will write to a generic stream.
+  PSOutputDev(PSOutputFunc outputFuncA, void *outputStreamA,
+	      XRef *xrefA, Catalog *catalog,
+	      int firstPage, int lastPage, PSOutMode modeA,
+	      int imgLLXA = 0, int imgLLYA = 0,
+	      int imgURXA = 0, int imgURYA = 0,
+	      GBool manualCtrlA = gFalse);
+
+  // Destructor -- writes the trailer and closes the file.
+  virtual ~PSOutputDev();
+
+  // Check if file was successfully created.
+  virtual GBool isOk() { return ok; }
+
+  //---- get info about output device
+
+  // Does this device use upside-down coordinates?
+  // (Upside-down means (0,0) is the top left corner of the page.)
+  virtual GBool upsideDown() { return gFalse; }
+
+  // Does this device use drawChar() or drawString()?
+  virtual GBool useDrawChar() { return gFalse; }
+
+  // Does this device use tilingPatternFill()?  If this returns false,
+  // tiling pattern fills will be reduced to a series of other drawing
+  // operations.
+  virtual GBool useTilingPatternFill() { return gTrue; }
+
+  // Does this device use functionShadedFill(), axialShadedFill(), and
+  // radialShadedFill()?  If this returns false, these shaded fills
+  // will be reduced to a series of other drawing operations.
+  virtual GBool useShadedFills()
+    { return level >= psLevel2; }
+
+  // Does this device use drawForm()?  If this returns false,
+  // form-type XObjects will be interpreted (i.e., unrolled).
+  virtual GBool useDrawForm() { return preload; }
+
+  // Does this device use beginType3Char/endType3Char?  Otherwise,
+  // text in Type 3 fonts will be drawn with drawChar/drawString.
+  virtual GBool interpretType3Chars() { return gFalse; }
+
+  //----- header/trailer (used only if manualCtrl is true)
+
+  // Write the document-level header.
+  void writeHeader(int firstPage, int lastPage,
+		   PDFRectangle *mediaBox, PDFRectangle *cropBox,
+		   int pageRotate);
+
+  // Write the Xpdf procset.
+  void writeXpdfProcset();
+
+  // Write the document-level setup.
+  void writeDocSetup(Catalog *catalog, int firstPage, int lastPage);
+
+  // Write the trailer for the current page.
+  void writePageTrailer();
+
+  // Write the document trailer.
+  void writeTrailer();
+
+  //----- initialization and control
+
+  // Check to see if a page slice should be displayed.  If this
+  // returns false, the page display is aborted.  Typically, an
+  // OutputDev will use some alternate means to display the page
+  // before returning false.
+  virtual GBool checkPageSlice(Page *page, double hDPI, double vDPI,
+			       int rotate, GBool useMediaBox, GBool crop,
+			       int sliceX, int sliceY, int sliceW, int sliceH,
+			       GBool printing, Catalog *catalog,
+			       GBool (*abortCheckCbk)(void *data) = NULL,
+			       void *abortCheckCbkData = NULL);
+
+  // Start a page.
+  virtual void startPage(int pageNum, GfxState *state);
+
+  // End a page.
+  virtual void endPage();
+
+  //----- save/restore graphics state
+  virtual void saveState(GfxState *state);
+  virtual void restoreState(GfxState *state);
+
+  //----- update graphics state
+  virtual void updateCTM(GfxState *state, double m11, double m12,
+			 double m21, double m22, double m31, double m32);
+  virtual void updateLineDash(GfxState *state);
+  virtual void updateFlatness(GfxState *state);
+  virtual void updateLineJoin(GfxState *state);
+  virtual void updateLineCap(GfxState *state);
+  virtual void updateMiterLimit(GfxState *state);
+  virtual void updateLineWidth(GfxState *state);
+  virtual void updateFillColorSpace(GfxState *state);
+  virtual void updateStrokeColorSpace(GfxState *state);
+  virtual void updateFillColor(GfxState *state);
+  virtual void updateStrokeColor(GfxState *state);
+  virtual void updateFillOverprint(GfxState *state);
+  virtual void updateStrokeOverprint(GfxState *state);
+  virtual void updateTransfer(GfxState *state);
+
+  //----- update text state
+  virtual void updateFont(GfxState *state);
+  virtual void updateTextMat(GfxState *state);
+  virtual void updateCharSpace(GfxState *state);
+  virtual void updateRender(GfxState *state);
+  virtual void updateRise(GfxState *state);
+  virtual void updateWordSpace(GfxState *state);
+  virtual void updateHorizScaling(GfxState *state);
+  virtual void updateTextPos(GfxState *state);
+  virtual void updateTextShift(GfxState *state, double shift);
+
+  //----- path painting
+  virtual void stroke(GfxState *state);
+  virtual void fill(GfxState *state);
+  virtual void eoFill(GfxState *state);
+  virtual void tilingPatternFill(GfxState *state, Object *str,
+				 int paintType, Dict *resDict,
+				 double *mat, double *bbox,
+				 int x0, int y0, int x1, int y1,
+				 double xStep, double yStep);
+  virtual GBool functionShadedFill(GfxState *state,
+				   GfxFunctionShading *shading);
+  virtual GBool axialShadedFill(GfxState *state, GfxAxialShading *shading);
+  virtual GBool radialShadedFill(GfxState *state, GfxRadialShading *shading);
+
+  //----- path clipping
+  virtual void clip(GfxState *state);
+  virtual void eoClip(GfxState *state);
+  virtual void clipToStrokePath(GfxState *state);
+
+  //----- text drawing
+  virtual void drawString(GfxState *state, GString *s);
+  virtual void endTextObject(GfxState *state);
+
+  //----- image drawing
+  virtual void drawImageMask(GfxState *state, Object *ref, Stream *str,
+			     int width, int height, GBool invert,
+			     GBool inlineImg);
+  virtual void drawImage(GfxState *state, Object *ref, Stream *str,
+			 int width, int height, GfxImageColorMap *colorMap,
+			 int *maskColors, GBool inlineImg);
+  virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
+			       int width, int height,
+			       GfxImageColorMap *colorMap,
+			       Stream *maskStr, int maskWidth, int maskHeight,
+			       GBool maskInvert);
+
+#if OPI_SUPPORT
+  //----- OPI functions
+  virtual void opiBegin(GfxState *state, Dict *opiDict);
+  virtual void opiEnd(GfxState *state, Dict *opiDict);
+#endif
+
+  //----- Type 3 font operators
+  virtual void type3D0(GfxState *state, double wx, double wy);
+  virtual void type3D1(GfxState *state, double wx, double wy,
+		       double llx, double lly, double urx, double ury);
+
+  //----- form XObjects
+  virtual void drawForm(Ref ref);
+
+  //----- PostScript XObjects
+  virtual void psXObject(Stream *psStream, Stream *level1Stream);
+
+  //----- miscellaneous
+  void setOffset(double x, double y)
+    { tx0 = x; ty0 = y; }
+  void setScale(double x, double y)
+    { xScale0 = x; yScale0 = y; }
+  void setRotate(int rotateA)
+    { rotate0 = rotateA; }
+  void setClip(double llx, double lly, double urx, double ury)
+    { clipLLX0 = llx; clipLLY0 = lly; clipURX0 = urx; clipURY0 = ury; }
+  void setUnderlayCbk(void (*cbk)(PSOutputDev *psOut, void *data),
+		      void *data)
+    { underlayCbk = cbk; underlayCbkData = data; }
+  void setOverlayCbk(void (*cbk)(PSOutputDev *psOut, void *data),
+		     void *data)
+    { overlayCbk = cbk; overlayCbkData = data; }
+
+private:
+
+  void init(PSOutputFunc outputFuncA, void *outputStreamA,
+	    PSFileType fileTypeA, XRef *xrefA, Catalog *catalog,
+	    int firstPage, int lastPage, PSOutMode modeA,
+	    int imgLLXA, int imgLLYA, int imgURXA, int imgURYA,
+	    GBool manualCtrlA);
+  void setupResources(Dict *resDict);
+  void setupFonts(Dict *resDict);
+  void setupFont(GfxFont *font, Dict *parentResDict);
+  void setupEmbeddedType1Font(Ref *id, GString *psName);
+  void setupExternalType1Font(GString *fileName, GString *psName);
+  void setupEmbeddedType1CFont(GfxFont *font, Ref *id, GString *psName);
+  void setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id, GString *psName);
+  void setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id, GString *psName);
+  void setupExternalTrueTypeFont(GfxFont *font, GString *psName);
+  void setupEmbeddedCIDType0Font(GfxFont *font, Ref *id, GString *psName);
+  void setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id, GString *psName,
+				    GBool needVerticalMetrics);
+  void setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id, GString *psName);
+  void setupType3Font(GfxFont *font, GString *psName, Dict *parentResDict);
+  void setupImages(Dict *resDict);
+  void setupImage(Ref id, Stream *str);
+  void setupForms(Dict *resDict);
+  void setupForm(Ref id, Object *strObj);
+  void addProcessColor(double c, double m, double y, double k);
+  void addCustomColor(GfxSeparationColorSpace *sepCS);
+  void doPath(GfxPath *path);
+  void doImageL1(Object *ref, GfxImageColorMap *colorMap,
+		 GBool invert, GBool inlineImg,
+		 Stream *str, int width, int height, int len);
+  void doImageL1Sep(GfxImageColorMap *colorMap,
+		    GBool invert, GBool inlineImg,
+		    Stream *str, int width, int height, int len);
+  void doImageL2(Object *ref, GfxImageColorMap *colorMap,
+		 GBool invert, GBool inlineImg,
+		 Stream *str, int width, int height, int len,
+		 int *maskColors, Stream *maskStr,
+		 int maskWidth, int maskHeight, GBool maskInvert);
+  void doImageL3(Object *ref, GfxImageColorMap *colorMap,
+		 GBool invert, GBool inlineImg,
+		 Stream *str, int width, int height, int len,
+		 int *maskColors, Stream *maskStr,
+		 int maskWidth, int maskHeight, GBool maskInvert);
+  void dumpColorSpaceL2(GfxColorSpace *colorSpace,
+			GBool genXform, GBool updateColors,
+			GBool map01);
+#if OPI_SUPPORT
+  void opiBegin20(GfxState *state, Dict *dict);
+  void opiBegin13(GfxState *state, Dict *dict);
+  void opiTransform(GfxState *state, double x0, double y0,
+		    double *x1, double *y1);
+  GBool getFileSpec(Object *fileSpec, Object *fileName);
+#endif
+  void cvtFunction(Function *func);
+  void writePSChar(char c);
+  void writePS(char *s);
+  void writePSFmt(const char *fmt, ...);
+  void writePSString(GString *s);
+  void writePSName(char *s);
+  GString *filterPSName(GString *name);
+  void writePSTextLine(GString *s);
+
+  PSLevel level;		// PostScript level (1, 2, separation)
+  PSOutMode mode;		// PostScript mode (PS, EPS, form)
+  int paperWidth;		// width of paper, in pts
+  int paperHeight;		// height of paper, in pts
+  int imgLLX, imgLLY,		// imageable area, in pts
+      imgURX, imgURY;
+  GBool preload;		// load all images into memory, and
+				//   predefine forms
+
+  PSOutputFunc outputFunc;
+  void *outputStream;
+  PSFileType fileType;		// file / pipe / stdout
+  GBool manualCtrl;
+  int seqPage;			// current sequential page number
+  void (*underlayCbk)(PSOutputDev *psOut, void *data);
+  void *underlayCbkData;
+  void (*overlayCbk)(PSOutputDev *psOut, void *data);
+  void *overlayCbkData;
+
+  XRef *xref;			// the xref table for this PDF file
+
+  Ref *fontIDs;			// list of object IDs of all used fonts
+  int fontIDLen;		// number of entries in fontIDs array
+  int fontIDSize;		// size of fontIDs array
+  Ref *fontFileIDs;		// list of object IDs of all embedded fonts
+  int fontFileIDLen;		// number of entries in fontFileIDs array
+  int fontFileIDSize;		// size of fontFileIDs array
+  GString **fontFileNames;	// list of names of all embedded external fonts
+  int fontFileNameLen;		// number of entries in fontFileNames array
+  int fontFileNameSize;		// size of fontFileNames array
+  int nextTrueTypeNum;		// next unique number to append to a TrueType
+				//   font name
+  PSFont8Info *font8Info;	// info for 8-bit fonts
+  int font8InfoLen;		// number of entries in font8Info array
+  int font8InfoSize;		// size of font8Info array
+  PSFont16Enc *font16Enc;	// encodings for substitute 16-bit fonts
+  int font16EncLen;		// number of entries in font16Enc array
+  int font16EncSize;		// size of font16Enc array
+  Ref *imgIDs;			// list of image IDs for in-memory images
+  int imgIDLen;			// number of entries in imgIDs array
+  int imgIDSize;		// size of imgIDs array
+  Ref *formIDs;			// list of IDs for predefined forms
+  int formIDLen;		// number of entries in formIDs array
+  int formIDSize;		// size of formIDs array
+  GList *xobjStack;		// stack of XObject dicts currently being
+				//   processed
+  int numSaves;			// current number of gsaves
+  int numTilingPatterns;	// current number of nested tiling patterns
+  int nextFunc;			// next unique number to use for a function
+
+  double tx0, ty0;		// global translation
+  double xScale0, yScale0;	// global scaling
+  int rotate0;			// rotation angle (0, 90, 180, 270)
+  double clipLLX0, clipLLY0,
+         clipURX0, clipURY0;
+  double tx, ty;		// global translation for current page
+  double xScale, yScale;	// global scaling for current page
+  int rotate;			// rotation angle for current page
+  double epsX1, epsY1,		// EPS bounding box (unrotated)
+         epsX2, epsY2;
+
+  GString *embFontList;		// resource comments for embedded fonts
+
+  int processColors;		// used process colors
+  PSOutCustomColor		// used custom colors
+    *customColors;
+
+  GBool haveTextClip;		// set if text has been drawn with a
+				//   clipping render mode
+
+  GBool inType3Char;		// inside a Type 3 CharProc
+  GString *t3String;		// Type 3 content string
+  double t3WX, t3WY,		// Type 3 character parameters
+         t3LLX, t3LLY, t3URX, t3URY;
+  GBool t3Cacheable;		// cleared if char is not cacheable
+  GBool t3NeedsRestore;		// set if a 'q' operator was issued
+
+#if OPI_SUPPORT
+  int opi13Nest;		// nesting level of OPI 1.3 objects
+  int opi20Nest;		// nesting level of OPI 2.0 objects
+#endif
+
+  GBool ok;			// set up ok?
+
+
+  friend class WinPDFPrinter;
+};
+
+#endif
diff --git a/xpdf/PSTokenizer.cc b/xpdf/PSTokenizer.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/PSTokenizer.cc
@@ -0,0 +1,135 @@
+//========================================================================
+//
+// PSTokenizer.cc
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "PSTokenizer.h"
+
+//------------------------------------------------------------------------
+
+// A '1' in this array means the character is white space.  A '1' or
+// '2' means the character ends a name or command.
+static char specialChars[256] = {
+  1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,   // 0x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 1x
+  1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2,   // 2x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0,   // 3x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 4x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0,   // 5x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 6x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0,   // 7x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 8x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 9x
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // ax
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // bx
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // cx
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // dx
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // ex
+  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0    // fx
+};
+
+//------------------------------------------------------------------------
+
+PSTokenizer::PSTokenizer(int (*getCharFuncA)(void *), void *dataA) {
+  getCharFunc = getCharFuncA;
+  data = dataA;
+  charBuf = -1;
+}
+
+PSTokenizer::~PSTokenizer() {
+}
+
+GBool PSTokenizer::getToken(char *buf, int size, int *length) {
+  GBool comment, backslash;
+  int c;
+  int i;
+
+  // skip whitespace and comments
+  comment = gFalse;
+  while (1) {
+    if ((c = getChar()) == EOF) {
+      buf[0] = '\0';
+      *length = 0;
+      return gFalse;
+    }
+    if (comment) {
+      if (c == '\x0a' || c == '\x0d') {
+	comment = gFalse;
+      }
+    } else if (c == '%') {
+      comment = gTrue;
+    } else if (specialChars[c] != 1) {
+      break;
+    }
+  }
+
+  // read a token
+  i = 0;
+  buf[i++] = c;
+  if (c == '(') {
+    backslash = gFalse;
+    while ((c = lookChar()) != EOF) {
+      if (i < size - 1) {
+	buf[i++] = c;
+      }
+      getChar();
+      if (c == '\\') {
+	backslash = gTrue;
+      } else if (!backslash && c == ')') {
+	break;
+      } else {
+	backslash = gFalse;
+      }
+    }
+  } else if (c == '<') {
+    while ((c = lookChar()) != EOF) {
+      getChar();
+      if (i < size - 1 && specialChars[c] != 1) {
+	buf[i++] = c;
+      }
+      if (c == '>') {
+	break;
+      }
+    }
+  } else if (c != '[' && c != ']') {
+    while ((c = lookChar()) != EOF && !specialChars[c]) {
+      getChar();
+      if (i < size - 1) {
+	buf[i++] = c;
+      }
+    }
+  }
+  buf[i] = '\0';
+  *length = i;
+
+  return gTrue;
+}
+
+int PSTokenizer::lookChar() {
+  if (charBuf < 0) {
+    charBuf = (*getCharFunc)(data);
+  }
+  return charBuf;
+}
+
+int PSTokenizer::getChar() {
+  int c;
+
+  if (charBuf < 0) {
+    charBuf = (*getCharFunc)(data);
+  }
+  c = charBuf;
+  charBuf = -1;
+  return c;
+}
diff --git a/xpdf/PSTokenizer.h b/xpdf/PSTokenizer.h
new file mode 100644
--- /dev/null
+++ b/xpdf/PSTokenizer.h
@@ -0,0 +1,41 @@
+//========================================================================
+//
+// PSTokenizer.h
+//
+// Copyright 2002-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef PSTOKENIZER_H
+#define PSTOKENIZER_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+
+//------------------------------------------------------------------------
+
+class PSTokenizer {
+public:
+
+  PSTokenizer(int (*getCharFuncA)(void *), void *dataA);
+  ~PSTokenizer();
+
+  // Get the next PostScript token.  Returns false at end-of-stream.
+  GBool getToken(char *buf, int size, int *length);
+
+private:
+
+  int lookChar();
+  int getChar();
+
+  int (*getCharFunc)(void *);
+  void *data;
+  int charBuf;
+};
+
+#endif
diff --git a/xpdf/Page.cc b/xpdf/Page.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Page.cc
@@ -0,0 +1,441 @@
+//========================================================================
+//
+// Page.cc
+//
+// Copyright 1996-2007 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stddef.h>
+#include "GlobalParams.h"
+#include "Object.h"
+#include "Array.h"
+#include "Dict.h"
+#include "XRef.h"
+#include "Link.h"
+#include "OutputDev.h"
+#ifndef PDF_PARSER_ONLY
+#include "Gfx.h"
+#include "GfxState.h"
+#include "Annot.h"
+#endif
+#include "Error.h"
+#include "Catalog.h"
+#include "Page.h"
+
+//------------------------------------------------------------------------
+// PDFRectangle
+//------------------------------------------------------------------------
+
+void PDFRectangle::clipTo(PDFRectangle *rect) {
+  if (x1 < rect->x1) {
+    x1 = rect->x1;
+  } else if (x1 > rect->x2) {
+    x1 = rect->x2;
+  }
+  if (x2 < rect->x1) {
+    x2 = rect->x1;
+  } else if (x2 > rect->x2) {
+    x2 = rect->x2;
+  }
+  if (y1 < rect->y1) {
+    y1 = rect->y1;
+  } else if (y1 > rect->y2) {
+    y1 = rect->y2;
+  }
+  if (y2 < rect->y1) {
+    y2 = rect->y1;
+  } else if (y2 > rect->y2) {
+    y2 = rect->y2;
+  }
+}
+
+//------------------------------------------------------------------------
+// PageAttrs
+//------------------------------------------------------------------------
+
+PageAttrs::PageAttrs(PageAttrs *attrs, Dict *dict) {
+  Object obj1;
+
+  // get old/default values
+  if (attrs) {
+    mediaBox = attrs->mediaBox;
+    cropBox = attrs->cropBox;
+    haveCropBox = attrs->haveCropBox;
+    rotate = attrs->rotate;
+    attrs->resources.copy(&resources);
+  } else {
+    // set default MediaBox to 8.5" x 11" -- this shouldn't be necessary
+    // but some (non-compliant) PDF files don't specify a MediaBox
+    mediaBox.x1 = 0;
+    mediaBox.y1 = 0;
+    mediaBox.x2 = 612;
+    mediaBox.y2 = 792;
+    cropBox.x1 = cropBox.y1 = cropBox.x2 = cropBox.y2 = 0;
+    haveCropBox = gFalse;
+    rotate = 0;
+    resources.initNull();
+  }
+
+  // media box
+  readBox(dict, "MediaBox", &mediaBox);
+
+  // crop box
+  if (readBox(dict, "CropBox", &cropBox)) {
+    haveCropBox = gTrue;
+  }
+  if (!haveCropBox) {
+    cropBox = mediaBox;
+  }
+
+  // other boxes
+  bleedBox = cropBox;
+  readBox(dict, "BleedBox", &bleedBox);
+  trimBox = cropBox;
+  readBox(dict, "TrimBox", &trimBox);
+  artBox = cropBox;
+  readBox(dict, "ArtBox", &artBox);
+
+  // clip all other boxes to the media box
+  cropBox.clipTo(&mediaBox);
+  bleedBox.clipTo(&mediaBox);
+  trimBox.clipTo(&mediaBox);
+  artBox.clipTo(&mediaBox);
+
+  // rotate
+  dict->lookup("Rotate", &obj1);
+  if (obj1.isInt()) {
+    rotate = obj1.getInt();
+  }
+  obj1.free();
+  while (rotate < 0) {
+    rotate += 360;
+  }
+  while (rotate >= 360) {
+    rotate -= 360;
+  }
+
+  // misc attributes
+  dict->lookup("LastModified", &lastModified);
+  dict->lookup("BoxColorInfo", &boxColorInfo);
+  dict->lookup("Group", &group);
+  dict->lookup("Metadata", &metadata);
+  dict->lookup("PieceInfo", &pieceInfo);
+  dict->lookup("SeparationInfo", &separationInfo);
+
+  // resource dictionary
+  dict->lookup("Resources", &obj1);
+  if (obj1.isDict()) {
+    resources.free();
+    obj1.copy(&resources);
+  }
+  obj1.free();
+}
+
+PageAttrs::~PageAttrs() {
+  lastModified.free();
+  boxColorInfo.free();
+  group.free();
+  metadata.free();
+  pieceInfo.free();
+  separationInfo.free();
+  resources.free();
+}
+
+GBool PageAttrs::readBox(Dict *dict, char *key, PDFRectangle *box) {
+  PDFRectangle tmp;
+  double t;
+  Object obj1, obj2;
+  GBool ok;
+
+  dict->lookup(key, &obj1);
+  if (obj1.isArray() && obj1.arrayGetLength() == 4) {
+    ok = gTrue;
+    obj1.arrayGet(0, &obj2);
+    if (obj2.isNum()) {
+      tmp.x1 = obj2.getNum();
+    } else {
+      ok = gFalse;
+    }
+    obj2.free();
+    obj1.arrayGet(1, &obj2);
+    if (obj2.isNum()) {
+      tmp.y1 = obj2.getNum();
+    } else {
+      ok = gFalse;
+    }
+    obj2.free();
+    obj1.arrayGet(2, &obj2);
+    if (obj2.isNum()) {
+      tmp.x2 = obj2.getNum();
+    } else {
+      ok = gFalse;
+    }
+    obj2.free();
+    obj1.arrayGet(3, &obj2);
+    if (obj2.isNum()) {
+      tmp.y2 = obj2.getNum();
+    } else {
+      ok = gFalse;
+    }
+    obj2.free();
+    if (ok) {
+      if (tmp.x1 > tmp.x2) {
+	t = tmp.x1; tmp.x1 = tmp.x2; tmp.x2 = t;
+      }
+      if (tmp.y1 > tmp.y2) {
+	t = tmp.y1; tmp.y1 = tmp.y2; tmp.y2 = t;
+      }
+      *box = tmp;
+    }
+  } else {
+    ok = gFalse;
+  }
+  obj1.free();
+  return ok;
+}
+
+//------------------------------------------------------------------------
+// Page
+//------------------------------------------------------------------------
+
+Page::Page(XRef *xrefA, int numA, Dict *pageDict, PageAttrs *attrsA) {
+  ok = gTrue;
+  xref = xrefA;
+  num = numA;
+
+  // get attributes
+  attrs = attrsA;
+
+  // annotations
+  pageDict->lookupNF("Annots", &annots);
+  if (!(annots.isRef() || annots.isArray() || annots.isNull())) {
+    error(-1, "Page annotations object (page %d) is wrong type (%s)",
+	  num, annots.getTypeName());
+    annots.free();
+    goto err2;
+  }
+
+  // contents
+  pageDict->lookupNF("Contents", &contents);
+  if (!(contents.isRef() || contents.isArray() ||
+	contents.isNull())) {
+    error(-1, "Page contents object (page %d) is wrong type (%s)",
+	  num, contents.getTypeName());
+    contents.free();
+    goto err1;
+  }
+
+  return;
+
+ err2:
+  annots.initNull();
+ err1:
+  contents.initNull();
+  ok = gFalse;
+}
+
+Page::~Page() {
+  delete attrs;
+  annots.free();
+  contents.free();
+}
+
+Links *Page::getLinks(Catalog *catalog) {
+  Links *links;
+  Object obj;
+
+  links = new Links(getAnnots(&obj), catalog->getBaseURI());
+  obj.free();
+  return links;
+}
+
+void Page::display(OutputDev *out, double hDPI, double vDPI,
+		   int rotate, GBool useMediaBox, GBool crop,
+		   GBool printing, Catalog *catalog,
+		   GBool (*abortCheckCbk)(void *data),
+		   void *abortCheckCbkData) {
+  displaySlice(out, hDPI, vDPI, rotate, useMediaBox, crop,
+	       -1, -1, -1, -1, printing, catalog,
+	       abortCheckCbk, abortCheckCbkData);
+}
+
+void Page::displaySlice(OutputDev *out, double hDPI, double vDPI,
+			int rotate, GBool useMediaBox, GBool crop,
+			int sliceX, int sliceY, int sliceW, int sliceH,
+			GBool printing, Catalog *catalog,
+			GBool (*abortCheckCbk)(void *data),
+			void *abortCheckCbkData) {
+#ifndef PDF_PARSER_ONLY
+  PDFRectangle *mediaBox, *cropBox;
+  PDFRectangle box;
+  Gfx *gfx;
+  Object obj;
+  Annots *annotList;
+  Dict *acroForm;
+  int i;
+
+  if (!out->checkPageSlice(this, hDPI, vDPI, rotate, useMediaBox, crop,
+			   sliceX, sliceY, sliceW, sliceH,
+			   printing, catalog,
+			   abortCheckCbk, abortCheckCbkData)) {
+    return;
+  }
+
+  rotate += getRotate();
+  if (rotate >= 360) {
+    rotate -= 360;
+  } else if (rotate < 0) {
+    rotate += 360;
+  }
+
+  makeBox(hDPI, vDPI, rotate, useMediaBox, out->upsideDown(),
+	  sliceX, sliceY, sliceW, sliceH, &box, &crop);
+  cropBox = getCropBox();
+
+  if (globalParams->getPrintCommands()) {
+    mediaBox = getMediaBox();
+    printf("***** MediaBox = ll:%g,%g ur:%g,%g\n",
+	   mediaBox->x1, mediaBox->y1, mediaBox->x2, mediaBox->y2);
+    printf("***** CropBox = ll:%g,%g ur:%g,%g\n",
+	   cropBox->x1, cropBox->y1, cropBox->x2, cropBox->y2);
+    printf("***** Rotate = %d\n", attrs->getRotate());
+  }
+
+  gfx = new Gfx(xref, out, num, attrs->getResourceDict(),
+		hDPI, vDPI, &box, crop ? cropBox : (PDFRectangle *)NULL,
+		rotate, abortCheckCbk, abortCheckCbkData);
+  contents.fetch(xref, &obj);
+  if (!obj.isNull()) {
+    gfx->saveState();
+    gfx->display(&obj);
+    gfx->restoreState();
+  }
+  obj.free();
+
+  // draw annotations
+  annotList = new Annots(xref, catalog, getAnnots(&obj));
+  obj.free();
+  acroForm = catalog->getAcroForm()->isDict() ?
+               catalog->getAcroForm()->getDict() : NULL;
+  if (acroForm) {
+    if (acroForm->lookup("NeedAppearances", &obj)) {
+      if (obj.isBool() && obj.getBool()) {
+	annotList->generateAppearances(acroForm);
+      }
+    }
+    obj.free();
+  }
+  if (annotList->getNumAnnots() > 0) {
+    if (globalParams->getPrintCommands()) {
+      printf("***** Annotations\n");
+    }
+    for (i = 0; i < annotList->getNumAnnots(); ++i) {
+      annotList->getAnnot(i)->draw(gfx, printing);
+    }
+    out->dump();
+  }
+  delete annotList;
+
+  delete gfx;
+#endif
+}
+
+void Page::makeBox(double hDPI, double vDPI, int rotate,
+		   GBool useMediaBox, GBool upsideDown,
+		   double sliceX, double sliceY, double sliceW, double sliceH,
+		   PDFRectangle *box, GBool *crop) {
+  PDFRectangle *mediaBox, *cropBox, *baseBox;
+  double kx, ky;
+
+  mediaBox = getMediaBox();
+  cropBox = getCropBox();
+  if (sliceW >= 0 && sliceH >= 0) {
+    baseBox = useMediaBox ? mediaBox : cropBox;
+    kx = 72.0 / hDPI;
+    ky = 72.0 / vDPI;
+    if (rotate == 90) {
+      if (upsideDown) {
+	box->x1 = baseBox->x1 + ky * sliceY;
+	box->x2 = baseBox->x1 + ky * (sliceY + sliceH);
+      } else {
+	box->x1 = baseBox->x2 - ky * (sliceY + sliceH);
+	box->x2 = baseBox->x2 - ky * sliceY;
+      }
+      box->y1 = baseBox->y1 + kx * sliceX;
+      box->y2 = baseBox->y1 + kx * (sliceX + sliceW);
+    } else if (rotate == 180) {
+      box->x1 = baseBox->x2 - kx * (sliceX + sliceW);
+      box->x2 = baseBox->x2 - kx * sliceX;
+      if (upsideDown) {
+	box->y1 = baseBox->y1 + ky * sliceY;
+	box->y2 = baseBox->y1 + ky * (sliceY + sliceH);
+      } else {
+	box->y1 = baseBox->y2 - ky * (sliceY + sliceH);
+	box->y2 = baseBox->y2 - ky * sliceY;
+      }
+    } else if (rotate == 270) {
+      if (upsideDown) {
+	box->x1 = baseBox->x2 - ky * (sliceY + sliceH);
+	box->x2 = baseBox->x2 - ky * sliceY;
+      } else {
+	box->x1 = baseBox->x1 + ky * sliceY;
+	box->x2 = baseBox->x1 + ky * (sliceY + sliceH);
+      }
+      box->y1 = baseBox->y2 - kx * (sliceX + sliceW);
+      box->y2 = baseBox->y2 - kx * sliceX;
+    } else {
+      box->x1 = baseBox->x1 + kx * sliceX;
+      box->x2 = baseBox->x1 + kx * (sliceX + sliceW);
+      if (upsideDown) {
+	box->y1 = baseBox->y2 - ky * (sliceY + sliceH);
+	box->y2 = baseBox->y2 - ky * sliceY;
+      } else {
+	box->y1 = baseBox->y1 + ky * sliceY;
+	box->y2 = baseBox->y1 + ky * (sliceY + sliceH);
+      }
+    }
+  } else if (useMediaBox) {
+    *box = *mediaBox;
+  } else {
+    *box = *cropBox;
+    *crop = gFalse;
+  }
+}
+
+void Page::processLinks(OutputDev *out, Catalog *catalog) {
+  Links *links;
+  int i;
+
+  links = getLinks(catalog);
+  for (i = 0; i < links->getNumLinks(); ++i) {
+    out->processLink(links->getLink(i), catalog);
+  }
+  delete links;
+}
+
+void Page::getDefaultCTM(double *ctm, double hDPI, double vDPI,
+			 int rotate, GBool useMediaBox, GBool upsideDown) {
+  GfxState *state;
+  int i;
+
+  rotate += getRotate();
+  if (rotate >= 360) {
+    rotate -= 360;
+  } else if (rotate < 0) {
+    rotate += 360;
+  }
+  state = new GfxState(hDPI, vDPI,
+		       useMediaBox ? getMediaBox() : getCropBox(),
+		       rotate, upsideDown);
+  for (i = 0; i < 6; ++i) {
+    ctm[i] = state->getCTM()[i];
+  }
+  delete state;
+}
diff --git a/xpdf/Page.h b/xpdf/Page.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Page.h
@@ -0,0 +1,187 @@
+//========================================================================
+//
+// Page.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef PAGE_H
+#define PAGE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "Object.h"
+
+class Dict;
+class XRef;
+class OutputDev;
+class Links;
+class Catalog;
+
+//------------------------------------------------------------------------
+
+class PDFRectangle {
+public:
+  double x1, y1, x2, y2;
+
+  PDFRectangle() { x1 = y1 = x2 = y2 = 0; }
+  PDFRectangle(double x1A, double y1A, double x2A, double y2A)
+    { x1 = x1A; y1 = y1A; x2 = x2A; y2 = y2A; }
+  GBool isValid() { return x1 != 0 || y1 != 0 || x2 != 0 || y2 != 0; }
+  void clipTo(PDFRectangle *rect);
+};
+
+//------------------------------------------------------------------------
+// PageAttrs
+//------------------------------------------------------------------------
+
+class PageAttrs {
+public:
+
+  // Construct a new PageAttrs object by merging a dictionary
+  // (of type Pages or Page) into another PageAttrs object.  If
+  // <attrs> is NULL, uses defaults.
+  PageAttrs(PageAttrs *attrs, Dict *dict);
+
+  // Destructor.
+  ~PageAttrs();
+
+  // Accessors.
+  PDFRectangle *getMediaBox() { return &mediaBox; }
+  PDFRectangle *getCropBox() { return &cropBox; }
+  GBool isCropped() { return haveCropBox; }
+  PDFRectangle *getBleedBox() { return &bleedBox; }
+  PDFRectangle *getTrimBox() { return &trimBox; }
+  PDFRectangle *getArtBox() { return &artBox; }
+  int getRotate() { return rotate; }
+  GString *getLastModified()
+    { return lastModified.isString()
+	? lastModified.getString() : (GString *)NULL; }
+  Dict *getBoxColorInfo()
+    { return boxColorInfo.isDict() ? boxColorInfo.getDict() : (Dict *)NULL; }
+  Dict *getGroup()
+    { return group.isDict() ? group.getDict() : (Dict *)NULL; }
+  Stream *getMetadata()
+    { return metadata.isStream() ? metadata.getStream() : (Stream *)NULL; }
+  Dict *getPieceInfo()
+    { return pieceInfo.isDict() ? pieceInfo.getDict() : (Dict *)NULL; }
+  Dict *getSeparationInfo()
+    { return separationInfo.isDict()
+	? separationInfo.getDict() : (Dict *)NULL; }
+  Dict *getResourceDict()
+    { return resources.isDict() ? resources.getDict() : (Dict *)NULL; }
+
+private:
+
+  GBool readBox(Dict *dict, char *key, PDFRectangle *box);
+
+  PDFRectangle mediaBox;
+  PDFRectangle cropBox;
+  GBool haveCropBox;
+  PDFRectangle bleedBox;
+  PDFRectangle trimBox;
+  PDFRectangle artBox;
+  int rotate;
+  Object lastModified;
+  Object boxColorInfo;
+  Object group;
+  Object metadata;
+  Object pieceInfo;
+  Object separationInfo;
+  Object resources;
+};
+
+//------------------------------------------------------------------------
+// Page
+//------------------------------------------------------------------------
+
+class Page {
+public:
+
+  // Constructor.
+  Page(XRef *xrefA, int numA, Dict *pageDict, PageAttrs *attrsA);
+
+  // Destructor.
+  ~Page();
+
+  // Is page valid?
+  GBool isOk() { return ok; }
+
+  // Get page parameters.
+  int getNum() { return num; }
+  PDFRectangle *getMediaBox() { return attrs->getMediaBox(); }
+  PDFRectangle *getCropBox() { return attrs->getCropBox(); }
+  GBool isCropped() { return attrs->isCropped(); }
+  double getMediaWidth() 
+    { return attrs->getMediaBox()->x2 - attrs->getMediaBox()->x1; }
+  double getMediaHeight()
+    { return attrs->getMediaBox()->y2 - attrs->getMediaBox()->y1; }
+  double getCropWidth() 
+    { return attrs->getCropBox()->x2 - attrs->getCropBox()->x1; }
+  double getCropHeight()
+    { return attrs->getCropBox()->y2 - attrs->getCropBox()->y1; }
+  PDFRectangle *getBleedBox() { return attrs->getBleedBox(); }
+  PDFRectangle *getTrimBox() { return attrs->getTrimBox(); }
+  PDFRectangle *getArtBox() { return attrs->getArtBox(); }
+  int getRotate() { return attrs->getRotate(); }
+  GString *getLastModified() { return attrs->getLastModified(); }
+  Dict *getBoxColorInfo() { return attrs->getBoxColorInfo(); }
+  Dict *getGroup() { return attrs->getGroup(); }
+  Stream *getMetadata() { return attrs->getMetadata(); }
+  Dict *getPieceInfo() { return attrs->getPieceInfo(); }
+  Dict *getSeparationInfo() { return attrs->getSeparationInfo(); }
+
+  // Get resource dictionary.
+  Dict *getResourceDict() { return attrs->getResourceDict(); }
+
+  // Get annotations array.
+  Object *getAnnots(Object *obj) { return annots.fetch(xref, obj); }
+
+  // Return a list of links.
+  Links *getLinks(Catalog *catalog);
+
+  // Get contents.
+  Object *getContents(Object *obj) { return contents.fetch(xref, obj); }
+
+  // Display a page.
+  void display(OutputDev *out, double hDPI, double vDPI,
+	       int rotate, GBool useMediaBox, GBool crop,
+	       GBool printing, Catalog *catalog,
+	       GBool (*abortCheckCbk)(void *data) = NULL,
+	       void *abortCheckCbkData = NULL);
+
+  // Display part of a page.
+  void displaySlice(OutputDev *out, double hDPI, double vDPI,
+		    int rotate, GBool useMediaBox, GBool crop,
+		    int sliceX, int sliceY, int sliceW, int sliceH,
+		    GBool printing, Catalog *catalog,
+		    GBool (*abortCheckCbk)(void *data) = NULL,
+		    void *abortCheckCbkData = NULL);
+
+  void makeBox(double hDPI, double vDPI, int rotate,
+	       GBool useMediaBox, GBool upsideDown,
+	       double sliceX, double sliceY, double sliceW, double sliceH,
+	       PDFRectangle *box, GBool *crop);
+
+  void processLinks(OutputDev *out, Catalog *catalog);
+
+  // Get the page's default CTM.
+  void getDefaultCTM(double *ctm, double hDPI, double vDPI,
+		     int rotate, GBool useMediaBox, GBool upsideDown);
+
+private:
+
+  XRef *xref;			// the xref table for this PDF file
+  int num;			// page number
+  PageAttrs *attrs;		// page attributes
+  Object annots;		// annotations array
+  Object contents;		// page contents
+  GBool ok;			// true if page is valid
+};
+
+#endif
diff --git a/xpdf/Parser.cc b/xpdf/Parser.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Parser.cc
@@ -0,0 +1,227 @@
+//========================================================================
+//
+// Parser.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stddef.h>
+#include "Object.h"
+#include "Array.h"
+#include "Dict.h"
+#include "Decrypt.h"
+#include "Parser.h"
+#include "XRef.h"
+#include "Error.h"
+
+Parser::Parser(XRef *xrefA, Lexer *lexerA, GBool allowStreamsA) {
+  xref = xrefA;
+  lexer = lexerA;
+  inlineImg = 0;
+  allowStreams = allowStreamsA;
+  lexer->getObj(&buf1);
+  lexer->getObj(&buf2);
+}
+
+Parser::~Parser() {
+  buf1.free();
+  buf2.free();
+  delete lexer;
+}
+
+Object *Parser::getObj(Object *obj, Guchar *fileKey,
+		       CryptAlgorithm encAlgorithm, int keyLength,
+		       int objNum, int objGen) {
+  char *key;
+  Stream *str;
+  Object obj2;
+  int num;
+  DecryptStream *decrypt;
+  GString *s, *s2;
+  int c;
+
+  // refill buffer after inline image data
+  if (inlineImg == 2) {
+    buf1.free();
+    buf2.free();
+    lexer->getObj(&buf1);
+    lexer->getObj(&buf2);
+    inlineImg = 0;
+  }
+
+  // array
+  if (buf1.isCmd("[")) {
+    shift();
+    obj->initArray(xref);
+    while (!buf1.isCmd("]") && !buf1.isEOF())
+      obj->arrayAdd(getObj(&obj2, fileKey, encAlgorithm, keyLength,
+			   objNum, objGen));
+    if (buf1.isEOF())
+      error(getPos(), "End of file inside array");
+    shift();
+
+  // dictionary or stream
+  } else if (buf1.isCmd("<<")) {
+    shift();
+    obj->initDict(xref);
+    while (!buf1.isCmd(">>") && !buf1.isEOF()) {
+      if (!buf1.isName()) {
+	error(getPos(), "Dictionary key must be a name object");
+	shift();
+      } else {
+	key = copyString(buf1.getName());
+	shift();
+	if (buf1.isEOF() || buf1.isError()) {
+	  gfree(key);
+	  break;
+	}
+	obj->dictAdd(key, getObj(&obj2, fileKey, encAlgorithm, keyLength,
+				 objNum, objGen));
+      }
+    }
+    if (buf1.isEOF())
+      error(getPos(), "End of file inside dictionary");
+    // stream objects are not allowed inside content streams or
+    // object streams
+    if (allowStreams && buf2.isCmd("stream")) {
+      if ((str = makeStream(obj, fileKey, encAlgorithm, keyLength,
+			    objNum, objGen))) {
+	obj->initStream(str);
+      } else {
+	obj->free();
+	obj->initError();
+      }
+    } else {
+      shift();
+    }
+
+  // indirect reference or integer
+  } else if (buf1.isInt()) {
+    num = buf1.getInt();
+    shift();
+    if (buf1.isInt() && buf2.isCmd("R")) {
+      obj->initRef(num, buf1.getInt());
+      shift();
+      shift();
+    } else {
+      obj->initInt(num);
+    }
+
+  // string
+  } else if (buf1.isString() && fileKey) {
+    s = buf1.getString();
+    s2 = new GString();
+    obj2.initNull();
+    decrypt = new DecryptStream(new MemStream(s->getCString(), 0,
+					      s->getLength(), &obj2),
+				fileKey, encAlgorithm, keyLength,
+				objNum, objGen);
+    decrypt->reset();
+    while ((c = decrypt->getChar()) != EOF) {
+      s2->append((char)c);
+    }
+    delete decrypt;
+    obj->initString(s2);
+    shift();
+
+  // simple object
+  } else {
+    buf1.copy(obj);
+    shift();
+  }
+
+  return obj;
+}
+
+Stream *Parser::makeStream(Object *dict, Guchar *fileKey,
+			   CryptAlgorithm encAlgorithm, int keyLength,
+			   int objNum, int objGen) {
+  Object obj;
+  BaseStream *baseStr;
+  Stream *str;
+  Guint pos, endPos, length;
+
+  // get stream start position
+  lexer->skipToNextLine();
+  pos = lexer->getPos();
+
+  // get length
+  dict->dictLookup("Length", &obj);
+  if (obj.isInt()) {
+    length = (Guint)obj.getInt();
+    obj.free();
+  } else {
+    error(getPos(), "Bad 'Length' attribute in stream");
+    obj.free();
+    return NULL;
+  }
+
+  // check for length in damaged file
+  if (xref && xref->getStreamEnd(pos, &endPos)) {
+    length = endPos - pos;
+  }
+
+  // in badly damaged PDF files, we can run off the end of the input
+  // stream immediately after the "stream" token
+  if (!lexer->getStream()) {
+    return NULL;
+  }
+  baseStr = lexer->getStream()->getBaseStream();
+
+  // skip over stream data
+  lexer->setPos(pos + length);
+
+  // refill token buffers and check for 'endstream'
+  shift();  // kill '>>'
+  shift();  // kill 'stream'
+  if (buf1.isCmd("endstream")) {
+    shift();
+  } else {
+    error(getPos(), "Missing 'endstream'");
+    // kludge for broken PDF files: just add 5k to the length, and
+    // hope its enough
+    length += 5000;
+  }
+
+  // make base stream
+  str = baseStr->makeSubStream(pos, gTrue, length, dict);
+
+  // handle decryption
+  if (fileKey) {
+    str = new DecryptStream(str, fileKey, encAlgorithm, keyLength,
+			    objNum, objGen);
+  }
+
+  // get filters
+  str = str->addFilters(dict);
+
+  return str;
+}
+
+void Parser::shift() {
+  if (inlineImg > 0) {
+    if (inlineImg < 2) {
+      ++inlineImg;
+    } else {
+      // in a damaged content stream, if 'ID' shows up in the middle
+      // of a dictionary, we need to reset
+      inlineImg = 0;
+    }
+  } else if (buf2.isCmd("ID")) {
+    lexer->skipChar();		// skip char after 'ID' command
+    inlineImg = 1;
+  }
+  buf1.free();
+  buf1 = buf2;
+  if (inlineImg > 0)		// don't buffer inline image data
+    buf2.initNull();
+  else
+    lexer->getObj(&buf2);
+}
diff --git a/xpdf/Parser.h b/xpdf/Parser.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Parser.h
@@ -0,0 +1,59 @@
+//========================================================================
+//
+// Parser.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef PARSER_H
+#define PARSER_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "Lexer.h"
+
+//------------------------------------------------------------------------
+// Parser
+//------------------------------------------------------------------------
+
+class Parser {
+public:
+
+  // Constructor.
+  Parser(XRef *xrefA, Lexer *lexerA, GBool allowStreamsA);
+
+  // Destructor.
+  ~Parser();
+
+  // Get the next object from the input stream.
+  Object *getObj(Object *obj, Guchar *fileKey = NULL,
+		 CryptAlgorithm encAlgorithm = cryptRC4, int keyLength = 0,
+		 int objNum = 0, int objGen = 0);
+
+  // Get stream.
+  Stream *getStream() { return lexer->getStream(); }
+
+  // Get current position in file.
+  int getPos() { return lexer->getPos(); }
+
+private:
+
+  XRef *xref;			// the xref table for this PDF file
+  Lexer *lexer;			// input stream
+  GBool allowStreams;		// parse stream objects?
+  Object buf1, buf2;		// next two tokens
+  int inlineImg;		// set when inline image data is encountered
+
+  Stream *makeStream(Object *dict, Guchar *fileKey,
+		     CryptAlgorithm encAlgorithm, int keyLength,
+		     int objNum, int objGen);
+  void shift();
+};
+
+#endif
+
diff --git a/xpdf/PreScanOutputDev.cc b/xpdf/PreScanOutputDev.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/PreScanOutputDev.cc
@@ -0,0 +1,257 @@
+//========================================================================
+//
+// PreScanOutputDev.cc
+//
+// Copyright 2005 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <math.h>
+#include "GlobalParams.h"
+#include "GfxFont.h"
+#include "Link.h"
+#include "PreScanOutputDev.h"
+
+//------------------------------------------------------------------------
+// PreScanOutputDev
+//------------------------------------------------------------------------
+
+PreScanOutputDev::PreScanOutputDev() {
+  clearStats();
+}
+
+PreScanOutputDev::~PreScanOutputDev() {
+}
+
+void PreScanOutputDev::startPage(int pageNum, GfxState *state) {
+}
+
+void PreScanOutputDev::endPage() {
+}
+
+void PreScanOutputDev::stroke(GfxState *state) {
+  double *dash;
+  int dashLen;
+  double dashStart;
+
+  check(state->getStrokeColorSpace(), state->getStrokeColor(),
+	state->getStrokeOpacity(), state->getBlendMode());
+  state->getLineDash(&dash, &dashLen, &dashStart);
+  if (dashLen != 0) {
+    gdi = gFalse;
+  }
+}
+
+void PreScanOutputDev::fill(GfxState *state) {
+  check(state->getFillColorSpace(), state->getFillColor(),
+	state->getFillOpacity(), state->getBlendMode());
+}
+
+void PreScanOutputDev::eoFill(GfxState *state) {
+  check(state->getFillColorSpace(), state->getFillColor(),
+	state->getFillOpacity(), state->getBlendMode());
+}
+
+void PreScanOutputDev::clip(GfxState *state) {
+  //~ check for a rectangle "near" the edge of the page;
+  //~   else set gdi to false
+}
+
+void PreScanOutputDev::eoClip(GfxState *state) {
+  //~ see clip()
+}
+
+void PreScanOutputDev::beginStringOp(GfxState *state) {
+  int render;
+  GfxFont *font;
+  double m11, m12, m21, m22;
+  Ref embRef;
+  DisplayFontParam *dfp;
+  GBool simpleTTF;
+
+  render = state->getRender();
+  if (!(render & 1)) {
+    check(state->getFillColorSpace(), state->getFillColor(),
+	  state->getFillOpacity(), state->getBlendMode());
+  }
+  if ((render & 3) == 1 || (render & 3) == 2) {
+    check(state->getStrokeColorSpace(), state->getStrokeColor(),
+	  state->getStrokeOpacity(), state->getBlendMode());
+  }
+
+  font = state->getFont();
+  state->getFontTransMat(&m11, &m12, &m21, &m22);
+  simpleTTF = fabs(m11 + m22) < 0.01 &&
+              m11 > 0 &&
+              fabs(m12) < 0.01 &&
+              fabs(m21) < 0.01 &&
+              fabs(state->getHorizScaling() - 1) < 0.001 &&
+              (font->getType() == fontTrueType ||
+	       font->getType() == fontTrueTypeOT) &&
+              (font->getEmbeddedFontID(&embRef) ||
+	       font->getExtFontFile() ||
+	       (font->getName() &&
+		(dfp = globalParams->getDisplayFont(font->getName())) &&
+		dfp->kind == displayFontTT));
+  if (simpleTTF) {
+    //~ need to create a FoFiTrueType object, and check for a Unicode cmap
+  }
+  if (state->getRender() != 0 || !simpleTTF) {
+    gdi = gFalse;
+  }
+}
+
+void PreScanOutputDev::endStringOp(GfxState *state) {
+}
+
+GBool PreScanOutputDev::beginType3Char(GfxState *state, double x, double y,
+				       double dx, double dy,
+				       CharCode code, Unicode *u, int uLen) {
+  // return false so all Type 3 chars get rendered (no caching)
+  return gFalse;
+}
+
+void PreScanOutputDev::endType3Char(GfxState *state) {
+}
+
+void PreScanOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
+				     int width, int height, GBool invert,
+				     GBool inlineImg) {
+  int i, j;
+
+  check(state->getFillColorSpace(), state->getFillColor(),
+	state->getFillOpacity(), state->getBlendMode());
+  gdi = gFalse;
+
+  if (inlineImg) {
+    str->reset();
+    j = height * ((width + 7) / 8);
+    for (i = 0; i < j; ++i)
+      str->getChar();
+    str->close();
+  }
+}
+
+void PreScanOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
+				 int width, int height,
+				 GfxImageColorMap *colorMap,
+				 int *maskColors, GBool inlineImg) {
+  GfxColorSpace *colorSpace;
+  int i, j;
+
+  colorSpace = colorMap->getColorSpace();
+  if (colorSpace->getMode() == csIndexed) {
+    colorSpace = ((GfxIndexedColorSpace *)colorSpace)->getBase();
+  }
+  if (colorSpace->getMode() != csDeviceGray &&
+      colorSpace->getMode() != csCalGray) {
+    gray = gFalse;
+  }
+  mono = gFalse;
+  if (state->getBlendMode() != gfxBlendNormal) {
+    transparency = gTrue;
+  }
+  gdi = gFalse;
+
+  if (inlineImg) {
+    str->reset();
+    j = height * ((width * colorMap->getNumPixelComps() *
+		   colorMap->getBits() + 7) / 8);
+    for (i = 0; i < j; ++i)
+      str->getChar();
+    str->close();
+  }
+}
+
+void PreScanOutputDev::drawMaskedImage(GfxState *state, Object *ref,
+				       Stream *str,
+				       int width, int height,
+				       GfxImageColorMap *colorMap,
+				       Stream *maskStr,
+				       int maskWidth, int maskHeight,
+				       GBool maskInvert) {
+  GfxColorSpace *colorSpace;
+
+  colorSpace = colorMap->getColorSpace();
+  if (colorSpace->getMode() == csIndexed) {
+    colorSpace = ((GfxIndexedColorSpace *)colorSpace)->getBase();
+  }
+  if (colorSpace->getMode() != csDeviceGray &&
+      colorSpace->getMode() != csCalGray) {
+    gray = gFalse;
+  }
+  mono = gFalse;
+  if (state->getBlendMode() != gfxBlendNormal) {
+    transparency = gTrue;
+  }
+  gdi = gFalse;
+}
+
+void PreScanOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref,
+					   Stream *str,
+					   int width, int height,
+					   GfxImageColorMap *colorMap,
+					   Stream *maskStr,
+					   int maskWidth, int maskHeight,
+					   GfxImageColorMap *maskColorMap) {
+  GfxColorSpace *colorSpace;
+
+  colorSpace = colorMap->getColorSpace();
+  if (colorSpace->getMode() == csIndexed) {
+    colorSpace = ((GfxIndexedColorSpace *)colorSpace)->getBase();
+  }
+  if (colorSpace->getMode() != csDeviceGray &&
+      colorSpace->getMode() != csCalGray) {
+    gray = gFalse;
+  }
+  mono = gFalse;
+  transparency = gTrue;
+  gdi = gFalse;
+}
+
+void PreScanOutputDev::beginTransparencyGroup(
+			   GfxState *state, double *bbox,
+			   GfxColorSpace *blendingColorSpace,
+			   GBool isolated, GBool knockout,
+			   GBool forSoftMask) {
+  transparency = gTrue;
+  gdi = gFalse;
+}
+
+void PreScanOutputDev::check(GfxColorSpace *colorSpace, GfxColor *color,
+			     double opacity, GfxBlendMode blendMode) {
+  GfxRGB rgb;
+
+  if (colorSpace->getMode() == csPattern) {
+    mono = gFalse;
+    gray = gFalse;
+    gdi = gFalse;
+  } else {
+    colorSpace->getRGB(color, &rgb);
+    if (rgb.r != rgb.g || rgb.g != rgb.b || rgb.b != rgb.r) {
+      mono = gFalse;
+      gray = gFalse;
+    } else if (!((rgb.r == 0 && rgb.g == 0 && rgb.b == 0) ||
+		 (rgb.r == gfxColorComp1 &&
+		  rgb.g == gfxColorComp1 &&
+		  rgb.b == gfxColorComp1))) {
+      mono = gFalse;
+    }
+  }
+  if (opacity != 1 || blendMode != gfxBlendNormal) {
+    transparency = gTrue;
+  }
+}
+
+void PreScanOutputDev::clearStats() {
+  mono = gTrue;
+  gray = gTrue;
+  transparency = gFalse;
+  gdi = gTrue;
+}
diff --git a/xpdf/PreScanOutputDev.h b/xpdf/PreScanOutputDev.h
new file mode 100644
--- /dev/null
+++ b/xpdf/PreScanOutputDev.h
@@ -0,0 +1,130 @@
+//========================================================================
+//
+// PreScanOutputDev.h
+//
+// Copyright 2005 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef PRESCANOUTPUTDEV_H
+#define PRESCANOUTPUTDEV_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "GfxState.h"
+#include "OutputDev.h"
+
+//------------------------------------------------------------------------
+// PreScanOutputDev
+//------------------------------------------------------------------------
+
+class PreScanOutputDev: public OutputDev {
+public:
+
+  // Constructor.
+  PreScanOutputDev();
+
+  // Destructor.
+  virtual ~PreScanOutputDev();
+
+  //----- get info about output device
+
+  // Does this device use upside-down coordinates?
+  // (Upside-down means (0,0) is the top left corner of the page.)
+  virtual GBool upsideDown() { return gTrue; }
+
+  // Does this device use drawChar() or drawString()?
+  virtual GBool useDrawChar() { return gTrue; }
+
+  // Does this device use beginType3Char/endType3Char?  Otherwise,
+  // text in Type 3 fonts will be drawn with drawChar/drawString.
+  virtual GBool interpretType3Chars() { return gTrue; }
+
+  //----- initialization and control
+
+  // Start a page.
+  virtual void startPage(int pageNum, GfxState *state);
+
+  // End a page.
+  virtual void endPage();
+
+  //----- path painting
+  virtual void stroke(GfxState *state);
+  virtual void fill(GfxState *state);
+  virtual void eoFill(GfxState *state);
+
+  //----- path clipping
+  virtual void clip(GfxState *state);
+  virtual void eoClip(GfxState *state);
+
+  //----- text drawing
+  virtual void beginStringOp(GfxState *state);
+  virtual void endStringOp(GfxState *state);
+  virtual GBool beginType3Char(GfxState *state, double x, double y,
+			       double dx, double dy,
+			       CharCode code, Unicode *u, int uLen);
+  virtual void endType3Char(GfxState *state);
+
+  //----- image drawing
+  virtual void drawImageMask(GfxState *state, Object *ref, Stream *str,
+			     int width, int height, GBool invert,
+			     GBool inlineImg);
+  virtual void drawImage(GfxState *state, Object *ref, Stream *str,
+			 int width, int height, GfxImageColorMap *colorMap,
+			 int *maskColors, GBool inlineImg);
+  virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
+			       int width, int height,
+			       GfxImageColorMap *colorMap,
+			       Stream *maskStr, int maskWidth, int maskHeight,
+			       GBool maskInvert);
+  virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
+				   int width, int height,
+				   GfxImageColorMap *colorMap,
+				   Stream *maskStr,
+				   int maskWidth, int maskHeight,
+				   GfxImageColorMap *maskColorMap);
+
+  //----- transparency groups and soft masks
+  virtual void beginTransparencyGroup(GfxState *state, double *bbox,
+				      GfxColorSpace *blendingColorSpace,
+				      GBool isolated, GBool knockout,
+				      GBool forSoftMask);
+
+  //----- special access
+
+  // Returns true if the operations performed since the last call to
+  // clearStats() are all monochrome (black or white).
+  GBool isMonochrome() { return mono; }
+
+  // Returns true if the operations performed since the last call to
+  // clearStats() are all gray.
+  GBool isGray() { return gray; }
+
+  // Returns true if the operations performed since the last call to
+  // clearStats() included any transparency.
+  GBool usesTransparency() { return transparency; }
+
+  // Returns true if the operations performed since the last call to
+  // clearStats() are all rasterizable by GDI calls in GDIOutputDev.
+  GBool isAllGDI() { return gdi; }
+
+  // Clear the stats used by the above functions.
+  void clearStats();
+
+private:
+
+  void check(GfxColorSpace *colorSpace, GfxColor *color,
+	     double opacity, GfxBlendMode blendMode);
+
+  GBool mono;
+  GBool gray;
+  GBool transparency;
+  GBool gdi;
+};
+
+#endif
diff --git a/xpdf/SecurityHandler.cc b/xpdf/SecurityHandler.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SecurityHandler.cc
@@ -0,0 +1,390 @@
+//========================================================================
+//
+// SecurityHandler.cc
+//
+// Copyright 2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include "GString.h"
+#include "PDFDoc.h"
+#include "Decrypt.h"
+#include "Error.h"
+#include "GlobalParams.h"
+#if HAVE_XPDFCORE
+#  include "XPDFCore.h"
+#elif HAVE_WINPDFCORE
+#  include "WinPDFCore.h"
+#endif
+#ifdef ENABLE_PLUGINS
+#  include "XpdfPluginAPI.h"
+#endif
+#include "SecurityHandler.h"
+
+//------------------------------------------------------------------------
+// SecurityHandler
+//------------------------------------------------------------------------
+
+SecurityHandler *SecurityHandler::make(PDFDoc *docA, Object *encryptDictA) {
+  Object filterObj;
+  SecurityHandler *secHdlr;
+#ifdef ENABLE_PLUGINS
+  XpdfSecurityHandler *xsh;
+#endif
+
+  encryptDictA->dictLookup("Filter", &filterObj);
+  if (filterObj.isName("Standard")) {
+    secHdlr = new StandardSecurityHandler(docA, encryptDictA);
+  } else if (filterObj.isName()) {
+#ifdef ENABLE_PLUGINS
+    if ((xsh = globalParams->getSecurityHandler(filterObj.getName()))) {
+      secHdlr = new ExternalSecurityHandler(docA, encryptDictA, xsh);
+    } else {
+#endif
+      error(-1, "Couldn't find the '%s' security handler",
+	    filterObj.getName());
+      secHdlr = NULL;
+#ifdef ENABLE_PLUGINS
+    }
+#endif
+  } else {
+    error(-1, "Missing or invalid 'Filter' entry in encryption dictionary");
+    secHdlr = NULL;
+  }
+  filterObj.free();
+  return secHdlr;
+}
+
+SecurityHandler::SecurityHandler(PDFDoc *docA) {
+  doc = docA;
+}
+
+SecurityHandler::~SecurityHandler() {
+}
+
+GBool SecurityHandler::checkEncryption(GString *ownerPassword,
+				       GString *userPassword) {
+  void *authData;
+  GBool ok;
+  int i;
+
+  if (ownerPassword || userPassword) {
+    authData = makeAuthData(ownerPassword, userPassword);
+  } else {
+    authData = NULL;
+  }
+  ok = authorize(authData);
+  if (authData) {
+    freeAuthData(authData);
+  }
+  for (i = 0; !ok && i < 3; ++i) {
+    if (!(authData = getAuthData())) {
+      break;
+    }
+    ok = authorize(authData);
+    if (authData) {
+      freeAuthData(authData);
+    }
+  }
+  if (!ok) {
+    error(-1, "Incorrect password");
+  }
+  return ok;
+}
+
+//------------------------------------------------------------------------
+// StandardSecurityHandler
+//------------------------------------------------------------------------
+
+class StandardAuthData {
+public:
+
+  StandardAuthData(GString *ownerPasswordA, GString *userPasswordA) {
+    ownerPassword = ownerPasswordA;
+    userPassword = userPasswordA;
+  }
+
+  ~StandardAuthData() {
+    if (ownerPassword) {
+      delete ownerPassword;
+    }
+    if (userPassword) {
+      delete userPassword;
+    }
+  }
+
+  GString *ownerPassword;
+  GString *userPassword;
+};
+
+StandardSecurityHandler::StandardSecurityHandler(PDFDoc *docA,
+						 Object *encryptDictA):
+  SecurityHandler(docA)
+{
+  Object versionObj, revisionObj, lengthObj;
+  Object ownerKeyObj, userKeyObj, permObj, fileIDObj;
+  Object fileIDObj1;
+  Object cryptFiltersObj, streamFilterObj, stringFilterObj;
+  Object cryptFilterObj, cfmObj, cfLengthObj;
+  Object encryptMetadataObj;
+
+  ok = gFalse;
+  fileID = NULL;
+  ownerKey = NULL;
+  userKey = NULL;
+
+  encryptDictA->dictLookup("V", &versionObj);
+  encryptDictA->dictLookup("R", &revisionObj);
+  encryptDictA->dictLookup("Length", &lengthObj);
+  encryptDictA->dictLookup("O", &ownerKeyObj);
+  encryptDictA->dictLookup("U", &userKeyObj);
+  encryptDictA->dictLookup("P", &permObj);
+  doc->getXRef()->getTrailerDict()->dictLookup("ID", &fileIDObj);
+  if (versionObj.isInt() &&
+      revisionObj.isInt() &&
+      ownerKeyObj.isString() && ownerKeyObj.getString()->getLength() == 32 &&
+      userKeyObj.isString() && userKeyObj.getString()->getLength() == 32 &&
+      permObj.isInt()) {
+    encVersion = versionObj.getInt();
+    encRevision = revisionObj.getInt();
+    encAlgorithm = cryptRC4;
+    // revision 2 forces a 40-bit key - some buggy PDF generators
+    // set the Length value incorrectly
+    if (encRevision == 2 || !lengthObj.isInt()) {
+      fileKeyLength = 5;
+    } else {
+      fileKeyLength = lengthObj.getInt() / 8;
+    }
+    encryptMetadata = gTrue;
+    //~ this currently only handles a subset of crypt filter functionality
+    if (encVersion == 4 && encRevision == 4) {
+      encryptDictA->dictLookup("CF", &cryptFiltersObj);
+      encryptDictA->dictLookup("StmF", &streamFilterObj);
+      encryptDictA->dictLookup("StrF", &stringFilterObj);
+      if (cryptFiltersObj.isDict() &&
+	  streamFilterObj.isName() &&
+	  stringFilterObj.isName() &&
+	  !strcmp(streamFilterObj.getName(), stringFilterObj.getName())) {
+	if (cryptFiltersObj.dictLookup(streamFilterObj.getName(),
+				       &cryptFilterObj)->isDict()) {
+	  cryptFilterObj.dictLookup("CFM", &cfmObj);
+	  if (cfmObj.isName("V2")) {
+	    encVersion = 2;
+	    encRevision = 3;
+	    if (cryptFilterObj.dictLookup("Length", &cfLengthObj)->isInt()) {
+	      //~ according to the spec, this should be cfLengthObj / 8
+	      fileKeyLength = cfLengthObj.getInt();
+	    }
+	    cfLengthObj.free();
+	  } else if (cfmObj.isName("AESV2")) {
+	    encVersion = 2;
+	    encRevision = 3;
+	    encAlgorithm = cryptAES;
+	    if (cryptFilterObj.dictLookup("Length", &cfLengthObj)->isInt()) {
+	      //~ according to the spec, this should be cfLengthObj / 8
+	      fileKeyLength = cfLengthObj.getInt();
+	    }
+	    cfLengthObj.free();
+	  }
+	  cfmObj.free();
+	}
+	cryptFilterObj.free();
+      }
+      stringFilterObj.free();
+      streamFilterObj.free();
+      cryptFiltersObj.free();
+      if (encryptDictA->dictLookup("EncryptMetadata",
+				   &encryptMetadataObj)->isBool()) {
+	encryptMetadata = encryptMetadataObj.getBool();
+      }
+      encryptMetadataObj.free();
+    }
+    permFlags = permObj.getInt();
+    ownerKey = ownerKeyObj.getString()->copy();
+    userKey = userKeyObj.getString()->copy();
+    if (encVersion >= 1 && encVersion <= 2 &&
+	encRevision >= 2 && encRevision <= 3) {
+      if (fileIDObj.isArray()) {
+	if (fileIDObj.arrayGet(0, &fileIDObj1)->isString()) {
+	  fileID = fileIDObj1.getString()->copy();
+	} else {
+	  fileID = new GString();
+	}
+	fileIDObj1.free();
+      } else {
+	fileID = new GString();
+      }
+      ok = gTrue;
+    } else {
+      error(-1, "Unsupported version/revision (%d/%d) of Standard security handler",
+	    encVersion, encRevision);
+    }
+  } else {
+    error(-1, "Weird encryption info");
+  }
+  if (fileKeyLength > 16) {
+    fileKeyLength = 16;
+  }
+  fileIDObj.free();
+  permObj.free();
+  userKeyObj.free();
+  ownerKeyObj.free();
+  lengthObj.free();
+  revisionObj.free();
+  versionObj.free();
+}
+
+StandardSecurityHandler::~StandardSecurityHandler() {
+  if (fileID) {
+    delete fileID;
+  }
+  if (ownerKey) {
+    delete ownerKey;
+  }
+  if (userKey) {
+    delete userKey;
+  }
+}
+
+void *StandardSecurityHandler::makeAuthData(GString *ownerPassword,
+					    GString *userPassword) {
+  return new StandardAuthData(ownerPassword ? ownerPassword->copy()
+			                    : (GString *)NULL,
+			      userPassword ? userPassword->copy()
+			                   : (GString *)NULL);
+}
+
+void *StandardSecurityHandler::getAuthData() {
+#if HAVE_XPDFCORE
+  XPDFCore *core;
+  GString *password;
+
+  if (!(core = (XPDFCore *)doc->getGUIData()) ||
+      !(password = core->getPassword())) {
+    return NULL;
+  }
+  return new StandardAuthData(password, password->copy());
+#elif HAVE_WINPDFCORE
+  WinPDFCore *core;
+  GString *password;
+
+  if (!(core = (WinPDFCore *)doc->getGUIData()) ||
+      !(password = core->getPassword())) {
+    return NULL;
+  }
+  return new StandardAuthData(password, password->copy());
+#else
+  return NULL;
+#endif
+}
+
+void StandardSecurityHandler::freeAuthData(void *authData) {
+  delete (StandardAuthData *)authData;
+}
+
+GBool StandardSecurityHandler::authorize(void *authData) {
+  GString *ownerPassword, *userPassword;
+
+  if (!ok) {
+    return gFalse;
+  }
+  if (authData) {
+    ownerPassword = ((StandardAuthData *)authData)->ownerPassword;
+    userPassword = ((StandardAuthData *)authData)->userPassword;
+  } else {
+    ownerPassword = NULL;
+    userPassword = NULL;
+  }
+  if (!Decrypt::makeFileKey(encVersion, encRevision, fileKeyLength,
+			    ownerKey, userKey, permFlags, fileID,
+			    ownerPassword, userPassword, fileKey,
+			    encryptMetadata, &ownerPasswordOk)) {
+    return gFalse;
+  }
+  return gTrue;
+}
+
+#ifdef ENABLE_PLUGINS
+
+//------------------------------------------------------------------------
+// ExternalSecurityHandler
+//------------------------------------------------------------------------
+
+ExternalSecurityHandler::ExternalSecurityHandler(PDFDoc *docA,
+						 Object *encryptDictA,
+						 XpdfSecurityHandler *xshA):
+  SecurityHandler(docA)
+{
+  encryptDictA->copy(&encryptDict);
+  xsh = xshA;
+  encAlgorithm = cryptRC4; //~ this should be obtained via getKey
+  ok = gFalse;
+
+  if (!(*xsh->newDoc)(xsh->handlerData, (XpdfDoc)docA,
+		      (XpdfObject)encryptDictA, &docData)) {
+    return;
+  }
+
+  ok = gTrue;
+}
+
+ExternalSecurityHandler::~ExternalSecurityHandler() {
+  (*xsh->freeDoc)(xsh->handlerData, docData);
+  encryptDict.free();
+}
+
+void *ExternalSecurityHandler::makeAuthData(GString *ownerPassword,
+					    GString *userPassword) {
+  char *opw, *upw;
+  void *authData;
+
+  opw = ownerPassword ? ownerPassword->getCString() : (char *)NULL;
+  upw = userPassword ? userPassword->getCString() : (char *)NULL;
+  if (!(*xsh->makeAuthData)(xsh->handlerData, docData, opw, upw, &authData)) {
+    return NULL;
+  }
+  return authData;
+}
+
+void *ExternalSecurityHandler::getAuthData() {
+  void *authData;
+
+  if (!(*xsh->getAuthData)(xsh->handlerData, docData, &authData)) {
+    return NULL;
+  }
+  return authData;
+}
+
+void ExternalSecurityHandler::freeAuthData(void *authData) {
+  (*xsh->freeAuthData)(xsh->handlerData, docData, authData);
+}
+
+GBool ExternalSecurityHandler::authorize(void *authData) {
+  char *key;
+  int length;
+
+  if (!ok) {
+    return gFalse;
+  }
+  permFlags = (*xsh->authorize)(xsh->handlerData, docData, authData);
+  if (!(permFlags & xpdfPermissionOpen)) {
+    return gFalse;
+  }
+  if (!(*xsh->getKey)(xsh->handlerData, docData, &key, &length, &encVersion)) {
+    return gFalse;
+  }
+  if ((fileKeyLength = length) > 16) {
+    fileKeyLength = 16;
+  }
+  memcpy(fileKey, key, fileKeyLength);
+  (*xsh->freeKey)(xsh->handlerData, docData, key, length);
+  return gTrue;
+}
+
+#endif // ENABLE_PLUGINS
diff --git a/xpdf/SecurityHandler.h b/xpdf/SecurityHandler.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SecurityHandler.h
@@ -0,0 +1,160 @@
+//========================================================================
+//
+// SecurityHandler.h
+//
+// Copyright 2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef SECURITYHANDLER_H
+#define SECURITYHANDLER_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "Object.h"
+
+class GString;
+class PDFDoc;
+struct XpdfSecurityHandler;
+
+//------------------------------------------------------------------------
+// SecurityHandler
+//------------------------------------------------------------------------
+
+class SecurityHandler {
+public:
+
+  static SecurityHandler *make(PDFDoc *docA, Object *encryptDictA);
+
+  SecurityHandler(PDFDoc *docA);
+  virtual ~SecurityHandler();
+
+  // Check the document's encryption.  If the document is encrypted,
+  // this will first try <ownerPassword> and <userPassword> (in
+  // "batch" mode), and if those fail, it will attempt to request a
+  // password from the user.  This is the high-level function that
+  // calls the lower level functions for the specific security handler
+  // (requesting a password three times, etc.).  Returns true if the
+  // document can be opened (if it's unencrypted, or if a correct
+  // password is obtained); false otherwise (encrypted and no correct
+  // password).
+  GBool checkEncryption(GString *ownerPassword,
+			GString *userPassword);
+
+  // Create authorization data for the specified owner and user
+  // passwords.  If the security handler doesn't support "batch" mode,
+  // this function should return NULL.
+  virtual void *makeAuthData(GString *ownerPassword,
+			     GString *userPassword) = 0;
+
+  // Construct authorization data, typically by prompting the user for
+  // a password.  Returns an authorization data object, or NULL to
+  // cancel.
+  virtual void *getAuthData() = 0;
+
+  // Free the authorization data returned by makeAuthData or
+  // getAuthData.
+  virtual void freeAuthData(void *authData) = 0;
+
+  // Attempt to authorize the document, using the supplied
+  // authorization data (which may be NULL).  Returns true if
+  // successful (i.e., if at least the right to open the document was
+  // granted).
+  virtual GBool authorize(void *authData) = 0;
+
+  // Return the various authorization parameters.  These are only
+  // valid after authorize has returned true.
+  virtual int getPermissionFlags() = 0;
+  virtual GBool getOwnerPasswordOk() = 0;
+  virtual Guchar *getFileKey() = 0;
+  virtual int getFileKeyLength() = 0;
+  virtual int getEncVersion() = 0;
+  virtual CryptAlgorithm getEncAlgorithm() = 0;
+
+protected:
+
+  PDFDoc *doc;
+};
+
+//------------------------------------------------------------------------
+// StandardSecurityHandler
+//------------------------------------------------------------------------
+
+class StandardSecurityHandler: public SecurityHandler {
+public:
+
+  StandardSecurityHandler(PDFDoc *docA, Object *encryptDictA);
+  virtual ~StandardSecurityHandler();
+
+  virtual void *makeAuthData(GString *ownerPassword,
+			     GString *userPassword);
+  virtual void *getAuthData();
+  virtual void freeAuthData(void *authData);
+  virtual GBool authorize(void *authData);
+  virtual int getPermissionFlags() { return permFlags; }
+  virtual GBool getOwnerPasswordOk() { return ownerPasswordOk; }
+  virtual Guchar *getFileKey() { return fileKey; }
+  virtual int getFileKeyLength() { return fileKeyLength; }
+  virtual int getEncVersion() { return encVersion; }
+  virtual CryptAlgorithm getEncAlgorithm() { return encAlgorithm; }
+
+private:
+
+  int permFlags;
+  GBool ownerPasswordOk;
+  Guchar fileKey[16];
+  int fileKeyLength;
+  int encVersion;
+  int encRevision;
+  CryptAlgorithm encAlgorithm;
+  GBool encryptMetadata;
+
+  GString *ownerKey, *userKey;
+  GString *fileID;
+  GBool ok;
+};
+
+#ifdef ENABLE_PLUGINS
+//------------------------------------------------------------------------
+// ExternalSecurityHandler
+//------------------------------------------------------------------------
+
+class ExternalSecurityHandler: public SecurityHandler {
+public:
+
+  ExternalSecurityHandler(PDFDoc *docA, Object *encryptDictA,
+			  XpdfSecurityHandler *xshA);
+  virtual ~ExternalSecurityHandler();
+
+  virtual void *makeAuthData(GString *ownerPassword,
+			     GString *userPassword);
+  virtual void *getAuthData();
+  virtual void freeAuthData(void *authData);
+  virtual GBool authorize(void *authData);
+  virtual int getPermissionFlags() { return permFlags; }
+  virtual GBool getOwnerPasswordOk() { return gFalse; }
+  virtual Guchar *getFileKey() { return fileKey; }
+  virtual int getFileKeyLength() { return fileKeyLength; }
+  virtual int getEncVersion() { return encVersion; }
+  virtual CryptAlgorithm getEncAlgorithm() { return encAlgorithm; }
+
+private:
+
+  Object encryptDict;
+  XpdfSecurityHandler *xsh;
+  void *docData;
+  int permFlags;
+  Guchar fileKey[16];
+  int fileKeyLength;
+  int encVersion;
+  CryptAlgorithm encAlgorithm;
+  GBool ok;
+};
+#endif // ENABLE_PLUGINS
+
+#endif
diff --git a/xpdf/Splash.cc b/xpdf/Splash.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Splash.cc
@@ -0,0 +1,3310 @@
+//========================================================================
+//
+// Splash.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "gmem.h"
+#include "SplashErrorCodes.h"
+#include "SplashMath.h"
+#include "SplashBitmap.h"
+#include "SplashState.h"
+#include "SplashPath.h"
+#include "SplashXPath.h"
+#include "SplashXPathScanner.h"
+#include "SplashPattern.h"
+#include "SplashScreen.h"
+#include "SplashFont.h"
+#include "SplashGlyphBitmap.h"
+#include "Splash.h"
+
+//------------------------------------------------------------------------
+
+// distance of Bezier control point from center for circle approximation
+// = (4 * (sqrt(2) - 1) / 3) * r
+#define bezierCircle ((SplashCoord)0.55228475)
+#define bezierCircle2 ((SplashCoord)(0.5 * 0.55228475))
+
+// Divide a 16-bit value (in [0, 255*255]) by 255, returning an 8-bit result.
+static inline Guchar div255(int x) {
+  return (Guchar)((x + (x >> 8) + 0x80) >> 8);
+}
+
+//------------------------------------------------------------------------
+// SplashPipe
+//------------------------------------------------------------------------
+
+#define splashPipeMaxStages 9
+
+struct SplashPipe {
+  // pixel coordinates
+  int x, y;
+
+  // source pattern
+  SplashPattern *pattern;
+
+  // source alpha and color
+  SplashCoord aInput;
+  GBool usesShape;
+  Guchar aSrc;
+  SplashColorPtr cSrc;
+  SplashColor cSrcVal;
+
+  // non-isolated group alpha0
+  Guchar *alpha0Ptr;
+
+  // soft mask
+  SplashColorPtr softMaskPtr;
+
+  // destination alpha and color
+  SplashColorPtr destColorPtr;
+  int destColorMask;
+  Guchar *destAlphaPtr;
+
+  // shape
+  SplashCoord shape;
+
+  // result alpha and color
+  GBool noTransparency;
+  SplashPipeResultColorCtrl resultColorCtrl;
+
+  // non-isolated group correction
+  int nonIsolatedGroup;
+};
+
+SplashPipeResultColorCtrl Splash::pipeResultColorNoAlphaBlend[] = {
+  splashPipeResultColorNoAlphaBlendMono,
+  splashPipeResultColorNoAlphaBlendMono,
+  splashPipeResultColorNoAlphaBlendRGB,
+  splashPipeResultColorNoAlphaBlendRGB
+#if SPLASH_CMYK
+  ,
+  splashPipeResultColorNoAlphaBlendCMYK
+#endif
+};
+
+SplashPipeResultColorCtrl Splash::pipeResultColorAlphaNoBlend[] = {
+  splashPipeResultColorAlphaNoBlendMono,
+  splashPipeResultColorAlphaNoBlendMono,
+  splashPipeResultColorAlphaNoBlendRGB,
+  splashPipeResultColorAlphaNoBlendRGB
+#if SPLASH_CMYK
+  ,
+  splashPipeResultColorAlphaNoBlendCMYK
+#endif
+};
+
+SplashPipeResultColorCtrl Splash::pipeResultColorAlphaBlend[] = {
+  splashPipeResultColorAlphaBlendMono,
+  splashPipeResultColorAlphaBlendMono,
+  splashPipeResultColorAlphaBlendRGB,
+  splashPipeResultColorAlphaBlendRGB
+#if SPLASH_CMYK
+  ,
+  splashPipeResultColorAlphaBlendCMYK
+#endif
+};
+
+//------------------------------------------------------------------------
+
+static void blendXor(SplashColorPtr src, SplashColorPtr dest,
+		     SplashColorPtr blend, SplashColorMode cm) {
+  int i;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    blend[i] = src[i] ^ dest[i];
+  }
+}
+
+//------------------------------------------------------------------------
+// modified region
+//------------------------------------------------------------------------
+
+void Splash::clearModRegion() {
+  modXMin = bitmap->getWidth();
+  modYMin = bitmap->getHeight();
+  modXMax = -1;
+  modYMax = -1;
+}
+
+inline void Splash::updateModX(int x) {
+  if (x < modXMin) {
+    modXMin = x;
+  }
+  if (x > modXMax) {
+    modXMax = x;
+  }
+}
+
+inline void Splash::updateModY(int y) {
+  if (y < modYMin) {
+    modYMin = y;
+  }
+  if (y > modYMax) {
+    modYMax = y;
+  }
+}
+
+//------------------------------------------------------------------------
+// pipeline
+//------------------------------------------------------------------------
+
+inline void Splash::pipeInit(SplashPipe *pipe, int x, int y,
+			     SplashPattern *pattern, SplashColorPtr cSrc,
+			     SplashCoord aInput, GBool usesShape,
+			     GBool nonIsolatedGroup) {
+  pipeSetXY(pipe, x, y);
+  pipe->pattern = NULL;
+
+  // source color
+  if (pattern) {
+    if (pattern->isStatic()) {
+      pattern->getColor(x, y, pipe->cSrcVal);
+    } else {
+      pipe->pattern = pattern;
+    }
+    pipe->cSrc = pipe->cSrcVal;
+  } else {
+    pipe->cSrc = cSrc;
+  }
+
+  // source alpha
+  pipe->aInput = aInput;
+  if (!state->softMask) {
+    if (usesShape) {
+      pipe->aInput *= 255;
+    } else {
+      pipe->aSrc = (Guchar)splashRound(pipe->aInput * 255);
+    }
+  }
+  pipe->usesShape = usesShape;
+
+  // result alpha
+  if (aInput == 1 && !state->softMask && !usesShape &&
+      !state->inNonIsolatedGroup) {
+    pipe->noTransparency = gTrue;
+  } else {
+    pipe->noTransparency = gFalse;
+  }
+
+  // result color
+  if (pipe->noTransparency) {
+    // the !state->blendFunc case is handled separately in pipeRun
+    pipe->resultColorCtrl = pipeResultColorNoAlphaBlend[bitmap->mode];
+  } else if (!state->blendFunc) {
+    pipe->resultColorCtrl = pipeResultColorAlphaNoBlend[bitmap->mode];
+  } else {
+    pipe->resultColorCtrl = pipeResultColorAlphaBlend[bitmap->mode];
+  }
+
+  // non-isolated group correction
+  if (nonIsolatedGroup) {
+    pipe->nonIsolatedGroup = splashColorModeNComps[bitmap->mode];
+  } else {
+    pipe->nonIsolatedGroup = 0;
+  }
+}
+
+inline void Splash::pipeRun(SplashPipe *pipe) {
+  Guchar aSrc, aDest, alpha2, alpha0, aResult;
+  SplashColor cDest, cBlend;
+  Guchar cResult0, cResult1, cResult2, cResult3;
+
+  //----- source color
+
+  // static pattern: handled in pipeInit
+  // fixed color: handled in pipeInit
+
+  // dynamic pattern
+  if (pipe->pattern) {
+    pipe->pattern->getColor(pipe->x, pipe->y, pipe->cSrcVal);
+  }
+
+  if (pipe->noTransparency && !state->blendFunc) {
+
+    //----- write destination pixel
+
+    switch (bitmap->mode) {
+    case splashModeMono1:
+      cResult0 = pipe->cSrc[0];
+      if (state->screen->test(pipe->x, pipe->y, cResult0)) {
+	*pipe->destColorPtr |= pipe->destColorMask;
+      } else {
+	*pipe->destColorPtr &= ~pipe->destColorMask;
+      }
+      if (!(pipe->destColorMask >>= 1)) {
+	pipe->destColorMask = 0x80;
+	++pipe->destColorPtr;
+      }
+      break;
+    case splashModeMono8:
+      *pipe->destColorPtr++ = pipe->cSrc[0];
+      break;
+    case splashModeRGB8:
+      *pipe->destColorPtr++ = pipe->cSrc[0];
+      *pipe->destColorPtr++ = pipe->cSrc[1];
+      *pipe->destColorPtr++ = pipe->cSrc[2];
+      break;
+    case splashModeBGR8:
+      *pipe->destColorPtr++ = pipe->cSrc[2];
+      *pipe->destColorPtr++ = pipe->cSrc[1];
+      *pipe->destColorPtr++ = pipe->cSrc[0];
+      break;
+#if SPLASH_CMYK
+    case splashModeCMYK8:
+      *pipe->destColorPtr++ = pipe->cSrc[0];
+      *pipe->destColorPtr++ = pipe->cSrc[1];
+      *pipe->destColorPtr++ = pipe->cSrc[2];
+      *pipe->destColorPtr++ = pipe->cSrc[3];
+      break;
+#endif
+    }
+    if (pipe->destAlphaPtr) {
+      *pipe->destAlphaPtr++ = 255;
+    }
+
+  } else {
+
+    //----- read destination pixel
+
+    switch (bitmap->mode) {
+    case splashModeMono1:
+      cDest[0] = (*pipe->destColorPtr & pipe->destColorMask) ? 0xff : 0x00;
+      break;
+    case splashModeMono8:
+      cDest[0] = *pipe->destColorPtr;
+      break;
+    case splashModeRGB8:
+      cDest[0] = pipe->destColorPtr[0];
+      cDest[1] = pipe->destColorPtr[1];
+      cDest[2] = pipe->destColorPtr[2];
+      break;
+    case splashModeBGR8:
+      cDest[0] = pipe->destColorPtr[2];
+      cDest[1] = pipe->destColorPtr[1];
+      cDest[2] = pipe->destColorPtr[0];
+      break;
+#if SPLASH_CMYK
+    case splashModeCMYK8:
+      cDest[0] = pipe->destColorPtr[0];
+      cDest[1] = pipe->destColorPtr[1];
+      cDest[2] = pipe->destColorPtr[2];
+      cDest[3] = pipe->destColorPtr[3];
+      break;
+#endif
+    }
+    if (pipe->destAlphaPtr) {
+      aDest = *pipe->destAlphaPtr;
+    } else {
+      aDest = 0xff;
+    }
+
+    //----- blend function
+
+    if (state->blendFunc) {
+      (*state->blendFunc)(pipe->cSrc, cDest, cBlend, bitmap->mode);
+    }
+
+    //----- source alpha
+
+    if (state->softMask) {
+      if (pipe->usesShape) {
+	aSrc = (Guchar)splashRound(pipe->aInput * *pipe->softMaskPtr++
+				   * pipe->shape);
+      } else {
+	aSrc = (Guchar)splashRound(pipe->aInput * *pipe->softMaskPtr++);
+      }
+    } else if (pipe->usesShape) {
+      // pipe->aInput is premultiplied by 255 in pipeInit
+      aSrc = (Guchar)splashRound(pipe->aInput * pipe->shape);
+    } else {
+      // precomputed in pipeInit
+      aSrc = pipe->aSrc;
+    }
+
+    //----- result alpha and non-isolated group element correction
+
+    if (pipe->noTransparency) {
+      alpha2 = aResult = 255;
+    } else {
+      aResult = aSrc + aDest - div255(aSrc * aDest);
+
+      if (pipe->alpha0Ptr) {
+	alpha0 = *pipe->alpha0Ptr++;
+	alpha2 = aResult + alpha0 - div255(aResult * alpha0);
+      } else {
+	alpha2 = aResult;
+      }
+    }
+
+    //----- result color
+
+    cResult0 = cResult1 = cResult2 = cResult3 = 0; // make gcc happy
+
+    switch (pipe->resultColorCtrl) {
+
+#if SPLASH_CMYK
+    case splashPipeResultColorNoAlphaBlendCMYK:
+      cResult3 = div255((255 - aDest) * pipe->cSrc[3] + aDest * cBlend[3]);
+#endif
+    case splashPipeResultColorNoAlphaBlendRGB:
+      cResult2 = div255((255 - aDest) * pipe->cSrc[2] + aDest * cBlend[2]);
+      cResult1 = div255((255 - aDest) * pipe->cSrc[1] + aDest * cBlend[1]);
+    case splashPipeResultColorNoAlphaBlendMono:
+      cResult0 = div255((255 - aDest) * pipe->cSrc[0] + aDest * cBlend[0]);
+      break;
+
+    case splashPipeResultColorAlphaNoBlendMono:
+      if (alpha2 == 0) {
+	cResult0 = 0;
+      } else {
+	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
+			     aSrc * pipe->cSrc[0]) / alpha2);
+      }
+      break;
+    case splashPipeResultColorAlphaNoBlendRGB:
+      if (alpha2 == 0) {
+	cResult0 = 0;
+	cResult1 = 0;
+	cResult2 = 0;
+      } else {
+	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
+			     aSrc * pipe->cSrc[0]) / alpha2);
+	cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] +
+			     aSrc * pipe->cSrc[1]) / alpha2);
+	cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] +
+			     aSrc * pipe->cSrc[2]) / alpha2);
+      }
+      break;
+#if SPLASH_CMYK
+    case splashPipeResultColorAlphaNoBlendCMYK:
+      if (alpha2 == 0) {
+	cResult0 = 0;
+	cResult1 = 0;
+	cResult2 = 0;
+	cResult3 = 0;
+      } else {
+	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
+			     aSrc * pipe->cSrc[0]) / alpha2);
+	cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] +
+			     aSrc * pipe->cSrc[1]) / alpha2);
+	cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] +
+			     aSrc * pipe->cSrc[2]) / alpha2);
+	cResult3 = (Guchar)(((alpha2 - aSrc) * cDest[3] +
+			     aSrc * pipe->cSrc[3]) / alpha2);
+      }
+      break;
+#endif
+
+    case splashPipeResultColorAlphaBlendMono:
+      if (alpha2 == 0) {
+	cResult0 = 0;
+      } else {
+	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
+			     aSrc * ((255 - aDest) * pipe->cSrc[0] +
+				     aDest * cBlend[0]) / 255) /
+			    alpha2);
+      }
+      break;
+    case splashPipeResultColorAlphaBlendRGB:
+      if (alpha2 == 0) {
+	cResult0 = 0;
+	cResult1 = 0;
+	cResult2 = 0;
+      } else {
+	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
+			     aSrc * ((255 - aDest) * pipe->cSrc[0] +
+				     aDest * cBlend[0]) / 255) /
+			    alpha2);
+	cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] +
+			     aSrc * ((255 - aDest) * pipe->cSrc[1] +
+				     aDest * cBlend[1]) / 255) /
+			    alpha2);
+	cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] +
+			     aSrc * ((255 - aDest) * pipe->cSrc[2] +
+				     aDest * cBlend[2]) / 255) /
+			    alpha2);
+      }
+      break;
+#if SPLASH_CMYK
+    case splashPipeResultColorAlphaBlendCMYK:
+      if (alpha2 == 0) {
+	cResult0 = 0;
+	cResult1 = 0;
+	cResult2 = 0;
+	cResult3 = 0;
+      } else {
+	cResult0 = (Guchar)(((alpha2 - aSrc) * cDest[0] +
+			     aSrc * ((255 - aDest) * pipe->cSrc[0] +
+				     aDest * cBlend[0]) / 255) /
+			    alpha2);
+	cResult1 = (Guchar)(((alpha2 - aSrc) * cDest[1] +
+			     aSrc * ((255 - aDest) * pipe->cSrc[1] +
+				     aDest * cBlend[1]) / 255) /
+			    alpha2);
+	cResult2 = (Guchar)(((alpha2 - aSrc) * cDest[2] +
+			     aSrc * ((255 - aDest) * pipe->cSrc[2] +
+				     aDest * cBlend[2]) / 255) /
+			    alpha2);
+	cResult3 = (Guchar)(((alpha2 - aSrc) * cDest[3] +
+			     aSrc * ((255 - aDest) * pipe->cSrc[3] +
+				     aDest * cBlend[3]) / 255) /
+			    alpha2);
+      }
+      break;
+#endif
+    }
+
+    //----- non-isolated group correction
+
+    if (aResult != 0) {
+      switch (pipe->nonIsolatedGroup) {
+#if SPLASH_CMYK
+      case 4:
+	cResult3 += (cResult3 - cDest[3]) * aDest *
+	            (255 - aResult) / (255 * aResult);
+#endif
+      case 3:
+	cResult2 += (cResult2 - cDest[2]) * aDest *
+	            (255 - aResult) / (255 * aResult);
+	cResult1 += (cResult1 - cDest[1]) * aDest *
+	            (255 - aResult) / (255 * aResult);
+      case 1:
+	cResult0 += (cResult0 - cDest[0]) * aDest *
+	            (255 - aResult) / (255 * aResult);
+      case 0:
+	break;
+      }
+    }
+
+    //----- write destination pixel
+
+    switch (bitmap->mode) {
+    case splashModeMono1:
+      if (state->screen->test(pipe->x, pipe->y, cResult0)) {
+	*pipe->destColorPtr |= pipe->destColorMask;
+      } else {
+	*pipe->destColorPtr &= ~pipe->destColorMask;
+      }
+      if (!(pipe->destColorMask >>= 1)) {
+	pipe->destColorMask = 0x80;
+	++pipe->destColorPtr;
+      }
+      break;
+    case splashModeMono8:
+      *pipe->destColorPtr++ = cResult0;
+      break;
+    case splashModeRGB8:
+      *pipe->destColorPtr++ = cResult0;
+      *pipe->destColorPtr++ = cResult1;
+      *pipe->destColorPtr++ = cResult2;
+      break;
+    case splashModeBGR8:
+      *pipe->destColorPtr++ = cResult2;
+      *pipe->destColorPtr++ = cResult1;
+      *pipe->destColorPtr++ = cResult0;
+      break;
+#if SPLASH_CMYK
+    case splashModeCMYK8:
+      *pipe->destColorPtr++ = cResult0;
+      *pipe->destColorPtr++ = cResult1;
+      *pipe->destColorPtr++ = cResult2;
+      *pipe->destColorPtr++ = cResult3;
+      break;
+#endif
+    }
+    if (pipe->destAlphaPtr) {
+      *pipe->destAlphaPtr++ = aResult;
+    }
+
+  }
+
+  ++pipe->x;
+}
+
+inline void Splash::pipeSetXY(SplashPipe *pipe, int x, int y) {
+  pipe->x = x;
+  pipe->y = y;
+  if (state->softMask) {
+    pipe->softMaskPtr =
+        &state->softMask->data[y * state->softMask->rowSize + x];
+  }
+  switch (bitmap->mode) {
+  case splashModeMono1:
+    pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + (x >> 3)];
+    pipe->destColorMask = 0x80 >> (x & 7);
+    break;
+  case splashModeMono8:
+    pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + x];
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x];
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x];
+    break;
+#endif
+  }
+  if (bitmap->alpha) {
+    pipe->destAlphaPtr = &bitmap->alpha[y * bitmap->width + x];
+  } else {
+    pipe->destAlphaPtr = NULL;
+  }
+  if (state->inNonIsolatedGroup && alpha0Bitmap->alpha) {
+    pipe->alpha0Ptr =
+        &alpha0Bitmap->alpha[(alpha0Y + y) * alpha0Bitmap->width +
+			     (alpha0X + x)];
+  } else {
+    pipe->alpha0Ptr = NULL;
+  }
+}
+
+inline void Splash::pipeIncX(SplashPipe *pipe) {
+  ++pipe->x;
+  if (state->softMask) {
+    ++pipe->softMaskPtr;
+  }
+  switch (bitmap->mode) {
+  case splashModeMono1:
+    if (!(pipe->destColorMask >>= 1)) {
+      pipe->destColorMask = 0x80;
+      ++pipe->destColorPtr;
+    }
+    break;
+  case splashModeMono8:
+    ++pipe->destColorPtr;
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    pipe->destColorPtr += 3;
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    pipe->destColorPtr += 4;
+    break;
+#endif
+  }
+  if (pipe->destAlphaPtr) {
+    ++pipe->destAlphaPtr;
+  }
+  if (pipe->alpha0Ptr) {
+    ++pipe->alpha0Ptr;
+  }
+}
+
+inline void Splash::drawPixel(SplashPipe *pipe, int x, int y, GBool noClip) {
+  if (noClip || state->clip->test(x, y)) {
+    pipeSetXY(pipe, x, y);
+    pipeRun(pipe);
+    updateModX(x);
+    updateModY(y);
+  }
+}
+
+inline void Splash::drawAAPixelInit() {
+  aaBufY = -1;
+}
+
+inline void Splash::drawAAPixel(SplashPipe *pipe, int x, int y) {
+#if splashAASize == 4
+  static int bitCount4[16] = { 0, 1, 1, 2, 1, 2, 2, 3,
+			       1, 2, 2, 3, 2, 3, 3, 4 };
+  int w;
+#else
+  int xx, yy;
+#endif
+  SplashColorPtr p;
+  int x0, x1, t;
+
+  if (x < 0 || x >= bitmap->width ||
+      y < state->clip->getYMinI() || y > state->clip->getYMaxI()) {
+    return;
+  }
+
+  // update aaBuf
+  if (y != aaBufY) {
+    memset(aaBuf->getDataPtr(), 0xff,
+	   aaBuf->getRowSize() * aaBuf->getHeight());
+    x0 = 0;
+    x1 = bitmap->width - 1;
+    state->clip->clipAALine(aaBuf, &x0, &x1, y);
+    aaBufY = y;
+  }
+
+  // compute the shape value
+#if splashAASize == 4
+  p = aaBuf->getDataPtr() + (x >> 1);
+  w = aaBuf->getRowSize();
+  if (x & 1) {
+    t = bitCount4[*p & 0x0f] + bitCount4[p[w] & 0x0f] +
+        bitCount4[p[2*w] & 0x0f] + bitCount4[p[3*w] & 0x0f];
+  } else {
+    t = bitCount4[*p >> 4] + bitCount4[p[w] >> 4] +
+        bitCount4[p[2*w] >> 4] + bitCount4[p[3*w] >> 4];
+  }
+#else
+  t = 0;
+  for (yy = 0; yy < splashAASize; ++yy) {
+    for (xx = 0; xx < splashAASize; ++xx) {
+      p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() +
+	  ((x * splashAASize + xx) >> 3);
+      t += (*p >> (7 - ((x * splashAASize + xx) & 7))) & 1;
+    }
+  }
+#endif
+
+  // draw the pixel
+  if (t != 0) {
+    pipeSetXY(pipe, x, y);
+    pipe->shape *= aaGamma[t];
+    pipeRun(pipe);
+    updateModX(x);
+    updateModY(y);
+  }
+}
+
+inline void Splash::drawSpan(SplashPipe *pipe, int x0, int x1, int y,
+			     GBool noClip) {
+  int x;
+
+  pipeSetXY(pipe, x0, y);
+  if (noClip) {
+    for (x = x0; x <= x1; ++x) {
+      pipeRun(pipe);
+    }
+    updateModX(x0);
+    updateModX(x1);
+    updateModY(y);
+  } else {
+    for (x = x0; x <= x1; ++x) {
+      if (state->clip->test(x, y)) {
+	pipeRun(pipe);
+	updateModX(x);
+	updateModY(y);
+      } else {
+	pipeIncX(pipe);
+      }
+    }
+  }
+}
+
+inline void Splash::drawAALine(SplashPipe *pipe, int x0, int x1, int y) {
+#if splashAASize == 4
+  static int bitCount4[16] = { 0, 1, 1, 2, 1, 2, 2, 3,
+			       1, 2, 2, 3, 2, 3, 3, 4 };
+  SplashColorPtr p0, p1, p2, p3;
+  int t;
+#else
+  SplashColorPtr p;
+  int xx, yy, t;
+#endif
+  int x;
+
+#if splashAASize == 4
+  p0 = aaBuf->getDataPtr() + (x0 >> 1);
+  p1 = p0 + aaBuf->getRowSize();
+  p2 = p1 + aaBuf->getRowSize();
+  p3 = p2 + aaBuf->getRowSize();
+#endif
+  pipeSetXY(pipe, x0, y);
+  for (x = x0; x <= x1; ++x) {
+
+    // compute the shape value
+#if splashAASize == 4
+    if (x & 1) {
+      t = bitCount4[*p0 & 0x0f] + bitCount4[*p1 & 0x0f] +
+	  bitCount4[*p2 & 0x0f] + bitCount4[*p3 & 0x0f];
+      ++p0; ++p1; ++p2; ++p3;
+    } else {
+      t = bitCount4[*p0 >> 4] + bitCount4[*p1 >> 4] +
+	  bitCount4[*p2 >> 4] + bitCount4[*p3 >> 4];
+    }
+#else
+    t = 0;
+    for (yy = 0; yy < splashAASize; ++yy) {
+      for (xx = 0; xx < splashAASize; ++xx) {
+	p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() +
+	    ((x * splashAASize + xx) >> 3);
+	t += (*p >> (7 - ((x * splashAASize + xx) & 7))) & 1;
+      }
+    }
+#endif
+
+    if (t != 0) {
+      pipe->shape = aaGamma[t];
+      pipeRun(pipe);
+      updateModX(x);
+      updateModY(y);
+    } else {
+      pipeIncX(pipe);
+    }
+  }
+}
+
+//------------------------------------------------------------------------
+
+// Transform a point from user space to device space.
+inline void Splash::transform(SplashCoord *matrix,
+			      SplashCoord xi, SplashCoord yi,
+			      SplashCoord *xo, SplashCoord *yo) {
+  //                          [ m[0] m[1] 0 ]
+  // [xo yo 1] = [xi yi 1] *  [ m[2] m[3] 0 ]
+  //                          [ m[4] m[5] 1 ]
+  *xo = xi * matrix[0] + yi * matrix[2] + matrix[4];
+  *yo = xi * matrix[1] + yi * matrix[3] + matrix[5];
+}
+
+//------------------------------------------------------------------------
+// Splash
+//------------------------------------------------------------------------
+
+Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
+	       SplashScreenParams *screenParams) {
+  int i;
+
+  bitmap = bitmapA;
+  vectorAntialias = vectorAntialiasA;
+  state = new SplashState(bitmap->width, bitmap->height, vectorAntialias,
+			  screenParams);
+  if (vectorAntialias) {
+    aaBuf = new SplashBitmap(splashAASize * bitmap->width, splashAASize,
+			     1, splashModeMono1, gFalse);
+    for (i = 0; i <= splashAASize * splashAASize; ++i) {
+      aaGamma[i] = splashPow((SplashCoord)i /
+			       (SplashCoord)(splashAASize * splashAASize),
+			     1.5);
+    }
+  } else {
+    aaBuf = NULL;
+  }
+  clearModRegion();
+  debugMode = gFalse;
+}
+
+Splash::Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
+	       SplashScreen *screenA) {
+  int i;
+
+  bitmap = bitmapA;
+  vectorAntialias = vectorAntialiasA;
+  state = new SplashState(bitmap->width, bitmap->height, vectorAntialias,
+			  screenA);
+  if (vectorAntialias) {
+    aaBuf = new SplashBitmap(splashAASize * bitmap->width, splashAASize,
+			     1, splashModeMono1, gFalse);
+    for (i = 0; i <= splashAASize * splashAASize; ++i) {
+      aaGamma[i] = splashPow((SplashCoord)i /
+			       (SplashCoord)(splashAASize * splashAASize),
+			     1.5);
+    }
+  } else {
+    aaBuf = NULL;
+  }
+  clearModRegion();
+  debugMode = gFalse;
+}
+
+Splash::~Splash() {
+  while (state->next) {
+    restoreState();
+  }
+  delete state;
+  if (vectorAntialias) {
+    delete aaBuf;
+  }
+}
+
+//------------------------------------------------------------------------
+// state read
+//------------------------------------------------------------------------
+
+SplashCoord *Splash::getMatrix() {
+  return state->matrix;
+}
+
+SplashPattern *Splash::getStrokePattern() {
+  return state->strokePattern;
+}
+
+SplashPattern *Splash::getFillPattern() {
+  return state->fillPattern;
+}
+
+SplashScreen *Splash::getScreen() {
+  return state->screen;
+}
+
+SplashBlendFunc Splash::getBlendFunc() {
+  return state->blendFunc;
+}
+
+SplashCoord Splash::getStrokeAlpha() {
+  return state->strokeAlpha;
+}
+
+SplashCoord Splash::getFillAlpha() {
+  return state->fillAlpha;
+}
+
+SplashCoord Splash::getLineWidth() {
+  return state->lineWidth;
+}
+
+int Splash::getLineCap() {
+  return state->lineCap;
+}
+
+int Splash::getLineJoin() {
+  return state->lineJoin;
+}
+
+SplashCoord Splash::getMiterLimit() {
+  return state->miterLimit;
+}
+
+SplashCoord Splash::getFlatness() {
+  return state->flatness;
+}
+
+SplashCoord *Splash::getLineDash() {
+  return state->lineDash;
+}
+
+int Splash::getLineDashLength() {
+  return state->lineDashLength;
+}
+
+SplashCoord Splash::getLineDashPhase() {
+  return state->lineDashPhase;
+}
+
+SplashClip *Splash::getClip() {
+  return state->clip;
+}
+
+SplashBitmap *Splash::getSoftMask() {
+  return state->softMask;
+}
+
+GBool Splash::getInNonIsolatedGroup() {
+  return state->inNonIsolatedGroup;
+}
+
+//------------------------------------------------------------------------
+// state write
+//------------------------------------------------------------------------
+
+void Splash::setMatrix(SplashCoord *matrix) {
+  memcpy(state->matrix, matrix, 6 * sizeof(SplashCoord));
+}
+
+void Splash::setStrokePattern(SplashPattern *strokePattern) {
+  state->setStrokePattern(strokePattern);
+}
+
+void Splash::setFillPattern(SplashPattern *fillPattern) {
+  state->setFillPattern(fillPattern);
+}
+
+void Splash::setScreen(SplashScreen *screen) {
+  state->setScreen(screen);
+}
+
+void Splash::setBlendFunc(SplashBlendFunc func) {
+  state->blendFunc = func;
+}
+
+void Splash::setStrokeAlpha(SplashCoord alpha) {
+  state->strokeAlpha = alpha;
+}
+
+void Splash::setFillAlpha(SplashCoord alpha) {
+  state->fillAlpha = alpha;
+}
+
+void Splash::setLineWidth(SplashCoord lineWidth) {
+  state->lineWidth = lineWidth;
+}
+
+void Splash::setLineCap(int lineCap) {
+  state->lineCap = lineCap;
+}
+
+void Splash::setLineJoin(int lineJoin) {
+  state->lineJoin = lineJoin;
+}
+
+void Splash::setMiterLimit(SplashCoord miterLimit) {
+  state->miterLimit = miterLimit;
+}
+
+void Splash::setFlatness(SplashCoord flatness) {
+  if (flatness < 1) {
+    state->flatness = 1;
+  } else {
+    state->flatness = flatness;
+  }
+}
+
+void Splash::setLineDash(SplashCoord *lineDash, int lineDashLength,
+			 SplashCoord lineDashPhase) {
+  state->setLineDash(lineDash, lineDashLength, lineDashPhase);
+}
+
+void Splash::setStrokeAdjust(GBool strokeAdjust) {
+  state->strokeAdjust = strokeAdjust;
+}
+
+void Splash::clipResetToRect(SplashCoord x0, SplashCoord y0,
+			     SplashCoord x1, SplashCoord y1) {
+  state->clip->resetToRect(x0, y0, x1, y1);
+}
+
+SplashError Splash::clipToRect(SplashCoord x0, SplashCoord y0,
+			       SplashCoord x1, SplashCoord y1) {
+  return state->clip->clipToRect(x0, y0, x1, y1);
+}
+
+SplashError Splash::clipToPath(SplashPath *path, GBool eo) {
+  return state->clip->clipToPath(path, state->matrix, state->flatness, eo);
+}
+
+void Splash::setSoftMask(SplashBitmap *softMask) {
+  state->setSoftMask(softMask);
+}
+
+void Splash::setInNonIsolatedGroup(SplashBitmap *alpha0BitmapA,
+				   int alpha0XA, int alpha0YA) {
+  alpha0Bitmap = alpha0BitmapA;
+  alpha0X = alpha0XA;
+  alpha0Y = alpha0YA;
+  state->inNonIsolatedGroup = gTrue;
+}
+
+//------------------------------------------------------------------------
+// state save/restore
+//------------------------------------------------------------------------
+
+void Splash::saveState() {
+  SplashState *newState;
+
+  newState = state->copy();
+  newState->next = state;
+  state = newState;
+}
+
+SplashError Splash::restoreState() {
+  SplashState *oldState;
+
+  if (!state->next) {
+    return splashErrNoSave;
+  }
+  oldState = state;
+  state = state->next;
+  delete oldState;
+  return splashOk;
+}
+
+//------------------------------------------------------------------------
+// drawing operations
+//------------------------------------------------------------------------
+
+void Splash::clear(SplashColorPtr color, Guchar alpha) {
+  SplashColorPtr row, p;
+  Guchar mono;
+  int x, y;
+
+  switch (bitmap->mode) {
+  case splashModeMono1:
+    mono = (color[0] & 0x80) ? 0xff : 0x00;
+    if (bitmap->rowSize < 0) {
+      memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1),
+	     mono, -bitmap->rowSize * bitmap->height);
+    } else {
+      memset(bitmap->data, mono, bitmap->rowSize * bitmap->height);
+    }
+    break;
+  case splashModeMono8:
+    if (bitmap->rowSize < 0) {
+      memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1),
+	     color[0], -bitmap->rowSize * bitmap->height);
+    } else {
+      memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height);
+    }
+    break;
+  case splashModeRGB8:
+    if (color[0] == color[1] && color[1] == color[2]) {
+      if (bitmap->rowSize < 0) {
+	memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1),
+	       color[0], -bitmap->rowSize * bitmap->height);
+      } else {
+	memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height);
+      }
+    } else {
+      row = bitmap->data;
+      for (y = 0; y < bitmap->height; ++y) {
+	p = row;
+	for (x = 0; x < bitmap->width; ++x) {
+	  *p++ = color[2];
+	  *p++ = color[1];
+	  *p++ = color[0];
+	}
+	row += bitmap->rowSize;
+      }
+    }
+    break;
+  case splashModeBGR8:
+    if (color[0] == color[1] && color[1] == color[2]) {
+      if (bitmap->rowSize < 0) {
+	memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1),
+	       color[0], -bitmap->rowSize * bitmap->height);
+      } else {
+	memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height);
+      }
+    } else {
+      row = bitmap->data;
+      for (y = 0; y < bitmap->height; ++y) {
+	p = row;
+	for (x = 0; x < bitmap->width; ++x) {
+	  *p++ = color[0];
+	  *p++ = color[1];
+	  *p++ = color[2];
+	}
+	row += bitmap->rowSize;
+      }
+    }
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    if (color[0] == color[1] && color[1] == color[2] && color[2] == color[3]) {
+      if (bitmap->rowSize < 0) {
+	memset(bitmap->data + bitmap->rowSize * (bitmap->height - 1),
+	       color[0], -bitmap->rowSize * bitmap->height);
+      } else {
+	memset(bitmap->data, color[0], bitmap->rowSize * bitmap->height);
+      }
+    } else {
+      row = bitmap->data;
+      for (y = 0; y < bitmap->height; ++y) {
+	p = row;
+	for (x = 0; x < bitmap->width; ++x) {
+	  *p++ = color[0];
+	  *p++ = color[1];
+	  *p++ = color[2];
+	  *p++ = color[3];
+	}
+	row += bitmap->rowSize;
+      }
+    }
+    break;
+#endif
+  }
+
+  if (bitmap->alpha) {
+    memset(bitmap->alpha, alpha, bitmap->width * bitmap->height);
+  }
+
+  updateModX(0);
+  updateModY(0);
+  updateModX(bitmap->width - 1);
+  updateModY(bitmap->height - 1);
+}
+
+SplashError Splash::stroke(SplashPath *path) {
+  SplashPath *path2, *dPath;
+
+  if (debugMode) {
+    printf("stroke [dash:%d] [width:%.2f]:\n",
+	   state->lineDashLength, (double)state->lineWidth);
+    dumpPath(path);
+  }
+  opClipRes = splashClipAllOutside;
+  if (path->length == 0) {
+    return splashErrEmptyPath;
+  }
+  path2 = flattenPath(path, state->matrix, state->flatness);
+  if (state->lineDashLength > 0) {
+    dPath = makeDashedPath(path2);
+    delete path2;
+    path2 = dPath;
+  }
+  if (state->lineWidth == 0) {
+    strokeNarrow(path2);
+  } else {
+    strokeWide(path2);
+  }
+  delete path2;
+  return splashOk;
+}
+
+void Splash::strokeNarrow(SplashPath *path) {
+  SplashPipe pipe;
+  SplashXPath *xPath;
+  SplashXPathSeg *seg;
+  int x0, x1, x2, x3, y0, y1, x, y, t;
+  SplashCoord dx, dy, dxdy;
+  SplashClipResult clipRes;
+  int nClipRes[3];
+  int i;
+
+  nClipRes[0] = nClipRes[1] = nClipRes[2] = 0;
+
+  xPath = new SplashXPath(path, state->matrix, state->flatness, gFalse);
+
+  pipeInit(&pipe, 0, 0, state->strokePattern, NULL, state->strokeAlpha,
+	   gFalse, gFalse);
+
+  for (i = 0, seg = xPath->segs; i < xPath->length; ++i, ++seg) {
+
+    x0 = splashFloor(seg->x0);
+    x1 = splashFloor(seg->x1);
+    y0 = splashFloor(seg->y0);
+    y1 = splashFloor(seg->y1);
+
+    // horizontal segment
+    if (y0 == y1) {
+      if (x0 > x1) {
+	t = x0; x0 = x1; x1 = t;
+      }
+      if ((clipRes = state->clip->testSpan(x0, x1, y0))
+	  != splashClipAllOutside) {
+	drawSpan(&pipe, x0, x1, y0, clipRes == splashClipAllInside);
+      }
+
+    // segment with |dx| > |dy|
+    } else if (splashAbs(seg->dxdy) > 1) {
+      dx = seg->x1 - seg->x0;
+      dy = seg->y1 - seg->y0;
+      dxdy = seg->dxdy;
+      if (y0 > y1) {
+	t = y0; y0 = y1; y1 = t;
+	t = x0; x0 = x1; x1 = t;
+	dx = -dx;
+	dy = -dy;
+      }
+      if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0,
+					   x0 <= x1 ? x1 : x0, y1))
+	  != splashClipAllOutside) {
+	if (dx > 0) {
+	  x2 = x0;
+	  x3 = splashFloor(seg->x0 + ((SplashCoord)y0 + 1 - seg->y0) * dxdy);
+	  drawSpan(&pipe, x2, (x2 <= x3 - 1) ? x3 - 1 : x2, y0,
+		   clipRes == splashClipAllInside);
+	  x2 = x3;
+	  for (y = y0 + 1; y <= y1 - 1; ++y) {
+	    x3 = splashFloor(seg->x0 + ((SplashCoord)y + 1 - seg->y0) * dxdy);
+	    drawSpan(&pipe, x2, x3 - 1, y, clipRes == splashClipAllInside);
+	    x2 = x3;
+	  }
+	  drawSpan(&pipe, x2, x2 <= x1 ? x1 : x2, y1,
+		   clipRes == splashClipAllInside);
+	} else {
+	  x2 = x0;
+	  x3 = splashFloor(seg->x0 + ((SplashCoord)y0 + 1 - seg->y0) * dxdy);
+	  drawSpan(&pipe, (x3 + 1 <= x2) ? x3 + 1 : x2, x2, y0,
+		   clipRes == splashClipAllInside);
+	  x2 = x3;
+	  for (y = y0 + 1; y <= y1 - 1; ++y) {
+	    x3 = splashFloor(seg->x0 + ((SplashCoord)y + 1 - seg->y0) * dxdy);
+	    drawSpan(&pipe, x3 + 1, x2, y, clipRes == splashClipAllInside);
+	    x2 = x3;
+	  }
+	  drawSpan(&pipe, x1, (x1 <= x2) ? x2 : x1, y1,
+		   clipRes == splashClipAllInside);
+	}
+      }
+
+    // segment with |dy| > |dx|
+    } else {
+      dxdy = seg->dxdy;
+      if (y0 > y1) {
+	t = x0; x0 = x1; x1 = t;
+	t = y0; y0 = y1; y1 = t;
+      }
+      if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0,
+					   x0 <= x1 ? x1 : x0, y1))
+	  != splashClipAllOutside) {
+	drawPixel(&pipe, x0, y0, clipRes == splashClipAllInside);
+	for (y = y0 + 1; y <= y1 - 1; ++y) {
+	  x = splashFloor(seg->x0 + ((SplashCoord)y - seg->y0) * dxdy);
+	  drawPixel(&pipe, x, y, clipRes == splashClipAllInside);
+	}
+	drawPixel(&pipe, x1, y1, clipRes == splashClipAllInside);
+      }
+    }
+    ++nClipRes[clipRes];
+  }
+  if (nClipRes[splashClipPartial] ||
+      (nClipRes[splashClipAllInside] && nClipRes[splashClipAllOutside])) {
+    opClipRes = splashClipPartial;
+  } else if (nClipRes[splashClipAllInside]) {
+    opClipRes = splashClipAllInside;
+  } else {
+    opClipRes = splashClipAllOutside;
+  }
+
+  delete xPath;
+}
+
+void Splash::strokeWide(SplashPath *path) {
+  SplashPath *path2;
+
+  path2 = makeStrokePath(path, gFalse);
+  fillWithPattern(path2, gFalse, state->strokePattern, state->strokeAlpha);
+  delete path2;
+}
+
+SplashPath *Splash::flattenPath(SplashPath *path, SplashCoord *matrix,
+				SplashCoord flatness) {
+  SplashPath *fPath;
+  SplashCoord flatness2;
+  Guchar flag;
+  int i;
+
+  fPath = new SplashPath();
+  flatness2 = flatness * flatness;
+  i = 0;
+  while (i < path->length) {
+    flag = path->flags[i];
+    if (flag & splashPathFirst) {
+      fPath->moveTo(path->pts[i].x, path->pts[i].y);
+      ++i;
+    } else {
+      if (flag & splashPathCurve) {
+	flattenCurve(path->pts[i-1].x, path->pts[i-1].y,
+		     path->pts[i  ].x, path->pts[i  ].y,
+		     path->pts[i+1].x, path->pts[i+1].y,
+		     path->pts[i+2].x, path->pts[i+2].y,
+		     matrix, flatness2, fPath);
+	i += 3;
+      } else {
+	fPath->lineTo(path->pts[i].x, path->pts[i].y);
+	++i;
+      }
+      if (path->flags[i-1] & splashPathClosed) {
+	fPath->close();
+      }
+    }
+  }
+  return fPath;
+}
+
+void Splash::flattenCurve(SplashCoord x0, SplashCoord y0,
+			  SplashCoord x1, SplashCoord y1,
+			  SplashCoord x2, SplashCoord y2,
+			  SplashCoord x3, SplashCoord y3,
+			  SplashCoord *matrix, SplashCoord flatness2,
+			  SplashPath *fPath) {
+  SplashCoord cx[splashMaxCurveSplits + 1][3];
+  SplashCoord cy[splashMaxCurveSplits + 1][3];
+  int cNext[splashMaxCurveSplits + 1];
+  SplashCoord xl0, xl1, xl2, xr0, xr1, xr2, xr3, xx1, xx2, xh;
+  SplashCoord yl0, yl1, yl2, yr0, yr1, yr2, yr3, yy1, yy2, yh;
+  SplashCoord dx, dy, mx, my, tx, ty, d1, d2;
+  int p1, p2, p3;
+
+  // initial segment
+  p1 = 0;
+  p2 = splashMaxCurveSplits;
+  cx[p1][0] = x0;  cy[p1][0] = y0;
+  cx[p1][1] = x1;  cy[p1][1] = y1;
+  cx[p1][2] = x2;  cy[p1][2] = y2;
+  cx[p2][0] = x3;  cy[p2][0] = y3;
+  cNext[p1] = p2;
+
+  while (p1 < splashMaxCurveSplits) {
+
+    // get the next segment
+    xl0 = cx[p1][0];  yl0 = cy[p1][0];
+    xx1 = cx[p1][1];  yy1 = cy[p1][1];
+    xx2 = cx[p1][2];  yy2 = cy[p1][2];
+    p2 = cNext[p1];
+    xr3 = cx[p2][0];  yr3 = cy[p2][0];
+
+    // compute the distances (in device space) from the control points
+    // to the midpoint of the straight line (this is a bit of a hack,
+    // but it's much faster than computing the actual distances to the
+    // line)
+    transform(matrix, (xl0 + xr3) * 0.5, (yl0 + yr3) * 0.5, &mx, &my);
+    transform(matrix, xx1, yy1, &tx, &ty);
+    dx = tx - mx;
+    dy = ty - my;
+    d1 = dx*dx + dy*dy;
+    transform(matrix, xx2, yy2, &tx, &ty);
+    dx = tx - mx;
+    dy = ty - my;
+    d2 = dx*dx + dy*dy;
+
+    // if the curve is flat enough, or no more subdivisions are
+    // allowed, add the straight line segment
+    if (p2 - p1 == 1 || (d1 <= flatness2 && d2 <= flatness2)) {
+      fPath->lineTo(xr3, yr3);
+      p1 = p2;
+
+    // otherwise, subdivide the curve
+    } else {
+      xl1 = (xl0 + xx1) * 0.5;
+      yl1 = (yl0 + yy1) * 0.5;
+      xh = (xx1 + xx2) * 0.5;
+      yh = (yy1 + yy2) * 0.5;
+      xl2 = (xl1 + xh) * 0.5;
+      yl2 = (yl1 + yh) * 0.5;
+      xr2 = (xx2 + xr3) * 0.5;
+      yr2 = (yy2 + yr3) * 0.5;
+      xr1 = (xh + xr2) * 0.5;
+      yr1 = (yh + yr2) * 0.5;
+      xr0 = (xl2 + xr1) * 0.5;
+      yr0 = (yl2 + yr1) * 0.5;
+      // add the new subdivision points
+      p3 = (p1 + p2) / 2;
+      cx[p1][1] = xl1;  cy[p1][1] = yl1;
+      cx[p1][2] = xl2;  cy[p1][2] = yl2;
+      cNext[p1] = p3;
+      cx[p3][0] = xr0;  cy[p3][0] = yr0;
+      cx[p3][1] = xr1;  cy[p3][1] = yr1;
+      cx[p3][2] = xr2;  cy[p3][2] = yr2;
+      cNext[p3] = p2;
+    }
+  }
+}
+
+SplashPath *Splash::makeDashedPath(SplashPath *path) {
+  SplashPath *dPath;
+  SplashCoord lineDashTotal;
+  SplashCoord lineDashStartPhase, lineDashDist, segLen;
+  SplashCoord x0, y0, x1, y1, xa, ya;
+  GBool lineDashStartOn, lineDashOn, newPath;
+  int lineDashStartIdx, lineDashIdx;
+  int i, j, k;
+
+  lineDashTotal = 0;
+  for (i = 0; i < state->lineDashLength; ++i) {
+    lineDashTotal += state->lineDash[i];
+  }
+  lineDashStartPhase = state->lineDashPhase;
+  i = splashFloor(lineDashStartPhase / lineDashTotal);
+  lineDashStartPhase -= (SplashCoord)i * lineDashTotal;
+  lineDashStartOn = gTrue;
+  lineDashStartIdx = 0;
+  while (lineDashStartPhase >= state->lineDash[lineDashStartIdx]) {
+    lineDashStartOn = !lineDashStartOn;
+    lineDashStartPhase -= state->lineDash[lineDashStartIdx];
+    ++lineDashStartIdx;
+  }
+
+  dPath = new SplashPath();
+
+  // process each subpath
+  i = 0;
+  while (i < path->length) {
+
+    // find the end of the subpath
+    for (j = i;
+	 j < path->length - 1 && !(path->flags[j] & splashPathLast);
+	 ++j) ;
+
+    // initialize the dash parameters
+    lineDashOn = lineDashStartOn;
+    lineDashIdx = lineDashStartIdx;
+    lineDashDist = state->lineDash[lineDashIdx] - lineDashStartPhase;
+
+    // process each segment of the subpath
+    newPath = gTrue;
+    for (k = i; k < j; ++k) {
+
+      // grab the segment
+      x0 = path->pts[k].x;
+      y0 = path->pts[k].y;
+      x1 = path->pts[k+1].x;
+      y1 = path->pts[k+1].y;
+      segLen = splashDist(x0, y0, x1, y1);
+
+      // process the segment
+      while (segLen > 0) {
+
+	if (lineDashDist >= segLen) {
+	  if (lineDashOn) {
+	    if (newPath) {
+	      dPath->moveTo(x0, y0);
+	      newPath = gFalse;
+	    }
+	    dPath->lineTo(x1, y1);
+	  }
+	  lineDashDist -= segLen;
+	  segLen = 0;
+
+	} else {
+	  xa = x0 + (lineDashDist / segLen) * (x1 - x0);
+	  ya = y0 + (lineDashDist / segLen) * (y1 - y0);
+	  if (lineDashOn) {
+	    if (newPath) {
+	      dPath->moveTo(x0, y0);
+	      newPath = gFalse;
+	    }
+	    dPath->lineTo(xa, ya);
+	  }
+	  x0 = xa;
+	  y0 = ya;
+	  segLen -= lineDashDist;
+	  lineDashDist = 0;
+	}
+
+	// get the next entry in the dash array
+	if (lineDashDist <= 0) {
+	  lineDashOn = !lineDashOn;
+	  if (++lineDashIdx == state->lineDashLength) {
+	    lineDashIdx = 0;
+	  }
+	  lineDashDist = state->lineDash[lineDashIdx];
+	  newPath = gTrue;
+	}
+      }
+    }
+    i = j + 1;
+  }
+
+  return dPath;
+}
+
+SplashError Splash::fill(SplashPath *path, GBool eo) {
+  if (debugMode) {
+    printf("fill [eo:%d]:\n", eo);
+    dumpPath(path);
+  }
+  return fillWithPattern(path, eo, state->fillPattern, state->fillAlpha);
+}
+
+SplashError Splash::fillWithPattern(SplashPath *path, GBool eo,
+				    SplashPattern *pattern,
+				    SplashCoord alpha) {
+  SplashPipe pipe;
+  SplashXPath *xPath;
+  SplashXPathScanner *scanner;
+  int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y;
+  SplashClipResult clipRes, clipRes2;
+
+  if (path->length == 0) {
+    return splashErrEmptyPath;
+  }
+  xPath = new SplashXPath(path, state->matrix, state->flatness, gTrue);
+  if (vectorAntialias) {
+    xPath->aaScale();
+  }
+  xPath->sort();
+  scanner = new SplashXPathScanner(xPath, eo);
+
+  // get the min and max x and y values
+  if (vectorAntialias) {
+    scanner->getBBoxAA(&xMinI, &yMinI, &xMaxI, &yMaxI);
+  } else {
+    scanner->getBBox(&xMinI, &yMinI, &xMaxI, &yMaxI);
+  }
+
+  // check clipping
+  if ((clipRes = state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI))
+      != splashClipAllOutside) {
+
+    // limit the y range
+    if (yMinI < state->clip->getYMinI()) {
+      yMinI = state->clip->getYMinI();
+    }
+    if (yMaxI > state->clip->getYMaxI()) {
+      yMaxI = state->clip->getYMaxI();
+    }
+
+    pipeInit(&pipe, 0, yMinI, pattern, NULL, alpha, vectorAntialias, gFalse);
+
+    // draw the spans
+    if (vectorAntialias) {
+      for (y = yMinI; y <= yMaxI; ++y) {
+	scanner->renderAALine(aaBuf, &x0, &x1, y);
+	if (clipRes != splashClipAllInside) {
+	  state->clip->clipAALine(aaBuf, &x0, &x1, y);
+	}
+	drawAALine(&pipe, x0, x1, y);
+      }
+    } else {
+      for (y = yMinI; y <= yMaxI; ++y) {
+	while (scanner->getNextSpan(y, &x0, &x1)) {
+	  if (clipRes == splashClipAllInside) {
+	    drawSpan(&pipe, x0, x1, y, gTrue);
+	  } else {
+	    // limit the x range
+	    if (x0 < state->clip->getXMinI()) {
+	      x0 = state->clip->getXMinI();
+	    }
+	    if (x1 > state->clip->getXMaxI()) {
+	      x1 = state->clip->getXMaxI();
+	    }
+	    clipRes2 = state->clip->testSpan(x0, x1, y);
+	    drawSpan(&pipe, x0, x1, y, clipRes2 == splashClipAllInside);
+	  }
+	}
+      }
+    }
+  }
+  opClipRes = clipRes;
+
+  delete scanner;
+  delete xPath;
+  return splashOk;
+}
+
+SplashError Splash::xorFill(SplashPath *path, GBool eo) {
+  SplashPipe pipe;
+  SplashXPath *xPath;
+  SplashXPathScanner *scanner;
+  int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y;
+  SplashClipResult clipRes, clipRes2;
+  SplashBlendFunc origBlendFunc;
+
+  if (path->length == 0) {
+    return splashErrEmptyPath;
+  }
+  xPath = new SplashXPath(path, state->matrix, state->flatness, gTrue);
+  xPath->sort();
+  scanner = new SplashXPathScanner(xPath, eo);
+
+  // get the min and max x and y values
+  scanner->getBBox(&xMinI, &yMinI, &xMaxI, &yMaxI);
+
+  // check clipping
+  if ((clipRes = state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI))
+      != splashClipAllOutside) {
+
+    // limit the y range
+    if (yMinI < state->clip->getYMinI()) {
+      yMinI = state->clip->getYMinI();
+    }
+    if (yMaxI > state->clip->getYMaxI()) {
+      yMaxI = state->clip->getYMaxI();
+    }
+
+    origBlendFunc = state->blendFunc;
+    state->blendFunc = &blendXor;
+    pipeInit(&pipe, 0, yMinI, state->fillPattern, NULL, 1, gFalse, gFalse);
+
+    // draw the spans
+    for (y = yMinI; y <= yMaxI; ++y) {
+      while (scanner->getNextSpan(y, &x0, &x1)) {
+	if (clipRes == splashClipAllInside) {
+	  drawSpan(&pipe, x0, x1, y, gTrue);
+	} else {
+	  // limit the x range
+	  if (x0 < state->clip->getXMinI()) {
+	    x0 = state->clip->getXMinI();
+	  }
+	  if (x1 > state->clip->getXMaxI()) {
+	    x1 = state->clip->getXMaxI();
+	  }
+	  clipRes2 = state->clip->testSpan(x0, x1, y);
+	  drawSpan(&pipe, x0, x1, y, clipRes2 == splashClipAllInside);
+	}
+      }
+    }
+    state->blendFunc = origBlendFunc;
+  }
+  opClipRes = clipRes;
+
+  delete scanner;
+  delete xPath;
+  return splashOk;
+}
+
+SplashError Splash::fillChar(SplashCoord x, SplashCoord y,
+			     int c, SplashFont *font) {
+  SplashGlyphBitmap glyph;
+  SplashCoord xt, yt;
+  int x0, y0, xFrac, yFrac;
+  SplashError err;
+
+  if (debugMode) {
+    printf("fillChar: x=%.2f y=%.2f c=%3d=0x%02x='%c'\n",
+	   (double)x, (double)y, c, c, c);
+  }
+  transform(state->matrix, x, y, &xt, &yt);
+  x0 = splashFloor(xt);
+  xFrac = splashFloor((xt - x0) * splashFontFraction);
+  y0 = splashFloor(yt);
+  yFrac = splashFloor((yt - y0) * splashFontFraction);
+  if (!font->getGlyph(c, xFrac, yFrac, &glyph)) {
+    return splashErrNoGlyph;
+  }
+  err = fillGlyph2(x0, y0, &glyph);
+  if (glyph.freeData) {
+    gfree(glyph.data);
+  }
+  return err;
+}
+
+SplashError Splash::fillGlyph(SplashCoord x, SplashCoord y,
+			      SplashGlyphBitmap *glyph) {
+  SplashCoord xt, yt;
+  int x0, y0;
+
+  transform(state->matrix, x, y, &xt, &yt);
+  x0 = splashFloor(xt);
+  y0 = splashFloor(yt);
+  return fillGlyph2(x0, y0, glyph);
+}
+
+SplashError Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph) {
+  SplashPipe pipe;
+  SplashClipResult clipRes;
+  GBool noClip;
+  int alpha0, alpha;
+  Guchar *p;
+  int x1, y1, xx, xx1, yy;
+
+  if ((clipRes = state->clip->testRect(x0 - glyph->x,
+				       y0 - glyph->y,
+				       x0 - glyph->x + glyph->w - 1,
+				       y0 - glyph->y + glyph->h - 1))
+      != splashClipAllOutside) {
+    noClip = clipRes == splashClipAllInside;
+
+    if (noClip) {
+      if (glyph->aa) {
+	pipeInit(&pipe, x0 - glyph->x, y0 - glyph->y,
+		 state->fillPattern, NULL, state->fillAlpha, gTrue, gFalse);
+	p = glyph->data;
+	for (yy = 0, y1 = y0 - glyph->y; yy < glyph->h; ++yy, ++y1) {
+	  pipeSetXY(&pipe, x0 - glyph->x, y1);
+	  for (xx = 0, x1 = x0 - glyph->x; xx < glyph->w; ++xx, ++x1) {
+	    alpha = *p++;
+	    if (alpha != 0) {
+	      pipe.shape = (SplashCoord)(alpha / 255.0);
+	      pipeRun(&pipe);
+	      updateModX(x1);
+	      updateModY(y1);
+	    } else {
+	      pipeIncX(&pipe);
+	    }
+	  }
+	}
+      } else {
+	pipeInit(&pipe, x0 - glyph->x, y0 - glyph->y,
+		 state->fillPattern, NULL, state->fillAlpha, gFalse, gFalse);
+	p = glyph->data;
+	for (yy = 0, y1 = y0 - glyph->y; yy < glyph->h; ++yy, ++y1) {
+	  pipeSetXY(&pipe, x0 - glyph->x, y1);
+	  for (xx = 0, x1 = x0 - glyph->x; xx < glyph->w; xx += 8) {
+	    alpha0 = *p++;
+	    for (xx1 = 0; xx1 < 8 && xx + xx1 < glyph->w; ++xx1, ++x1) {
+	      if (alpha0 & 0x80) {
+		pipeRun(&pipe);
+		updateModX(x1);
+		updateModY(y1);
+	      } else {
+		pipeIncX(&pipe);
+	      }
+	      alpha0 <<= 1;
+	    }
+	  }
+	}
+      }
+    } else {
+      if (glyph->aa) {
+	pipeInit(&pipe, x0 - glyph->x, y0 - glyph->y,
+		 state->fillPattern, NULL, state->fillAlpha, gTrue, gFalse);
+	p = glyph->data;
+	for (yy = 0, y1 = y0 - glyph->y; yy < glyph->h; ++yy, ++y1) {
+	  pipeSetXY(&pipe, x0 - glyph->x, y1);
+	  for (xx = 0, x1 = x0 - glyph->x; xx < glyph->w; ++xx, ++x1) {
+	    if (state->clip->test(x1, y1)) {
+	      alpha = *p++;
+	      if (alpha != 0) {
+		pipe.shape = (SplashCoord)(alpha / 255.0);
+		pipeRun(&pipe);
+		updateModX(x1);
+		updateModY(y1);
+	      } else {
+		pipeIncX(&pipe);
+	      }
+	    } else {
+	      pipeIncX(&pipe);
+	      ++p;
+	    }
+	  }
+	}
+      } else {
+	pipeInit(&pipe, x0 - glyph->x, y0 - glyph->y,
+		 state->fillPattern, NULL, state->fillAlpha, gFalse, gFalse);
+	p = glyph->data;
+	for (yy = 0, y1 = y0 - glyph->y; yy < glyph->h; ++yy, ++y1) {
+	  pipeSetXY(&pipe, x0 - glyph->x, y1);
+	  for (xx = 0, x1 = x0 - glyph->x; xx < glyph->w; xx += 8) {
+	    alpha0 = *p++;
+	    for (xx1 = 0; xx1 < 8 && xx + xx1 < glyph->w; ++xx1, ++x1) {
+	      if (state->clip->test(x1, y1)) {
+		if (alpha0 & 0x80) {
+		  pipeRun(&pipe);
+		  updateModX(x1);
+		  updateModY(y1);
+		} else {
+		  pipeIncX(&pipe);
+		}
+	      } else {
+		pipeIncX(&pipe);
+	      }
+	      alpha0 <<= 1;
+	    }
+	  }
+	}
+      }
+    }
+  }
+  opClipRes = clipRes;
+
+  return splashOk;
+}
+
+SplashError Splash::fillImageMask(SplashImageMaskSource src, void *srcData,
+				  int w, int h, SplashCoord *mat,
+				  GBool glyphMode) {
+  SplashPipe pipe;
+  GBool rot;
+  SplashCoord xScale, yScale, xShear, yShear, yShear1;
+  int tx, tx2, ty, ty2, scaledWidth, scaledHeight, xSign, ySign;
+  int ulx, uly, llx, lly, urx, ury, lrx, lry;
+  int ulx1, uly1, llx1, lly1, urx1, ury1, lrx1, lry1;
+  int xMin, xMax, yMin, yMax;
+  SplashClipResult clipRes, clipRes2;
+  int yp, yq, yt, yStep, lastYStep;
+  int xp, xq, xt, xStep, xSrc;
+  int k1, spanXMin, spanXMax, spanY;
+  SplashColorPtr pixBuf, p;
+  int pixAcc;
+  int x, y, x1, x2, y2;
+  SplashCoord y1;
+  int n, m, i, j;
+
+  if (debugMode) {
+    printf("fillImageMask: w=%d h=%d mat=[%.2f %.2f %.2f %.2f %.2f %.2f]\n",
+	   w, h, (double)mat[0], (double)mat[1], (double)mat[2],
+	   (double)mat[3], (double)mat[4], (double)mat[5]);
+  }
+
+  // check for singular matrix
+  if (splashAbs(mat[0] * mat[3] - mat[1] * mat[2]) < 0.000001) {
+    return splashErrSingularMatrix;
+  }
+
+  // compute scale, shear, rotation, translation parameters
+  rot = splashAbs(mat[1]) > splashAbs(mat[0]);
+  if (rot) {
+    xScale = -mat[1];
+    yScale = mat[2] - (mat[0] * mat[3]) / mat[1];
+    xShear = -mat[3] / yScale;
+    yShear = -mat[0] / mat[1];
+  } else {
+    xScale = mat[0];
+    yScale = mat[3] - (mat[1] * mat[2]) / mat[0];
+    xShear = mat[2] / yScale;
+    yShear = mat[1] / mat[0];
+  }
+  // Note 1: The PDF spec says that all pixels whose *centers* lie
+  // within the region get painted -- but that doesn't seem to match
+  // up with what Acrobat actually does: it ends up leaving gaps
+  // between image stripes.  So we use the same rule here as for
+  // fills: any pixel that overlaps the region gets painted.
+  // Note 2: The "glyphMode" flag is a kludge: it switches back to
+  // "correct" behavior (matching the spec), for use in rendering Type
+  // 3 fonts.
+  // Note 3: The +/-0.01 in these computations is to avoid floating
+  // point precision problems which can lead to gaps between image
+  // stripes (it can cause image stripes to overlap, but that's a much
+  // less visible problem).
+  if (glyphMode) {
+    if (xScale >= 0) {
+      tx = splashRound(mat[4]);
+      tx2 = splashRound(mat[4] + xScale) - 1;
+    } else {
+      tx = splashRound(mat[4]) - 1;
+      tx2 = splashRound(mat[4] + xScale);
+    }
+  } else {
+    if (xScale >= 0) {
+      tx = splashFloor(mat[4] - 0.01);
+      tx2 = splashFloor(mat[4] + xScale + 0.01);
+    } else {
+      tx = splashFloor(mat[4] + 0.01);
+      tx2 = splashFloor(mat[4] + xScale - 0.01);
+    }
+  }
+  scaledWidth = abs(tx2 - tx) + 1;
+  if (glyphMode) {
+    if (yScale >= 0) {
+      ty = splashRound(mat[5]);
+      ty2 = splashRound(mat[5] + yScale) - 1;
+    } else {
+      ty = splashRound(mat[5]) - 1;
+      ty2 = splashRound(mat[5] + yScale);
+    }
+  } else {
+    if (yScale >= 0) {
+      ty = splashFloor(mat[5] - 0.01);
+      ty2 = splashFloor(mat[5] + yScale + 0.01);
+    } else {
+      ty = splashFloor(mat[5] + 0.01);
+      ty2 = splashFloor(mat[5] + yScale - 0.01);
+    }
+  }
+  scaledHeight = abs(ty2 - ty) + 1;
+  xSign = (xScale < 0) ? -1 : 1;
+  ySign = (yScale < 0) ? -1 : 1;
+  yShear1 = (SplashCoord)xSign * yShear;
+
+  // clipping
+  ulx1 = 0;
+  uly1 = 0;
+  urx1 = xSign * (scaledWidth - 1);
+  ury1 = (int)(yShear * urx1);
+  llx1 = splashRound(xShear * ySign * (scaledHeight - 1));
+  lly1 = ySign * (scaledHeight - 1) + (int)(yShear * llx1);
+  lrx1 = xSign * (scaledWidth - 1) +
+           splashRound(xShear * ySign * (scaledHeight - 1));
+  lry1 = ySign * (scaledHeight - 1) + (int)(yShear * lrx1);
+  if (rot) {
+    ulx = tx + uly1;    uly = ty - ulx1;
+    urx = tx + ury1;    ury = ty - urx1;
+    llx = tx + lly1;    lly = ty - llx1;
+    lrx = tx + lry1;    lry = ty - lrx1;
+  } else {
+    ulx = tx + ulx1;    uly = ty + uly1;
+    urx = tx + urx1;    ury = ty + ury1;
+    llx = tx + llx1;    lly = ty + lly1;
+    lrx = tx + lrx1;    lry = ty + lry1;
+  }
+  xMin = (ulx < urx) ? (ulx < llx) ? (ulx < lrx) ? ulx : lrx
+                                   : (llx < lrx) ? llx : lrx
+		     : (urx < llx) ? (urx < lrx) ? urx : lrx
+                                   : (llx < lrx) ? llx : lrx;
+  xMax = (ulx > urx) ? (ulx > llx) ? (ulx > lrx) ? ulx : lrx
+                                   : (llx > lrx) ? llx : lrx
+		     : (urx > llx) ? (urx > lrx) ? urx : lrx
+                                   : (llx > lrx) ? llx : lrx;
+  yMin = (uly < ury) ? (uly < lly) ? (uly < lry) ? uly : lry
+                                   : (lly < lry) ? lly : lry
+		     : (ury < lly) ? (ury < lry) ? ury : lry
+                                   : (lly < lry) ? lly : lry;
+  yMax = (uly > ury) ? (uly > lly) ? (uly > lry) ? uly : lry
+                                   : (lly > lry) ? lly : lry
+		     : (ury > lly) ? (ury > lry) ? ury : lry
+                                   : (lly > lry) ? lly : lry;
+  clipRes = state->clip->testRect(xMin, yMin, xMax, yMax);
+  opClipRes = clipRes;
+
+  // compute Bresenham parameters for x and y scaling
+  yp = h / scaledHeight;
+  yq = h % scaledHeight;
+  xp = w / scaledWidth;
+  xq = w % scaledWidth;
+
+  // allocate pixel buffer
+  pixBuf = (SplashColorPtr)gmalloc((yp + 1) * w);
+
+  // initialize the pixel pipe
+  pipeInit(&pipe, 0, 0, state->fillPattern, NULL, state->fillAlpha,
+	   gTrue, gFalse);
+  if (vectorAntialias) {
+    drawAAPixelInit();
+  }
+
+  // init y scale Bresenham
+  yt = 0;
+  lastYStep = 1;
+
+  for (y = 0; y < scaledHeight; ++y) {
+
+    // y scale Bresenham
+    yStep = yp;
+    yt += yq;
+    if (yt >= scaledHeight) {
+      yt -= scaledHeight;
+      ++yStep;
+    }
+
+    // read row(s) from image
+    n = (yp > 0) ? yStep : lastYStep;
+    if (n > 0) {
+      p = pixBuf;
+      for (i = 0; i < n; ++i) {
+	(*src)(srcData, p);
+	p += w;
+      }
+    }
+    lastYStep = yStep;
+
+    // loop-invariant constants
+    k1 = splashRound(xShear * ySign * y);
+
+    // clipping test
+    if (clipRes != splashClipAllInside &&
+	!rot &&
+	(int)(yShear * k1) ==
+	  (int)(yShear * (xSign * (scaledWidth - 1) + k1))) {
+      if (xSign > 0) {
+	spanXMin = tx + k1;
+	spanXMax = spanXMin + (scaledWidth - 1);
+      } else {
+	spanXMax = tx + k1;
+	spanXMin = spanXMax - (scaledWidth - 1);
+      }
+      spanY = ty + ySign * y + (int)(yShear * k1);
+      clipRes2 = state->clip->testSpan(spanXMin, spanXMax, spanY);
+      if (clipRes2 == splashClipAllOutside) {
+	continue;
+      }
+    } else {
+      clipRes2 = clipRes;
+    }
+
+    // init x scale Bresenham
+    xt = 0;
+    xSrc = 0;
+
+    // x shear
+    x1 = k1;
+
+    // y shear
+    y1 = (SplashCoord)ySign * y + yShear * x1;
+    // this is a kludge: if yShear1 is negative, then (int)y1 would
+    // change immediately after the first pixel, which is not what we
+    // want
+    if (yShear1 < 0) {
+      y1 += 0.999;
+    }
+
+    // loop-invariant constants
+    n = yStep > 0 ? yStep : 1;
+
+    for (x = 0; x < scaledWidth; ++x) {
+
+      // x scale Bresenham
+      xStep = xp;
+      xt += xq;
+      if (xt >= scaledWidth) {
+	xt -= scaledWidth;
+	++xStep;
+      }
+
+      // rotation
+      if (rot) {
+	x2 = (int)y1;
+	y2 = -x1;
+      } else {
+	x2 = x1;
+	y2 = (int)y1;
+      }
+
+      // compute the alpha value for (x,y) after the x and y scaling
+      // operations
+      m = xStep > 0 ? xStep : 1;
+      p = pixBuf + xSrc;
+      pixAcc = 0;
+      for (i = 0; i < n; ++i) {
+	for (j = 0; j < m; ++j) {
+	  pixAcc += *p++;
+	}
+	p += w - m;
+      }
+
+      // blend fill color with background
+      if (pixAcc != 0) {
+	pipe.shape = (pixAcc == n * m)
+	                 ? (SplashCoord)1
+	                 : (SplashCoord)pixAcc / (SplashCoord)(n * m);
+	if (vectorAntialias && clipRes2 != splashClipAllInside) {
+	  drawAAPixel(&pipe, tx + x2, ty + y2);
+	} else {
+	  drawPixel(&pipe, tx + x2, ty + y2, clipRes2 == splashClipAllInside);
+	}
+      }
+
+      // x scale Bresenham
+      xSrc += xStep;
+
+      // x shear
+      x1 += xSign;
+
+      // y shear
+      y1 += yShear1;
+    }
+  }
+
+  // free memory
+  gfree(pixBuf);
+
+  return splashOk;
+}
+
+SplashError Splash::drawImage(SplashImageSource src, void *srcData,
+			      SplashColorMode srcMode, GBool srcAlpha,
+			      int w, int h, SplashCoord *mat) {
+  SplashPipe pipe;
+  GBool ok, rot;
+  SplashCoord xScale, yScale, xShear, yShear, yShear1;
+  int tx, tx2, ty, ty2, scaledWidth, scaledHeight, xSign, ySign;
+  int ulx, uly, llx, lly, urx, ury, lrx, lry;
+  int ulx1, uly1, llx1, lly1, urx1, ury1, lrx1, lry1;
+  int xMin, xMax, yMin, yMax;
+  SplashClipResult clipRes, clipRes2;
+  int yp, yq, yt, yStep, lastYStep;
+  int xp, xq, xt, xStep, xSrc;
+  int k1, spanXMin, spanXMax, spanY;
+  SplashColorPtr colorBuf, p;
+  SplashColor pix;
+  Guchar *alphaBuf, *q;
+#if SPLASH_CMYK
+  int pixAcc0, pixAcc1, pixAcc2, pixAcc3;
+#else
+  int pixAcc0, pixAcc1, pixAcc2;
+#endif
+  int alphaAcc;
+  SplashCoord pixMul, alphaMul, alpha;
+  int x, y, x1, x2, y2;
+  SplashCoord y1;
+  int nComps, n, m, i, j;
+
+  if (debugMode) {
+    printf("drawImage: srcMode=%d srcAlpha=%d w=%d h=%d mat=[%.2f %.2f %.2f %.2f %.2f %.2f]\n",
+	   srcMode, srcAlpha, w, h, (double)mat[0], (double)mat[1], (double)mat[2],
+	   (double)mat[3], (double)mat[4], (double)mat[5]);
+  }
+
+  // check color modes
+  ok = gFalse; // make gcc happy
+  nComps = 0; // make gcc happy
+  switch (bitmap->mode) {
+  case splashModeMono1:
+  case splashModeMono8:
+    ok = srcMode == splashModeMono8;
+    nComps = 1;
+    break;
+  case splashModeRGB8:
+    ok = srcMode == splashModeRGB8;
+    nComps = 3;
+    break;
+  case splashModeBGR8:
+    ok = srcMode == splashModeBGR8;
+    nComps = 3;
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    ok = srcMode == splashModeCMYK8;
+    nComps = 4;
+    break;
+#endif
+  }
+  if (!ok) {
+    return splashErrModeMismatch;
+  }
+
+  // check for singular matrix
+  if (splashAbs(mat[0] * mat[3] - mat[1] * mat[2]) < 0.000001) {
+    return splashErrSingularMatrix;
+  }
+
+  // compute scale, shear, rotation, translation parameters
+  rot = splashAbs(mat[1]) > splashAbs(mat[0]);
+  if (rot) {
+    xScale = -mat[1];
+    yScale = mat[2] - (mat[0] * mat[3]) / mat[1];
+    xShear = -mat[3] / yScale;
+    yShear = -mat[0] / mat[1];
+  } else {
+    xScale = mat[0];
+    yScale = mat[3] - (mat[1] * mat[2]) / mat[0];
+    xShear = mat[2] / yScale;
+    yShear = mat[1] / mat[0];
+  }
+  // Note 1: The PDF spec says that all pixels whose *centers* lie
+  // within the region get painted -- but that doesn't seem to match
+  // up with what Acrobat actually does: it ends up leaving gaps
+  // between image stripes.  So we use the same rule here as for
+  // fills: any pixel that overlaps the region gets painted.
+  // Note 2: The +/-0.01 in these computations is to avoid floating
+  // point precision problems which can lead to gaps between image
+  // stripes (it can cause image stripes to overlap, but that's a much
+  // less visible problem).
+  if (xScale >= 0) {
+    tx = splashFloor(mat[4] - 0.01);
+    tx2 = splashFloor(mat[4] + xScale + 0.01);
+  } else {
+    tx = splashFloor(mat[4] + 0.01);
+    tx2 = splashFloor(mat[4] + xScale - 0.01);
+  }
+  scaledWidth = abs(tx2 - tx) + 1;
+  if (yScale >= 0) {
+    ty = splashFloor(mat[5] - 0.01);
+    ty2 = splashFloor(mat[5] + yScale + 0.01);
+  } else {
+    ty = splashFloor(mat[5] + 0.01);
+    ty2 = splashFloor(mat[5] + yScale - 0.01);
+  }
+  scaledHeight = abs(ty2 - ty) + 1;
+  xSign = (xScale < 0) ? -1 : 1;
+  ySign = (yScale < 0) ? -1 : 1;
+  yShear1 = (SplashCoord)xSign * yShear;
+
+  // clipping
+  ulx1 = 0;
+  uly1 = 0;
+  urx1 = xSign * (scaledWidth - 1);
+  ury1 = (int)(yShear * urx1);
+  llx1 = splashRound(xShear * ySign * (scaledHeight - 1));
+  lly1 = ySign * (scaledHeight - 1) + (int)(yShear * llx1);
+  lrx1 = xSign * (scaledWidth - 1) +
+           splashRound(xShear * ySign * (scaledHeight - 1));
+  lry1 = ySign * (scaledHeight - 1) + (int)(yShear * lrx1);
+  if (rot) {
+    ulx = tx + uly1;    uly = ty - ulx1;
+    urx = tx + ury1;    ury = ty - urx1;
+    llx = tx + lly1;    lly = ty - llx1;
+    lrx = tx + lry1;    lry = ty - lrx1;
+  } else {
+    ulx = tx + ulx1;    uly = ty + uly1;
+    urx = tx + urx1;    ury = ty + ury1;
+    llx = tx + llx1;    lly = ty + lly1;
+    lrx = tx + lrx1;    lry = ty + lry1;
+  }
+  xMin = (ulx < urx) ? (ulx < llx) ? (ulx < lrx) ? ulx : lrx
+                                   : (llx < lrx) ? llx : lrx
+		     : (urx < llx) ? (urx < lrx) ? urx : lrx
+                                   : (llx < lrx) ? llx : lrx;
+  xMax = (ulx > urx) ? (ulx > llx) ? (ulx > lrx) ? ulx : lrx
+                                   : (llx > lrx) ? llx : lrx
+		     : (urx > llx) ? (urx > lrx) ? urx : lrx
+                                   : (llx > lrx) ? llx : lrx;
+  yMin = (uly < ury) ? (uly < lly) ? (uly < lry) ? uly : lry
+                                   : (lly < lry) ? lly : lry
+		     : (ury < lly) ? (ury < lry) ? ury : lry
+                                   : (lly < lry) ? lly : lry;
+  yMax = (uly > ury) ? (uly > lly) ? (uly > lry) ? uly : lry
+                                   : (lly > lry) ? lly : lry
+		     : (ury > lly) ? (ury > lry) ? ury : lry
+                                   : (lly > lry) ? lly : lry;
+  clipRes = state->clip->testRect(xMin, yMin, xMax, yMax);
+  opClipRes = clipRes;
+  if (clipRes == splashClipAllOutside) {
+    return splashOk;
+  }
+
+  // compute Bresenham parameters for x and y scaling
+  yp = h / scaledHeight;
+  yq = h % scaledHeight;
+  xp = w / scaledWidth;
+  xq = w % scaledWidth;
+
+  // allocate pixel buffers
+  colorBuf = (SplashColorPtr)gmalloc((yp + 1) * w * nComps);
+  if (srcAlpha) {
+    alphaBuf = (Guchar *)gmalloc((yp + 1) * w);
+  } else {
+    alphaBuf = NULL;
+  }
+
+  pixAcc0 = pixAcc1 = pixAcc2 = 0; // make gcc happy
+#if SPLASH_CMYK
+  pixAcc3 = 0; // make gcc happy
+#endif
+
+  // initialize the pixel pipe
+  pipeInit(&pipe, 0, 0, NULL, pix, state->fillAlpha,
+	   srcAlpha || (vectorAntialias && clipRes != splashClipAllInside),
+	   gFalse);
+  if (vectorAntialias) {
+    drawAAPixelInit();
+  }
+
+  if (srcAlpha) {
+
+    // init y scale Bresenham
+    yt = 0;
+    lastYStep = 1;
+
+    for (y = 0; y < scaledHeight; ++y) {
+
+      // y scale Bresenham
+      yStep = yp;
+      yt += yq;
+      if (yt >= scaledHeight) {
+	yt -= scaledHeight;
+	++yStep;
+      }
+
+      // read row(s) from image
+      n = (yp > 0) ? yStep : lastYStep;
+      if (n > 0) {
+	p = colorBuf;
+	q = alphaBuf;
+	for (i = 0; i < n; ++i) {
+	  (*src)(srcData, p, q);
+	  p += w * nComps;
+	  q += w;
+	}
+      }
+      lastYStep = yStep;
+
+      // loop-invariant constants
+      k1 = splashRound(xShear * ySign * y);
+
+      // clipping test
+      if (clipRes != splashClipAllInside &&
+	  !rot &&
+	  (int)(yShear * k1) ==
+	    (int)(yShear * (xSign * (scaledWidth - 1) + k1))) {
+	if (xSign > 0) {
+	  spanXMin = tx + k1;
+	  spanXMax = spanXMin + (scaledWidth - 1);
+	} else {
+	  spanXMax = tx + k1;
+	  spanXMin = spanXMax - (scaledWidth - 1);
+	}
+	spanY = ty + ySign * y + (int)(yShear * k1);
+	clipRes2 = state->clip->testSpan(spanXMin, spanXMax, spanY);
+	if (clipRes2 == splashClipAllOutside) {
+	  continue;
+	}
+      } else {
+	clipRes2 = clipRes;
+      }
+
+      // init x scale Bresenham
+      xt = 0;
+      xSrc = 0;
+
+      // x shear
+      x1 = k1;
+
+      // y shear
+      y1 = (SplashCoord)ySign * y + yShear * x1;
+      // this is a kludge: if yShear1 is negative, then (int)y1 would
+      // change immediately after the first pixel, which is not what
+      // we want
+      if (yShear1 < 0) {
+	y1 += 0.999;
+      }
+
+      // loop-invariant constants
+      n = yStep > 0 ? yStep : 1;
+
+      switch (srcMode) {
+
+      case splashModeMono1:
+      case splashModeMono8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  alphaAcc = 0;
+	  p = colorBuf + xSrc;
+	  q = alphaBuf + xSrc;
+	  pixAcc0 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	      alphaAcc += *q++;
+	    }
+	    p += w - m;
+	    q += w - m;
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+	  alphaMul = pixMul * (1.0 / 255.0);
+	  alpha = (SplashCoord)alphaAcc * alphaMul;
+
+	  if (alpha > 0) {
+	    pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+
+	    // set pixel
+	    pipe.shape = alpha;
+	    if (vectorAntialias && clipRes != splashClipAllInside) {
+	      drawAAPixel(&pipe, tx + x2, ty + y2);
+	    } else {
+	      drawPixel(&pipe, tx + x2, ty + y2,
+			clipRes2 == splashClipAllInside);
+	    }
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+
+      case splashModeRGB8:
+      case splashModeBGR8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  alphaAcc = 0;
+	  p = colorBuf + xSrc * 3;
+	  q = alphaBuf + xSrc;
+	  pixAcc0 = pixAcc1 = pixAcc2 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	      pixAcc1 += *p++;
+	      pixAcc2 += *p++;
+	      alphaAcc += *q++;
+	    }
+	    p += 3 * (w - m);
+	    q += w - m;
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+	  alphaMul = pixMul * (1.0 / 255.0);
+	  alpha = (SplashCoord)alphaAcc * alphaMul;
+
+	  if (alpha > 0) {
+	    pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+	    pix[1] = (int)((SplashCoord)pixAcc1 * pixMul);
+	    pix[2] = (int)((SplashCoord)pixAcc2 * pixMul);
+
+	    // set pixel
+	    pipe.shape = alpha;
+	    if (vectorAntialias && clipRes != splashClipAllInside) {
+	      drawAAPixel(&pipe, tx + x2, ty + y2);
+	    } else {
+	      drawPixel(&pipe, tx + x2, ty + y2,
+			clipRes2 == splashClipAllInside);
+	    }
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+
+#if SPLASH_CMYK
+      case splashModeCMYK8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  alphaAcc = 0;
+	  p = colorBuf + xSrc * 4;
+	  q = alphaBuf + xSrc;
+	  pixAcc0 = pixAcc1 = pixAcc2 = pixAcc3 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	      pixAcc1 += *p++;
+	      pixAcc2 += *p++;
+	      pixAcc3 += *p++;
+	      alphaAcc += *q++;
+	    }
+	    p += 4 * (w - m);
+	    q += w - m;
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+	  alphaMul = pixMul * (1.0 / 255.0);
+	  alpha = (SplashCoord)alphaAcc * alphaMul;
+
+	  if (alpha > 0) {
+	    pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+	    pix[1] = (int)((SplashCoord)pixAcc1 * pixMul);
+	    pix[2] = (int)((SplashCoord)pixAcc2 * pixMul);
+	    pix[3] = (int)((SplashCoord)pixAcc3 * pixMul);
+
+	    // set pixel
+	    pipe.shape = alpha;
+	    if (vectorAntialias && clipRes != splashClipAllInside) {
+	      drawAAPixel(&pipe, tx + x2, ty + y2);
+	    } else {
+	      drawPixel(&pipe, tx + x2, ty + y2,
+			clipRes2 == splashClipAllInside);
+	    }
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+#endif // SPLASH_CMYK
+      }
+    }
+
+  } else {
+
+    // init y scale Bresenham
+    yt = 0;
+    lastYStep = 1;
+
+    for (y = 0; y < scaledHeight; ++y) {
+
+      // y scale Bresenham
+      yStep = yp;
+      yt += yq;
+      if (yt >= scaledHeight) {
+	yt -= scaledHeight;
+	++yStep;
+      }
+
+      // read row(s) from image
+      n = (yp > 0) ? yStep : lastYStep;
+      if (n > 0) {
+	p = colorBuf;
+	for (i = 0; i < n; ++i) {
+	  (*src)(srcData, p, NULL);
+	  p += w * nComps;
+	}
+      }
+      lastYStep = yStep;
+
+      // loop-invariant constants
+      k1 = splashRound(xShear * ySign * y);
+
+      // clipping test
+      if (clipRes != splashClipAllInside &&
+	  !rot &&
+	  (int)(yShear * k1) ==
+	    (int)(yShear * (xSign * (scaledWidth - 1) + k1))) {
+	if (xSign > 0) {
+	  spanXMin = tx + k1;
+	  spanXMax = spanXMin + (scaledWidth - 1);
+	} else {
+	  spanXMax = tx + k1;
+	  spanXMin = spanXMax - (scaledWidth - 1);
+	}
+	spanY = ty + ySign * y + (int)(yShear * k1);
+	clipRes2 = state->clip->testSpan(spanXMin, spanXMax, spanY);
+	if (clipRes2 == splashClipAllOutside) {
+	  continue;
+	}
+      } else {
+	clipRes2 = clipRes;
+      }
+
+      // init x scale Bresenham
+      xt = 0;
+      xSrc = 0;
+
+      // x shear
+      x1 = k1;
+
+      // y shear
+      y1 = (SplashCoord)ySign * y + yShear * x1;
+      // this is a kludge: if yShear1 is negative, then (int)y1 would
+      // change immediately after the first pixel, which is not what
+      // we want
+      if (yShear1 < 0) {
+	y1 += 0.999;
+      }
+
+      // loop-invariant constants
+      n = yStep > 0 ? yStep : 1;
+
+      switch (srcMode) {
+
+      case splashModeMono1:
+      case splashModeMono8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  p = colorBuf + xSrc;
+	  pixAcc0 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	    }
+	    p += w - m;
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+
+	  pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+
+	  // set pixel
+	  if (vectorAntialias && clipRes != splashClipAllInside) {
+	    pipe.shape = (SplashCoord)1;
+	    drawAAPixel(&pipe, tx + x2, ty + y2);
+	  } else {
+	    drawPixel(&pipe, tx + x2, ty + y2,
+		      clipRes2 == splashClipAllInside);
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+
+      case splashModeRGB8:
+      case splashModeBGR8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  p = colorBuf + xSrc * 3;
+	  pixAcc0 = pixAcc1 = pixAcc2 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	      pixAcc1 += *p++;
+	      pixAcc2 += *p++;
+	    }
+	    p += 3 * (w - m);
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+
+	  pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+	  pix[1] = (int)((SplashCoord)pixAcc1 * pixMul);
+	  pix[2] = (int)((SplashCoord)pixAcc2 * pixMul);
+
+	  // set pixel
+	  if (vectorAntialias && clipRes != splashClipAllInside) {
+	    pipe.shape = (SplashCoord)1;
+	    drawAAPixel(&pipe, tx + x2, ty + y2);
+	  } else {
+	    drawPixel(&pipe, tx + x2, ty + y2,
+		      clipRes2 == splashClipAllInside);
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+
+#if SPLASH_CMYK
+      case splashModeCMYK8:
+	for (x = 0; x < scaledWidth; ++x) {
+
+	  // x scale Bresenham
+	  xStep = xp;
+	  xt += xq;
+	  if (xt >= scaledWidth) {
+	    xt -= scaledWidth;
+	    ++xStep;
+	  }
+
+	  // rotation
+	  if (rot) {
+	    x2 = (int)y1;
+	    y2 = -x1;
+	  } else {
+	    x2 = x1;
+	    y2 = (int)y1;
+	  }
+
+	  // compute the filtered pixel at (x,y) after the x and y scaling
+	  // operations
+	  m = xStep > 0 ? xStep : 1;
+	  p = colorBuf + xSrc * 4;
+	  pixAcc0 = pixAcc1 = pixAcc2 = pixAcc3 = 0;
+	  for (i = 0; i < n; ++i) {
+	    for (j = 0; j < m; ++j) {
+	      pixAcc0 += *p++;
+	      pixAcc1 += *p++;
+	      pixAcc2 += *p++;
+	      pixAcc3 += *p++;
+	    }
+	    p += 4 * (w - m);
+	  }
+	  pixMul = (SplashCoord)1 / (SplashCoord)(n * m);
+
+	  pix[0] = (int)((SplashCoord)pixAcc0 * pixMul);
+	  pix[1] = (int)((SplashCoord)pixAcc1 * pixMul);
+	  pix[2] = (int)((SplashCoord)pixAcc2 * pixMul);
+	  pix[3] = (int)((SplashCoord)pixAcc3 * pixMul);
+
+	  // set pixel
+	  if (vectorAntialias && clipRes != splashClipAllInside) {
+	    pipe.shape = (SplashCoord)1;
+	    drawAAPixel(&pipe, tx + x2, ty + y2);
+	  } else {
+	    drawPixel(&pipe, tx + x2, ty + y2,
+		      clipRes2 == splashClipAllInside);
+	  }
+
+	  // x scale Bresenham
+	  xSrc += xStep;
+
+	  // x shear
+	  x1 += xSign;
+
+	  // y shear
+	  y1 += yShear1;
+	}
+	break;
+#endif // SPLASH_CMYK
+      }
+    }
+
+  }
+
+  gfree(colorBuf);
+  gfree(alphaBuf);
+
+  return splashOk;
+}
+
+SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc,
+			      int xDest, int yDest, int w, int h,
+			      GBool noClip, GBool nonIsolated) {
+  SplashPipe pipe;
+  SplashColor pixel;
+  Guchar alpha;
+  Guchar *ap;
+  int x, y;
+
+  if (src->mode != bitmap->mode) {
+    return splashErrModeMismatch;
+  }
+
+  if (src->alpha) {
+    pipeInit(&pipe, xDest, yDest, NULL, pixel, state->fillAlpha,
+	     gTrue, nonIsolated);
+    for (y = 0; y < h; ++y) {
+      pipeSetXY(&pipe, xDest, yDest + y);
+      ap = src->getAlphaPtr() + (ySrc + y) * src->getWidth() + xSrc;
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	alpha = *ap++;
+	if (noClip || state->clip->test(xDest + x, yDest + y)) {
+	  // this uses shape instead of alpha, which isn't technically
+	  // correct, but works out the same
+	  pipe.shape = (SplashCoord)(alpha / 255.0);
+	  pipeRun(&pipe);
+	  updateModX(xDest + x);
+	  updateModY(yDest + y);
+	} else {
+	  pipeIncX(&pipe);
+	}
+      }
+    }
+  } else {
+    pipeInit(&pipe, xDest, yDest, NULL, pixel, state->fillAlpha,
+	     gFalse, nonIsolated);
+    for (y = 0; y < h; ++y) {
+      pipeSetXY(&pipe, xDest, yDest + y);
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	if (noClip || state->clip->test(xDest + x, yDest + y)) {
+	  pipeRun(&pipe);
+	  updateModX(xDest + x);
+	  updateModY(yDest + y);
+	} else {
+	  pipeIncX(&pipe);
+	}
+      }
+    }
+  }
+
+  return splashOk;
+}
+
+void Splash::compositeBackground(SplashColorPtr color) {
+  SplashColorPtr p;
+  Guchar *q;
+  Guchar alpha, alpha1, c, color0, color1, color2, color3;
+  int x, y, mask;
+
+  switch (bitmap->mode) {
+  case splashModeMono1:
+    color0 = color[0];
+    for (y = 0; y < bitmap->height; ++y) {
+      p = &bitmap->data[y * bitmap->rowSize];
+      q = &bitmap->alpha[y * bitmap->width];
+      mask = 0x80;
+      for (x = 0; x < bitmap->width; ++x) {
+	alpha = *q++;
+	alpha1 = 255 - alpha;
+	c = (*p & mask) ? 0xff : 0x00;
+	c = div255(alpha1 * color0 + alpha * c);
+	if (c & 0x80) {
+	  *p |= mask;
+	} else {
+	  *p &= ~mask;
+	}
+	if (!(mask >>= 1)) {
+	  mask = 0x80;
+	  ++p;
+	}
+      }
+    }
+    break;
+  case splashModeMono8:
+    color0 = color[0];
+    for (y = 0; y < bitmap->height; ++y) {
+      p = &bitmap->data[y * bitmap->rowSize];
+      q = &bitmap->alpha[y * bitmap->width];
+      for (x = 0; x < bitmap->width; ++x) {
+	alpha = *q++;
+	alpha1 = 255 - alpha;
+	p[0] = div255(alpha1 * color0 + alpha * p[0]);
+	++p;
+      }
+    }
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    color0 = color[0];
+    color1 = color[1];
+    color2 = color[2];
+    for (y = 0; y < bitmap->height; ++y) {
+      p = &bitmap->data[y * bitmap->rowSize];
+      q = &bitmap->alpha[y * bitmap->width];
+      for (x = 0; x < bitmap->width; ++x) {
+	alpha = *q++;
+	alpha1 = 255 - alpha;
+	p[0] = div255(alpha1 * color0 + alpha * p[0]);
+	p[1] = div255(alpha1 * color1 + alpha * p[1]);
+	p[2] = div255(alpha1 * color2 + alpha * p[2]);
+	p += 3;
+      }
+    }
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    color0 = color[0];
+    color1 = color[1];
+    color2 = color[2];
+    color3 = color[3];
+    for (y = 0; y < bitmap->height; ++y) {
+      p = &bitmap->data[y * bitmap->rowSize];
+      q = &bitmap->alpha[y * bitmap->width];
+      for (x = 0; x < bitmap->width; ++x) {
+	alpha = *q++;
+	alpha1 = 255 - alpha;
+	p[0] = div255(alpha1 * color0 + alpha * p[0]);
+	p[1] = div255(alpha1 * color1 + alpha * p[1]);
+	p[2] = div255(alpha1 * color2 + alpha * p[2]);
+	p[3] = div255(alpha1 * color3 + alpha * p[3]);
+	p += 4;
+      }
+    }
+    break;
+#endif
+  }
+  memset(bitmap->alpha, 255, bitmap->width * bitmap->height);
+}
+
+SplashError Splash::blitTransparent(SplashBitmap *src, int xSrc, int ySrc,
+				    int xDest, int yDest, int w, int h) {
+  SplashColor pixel;
+  SplashColorPtr p;
+  Guchar *q;
+  int x, y, mask;
+
+  if (src->mode != bitmap->mode) {
+    return splashErrModeMismatch;
+  }
+
+  switch (bitmap->mode) {
+  case splashModeMono1:
+    for (y = 0; y < h; ++y) {
+      p = &bitmap->data[(yDest + y) * bitmap->rowSize + (xDest >> 3)];
+      mask = 0x80 >> (xDest & 7);
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	if (pixel[0]) {
+	  *p |= mask;
+	} else {
+	  *p &= ~mask;
+	}
+	if (!(mask >>= 1)) {
+	  mask = 0x80;
+	  ++p;
+	}
+      }
+    }
+    break;
+  case splashModeMono8:
+    for (y = 0; y < h; ++y) {
+      p = &bitmap->data[(yDest + y) * bitmap->rowSize + xDest];
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	*p++ = pixel[0];
+      }
+    }
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    for (y = 0; y < h; ++y) {
+      p = &bitmap->data[(yDest + y) * bitmap->rowSize + 3 * xDest];
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	*p++ = pixel[0];
+	*p++ = pixel[1];
+	*p++ = pixel[2];
+      }
+    }
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    for (y = 0; y < h; ++y) {
+      p = &bitmap->data[(yDest + y) * bitmap->rowSize + 4 * xDest];
+      for (x = 0; x < w; ++x) {
+	src->getPixel(xSrc + x, ySrc + y, pixel);
+	*p++ = pixel[0];
+	*p++ = pixel[1];
+	*p++ = pixel[2];
+	*p++ = pixel[3];
+      }
+    }
+    break;
+#endif
+  }
+
+  if (bitmap->alpha) {
+    for (y = 0; y < h; ++y) {
+      q = &bitmap->alpha[(yDest + y) * bitmap->width + xDest];
+      for (x = 0; x < w; ++x) {
+	*q++ = 0x00;
+      }
+    }
+  }
+
+  return splashOk;
+}
+
+SplashPath *Splash::makeStrokePath(SplashPath *path, GBool flatten) {
+  SplashPath *pathIn, *pathOut;
+  SplashCoord w, d, dx, dy, wdx, wdy, dxNext, dyNext, wdxNext, wdyNext;
+  SplashCoord crossprod, dotprod, miter, m;
+  GBool first, last, closed;
+  int subpathStart, next, i;
+  int left0, left1, left2, right0, right1, right2, join0, join1, join2;
+  int leftFirst, rightFirst, firstPt;
+
+  if (flatten) {
+    pathIn = flattenPath(path, state->matrix, state->flatness);
+    if (state->lineDashLength > 0) {
+      pathOut = makeDashedPath(pathIn);
+      delete pathIn;
+      pathIn = pathOut;
+    }
+  } else {
+    pathIn = path;
+  }
+
+  subpathStart = 0; // make gcc happy
+  closed = gFalse; // make gcc happy
+  left0 = left1 = right0 = right1 = join0 = join1 = 0; // make gcc happy
+  leftFirst = rightFirst = firstPt = 0; // make gcc happy
+
+  pathOut = new SplashPath();
+  w = state->lineWidth;
+
+  for (i = 0; i < pathIn->length - 1; ++i) {
+    if (pathIn->flags[i] & splashPathLast) {
+      continue;
+    }
+    if ((first = pathIn->flags[i] & splashPathFirst)) {
+      subpathStart = i;
+      closed = pathIn->flags[i] & splashPathClosed;
+    }
+    last = pathIn->flags[i+1] & splashPathLast;
+
+    // compute the deltas for segment (i, i+1)
+    d = splashDist(pathIn->pts[i].x, pathIn->pts[i].y,
+		   pathIn->pts[i+1].x, pathIn->pts[i+1].y);
+    if (d == 0) {
+      // we need to draw end caps on zero-length lines
+      //~ not clear what the behavior should be for splashLineCapButt
+      //~   with d==0
+      dx = 0;
+      dy = 1;
+    } else {
+      d = (SplashCoord)1 / d;
+      dx = d * (pathIn->pts[i+1].x - pathIn->pts[i].x);
+      dy = d * (pathIn->pts[i+1].y - pathIn->pts[i].y);
+    }
+    wdx = (SplashCoord)0.5 * w * dx;
+    wdy = (SplashCoord)0.5 * w * dy;
+
+    // compute the deltas for segment (i+1, next)
+    next = last ? subpathStart + 1 : i + 2;
+    d = splashDist(pathIn->pts[i+1].x, pathIn->pts[i+1].y,
+		   pathIn->pts[next].x, pathIn->pts[next].y);
+    if (d == 0) {
+      // we need to draw end caps on zero-length lines
+      //~ not clear what the behavior should be for splashLineCapButt
+      //~   with d==0
+      dxNext = 0;
+      dyNext = 1;
+    } else {
+      d = (SplashCoord)1 / d;
+      dxNext = d * (pathIn->pts[next].x - pathIn->pts[i+1].x);
+      dyNext = d * (pathIn->pts[next].y - pathIn->pts[i+1].y);
+    }
+    wdxNext = (SplashCoord)0.5 * w * dxNext;
+    wdyNext = (SplashCoord)0.5 * w * dyNext;
+
+    // draw the start cap
+    pathOut->moveTo(pathIn->pts[i].x - wdy, pathIn->pts[i].y + wdx);
+    if (i == subpathStart) {
+      firstPt = pathOut->length - 1;
+    }
+    if (first && !closed) {
+      switch (state->lineCap) {
+      case splashLineCapButt:
+	pathOut->lineTo(pathIn->pts[i].x + wdy, pathIn->pts[i].y - wdx);
+	break;
+      case splashLineCapRound:
+	pathOut->curveTo(pathIn->pts[i].x - wdy - bezierCircle * wdx,
+			 pathIn->pts[i].y + wdx - bezierCircle * wdy,
+			 pathIn->pts[i].x - wdx - bezierCircle * wdy,
+			 pathIn->pts[i].y - wdy + bezierCircle * wdx,
+			 pathIn->pts[i].x - wdx,
+			 pathIn->pts[i].y - wdy);
+	pathOut->curveTo(pathIn->pts[i].x - wdx + bezierCircle * wdy,
+			 pathIn->pts[i].y - wdy - bezierCircle * wdx,
+			 pathIn->pts[i].x + wdy - bezierCircle * wdx,
+			 pathIn->pts[i].y - wdx - bezierCircle * wdy,
+			 pathIn->pts[i].x + wdy,
+			 pathIn->pts[i].y - wdx);
+	break;
+      case splashLineCapProjecting:
+	pathOut->lineTo(pathIn->pts[i].x - wdx - wdy,
+			pathIn->pts[i].y + wdx - wdy);
+	pathOut->lineTo(pathIn->pts[i].x - wdx + wdy,
+			pathIn->pts[i].y - wdx - wdy);
+	pathOut->lineTo(pathIn->pts[i].x + wdy,
+			pathIn->pts[i].y - wdx);
+	break;
+      }
+    } else {
+      pathOut->lineTo(pathIn->pts[i].x + wdy, pathIn->pts[i].y - wdx);
+    }
+
+    // draw the left side of the segment rectangle
+    left2 = pathOut->length - 1;
+    pathOut->lineTo(pathIn->pts[i+1].x + wdy, pathIn->pts[i+1].y - wdx);
+
+    // draw the end cap
+    if (last && !closed) {
+      switch (state->lineCap) {
+      case splashLineCapButt:
+	pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx);
+	break;
+      case splashLineCapRound:
+	pathOut->curveTo(pathIn->pts[i+1].x + wdy + bezierCircle * wdx,
+			 pathIn->pts[i+1].y - wdx + bezierCircle * wdy,
+			 pathIn->pts[i+1].x + wdx + bezierCircle * wdy,
+			 pathIn->pts[i+1].y + wdy - bezierCircle * wdx,
+			 pathIn->pts[i+1].x + wdx,
+			 pathIn->pts[i+1].y + wdy);
+	pathOut->curveTo(pathIn->pts[i+1].x + wdx - bezierCircle * wdy,
+			 pathIn->pts[i+1].y + wdy + bezierCircle * wdx,
+			 pathIn->pts[i+1].x - wdy + bezierCircle * wdx,
+			 pathIn->pts[i+1].y + wdx + bezierCircle * wdy,
+			 pathIn->pts[i+1].x - wdy,
+			 pathIn->pts[i+1].y + wdx);
+	break;
+      case splashLineCapProjecting:
+	pathOut->lineTo(pathIn->pts[i+1].x + wdy + wdx,
+			pathIn->pts[i+1].y - wdx + wdy);
+	pathOut->lineTo(pathIn->pts[i+1].x - wdy + wdx,
+			pathIn->pts[i+1].y + wdx + wdy);
+	pathOut->lineTo(pathIn->pts[i+1].x - wdy,
+			pathIn->pts[i+1].y + wdx);
+	break;
+      }
+    } else {
+      pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx);
+    }
+
+    // draw the right side of the segment rectangle
+    right2 = pathOut->length - 1;
+    pathOut->close();
+
+    // draw the join
+    join2 = pathOut->length;
+    if (!last || closed) {
+      crossprod = dx * dyNext - dy * dxNext;
+      dotprod = -(dx * dxNext + dy * dyNext);
+      if (dotprod > 0.99999) {
+	// avoid a divide-by-zero -- set miter to something arbitrary
+	// such that sqrt(miter) will exceed miterLimit (and m is never
+	// used in that situation)
+	miter = (state->miterLimit + 1) * (state->miterLimit + 1);
+	m = 0;
+      } else {
+	miter = (SplashCoord)2 / ((SplashCoord)1 - dotprod);
+	if (miter < 1) {
+	  // this can happen because of floating point inaccuracies
+	  miter = 1;
+	}
+	m = splashSqrt(miter - 1);
+      }
+
+      // round join
+      if (state->lineJoin == splashLineJoinRound) {
+	pathOut->moveTo(pathIn->pts[i+1].x + (SplashCoord)0.5 * w,
+			pathIn->pts[i+1].y);
+	pathOut->curveTo(pathIn->pts[i+1].x + (SplashCoord)0.5 * w,
+			 pathIn->pts[i+1].y + bezierCircle2 * w,
+			 pathIn->pts[i+1].x + bezierCircle2 * w,
+			 pathIn->pts[i+1].y + (SplashCoord)0.5 * w,
+			 pathIn->pts[i+1].x,
+			 pathIn->pts[i+1].y + (SplashCoord)0.5 * w);
+	pathOut->curveTo(pathIn->pts[i+1].x - bezierCircle2 * w,
+			 pathIn->pts[i+1].y + (SplashCoord)0.5 * w,
+			 pathIn->pts[i+1].x - (SplashCoord)0.5 * w,
+			 pathIn->pts[i+1].y + bezierCircle2 * w,
+			 pathIn->pts[i+1].x - (SplashCoord)0.5 * w,
+			 pathIn->pts[i+1].y);
+	pathOut->curveTo(pathIn->pts[i+1].x - (SplashCoord)0.5 * w,
+			 pathIn->pts[i+1].y - bezierCircle2 * w,
+			 pathIn->pts[i+1].x - bezierCircle2 * w,
+			 pathIn->pts[i+1].y - (SplashCoord)0.5 * w,
+			 pathIn->pts[i+1].x,
+			 pathIn->pts[i+1].y - (SplashCoord)0.5 * w);
+	pathOut->curveTo(pathIn->pts[i+1].x + bezierCircle2 * w,
+			 pathIn->pts[i+1].y - (SplashCoord)0.5 * w,
+			 pathIn->pts[i+1].x + (SplashCoord)0.5 * w,
+			 pathIn->pts[i+1].y - bezierCircle2 * w,
+			 pathIn->pts[i+1].x + (SplashCoord)0.5 * w,
+			 pathIn->pts[i+1].y);
+
+      } else {
+	pathOut->moveTo(pathIn->pts[i+1].x, pathIn->pts[i+1].y);
+
+	// angle < 180
+	if (crossprod < 0) {
+	  pathOut->lineTo(pathIn->pts[i+1].x - wdyNext,
+			  pathIn->pts[i+1].y + wdxNext);
+	  // miter join inside limit
+	  if (state->lineJoin == splashLineJoinMiter &&
+	      splashSqrt(miter) <= state->miterLimit) {
+	    pathOut->lineTo(pathIn->pts[i+1].x - wdy + wdx * m,
+			    pathIn->pts[i+1].y + wdx + wdy * m);
+	    pathOut->lineTo(pathIn->pts[i+1].x - wdy,
+			    pathIn->pts[i+1].y + wdx);
+	  // bevel join or miter join outside limit
+	  } else {
+	    pathOut->lineTo(pathIn->pts[i+1].x - wdy, pathIn->pts[i+1].y + wdx);
+	  }
+
+	// angle >= 180
+	} else {
+	  pathOut->lineTo(pathIn->pts[i+1].x + wdy,
+			  pathIn->pts[i+1].y - wdx);
+	  // miter join inside limit
+	  if (state->lineJoin == splashLineJoinMiter &&
+	      splashSqrt(miter) <= state->miterLimit) {
+	    pathOut->lineTo(pathIn->pts[i+1].x + wdy + wdx * m,
+			    pathIn->pts[i+1].y - wdx + wdy * m);
+	    pathOut->lineTo(pathIn->pts[i+1].x + wdyNext,
+			    pathIn->pts[i+1].y - wdxNext);
+	  // bevel join or miter join outside limit
+	  } else {
+	    pathOut->lineTo(pathIn->pts[i+1].x + wdyNext,
+			    pathIn->pts[i+1].y - wdxNext);
+	  }
+	}
+      }
+
+      pathOut->close();
+    }
+
+    // add stroke adjustment hints
+    if (state->strokeAdjust) {
+      if (i >= subpathStart + 1) {
+	if (i >= subpathStart + 2) {
+	  pathOut->addStrokeAdjustHint(left1, right1, left0 + 1, right0);
+	  pathOut->addStrokeAdjustHint(left1, right1, join0, left2);
+	} else {
+	  pathOut->addStrokeAdjustHint(left1, right1, firstPt, left2);
+	}
+	pathOut->addStrokeAdjustHint(left1, right1, right2 + 1, right2 + 1);
+      }
+      left0 = left1;
+      left1 = left2;
+      right0 = right1;
+      right1 = right2;
+      join0 = join1;
+      join1 = join2;
+      if (i == subpathStart) {
+	leftFirst = left2;
+	rightFirst = right2;
+      }
+      if (last) {
+	if (i >= subpathStart + 2) {
+	  pathOut->addStrokeAdjustHint(left1, right1, left0 + 1, right0);
+	  pathOut->addStrokeAdjustHint(left1, right1,
+				       join0, pathOut->length - 1);
+	} else {
+	  pathOut->addStrokeAdjustHint(left1, right1,
+				       firstPt, pathOut->length - 1);
+	}
+	if (closed) {
+	  pathOut->addStrokeAdjustHint(left1, right1, firstPt, leftFirst);
+	  pathOut->addStrokeAdjustHint(left1, right1,
+				       rightFirst + 1, rightFirst + 1);
+	  pathOut->addStrokeAdjustHint(leftFirst, rightFirst,
+				       left1 + 1, right1);
+	  pathOut->addStrokeAdjustHint(leftFirst, rightFirst,
+				       join1, pathOut->length - 1);
+	}
+      }
+    }
+  }
+
+  if (pathIn != path) {
+    delete pathIn;
+  }
+
+  return pathOut;
+}
+
+void Splash::dumpPath(SplashPath *path) {
+  int i;
+
+  for (i = 0; i < path->length; ++i) {
+    printf("  %3d: x=%8.2f y=%8.2f%s%s%s%s\n",
+	   i, (double)path->pts[i].x, (double)path->pts[i].y,
+	   (path->flags[i] & splashPathFirst) ? " first" : "",
+	   (path->flags[i] & splashPathLast) ? " last" : "",
+	   (path->flags[i] & splashPathClosed) ? " closed" : "",
+	   (path->flags[i] & splashPathCurve) ? " curve" : "");
+  }
+}
+
+void Splash::dumpXPath(SplashXPath *path) {
+  int i;
+
+  for (i = 0; i < path->length; ++i) {
+    printf("  %4d: x0=%8.2f y0=%8.2f x1=%8.2f y1=%8.2f %s%s%s%s%s%s%s\n",
+	   i, (double)path->segs[i].x0, (double)path->segs[i].y0,
+	   (double)path->segs[i].x1, (double)path->segs[i].y1,
+	   (path->segs[i].flags	& splashXPathFirst) ? "F" : " ",
+	   (path->segs[i].flags	& splashXPathLast) ? "L" : " ",
+	   (path->segs[i].flags	& splashXPathEnd0) ? "0" : " ",
+	   (path->segs[i].flags	& splashXPathEnd1) ? "1" : " ",
+	   (path->segs[i].flags	& splashXPathHoriz) ? "H" : " ",
+	   (path->segs[i].flags	& splashXPathVert) ? "V" : " ",
+	   (path->segs[i].flags	& splashXPathFlip) ? "P" : " ");
+  }
+}
diff --git a/xpdf/Splash.h b/xpdf/Splash.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Splash.h
@@ -0,0 +1,293 @@
+//========================================================================
+//
+// Splash.h
+//
+//========================================================================
+
+#ifndef SPLASH_H
+#define SPLASH_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashTypes.h"
+#include "SplashClip.h"
+
+class Splash;
+class SplashBitmap;
+struct SplashGlyphBitmap;
+class SplashState;
+class SplashPattern;
+class SplashScreen;
+class SplashPath;
+class SplashXPath;
+class SplashFont;
+struct SplashPipe;
+
+//------------------------------------------------------------------------
+
+// Retrieves the next line of pixels in an image mask.  Normally,
+// fills in *<line> and returns true.  If the image stream is
+// exhausted, returns false.
+typedef GBool (*SplashImageMaskSource)(void *data, SplashColorPtr pixel);
+
+// Retrieves the next line of pixels in an image.  Normally, fills in
+// *<line> and returns true.  If the image stream is exhausted,
+// returns false.
+typedef GBool (*SplashImageSource)(void *data, SplashColorPtr colorLine,
+				   Guchar *alphaLine);
+
+//------------------------------------------------------------------------
+
+enum SplashPipeResultColorCtrl {
+#if SPLASH_CMYK
+  splashPipeResultColorNoAlphaBlendCMYK,
+#endif
+  splashPipeResultColorNoAlphaBlendRGB,
+  splashPipeResultColorNoAlphaBlendMono,
+  splashPipeResultColorAlphaNoBlendMono,
+  splashPipeResultColorAlphaNoBlendRGB,
+#if SPLASH_CMYK
+  splashPipeResultColorAlphaNoBlendCMYK,
+#endif
+  splashPipeResultColorAlphaBlendMono,
+  splashPipeResultColorAlphaBlendRGB
+#if SPLASH_CMYK
+  ,
+  splashPipeResultColorAlphaBlendCMYK
+#endif
+};
+
+//------------------------------------------------------------------------
+// Splash
+//------------------------------------------------------------------------
+
+class Splash {
+public:
+
+  // Create a new rasterizer object.
+  Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
+	 SplashScreenParams *screenParams = NULL);
+  Splash(SplashBitmap *bitmapA, GBool vectorAntialiasA,
+	 SplashScreen *screenA);
+
+  ~Splash();
+
+  //----- state read
+
+  SplashCoord *getMatrix();
+  SplashPattern *getStrokePattern();
+  SplashPattern *getFillPattern();
+  SplashScreen *getScreen();
+  SplashBlendFunc getBlendFunc();
+  SplashCoord getStrokeAlpha();
+  SplashCoord getFillAlpha();
+  SplashCoord getLineWidth();
+  int getLineCap();
+  int getLineJoin();
+  SplashCoord getMiterLimit();
+  SplashCoord getFlatness();
+  SplashCoord *getLineDash();
+  int getLineDashLength();
+  SplashCoord getLineDashPhase();
+  SplashClip *getClip();
+  SplashBitmap *getSoftMask();
+  GBool getInNonIsolatedGroup();
+
+  //----- state write
+
+  void setMatrix(SplashCoord *matrix);
+  void setStrokePattern(SplashPattern *strokeColor);
+  void setFillPattern(SplashPattern *fillColor);
+  void setScreen(SplashScreen *screen);
+  void setBlendFunc(SplashBlendFunc func);
+  void setStrokeAlpha(SplashCoord alpha);
+  void setFillAlpha(SplashCoord alpha);
+  void setLineWidth(SplashCoord lineWidth);
+  void setLineCap(int lineCap);
+  void setLineJoin(int lineJoin);
+  void setMiterLimit(SplashCoord miterLimit);
+  void setFlatness(SplashCoord flatness);
+  // the <lineDash> array will be copied
+  void setLineDash(SplashCoord *lineDash, int lineDashLength,
+		   SplashCoord lineDashPhase);
+  void setStrokeAdjust(GBool strokeAdjust);
+  // NB: uses transformed coordinates.
+  void clipResetToRect(SplashCoord x0, SplashCoord y0,
+		       SplashCoord x1, SplashCoord y1);
+  // NB: uses transformed coordinates.
+  SplashError clipToRect(SplashCoord x0, SplashCoord y0,
+			 SplashCoord x1, SplashCoord y1);
+  // NB: uses untransformed coordinates.
+  SplashError clipToPath(SplashPath *path, GBool eo);
+  void setSoftMask(SplashBitmap *softMask);
+  void setInNonIsolatedGroup(SplashBitmap *alpha0BitmapA,
+			     int alpha0XA, int alpha0YA);
+
+  //----- state save/restore
+
+  void saveState();
+  SplashError restoreState();
+
+  //----- drawing operations
+
+  // Fill the bitmap with <color>.  This is not subject to clipping.
+  void clear(SplashColorPtr color, Guchar alpha = 0x00);
+
+  // Stroke a path using the current stroke pattern.
+  SplashError stroke(SplashPath *path);
+
+  // Fill a path using the current fill pattern.
+  SplashError fill(SplashPath *path, GBool eo);
+
+  // Fill a path, XORing with the current fill pattern.
+  SplashError xorFill(SplashPath *path, GBool eo);
+
+  // Draw a character, using the current fill pattern.
+  SplashError fillChar(SplashCoord x, SplashCoord y, int c, SplashFont *font);
+
+  // Draw a glyph, using the current fill pattern.  This function does
+  // not free any data, i.e., it ignores glyph->freeData.
+  SplashError fillGlyph(SplashCoord x, SplashCoord y,
+			SplashGlyphBitmap *glyph);
+
+  // Draws an image mask using the fill color.  This will read <h>
+  // lines of <w> pixels from <src>, starting with the top line.  "1"
+  // pixels will be drawn with the current fill color; "0" pixels are
+  // transparent.  The matrix:
+  //    [ mat[0] mat[1] 0 ]
+  //    [ mat[2] mat[3] 0 ]
+  //    [ mat[4] mat[5] 1 ]
+  // maps a unit square to the desired destination for the image, in
+  // PostScript style:
+  //    [x' y' 1] = [x y 1] * mat
+  // Note that the Splash y axis points downward, and the image source
+  // is assumed to produce pixels in raster order, starting from the
+  // top line.
+  SplashError fillImageMask(SplashImageMaskSource src, void *srcData,
+			    int w, int h, SplashCoord *mat,
+			    GBool glyphMode);
+
+  // Draw an image.  This will read <h> lines of <w> pixels from
+  // <src>, starting with the top line.  These pixels are assumed to
+  // be in the source mode, <srcMode>.  If <srcAlpha> is true, the
+  // alpha values returned by <src> are used; otherwise they are
+  // ignored.  The following combinations of source and target modes
+  // are supported:
+  //    source       target
+  //    ------       ------
+  //    Mono1        Mono1
+  //    Mono8        Mono1   -- with dithering
+  //    Mono8        Mono8
+  //    RGB8         RGB8
+  //    BGR8         BGR8
+  //    CMYK8        CMYK8
+  // The matrix behaves as for fillImageMask.
+  SplashError drawImage(SplashImageSource src, void *srcData,
+			SplashColorMode srcMode, GBool srcAlpha,
+			int w, int h, SplashCoord *mat);
+
+  // Composite a rectangular region from <src> onto this Splash
+  // object.
+  SplashError composite(SplashBitmap *src, int xSrc, int ySrc,
+			int xDest, int yDest, int w, int h,
+			GBool noClip, GBool nonIsolated);
+
+  // Composite this Splash object onto a background color.  The
+  // background alpha is assumed to be 1.
+  void compositeBackground(SplashColorPtr color);
+
+  // Copy a rectangular region from <src> onto the bitmap belonging to
+  // this Splash object.  The destination alpha values are all set to
+  // zero.
+  SplashError blitTransparent(SplashBitmap *src, int xSrc, int ySrc,
+			      int xDest, int yDest, int w, int h);
+
+  //----- misc
+
+  // Construct a path for a stroke, given the path to be stroked, and
+  // using the current line parameters.  If <flatten> is true, this
+  // function will first flatten the path and handle the linedash.
+  SplashPath *makeStrokePath(SplashPath *path, GBool flatten = gTrue);
+
+  // Return the associated bitmap.
+  SplashBitmap *getBitmap() { return bitmap; }
+
+  // Get a bounding box which includes all modifications since the
+  // last call to clearModRegion.
+  void getModRegion(int *xMin, int *yMin, int *xMax, int *yMax)
+    { *xMin = modXMin; *yMin = modYMin; *xMax = modXMax; *yMax = modYMax; }
+
+  // Clear the modified region bounding box.
+  void clearModRegion();
+
+  // Get clipping status for the last drawing operation subject to
+  // clipping.
+  SplashClipResult getClipRes() { return opClipRes; }
+
+  // Toggle debug mode on or off.
+  void setDebugMode(GBool debugModeA) { debugMode = debugModeA; }
+
+#if 1 //~tmp: turn off anti-aliasing temporarily
+  GBool getVectorAntialias() { return vectorAntialias; }
+  void setVectorAntialias(GBool vaa) { vectorAntialias = vaa; }
+#endif
+
+private:
+
+  void pipeInit(SplashPipe *pipe, int x, int y,
+		SplashPattern *pattern, SplashColorPtr cSrc,
+		SplashCoord aInput, GBool usesShape,
+		GBool nonIsolatedGroup);
+  void pipeRun(SplashPipe *pipe);
+  void pipeSetXY(SplashPipe *pipe, int x, int y);
+  void pipeIncX(SplashPipe *pipe);
+  void drawPixel(SplashPipe *pipe, int x, int y, GBool noClip);
+  void drawAAPixelInit();
+  void drawAAPixel(SplashPipe *pipe, int x, int y);
+  void drawSpan(SplashPipe *pipe, int x0, int x1, int y, GBool noClip);
+  void drawAALine(SplashPipe *pipe, int x0, int x1, int y);
+  void transform(SplashCoord *matrix, SplashCoord xi, SplashCoord yi,
+		 SplashCoord *xo, SplashCoord *yo);
+  void updateModX(int x);
+  void updateModY(int y);
+  void strokeNarrow(SplashPath *path);
+  void strokeWide(SplashPath *path);
+  SplashPath *flattenPath(SplashPath *path, SplashCoord *matrix,
+			  SplashCoord flatness);
+  void flattenCurve(SplashCoord x0, SplashCoord y0,
+		    SplashCoord x1, SplashCoord y1,
+		    SplashCoord x2, SplashCoord y2,
+		    SplashCoord x3, SplashCoord y3,
+		    SplashCoord *matrix, SplashCoord flatness2,
+		    SplashPath *fPath);
+  SplashPath *makeDashedPath(SplashPath *xPath);
+  SplashError fillWithPattern(SplashPath *path, GBool eo,
+			      SplashPattern *pattern, SplashCoord alpha);
+  SplashError fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph);
+  void dumpPath(SplashPath *path);
+  void dumpXPath(SplashXPath *path);
+
+  static SplashPipeResultColorCtrl pipeResultColorNoAlphaBlend[];
+  static SplashPipeResultColorCtrl pipeResultColorAlphaNoBlend[];
+  static SplashPipeResultColorCtrl pipeResultColorAlphaBlend[];
+  static int pipeNonIsoGroupCorrection[];
+
+  SplashBitmap *bitmap;
+  SplashState *state;
+  SplashBitmap *aaBuf;
+  int aaBufY;
+  SplashBitmap *alpha0Bitmap;	// for non-isolated groups, this is the
+				//   bitmap containing the alpha0 values
+  int alpha0X, alpha0Y;		// offset within alpha0Bitmap
+  SplashCoord aaGamma[splashAASize * splashAASize + 1];
+  int modXMin, modYMin, modXMax, modYMax;
+  SplashClipResult opClipRes;
+  GBool vectorAntialias;
+  GBool debugMode;
+};
+
+#endif
diff --git a/xpdf/SplashBitmap.cc b/xpdf/SplashBitmap.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashBitmap.cc
@@ -0,0 +1,188 @@
+//========================================================================
+//
+// SplashBitmap.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include "gmem.h"
+#include "SplashErrorCodes.h"
+#include "SplashBitmap.h"
+
+//------------------------------------------------------------------------
+// SplashBitmap
+//------------------------------------------------------------------------
+
+SplashBitmap::SplashBitmap(int widthA, int heightA, int rowPad,
+			   SplashColorMode modeA, GBool alphaA,
+			   GBool topDown) {
+  width = widthA;
+  height = heightA;
+  mode = modeA;
+  switch (mode) {
+  case splashModeMono1:
+    rowSize = (width + 7) >> 3;
+    break;
+  case splashModeMono8:
+    rowSize = width;
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    rowSize = width * 3;
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    rowSize = width * 4;
+    break;
+#endif
+  }
+  rowSize += rowPad - 1;
+  rowSize -= rowSize % rowPad;
+  data = (SplashColorPtr)gmalloc(rowSize * height);
+  if (!topDown) {
+    data += (height - 1) * rowSize;
+    rowSize = -rowSize;
+  }
+  if (alphaA) {
+    alpha = (Guchar *)gmalloc(width * height);
+  } else {
+    alpha = NULL;
+  }
+}
+
+
+SplashBitmap::~SplashBitmap() {
+  if (rowSize < 0) {
+    gfree(data + (height - 1) * rowSize);
+  } else {
+    gfree(data);
+  }
+  gfree(alpha);
+}
+
+SplashError SplashBitmap::writePNMFile(char *fileName) {
+  FILE *f;
+  SplashColorPtr row, p;
+  int x, y;
+
+  if (!(f = fopen(fileName, "wb"))) {
+    return splashErrOpenFile;
+  }
+
+  switch (mode) {
+
+  case splashModeMono1:
+    fprintf(f, "P4\n%d %d\n", width, height);
+    row = data;
+    for (y = 0; y < height; ++y) {
+      p = row;
+      for (x = 0; x < width; x += 8) {
+	fputc(*p ^ 0xff, f);
+	++p;
+      }
+      row += rowSize;
+    }
+    break;
+
+  case splashModeMono8:
+    fprintf(f, "P5\n%d %d\n255\n", width, height);
+    row = data;
+    for (y = 0; y < height; ++y) {
+      p = row;
+      for (x = 0; x < width; ++x) {
+	fputc(*p, f);
+	++p;
+      }
+      row += rowSize;
+    }
+    break;
+
+  case splashModeRGB8:
+    fprintf(f, "P6\n%d %d\n255\n", width, height);
+    row = data;
+    for (y = 0; y < height; ++y) {
+      p = row;
+      for (x = 0; x < width; ++x) {
+	fputc(splashRGB8R(p), f);
+	fputc(splashRGB8G(p), f);
+	fputc(splashRGB8B(p), f);
+	p += 3;
+      }
+      row += rowSize;
+    }
+    break;
+
+  case splashModeBGR8:
+    fprintf(f, "P6\n%d %d\n255\n", width, height);
+    row = data;
+    for (y = 0; y < height; ++y) {
+      p = row;
+      for (x = 0; x < width; ++x) {
+	fputc(splashBGR8R(p), f);
+	fputc(splashBGR8G(p), f);
+	fputc(splashBGR8B(p), f);
+	p += 3;
+      }
+      row += rowSize;
+    }
+    break;
+
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    // PNM doesn't support CMYK
+    break;
+#endif
+  }
+
+  fclose(f);
+  return splashOk;
+}
+
+void SplashBitmap::getPixel(int x, int y, SplashColorPtr pixel) {
+  SplashColorPtr p;
+
+  if (y < 0 || y >= height || x < 0 || x >= width) {
+    return;
+  }
+  switch (mode) {
+  case splashModeMono1:
+    p = &data[y * rowSize + (x >> 3)];
+    pixel[0] = (p[0] & (0x80 >> (x & 7))) ? 0xff : 0x00;
+    break;
+  case splashModeMono8:
+    p = &data[y * rowSize + x];
+    pixel[0] = p[0];
+    break;
+  case splashModeRGB8:
+    p = &data[y * rowSize + 3 * x];
+    pixel[0] = p[0];
+    pixel[1] = p[1];
+    pixel[2] = p[2];
+    break;
+  case splashModeBGR8:
+    p = &data[y * rowSize + 3 * x];
+    pixel[0] = p[2];
+    pixel[1] = p[1];
+    pixel[2] = p[0];
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    p = &data[y * rowSize + 4 * x];
+    pixel[0] = p[0];
+    pixel[1] = p[1];
+    pixel[2] = p[2];
+    pixel[3] = p[3];
+    break;
+#endif
+  }
+}
+
+Guchar SplashBitmap::getAlpha(int x, int y) {
+  return alpha[y * width + x];
+}
diff --git a/xpdf/SplashBitmap.h b/xpdf/SplashBitmap.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashBitmap.h
@@ -0,0 +1,61 @@
+//========================================================================
+//
+// SplashBitmap.h
+//
+//========================================================================
+
+#ifndef SPLASHBITMAP_H
+#define SPLASHBITMAP_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashTypes.h"
+
+//------------------------------------------------------------------------
+// SplashBitmap
+//------------------------------------------------------------------------
+
+class SplashBitmap {
+public:
+
+  // Create a new bitmap.  It will have <widthA> x <heightA> pixels in
+  // color mode <modeA>.  Rows will be padded out to a multiple of
+  // <rowPad> bytes.  If <topDown> is false, the bitmap will be stored
+  // upside-down, i.e., with the last row first in memory.
+  SplashBitmap(int widthA, int heightA, int rowPad,
+	       SplashColorMode modeA, GBool alphaA,
+	       GBool topDown = gTrue);
+
+  ~SplashBitmap();
+
+  int getWidth() { return width; }
+  int getHeight() { return height; }
+  int getRowSize() { return rowSize; }
+  int getAlphaRowSize() { return width; }
+  SplashColorMode getMode() { return mode; }
+  SplashColorPtr getDataPtr() { return data; }
+  Guchar *getAlphaPtr() { return alpha; }
+
+  SplashError writePNMFile(char *fileName);
+
+  void getPixel(int x, int y, SplashColorPtr pixel);
+  Guchar getAlpha(int x, int y);
+
+private:
+
+  int width, height;		// size of bitmap
+  int rowSize;			// size of one row of data, in bytes
+				//   - negative for bottom-up bitmaps
+  SplashColorMode mode;		// color mode
+  SplashColorPtr data;		// pointer to row zero of the color data
+  Guchar *alpha;		// pointer to row zero of the alpha data
+				//   (always top-down)
+
+  friend class Splash;
+};
+
+#endif
diff --git a/xpdf/SplashClip.cc b/xpdf/SplashClip.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashClip.cc
@@ -0,0 +1,382 @@
+//========================================================================
+//
+// SplashClip.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "gmem.h"
+#include "SplashErrorCodes.h"
+#include "SplashPath.h"
+#include "SplashXPath.h"
+#include "SplashXPathScanner.h"
+#include "SplashBitmap.h"
+#include "SplashClip.h"
+
+//------------------------------------------------------------------------
+// SplashClip.flags
+//------------------------------------------------------------------------
+
+#define splashClipEO       0x01	// use even-odd rule
+
+//------------------------------------------------------------------------
+// SplashClip
+//------------------------------------------------------------------------
+
+SplashClip::SplashClip(SplashCoord x0, SplashCoord y0,
+		       SplashCoord x1, SplashCoord y1,
+		       GBool antialiasA) {
+  antialias = antialiasA;
+  if (x0 < x1) {
+    xMin = x0;
+    xMax = x1;
+  } else {
+    xMin = x1;
+    xMax = x0;
+  }
+  if (y0 < y1) {
+    yMin = y0;
+    yMax = y1;
+  } else {
+    yMin = y1;
+    yMax = y0;
+  }
+  xMinI = splashFloor(xMin);
+  yMinI = splashFloor(yMin);
+  xMaxI = splashFloor(xMax);
+  yMaxI = splashFloor(yMax);
+  paths = NULL;
+  flags = NULL;
+  scanners = NULL;
+  length = size = 0;
+}
+
+SplashClip::SplashClip(SplashClip *clip) {
+  int i;
+
+  antialias = clip->antialias;
+  xMin = clip->xMin;
+  yMin = clip->yMin;
+  xMax = clip->xMax;
+  yMax = clip->yMax;
+  xMinI = clip->xMinI;
+  yMinI = clip->yMinI;
+  xMaxI = clip->xMaxI;
+  yMaxI = clip->yMaxI;
+  length = clip->length;
+  size = clip->size;
+  paths = (SplashXPath **)gmallocn(size, sizeof(SplashXPath *));
+  flags = (Guchar *)gmallocn(size, sizeof(Guchar));
+  scanners = (SplashXPathScanner **)
+                 gmallocn(size, sizeof(SplashXPathScanner *));
+  for (i = 0; i < length; ++i) {
+    paths[i] = clip->paths[i]->copy();
+    flags[i] = clip->flags[i];
+    scanners[i] = new SplashXPathScanner(paths[i], flags[i] & splashClipEO);
+  }
+}
+
+SplashClip::~SplashClip() {
+  int i;
+
+  for (i = 0; i < length; ++i) {
+    delete paths[i];
+    delete scanners[i];
+  }
+  gfree(paths);
+  gfree(flags);
+  gfree(scanners);
+}
+
+void SplashClip::grow(int nPaths) {
+  if (length + nPaths > size) {
+    if (size == 0) {
+      size = 32;
+    }
+    while (size < length + nPaths) {
+      size *= 2;
+    }
+    paths = (SplashXPath **)greallocn(paths, size, sizeof(SplashXPath *));
+    flags = (Guchar *)greallocn(flags, size, sizeof(Guchar));
+    scanners = (SplashXPathScanner **)
+                   greallocn(scanners, size, sizeof(SplashXPathScanner *));
+  }
+}
+
+void SplashClip::resetToRect(SplashCoord x0, SplashCoord y0,
+			     SplashCoord x1, SplashCoord y1) {
+  int i;
+
+  for (i = 0; i < length; ++i) {
+    delete paths[i];
+    delete scanners[i];
+  }
+  gfree(paths);
+  gfree(flags);
+  gfree(scanners);
+  paths = NULL;
+  flags = NULL;
+  scanners = NULL;
+  length = size = 0;
+
+  if (x0 < x1) {
+    xMin = x0;
+    xMax = x1;
+  } else {
+    xMin = x1;
+    xMax = x0;
+  }
+  if (y0 < y1) {
+    yMin = y0;
+    yMax = y1;
+  } else {
+    yMin = y1;
+    yMax = y0;
+  }
+  xMinI = splashFloor(xMin);
+  yMinI = splashFloor(yMin);
+  xMaxI = splashFloor(xMax);
+  yMaxI = splashFloor(yMax);
+}
+
+SplashError SplashClip::clipToRect(SplashCoord x0, SplashCoord y0,
+				   SplashCoord x1, SplashCoord y1) {
+  if (x0 < x1) {
+    if (x0 > xMin) {
+      xMin = x0;
+      xMinI = splashFloor(xMin);
+    }
+    if (x1 < xMax) {
+      xMax = x1;
+      xMaxI = splashFloor(xMax);
+    }
+  } else {
+    if (x1 > xMin) {
+      xMin = x1;
+      xMinI = splashFloor(xMin);
+    }
+    if (x0 < xMax) {
+      xMax = x0;
+      xMaxI = splashFloor(xMax);
+    }
+  }
+  if (y0 < y1) {
+    if (y0 > yMin) {
+      yMin = y0;
+      yMinI = splashFloor(yMin);
+    }
+    if (y1 < yMax) {
+      yMax = y1;
+      yMaxI = splashFloor(yMax);
+    }
+  } else {
+    if (y1 > yMin) {
+      yMin = y1;
+      yMinI = splashFloor(yMin);
+    }
+    if (y0 < yMax) {
+      yMax = y0;
+      yMaxI = splashFloor(yMax);
+    }
+  }
+  return splashOk;
+}
+
+SplashError SplashClip::clipToPath(SplashPath *path, SplashCoord *matrix,
+				   SplashCoord flatness, GBool eo) {
+  SplashXPath *xPath;
+
+  xPath = new SplashXPath(path, matrix, flatness, gTrue);
+
+  // check for an empty path
+  if (xPath->length == 0) {
+    xMax = xMin - 1;
+    yMax = yMin - 1;
+    xMaxI = splashFloor(xMax);
+    yMaxI = splashFloor(yMax);
+    delete xPath;
+
+  // check for a rectangle
+  } else if (xPath->length == 4 &&
+	     ((xPath->segs[0].x0 == xPath->segs[0].x1 &&
+	       xPath->segs[0].x0 == xPath->segs[1].x0 &&
+	       xPath->segs[0].x0 == xPath->segs[3].x1 &&
+	       xPath->segs[2].x0 == xPath->segs[2].x1 &&
+	       xPath->segs[2].x0 == xPath->segs[1].x1 &&
+	       xPath->segs[2].x0 == xPath->segs[3].x0 &&
+	       xPath->segs[1].y0 == xPath->segs[1].y1 &&
+	       xPath->segs[1].y0 == xPath->segs[0].y1 &&
+	       xPath->segs[1].y0 == xPath->segs[2].y0 &&
+	       xPath->segs[3].y0 == xPath->segs[3].y1 &&
+	       xPath->segs[3].y0 == xPath->segs[0].y0 &&
+	       xPath->segs[3].y0 == xPath->segs[2].y1) ||
+	      (xPath->segs[0].y0 == xPath->segs[0].y1 &&
+	       xPath->segs[0].y0 == xPath->segs[1].y0 &&
+	       xPath->segs[0].y0 == xPath->segs[3].y1 &&
+	       xPath->segs[2].y0 == xPath->segs[2].y1 &&
+	       xPath->segs[2].y0 == xPath->segs[1].y1 &&
+	       xPath->segs[2].y0 == xPath->segs[3].y0 &&
+	       xPath->segs[1].x0 == xPath->segs[1].x1 &&
+	       xPath->segs[1].x0 == xPath->segs[0].x1 &&
+	       xPath->segs[1].x0 == xPath->segs[2].x0 &&
+	       xPath->segs[3].x0 == xPath->segs[3].x1 &&
+	       xPath->segs[3].x0 == xPath->segs[0].x0 &&
+	       xPath->segs[3].x0 == xPath->segs[2].x1))) {
+    clipToRect(xPath->segs[0].x0, xPath->segs[0].y0,
+	       xPath->segs[2].x0, xPath->segs[2].y0);
+    delete xPath;
+
+  } else {
+    grow(1);
+    if (antialias) {
+      xPath->aaScale();
+    }
+    xPath->sort();
+    paths[length] = xPath;
+    flags[length] = eo ? splashClipEO : 0;
+    scanners[length] = new SplashXPathScanner(xPath, eo);
+    ++length;
+  }
+
+  return splashOk;
+}
+
+GBool SplashClip::test(int x, int y) {
+  int i;
+
+  // check the rectangle
+  if (x < xMinI || x > xMaxI || y < yMinI || y > yMaxI) {
+    return gFalse;
+  }
+
+  // check the paths
+  if (antialias) {
+    for (i = 0; i < length; ++i) {
+      if (!scanners[i]->test(x * splashAASize, y * splashAASize)) {
+	return gFalse;
+      }
+    }
+  } else {
+    for (i = 0; i < length; ++i) {
+      if (!scanners[i]->test(x, y)) {
+	return gFalse;
+      }
+    }
+  }
+
+  return gTrue;
+}
+
+SplashClipResult SplashClip::testRect(int rectXMin, int rectYMin,
+				      int rectXMax, int rectYMax) {
+  // This tests the rectangle:
+  //     x = [rectXMin, rectXMax + 1)    (note: rect coords are ints)
+  //     y = [rectYMin, rectYMax + 1)
+  // against the clipping region:
+  //     x = [xMin, xMax]                (note: clipping coords are fp)
+  //     y = [yMin, yMax]
+  if ((SplashCoord)(rectXMax + 1) <= xMin || (SplashCoord)rectXMin > xMax ||
+      (SplashCoord)(rectYMax + 1) <= yMin || (SplashCoord)rectYMin > yMax) {
+    return splashClipAllOutside;
+  }
+  if ((SplashCoord)rectXMin >= xMin && (SplashCoord)(rectXMax + 1) <= xMax &&
+      (SplashCoord)rectYMin >= yMin && (SplashCoord)(rectYMax + 1) <= yMax &&
+      length == 0) {
+    return splashClipAllInside;
+  }
+  return splashClipPartial;
+}
+
+SplashClipResult SplashClip::testSpan(int spanXMin, int spanXMax, int spanY) {
+  int i;
+
+  // This tests the rectangle:
+  //     x = [spanXMin, spanXMax + 1)    (note: span coords are ints)
+  //     y = [spanY, spanY + 1)
+  // against the clipping region:
+  //     x = [xMin, xMax]                (note: clipping coords are fp)
+  //     y = [yMin, yMax]
+  if ((SplashCoord)(spanXMax + 1) <= xMin || (SplashCoord)spanXMin > xMax ||
+      (SplashCoord)(spanY + 1) <= yMin || (SplashCoord)spanY > yMax) {
+    return splashClipAllOutside;
+  }
+  if (!((SplashCoord)spanXMin >= xMin && (SplashCoord)(spanXMax + 1) <= xMax &&
+	(SplashCoord)spanY >= yMin && (SplashCoord)(spanY + 1) <= yMax)) {
+    return splashClipPartial;
+  }
+  if (antialias) {
+    for (i = 0; i < length; ++i) {
+      if (!scanners[i]->testSpan(spanXMin * splashAASize,
+				 spanXMax * splashAASize + (splashAASize - 1),
+				 spanY * splashAASize)) {
+	return splashClipPartial;
+      }
+    }
+  } else {
+    for (i = 0; i < length; ++i) {
+      if (!scanners[i]->testSpan(spanXMin, spanXMax, spanY)) {
+	return splashClipPartial;
+      }
+    }
+  }
+  return splashClipAllInside;
+}
+
+void SplashClip::clipAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y) {
+  int xx0, xx1, xx, yy, i;
+  SplashColorPtr p;
+
+  // zero out pixels with x < xMin
+  xx0 = *x0 * splashAASize;
+  xx1 = splashFloor(xMin * splashAASize);
+  if (xx1 > aaBuf->getWidth()) {
+    xx1 = aaBuf->getWidth();
+  }
+  if (xx0 < xx1) {
+    xx0 &= ~7;
+    for (yy = 0; yy < splashAASize; ++yy) {
+      p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx0 >> 3);
+      for (xx = xx0; xx + 7 < xx1; xx += 8) {
+	*p++ = 0;
+      }
+      if (xx < xx1) {
+	*p &= 0xff >> (xx1 & 7);
+      }
+    }
+    *x0 = splashFloor(xMin);
+  }
+
+  // zero out pixels with x > xMax
+  xx0 = splashFloor(xMax * splashAASize) + 1;
+  if (xx0 < 0) {
+    xx0 = 0;
+  }
+  xx1 = (*x1 + 1) * splashAASize;
+  if (xx0 < xx1) {
+    for (yy = 0; yy < splashAASize; ++yy) {
+      p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx0 >> 3);
+      xx = xx0;
+      if (xx & 7) {
+	*p &= 0xff00 >> (xx & 7);
+	xx = (xx & ~7) + 8;
+	++p;
+      }
+      for (; xx < xx1; xx += 8) {
+	*p++ = 0;
+      }
+    }
+    *x1 = splashFloor(xMax);
+  }
+
+  // check the paths
+  for (i = 0; i < length; ++i) {
+    scanners[i]->clipAALine(aaBuf, x0, x1, y);
+  }
+}
diff --git a/xpdf/SplashClip.h b/xpdf/SplashClip.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashClip.h
@@ -0,0 +1,107 @@
+//========================================================================
+//
+// SplashClip.h
+//
+//========================================================================
+
+#ifndef SPLASHCLIP_H
+#define SPLASHCLIP_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashTypes.h"
+#include "SplashMath.h"
+
+class SplashPath;
+class SplashXPath;
+class SplashXPathScanner;
+class SplashBitmap;
+
+//------------------------------------------------------------------------
+
+enum SplashClipResult {
+  splashClipAllInside,
+  splashClipAllOutside,
+  splashClipPartial
+};
+
+//------------------------------------------------------------------------
+// SplashClip
+//------------------------------------------------------------------------
+
+class SplashClip {
+public:
+
+  // Create a clip, for the given rectangle.
+  SplashClip(SplashCoord x0, SplashCoord y0,
+	     SplashCoord x1, SplashCoord y1,
+	     GBool antialiasA);
+
+  // Copy a clip.
+  SplashClip *copy() { return new SplashClip(this); }
+
+  ~SplashClip();
+
+  // Reset the clip to a rectangle.
+  void resetToRect(SplashCoord x0, SplashCoord y0,
+		   SplashCoord x1, SplashCoord y1);
+
+  // Intersect the clip with a rectangle.
+  SplashError clipToRect(SplashCoord x0, SplashCoord y0,
+			 SplashCoord x1, SplashCoord y1);
+
+  // Interesect the clip with <path>.
+  SplashError clipToPath(SplashPath *path, SplashCoord *matrix,
+			 SplashCoord flatness, GBool eo);
+
+  // Returns true if (<x>,<y>) is inside the clip.
+  GBool test(int x, int y);
+
+  // Tests a rectangle against the clipping region.  Returns one of:
+  //   - splashClipAllInside if the entire rectangle is inside the
+  //     clipping region, i.e., all pixels in the rectangle are
+  //     visible
+  //   - splashClipAllOutside if the entire rectangle is outside the
+  //     clipping region, i.e., all the pixels in the rectangle are
+  //     clipped
+  //   - splashClipPartial if the rectangle is part inside and part
+  //     outside the clipping region
+  SplashClipResult testRect(int rectXMin, int rectYMin,
+			    int rectXMax, int rectYMax);
+
+  // Similar to testRect, but tests a horizontal span.
+  SplashClipResult testSpan(int spanXMin, int spanXMax, int spanY);
+
+  // Clips an anti-aliased line by setting pixels to zero.  On entry,
+  // all non-zero pixels are between <x0> and <x1>.  This function
+  // will update <x0> and <x1>.
+  void clipAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y);
+
+  // Get the rectangle part of the clip region, in integer coordinates.
+  int getXMinI() { return xMinI; }
+  int getXMaxI() { return xMaxI; }
+  int getYMinI() { return yMinI; }
+  int getYMaxI() { return yMaxI; }
+
+  // Get the number of arbitrary paths used by the clip region.
+  int getNumPaths() { return length; }
+
+private:
+
+  SplashClip(SplashClip *clip);
+  void grow(int nPaths);
+
+  GBool antialias;
+  SplashCoord xMin, yMin, xMax, yMax;
+  int xMinI, yMinI, xMaxI, yMaxI;
+  SplashXPath **paths;
+  Guchar *flags;
+  SplashXPathScanner **scanners;
+  int length, size;
+};
+
+#endif
diff --git a/xpdf/SplashErrorCodes.h b/xpdf/SplashErrorCodes.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashErrorCodes.h
@@ -0,0 +1,32 @@
+//========================================================================
+//
+// SplashErrorCodes.h
+//
+//========================================================================
+
+#ifndef SPLASHERRORCODES_H
+#define SPLASHERRORCODES_H
+
+#include <aconf.h>
+
+//------------------------------------------------------------------------
+
+#define splashOk                 0	// no error
+
+#define splashErrNoCurPt         1	// no current point
+
+#define splashErrEmptyPath       2	// zero points in path
+
+#define splashErrBogusPath       3	// only one point in subpath
+
+#define splashErrNoSave	         4	// state stack is empty
+
+#define splashErrOpenFile        5	// couldn't open file
+
+#define splashErrNoGlyph         6	// couldn't get the requested glyph
+
+#define splashErrModeMismatch    7	// invalid combination of color modes
+
+#define splashErrSingularMatrix  8	// matrix is singular
+
+#endif
diff --git a/xpdf/SplashFTFont.cc b/xpdf/SplashFTFont.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFTFont.cc
@@ -0,0 +1,357 @@
+//========================================================================
+//
+// SplashFTFont.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <ft2build.h>
+#include FT_OUTLINE_H
+#include FT_SIZES_H
+#include FT_GLYPH_H
+#include "gmem.h"
+#include "SplashMath.h"
+#include "SplashGlyphBitmap.h"
+#include "SplashPath.h"
+#include "SplashFTFontEngine.h"
+#include "SplashFTFontFile.h"
+#include "SplashFTFont.h"
+
+//------------------------------------------------------------------------
+
+static int glyphPathMoveTo(const FT_Vector *pt, void *path);
+static int glyphPathLineTo(const FT_Vector *pt, void *path);
+static int glyphPathConicTo(const FT_Vector *ctrl, const FT_Vector *pt,
+			    void *path);
+static int glyphPathCubicTo(const FT_Vector *ctrl1, const FT_Vector *ctrl2,
+			    const FT_Vector *pt, void *path);
+
+//------------------------------------------------------------------------
+// SplashFTFont
+//------------------------------------------------------------------------
+
+SplashFTFont::SplashFTFont(SplashFTFontFile *fontFileA, SplashCoord *matA,
+			   SplashCoord *textMatA):
+  SplashFont(fontFileA, matA, textMatA, fontFileA->engine->aa)
+{
+  FT_Face face;
+  SplashCoord size, div;
+  int x, y;
+
+  face = fontFileA->face;
+  if (FT_New_Size(face, &sizeObj)) {
+    return;
+  }
+  face->size = sizeObj;
+  size = splashSqrt(mat[2]*mat[2] + mat[3]*mat[3]);
+  if (FT_Set_Pixel_Sizes(face, 0, (int)size)) {
+    return;
+  }
+  // if the textMat values are too small, FreeType's fixed point
+  // arithmetic doesn't work so well
+  textScale = splashSqrt(textMat[2]*textMat[2] + textMat[3]*textMat[3]) / size;
+
+  div = face->bbox.xMax > 20000 ? 65536 : 1;
+
+  // transform the four corners of the font bounding box -- the min
+  // and max values form the bounding box of the transformed font
+  x = (int)((mat[0] * face->bbox.xMin + mat[2] * face->bbox.yMin) /
+	    (div * face->units_per_EM));
+  xMin = xMax = x;
+  y = (int)((mat[1] * face->bbox.xMin + mat[3] * face->bbox.yMin) /
+	    (div * face->units_per_EM));
+  yMin = yMax = y;
+  x = (int)((mat[0] * face->bbox.xMin + mat[2] * face->bbox.yMax) /
+	    (div * face->units_per_EM));
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  y = (int)((mat[1] * face->bbox.xMin + mat[3] * face->bbox.yMax) /
+	    (div * face->units_per_EM));
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  x = (int)((mat[0] * face->bbox.xMax + mat[2] * face->bbox.yMin) /
+	    (div * face->units_per_EM));
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  y = (int)((mat[1] * face->bbox.xMax + mat[3] * face->bbox.yMin) /
+	    (div * face->units_per_EM));
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  x = (int)((mat[0] * face->bbox.xMax + mat[2] * face->bbox.yMax) /
+	    (div * face->units_per_EM));
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  y = (int)((mat[1] * face->bbox.xMax + mat[3] * face->bbox.yMax) /
+	    (div * face->units_per_EM));
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  // This is a kludge: some buggy PDF generators embed fonts with
+  // zero bounding boxes.
+  if (xMax == xMin) {
+    xMin = 0;
+    xMax = (int)size;
+  }
+  if (yMax == yMin) {
+    yMin = 0;
+    yMax = (int)((SplashCoord)1.2 * size);
+  }
+
+  // compute the transform matrix
+#if USE_FIXEDPOINT
+  matrix.xx = (FT_Fixed)((mat[0] / size).getRaw());
+  matrix.yx = (FT_Fixed)((mat[1] / size).getRaw());
+  matrix.xy = (FT_Fixed)((mat[2] / size).getRaw());
+  matrix.yy = (FT_Fixed)((mat[3] / size).getRaw());
+  textMatrix.xx = (FT_Fixed)((textMat[0] / (size * textScale)).getRaw());
+  textMatrix.yx = (FT_Fixed)((textMat[1] / (size * textScale)).getRaw());
+  textMatrix.xy = (FT_Fixed)((textMat[2] / (size * textScale)).getRaw());
+  textMatrix.yy = (FT_Fixed)((textMat[3] / (size * textScale)).getRaw());
+#else
+  matrix.xx = (FT_Fixed)((mat[0] / size) * 65536);
+  matrix.yx = (FT_Fixed)((mat[1] / size) * 65536);
+  matrix.xy = (FT_Fixed)((mat[2] / size) * 65536);
+  matrix.yy = (FT_Fixed)((mat[3] / size) * 65536);
+  textMatrix.xx = (FT_Fixed)((textMat[0] / (size * textScale)) * 65536);
+  textMatrix.yx = (FT_Fixed)((textMat[1] / (size * textScale)) * 65536);
+  textMatrix.xy = (FT_Fixed)((textMat[2] / (size * textScale)) * 65536);
+  textMatrix.yy = (FT_Fixed)((textMat[3] / (size * textScale)) * 65536);
+#endif
+}
+
+SplashFTFont::~SplashFTFont() {
+}
+
+GBool SplashFTFont::getGlyph(int c, int xFrac, int yFrac,
+			     SplashGlyphBitmap *bitmap) {
+  return SplashFont::getGlyph(c, xFrac, 0, bitmap);
+}
+
+GBool SplashFTFont::makeGlyph(int c, int xFrac, int yFrac,
+			      SplashGlyphBitmap *bitmap) {
+  SplashFTFontFile *ff;
+  FT_Vector offset;
+  FT_GlyphSlot slot;
+  FT_UInt gid;
+  int rowSize;
+  Guchar *p, *q;
+  int i;
+
+  ff = (SplashFTFontFile *)fontFile;
+
+  ff->face->size = sizeObj;
+  offset.x = (FT_Pos)(int)((SplashCoord)xFrac * splashFontFractionMul * 64);
+  offset.y = 0;
+  FT_Set_Transform(ff->face, &matrix, &offset);
+  slot = ff->face->glyph;
+
+  if (ff->codeToGID && c < ff->codeToGIDLen) {
+    gid = (FT_UInt)ff->codeToGID[c];
+  } else {
+    gid = (FT_UInt)c;
+  }
+  if (ff->trueType && gid == 0) {
+    // skip the TrueType notdef glyph
+    return gFalse;
+  }
+
+  // if we have the FT2 bytecode interpreter, autohinting won't be used
+#ifdef TT_CONFIG_OPTION_BYTECODE_INTERPRETER
+  if (FT_Load_Glyph(ff->face, gid,
+		    aa ? FT_LOAD_NO_BITMAP : FT_LOAD_DEFAULT)) {
+    return gFalse;
+  }
+#else
+  // FT2's autohinting doesn't always work very well (especially with
+  // font subsets), so turn it off if anti-aliasing is enabled; if
+  // anti-aliasing is disabled, this seems to be a tossup - some fonts
+  // look better with hinting, some without, so leave hinting on
+  if (FT_Load_Glyph(ff->face, gid,
+		    aa ? FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP
+                       : FT_LOAD_DEFAULT)) {
+    return gFalse;
+  }
+#endif
+  if (FT_Render_Glyph(slot, aa ? ft_render_mode_normal
+		               : ft_render_mode_mono)) {
+    return gFalse;
+  }
+
+  bitmap->x = -slot->bitmap_left;
+  bitmap->y = slot->bitmap_top;
+  bitmap->w = slot->bitmap.width;
+  bitmap->h = slot->bitmap.rows;
+  bitmap->aa = aa;
+  if (aa) {
+    rowSize = bitmap->w;
+  } else {
+    rowSize = (bitmap->w + 7) >> 3;
+  }
+  bitmap->data = (Guchar *)gmalloc(rowSize * bitmap->h);
+  bitmap->freeData = gTrue;
+  for (i = 0, p = bitmap->data, q = slot->bitmap.buffer;
+       i < bitmap->h;
+       ++i, p += rowSize, q += slot->bitmap.pitch) {
+    memcpy(p, q, rowSize);
+  }
+
+  return gTrue;
+}
+
+struct SplashFTFontPath {
+  SplashPath *path;
+  SplashCoord textScale;
+  GBool needClose;
+};
+
+SplashPath *SplashFTFont::getGlyphPath(int c) {
+  static FT_Outline_Funcs outlineFuncs = {
+#if FREETYPE_MINOR <= 1
+    (int (*)(FT_Vector *, void *))&glyphPathMoveTo,
+    (int (*)(FT_Vector *, void *))&glyphPathLineTo,
+    (int (*)(FT_Vector *, FT_Vector *, void *))&glyphPathConicTo,
+    (int (*)(FT_Vector *, FT_Vector *, FT_Vector *, void *))&glyphPathCubicTo,
+#else
+    &glyphPathMoveTo,
+    &glyphPathLineTo,
+    &glyphPathConicTo,
+    &glyphPathCubicTo,
+#endif
+    0, 0
+  };
+  SplashFTFontFile *ff;
+  SplashFTFontPath path;
+  FT_GlyphSlot slot;
+  FT_UInt gid;
+  FT_Glyph glyph;
+
+  ff = (SplashFTFontFile *)fontFile;
+  ff->face->size = sizeObj;
+  FT_Set_Transform(ff->face, &textMatrix, NULL);
+  slot = ff->face->glyph;
+  if (ff->codeToGID && c < ff->codeToGIDLen) {
+    gid = ff->codeToGID[c];
+  } else {
+    gid = (FT_UInt)c;
+  }
+  if (ff->trueType && gid == 0) {
+    // skip the TrueType notdef glyph
+    return NULL;
+  }
+  if (FT_Load_Glyph(ff->face, gid, FT_LOAD_NO_BITMAP)) {
+    return NULL;
+  }
+  if (FT_Get_Glyph(slot, &glyph)) {
+    return NULL;
+  }
+  path.path = new SplashPath();
+  path.textScale = textScale;
+  path.needClose = gFalse;
+  FT_Outline_Decompose(&((FT_OutlineGlyph)glyph)->outline,
+		       &outlineFuncs, &path);
+  if (path.needClose) {
+    path.path->close();
+  }
+  FT_Done_Glyph(glyph);
+  return path.path;
+}
+
+static int glyphPathMoveTo(const FT_Vector *pt, void *path) {
+  SplashFTFontPath *p = (SplashFTFontPath *)path;
+
+  if (p->needClose) {
+    p->path->close();
+    p->needClose = gFalse;
+  }
+  p->path->moveTo((SplashCoord)pt->x * p->textScale / 64.0,
+		  (SplashCoord)pt->y * p->textScale / 64.0);
+  return 0;
+}
+
+static int glyphPathLineTo(const FT_Vector *pt, void *path) {
+  SplashFTFontPath *p = (SplashFTFontPath *)path;
+
+  p->path->lineTo((SplashCoord)pt->x * p->textScale / 64.0,
+		  (SplashCoord)pt->y * p->textScale / 64.0);
+  p->needClose = gTrue;
+  return 0;
+}
+
+static int glyphPathConicTo(const FT_Vector *ctrl, const FT_Vector *pt,
+			    void *path) {
+  SplashFTFontPath *p = (SplashFTFontPath *)path;
+  SplashCoord x0, y0, x1, y1, x2, y2, x3, y3, xc, yc;
+
+  if (!p->path->getCurPt(&x0, &y0)) {
+    return 0;
+  }
+  xc = (SplashCoord)ctrl->x * p->textScale / 64.0;
+  yc = (SplashCoord)ctrl->y * p->textScale / 64.0;
+  x3 = (SplashCoord)pt->x * p->textScale / 64.0;
+  y3 = (SplashCoord)pt->y * p->textScale / 64.0;
+
+  // A second-order Bezier curve is defined by two endpoints, p0 and
+  // p3, and one control point, pc:
+  //
+  //     p(t) = (1-t)^2*p0 + t*(1-t)*pc + t^2*p3
+  //
+  // A third-order Bezier curve is defined by the same two endpoints,
+  // p0 and p3, and two control points, p1 and p2:
+  //
+  //     p(t) = (1-t)^3*p0 + 3t*(1-t)^2*p1 + 3t^2*(1-t)*p2 + t^3*p3
+  //
+  // Applying some algebra, we can convert a second-order curve to a
+  // third-order curve:
+  //
+  //     p1 = (1/3) * (p0 + 2pc)
+  //     p2 = (1/3) * (2pc + p3)
+
+  x1 = (SplashCoord)(1.0 / 3.0) * (x0 + (SplashCoord)2 * xc);
+  y1 = (SplashCoord)(1.0 / 3.0) * (y0 + (SplashCoord)2 * yc);
+  x2 = (SplashCoord)(1.0 / 3.0) * ((SplashCoord)2 * xc + x3);
+  y2 = (SplashCoord)(1.0 / 3.0) * ((SplashCoord)2 * yc + y3);
+
+  p->path->curveTo(x1, y1, x2, y2, x3, y3);
+  p->needClose = gTrue;
+  return 0;
+}
+
+static int glyphPathCubicTo(const FT_Vector *ctrl1, const FT_Vector *ctrl2,
+			    const FT_Vector *pt, void *path) {
+  SplashFTFontPath *p = (SplashFTFontPath *)path;
+
+  p->path->curveTo((SplashCoord)ctrl1->x * p->textScale / 64.0,
+		   (SplashCoord)ctrl1->y * p->textScale / 64.0,
+		   (SplashCoord)ctrl2->x * p->textScale / 64.0,
+		   (SplashCoord)ctrl2->y * p->textScale / 64.0,
+		   (SplashCoord)pt->x * p->textScale / 64.0,
+		   (SplashCoord)pt->y * p->textScale / 64.0);
+  p->needClose = gTrue;
+  return 0;
+}
+
+#endif // HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
diff --git a/xpdf/SplashFTFont.h b/xpdf/SplashFTFont.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFTFont.h
@@ -0,0 +1,58 @@
+//========================================================================
+//
+// SplashFTFont.h
+//
+//========================================================================
+
+#ifndef SPLASHFTFONT_H
+#define SPLASHFTFONT_H
+
+#include <aconf.h>
+
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include "SplashFont.h"
+
+class SplashFTFontFile;
+
+//------------------------------------------------------------------------
+// SplashFTFont
+//------------------------------------------------------------------------
+
+class SplashFTFont: public SplashFont {
+public:
+
+  SplashFTFont(SplashFTFontFile *fontFileA, SplashCoord *matA,
+	       SplashCoord *textMatA);
+
+  virtual ~SplashFTFont();
+
+  // Munge xFrac and yFrac before calling SplashFont::getGlyph.
+  virtual GBool getGlyph(int c, int xFrac, int yFrac,
+			 SplashGlyphBitmap *bitmap);
+
+  // Rasterize a glyph.  The <xFrac> and <yFrac> values are the same
+  // as described for getGlyph.
+  virtual GBool makeGlyph(int c, int xFrac, int yFrac,
+			  SplashGlyphBitmap *bitmap);
+
+  // Return the path for a glyph.
+  virtual SplashPath *getGlyphPath(int c);
+
+private:
+
+  FT_Size sizeObj;
+  FT_Matrix matrix;
+  FT_Matrix textMatrix;
+  SplashCoord textScale;
+};
+
+#endif // HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+
+#endif
diff --git a/xpdf/SplashFTFontEngine.cc b/xpdf/SplashFTFontEngine.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFTFontEngine.cc
@@ -0,0 +1,179 @@
+//========================================================================
+//
+// SplashFTFontEngine.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#ifndef WIN32
+#  include <unistd.h>
+#endif
+#include "gmem.h"
+#include "GString.h"
+#include "gfile.h"
+#include "FoFiTrueType.h"
+#include "FoFiType1C.h"
+#include "SplashFTFontFile.h"
+#include "SplashFTFontEngine.h"
+
+#ifdef VMS
+#if (__VMS_VER < 70000000)
+extern "C" int unlink(char *filename);
+#endif
+#endif
+
+//------------------------------------------------------------------------
+
+static void fileWrite(void *stream, char *data, int len) {
+  fwrite(data, 1, len, (FILE *)stream);
+}
+
+//------------------------------------------------------------------------
+// SplashFTFontEngine
+//------------------------------------------------------------------------
+
+SplashFTFontEngine::SplashFTFontEngine(GBool aaA, FT_Library libA) {
+  FT_Int major, minor, patch;
+
+  aa = aaA;
+  lib = libA;
+
+  // as of FT 2.1.8, CID fonts are indexed by CID instead of GID
+  FT_Library_Version(lib, &major, &minor, &patch);
+  useCIDs = major > 2 ||
+            (major == 2 && (minor > 1 || (minor == 1 && patch > 7)));
+}
+
+SplashFTFontEngine *SplashFTFontEngine::init(GBool aaA) {
+  FT_Library libA;
+
+  if (FT_Init_FreeType(&libA)) {
+    return NULL;
+  }
+  return new SplashFTFontEngine(aaA, libA);
+}
+
+SplashFTFontEngine::~SplashFTFontEngine() {
+  FT_Done_FreeType(lib);
+}
+
+SplashFontFile *SplashFTFontEngine::loadType1Font(SplashFontFileID *idA,
+						  char *fileName,
+						  GBool deleteFile,
+						  char **enc) {
+  return SplashFTFontFile::loadType1Font(this, idA, fileName, deleteFile, enc);
+}
+
+SplashFontFile *SplashFTFontEngine::loadType1CFont(SplashFontFileID *idA,
+						   char *fileName,
+						   GBool deleteFile,
+						   char **enc) {
+  return SplashFTFontFile::loadType1Font(this, idA, fileName, deleteFile, enc);
+}
+
+SplashFontFile *SplashFTFontEngine::loadOpenTypeT1CFont(SplashFontFileID *idA,
+							char *fileName,
+							GBool deleteFile,
+							char **enc) {
+  return SplashFTFontFile::loadType1Font(this, idA, fileName, deleteFile, enc);
+}
+
+SplashFontFile *SplashFTFontEngine::loadCIDFont(SplashFontFileID *idA,
+						char *fileName,
+						GBool deleteFile) {
+  FoFiType1C *ff;
+  Gushort *cidToGIDMap;
+  int nCIDs;
+  SplashFontFile *ret;
+
+  // check for a CFF font
+  if (useCIDs) {
+    cidToGIDMap = NULL;
+    nCIDs = 0;
+  } else if ((ff = FoFiType1C::load(fileName))) {
+    cidToGIDMap = ff->getCIDToGIDMap(&nCIDs);
+    delete ff;
+  } else {
+    cidToGIDMap = NULL;
+    nCIDs = 0;
+  }
+  ret = SplashFTFontFile::loadCIDFont(this, idA, fileName, deleteFile,
+				      cidToGIDMap, nCIDs);
+  if (!ret) {
+    gfree(cidToGIDMap);
+  }
+  return ret;
+}
+
+SplashFontFile *SplashFTFontEngine::loadOpenTypeCFFFont(SplashFontFileID *idA,
+							char *fileName,
+							GBool deleteFile) {
+  FoFiTrueType *ff;
+  GBool isCID;
+  Gushort *cidToGIDMap;
+  int nCIDs;
+  SplashFontFile *ret;
+
+  cidToGIDMap = NULL;
+  nCIDs = 0;
+  isCID = gFalse;
+  if (!useCIDs) {
+    if ((ff = FoFiTrueType::load(fileName))) {
+      if (ff->isOpenTypeCFF()) {
+	cidToGIDMap = ff->getCIDToGIDMap(&nCIDs);
+      }
+      delete ff;
+    }
+  }
+  ret = SplashFTFontFile::loadCIDFont(this, idA, fileName, deleteFile,
+				      cidToGIDMap, nCIDs);
+  if (!ret) {
+    gfree(cidToGIDMap);
+  }
+  return ret;
+}
+
+SplashFontFile *SplashFTFontEngine::loadTrueTypeFont(SplashFontFileID *idA,
+						     char *fileName,
+						     GBool deleteFile,
+						     Gushort *codeToGID,
+						     int codeToGIDLen) {
+  FoFiTrueType *ff;
+  GString *tmpFileName;
+  FILE *tmpFile;
+  SplashFontFile *ret;
+
+  if (!(ff = FoFiTrueType::load(fileName))) {
+    return NULL;
+  }
+  tmpFileName = NULL;
+  if (!openTempFile(&tmpFileName, &tmpFile, "wb", NULL)) {
+    delete ff;
+    return NULL;
+  }
+  ff->writeTTF(&fileWrite, tmpFile);
+  delete ff;
+  fclose(tmpFile);
+  ret = SplashFTFontFile::loadTrueTypeFont(this, idA,
+					   tmpFileName->getCString(),
+					   gTrue, codeToGID, codeToGIDLen);
+  if (ret) {
+    if (deleteFile) {
+      unlink(fileName);
+    }
+  } else {
+    unlink(tmpFileName->getCString());
+  }
+  delete tmpFileName;
+  return ret;
+}
+
+#endif // HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
diff --git a/xpdf/SplashFTFontEngine.h b/xpdf/SplashFTFontEngine.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFTFontEngine.h
@@ -0,0 +1,65 @@
+//========================================================================
+//
+// SplashFTFontEngine.h
+//
+//========================================================================
+
+#ifndef SPLASHFTFONTENGINE_H
+#define SPLASHFTFONTENGINE_H
+
+#include <aconf.h>
+
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include "gtypes.h"
+
+class SplashFontFile;
+class SplashFontFileID;
+
+//------------------------------------------------------------------------
+// SplashFTFontEngine
+//------------------------------------------------------------------------
+
+class SplashFTFontEngine {
+public:
+
+  static SplashFTFontEngine *init(GBool aaA);
+
+  ~SplashFTFontEngine();
+
+  // Load fonts.
+  SplashFontFile *loadType1Font(SplashFontFileID *idA, char *fileName,
+				GBool deleteFile, char **enc);
+  SplashFontFile *loadType1CFont(SplashFontFileID *idA, char *fileName,
+				 GBool deleteFile, char **enc);
+  SplashFontFile *loadOpenTypeT1CFont(SplashFontFileID *idA, char *fileName,
+				      GBool deleteFile, char **enc);
+  SplashFontFile *loadCIDFont(SplashFontFileID *idA, char *fileName,
+			      GBool deleteFile);
+  SplashFontFile *loadOpenTypeCFFFont(SplashFontFileID *idA, char *fileName,
+				      GBool deleteFile);
+  SplashFontFile *loadTrueTypeFont(SplashFontFileID *idA, char *fileName,
+				   GBool deleteFile,
+				   Gushort *codeToGID, int codeToGIDLen);
+
+private:
+
+  SplashFTFontEngine(GBool aaA, FT_Library libA);
+
+  GBool aa;
+  FT_Library lib;
+  GBool useCIDs;
+
+  friend class SplashFTFontFile;
+  friend class SplashFTFont;
+};
+
+#endif // HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+
+#endif
diff --git a/xpdf/SplashFTFontFile.cc b/xpdf/SplashFTFontFile.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFTFontFile.cc
@@ -0,0 +1,114 @@
+//========================================================================
+//
+// SplashFTFontFile.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include "gmem.h"
+#include "SplashFTFontEngine.h"
+#include "SplashFTFont.h"
+#include "SplashFTFontFile.h"
+
+//------------------------------------------------------------------------
+// SplashFTFontFile
+//------------------------------------------------------------------------
+
+SplashFontFile *SplashFTFontFile::loadType1Font(SplashFTFontEngine *engineA,
+						SplashFontFileID *idA,
+						char *fileNameA,
+						GBool deleteFileA,
+						char **encA) {
+  FT_Face faceA;
+  Gushort *codeToGIDA;
+  char *name;
+  int i;
+
+  if (FT_New_Face(engineA->lib, fileNameA, 0, &faceA)) {
+    return NULL;
+  }
+  codeToGIDA = (Gushort *)gmallocn(256, sizeof(int));
+  for (i = 0; i < 256; ++i) {
+    codeToGIDA[i] = 0;
+    if ((name = encA[i])) {
+      codeToGIDA[i] = (Gushort)FT_Get_Name_Index(faceA, name);
+    }
+  }
+
+  return new SplashFTFontFile(engineA, idA, fileNameA, deleteFileA,
+			      faceA, codeToGIDA, 256, gFalse);
+}
+
+SplashFontFile *SplashFTFontFile::loadCIDFont(SplashFTFontEngine *engineA,
+					      SplashFontFileID *idA,
+					      char *fileNameA,
+					      GBool deleteFileA,
+					      Gushort *codeToGIDA,
+					      int codeToGIDLenA) {
+  FT_Face faceA;
+
+  if (FT_New_Face(engineA->lib, fileNameA, 0, &faceA)) {
+    return NULL;
+  }
+
+  return new SplashFTFontFile(engineA, idA, fileNameA, deleteFileA,
+			      faceA, codeToGIDA, codeToGIDLenA, gFalse);
+}
+
+SplashFontFile *SplashFTFontFile::loadTrueTypeFont(SplashFTFontEngine *engineA,
+						   SplashFontFileID *idA,
+						   char *fileNameA,
+						   GBool deleteFileA,
+						   Gushort *codeToGIDA,
+						   int codeToGIDLenA) {
+  FT_Face faceA;
+
+  if (FT_New_Face(engineA->lib, fileNameA, 0, &faceA)) {
+    return NULL;
+  }
+
+  return new SplashFTFontFile(engineA, idA, fileNameA, deleteFileA,
+			      faceA, codeToGIDA, codeToGIDLenA, gTrue);
+}
+
+SplashFTFontFile::SplashFTFontFile(SplashFTFontEngine *engineA,
+				   SplashFontFileID *idA,
+				   char *fileNameA, GBool deleteFileA,
+				   FT_Face faceA,
+				   Gushort *codeToGIDA, int codeToGIDLenA,
+				   GBool trueTypeA):
+  SplashFontFile(idA, fileNameA, deleteFileA)
+{
+  engine = engineA;
+  face = faceA;
+  codeToGID = codeToGIDA;
+  codeToGIDLen = codeToGIDLenA;
+  trueType = trueTypeA;
+}
+
+SplashFTFontFile::~SplashFTFontFile() {
+  if (face) {
+    FT_Done_Face(face);
+  }
+  if (codeToGID) {
+    gfree(codeToGID);
+  }
+}
+
+SplashFont *SplashFTFontFile::makeFont(SplashCoord *mat,
+				       SplashCoord *textMat) {
+  SplashFont *font;
+
+  font = new SplashFTFont(this, mat, textMat);
+  font->initCache();
+  return font;
+}
+
+#endif // HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
diff --git a/xpdf/SplashFTFontFile.h b/xpdf/SplashFTFontFile.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFTFontFile.h
@@ -0,0 +1,73 @@
+//========================================================================
+//
+// SplashFTFontFile.h
+//
+//========================================================================
+
+#ifndef SPLASHFTFONTFILE_H
+#define SPLASHFTFONTFILE_H
+
+#include <aconf.h>
+
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include "SplashFontFile.h"
+
+class SplashFontFileID;
+class SplashFTFontEngine;
+
+//------------------------------------------------------------------------
+// SplashFTFontFile
+//------------------------------------------------------------------------
+
+class SplashFTFontFile: public SplashFontFile {
+public:
+
+  static SplashFontFile *loadType1Font(SplashFTFontEngine *engineA,
+				       SplashFontFileID *idA, char *fileNameA,
+				       GBool deleteFileA, char **encA);
+  static SplashFontFile *loadCIDFont(SplashFTFontEngine *engineA,
+				     SplashFontFileID *idA, char *fileNameA,
+				     GBool deleteFileA,
+				     Gushort *codeToCIDA, int codeToGIDLenA);
+  static SplashFontFile *loadTrueTypeFont(SplashFTFontEngine *engineA,
+					  SplashFontFileID *idA,
+					  char *fileNameA,
+					  GBool deleteFileA,
+					  Gushort *codeToGIDA,
+					  int codeToGIDLenA);
+
+  virtual ~SplashFTFontFile();
+
+  // Create a new SplashFTFont, i.e., a scaled instance of this font
+  // file.
+  virtual SplashFont *makeFont(SplashCoord *mat,
+			       SplashCoord *textMat);
+
+private:
+
+  SplashFTFontFile(SplashFTFontEngine *engineA,
+		   SplashFontFileID *idA,
+		   char *fileNameA, GBool deleteFileA,
+		   FT_Face faceA,
+		   Gushort *codeToGIDA, int codeToGIDLenA,
+		   GBool trueTypeA);
+
+  SplashFTFontEngine *engine;
+  FT_Face face;
+  Gushort *codeToGID;
+  int codeToGIDLen;
+  GBool trueType;
+
+  friend class SplashFTFont;
+};
+
+#endif // HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+
+#endif
diff --git a/xpdf/SplashFont.cc b/xpdf/SplashFont.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFont.cc
@@ -0,0 +1,176 @@
+//========================================================================
+//
+// SplashFont.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <string.h>
+#include "gmem.h"
+#include "SplashMath.h"
+#include "SplashGlyphBitmap.h"
+#include "SplashFontFile.h"
+#include "SplashFont.h"
+
+//------------------------------------------------------------------------
+
+struct SplashFontCacheTag {
+  int c;
+  short xFrac, yFrac;		// x and y fractions
+  int mru;			// valid bit (0x80000000) and MRU index
+  int x, y, w, h;		// offset and size of glyph
+};
+
+//------------------------------------------------------------------------
+// SplashFont
+//------------------------------------------------------------------------
+
+SplashFont::SplashFont(SplashFontFile *fontFileA, SplashCoord *matA,
+		       SplashCoord *textMatA, GBool aaA) {
+  fontFile = fontFileA;
+  fontFile->incRefCnt();
+  mat[0] = matA[0];
+  mat[1] = matA[1];
+  mat[2] = matA[2];
+  mat[3] = matA[3];
+  textMat[0] = textMatA[0];
+  textMat[1] = textMatA[1];
+  textMat[2] = textMatA[2];
+  textMat[3] = textMatA[3];
+  aa = aaA;
+
+  cache = NULL;
+  cacheTags = NULL;
+
+  xMin = yMin = xMax = yMax = 0;
+}
+
+void SplashFont::initCache() {
+  int i;
+
+  // this should be (max - min + 1), but we add some padding to
+  // deal with rounding errors
+  glyphW = xMax - xMin + 3;
+  glyphH = yMax - yMin + 3;
+  if (aa) {
+    glyphSize = glyphW * glyphH;
+  } else {
+    glyphSize = ((glyphW + 7) >> 3) * glyphH;
+  }
+
+  // set up the glyph pixmap cache
+  cacheAssoc = 8;
+  if (glyphSize <= 256) {
+    cacheSets = 8;
+  } else if (glyphSize <= 512) {
+    cacheSets = 4;
+  } else if (glyphSize <= 1024) {
+    cacheSets = 2;
+  } else {
+    cacheSets = 1;
+  }
+  cache = (Guchar *)gmallocn(cacheSets * cacheAssoc, glyphSize);
+  cacheTags = (SplashFontCacheTag *)gmallocn(cacheSets * cacheAssoc,
+					     sizeof(SplashFontCacheTag));
+  for (i = 0; i < cacheSets * cacheAssoc; ++i) {
+    cacheTags[i].mru = i & (cacheAssoc - 1);
+  }
+}
+
+SplashFont::~SplashFont() {
+  fontFile->decRefCnt();
+  if (cache) {
+    gfree(cache);
+  }
+  if (cacheTags) {
+    gfree(cacheTags);
+  }
+}
+
+GBool SplashFont::getGlyph(int c, int xFrac, int yFrac,
+			   SplashGlyphBitmap *bitmap) {
+  SplashGlyphBitmap bitmap2;
+  int size;
+  Guchar *p;
+  int i, j, k;
+
+  // no fractional coordinates for large glyphs or non-anti-aliased
+  // glyphs
+  if (!aa || glyphH > 50) {
+    xFrac = yFrac = 0;
+  }
+
+  // check the cache
+  i = (c & (cacheSets - 1)) * cacheAssoc;
+  for (j = 0; j < cacheAssoc; ++j) {
+    if ((cacheTags[i+j].mru & 0x80000000) &&
+	cacheTags[i+j].c == c &&
+	(int)cacheTags[i+j].xFrac == xFrac &&
+	(int)cacheTags[i+j].yFrac == yFrac) {
+      bitmap->x = cacheTags[i+j].x;
+      bitmap->y = cacheTags[i+j].y;
+      bitmap->w = cacheTags[i+j].w;
+      bitmap->h = cacheTags[i+j].h;
+      for (k = 0; k < cacheAssoc; ++k) {
+	if (k != j &&
+	    (cacheTags[i+k].mru & 0x7fffffff) <
+	      (cacheTags[i+j].mru & 0x7fffffff)) {
+	  ++cacheTags[i+k].mru;
+	}
+      }
+      cacheTags[i+j].mru = 0x80000000;
+      bitmap->aa = aa;
+      bitmap->data = cache + (i+j) * glyphSize;
+      bitmap->freeData = gFalse;
+      return gTrue;
+    }
+  }
+
+  // generate the glyph bitmap
+  if (!makeGlyph(c, xFrac, yFrac, &bitmap2)) {
+    return gFalse;
+  }
+
+  // if the glyph doesn't fit in the bounding box, return a temporary
+  // uncached bitmap
+  if (bitmap2.w > glyphW || bitmap2.h > glyphH) {
+    *bitmap = bitmap2;
+    return gTrue;
+  }
+
+  // insert glyph pixmap in cache
+  if (aa) {
+    size = bitmap2.w * bitmap2.h;
+  } else {
+    size = ((bitmap2.w + 7) >> 3) * bitmap2.h;
+  }
+  p = NULL; // make gcc happy
+  for (j = 0; j < cacheAssoc; ++j) {
+    if ((cacheTags[i+j].mru & 0x7fffffff) == cacheAssoc - 1) {
+      cacheTags[i+j].mru = 0x80000000;
+      cacheTags[i+j].c = c;
+      cacheTags[i+j].xFrac = (short)xFrac;
+      cacheTags[i+j].yFrac = (short)yFrac;
+      cacheTags[i+j].x = bitmap2.x;
+      cacheTags[i+j].y = bitmap2.y;
+      cacheTags[i+j].w = bitmap2.w;
+      cacheTags[i+j].h = bitmap2.h;
+      p = cache + (i+j) * glyphSize;
+      memcpy(p, bitmap2.data, size);
+    } else {
+      ++cacheTags[i+j].mru;
+    }
+  }
+  *bitmap = bitmap2;
+  bitmap->data = p;
+  bitmap->freeData = gFalse;
+  if (bitmap2.freeData) {
+    gfree(bitmap2.data);
+  }
+  return gTrue;
+}
diff --git a/xpdf/SplashFont.h b/xpdf/SplashFont.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFont.h
@@ -0,0 +1,104 @@
+//========================================================================
+//
+// SplashFont.h
+//
+//========================================================================
+
+#ifndef SPLASHFONT_H
+#define SPLASHFONT_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "SplashTypes.h"
+
+struct SplashGlyphBitmap;
+struct SplashFontCacheTag;
+class SplashFontFile;
+class SplashPath;
+
+//------------------------------------------------------------------------
+
+// Fractional positioning uses this many bits to the right of the
+// decimal points.
+#define splashFontFractionBits 2
+#define splashFontFraction     (1 << splashFontFractionBits)
+#define splashFontFractionMul \
+                       ((SplashCoord)1 / (SplashCoord)splashFontFraction)
+
+//------------------------------------------------------------------------
+// SplashFont
+//------------------------------------------------------------------------
+
+class SplashFont {
+public:
+
+  SplashFont(SplashFontFile *fontFileA, SplashCoord *matA,
+	     SplashCoord *textMatA, GBool aaA);
+
+  // This must be called after the constructor, so that the subclass
+  // constructor has a chance to compute the bbox.
+  void initCache();
+
+  virtual ~SplashFont();
+
+  SplashFontFile *getFontFile() { return fontFile; }
+
+  // Return true if <this> matches the specified font file and matrix.
+  GBool matches(SplashFontFile *fontFileA, SplashCoord *matA,
+		SplashCoord *textMatA) {
+    return fontFileA == fontFile &&
+           matA[0] == mat[0] && matA[1] == mat[1] &&
+           matA[2] == mat[2] && matA[3] == mat[3] &&
+           textMatA[0] == textMat[0] && textMatA[1] == textMat[1] &&
+           textMatA[2] == textMat[2] && textMatA[3] == textMat[3];
+  }
+
+  // Get a glyph - this does a cache lookup first, and if not found,
+  // creates a new bitmap and adds it to the cache.  The <xFrac> and
+  // <yFrac> values are splashFontFractionBits bits each, representing
+  // the numerators of fractions in [0, 1), where the denominator is
+  // splashFontFraction = 1 << splashFontFractionBits.  Subclasses
+  // should override this to zero out xFrac and/or yFrac if they don't
+  // support fractional coordinates.
+  virtual GBool getGlyph(int c, int xFrac, int yFrac,
+			 SplashGlyphBitmap *bitmap);
+
+  // Rasterize a glyph.  The <xFrac> and <yFrac> values are the same
+  // as described for getGlyph.
+  virtual GBool makeGlyph(int c, int xFrac, int yFrac,
+			  SplashGlyphBitmap *bitmap) = 0;
+
+  // Return the path for a glyph.
+  virtual SplashPath *getGlyphPath(int c) = 0;
+
+  // Return the font transform matrix.
+  SplashCoord *getMatrix() { return mat; }
+
+  // Return the glyph bounding box.
+  void getBBox(int *xMinA, int *yMinA, int *xMaxA, int *yMaxA)
+    { *xMinA = xMin; *yMinA = yMin; *xMaxA = xMax; *yMaxA = yMax; }
+
+protected:
+
+  SplashFontFile *fontFile;
+  SplashCoord mat[4];		// font transform matrix
+				//   (text space -> device space)
+  SplashCoord textMat[4];	// text transform matrix
+				//   (text space -> user space)
+  GBool aa;			// anti-aliasing
+  int xMin, yMin, xMax, yMax;	// glyph bounding box
+  Guchar *cache;		// glyph bitmap cache
+  SplashFontCacheTag *		// cache tags
+    cacheTags;
+  int glyphW, glyphH;		// size of glyph bitmaps
+  int glyphSize;		// size of glyph bitmaps, in bytes
+  int cacheSets;		// number of sets in cache
+  int cacheAssoc;		// cache associativity (glyphs per set)
+};
+
+#endif
diff --git a/xpdf/SplashFontEngine.cc b/xpdf/SplashFontEngine.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFontEngine.cc
@@ -0,0 +1,317 @@
+//========================================================================
+//
+// SplashFontEngine.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#if HAVE_T1LIB_H
+#include <t1lib.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#ifndef WIN32
+#  include <unistd.h>
+#endif
+#include "gmem.h"
+#include "GString.h"
+#include "SplashMath.h"
+#include "SplashT1FontEngine.h"
+#include "SplashFTFontEngine.h"
+#include "SplashFontFile.h"
+#include "SplashFontFileID.h"
+#include "SplashFont.h"
+#include "SplashFontEngine.h"
+
+#ifdef VMS
+#if (__VMS_VER < 70000000)
+extern "C" int unlink(char *filename);
+#endif
+#endif
+
+//------------------------------------------------------------------------
+// SplashFontEngine
+//------------------------------------------------------------------------
+
+SplashFontEngine::SplashFontEngine(
+#if HAVE_T1LIB_H
+				   GBool enableT1lib,
+#endif
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+				   GBool enableFreeType,
+#endif
+				   GBool aa) {
+  int i;
+
+  for (i = 0; i < splashFontCacheSize; ++i) {
+    fontCache[i] = NULL;
+  }
+
+#if HAVE_T1LIB_H
+  if (enableT1lib) {
+    t1Engine = SplashT1FontEngine::init(aa);
+  } else {
+    t1Engine = NULL;
+  }
+#endif
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+  if (enableFreeType) {
+    ftEngine = SplashFTFontEngine::init(aa);
+  } else {
+    ftEngine = NULL;
+  }
+#endif
+}
+
+SplashFontEngine::~SplashFontEngine() {
+  int i;
+
+  for (i = 0; i < splashFontCacheSize; ++i) {
+    if (fontCache[i]) {
+      delete fontCache[i];
+    }
+  }
+
+#if HAVE_T1LIB_H
+  if (t1Engine) {
+    delete t1Engine;
+  }
+#endif
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+  if (ftEngine) {
+    delete ftEngine;
+  }
+#endif
+}
+
+SplashFontFile *SplashFontEngine::getFontFile(SplashFontFileID *id) {
+  SplashFontFile *fontFile;
+  int i;
+
+  for (i = 0; i < splashFontCacheSize; ++i) {
+    if (fontCache[i]) {
+      fontFile = fontCache[i]->getFontFile();
+      if (fontFile && fontFile->getID()->matches(id)) {
+	return fontFile;
+      }
+    }
+  }
+  return NULL;
+}
+
+SplashFontFile *SplashFontEngine::loadType1Font(SplashFontFileID *idA,
+						char *fileName,
+						GBool deleteFile, char **enc) {
+  SplashFontFile *fontFile;
+
+  fontFile = NULL;
+#if HAVE_T1LIB_H
+  if (!fontFile && t1Engine) {
+    fontFile = t1Engine->loadType1Font(idA, fileName, deleteFile, enc);
+  }
+#endif
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+  if (!fontFile && ftEngine) {
+    fontFile = ftEngine->loadType1Font(idA, fileName, deleteFile, enc);
+  }
+#endif
+
+#ifndef WIN32
+  // delete the (temporary) font file -- with Unix hard link
+  // semantics, this will remove the last link; otherwise it will
+  // return an error, leaving the file to be deleted later (if
+  // loadXYZFont failed, the file will always be deleted)
+  if (deleteFile) {
+    unlink(fontFile ? fontFile->fileName->getCString() : fileName);
+  }
+#endif
+
+  return fontFile;
+}
+
+SplashFontFile *SplashFontEngine::loadType1CFont(SplashFontFileID *idA,
+						 char *fileName,
+						 GBool deleteFile,
+						 char **enc) {
+  SplashFontFile *fontFile;
+
+  fontFile = NULL;
+#if HAVE_T1LIB_H
+  if (!fontFile && t1Engine) {
+    fontFile = t1Engine->loadType1CFont(idA, fileName, deleteFile, enc);
+  }
+#endif
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+  if (!fontFile && ftEngine) {
+    fontFile = ftEngine->loadType1CFont(idA, fileName, deleteFile, enc);
+  }
+#endif
+
+#ifndef WIN32
+  // delete the (temporary) font file -- with Unix hard link
+  // semantics, this will remove the last link; otherwise it will
+  // return an error, leaving the file to be deleted later (if
+  // loadXYZFont failed, the file will always be deleted)
+  if (deleteFile) {
+    unlink(fontFile ? fontFile->fileName->getCString() : fileName);
+  }
+#endif
+
+  return fontFile;
+}
+
+SplashFontFile *SplashFontEngine::loadOpenTypeT1CFont(SplashFontFileID *idA,
+						      char *fileName,
+						      GBool deleteFile,
+						      char **enc) {
+  SplashFontFile *fontFile;
+
+  fontFile = NULL;
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+  if (!fontFile && ftEngine) {
+    fontFile = ftEngine->loadOpenTypeT1CFont(idA, fileName, deleteFile, enc);
+  }
+#endif
+
+#ifndef WIN32
+  // delete the (temporary) font file -- with Unix hard link
+  // semantics, this will remove the last link; otherwise it will
+  // return an error, leaving the file to be deleted later (if
+  // loadXYZFont failed, the file will always be deleted)
+  if (deleteFile) {
+    unlink(fontFile ? fontFile->fileName->getCString() : fileName);
+  }
+#endif
+
+  return fontFile;
+}
+
+SplashFontFile *SplashFontEngine::loadCIDFont(SplashFontFileID *idA,
+					      char *fileName,
+					      GBool deleteFile) {
+  SplashFontFile *fontFile;
+
+  fontFile = NULL;
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+  if (!fontFile && ftEngine) {
+    fontFile = ftEngine->loadCIDFont(idA, fileName, deleteFile);
+  }
+#endif
+
+#ifndef WIN32
+  // delete the (temporary) font file -- with Unix hard link
+  // semantics, this will remove the last link; otherwise it will
+  // return an error, leaving the file to be deleted later (if
+  // loadXYZFont failed, the file will always be deleted)
+  if (deleteFile) {
+    unlink(fontFile ? fontFile->fileName->getCString() : fileName);
+  }
+#endif
+
+  return fontFile;
+}
+
+SplashFontFile *SplashFontEngine::loadOpenTypeCFFFont(SplashFontFileID *idA,
+						      char *fileName,
+						      GBool deleteFile) {
+  SplashFontFile *fontFile;
+
+  fontFile = NULL;
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+  if (!fontFile && ftEngine) {
+    fontFile = ftEngine->loadOpenTypeCFFFont(idA, fileName, deleteFile);
+  }
+#endif
+
+#ifndef WIN32
+  // delete the (temporary) font file -- with Unix hard link
+  // semantics, this will remove the last link; otherwise it will
+  // return an error, leaving the file to be deleted later (if
+  // loadXYZFont failed, the file will always be deleted)
+  if (deleteFile) {
+    unlink(fontFile ? fontFile->fileName->getCString() : fileName);
+  }
+#endif
+
+  return fontFile;
+}
+
+SplashFontFile *SplashFontEngine::loadTrueTypeFont(SplashFontFileID *idA,
+						   char *fileName,
+						   GBool deleteFile,
+						   Gushort *codeToGID,
+						   int codeToGIDLen) {
+  SplashFontFile *fontFile;
+
+  fontFile = NULL;
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+  if (!fontFile && ftEngine) {
+    fontFile = ftEngine->loadTrueTypeFont(idA, fileName, deleteFile,
+					  codeToGID, codeToGIDLen);
+  }
+#endif
+
+  if (!fontFile) {
+    gfree(codeToGID);
+  }
+
+#ifndef WIN32
+  // delete the (temporary) font file -- with Unix hard link
+  // semantics, this will remove the last link; otherwise it will
+  // return an error, leaving the file to be deleted later (if
+  // loadXYZFont failed, the file will always be deleted)
+  if (deleteFile) {
+    unlink(fontFile ? fontFile->fileName->getCString() : fileName);
+  }
+#endif
+
+  return fontFile;
+}
+
+SplashFont *SplashFontEngine::getFont(SplashFontFile *fontFile,
+				      SplashCoord *textMat,
+				      SplashCoord *ctm) {
+  SplashCoord mat[4];
+  SplashFont *font;
+  int i, j;
+
+  mat[0] = textMat[0] * ctm[0] + textMat[1] * ctm[2];
+  mat[1] = -(textMat[0] * ctm[1] + textMat[1] * ctm[3]);
+  mat[2] = textMat[2] * ctm[0] + textMat[3] * ctm[2];
+  mat[3] = -(textMat[2] * ctm[1] + textMat[3] * ctm[3]);
+  if (splashAbs(mat[0] * mat[3] - mat[1] * mat[2]) < 0.01) {
+    // avoid a singular (or close-to-singular) matrix
+    mat[0] = 0.01;  mat[1] = 0;
+    mat[2] = 0;     mat[3] = 0.01;
+  }
+
+  font = fontCache[0];
+  if (font && font->matches(fontFile, mat, textMat)) {
+    return font;
+  }
+  for (i = 1; i < splashFontCacheSize; ++i) {
+    font = fontCache[i];
+    if (font && font->matches(fontFile, mat, textMat)) {
+      for (j = i; j > 0; --j) {
+	fontCache[j] = fontCache[j-1];
+      }
+      fontCache[0] = font;
+      return font;
+    }
+  }
+  font = fontFile->makeFont(mat, textMat);
+  if (fontCache[splashFontCacheSize - 1]) {
+    delete fontCache[splashFontCacheSize - 1];
+  }
+  for (j = splashFontCacheSize - 1; j > 0; --j) {
+    fontCache[j] = fontCache[j-1];
+  }
+  fontCache[0] = font;
+  return font;
+}
diff --git a/xpdf/SplashFontEngine.h b/xpdf/SplashFontEngine.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFontEngine.h
@@ -0,0 +1,91 @@
+//========================================================================
+//
+// SplashFontEngine.h
+//
+//========================================================================
+
+#ifndef SPLASHFONTENGINE_H
+#define SPLASHFONTENGINE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+
+class SplashT1FontEngine;
+class SplashFTFontEngine;
+class SplashDTFontEngine;
+class SplashDT4FontEngine;
+class SplashFontFile;
+class SplashFontFileID;
+class SplashFont;
+
+//------------------------------------------------------------------------
+
+#define splashFontCacheSize 16
+
+//------------------------------------------------------------------------
+// SplashFontEngine
+//------------------------------------------------------------------------
+
+class SplashFontEngine {
+public:
+
+  // Create a font engine.
+  SplashFontEngine(
+#if HAVE_T1LIB_H
+		   GBool enableT1lib,
+#endif
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+		   GBool enableFreeType,
+#endif
+		   GBool aa);
+
+  ~SplashFontEngine();
+
+  // Get a font file from the cache.  Returns NULL if there is no
+  // matching entry in the cache.
+  SplashFontFile *getFontFile(SplashFontFileID *id);
+
+  // Load fonts - these create new SplashFontFile objects.
+  SplashFontFile *loadType1Font(SplashFontFileID *idA, char *fileName,
+				GBool deleteFile, char **enc);
+  SplashFontFile *loadType1CFont(SplashFontFileID *idA, char *fileName,
+				 GBool deleteFile, char **enc);
+  SplashFontFile *loadOpenTypeT1CFont(SplashFontFileID *idA, char *fileName,
+				      GBool deleteFile, char **enc);
+  SplashFontFile *loadCIDFont(SplashFontFileID *idA, char *fileName,
+			      GBool deleteFile);
+  SplashFontFile *loadOpenTypeCFFFont(SplashFontFileID *idA, char *fileName,
+				      GBool deleteFile);
+  SplashFontFile *loadTrueTypeFont(SplashFontFileID *idA, char *fileName,
+				   GBool deleteFile,
+				   Gushort *codeToGID, int codeToGIDLen);
+
+  // Get a font - this does a cache lookup first, and if not found,
+  // creates a new SplashFont object and adds it to the cache.  The
+  // matrix, mat = textMat * ctm:
+  //    [ mat[0] mat[1] ]
+  //    [ mat[2] mat[3] ]
+  // specifies the font transform in PostScript style:
+  //    [x' y'] = [x y] * mat
+  // Note that the Splash y axis points downward.
+  SplashFont *getFont(SplashFontFile *fontFile,
+		      SplashCoord *textMat, SplashCoord *ctm);
+
+private:
+
+  SplashFont *fontCache[splashFontCacheSize];
+
+#if HAVE_T1LIB_H
+  SplashT1FontEngine *t1Engine;
+#endif
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+  SplashFTFontEngine *ftEngine;
+#endif
+};
+
+#endif
diff --git a/xpdf/SplashFontFile.cc b/xpdf/SplashFontFile.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFontFile.cc
@@ -0,0 +1,55 @@
+//========================================================================
+//
+// SplashFontFile.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#ifndef WIN32
+#  include <unistd.h>
+#endif
+#include "GString.h"
+#include "SplashFontFile.h"
+#include "SplashFontFileID.h"
+
+#ifdef VMS
+#if (__VMS_VER < 70000000)
+extern "C" int unlink(char *filename);
+#endif
+#endif
+
+//------------------------------------------------------------------------
+// SplashFontFile
+//------------------------------------------------------------------------
+
+SplashFontFile::SplashFontFile(SplashFontFileID *idA, char *fileNameA,
+			       GBool deleteFileA) {
+  id = idA;
+  fileName = new GString(fileNameA);
+  deleteFile = deleteFileA;
+  refCnt = 0;
+}
+
+SplashFontFile::~SplashFontFile() {
+  if (deleteFile) {
+    unlink(fileName->getCString());
+  }
+  delete fileName;
+  delete id;
+}
+
+void SplashFontFile::incRefCnt() {
+  ++refCnt;
+}
+
+void SplashFontFile::decRefCnt() {
+  if (!--refCnt) {
+    delete this;
+  }
+}
diff --git a/xpdf/SplashFontFile.h b/xpdf/SplashFontFile.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFontFile.h
@@ -0,0 +1,60 @@
+//========================================================================
+//
+// SplashFontFile.h
+//
+//========================================================================
+
+#ifndef SPLASHFONTFILE_H
+#define SPLASHFONTFILE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "SplashTypes.h"
+
+class GString;
+class SplashFontEngine;
+class SplashFont;
+class SplashFontFileID;
+
+//------------------------------------------------------------------------
+// SplashFontFile
+//------------------------------------------------------------------------
+
+class SplashFontFile {
+public:
+
+  virtual ~SplashFontFile();
+
+  // Create a new SplashFont, i.e., a scaled instance of this font
+  // file.
+  virtual SplashFont *makeFont(SplashCoord *mat, SplashCoord *textMat) = 0;
+
+  // Get the font file ID.
+  SplashFontFileID *getID() { return id; }
+
+  // Increment the reference count.
+  void incRefCnt();
+
+  // Decrement the reference count.  If the new value is zero, delete
+  // the SplashFontFile object.
+  void decRefCnt();
+
+protected:
+
+  SplashFontFile(SplashFontFileID *idA, char *fileNameA,
+		 GBool deleteFileA);
+
+  SplashFontFileID *id;
+  GString *fileName;
+  GBool deleteFile;
+  int refCnt;
+
+  friend class SplashFontEngine;
+};
+
+#endif
diff --git a/xpdf/SplashFontFileID.cc b/xpdf/SplashFontFileID.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFontFileID.cc
@@ -0,0 +1,23 @@
+//========================================================================
+//
+// SplashFontFileID.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include "SplashFontFileID.h"
+
+//------------------------------------------------------------------------
+// SplashFontFileID
+//------------------------------------------------------------------------
+
+SplashFontFileID::SplashFontFileID() {
+}
+
+SplashFontFileID::~SplashFontFileID() {
+}
diff --git a/xpdf/SplashFontFileID.h b/xpdf/SplashFontFileID.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashFontFileID.h
@@ -0,0 +1,30 @@
+//========================================================================
+//
+// SplashFontFileID.h
+//
+//========================================================================
+
+#ifndef SPLASHFONTFILEID_H
+#define SPLASHFONTFILEID_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+
+//------------------------------------------------------------------------
+// SplashFontFileID
+//------------------------------------------------------------------------
+
+class SplashFontFileID {
+public:
+
+  SplashFontFileID();
+  virtual ~SplashFontFileID();
+  virtual GBool matches(SplashFontFileID *id) = 0;
+};
+
+#endif
diff --git a/xpdf/SplashGlyphBitmap.h b/xpdf/SplashGlyphBitmap.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashGlyphBitmap.h
@@ -0,0 +1,26 @@
+//========================================================================
+//
+// SplashGlyphBitmap.h
+//
+//========================================================================
+
+#ifndef SPLASHGLYPHBITMAP_H
+#define SPLASHGLYPHBITMAP_H
+
+#include <aconf.h>
+
+#include "gtypes.h"
+
+//------------------------------------------------------------------------
+// SplashGlyphBitmap
+//------------------------------------------------------------------------
+
+struct SplashGlyphBitmap {
+  int x, y, w, h;		// offset and size of glyph
+  GBool aa;			// anti-aliased: true means 8-bit alpha
+				//   bitmap; false means 1-bit
+  Guchar *data;			// bitmap data
+  GBool freeData;		// true if data memory should be freed
+};
+
+#endif
diff --git a/xpdf/SplashMath.h b/xpdf/SplashMath.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashMath.h
@@ -0,0 +1,89 @@
+//========================================================================
+//
+// SplashMath.h
+//
+//========================================================================
+
+#ifndef SPLASHMATH_H
+#define SPLASHMATH_H
+
+#include <aconf.h>
+#if USE_FIXEDPONT
+#include "FixedPoint.h"
+#else
+#include <math.h>
+#endif
+#include "SplashTypes.h"
+
+static inline SplashCoord splashAbs(SplashCoord x) {
+#if USE_FIXEDPOINT
+  return FixedPoint::abs(x);
+#else
+  return fabs(x);
+#endif
+}
+
+static inline int splashFloor(SplashCoord x) {
+#if USE_FIXEDPOINT
+  return FixedPoint::floor(x);
+#else
+  return (int)floor(x);
+#endif
+}
+
+static inline int splashCeil(SplashCoord x) {
+#if USE_FIXEDPOINT
+  return FixedPoint::ceil(x);
+#else
+  return (int)ceil(x);
+#endif
+}
+
+static inline int splashRound(SplashCoord x) {
+#if USE_FIXEDPOINT
+  return FixedPoint::round(x);
+#else
+  return (int)floor(x + 0.5);
+#endif
+}
+
+static inline SplashCoord splashSqrt(SplashCoord x) {
+#if USE_FIXEDPOINT
+  return FixedPoint::sqrt(x);
+#else
+  return sqrt(x);
+#endif
+}
+
+static inline SplashCoord splashPow(SplashCoord x, SplashCoord y) {
+#if USE_FIXEDPOINT
+  return FixedPoint::pow(x, y);
+#else
+  return pow(x, y);
+#endif
+}
+
+static inline SplashCoord splashDist(SplashCoord x0, SplashCoord y0,
+				     SplashCoord x1, SplashCoord y1) {
+  SplashCoord dx, dy;
+  dx = x1 - x0;
+  dy = y1 - y0;
+#if USE_FIXEDPOINT
+  // this handles the situation where dx*dx or dy*dy is too large to
+  // fit in the 16.16 fixed point format
+  SplashCoord dxa, dya;
+  dxa = splashAbs(dx);
+  dya = splashAbs(dy);
+  if (dxa == 0 && dya == 0) {
+    return 0;
+  } else if (dxa > dya) {
+    return dxa * FixedPoint::sqrt(dya / dxa + 1);
+  } else {
+    return dya * FixedPoint::sqrt(dxa / dya + 1);
+  }
+#else
+  return sqrt(dx * dx + dy * dy);
+#endif
+}
+
+#endif
diff --git a/xpdf/SplashOutputDev.cc b/xpdf/SplashOutputDev.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashOutputDev.cc
@@ -0,0 +1,2845 @@
+//========================================================================
+//
+// SplashOutputDev.cc
+//
+// Copyright 2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <string.h>
+#include <math.h>
+#include "gfile.h"
+#include "GlobalParams.h"
+#include "Error.h"
+#include "Object.h"
+#include "GfxFont.h"
+#include "Link.h"
+#include "CharCodeToUnicode.h"
+#include "FontEncodingTables.h"
+#include "FoFiTrueType.h"
+#include "SplashBitmap.h"
+#include "SplashGlyphBitmap.h"
+#include "SplashPattern.h"
+#include "SplashScreen.h"
+#include "SplashPath.h"
+#include "SplashState.h"
+#include "SplashErrorCodes.h"
+#include "SplashFontEngine.h"
+#include "SplashFont.h"
+#include "SplashFontFile.h"
+#include "SplashFontFileID.h"
+#include "Splash.h"
+#include "SplashOutputDev.h"
+
+#ifdef VMS
+#if (__VMS_VER < 70000000)
+extern "C" int unlink(char *filename);
+#endif
+#endif
+
+//------------------------------------------------------------------------
+
+// Divide a 16-bit value (in [0, 255*255]) by 255, returning an 8-bit result.
+static inline Guchar div255(int x) {
+  return (Guchar)((x + (x >> 8) + 0x80) >> 8);
+}
+
+//------------------------------------------------------------------------
+// Blend functions
+//------------------------------------------------------------------------
+
+static void splashOutBlendMultiply(SplashColorPtr src, SplashColorPtr dest,
+				   SplashColorPtr blend, SplashColorMode cm) {
+  int i;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    blend[i] = (dest[i] * src[i]) / 255;
+  }
+}
+
+static void splashOutBlendScreen(SplashColorPtr src, SplashColorPtr dest,
+				 SplashColorPtr blend, SplashColorMode cm) {
+  int i;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    blend[i] = dest[i] + src[i] - (dest[i] * src[i]) / 255;
+  }
+}
+
+static void splashOutBlendOverlay(SplashColorPtr src, SplashColorPtr dest,
+				  SplashColorPtr blend, SplashColorMode cm) {
+  int i;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    blend[i] = dest[i] < 0x80
+                 ? (src[i] * 2 * dest[i]) / 255
+                 : 255 - 2 * ((255 - src[i]) * (255 - dest[i])) / 255;
+  }
+}
+
+static void splashOutBlendDarken(SplashColorPtr src, SplashColorPtr dest,
+				 SplashColorPtr blend, SplashColorMode cm) {
+  int i;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    blend[i] = dest[i] < src[i] ? dest[i] : src[i];
+  }
+}
+
+static void splashOutBlendLighten(SplashColorPtr src, SplashColorPtr dest,
+				  SplashColorPtr blend, SplashColorMode cm) {
+  int i;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    blend[i] = dest[i] > src[i] ? dest[i] : src[i];
+  }
+}
+
+static void splashOutBlendColorDodge(SplashColorPtr src, SplashColorPtr dest,
+				     SplashColorPtr blend,
+				     SplashColorMode cm) {
+  int i, x;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    if (src[i] == 255) {
+      blend[i] = 255;
+    } else {
+      x = (dest[i] * 255) / (255 - src[i]);
+      blend[i] = x <= 255 ? x : 255;
+    }
+  }
+}
+
+static void splashOutBlendColorBurn(SplashColorPtr src, SplashColorPtr dest,
+				    SplashColorPtr blend, SplashColorMode cm) {
+  int i, x;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    if (src[i] == 0) {
+      blend[i] = 0;
+    } else {
+      x = ((255 - dest[i]) * 255) / src[i];
+      blend[i] = x <= 255 ? 255 - x : 0;
+    }
+  }
+}
+
+static void splashOutBlendHardLight(SplashColorPtr src, SplashColorPtr dest,
+				    SplashColorPtr blend, SplashColorMode cm) {
+  int i;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    blend[i] = src[i] < 0x80
+                 ? (dest[i] * 2 * src[i]) / 255
+                 : 255 - 2 * ((255 - dest[i]) * (255 - src[i])) / 255;
+  }
+}
+
+static void splashOutBlendSoftLight(SplashColorPtr src, SplashColorPtr dest,
+				    SplashColorPtr blend, SplashColorMode cm) {
+  int i, x;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    if (src[i] < 0x80) {
+      blend[i] = dest[i] - (255 - 2 * src[i]) * dest[i] * (255 - dest[i]) /
+	         (255 * 255);
+    } else {
+      if (dest[i] < 0x40) {
+	x = (((((16 * dest[i] - 12 * 255) * dest[i]) / 255)
+	      + 4 * 255) * dest[i]) / 255;
+      } else {
+	x = (int)sqrt(255.0 * dest[i]);
+      }
+      blend[i] = dest[i] + (2 * src[i] - 255) * (x - dest[i]) / 255;
+    }
+  }
+}
+
+static void splashOutBlendDifference(SplashColorPtr src, SplashColorPtr dest,
+				     SplashColorPtr blend,
+				     SplashColorMode cm) {
+  int i;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    blend[i] = dest[i] < src[i] ? src[i] - dest[i] : dest[i] - src[i];
+  }
+}
+
+static void splashOutBlendExclusion(SplashColorPtr src, SplashColorPtr dest,
+				    SplashColorPtr blend, SplashColorMode cm) {
+  int i;
+
+  for (i = 0; i < splashColorModeNComps[cm]; ++i) {
+    blend[i] = dest[i] + src[i] - (2 * dest[i] * src[i]) / 255;
+  }
+}
+
+static void cvtRGBToHSV(Guchar r, Guchar g, Guchar b, int *h, int *s, int *v) {
+  int cmax, cmid, cmin, x;
+
+  if (r >= g) {
+    if (g >= b)      { x = 0; cmax = r; cmid = g; cmin = b; }
+    else if (b >= r) { x = 4; cmax = b; cmid = r; cmin = g; }
+    else             { x = 5; cmax = r; cmid = b; cmin = g; }
+  } else {
+    if (r >= b)      { x = 1; cmax = g; cmid = r; cmin = b; }
+    else if (g >= b) { x = 2; cmax = g; cmid = b; cmin = r; }
+    else             { x = 3; cmax = b; cmid = g; cmin = r; }
+  }
+  if (cmax == cmin) {
+    *h = *s = 0;
+  } else {
+    *h = x * 60;
+    if (x & 1) {
+      *h += ((cmax - cmid) * 60) / (cmax - cmin);
+    } else {
+      *h += ((cmid - cmin) * 60) / (cmax - cmin);
+    }
+    *s = (255 * (cmax - cmin)) / cmax;
+  }
+  *v = cmax;
+}
+
+static void cvtHSVToRGB(int h, int s, int v, Guchar *r, Guchar *g, Guchar *b) {
+  int x, f, cmax, cmid, cmin;
+
+  if (s == 0) {
+    *r = *g = *b = v;
+  } else {
+    x = h / 60;
+    f = h % 60;
+    cmax = v;
+    if (x & 1) {
+      cmid = div255(v * 255 - ((s * f) / 60));
+    } else {
+      cmid = div255(v * (255 - ((s * (60 - f)) / 60)));
+    }
+    cmin = div255(v * (255 - s));
+    switch (x) {
+    case 0: *r = cmax; *g = cmid; *b = cmin; break;
+    case 1: *g = cmax; *r = cmid; *b = cmin; break;
+    case 2: *g = cmax; *b = cmid; *r = cmin; break;
+    case 3: *b = cmax; *g = cmid; *r = cmin; break;
+    case 4: *b = cmax; *r = cmid; *g = cmin; break;
+    case 5: *r = cmax; *b = cmid; *g = cmin; break;
+    }
+  }
+}
+
+static void splashOutBlendHue(SplashColorPtr src, SplashColorPtr dest,
+			      SplashColorPtr blend, SplashColorMode cm) {
+  int hs, ss, vs, hd, sd, vd;
+#if SPLASH_CMYK
+  Guchar r, g, b;
+#endif
+
+  switch (cm) {
+  case splashModeMono1:
+  case splashModeMono8:
+    blend[0] = dest[0];
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    cvtRGBToHSV(src[0], src[1], src[2], &hs, &ss, &vs);
+    cvtRGBToHSV(dest[0], dest[1], dest[2], &hd, &sd, &vd);
+    cvtHSVToRGB(hs, sd, vd, &blend[0], &blend[1], &blend[2]);
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    //~ (0xff - ...) should be clipped
+    cvtRGBToHSV(0xff - (src[0] + src[3]),
+		0xff - (src[1] + src[3]),
+		0xff - (src[2] + src[3]), &hs, &ss, &vs);
+    cvtRGBToHSV(0xff - (dest[0] + dest[3]),
+		0xff - (dest[1] + dest[3]),
+		0xff - (dest[2] + dest[3]), &hd, &sd, &vd);
+    cvtHSVToRGB(hs, sd, vd, &r, &g, &b);
+    //~ should do black generation
+    blend[0] = 0xff - r;
+    blend[1] = 0xff - g;
+    blend[2] = 0xff - b;
+    blend[3] = 0;
+    break;
+#endif
+  }
+}
+
+static void splashOutBlendSaturation(SplashColorPtr src, SplashColorPtr dest,
+				     SplashColorPtr blend,
+				     SplashColorMode cm) {
+  int hs, ss, vs, hd, sd, vd;
+#if SPLASH_CMYK
+  Guchar r, g, b;
+#endif
+
+  switch (cm) {
+  case splashModeMono1:
+  case splashModeMono8:
+    blend[0] = dest[0];
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    cvtRGBToHSV(src[0], src[1], src[2], &hs, &ss, &vs);
+    cvtRGBToHSV(dest[0], dest[1], dest[2], &hd, &sd, &vd);
+    cvtHSVToRGB(hd, ss, vd, &blend[0], &blend[1], &blend[2]);
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    //~ (0xff - ...) should be clipped
+    cvtRGBToHSV(0xff - (src[0] + src[3]),
+		0xff - (src[1] + src[3]),
+		0xff - (src[2] + src[3]), &hs, &ss, &vs);
+    cvtRGBToHSV(0xff - (dest[0] + dest[3]),
+		0xff - (dest[1] + dest[3]),
+		0xff - (dest[2] + dest[3]), &hd, &sd, &vd);
+    cvtHSVToRGB(hd, ss, vd, &r, &g, &b);
+    //~ should do black generation
+    blend[0] = 0xff - r;
+    blend[1] = 0xff - g;
+    blend[2] = 0xff - b;
+    blend[3] = 0;
+    break;
+#endif
+  }
+}
+
+static void splashOutBlendColor(SplashColorPtr src, SplashColorPtr dest,
+				SplashColorPtr blend, SplashColorMode cm) {
+  int hs, ss, vs, hd, sd, vd;
+#if SPLASH_CMYK
+  Guchar r, g, b;
+#endif
+
+  switch (cm) {
+  case splashModeMono1:
+  case splashModeMono8:
+    blend[0] = dest[0];
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    cvtRGBToHSV(src[0], src[1], src[2], &hs, &ss, &vs);
+    cvtRGBToHSV(dest[0], dest[1], dest[2], &hd, &sd, &vd);
+    cvtHSVToRGB(hs, ss, vd, &blend[0], &blend[1], &blend[2]);
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    //~ (0xff - ...) should be clipped
+    cvtRGBToHSV(0xff - (src[0] + src[3]),
+		0xff - (src[1] + src[3]),
+		0xff - (src[2] + src[3]), &hs, &ss, &vs);
+    cvtRGBToHSV(0xff - (dest[0] + dest[3]),
+		0xff - (dest[1] + dest[3]),
+		0xff - (dest[2] + dest[3]), &hd, &sd, &vd);
+    cvtHSVToRGB(hs, ss, vd, &r, &g, &b);
+    //~ should do black generation
+    blend[0] = 0xff - r;
+    blend[1] = 0xff - g;
+    blend[2] = 0xff - b;
+    blend[3] = 0;
+    break;
+#endif
+  }
+}
+
+static void splashOutBlendLuminosity(SplashColorPtr src, SplashColorPtr dest,
+				     SplashColorPtr blend,
+				     SplashColorMode cm) {
+  int hs, ss, vs, hd, sd, vd;
+#if SPLASH_CMYK
+  Guchar r, g, b;
+#endif
+
+  switch (cm) {
+  case splashModeMono1:
+  case splashModeMono8:
+    blend[0] = dest[0];
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    cvtRGBToHSV(src[0], src[1], src[2], &hs, &ss, &vs);
+    cvtRGBToHSV(dest[0], dest[1], dest[2], &hd, &sd, &vd);
+    cvtHSVToRGB(hd, sd, vs, &blend[0], &blend[1], &blend[2]);
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    //~ (0xff - ...) should be clipped
+    cvtRGBToHSV(0xff - (src[0] + src[3]),
+		0xff - (src[1] + src[3]),
+		0xff - (src[2] + src[3]), &hs, &ss, &vs);
+    cvtRGBToHSV(0xff - (dest[0] + dest[3]),
+		0xff - (dest[1] + dest[3]),
+		0xff - (dest[2] + dest[3]), &hd, &sd, &vd);
+    cvtHSVToRGB(hd, sd, vs, &r, &g, &b);
+    //~ should do black generation
+    blend[0] = 0xff - r;
+    blend[1] = 0xff - g;
+    blend[2] = 0xff - b;
+    blend[3] = 0;
+    break;
+#endif
+  }
+}
+
+// NB: This must match the GfxBlendMode enum defined in GfxState.h.
+SplashBlendFunc splashOutBlendFuncs[] = {
+  NULL,
+  &splashOutBlendMultiply,
+  &splashOutBlendScreen,
+  &splashOutBlendOverlay,
+  &splashOutBlendDarken,
+  &splashOutBlendLighten,
+  &splashOutBlendColorDodge,
+  &splashOutBlendColorBurn,
+  &splashOutBlendHardLight,
+  &splashOutBlendSoftLight,
+  &splashOutBlendDifference,
+  &splashOutBlendExclusion,
+  &splashOutBlendHue,
+  &splashOutBlendSaturation,
+  &splashOutBlendColor,
+  &splashOutBlendLuminosity
+};
+
+//------------------------------------------------------------------------
+// Font substitutions
+//------------------------------------------------------------------------
+
+struct SplashOutFontSubst {
+  char *name;
+  double mWidth;
+};
+
+// index: {symbolic:12, fixed:8, serif:4, sans-serif:0} + bold*2 + italic
+static SplashOutFontSubst splashOutSubstFonts[16] = {
+  {"Helvetica",             0.833},
+  {"Helvetica-Oblique",     0.833},
+  {"Helvetica-Bold",        0.889},
+  {"Helvetica-BoldOblique", 0.889},
+  {"Times-Roman",           0.788},
+  {"Times-Italic",          0.722},
+  {"Times-Bold",            0.833},
+  {"Times-BoldItalic",      0.778},
+  {"Courier",               0.600},
+  {"Courier-Oblique",       0.600},
+  {"Courier-Bold",          0.600},
+  {"Courier-BoldOblique",   0.600},
+  {"Symbol",                0.576},
+  {"Symbol",                0.576},
+  {"Symbol",                0.576},
+  {"Symbol",                0.576}
+};
+
+//------------------------------------------------------------------------
+// SplashOutFontFileID
+//------------------------------------------------------------------------
+
+class SplashOutFontFileID: public SplashFontFileID {
+public:
+
+  SplashOutFontFileID(Ref *rA) { r = *rA; substIdx = -1; }
+
+  ~SplashOutFontFileID() {}
+
+  GBool matches(SplashFontFileID *id) {
+    return ((SplashOutFontFileID *)id)->r.num == r.num &&
+           ((SplashOutFontFileID *)id)->r.gen == r.gen;
+  }
+
+  void setSubstIdx(int substIdxA) { substIdx = substIdxA; }
+  int getSubstIdx() { return substIdx; }
+
+private:
+
+  Ref r;
+  int substIdx;
+};
+
+//------------------------------------------------------------------------
+// T3FontCache
+//------------------------------------------------------------------------
+
+struct T3FontCacheTag {
+  Gushort code;
+  Gushort mru;			// valid bit (0x8000) and MRU index
+};
+
+class T3FontCache {
+public:
+
+  T3FontCache(Ref *fontID, double m11A, double m12A,
+	      double m21A, double m22A,
+	      int glyphXA, int glyphYA, int glyphWA, int glyphHA,
+	      GBool aa, GBool validBBoxA);
+  ~T3FontCache();
+  GBool matches(Ref *idA, double m11A, double m12A,
+		double m21A, double m22A)
+    { return fontID.num == idA->num && fontID.gen == idA->gen &&
+	     m11 == m11A && m12 == m12A && m21 == m21A && m22 == m22A; }
+
+  Ref fontID;			// PDF font ID
+  double m11, m12, m21, m22;	// transform matrix
+  int glyphX, glyphY;		// pixel offset of glyph bitmaps
+  int glyphW, glyphH;		// size of glyph bitmaps, in pixels
+  GBool validBBox;		// false if the bbox was [0 0 0 0]
+  int glyphSize;		// size of glyph bitmaps, in bytes
+  int cacheSets;		// number of sets in cache
+  int cacheAssoc;		// cache associativity (glyphs per set)
+  Guchar *cacheData;		// glyph pixmap cache
+  T3FontCacheTag *cacheTags;	// cache tags, i.e., char codes
+};
+
+T3FontCache::T3FontCache(Ref *fontIDA, double m11A, double m12A,
+			 double m21A, double m22A,
+			 int glyphXA, int glyphYA, int glyphWA, int glyphHA,
+			 GBool validBBoxA, GBool aa) {
+  int i;
+
+  fontID = *fontIDA;
+  m11 = m11A;
+  m12 = m12A;
+  m21 = m21A;
+  m22 = m22A;
+  glyphX = glyphXA;
+  glyphY = glyphYA;
+  glyphW = glyphWA;
+  glyphH = glyphHA;
+  validBBox = validBBoxA;
+  if (aa) {
+    glyphSize = glyphW * glyphH;
+  } else {
+    glyphSize = ((glyphW + 7) >> 3) * glyphH;
+  }
+  cacheAssoc = 8;
+  if (glyphSize <= 256) {
+    cacheSets = 8;
+  } else if (glyphSize <= 512) {
+    cacheSets = 4;
+  } else if (glyphSize <= 1024) {
+    cacheSets = 2;
+  } else {
+    cacheSets = 1;
+  }
+  cacheData = (Guchar *)gmallocn(cacheSets * cacheAssoc, glyphSize);
+  cacheTags = (T3FontCacheTag *)gmallocn(cacheSets * cacheAssoc,
+					 sizeof(T3FontCacheTag));
+  for (i = 0; i < cacheSets * cacheAssoc; ++i) {
+    cacheTags[i].mru = i & (cacheAssoc - 1);
+  }
+}
+
+T3FontCache::~T3FontCache() {
+  gfree(cacheData);
+  gfree(cacheTags);
+}
+
+struct T3GlyphStack {
+  Gushort code;			// character code
+
+  //----- cache info
+  T3FontCache *cache;		// font cache for the current font
+  T3FontCacheTag *cacheTag;	// pointer to cache tag for the glyph
+  Guchar *cacheData;		// pointer to cache data for the glyph
+
+  //----- saved state
+  SplashBitmap *origBitmap;
+  Splash *origSplash;
+  double origCTM4, origCTM5;
+
+  T3GlyphStack *next;		// next object on stack
+};
+
+//------------------------------------------------------------------------
+// SplashTransparencyGroup
+//------------------------------------------------------------------------
+
+struct SplashTransparencyGroup {
+  int tx, ty;			// translation coordinates
+  SplashBitmap *tBitmap;	// bitmap for transparency group
+  GfxColorSpace *blendingColorSpace;
+  GBool isolated;
+
+  //----- saved state
+  SplashBitmap *origBitmap;
+  Splash *origSplash;
+
+  SplashTransparencyGroup *next;
+};
+
+//------------------------------------------------------------------------
+// SplashOutputDev
+//------------------------------------------------------------------------
+
+SplashOutputDev::SplashOutputDev(SplashColorMode colorModeA,
+				 int bitmapRowPadA,
+				 GBool reverseVideoA,
+				 SplashColorPtr paperColorA,
+				 GBool bitmapTopDownA,
+				 GBool allowAntialiasA) {
+  colorMode = colorModeA;
+  bitmapRowPad = bitmapRowPadA;
+  bitmapTopDown = bitmapTopDownA;
+  allowAntialias = allowAntialiasA;
+  vectorAntialias = allowAntialias &&
+		      globalParams->getVectorAntialias() &&
+		      colorMode != splashModeMono1;
+  setupScreenParams(72.0, 72.0);
+  reverseVideo = reverseVideoA;
+  splashColorCopy(paperColor, paperColorA);
+
+  xref = NULL;
+
+  bitmap = new SplashBitmap(1, 1, bitmapRowPad, colorMode,
+			    colorMode != splashModeMono1, bitmapTopDown);
+  splash = new Splash(bitmap, vectorAntialias, &screenParams);
+  splash->clear(paperColor, 0);
+
+  fontEngine = NULL;
+
+  nT3Fonts = 0;
+  t3GlyphStack = NULL;
+
+  font = NULL;
+  needFontUpdate = gFalse;
+  textClipPath = NULL;
+
+  transpGroupStack = NULL;
+}
+
+void SplashOutputDev::setupScreenParams(double hDPI, double vDPI) {
+  screenParams.size = globalParams->getScreenSize();
+  screenParams.dotRadius = globalParams->getScreenDotRadius();
+  screenParams.gamma = (SplashCoord)globalParams->getScreenGamma();
+  screenParams.blackThreshold =
+      (SplashCoord)globalParams->getScreenBlackThreshold();
+  screenParams.whiteThreshold =
+      (SplashCoord)globalParams->getScreenWhiteThreshold();
+  switch (globalParams->getScreenType()) {
+  case screenDispersed:
+    screenParams.type = splashScreenDispersed;
+    if (screenParams.size < 0) {
+      screenParams.size = 4;
+    }
+    break;
+  case screenClustered:
+    screenParams.type = splashScreenClustered;
+    if (screenParams.size < 0) {
+      screenParams.size = 10;
+    }
+    break;
+  case screenStochasticClustered:
+    screenParams.type = splashScreenStochasticClustered;
+    if (screenParams.size < 0) {
+      screenParams.size = 100;
+    }
+    if (screenParams.dotRadius < 0) {
+      screenParams.dotRadius = 2;
+    }
+    break;
+  case screenUnset:
+  default:
+    // use clustered dithering for resolution >= 300 dpi
+    // (compare to 299.9 to avoid floating point issues)
+    if (hDPI > 299.9 && vDPI > 299.9) {
+      screenParams.type = splashScreenStochasticClustered;
+      if (screenParams.size < 0) {
+	screenParams.size = 100;
+      }
+      if (screenParams.dotRadius < 0) {
+	screenParams.dotRadius = 2;
+      }
+    } else {
+      screenParams.type = splashScreenDispersed;
+      if (screenParams.size < 0) {
+	screenParams.size = 4;
+      }
+    }
+  }
+}
+
+SplashOutputDev::~SplashOutputDev() {
+  int i;
+
+  for (i = 0; i < nT3Fonts; ++i) {
+    delete t3FontCache[i];
+  }
+  if (fontEngine) {
+    delete fontEngine;
+  }
+  if (splash) {
+    delete splash;
+  }
+  if (bitmap) {
+    delete bitmap;
+  }
+}
+
+void SplashOutputDev::startDoc(XRef *xrefA) {
+  int i;
+
+  xref = xrefA;
+  if (fontEngine) {
+    delete fontEngine;
+  }
+  fontEngine = new SplashFontEngine(
+#if HAVE_T1LIB_H
+				    globalParams->getEnableT1lib(),
+#endif
+#if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H
+				    globalParams->getEnableFreeType(),
+#endif
+				    allowAntialias &&
+				      globalParams->getAntialias() &&
+				      colorMode != splashModeMono1);
+  for (i = 0; i < nT3Fonts; ++i) {
+    delete t3FontCache[i];
+  }
+  nT3Fonts = 0;
+}
+
+void SplashOutputDev::startPage(int pageNum, GfxState *state) {
+  int w, h;
+  double *ctm;
+  SplashCoord mat[6];
+  SplashColor color;
+
+  if (state) {
+    setupScreenParams(state->getHDPI(), state->getVDPI());
+    w = (int)(state->getPageWidth() + 0.5);
+    if (w <= 0) {
+      w = 1;
+    }
+    h = (int)(state->getPageHeight() + 0.5);
+    if (h <= 0) {
+      h = 1;
+    }
+  } else {
+    w = h = 1;
+  }
+  if (splash) {
+    delete splash;
+  }
+  if (!bitmap || w != bitmap->getWidth() || h != bitmap->getHeight()) {
+    if (bitmap) {
+      delete bitmap;
+    }
+    bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode,
+			      colorMode != splashModeMono1, bitmapTopDown);
+  }
+  splash = new Splash(bitmap, vectorAntialias, &screenParams);
+  if (state) {
+    ctm = state->getCTM();
+    mat[0] = (SplashCoord)ctm[0];
+    mat[1] = (SplashCoord)ctm[1];
+    mat[2] = (SplashCoord)ctm[2];
+    mat[3] = (SplashCoord)ctm[3];
+    mat[4] = (SplashCoord)ctm[4];
+    mat[5] = (SplashCoord)ctm[5];
+    splash->setMatrix(mat);
+  }
+  switch (colorMode) {
+  case splashModeMono1:
+  case splashModeMono8:
+    color[0] = 0;
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    color[0] = color[1] = color[2] = 0;
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    color[0] = color[1] = color[2] = color[3] = 0;
+    break;
+#endif
+  }
+  splash->setStrokePattern(new SplashSolidColor(color));
+  splash->setFillPattern(new SplashSolidColor(color));
+  splash->setLineCap(splashLineCapButt);
+  splash->setLineJoin(splashLineJoinMiter);
+  splash->setLineDash(NULL, 0, 0);
+  splash->setMiterLimit(10);
+  splash->setFlatness(1);
+  // the SA parameter supposedly defaults to false, but Acrobat
+  // apparently hardwires it to true
+  splash->setStrokeAdjust(globalParams->getStrokeAdjust());
+  splash->clear(paperColor, 0);
+}
+
+void SplashOutputDev::endPage() {
+  if (colorMode != splashModeMono1) {
+    splash->compositeBackground(paperColor);
+  }
+}
+
+void SplashOutputDev::saveState(GfxState *state) {
+  splash->saveState();
+}
+
+void SplashOutputDev::restoreState(GfxState *state) {
+  splash->restoreState();
+  needFontUpdate = gTrue;
+}
+
+void SplashOutputDev::updateAll(GfxState *state) {
+  updateLineDash(state);
+  updateLineJoin(state);
+  updateLineCap(state);
+  updateLineWidth(state);
+  updateFlatness(state);
+  updateMiterLimit(state);
+  updateStrokeAdjust(state);
+  updateFillColor(state);
+  updateStrokeColor(state);
+  needFontUpdate = gTrue;
+}
+
+void SplashOutputDev::updateCTM(GfxState *state, double m11, double m12,
+				double m21, double m22,
+				double m31, double m32) {
+  double *ctm;
+  SplashCoord mat[6];
+
+  ctm = state->getCTM();
+  mat[0] = (SplashCoord)ctm[0];
+  mat[1] = (SplashCoord)ctm[1];
+  mat[2] = (SplashCoord)ctm[2];
+  mat[3] = (SplashCoord)ctm[3];
+  mat[4] = (SplashCoord)ctm[4];
+  mat[5] = (SplashCoord)ctm[5];
+  splash->setMatrix(mat);
+}
+
+void SplashOutputDev::updateLineDash(GfxState *state) {
+  double *dashPattern;
+  int dashLength;
+  double dashStart;
+  SplashCoord dash[20];
+  int i;
+
+  state->getLineDash(&dashPattern, &dashLength, &dashStart);
+  if (dashLength > 20) {
+    dashLength = 20;
+  }
+  for (i = 0; i < dashLength; ++i) {
+    dash[i] = (SplashCoord)dashPattern[i];
+    if (dash[i] < 0) {
+      dash[i] = 0;
+    }
+  }
+  splash->setLineDash(dash, dashLength, (SplashCoord)dashStart);
+}
+
+void SplashOutputDev::updateFlatness(GfxState *state) {
+  splash->setFlatness(state->getFlatness());
+}
+
+void SplashOutputDev::updateLineJoin(GfxState *state) {
+  splash->setLineJoin(state->getLineJoin());
+}
+
+void SplashOutputDev::updateLineCap(GfxState *state) {
+  splash->setLineCap(state->getLineCap());
+}
+
+void SplashOutputDev::updateMiterLimit(GfxState *state) {
+  splash->setMiterLimit(state->getMiterLimit());
+}
+
+void SplashOutputDev::updateLineWidth(GfxState *state) {
+  splash->setLineWidth(state->getLineWidth());
+}
+
+void SplashOutputDev::updateStrokeAdjust(GfxState *state) {
+#if 0 // the SA parameter supposedly defaults to false, but Acrobat
+      // apparently hardwires it to true
+  splash->setStrokeAdjust(state->getStrokeAdjust());
+#endif
+}
+
+void SplashOutputDev::updateFillColor(GfxState *state) {
+  GfxGray gray;
+  GfxRGB rgb;
+#if SPLASH_CMYK
+  GfxCMYK cmyk;
+#endif
+
+  state->getFillGray(&gray);
+  state->getFillRGB(&rgb);
+#if SPLASH_CMYK
+  state->getFillCMYK(&cmyk);
+  splash->setFillPattern(getColor(gray, &rgb, &cmyk));
+#else
+  splash->setFillPattern(getColor(gray, &rgb));
+#endif
+}
+
+void SplashOutputDev::updateStrokeColor(GfxState *state) {
+  GfxGray gray;
+  GfxRGB rgb;
+#if SPLASH_CMYK
+  GfxCMYK cmyk;
+#endif
+
+  state->getStrokeGray(&gray);
+  state->getStrokeRGB(&rgb);
+#if SPLASH_CMYK
+  state->getStrokeCMYK(&cmyk);
+  splash->setStrokePattern(getColor(gray, &rgb, &cmyk));
+#else
+  splash->setStrokePattern(getColor(gray, &rgb));
+#endif
+}
+
+#if SPLASH_CMYK
+SplashPattern *SplashOutputDev::getColor(GfxGray gray, GfxRGB *rgb,
+					 GfxCMYK *cmyk) {
+#else
+SplashPattern *SplashOutputDev::getColor(GfxGray gray, GfxRGB *rgb) {
+#endif
+  SplashPattern *pattern;
+  SplashColor color;
+  GfxColorComp r, g, b;
+
+  if (reverseVideo) {
+    gray = gfxColorComp1 - gray;
+    r = gfxColorComp1 - rgb->r;
+    g = gfxColorComp1 - rgb->g;
+    b = gfxColorComp1 - rgb->b;
+  } else {
+    r = rgb->r;
+    g = rgb->g;
+    b = rgb->b;
+  }
+
+  pattern = NULL; // make gcc happy
+  switch (colorMode) {
+  case splashModeMono1:
+  case splashModeMono8:
+    color[0] = colToByte(gray);
+    pattern = new SplashSolidColor(color);
+    break;
+  case splashModeRGB8:
+  case splashModeBGR8:
+    color[0] = colToByte(r);
+    color[1] = colToByte(g);
+    color[2] = colToByte(b);
+    pattern = new SplashSolidColor(color);
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    color[0] = colToByte(cmyk->c);
+    color[1] = colToByte(cmyk->m);
+    color[2] = colToByte(cmyk->y);
+    color[3] = colToByte(cmyk->k);
+    pattern = new SplashSolidColor(color);
+    break;
+#endif
+  }
+
+  return pattern;
+}
+
+void SplashOutputDev::updateBlendMode(GfxState *state) {
+  splash->setBlendFunc(splashOutBlendFuncs[state->getBlendMode()]);
+}
+
+void SplashOutputDev::updateFillOpacity(GfxState *state) {
+  splash->setFillAlpha((SplashCoord)state->getFillOpacity());
+}
+
+void SplashOutputDev::updateStrokeOpacity(GfxState *state) {
+  splash->setStrokeAlpha((SplashCoord)state->getStrokeOpacity());
+}
+
+void SplashOutputDev::updateFont(GfxState *state) {
+  needFontUpdate = gTrue;
+}
+
+void SplashOutputDev::doUpdateFont(GfxState *state) {
+  GfxFont *gfxFont;
+  GfxFontType fontType;
+  SplashOutFontFileID *id;
+  SplashFontFile *fontFile;
+  FoFiTrueType *ff;
+  Ref embRef;
+  Object refObj, strObj;
+  GString *tmpFileName, *fileName, *substName;
+  FILE *tmpFile;
+  Gushort *codeToGID;
+  DisplayFontParam *dfp;
+  CharCodeToUnicode *ctu;
+  double *textMat;
+  double m11, m12, m21, m22, w1, w2, fontSize;
+  SplashCoord mat[4];
+  char *name;
+  Unicode uBuf[8];
+  int c, substIdx, n, code, cmap;
+
+  needFontUpdate = gFalse;
+  font = NULL;
+  tmpFileName = NULL;
+  substIdx = -1;
+  dfp = NULL;
+
+  if (!(gfxFont = state->getFont())) {
+    goto err1;
+  }
+  fontType = gfxFont->getType();
+  if (fontType == fontType3) {
+    goto err1;
+  }
+
+  // check the font file cache
+  id = new SplashOutFontFileID(gfxFont->getID());
+  if ((fontFile = fontEngine->getFontFile(id))) {
+    delete id;
+
+  } else {
+
+    // if there is an embedded font, write it to disk
+    if (gfxFont->getEmbeddedFontID(&embRef)) {
+      if (!openTempFile(&tmpFileName, &tmpFile, "wb", NULL)) {
+	error(-1, "Couldn't create temporary font file");
+	goto err2;
+      }
+      refObj.initRef(embRef.num, embRef.gen);
+      refObj.fetch(xref, &strObj);
+      refObj.free();
+      if (!strObj.isStream()) {
+	error(-1, "Embedded font object is wrong type");
+	strObj.free();
+	fclose(tmpFile);
+	goto err2;
+      }
+      strObj.streamReset();
+      while ((c = strObj.streamGetChar()) != EOF) {
+	fputc(c, tmpFile);
+      }
+      strObj.streamClose();
+      strObj.free();
+      fclose(tmpFile);
+      fileName = tmpFileName;
+
+    // if there is an external font file, use it
+    } else if (!(fileName = gfxFont->getExtFontFile())) {
+
+      // look for a display font mapping or a substitute font
+      if (gfxFont->isCIDFont()) {
+	if (((GfxCIDFont *)gfxFont)->getCollection()) {
+	  dfp = globalParams->
+	          getDisplayCIDFont(gfxFont->getName(),
+				    ((GfxCIDFont *)gfxFont)->getCollection());
+	}
+      } else {
+	if (gfxFont->getName()) {
+	  dfp = globalParams->getDisplayFont(gfxFont->getName());
+	}
+	if (!dfp) {
+	  // 8-bit font substitution
+	  if (gfxFont->isFixedWidth()) {
+	    substIdx = 8;
+	  } else if (gfxFont->isSerif()) {
+	    substIdx = 4;
+	  } else {
+	    substIdx = 0;
+	  }
+	  if (gfxFont->isBold()) {
+	    substIdx += 2;
+	  }
+	  if (gfxFont->isItalic()) {
+	    substIdx += 1;
+	  }
+	  substName = new GString(splashOutSubstFonts[substIdx].name);
+	  dfp = globalParams->getDisplayFont(substName);
+	  delete substName;
+	  id->setSubstIdx(substIdx);
+	}
+      }
+      if (!dfp) {
+	error(-1, "Couldn't find a font for '%s'",
+	      gfxFont->getName() ? gfxFont->getName()->getCString()
+	                         : "(unnamed)");
+	goto err2;
+      }
+      switch (dfp->kind) {
+      case displayFontT1:
+	fileName = dfp->t1.fileName;
+	fontType = gfxFont->isCIDFont() ? fontCIDType0 : fontType1;
+	break;
+      case displayFontTT:
+	fileName = dfp->tt.fileName;
+	fontType = gfxFont->isCIDFont() ? fontCIDType2 : fontTrueType;
+	break;
+      }
+    }
+
+    // load the font file
+    switch (fontType) {
+    case fontType1:
+      if (!(fontFile = fontEngine->loadType1Font(
+			   id,
+			   fileName->getCString(),
+			   fileName == tmpFileName,
+			   ((Gfx8BitFont *)gfxFont)->getEncoding()))) {
+	error(-1, "Couldn't create a font for '%s'",
+	      gfxFont->getName() ? gfxFont->getName()->getCString()
+	                         : "(unnamed)");
+	goto err2;
+      }
+      break;
+    case fontType1C:
+      if (!(fontFile = fontEngine->loadType1CFont(
+			   id,
+			   fileName->getCString(),
+			   fileName == tmpFileName,
+			   ((Gfx8BitFont *)gfxFont)->getEncoding()))) {
+	error(-1, "Couldn't create a font for '%s'",
+	      gfxFont->getName() ? gfxFont->getName()->getCString()
+	                         : "(unnamed)");
+	goto err2;
+      }
+      break;
+    case fontType1COT:
+      if (!(fontFile = fontEngine->loadOpenTypeT1CFont(
+			   id,
+			   fileName->getCString(),
+			   fileName == tmpFileName,
+			   ((Gfx8BitFont *)gfxFont)->getEncoding()))) {
+	error(-1, "Couldn't create a font for '%s'",
+	      gfxFont->getName() ? gfxFont->getName()->getCString()
+	                         : "(unnamed)");
+	goto err2;
+      }
+      break;
+    case fontTrueType:
+    case fontTrueTypeOT:
+      if ((ff = FoFiTrueType::load(fileName->getCString()))) {
+	codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff);
+	n = 256;
+	delete ff;
+      } else {
+	codeToGID = NULL;
+	n = 0;
+      }
+      if (!(fontFile = fontEngine->loadTrueTypeFont(
+			   id,
+			   fileName->getCString(),
+			   fileName == tmpFileName,
+			   codeToGID, n))) {
+	error(-1, "Couldn't create a font for '%s'",
+	      gfxFont->getName() ? gfxFont->getName()->getCString()
+	                         : "(unnamed)");
+	goto err2;
+      }
+      break;
+    case fontCIDType0:
+    case fontCIDType0C:
+      if (!(fontFile = fontEngine->loadCIDFont(
+			   id,
+			   fileName->getCString(),
+			   fileName == tmpFileName))) {
+	error(-1, "Couldn't create a font for '%s'",
+	      gfxFont->getName() ? gfxFont->getName()->getCString()
+	                         : "(unnamed)");
+	goto err2;
+      }
+      break;
+    case fontCIDType0COT:
+      if (!(fontFile = fontEngine->loadOpenTypeCFFFont(
+			   id,
+			   fileName->getCString(),
+			   fileName == tmpFileName))) {
+	error(-1, "Couldn't create a font for '%s'",
+	      gfxFont->getName() ? gfxFont->getName()->getCString()
+	                         : "(unnamed)");
+	goto err2;
+      }
+      break;
+    case fontCIDType2:
+    case fontCIDType2OT:
+      codeToGID = NULL;
+      n = 0;
+      if (dfp) {
+	// create a CID-to-GID mapping, via Unicode
+	if ((ctu = ((GfxCIDFont *)gfxFont)->getToUnicode())) {
+	  if ((ff = FoFiTrueType::load(fileName->getCString()))) {
+	    // look for a Unicode cmap
+	    for (cmap = 0; cmap < ff->getNumCmaps(); ++cmap) {
+	      if ((ff->getCmapPlatform(cmap) == 3 &&
+		   ff->getCmapEncoding(cmap) == 1) ||
+		  ff->getCmapPlatform(cmap) == 0) {
+		break;
+	      }
+	    }
+	    if (cmap < ff->getNumCmaps()) {
+	      // map CID -> Unicode -> GID
+	      n = ctu->getLength();
+	      codeToGID = (Gushort *)gmallocn(n, sizeof(Gushort));
+	      for (code = 0; code < n; ++code) {
+		if (ctu->mapToUnicode(code, uBuf, 8) > 0) {
+		  codeToGID[code] = ff->mapCodeToGID(cmap, uBuf[0]);
+		} else {
+		  codeToGID[code] = 0;
+		}
+	      }
+	    }
+	    delete ff;
+	  }
+	  ctu->decRefCnt();
+	} else {
+	  error(-1, "Couldn't find a mapping to Unicode for font '%s'",
+		gfxFont->getName() ? gfxFont->getName()->getCString()
+		                   : "(unnamed)");
+	}
+      } else {
+	if (((GfxCIDFont *)gfxFont)->getCIDToGID()) {
+	  n = ((GfxCIDFont *)gfxFont)->getCIDToGIDLen();
+	  codeToGID = (Gushort *)gmallocn(n, sizeof(Gushort));
+	  memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(),
+		 n * sizeof(Gushort));
+	}
+      }
+      if (!(fontFile = fontEngine->loadTrueTypeFont(
+			   id,
+			   fileName->getCString(),
+			   fileName == tmpFileName,
+			   codeToGID, n))) {
+	error(-1, "Couldn't create a font for '%s'",
+	      gfxFont->getName() ? gfxFont->getName()->getCString()
+	                         : "(unnamed)");
+	goto err2;
+      }
+      break;
+    default:
+      // this shouldn't happen
+      goto err2;
+    }
+  }
+
+  // get the font matrix
+  textMat = state->getTextMat();
+  fontSize = state->getFontSize();
+  m11 = textMat[0] * fontSize * state->getHorizScaling();
+  m12 = textMat[1] * fontSize * state->getHorizScaling();
+  m21 = textMat[2] * fontSize;
+  m22 = textMat[3] * fontSize;
+
+  // for substituted fonts: adjust the font matrix -- compare the
+  // width of 'm' in the original font and the substituted font
+  substIdx = ((SplashOutFontFileID *)fontFile->getID())->getSubstIdx();
+  if (substIdx >= 0) {
+    for (code = 0; code < 256; ++code) {
+      if ((name = ((Gfx8BitFont *)gfxFont)->getCharName(code)) &&
+	  name[0] == 'm' && name[1] == '\0') {
+	break;
+      }
+    }
+    if (code < 256) {
+      w1 = ((Gfx8BitFont *)gfxFont)->getWidth(code);
+      w2 = splashOutSubstFonts[substIdx].mWidth;
+      if (!gfxFont->isSymbolic()) {
+	// if real font is substantially narrower than substituted
+	// font, reduce the font size accordingly
+	if (w1 > 0.01 && w1 < 0.9 * w2) {
+	  w1 /= w2;
+	  m11 *= w1;
+	  m21 *= w1;
+	}
+      }
+    }
+  }
+
+  // create the scaled font
+  mat[0] = m11;  mat[1] = m12;
+  mat[2] = m21;  mat[3] = m22;
+  font = fontEngine->getFont(fontFile, mat, splash->getMatrix());
+
+  if (tmpFileName) {
+    delete tmpFileName;
+  }
+  return;
+
+ err2:
+  delete id;
+ err1:
+  if (tmpFileName) {
+    unlink(tmpFileName->getCString());
+    delete tmpFileName;
+  }
+  return;
+}
+
+void SplashOutputDev::stroke(GfxState *state) {
+  SplashPath *path;
+
+  if (state->getStrokeColorSpace()->isNonMarking()) {
+    return;
+  }
+  path = convertPath(state, state->getPath());
+  splash->stroke(path);
+  delete path;
+}
+
+void SplashOutputDev::fill(GfxState *state) {
+  SplashPath *path;
+
+  if (state->getFillColorSpace()->isNonMarking()) {
+    return;
+  }
+  path = convertPath(state, state->getPath());
+  splash->fill(path, gFalse);
+  delete path;
+}
+
+void SplashOutputDev::eoFill(GfxState *state) {
+  SplashPath *path;
+
+  if (state->getFillColorSpace()->isNonMarking()) {
+    return;
+  }
+  path = convertPath(state, state->getPath());
+  splash->fill(path, gTrue);
+  delete path;
+}
+
+void SplashOutputDev::clip(GfxState *state) {
+  SplashPath *path;
+
+  path = convertPath(state, state->getPath());
+  splash->clipToPath(path, gFalse);
+  delete path;
+}
+
+void SplashOutputDev::eoClip(GfxState *state) {
+  SplashPath *path;
+
+  path = convertPath(state, state->getPath());
+  splash->clipToPath(path, gTrue);
+  delete path;
+}
+
+void SplashOutputDev::clipToStrokePath(GfxState *state) {
+  SplashPath *path, *path2;
+
+  path = convertPath(state, state->getPath());
+  path2 = splash->makeStrokePath(path);
+  delete path;
+  splash->clipToPath(path2, gFalse);
+  delete path2;
+}
+
+SplashPath *SplashOutputDev::convertPath(GfxState *state, GfxPath *path) {
+  SplashPath *sPath;
+  GfxSubpath *subpath;
+  int i, j;
+
+  sPath = new SplashPath();
+  for (i = 0; i < path->getNumSubpaths(); ++i) {
+    subpath = path->getSubpath(i);
+    if (subpath->getNumPoints() > 0) {
+      sPath->moveTo((SplashCoord)subpath->getX(0),
+		    (SplashCoord)subpath->getY(0));
+      j = 1;
+      while (j < subpath->getNumPoints()) {
+	if (subpath->getCurve(j)) {
+	  sPath->curveTo((SplashCoord)subpath->getX(j),
+			 (SplashCoord)subpath->getY(j),
+			 (SplashCoord)subpath->getX(j+1),
+			 (SplashCoord)subpath->getY(j+1),
+			 (SplashCoord)subpath->getX(j+2),
+			 (SplashCoord)subpath->getY(j+2));
+	  j += 3;
+	} else {
+	  sPath->lineTo((SplashCoord)subpath->getX(j),
+			(SplashCoord)subpath->getY(j));
+	  ++j;
+	}
+      }
+      if (subpath->isClosed()) {
+	sPath->close();
+      }
+    }
+  }
+  return sPath;
+}
+
+void SplashOutputDev::drawChar(GfxState *state, double x, double y,
+			       double dx, double dy,
+			       double originX, double originY,
+			       CharCode code, int nBytes,
+			       Unicode *u, int uLen) {
+  SplashPath *path;
+  int render;
+
+  // check for invisible text -- this is used by Acrobat Capture
+  render = state->getRender();
+  if (render == 3) {
+    return;
+  }
+
+  if (needFontUpdate) {
+    doUpdateFont(state);
+  }
+  if (!font) {
+    return;
+  }
+
+  x -= originX;
+  y -= originY;
+
+  // fill
+  if (!(render & 1)) {
+    if (!state->getFillColorSpace()->isNonMarking()) {
+      splash->fillChar((SplashCoord)x, (SplashCoord)y, code, font);
+    }
+  }
+
+  // stroke
+  if ((render & 3) == 1 || (render & 3) == 2) {
+    if (!state->getStrokeColorSpace()->isNonMarking()) {
+      if ((path = font->getGlyphPath(code))) {
+	path->offset((SplashCoord)x, (SplashCoord)y);
+	splash->stroke(path);
+	delete path;
+      }
+    }
+  }
+
+  // clip
+  if (render & 4) {
+    if ((path = font->getGlyphPath(code))) {
+      path->offset((SplashCoord)x, (SplashCoord)y);
+      if (textClipPath) {
+	textClipPath->append(path);
+	delete path;
+      } else {
+	textClipPath = path;
+      }
+    }
+  }
+}
+
+GBool SplashOutputDev::beginType3Char(GfxState *state, double x, double y,
+				      double dx, double dy,
+				      CharCode code, Unicode *u, int uLen) {
+  GfxFont *gfxFont;
+  Ref *fontID;
+  double *ctm, *bbox;
+  T3FontCache *t3Font;
+  T3GlyphStack *t3gs;
+  GBool validBBox;
+  double x1, y1, xMin, yMin, xMax, yMax, xt, yt;
+  int i, j;
+
+  if (!(gfxFont = state->getFont())) {
+    return gFalse;
+  }
+  fontID = gfxFont->getID();
+  ctm = state->getCTM();
+  state->transform(0, 0, &xt, &yt);
+
+  // is it the first (MRU) font in the cache?
+  if (!(nT3Fonts > 0 &&
+	t3FontCache[0]->matches(fontID, ctm[0], ctm[1], ctm[2], ctm[3]))) {
+
+    // is the font elsewhere in the cache?
+    for (i = 1; i < nT3Fonts; ++i) {
+      if (t3FontCache[i]->matches(fontID, ctm[0], ctm[1], ctm[2], ctm[3])) {
+	t3Font = t3FontCache[i];
+	for (j = i; j > 0; --j) {
+	  t3FontCache[j] = t3FontCache[j - 1];
+	}
+	t3FontCache[0] = t3Font;
+	break;
+      }
+    }
+    if (i >= nT3Fonts) {
+
+      // create new entry in the font cache
+      if (nT3Fonts == splashOutT3FontCacheSize) {
+	delete t3FontCache[nT3Fonts - 1];
+	--nT3Fonts;
+      }
+      for (j = nT3Fonts; j > 0; --j) {
+	t3FontCache[j] = t3FontCache[j - 1];
+      }
+      ++nT3Fonts;
+      bbox = gfxFont->getFontBBox();
+      if (bbox[0] == 0 && bbox[1] == 0 && bbox[2] == 0 && bbox[3] == 0) {
+	// unspecified bounding box -- just take a guess
+	xMin = xt - 5;
+	xMax = xMin + 30;
+	yMax = yt + 15;
+	yMin = yMax - 45;
+	validBBox = gFalse;
+      } else {
+	state->transform(bbox[0], bbox[1], &x1, &y1);
+	xMin = xMax = x1;
+	yMin = yMax = y1;
+	state->transform(bbox[0], bbox[3], &x1, &y1);
+	if (x1 < xMin) {
+	  xMin = x1;
+	} else if (x1 > xMax) {
+	  xMax = x1;
+	}
+	if (y1 < yMin) {
+	  yMin = y1;
+	} else if (y1 > yMax) {
+	  yMax = y1;
+	}
+	state->transform(bbox[2], bbox[1], &x1, &y1);
+	if (x1 < xMin) {
+	  xMin = x1;
+	} else if (x1 > xMax) {
+	  xMax = x1;
+	}
+	if (y1 < yMin) {
+	  yMin = y1;
+	} else if (y1 > yMax) {
+	  yMax = y1;
+	}
+	state->transform(bbox[2], bbox[3], &x1, &y1);
+	if (x1 < xMin) {
+	  xMin = x1;
+	} else if (x1 > xMax) {
+	  xMax = x1;
+	}
+	if (y1 < yMin) {
+	  yMin = y1;
+	} else if (y1 > yMax) {
+	  yMax = y1;
+	}
+	validBBox = gTrue;
+      }
+      t3FontCache[0] = new T3FontCache(fontID, ctm[0], ctm[1], ctm[2], ctm[3],
+	                               (int)floor(xMin - xt),
+				       (int)floor(yMin - yt),
+				       (int)ceil(xMax) - (int)floor(xMin) + 3,
+				       (int)ceil(yMax) - (int)floor(yMin) + 3,
+				       validBBox,
+				       colorMode != splashModeMono1);
+    }
+  }
+  t3Font = t3FontCache[0];
+
+  // is the glyph in the cache?
+  i = (code & (t3Font->cacheSets - 1)) * t3Font->cacheAssoc;
+  for (j = 0; j < t3Font->cacheAssoc; ++j) {
+    if ((t3Font->cacheTags[i+j].mru & 0x8000) &&
+	t3Font->cacheTags[i+j].code == code) {
+      drawType3Glyph(t3Font, &t3Font->cacheTags[i+j],
+		     t3Font->cacheData + (i+j) * t3Font->glyphSize);
+      return gTrue;
+    }
+  }
+
+  // push a new Type 3 glyph record
+  t3gs = new T3GlyphStack();
+  t3gs->next = t3GlyphStack;
+  t3GlyphStack = t3gs;
+  t3GlyphStack->code = code;
+  t3GlyphStack->cache = t3Font;
+  t3GlyphStack->cacheTag = NULL;
+  t3GlyphStack->cacheData = NULL;
+
+  return gFalse;
+}
+
+void SplashOutputDev::endType3Char(GfxState *state) {
+  T3GlyphStack *t3gs;
+  double *ctm;
+
+  if (t3GlyphStack->cacheTag) {
+    memcpy(t3GlyphStack->cacheData, bitmap->getDataPtr(),
+	   t3GlyphStack->cache->glyphSize);
+    delete bitmap;
+    delete splash;
+    bitmap = t3GlyphStack->origBitmap;
+    splash = t3GlyphStack->origSplash;
+    ctm = state->getCTM();
+    state->setCTM(ctm[0], ctm[1], ctm[2], ctm[3],
+		  t3GlyphStack->origCTM4, t3GlyphStack->origCTM5);
+    updateCTM(state, 0, 0, 0, 0, 0, 0);
+    drawType3Glyph(t3GlyphStack->cache,
+		   t3GlyphStack->cacheTag, t3GlyphStack->cacheData);
+  }
+  t3gs = t3GlyphStack;
+  t3GlyphStack = t3gs->next;
+  delete t3gs;
+}
+
+void SplashOutputDev::type3D0(GfxState *state, double wx, double wy) {
+}
+
+void SplashOutputDev::type3D1(GfxState *state, double wx, double wy,
+			      double llx, double lly, double urx, double ury) {
+  double *ctm;
+  T3FontCache *t3Font;
+  SplashColor color;
+  double xt, yt, xMin, xMax, yMin, yMax, x1, y1;
+  int i, j;
+
+  t3Font = t3GlyphStack->cache;
+
+  // check for a valid bbox
+  state->transform(0, 0, &xt, &yt);
+  state->transform(llx, lly, &x1, &y1);
+  xMin = xMax = x1;
+  yMin = yMax = y1;
+  state->transform(llx, ury, &x1, &y1);
+  if (x1 < xMin) {
+    xMin = x1;
+  } else if (x1 > xMax) {
+    xMax = x1;
+  }
+  if (y1 < yMin) {
+    yMin = y1;
+  } else if (y1 > yMax) {
+    yMax = y1;
+  }
+  state->transform(urx, lly, &x1, &y1);
+  if (x1 < xMin) {
+    xMin = x1;
+  } else if (x1 > xMax) {
+    xMax = x1;
+  }
+  if (y1 < yMin) {
+    yMin = y1;
+  } else if (y1 > yMax) {
+    yMax = y1;
+  }
+  state->transform(urx, ury, &x1, &y1);
+  if (x1 < xMin) {
+    xMin = x1;
+  } else if (x1 > xMax) {
+    xMax = x1;
+  }
+  if (y1 < yMin) {
+    yMin = y1;
+  } else if (y1 > yMax) {
+    yMax = y1;
+  }
+  if (xMin - xt < t3Font->glyphX ||
+      yMin - yt < t3Font->glyphY ||
+      xMax - xt > t3Font->glyphX + t3Font->glyphW ||
+      yMax - yt > t3Font->glyphY + t3Font->glyphH) {
+    if (t3Font->validBBox) {
+      error(-1, "Bad bounding box in Type 3 glyph");
+    }
+    return;
+  }
+
+  // allocate a cache entry
+  i = (t3GlyphStack->code & (t3Font->cacheSets - 1)) * t3Font->cacheAssoc;
+  for (j = 0; j < t3Font->cacheAssoc; ++j) {
+    if ((t3Font->cacheTags[i+j].mru & 0x7fff) == t3Font->cacheAssoc - 1) {
+      t3Font->cacheTags[i+j].mru = 0x8000;
+      t3Font->cacheTags[i+j].code = t3GlyphStack->code;
+      t3GlyphStack->cacheTag = &t3Font->cacheTags[i+j];
+      t3GlyphStack->cacheData = t3Font->cacheData + (i+j) * t3Font->glyphSize;
+    } else {
+      ++t3Font->cacheTags[i+j].mru;
+    }
+  }
+
+  // save state
+  t3GlyphStack->origBitmap = bitmap;
+  t3GlyphStack->origSplash = splash;
+  ctm = state->getCTM();
+  t3GlyphStack->origCTM4 = ctm[4];
+  t3GlyphStack->origCTM5 = ctm[5];
+
+  // create the temporary bitmap
+  if (colorMode == splashModeMono1) {
+    bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1,
+			      splashModeMono1, gFalse);
+    splash = new Splash(bitmap, gFalse,
+			t3GlyphStack->origSplash->getScreen());
+    color[0] = 0;
+    splash->clear(color);
+    color[0] = 1;
+  } else {
+    bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1,
+			      splashModeMono8, gFalse);
+    splash = new Splash(bitmap, vectorAntialias,
+			t3GlyphStack->origSplash->getScreen());
+    color[0] = 0x00;
+    splash->clear(color);
+    color[0] = 0xff;
+  }
+  splash->setFillPattern(new SplashSolidColor(color));
+  splash->setStrokePattern(new SplashSolidColor(color));
+  //~ this should copy other state from t3GlyphStack->origSplash?
+  state->setCTM(ctm[0], ctm[1], ctm[2], ctm[3],
+		-t3Font->glyphX, -t3Font->glyphY);
+  updateCTM(state, 0, 0, 0, 0, 0, 0);
+}
+
+void SplashOutputDev::drawType3Glyph(T3FontCache *t3Font,
+				     T3FontCacheTag *tag, Guchar *data) {
+  SplashGlyphBitmap glyph;
+
+  glyph.x = -t3Font->glyphX;
+  glyph.y = -t3Font->glyphY;
+  glyph.w = t3Font->glyphW;
+  glyph.h = t3Font->glyphH;
+  glyph.aa = colorMode != splashModeMono1;
+  glyph.data = data;
+  glyph.freeData = gFalse;
+  splash->fillGlyph(0, 0, &glyph);
+}
+
+void SplashOutputDev::endTextObject(GfxState *state) {
+  if (textClipPath) {
+    splash->clipToPath(textClipPath, gFalse);
+    delete textClipPath;
+    textClipPath = NULL;
+  }
+}
+
+struct SplashOutImageMaskData {
+  ImageStream *imgStr;
+  GBool invert;
+  int width, height, y;
+};
+
+GBool SplashOutputDev::imageMaskSrc(void *data, SplashColorPtr line) {
+  SplashOutImageMaskData *imgMaskData = (SplashOutImageMaskData *)data;
+  Guchar *p;
+  SplashColorPtr q;
+  int x;
+
+  if (imgMaskData->y == imgMaskData->height) {
+    return gFalse;
+  }
+  for (x = 0, p = imgMaskData->imgStr->getLine(), q = line;
+       x < imgMaskData->width;
+       ++x) {
+    *q++ = *p++ ^ imgMaskData->invert;
+  }
+  ++imgMaskData->y;
+  return gTrue;
+}
+
+void SplashOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
+				    int width, int height, GBool invert,
+				    GBool inlineImg) {
+  double *ctm;
+  SplashCoord mat[6];
+  SplashOutImageMaskData imgMaskData;
+
+  if (state->getFillColorSpace()->isNonMarking()) {
+    return;
+  }
+
+  ctm = state->getCTM();
+  mat[0] = ctm[0];
+  mat[1] = ctm[1];
+  mat[2] = -ctm[2];
+  mat[3] = -ctm[3];
+  mat[4] = ctm[2] + ctm[4];
+  mat[5] = ctm[3] + ctm[5];
+
+  imgMaskData.imgStr = new ImageStream(str, width, 1, 1);
+  imgMaskData.imgStr->reset();
+  imgMaskData.invert = invert ? 0 : 1;
+  imgMaskData.width = width;
+  imgMaskData.height = height;
+  imgMaskData.y = 0;
+
+  splash->fillImageMask(&imageMaskSrc, &imgMaskData, width, height, mat,
+			t3GlyphStack != NULL);
+  if (inlineImg) {
+    while (imgMaskData.y < height) {
+      imgMaskData.imgStr->getLine();
+      ++imgMaskData.y;
+    }
+  }
+
+  delete imgMaskData.imgStr;
+  str->close();
+}
+
+struct SplashOutImageData {
+  ImageStream *imgStr;
+  GfxImageColorMap *colorMap;
+  SplashColorPtr lookup;
+  int *maskColors;
+  SplashColorMode colorMode;
+  int width, height, y;
+};
+
+GBool SplashOutputDev::imageSrc(void *data, SplashColorPtr colorLine,
+				Guchar *alphaLine) {
+  SplashOutImageData *imgData = (SplashOutImageData *)data;
+  Guchar *p;
+  SplashColorPtr q, col;
+  GfxRGB rgb;
+  GfxGray gray;
+#if SPLASH_CMYK
+  GfxCMYK cmyk;
+#endif
+  int nComps, x;
+
+  if (imgData->y == imgData->height) {
+    return gFalse;
+  }
+
+  nComps = imgData->colorMap->getNumPixelComps();
+
+  if (imgData->lookup) {
+    switch (imgData->colorMode) {
+    case splashModeMono1:
+    case splashModeMono8:
+      for (x = 0, p = imgData->imgStr->getLine(), q = colorLine;
+	   x < imgData->width;
+	   ++x, ++p) {
+	*q++ = imgData->lookup[*p];
+      }
+      break;
+    case splashModeRGB8:
+    case splashModeBGR8:
+      for (x = 0, p = imgData->imgStr->getLine(), q = colorLine;
+	   x < imgData->width;
+	   ++x, ++p) {
+	col = &imgData->lookup[3 * *p];
+	*q++ = col[0];
+	*q++ = col[1];
+	*q++ = col[2];
+      }
+      break;
+#if SPLASH_CMYK
+    case splashModeCMYK8:
+      for (x = 0, p = imgData->imgStr->getLine(), q = colorLine;
+	   x < imgData->width;
+	   ++x, ++p) {
+	col = &imgData->lookup[4 * *p];
+	*q++ = col[0];
+	*q++ = col[1];
+	*q++ = col[2];
+	*q++ = col[3];
+      }
+      break;
+#endif
+    }
+  } else {
+    switch (imgData->colorMode) {
+    case splashModeMono1:
+    case splashModeMono8:
+      for (x = 0, p = imgData->imgStr->getLine(), q = colorLine;
+	   x < imgData->width;
+	   ++x, p += nComps) {
+	imgData->colorMap->getGray(p, &gray);
+	*q++ = colToByte(gray);
+      }
+      break;
+    case splashModeRGB8:
+    case splashModeBGR8:
+      for (x = 0, p = imgData->imgStr->getLine(), q = colorLine;
+	   x < imgData->width;
+	   ++x, p += nComps) {
+	imgData->colorMap->getRGB(p, &rgb);
+	*q++ = colToByte(rgb.r);
+	*q++ = colToByte(rgb.g);
+	*q++ = colToByte(rgb.b);
+      }
+      break;
+#if SPLASH_CMYK
+    case splashModeCMYK8:
+      for (x = 0, p = imgData->imgStr->getLine(), q = colorLine;
+	   x < imgData->width;
+	   ++x, p += nComps) {
+	imgData->colorMap->getCMYK(p, &cmyk);
+	*q++ = colToByte(cmyk.c);
+	*q++ = colToByte(cmyk.m);
+	*q++ = colToByte(cmyk.y);
+	*q++ = colToByte(cmyk.k);
+      }
+      break;
+#endif
+    }
+  }
+
+  ++imgData->y;
+  return gTrue;
+}
+
+GBool SplashOutputDev::alphaImageSrc(void *data, SplashColorPtr colorLine,
+				     Guchar *alphaLine) {
+  SplashOutImageData *imgData = (SplashOutImageData *)data;
+  Guchar *p, *aq;
+  SplashColorPtr q, col;
+  GfxRGB rgb;
+  GfxGray gray;
+#if SPLASH_CMYK
+  GfxCMYK cmyk;
+#endif
+  Guchar alpha;
+  int nComps, x, i;
+
+  if (imgData->y == imgData->height) {
+    return gFalse;
+  }
+
+  nComps = imgData->colorMap->getNumPixelComps();
+
+  for (x = 0, p = imgData->imgStr->getLine(), q = colorLine, aq = alphaLine;
+       x < imgData->width;
+       ++x, p += nComps) {
+    alpha = 0;
+    for (i = 0; i < nComps; ++i) {
+      if (p[i] < imgData->maskColors[2*i] ||
+	  p[i] > imgData->maskColors[2*i+1]) {
+	alpha = 0xff;
+	break;
+      }
+    }
+    if (imgData->lookup) {
+      switch (imgData->colorMode) {
+      case splashModeMono1:
+      case splashModeMono8:
+	*q++ = imgData->lookup[*p];
+	*aq++ = alpha;
+	break;
+      case splashModeRGB8:
+      case splashModeBGR8:
+	col = &imgData->lookup[3 * *p];
+	*q++ = col[0];
+	*q++ = col[1];
+	*q++ = col[2];
+	*aq++ = alpha;
+	break;
+#if SPLASH_CMYK
+      case splashModeCMYK8:
+	col = &imgData->lookup[4 * *p];
+	*q++ = col[0];
+	*q++ = col[1];
+	*q++ = col[2];
+	*q++ = col[3];
+	*aq++ = alpha;
+	break;
+#endif
+      }
+    } else {
+      switch (imgData->colorMode) {
+      case splashModeMono1:
+      case splashModeMono8:
+	imgData->colorMap->getGray(p, &gray);
+	*q++ = colToByte(gray);
+	*aq++ = alpha;
+	break;
+      case splashModeRGB8:
+      case splashModeBGR8:
+	imgData->colorMap->getRGB(p, &rgb);
+	*q++ = colToByte(rgb.r);
+	*q++ = colToByte(rgb.g);
+	*q++ = colToByte(rgb.b);
+	*aq++ = alpha;
+	break;
+#if SPLASH_CMYK
+      case splashModeCMYK8:
+	imgData->colorMap->getCMYK(p, &cmyk);
+	*q++ = colToByte(cmyk.c);
+	*q++ = colToByte(cmyk.m);
+	*q++ = colToByte(cmyk.y);
+	*q++ = colToByte(cmyk.k);
+	*aq++ = alpha;
+	break;
+#endif
+      }
+    }
+  }
+
+  ++imgData->y;
+  return gTrue;
+}
+
+void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
+				int width, int height,
+				GfxImageColorMap *colorMap,
+				int *maskColors, GBool inlineImg) {
+  double *ctm;
+  SplashCoord mat[6];
+  SplashOutImageData imgData;
+  SplashColorMode srcMode;
+  SplashImageSource src;
+  GfxGray gray;
+  GfxRGB rgb;
+#if SPLASH_CMYK
+  GfxCMYK cmyk;
+#endif
+  Guchar pix;
+  int n, i;
+
+  ctm = state->getCTM();
+  mat[0] = ctm[0];
+  mat[1] = ctm[1];
+  mat[2] = -ctm[2];
+  mat[3] = -ctm[3];
+  mat[4] = ctm[2] + ctm[4];
+  mat[5] = ctm[3] + ctm[5];
+
+  imgData.imgStr = new ImageStream(str, width,
+				   colorMap->getNumPixelComps(),
+				   colorMap->getBits());
+  imgData.imgStr->reset();
+  imgData.colorMap = colorMap;
+  imgData.maskColors = maskColors;
+  imgData.colorMode = colorMode;
+  imgData.width = width;
+  imgData.height = height;
+  imgData.y = 0;
+
+  // special case for one-channel (monochrome/gray/separation) images:
+  // build a lookup table here
+  imgData.lookup = NULL;
+  if (colorMap->getNumPixelComps() == 1) {
+    n = 1 << colorMap->getBits();
+    switch (colorMode) {
+    case splashModeMono1:
+    case splashModeMono8:
+      imgData.lookup = (SplashColorPtr)gmalloc(n);
+      for (i = 0; i < n; ++i) {
+	pix = (Guchar)i;
+	colorMap->getGray(&pix, &gray);
+	imgData.lookup[i] = colToByte(gray);
+      }
+      break;
+    case splashModeRGB8:
+    case splashModeBGR8:
+      imgData.lookup = (SplashColorPtr)gmalloc(3 * n);
+      for (i = 0; i < n; ++i) {
+	pix = (Guchar)i;
+	colorMap->getRGB(&pix, &rgb);
+	imgData.lookup[3*i] = colToByte(rgb.r);
+	imgData.lookup[3*i+1] = colToByte(rgb.g);
+	imgData.lookup[3*i+2] = colToByte(rgb.b);
+      }
+      break;
+#if SPLASH_CMYK
+    case splashModeCMYK8:
+      imgData.lookup = (SplashColorPtr)gmalloc(4 * n);
+      for (i = 0; i < n; ++i) {
+	pix = (Guchar)i;
+	colorMap->getCMYK(&pix, &cmyk);
+	imgData.lookup[4*i] = colToByte(cmyk.c);
+	imgData.lookup[4*i+1] = colToByte(cmyk.m);
+	imgData.lookup[4*i+2] = colToByte(cmyk.y);
+	imgData.lookup[4*i+3] = colToByte(cmyk.k);
+      }
+      break;
+#endif
+      break;
+    }
+  }
+
+  if (colorMode == splashModeMono1) {
+    srcMode = splashModeMono8;
+  } else {
+    srcMode = colorMode;
+  }
+  src = maskColors ? &alphaImageSrc : &imageSrc;
+  splash->drawImage(src, &imgData, srcMode, maskColors ? gTrue : gFalse,
+		    width, height, mat);
+  if (inlineImg) {
+    while (imgData.y < height) {
+      imgData.imgStr->getLine();
+      ++imgData.y;
+    }
+  }
+
+  gfree(imgData.lookup);
+  delete imgData.imgStr;
+  str->close();
+}
+
+struct SplashOutMaskedImageData {
+  ImageStream *imgStr;
+  GfxImageColorMap *colorMap;
+  SplashBitmap *mask;
+  SplashColorPtr lookup;
+  SplashColorMode colorMode;
+  int width, height, y;
+};
+
+GBool SplashOutputDev::maskedImageSrc(void *data, SplashColorPtr colorLine,
+				      Guchar *alphaLine) {
+  SplashOutMaskedImageData *imgData = (SplashOutMaskedImageData *)data;
+  Guchar *p, *aq;
+  SplashColor maskColor;
+  SplashColorPtr q, col;
+  GfxRGB rgb;
+  GfxGray gray;
+#if SPLASH_CMYK
+  GfxCMYK cmyk;
+#endif
+  Guchar alpha;
+  int nComps, x;
+
+  if (imgData->y == imgData->height) {
+    return gFalse;
+  }
+
+  nComps = imgData->colorMap->getNumPixelComps();
+
+  for (x = 0, p = imgData->imgStr->getLine(), q = colorLine, aq = alphaLine;
+       x < imgData->width;
+       ++x, p += nComps) {
+    imgData->mask->getPixel(x, imgData->y, maskColor);
+    alpha = maskColor[0] ? 0xff : 0x00;
+    if (imgData->lookup) {
+      switch (imgData->colorMode) {
+      case splashModeMono1:
+      case splashModeMono8:
+	*q++ = imgData->lookup[*p];
+	*aq++ = alpha;
+	break;
+      case splashModeRGB8:
+      case splashModeBGR8:
+	col = &imgData->lookup[3 * *p];
+	*q++ = col[0];
+	*q++ = col[1];
+	*q++ = col[2];
+	*aq++ = alpha;
+	break;
+#if SPLASH_CMYK
+      case splashModeCMYK8:
+	col = &imgData->lookup[4 * *p];
+	*q++ = col[0];
+	*q++ = col[1];
+	*q++ = col[2];
+	*q++ = col[3];
+	*aq++ = alpha;
+	break;
+#endif
+      }
+    } else {
+      switch (imgData->colorMode) {
+      case splashModeMono1:
+      case splashModeMono8:
+	imgData->colorMap->getGray(p, &gray);
+	*q++ = colToByte(gray);
+	*aq++ = alpha;
+	break;
+      case splashModeRGB8:
+      case splashModeBGR8:
+	imgData->colorMap->getRGB(p, &rgb);
+	*q++ = colToByte(rgb.r);
+	*q++ = colToByte(rgb.g);
+	*q++ = colToByte(rgb.b);
+	*aq++ = alpha;
+	break;
+#if SPLASH_CMYK
+      case splashModeCMYK8:
+	imgData->colorMap->getCMYK(p, &cmyk);
+	*q++ = colToByte(cmyk.c);
+	*q++ = colToByte(cmyk.m);
+	*q++ = colToByte(cmyk.y);
+	*q++ = colToByte(cmyk.k);
+	*aq++ = alpha;
+	break;
+#endif
+      }
+    }
+  }
+
+  ++imgData->y;
+  return gTrue;
+}
+
+void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref,
+				      Stream *str, int width, int height,
+				      GfxImageColorMap *colorMap,
+				      Stream *maskStr, int maskWidth,
+				      int maskHeight, GBool maskInvert) {
+  GfxImageColorMap *maskColorMap;
+  Object maskDecode, decodeLow, decodeHigh;
+  double *ctm;
+  SplashCoord mat[6];
+  SplashOutMaskedImageData imgData;
+  SplashOutImageMaskData imgMaskData;
+  SplashColorMode srcMode;
+  SplashBitmap *maskBitmap;
+  Splash *maskSplash;
+  SplashColor maskColor;
+  GfxGray gray;
+  GfxRGB rgb;
+#if SPLASH_CMYK
+  GfxCMYK cmyk;
+#endif
+  Guchar pix;
+  int n, i;
+
+  // If the mask is higher resolution than the image, use
+  // drawSoftMaskedImage() instead.
+  if (maskWidth > width || maskHeight > height) {
+    decodeLow.initInt(maskInvert ? 0 : 1);
+    decodeHigh.initInt(maskInvert ? 1 : 0);
+    maskDecode.initArray(xref);
+    maskDecode.arrayAdd(&decodeLow);
+    maskDecode.arrayAdd(&decodeHigh);
+    maskColorMap = new GfxImageColorMap(1, &maskDecode,
+					new GfxDeviceGrayColorSpace());
+    maskDecode.free();
+    drawSoftMaskedImage(state, ref, str, width, height, colorMap,
+			maskStr, maskWidth, maskHeight, maskColorMap);
+    delete maskColorMap;
+
+  } else {
+
+    //----- scale the mask image to the same size as the source image
+
+    mat[0] = (SplashCoord)width;
+    mat[1] = 0;
+    mat[2] = 0;
+    mat[3] = (SplashCoord)height;
+    mat[4] = 0;
+    mat[5] = 0;
+    imgMaskData.imgStr = new ImageStream(maskStr, maskWidth, 1, 1);
+    imgMaskData.imgStr->reset();
+    imgMaskData.invert = maskInvert ? 0 : 1;
+    imgMaskData.width = maskWidth;
+    imgMaskData.height = maskHeight;
+    imgMaskData.y = 0;
+    maskBitmap = new SplashBitmap(width, height, 1, splashModeMono1, gFalse);
+    maskSplash = new Splash(maskBitmap, gFalse);
+    maskColor[0] = 0;
+    maskSplash->clear(maskColor);
+    maskColor[0] = 0xff;
+    maskSplash->setFillPattern(new SplashSolidColor(maskColor));
+    maskSplash->fillImageMask(&imageMaskSrc, &imgMaskData,
+			      maskWidth, maskHeight, mat, gFalse);
+    delete imgMaskData.imgStr;
+    maskStr->close();
+    delete maskSplash;
+
+    //----- draw the source image
+
+    ctm = state->getCTM();
+    mat[0] = ctm[0];
+    mat[1] = ctm[1];
+    mat[2] = -ctm[2];
+    mat[3] = -ctm[3];
+    mat[4] = ctm[2] + ctm[4];
+    mat[5] = ctm[3] + ctm[5];
+
+    imgData.imgStr = new ImageStream(str, width,
+				     colorMap->getNumPixelComps(),
+				     colorMap->getBits());
+    imgData.imgStr->reset();
+    imgData.colorMap = colorMap;
+    imgData.mask = maskBitmap;
+    imgData.colorMode = colorMode;
+    imgData.width = width;
+    imgData.height = height;
+    imgData.y = 0;
+
+    // special case for one-channel (monochrome/gray/separation) images:
+    // build a lookup table here
+    imgData.lookup = NULL;
+    if (colorMap->getNumPixelComps() == 1) {
+      n = 1 << colorMap->getBits();
+      switch (colorMode) {
+      case splashModeMono1:
+      case splashModeMono8:
+	imgData.lookup = (SplashColorPtr)gmalloc(n);
+	for (i = 0; i < n; ++i) {
+	  pix = (Guchar)i;
+	  colorMap->getGray(&pix, &gray);
+	  imgData.lookup[i] = colToByte(gray);
+	}
+	break;
+      case splashModeRGB8:
+      case splashModeBGR8:
+	imgData.lookup = (SplashColorPtr)gmalloc(3 * n);
+	for (i = 0; i < n; ++i) {
+	  pix = (Guchar)i;
+	  colorMap->getRGB(&pix, &rgb);
+	  imgData.lookup[3*i] = colToByte(rgb.r);
+	  imgData.lookup[3*i+1] = colToByte(rgb.g);
+	  imgData.lookup[3*i+2] = colToByte(rgb.b);
+	}
+	break;
+#if SPLASH_CMYK
+      case splashModeCMYK8:
+	imgData.lookup = (SplashColorPtr)gmalloc(4 * n);
+	for (i = 0; i < n; ++i) {
+	  pix = (Guchar)i;
+	  colorMap->getCMYK(&pix, &cmyk);
+	  imgData.lookup[4*i] = colToByte(cmyk.c);
+	  imgData.lookup[4*i+1] = colToByte(cmyk.m);
+	  imgData.lookup[4*i+2] = colToByte(cmyk.y);
+	  imgData.lookup[4*i+3] = colToByte(cmyk.k);
+	}
+	break;
+#endif
+      }
+    }
+
+    if (colorMode == splashModeMono1) {
+      srcMode = splashModeMono8;
+    } else {
+      srcMode = colorMode;
+    }
+    splash->drawImage(&maskedImageSrc, &imgData, srcMode, gTrue,
+		      width, height, mat);
+
+    delete maskBitmap;
+    gfree(imgData.lookup);
+    delete imgData.imgStr;
+    str->close();
+  }
+}
+
+void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref,
+					  Stream *str, int width, int height,
+					  GfxImageColorMap *colorMap,
+					  Stream *maskStr,
+					  int maskWidth, int maskHeight,
+					  GfxImageColorMap *maskColorMap) {
+  double *ctm;
+  SplashCoord mat[6];
+  SplashOutImageData imgData;
+  SplashOutImageData imgMaskData;
+  SplashColorMode srcMode;
+  SplashBitmap *maskBitmap;
+  Splash *maskSplash;
+  SplashColor maskColor;
+  GfxGray gray;
+  GfxRGB rgb;
+#if SPLASH_CMYK
+  GfxCMYK cmyk;
+#endif
+  Guchar pix;
+  int n, i;
+
+  ctm = state->getCTM();
+  mat[0] = ctm[0];
+  mat[1] = ctm[1];
+  mat[2] = -ctm[2];
+  mat[3] = -ctm[3];
+  mat[4] = ctm[2] + ctm[4];
+  mat[5] = ctm[3] + ctm[5];
+
+  //----- set up the soft mask
+
+  imgMaskData.imgStr = new ImageStream(maskStr, maskWidth,
+				       maskColorMap->getNumPixelComps(),
+				       maskColorMap->getBits());
+  imgMaskData.imgStr->reset();
+  imgMaskData.colorMap = maskColorMap;
+  imgMaskData.maskColors = NULL;
+  imgMaskData.colorMode = splashModeMono8;
+  imgMaskData.width = maskWidth;
+  imgMaskData.height = maskHeight;
+  imgMaskData.y = 0;
+  n = 1 << maskColorMap->getBits();
+  imgMaskData.lookup = (SplashColorPtr)gmalloc(n);
+  for (i = 0; i < n; ++i) {
+    pix = (Guchar)i;
+    maskColorMap->getGray(&pix, &gray);
+    imgMaskData.lookup[i] = colToByte(gray);
+  }
+  maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(),
+				1, splashModeMono8, gFalse);
+  maskSplash = new Splash(maskBitmap, vectorAntialias);
+  maskColor[0] = 0;
+  maskSplash->clear(maskColor);
+  maskSplash->drawImage(&imageSrc, &imgMaskData, splashModeMono8, gFalse,
+			maskWidth, maskHeight, mat);
+  delete imgMaskData.imgStr;
+  maskStr->close();
+  gfree(imgMaskData.lookup);
+  delete maskSplash;
+  splash->setSoftMask(maskBitmap);
+
+  //----- draw the source image
+
+  imgData.imgStr = new ImageStream(str, width,
+				   colorMap->getNumPixelComps(),
+				   colorMap->getBits());
+  imgData.imgStr->reset();
+  imgData.colorMap = colorMap;
+  imgData.maskColors = NULL;
+  imgData.colorMode = colorMode;
+  imgData.width = width;
+  imgData.height = height;
+  imgData.y = 0;
+
+  // special case for one-channel (monochrome/gray/separation) images:
+  // build a lookup table here
+  imgData.lookup = NULL;
+  if (colorMap->getNumPixelComps() == 1) {
+    n = 1 << colorMap->getBits();
+    switch (colorMode) {
+    case splashModeMono1:
+    case splashModeMono8:
+      imgData.lookup = (SplashColorPtr)gmalloc(n);
+      for (i = 0; i < n; ++i) {
+	pix = (Guchar)i;
+	colorMap->getGray(&pix, &gray);
+	imgData.lookup[i] = colToByte(gray);
+      }
+      break;
+    case splashModeRGB8:
+    case splashModeBGR8:
+      imgData.lookup = (SplashColorPtr)gmalloc(3 * n);
+      for (i = 0; i < n; ++i) {
+	pix = (Guchar)i;
+	colorMap->getRGB(&pix, &rgb);
+	imgData.lookup[3*i] = colToByte(rgb.r);
+	imgData.lookup[3*i+1] = colToByte(rgb.g);
+	imgData.lookup[3*i+2] = colToByte(rgb.b);
+      }
+      break;
+#if SPLASH_CMYK
+    case splashModeCMYK8:
+      imgData.lookup = (SplashColorPtr)gmalloc(4 * n);
+      for (i = 0; i < n; ++i) {
+	pix = (Guchar)i;
+	colorMap->getCMYK(&pix, &cmyk);
+	imgData.lookup[4*i] = colToByte(cmyk.c);
+	imgData.lookup[4*i+1] = colToByte(cmyk.m);
+	imgData.lookup[4*i+2] = colToByte(cmyk.y);
+	imgData.lookup[4*i+3] = colToByte(cmyk.k);
+      }
+      break;
+#endif
+    }
+  }
+
+  if (colorMode == splashModeMono1) {
+    srcMode = splashModeMono8;
+  } else {
+    srcMode = colorMode;
+  }
+  splash->drawImage(&imageSrc, &imgData, srcMode, gFalse, width, height, mat);
+
+  splash->setSoftMask(NULL);
+  gfree(imgData.lookup);
+  delete imgData.imgStr;
+  str->close();
+}
+
+void SplashOutputDev::beginTransparencyGroup(GfxState *state, double *bbox,
+					     GfxColorSpace *blendingColorSpace,
+					     GBool isolated, GBool knockout,
+					     GBool forSoftMask) {
+  SplashTransparencyGroup *transpGroup;
+  SplashColor color;
+  double xMin, yMin, xMax, yMax, x, y;
+  int tx, ty, w, h;
+
+  // transform the bbox
+  state->transform(bbox[0], bbox[1], &x, &y);
+  xMin = xMax = x;
+  yMin = yMax = y;
+  state->transform(bbox[0], bbox[3], &x, &y);
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  state->transform(bbox[2], bbox[1], &x, &y);
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  state->transform(bbox[2], bbox[3], &x, &y);
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  tx = (int)floor(xMin);
+  if (tx < 0) {
+    tx = 0;
+  } else if (tx > bitmap->getWidth()) {
+    tx = bitmap->getWidth();
+  }
+  ty = (int)floor(yMin);
+  if (ty < 0) {
+    ty = 0;
+  } else if (ty > bitmap->getHeight()) {
+    ty = bitmap->getHeight();
+  }
+  w = (int)ceil(xMax) - tx + 1;
+  if (tx + w > bitmap->getWidth()) {
+    w = bitmap->getWidth() - tx;
+  }
+  if (w < 1) {
+    w = 1;
+  }
+  h = (int)ceil(yMax) - ty + 1;
+  if (ty + h > bitmap->getHeight()) {
+    h = bitmap->getHeight() - ty;
+  }
+  if (h < 1) {
+    h = 1;
+  }
+
+  // push a new stack entry
+  transpGroup = new SplashTransparencyGroup();
+  transpGroup->tx = tx;
+  transpGroup->ty = ty;
+  transpGroup->blendingColorSpace = blendingColorSpace;
+  transpGroup->isolated = isolated;
+  transpGroup->next = transpGroupStack;
+  transpGroupStack = transpGroup;
+
+  // save state
+  transpGroup->origBitmap = bitmap;
+  transpGroup->origSplash = splash;
+
+  //~ this ignores the blendingColorSpace arg
+
+  // create the temporary bitmap
+  bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, gTrue,
+			    bitmapTopDown); 
+  splash = new Splash(bitmap, vectorAntialias,
+		      transpGroup->origSplash->getScreen());
+  if (isolated) {
+    switch (colorMode) {
+    case splashModeMono1:
+    case splashModeMono8:
+      color[0] = 0;
+      break;
+    case splashModeRGB8:
+    case splashModeBGR8:
+      color[0] = color[1] = color[2] = 0;
+      break;
+#if SPLASH_CMYK
+    case splashModeCMYK8:
+      color[0] = color[1] = color[2] = color[3] = 0;
+      break;
+#endif
+    default:
+      // make gcc happy
+      break;
+    }
+    splash->clear(color, 0);
+  } else {
+    splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h);
+    splash->setInNonIsolatedGroup(transpGroup->origBitmap, tx, ty);
+  }
+  transpGroup->tBitmap = bitmap;
+  state->shiftCTM(-tx, -ty);
+  updateCTM(state, 0, 0, 0, 0, 0, 0);
+}
+
+void SplashOutputDev::endTransparencyGroup(GfxState *state) {
+  double *ctm;
+
+  // restore state
+  delete splash;
+  bitmap = transpGroupStack->origBitmap;
+  splash = transpGroupStack->origSplash;
+  ctm = state->getCTM();
+  state->shiftCTM(transpGroupStack->tx, transpGroupStack->ty);
+  updateCTM(state, 0, 0, 0, 0, 0, 0);
+}
+
+void SplashOutputDev::paintTransparencyGroup(GfxState *state, double *bbox) {
+  SplashBitmap *tBitmap;
+  SplashTransparencyGroup *transpGroup;
+  GBool isolated;
+  int tx, ty;
+
+  tx = transpGroupStack->tx;
+  ty = transpGroupStack->ty;
+  tBitmap = transpGroupStack->tBitmap;
+  isolated = transpGroupStack->isolated;
+
+  // paint the transparency group onto the parent bitmap
+  // - the clip path was set in the parent's state)
+  splash->composite(tBitmap, 0, 0, tx, ty,
+		    tBitmap->getWidth(), tBitmap->getHeight(),
+		    gFalse, !isolated);
+
+  // pop the stack
+  transpGroup = transpGroupStack;
+  transpGroupStack = transpGroup->next;
+  delete transpGroup;
+
+  delete tBitmap;
+}
+
+void SplashOutputDev::setSoftMask(GfxState *state, double *bbox,
+				  GBool alpha, Function *transferFunc,
+				  GfxColor *backdropColor) {
+  SplashBitmap *softMask, *tBitmap;
+  Splash *tSplash;
+  SplashTransparencyGroup *transpGroup;
+  SplashColor color;
+  SplashColorPtr p;
+  GfxGray gray;
+  GfxRGB rgb;
+#if SPLASH_CMYK
+  GfxCMYK cmyk;
+#endif
+  double lum, lum2;
+  int tx, ty, x, y;
+
+  tx = transpGroupStack->tx;
+  ty = transpGroupStack->ty;
+  tBitmap = transpGroupStack->tBitmap;
+
+  // composite with backdrop color
+  if (!alpha && colorMode != splashModeMono1) {
+    //~ need to correctly handle the case where no blending color
+    //~ space is given
+    tSplash = new Splash(tBitmap, vectorAntialias,
+			 transpGroupStack->origSplash->getScreen());
+    if (transpGroupStack->blendingColorSpace) {
+      switch (colorMode) {
+      case splashModeMono1:
+	// transparency is not supported in mono1 mode
+	break;
+      case splashModeMono8:
+	transpGroupStack->blendingColorSpace->getGray(backdropColor, &gray);
+	color[0] = colToByte(gray);
+	tSplash->compositeBackground(color);
+	break;
+      case splashModeRGB8:
+      case splashModeBGR8:
+	transpGroupStack->blendingColorSpace->getRGB(backdropColor, &rgb);
+	color[0] = colToByte(rgb.r);
+	color[1] = colToByte(rgb.g);
+	color[2] = colToByte(rgb.b);
+	tSplash->compositeBackground(color);
+	break;
+#if SPLASH_CMYK
+      case splashModeCMYK8:
+	transpGroupStack->blendingColorSpace->getCMYK(backdropColor, &cmyk);
+	color[0] = colToByte(cmyk.c);
+	color[1] = colToByte(cmyk.m);
+	color[2] = colToByte(cmyk.y);
+	color[3] = colToByte(cmyk.k);
+	tSplash->compositeBackground(color);
+	break;
+#endif
+      }
+      delete tSplash;
+    }
+  }
+
+  softMask = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(),
+			      1, splashModeMono8, gFalse);
+  memset(softMask->getDataPtr(), 0,
+	 softMask->getRowSize() * softMask->getHeight());
+  p = softMask->getDataPtr() + ty * softMask->getRowSize() + tx;
+  for (y = 0; y < tBitmap->getHeight(); ++y) {
+    for (x = 0; x < tBitmap->getWidth(); ++x) {
+      tBitmap->getPixel(x, y, color);
+      if (alpha) {
+	//~ unimplemented
+      } else {
+	// convert to luminosity
+	switch (colorMode) {
+	case splashModeMono1:
+	case splashModeMono8:
+	  lum = color[0] / 255.0;
+	  break;
+	case splashModeRGB8:
+	case splashModeBGR8:
+	  lum = (0.3 / 255.0) * color[0] +
+	        (0.59 / 255.0) * color[1] +
+	        (0.11 / 255.0) * color[2];
+	  break;
+#if SPLASH_CMYK
+	case splashModeCMYK8:
+	  lum = (1 - color[4] / 255.0)
+	        - (0.3 / 255.0) * color[0]
+	        - (0.59 / 255.0) * color[1]
+	        - (0.11 / 255.0) * color[2];
+	  if (lum < 0) {
+	    lum = 0;
+	  }
+	  break;
+#endif
+	}
+	if (transferFunc) {
+	  transferFunc->transform(&lum, &lum2);
+	} else {
+	  lum2 = lum;
+	}
+	p[x] = (int)(lum2 * 255.0 + 0.5);
+      }
+    }
+    p += softMask->getRowSize();
+  }
+  splash->setSoftMask(softMask);
+
+  // pop the stack
+  transpGroup = transpGroupStack;
+  transpGroupStack = transpGroup->next;
+  delete transpGroup;
+
+  delete tBitmap;
+}
+
+void SplashOutputDev::clearSoftMask(GfxState *state) {
+  splash->setSoftMask(NULL);
+}
+
+void SplashOutputDev::setPaperColor(SplashColorPtr paperColorA) {
+  splashColorCopy(paperColor, paperColorA);
+}
+
+int SplashOutputDev::getBitmapWidth() {
+  return bitmap->getWidth();
+}
+
+int SplashOutputDev::getBitmapHeight() {
+  return bitmap->getHeight();
+}
+
+SplashBitmap *SplashOutputDev::takeBitmap() {
+  SplashBitmap *ret;
+
+  ret = bitmap;
+  bitmap = new SplashBitmap(1, 1, bitmapRowPad, colorMode,
+			    colorMode != splashModeMono1, bitmapTopDown);
+  return ret;
+}
+
+void SplashOutputDev::getModRegion(int *xMin, int *yMin,
+				   int *xMax, int *yMax) {
+  splash->getModRegion(xMin, yMin, xMax, yMax);
+}
+
+void SplashOutputDev::clearModRegion() {
+  splash->clearModRegion();
+}
+
+void SplashOutputDev::setFillColor(int r, int g, int b) {
+  GfxRGB rgb;
+  GfxGray gray;
+#if SPLASH_CMYK
+  GfxCMYK cmyk;
+#endif
+
+  rgb.r = byteToCol(r);
+  rgb.g = byteToCol(g);
+  rgb.b = byteToCol(b);
+  gray = (GfxColorComp)(0.299 * rgb.r + 0.587 * rgb.g + 0.114 * rgb.g + 0.5);
+  if (gray > gfxColorComp1) {
+    gray = gfxColorComp1;
+  }
+#if SPLASH_CMYK
+  cmyk.c = gfxColorComp1 - rgb.r;
+  cmyk.m = gfxColorComp1 - rgb.g;
+  cmyk.y = gfxColorComp1 - rgb.b;
+  cmyk.k = 0;
+  splash->setFillPattern(getColor(gray, &rgb, &cmyk));
+#else
+  splash->setFillPattern(getColor(gray, &rgb));
+#endif
+}
+
+SplashFont *SplashOutputDev::getFont(GString *name, double *textMatA) {
+  DisplayFontParam *dfp;
+  Ref ref;
+  SplashOutFontFileID *id;
+  SplashFontFile *fontFile;
+  SplashFont *fontObj;
+  FoFiTrueType *ff;
+  Gushort *codeToGID;
+  Unicode u;
+  SplashCoord textMat[4];
+  int cmap, i;
+
+  for (i = 0; i < 16; ++i) {
+    if (!name->cmp(splashOutSubstFonts[i].name)) {
+      break;
+    }
+  }
+  if (i == 16) {
+    return NULL;
+  }
+  ref.num = i;
+  ref.gen = -1;
+  id = new SplashOutFontFileID(&ref);
+
+  // check the font file cache
+  if ((fontFile = fontEngine->getFontFile(id))) {
+    delete id;
+
+  // load the font file
+  } else {
+    dfp = globalParams->getDisplayFont(name);
+    if (dfp && dfp->kind == displayFontT1) {
+      fontFile = fontEngine->loadType1Font(id, dfp->t1.fileName->getCString(),
+					   gFalse, winAnsiEncoding);
+    } else if (dfp && dfp->kind == displayFontTT) {
+      if (!(ff = FoFiTrueType::load(dfp->tt.fileName->getCString()))) {
+	return NULL;
+      }
+      for (cmap = 0; cmap < ff->getNumCmaps(); ++cmap) {
+	if ((ff->getCmapPlatform(cmap) == 3 &&
+	     ff->getCmapEncoding(cmap) == 1) ||
+	    ff->getCmapPlatform(cmap) == 0) {
+	  break;
+	}
+      }
+      if (cmap == ff->getNumCmaps()) {
+	delete ff;
+	return NULL;
+      }
+      codeToGID = (Gushort *)gmallocn(256, sizeof(Gushort));
+      for (i = 0; i < 256; ++i) {
+	codeToGID[i] = 0;
+	if (winAnsiEncoding[i] &&
+	    (u = globalParams->mapNameToUnicode(winAnsiEncoding[i]))) {
+	  codeToGID[i] = ff->mapCodeToGID(cmap, u);
+	}
+      }
+      delete ff;
+      fontFile = fontEngine->loadTrueTypeFont(id,
+					      dfp->tt.fileName->getCString(),
+					      gFalse, codeToGID, 256);
+    } else {
+      return NULL;
+    }
+  }
+
+  // create the scaled font
+  textMat[0] = (SplashCoord)textMatA[0];
+  textMat[1] = (SplashCoord)textMatA[1];
+  textMat[2] = (SplashCoord)textMatA[2];
+  textMat[3] = (SplashCoord)textMatA[3];
+  fontObj = fontEngine->getFont(fontFile, textMat, splash->getMatrix());
+
+  return fontObj;
+}
+
+#if 1 //~tmp: turn off anti-aliasing temporarily
+GBool SplashOutputDev::getVectorAntialias() {
+  return splash->getVectorAntialias();
+}
+
+void SplashOutputDev::setVectorAntialias(GBool vaa) {
+  splash->setVectorAntialias(vaa);
+}
+#endif
diff --git a/xpdf/SplashOutputDev.h b/xpdf/SplashOutputDev.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashOutputDev.h
@@ -0,0 +1,247 @@
+//========================================================================
+//
+// SplashOutputDev.h
+//
+// Copyright 2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef SPLASHOUTPUTDEV_H
+#define SPLASHOUTPUTDEV_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "SplashTypes.h"
+#include "config.h"
+#include "OutputDev.h"
+#include "GfxState.h"
+
+class Gfx8BitFont;
+class SplashBitmap;
+class Splash;
+class SplashPath;
+class SplashPattern;
+class SplashFontEngine;
+class SplashFont;
+class T3FontCache;
+struct T3FontCacheTag;
+struct T3GlyphStack;
+struct SplashTransparencyGroup;
+
+//------------------------------------------------------------------------
+
+// number of Type 3 fonts to cache
+#define splashOutT3FontCacheSize 8
+
+//------------------------------------------------------------------------
+// SplashOutputDev
+//------------------------------------------------------------------------
+
+class SplashOutputDev: public OutputDev {
+public:
+
+  // Constructor.
+  SplashOutputDev(SplashColorMode colorModeA, int bitmapRowPadA,
+		  GBool reverseVideoA, SplashColorPtr paperColorA,
+		  GBool bitmapTopDownA = gTrue,
+		  GBool allowAntialiasA = gTrue);
+
+  // Destructor.
+  virtual ~SplashOutputDev();
+
+  //----- get info about output device
+
+  // Does this device use upside-down coordinates?
+  // (Upside-down means (0,0) is the top left corner of the page.)
+  virtual GBool upsideDown() { return gTrue; }
+
+  // Does this device use drawChar() or drawString()?
+  virtual GBool useDrawChar() { return gTrue; }
+
+  // Does this device use beginType3Char/endType3Char?  Otherwise,
+  // text in Type 3 fonts will be drawn with drawChar/drawString.
+  virtual GBool interpretType3Chars() { return gTrue; }
+
+  //----- initialization and control
+
+  // Start a page.
+  virtual void startPage(int pageNum, GfxState *state);
+
+  // End a page.
+  virtual void endPage();
+
+  //----- save/restore graphics state
+  virtual void saveState(GfxState *state);
+  virtual void restoreState(GfxState *state);
+
+  //----- update graphics state
+  virtual void updateAll(GfxState *state);
+  virtual void updateCTM(GfxState *state, double m11, double m12,
+			 double m21, double m22, double m31, double m32);
+  virtual void updateLineDash(GfxState *state);
+  virtual void updateFlatness(GfxState *state);
+  virtual void updateLineJoin(GfxState *state);
+  virtual void updateLineCap(GfxState *state);
+  virtual void updateMiterLimit(GfxState *state);
+  virtual void updateLineWidth(GfxState *state);
+  virtual void updateStrokeAdjust(GfxState *state);
+  virtual void updateFillColor(GfxState *state);
+  virtual void updateStrokeColor(GfxState *state);
+  virtual void updateBlendMode(GfxState *state);
+  virtual void updateFillOpacity(GfxState *state);
+  virtual void updateStrokeOpacity(GfxState *state);
+
+  //----- update text state
+  virtual void updateFont(GfxState *state);
+
+  //----- path painting
+  virtual void stroke(GfxState *state);
+  virtual void fill(GfxState *state);
+  virtual void eoFill(GfxState *state);
+
+  //----- path clipping
+  virtual void clip(GfxState *state);
+  virtual void eoClip(GfxState *state);
+  virtual void clipToStrokePath(GfxState *state);
+
+  //----- text drawing
+  virtual void drawChar(GfxState *state, double x, double y,
+			double dx, double dy,
+			double originX, double originY,
+			CharCode code, int nBytes, Unicode *u, int uLen);
+  virtual GBool beginType3Char(GfxState *state, double x, double y,
+			       double dx, double dy,
+			       CharCode code, Unicode *u, int uLen);
+  virtual void endType3Char(GfxState *state);
+  virtual void endTextObject(GfxState *state);
+
+  //----- image drawing
+  virtual void drawImageMask(GfxState *state, Object *ref, Stream *str,
+			     int width, int height, GBool invert,
+			     GBool inlineImg);
+  virtual void drawImage(GfxState *state, Object *ref, Stream *str,
+			 int width, int height, GfxImageColorMap *colorMap,
+			 int *maskColors, GBool inlineImg);
+  virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
+			       int width, int height,
+			       GfxImageColorMap *colorMap,
+			       Stream *maskStr, int maskWidth, int maskHeight,
+			       GBool maskInvert);
+  virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
+				   int width, int height,
+				   GfxImageColorMap *colorMap,
+				   Stream *maskStr,
+				   int maskWidth, int maskHeight,
+				   GfxImageColorMap *maskColorMap);
+
+  //----- Type 3 font operators
+  virtual void type3D0(GfxState *state, double wx, double wy);
+  virtual void type3D1(GfxState *state, double wx, double wy,
+		       double llx, double lly, double urx, double ury);
+
+  //----- transparency groups and soft masks
+  virtual void beginTransparencyGroup(GfxState *state, double *bbox,
+				      GfxColorSpace *blendingColorSpace,
+				      GBool isolated, GBool knockout,
+				      GBool forSoftMask);
+  virtual void endTransparencyGroup(GfxState *state);
+  virtual void paintTransparencyGroup(GfxState *state, double *bbox);
+  virtual void setSoftMask(GfxState *state, double *bbox, GBool alpha,
+			   Function *transferFunc, GfxColor *backdropColor);
+  virtual void clearSoftMask(GfxState *state);
+
+  //----- special access
+
+  // Called to indicate that a new PDF document has been loaded.
+  void startDoc(XRef *xrefA);
+ 
+  void setPaperColor(SplashColorPtr paperColorA);
+
+  GBool isReverseVideo() { return reverseVideo; }
+  void setReverseVideo(GBool reverseVideoA) { reverseVideo = reverseVideoA; }
+
+  // Get the bitmap and its size.
+  SplashBitmap *getBitmap() { return bitmap; }
+  int getBitmapWidth();
+  int getBitmapHeight();
+
+  // Returns the last rasterized bitmap, transferring ownership to the
+  // caller.
+  SplashBitmap *takeBitmap();
+
+  // Get the Splash object.
+  Splash *getSplash() { return splash; }
+
+  // Get the modified region.
+  void getModRegion(int *xMin, int *yMin, int *xMax, int *yMax);
+
+  // Clear the modified region.
+  void clearModRegion();
+
+  // Set the Splash fill color.
+  void setFillColor(int r, int g, int b);
+
+  // Get a font object for a Base-14 font, using the Latin-1 encoding.
+  SplashFont *getFont(GString *name, double *textMatA);
+
+  SplashFont *getCurrentFont() { return font; }
+
+#if 1 //~tmp: turn off anti-aliasing temporarily
+  virtual GBool getVectorAntialias();
+  virtual void setVectorAntialias(GBool vaa);
+#endif
+
+private:
+
+  void setupScreenParams(double hDPI, double vDPI);
+#if SPLASH_CMYK
+  SplashPattern *getColor(GfxGray gray, GfxRGB *rgb, GfxCMYK *cmyk);
+#else
+  SplashPattern *getColor(GfxGray gray, GfxRGB *rgb);
+#endif
+  SplashPath *convertPath(GfxState *state, GfxPath *path);
+  void doUpdateFont(GfxState *state);
+  void drawType3Glyph(T3FontCache *t3Font,
+		      T3FontCacheTag *tag, Guchar *data);
+  static GBool imageMaskSrc(void *data, SplashColorPtr line);
+  static GBool imageSrc(void *data, SplashColorPtr colorLine,
+			Guchar *alphaLine);
+  static GBool alphaImageSrc(void *data, SplashColorPtr line,
+			     Guchar *alphaLine);
+  static GBool maskedImageSrc(void *data, SplashColorPtr line,
+			      Guchar *alphaLine);
+
+  SplashColorMode colorMode;
+  int bitmapRowPad;
+  GBool bitmapTopDown;
+  GBool allowAntialias;
+  GBool vectorAntialias;
+  GBool reverseVideo;		// reverse video mode
+  SplashColor paperColor;	// paper color
+  SplashScreenParams screenParams;
+
+  XRef *xref;			// xref table for current document
+
+  SplashBitmap *bitmap;
+  Splash *splash;
+  SplashFontEngine *fontEngine;
+
+  T3FontCache *			// Type 3 font cache
+    t3FontCache[splashOutT3FontCacheSize];
+  int nT3Fonts;			// number of valid entries in t3FontCache
+  T3GlyphStack *t3GlyphStack;	// Type 3 glyph context stack
+
+  SplashFont *font;		// current font
+  GBool needFontUpdate;		// set when the font needs to be updated
+  SplashPath *textClipPath;	// clipping path built with text object
+
+  SplashTransparencyGroup *	// transparency group stack
+    transpGroupStack;
+};
+
+#endif
diff --git a/xpdf/SplashPath.cc b/xpdf/SplashPath.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashPath.cc
@@ -0,0 +1,184 @@
+//========================================================================
+//
+// SplashPath.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <string.h>
+#include "gmem.h"
+#include "SplashErrorCodes.h"
+#include "SplashPath.h"
+
+//------------------------------------------------------------------------
+// SplashPath
+//------------------------------------------------------------------------
+
+// A path can be in three possible states:
+//
+// 1. no current point -- zero or more finished subpaths
+//    [curSubpath == length]
+//
+// 2. one point in subpath
+//    [curSubpath == length - 1]
+//
+// 3. open subpath with two or more points
+//    [curSubpath < length - 1]
+
+SplashPath::SplashPath() {
+  pts = NULL;
+  flags = NULL;
+  length = size = 0;
+  curSubpath = 0;
+  hints = NULL;
+  hintsLength = hintsSize = 0;
+}
+
+SplashPath::SplashPath(SplashPath *path) {
+  length = path->length;
+  size = path->size;
+  pts = (SplashPathPoint *)gmallocn(size, sizeof(SplashPathPoint));
+  flags = (Guchar *)gmallocn(size, sizeof(Guchar));
+  memcpy(pts, path->pts, length * sizeof(SplashPathPoint));
+  memcpy(flags, path->flags, length * sizeof(Guchar));
+  curSubpath = path->curSubpath;
+  if (path->hints) {
+    hintsLength = hintsSize = path->hintsLength;
+    hints = (SplashPathHint *)gmallocn(hintsSize, sizeof(SplashPathHint));
+    memcpy(hints, path->hints, hintsLength * sizeof(SplashPathHint));
+  } else {
+    hints = NULL;
+  }
+}
+
+SplashPath::~SplashPath() {
+  gfree(pts);
+  gfree(flags);
+  gfree(hints);
+}
+
+// Add space for <nPts> more points.
+void SplashPath::grow(int nPts) {
+  if (length + nPts > size) {
+    if (size == 0) {
+      size = 32;
+    }
+    while (size < length + nPts) {
+      size *= 2;
+    }
+    pts = (SplashPathPoint *)greallocn(pts, size, sizeof(SplashPathPoint));
+    flags = (Guchar *)greallocn(flags, size, sizeof(Guchar));
+  }
+}
+
+void SplashPath::append(SplashPath *path) {
+  int i;
+
+  curSubpath = length + path->curSubpath;
+  grow(path->length);
+  for (i = 0; i < path->length; ++i) {
+    pts[length] = path->pts[i];
+    flags[length] = path->flags[i];
+    ++length;
+  }
+}
+
+SplashError SplashPath::moveTo(SplashCoord x, SplashCoord y) {
+  if (onePointSubpath()) {
+    return splashErrBogusPath;
+  }
+  grow(1);
+  pts[length].x = x;
+  pts[length].y = y;
+  flags[length] = splashPathFirst | splashPathLast;
+  curSubpath = length++;
+  return splashOk;
+}
+
+SplashError SplashPath::lineTo(SplashCoord x, SplashCoord y) {
+  if (noCurrentPoint()) {
+    return splashErrNoCurPt;
+  }
+  flags[length-1] &= ~splashPathLast;
+  grow(1);
+  pts[length].x = x;
+  pts[length].y = y;
+  flags[length] = splashPathLast;
+  ++length;
+  return splashOk;
+}
+
+SplashError SplashPath::curveTo(SplashCoord x1, SplashCoord y1,
+				SplashCoord x2, SplashCoord y2,
+				SplashCoord x3, SplashCoord y3) {
+  if (noCurrentPoint()) {
+    return splashErrNoCurPt;
+  }
+  flags[length-1] &= ~splashPathLast;
+  grow(3);
+  pts[length].x = x1;
+  pts[length].y = y1;
+  flags[length] = splashPathCurve;
+  ++length;
+  pts[length].x = x2;
+  pts[length].y = y2;
+  flags[length] = splashPathCurve;
+  ++length;
+  pts[length].x = x3;
+  pts[length].y = y3;
+  flags[length] = splashPathLast;
+  ++length;
+  return splashOk;
+}
+
+SplashError SplashPath::close() {
+  if (noCurrentPoint()) {
+    return splashErrNoCurPt;
+  }
+  if (curSubpath == length - 1 ||
+      pts[length - 1].x != pts[curSubpath].x ||
+      pts[length - 1].y != pts[curSubpath].y) {
+    lineTo(pts[curSubpath].x, pts[curSubpath].y);
+  }
+  flags[curSubpath] |= splashPathClosed;
+  flags[length - 1] |= splashPathClosed;
+  curSubpath = length;
+  return splashOk;
+}
+
+void SplashPath::addStrokeAdjustHint(int ctrl0, int ctrl1,
+				     int firstPt, int lastPt) {
+  if (hintsLength == hintsSize) {
+    hintsSize = hintsLength ? 2 * hintsLength : 8;
+    hints = (SplashPathHint *)greallocn(hints, hintsSize,
+					sizeof(SplashPathHint));
+  }
+  hints[hintsLength].ctrl0 = ctrl0;
+  hints[hintsLength].ctrl1 = ctrl1;
+  hints[hintsLength].firstPt = firstPt;
+  hints[hintsLength].lastPt = lastPt;
+  ++hintsLength;
+}
+
+void SplashPath::offset(SplashCoord dx, SplashCoord dy) {
+  int i;
+
+  for (i = 0; i < length; ++i) {
+    pts[i].x += dx;
+    pts[i].y += dy;
+  }
+}
+
+GBool SplashPath::getCurPt(SplashCoord *x, SplashCoord *y) {
+  if (noCurrentPoint()) {
+    return gFalse;
+  }
+  *x = pts[length - 1].x;
+  *y = pts[length - 1].y;
+  return gTrue;
+}
diff --git a/xpdf/SplashPath.h b/xpdf/SplashPath.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashPath.h
@@ -0,0 +1,121 @@
+//========================================================================
+//
+// SplashPath.h
+//
+//========================================================================
+
+#ifndef SPLASHPATH_H
+#define SPLASHPATH_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashTypes.h"
+
+//------------------------------------------------------------------------
+// SplashPathPoint
+//------------------------------------------------------------------------
+
+struct SplashPathPoint {
+  SplashCoord x, y;
+};
+
+//------------------------------------------------------------------------
+// SplashPath.flags
+//------------------------------------------------------------------------
+
+// first point on each subpath sets this flag
+#define splashPathFirst         0x01
+
+// last point on each subpath sets this flag
+#define splashPathLast          0x02
+
+// if the subpath is closed, its first and last points must be
+// identical, and must set this flag
+#define splashPathClosed        0x04
+
+// curve control points set this flag
+#define splashPathCurve         0x08
+
+//------------------------------------------------------------------------
+// SplashPathHint
+//------------------------------------------------------------------------
+
+struct SplashPathHint {
+  int ctrl0, ctrl1;
+  int firstPt, lastPt;
+};
+
+//------------------------------------------------------------------------
+// SplashPath
+//------------------------------------------------------------------------
+
+class SplashPath {
+public:
+
+  // Create an empty path.
+  SplashPath();
+
+  // Copy a path.
+  SplashPath *copy() { return new SplashPath(this); }
+
+  ~SplashPath();
+
+  // Append <path> to <this>.
+  void append(SplashPath *path);
+
+  // Start a new subpath.
+  SplashError moveTo(SplashCoord x, SplashCoord y);
+
+  // Add a line segment to the last subpath.
+  SplashError lineTo(SplashCoord x, SplashCoord y);
+
+  // Add a third-order (cubic) Bezier curve segment to the last
+  // subpath.
+  SplashError curveTo(SplashCoord x1, SplashCoord y1,
+		      SplashCoord x2, SplashCoord y2,
+		      SplashCoord x3, SplashCoord y3);
+
+  // Close the last subpath, adding a line segment if necessary.
+  SplashError close();
+
+  // Add a stroke adjustment hint.  The controlling segments are
+  // <ctrl0> and <ctrl1> (where segments are identified by their first
+  // point), and the points to be adjusted are <firstPt> .. <lastPt>.
+  void addStrokeAdjustHint(int ctrl0, int ctrl1, int firstPt, int lastPt);
+
+  // Add (<dx>, <dy>) to every point on this path.
+  void offset(SplashCoord dx, SplashCoord dy);
+
+  // Get the points on the path.
+  int getLength() { return length; }
+  void getPoint(int i, double *x, double *y, Guchar *f)
+    { *x = pts[i].x; *y = pts[i].y; *f = flags[i]; }
+
+  // Get the current point.
+  GBool getCurPt(SplashCoord *x, SplashCoord *y);
+
+private:
+
+  SplashPath(SplashPath *path);
+  void grow(int nPts);
+  GBool noCurrentPoint() { return curSubpath == length; }
+  GBool onePointSubpath() { return curSubpath == length - 1; }
+  GBool openSubpath() { return curSubpath < length - 1; }
+
+  SplashPathPoint *pts;		// array of points
+  Guchar *flags;		// array of flags
+  int length, size;		// length/size of the pts and flags arrays
+  int curSubpath;		// index of first point in last subpath
+
+  SplashPathHint *hints;	// list of hints
+  int hintsLength, hintsSize;
+
+  friend class SplashXPath;
+  friend class Splash;
+};
+
+#endif
diff --git a/xpdf/SplashPattern.cc b/xpdf/SplashPattern.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashPattern.cc
@@ -0,0 +1,40 @@
+//========================================================================
+//
+// SplashPattern.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include "SplashMath.h"
+#include "SplashScreen.h"
+#include "SplashPattern.h"
+
+//------------------------------------------------------------------------
+// SplashPattern
+//------------------------------------------------------------------------
+
+SplashPattern::SplashPattern() {
+}
+
+SplashPattern::~SplashPattern() {
+}
+
+//------------------------------------------------------------------------
+// SplashSolidColor
+//------------------------------------------------------------------------
+
+SplashSolidColor::SplashSolidColor(SplashColorPtr colorA) {
+  splashColorCopy(color, colorA);
+}
+
+SplashSolidColor::~SplashSolidColor() {
+}
+
+void SplashSolidColor::getColor(int x, int y, SplashColorPtr c) {
+  splashColorCopy(c, color);
+}
diff --git a/xpdf/SplashPattern.h b/xpdf/SplashPattern.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashPattern.h
@@ -0,0 +1,65 @@
+//========================================================================
+//
+// SplashPattern.h
+//
+//========================================================================
+
+#ifndef SPLASHPATTERN_H
+#define SPLASHPATTERN_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashTypes.h"
+
+class SplashScreen;
+
+//------------------------------------------------------------------------
+// SplashPattern
+//------------------------------------------------------------------------
+
+class SplashPattern {
+public:
+
+  SplashPattern();
+
+  virtual SplashPattern *copy() = 0;
+
+  virtual ~SplashPattern();
+
+  // Return the color value for a specific pixel.
+  virtual void getColor(int x, int y, SplashColorPtr c) = 0;
+
+  // Returns true if this pattern object will return the same color
+  // value for all pixels.
+  virtual GBool isStatic() = 0;
+
+private:
+};
+
+//------------------------------------------------------------------------
+// SplashSolidColor
+//------------------------------------------------------------------------
+
+class SplashSolidColor: public SplashPattern {
+public:
+
+  SplashSolidColor(SplashColorPtr colorA);
+
+  virtual SplashPattern *copy() { return new SplashSolidColor(color); }
+
+  virtual ~SplashSolidColor();
+
+  virtual void getColor(int x, int y, SplashColorPtr c);
+
+  virtual GBool isStatic() { return gTrue; }
+
+private:
+
+  SplashColor color;
+};
+
+#endif
diff --git a/xpdf/SplashScreen.cc b/xpdf/SplashScreen.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashScreen.cc
@@ -0,0 +1,383 @@
+//========================================================================
+//
+// SplashScreen.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "gmem.h"
+#include "SplashMath.h"
+#include "SplashScreen.h"
+
+//------------------------------------------------------------------------
+
+static SplashScreenParams defaultParams = {
+  splashScreenDispersed,	// type
+  2,				// size
+  2,				// dotRadius
+  1.0,				// gamma
+  0.0,				// blackThreshold
+  1.0				// whiteThreshold
+};
+
+//------------------------------------------------------------------------
+
+struct SplashScreenPoint {
+  int x, y;
+  int dist;
+};
+
+static int cmpDistances(const void *p0, const void *p1) {
+  return ((SplashScreenPoint *)p0)->dist - ((SplashScreenPoint *)p1)->dist;
+}
+
+//------------------------------------------------------------------------
+// SplashScreen
+//------------------------------------------------------------------------
+
+// If <clustered> is true, this generates a 45 degree screen using a
+// circular dot spot function.  DPI = resolution / ((size / 2) *
+// sqrt(2)).  If <clustered> is false, this generates an optimal
+// threshold matrix using recursive tesselation.  Gamma correction
+// (gamma = 1 / 1.33) is also computed here.
+SplashScreen::SplashScreen(SplashScreenParams *params) {
+  Guchar u, black, white;
+  int i;
+
+  if (!params) {
+    params = &defaultParams;
+  }
+
+  switch (params->type) {
+
+  case splashScreenDispersed:
+    // size must be a power of 2
+    for (size = 1; size < params->size; size <<= 1) ;
+    mat = (Guchar *)gmallocn(size * size, sizeof(Guchar));
+    buildDispersedMatrix(size/2, size/2, 1, size/2, 1);
+    break;
+
+  case splashScreenClustered:
+    // size must be even
+    size = (params->size >> 1) << 1;
+    if (size < 2) {
+      size = 2;
+    }
+    mat = (Guchar *)gmallocn(size * size, sizeof(Guchar));
+    buildClusteredMatrix();
+    break;
+
+  case splashScreenStochasticClustered:
+    // size must be at least 2*r
+    if (params->size < 2 * params->dotRadius) {
+      size = 2 * params->dotRadius;
+    } else {
+      size = params->size;
+    }
+    mat = (Guchar *)gmallocn(size * size, sizeof(Guchar));
+    buildSCDMatrix(params->dotRadius);
+    break;
+  }
+
+  // do gamma correction and compute minVal/maxVal
+  minVal = 255;
+  maxVal = 0;
+  black = splashRound((SplashCoord)255.0 * params->blackThreshold);
+  if (black < 1) {
+    black = 1;
+  }
+  white = splashRound((SplashCoord)255.0 * params->whiteThreshold);
+  if (white > 255) {
+    white = 255;
+  }
+  for (i = 0; i < size * size; ++i) {
+    u = splashRound((SplashCoord)255.0 *
+		    splashPow((SplashCoord)mat[i] / 255.0, params->gamma));
+    if (u < black) {
+      u = black;
+    } else if (u >= white) {
+      u = white;
+    }
+    mat[i] = u;
+    if (u < minVal) {
+      minVal = u;
+    } else if (u > maxVal) {
+      maxVal = u;
+    }
+  }
+}
+
+void SplashScreen::buildDispersedMatrix(int i, int j, int val,
+					int delta, int offset) {
+  if (delta == 0) {
+    // map values in [1, size^2] --> [1, 255]
+    mat[i * size + j] = 1 + (254 * (val - 1)) / (size * size - 1);
+  } else {
+    buildDispersedMatrix(i, j,
+			 val, delta / 2, 4*offset);
+    buildDispersedMatrix((i + delta) % size, (j + delta) % size,
+			 val + offset, delta / 2, 4*offset);
+    buildDispersedMatrix((i + delta) % size, j,
+			 val + 2*offset, delta / 2, 4*offset);
+    buildDispersedMatrix((i + 2*delta) % size, (j + delta) % size,
+			 val + 3*offset, delta / 2, 4*offset);
+  }
+}
+
+void SplashScreen::buildClusteredMatrix() {
+  SplashCoord *dist;
+  SplashCoord u, v, d;
+  Guchar val;
+  int size2, x, y, x1, y1, i;
+
+  size2 = size >> 1;
+
+  // initialize the threshold matrix
+  for (y = 0; y < size; ++y) {
+    for (x = 0; x < size; ++x) {
+      mat[y * size + x] = 0;
+    }
+  }
+
+  // build the distance matrix
+  dist = (SplashCoord *)gmallocn(size * size2, sizeof(SplashCoord));
+  for (y = 0; y < size2; ++y) {
+    for (x = 0; x < size2; ++x) {
+      if (x + y < size2 - 1) {
+	u = (SplashCoord)x + 0.5 - 0;
+	v = (SplashCoord)y + 0.5 - 0;
+      } else {
+	u = (SplashCoord)x + 0.5 - (SplashCoord)size2;
+	v = (SplashCoord)y + 0.5 - (SplashCoord)size2;
+      }
+      dist[y * size2 + x] = u*u + v*v;
+    }
+  }
+  for (y = 0; y < size2; ++y) {
+    for (x = 0; x < size2; ++x) {
+      if (x < y) {
+	u = (SplashCoord)x + 0.5 - 0;
+	v = (SplashCoord)y + 0.5 - (SplashCoord)size2;
+      } else {
+	u = (SplashCoord)x + 0.5 - (SplashCoord)size2;
+	v = (SplashCoord)y + 0.5 - 0;
+      }
+      dist[(size2 + y) * size2 + x] = u*u + v*v;
+    }
+  }
+
+  // build the threshold matrix
+  minVal = 1;
+  maxVal = 0;
+  x1 = y1 = 0; // make gcc happy
+  for (i = 0; i < size * size2; ++i) {
+    d = -1;
+    for (y = 0; y < size; ++y) {
+      for (x = 0; x < size2; ++x) {
+	if (mat[y * size + x] == 0 &&
+	    dist[y * size2 + x] > d) {
+	  x1 = x;
+	  y1 = y;
+	  d = dist[y1 * size2 + x1];
+	}
+      }
+    }
+    // map values in [0, 2*size*size2-1] --> [1, 255]
+    val = 1 + (254 * (2*i)) / (2*size*size2 - 1);
+    mat[y1 * size + x1] = val;
+    val = 1 + (254 * (2*i+1)) / (2*size*size2 - 1);
+    if (y1 < size2) {
+      mat[(y1 + size2) * size + x1 + size2] = val;
+    } else {
+      mat[(y1 - size2) * size + x1 + size2] = val;
+    }
+  }
+
+  gfree(dist);
+}
+
+// Compute the distance between two points on a toroid.
+int SplashScreen::distance(int x0, int y0, int x1, int y1) {
+  int dx0, dx1, dx, dy0, dy1, dy;
+
+  dx0 = abs(x0 - x1);
+  dx1 = size - dx0;
+  dx = dx0 < dx1 ? dx0 : dx1;
+  dy0 = abs(y0 - y1);
+  dy1 = size - dy0;
+  dy = dy0 < dy1 ? dy0 : dy1;
+  return dx * dx + dy * dy;
+}
+
+// Algorithm taken from:
+// Victor Ostromoukhov and Roger D. Hersch, "Stochastic Clustered-Dot
+// Dithering" in Color Imaging: Device-Independent Color, Color
+// Hardcopy, and Graphic Arts IV, SPIE Vol. 3648, pp. 496-505, 1999.
+void SplashScreen::buildSCDMatrix(int r) {
+  SplashScreenPoint *dots, *pts;
+  int dotsLen, dotsSize;
+  char *tmpl;
+  char *grid;
+  int *region, *dist;
+  int x, y, xx, yy, x0, x1, y0, y1, i, j, d, iMin, dMin, n;
+
+  //~ this should probably happen somewhere else
+  srand(123);
+
+  // generate the random space-filling curve
+  pts = (SplashScreenPoint *)gmallocn(size * size, sizeof(SplashScreenPoint));
+  i = 0;
+  for (y = 0; y < size; ++y) {
+    for (x = 0; x < size; ++x) {
+      pts[i].x = x;
+      pts[i].y = y;
+      ++i;
+    }
+  }
+  for (i = 0; i < size * size; ++i) {
+    j = i + (int)((double)(size * size - i) *
+		  (double)rand() / ((double)RAND_MAX + 1.0));
+    x = pts[i].x;
+    y = pts[i].y;
+    pts[i].x = pts[j].x;
+    pts[i].y = pts[j].y;
+    pts[j].x = x;
+    pts[j].y = y;
+  }
+
+  // construct the circle template
+  tmpl = (char *)gmallocn((r+1)*(r+1), sizeof(char));
+  for (y = 0; y <= r; ++y) {
+    for (x = 0; x <= r; ++x) {
+      tmpl[y*(r+1) + x] = (x * y <= r * r) ? 1 : 0;
+    }
+  }
+
+  // mark all grid cells as free
+  grid = (char *)gmallocn(size * size, sizeof(char));
+  for (y = 0; y < size; ++y) {
+    for (x = 0; x < size; ++x) {
+      grid[y*size + x] = 0;
+    }
+  }
+
+  // walk the space-filling curve, adding dots
+  dotsLen = 0;
+  dotsSize = 32;
+  dots = (SplashScreenPoint *)gmallocn(dotsSize, sizeof(SplashScreenPoint));
+  for (i = 0; i < size * size; ++i) {
+    x = pts[i].x;
+    y = pts[i].y;
+    if (!grid[y*size + x]) {
+      if (dotsLen == dotsSize) {
+	dotsSize *= 2;
+	dots = (SplashScreenPoint *)greallocn(dots, dotsSize,
+					      sizeof(SplashScreenPoint));
+      }
+      dots[dotsLen++] = pts[i];
+      for (yy = 0; yy <= r; ++yy) {
+	y0 = (y + yy) % size;
+	y1 = (y - yy + size) % size;
+	for (xx = 0; xx <= r; ++xx) {
+	  if (tmpl[yy*(r+1) + xx]) {
+	    x0 = (x + xx) % size;
+	    x1 = (x - xx + size) % size;
+	    grid[y0*size + x0] = 1;
+	    grid[y0*size + x1] = 1;
+	    grid[y1*size + x0] = 1;
+	    grid[y1*size + x1] = 1;
+	  }
+	}
+      }
+    }
+  }
+
+  gfree(tmpl);
+  gfree(grid);
+
+  // assign each cell to a dot, compute distance to center of dot
+  region = (int *)gmallocn(size * size, sizeof(int));
+  dist = (int *)gmallocn(size * size, sizeof(int));
+  for (y = 0; y < size; ++y) {
+    for (x = 0; x < size; ++x) {
+      iMin = 0;
+      dMin = distance(dots[0].x, dots[0].y, x, y);
+      for (i = 1; i < dotsLen; ++i) {
+	d = distance(dots[i].x, dots[i].y, x, y);
+	if (d < dMin) {
+	  iMin = i;
+	  dMin = d;
+	}
+      }
+      region[y*size + x] = iMin;
+      dist[y*size + x] = dMin;
+    }
+  }
+
+  // compute threshold values
+  for (i = 0; i < dotsLen; ++i) {
+    n = 0;
+    for (y = 0; y < size; ++y) {
+      for (x = 0; x < size; ++x) {
+	if (region[y*size + x] == i) {
+	  pts[n].x = x;
+	  pts[n].y = y;
+	  pts[n].dist = distance(dots[i].x, dots[i].y, x, y);
+	  ++n;
+	}
+      }
+    }
+    qsort(pts, n, sizeof(SplashScreenPoint), &cmpDistances);
+    for (j = 0; j < n; ++j) {
+      // map values in [0 .. n-1] --> [255 .. 1]
+      mat[pts[j].y * size + pts[j].x] = 255 - (254 * j) / (n - 1);
+    }
+  }
+
+  gfree(pts);
+  gfree(region);
+  gfree(dist);
+
+  gfree(dots);
+}
+
+SplashScreen::SplashScreen(SplashScreen *screen) {
+  size = screen->size;
+  mat = (Guchar *)gmallocn(size * size, sizeof(Guchar));
+  memcpy(mat, screen->mat, size * size * sizeof(Guchar));
+  minVal = screen->minVal;
+  maxVal = screen->maxVal;
+}
+
+SplashScreen::~SplashScreen() {
+  gfree(mat);
+}
+
+int SplashScreen::test(int x, int y, Guchar value) {
+  int xx, yy;
+
+  if (value < minVal) {
+    return 0;
+  }
+  if (value >= maxVal) {
+    return 1;
+  }
+  if ((xx = x % size) < 0) {
+    xx = -xx;
+  }
+  if ((yy = y % size) < 0) {
+    yy = -yy;
+  }
+  return value < mat[yy * size + xx] ? 0 : 1;
+}
+
+GBool SplashScreen::isStatic(Guchar value) {
+  return value < minVal || value >= maxVal;
+}
diff --git a/xpdf/SplashScreen.h b/xpdf/SplashScreen.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashScreen.h
@@ -0,0 +1,56 @@
+//========================================================================
+//
+// SplashScreen.h
+//
+//========================================================================
+
+#ifndef SPLASHSCREEN_H
+#define SPLASHSCREEN_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashTypes.h"
+
+//------------------------------------------------------------------------
+// SplashScreen
+//------------------------------------------------------------------------
+
+class SplashScreen {
+public:
+
+  SplashScreen(SplashScreenParams *params);
+  SplashScreen(SplashScreen *screen);
+  ~SplashScreen();
+
+  SplashScreen *copy() { return new SplashScreen(this); }
+
+  // Return the computed pixel value (0=black, 1=white) for the gray
+  // level <value> at (<x>, <y>).
+  int test(int x, int y, Guchar value);
+
+  // Returns true if value is above the white threshold or below the
+  // black threshold, i.e., if the corresponding halftone will be
+  // solid white or black.
+  GBool isStatic(Guchar value);
+
+private:
+
+  void buildDispersedMatrix(int i, int j, int val,
+			    int delta, int offset);
+  void buildClusteredMatrix();
+  int distance(int x0, int y0, int x1, int y1);
+  void buildSCDMatrix(int r);
+
+  Guchar *mat;			// threshold matrix
+  int size;			// size of the threshold matrix
+  Guchar minVal;		// any pixel value below minVal generates
+				//   solid black
+  Guchar maxVal;		// any pixel value above maxVal generates
+				//   solid white
+};
+
+#endif
diff --git a/xpdf/SplashState.cc b/xpdf/SplashState.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashState.cc
@@ -0,0 +1,165 @@
+//========================================================================
+//
+// SplashState.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <string.h>
+#include "gmem.h"
+#include "SplashPattern.h"
+#include "SplashScreen.h"
+#include "SplashClip.h"
+#include "SplashBitmap.h"
+#include "SplashState.h"
+
+//------------------------------------------------------------------------
+// SplashState
+//------------------------------------------------------------------------
+
+// number of components in each color mode
+int splashColorModeNComps[] = {
+  1, 1, 3, 3, 4
+};
+
+SplashState::SplashState(int width, int height, GBool vectorAntialias,
+			 SplashScreenParams *screenParams) {
+  SplashColor color;
+
+  matrix[0] = 1;  matrix[1] = 0;
+  matrix[2] = 0;  matrix[3] = 1;
+  matrix[4] = 0;  matrix[5] = 0;
+  memset(&color, 0, sizeof(SplashColor));
+  strokePattern = new SplashSolidColor(color);
+  fillPattern = new SplashSolidColor(color);
+  screen = new SplashScreen(screenParams);
+  blendFunc = NULL;
+  strokeAlpha = 1;
+  fillAlpha = 1;
+  lineWidth = 0;
+  lineCap = splashLineCapButt;
+  lineJoin = splashLineJoinMiter;
+  miterLimit = 10;
+  flatness = 1;
+  lineDash = NULL;
+  lineDashLength = 0;
+  lineDashPhase = 0;
+  strokeAdjust = gFalse;
+  clip = new SplashClip(0, 0, width - 0.001, height - 0.001, vectorAntialias);
+  softMask = NULL;
+  deleteSoftMask = gFalse;
+  inNonIsolatedGroup = gFalse;
+  next = NULL;
+}
+
+SplashState::SplashState(int width, int height, GBool vectorAntialias,
+			 SplashScreen *screenA) {
+  SplashColor color;
+
+  matrix[0] = 1;  matrix[1] = 0;
+  matrix[2] = 0;  matrix[3] = 1;
+  matrix[4] = 0;  matrix[5] = 0;
+  memset(&color, 0, sizeof(SplashColor));
+  strokePattern = new SplashSolidColor(color);
+  fillPattern = new SplashSolidColor(color);
+  screen = screenA->copy();
+  blendFunc = NULL;
+  strokeAlpha = 1;
+  fillAlpha = 1;
+  lineWidth = 0;
+  lineCap = splashLineCapButt;
+  lineJoin = splashLineJoinMiter;
+  miterLimit = 10;
+  flatness = 1;
+  lineDash = NULL;
+  lineDashLength = 0;
+  lineDashPhase = 0;
+  strokeAdjust = gFalse;
+  clip = new SplashClip(0, 0, width - 0.001, height - 0.001, vectorAntialias);
+  softMask = NULL;
+  deleteSoftMask = gFalse;
+  inNonIsolatedGroup = gFalse;
+  next = NULL;
+}
+
+SplashState::SplashState(SplashState *state) {
+  memcpy(matrix, state->matrix, 6 * sizeof(SplashCoord));
+  strokePattern = state->strokePattern->copy();
+  fillPattern = state->fillPattern->copy();
+  screen = state->screen->copy();
+  blendFunc = state->blendFunc;
+  strokeAlpha = state->strokeAlpha;
+  fillAlpha = state->fillAlpha;
+  lineWidth = state->lineWidth;
+  lineCap = state->lineCap;
+  lineJoin = state->lineJoin;
+  miterLimit = state->miterLimit;
+  flatness = state->flatness;
+  if (state->lineDash) {
+    lineDashLength = state->lineDashLength;
+    lineDash = (SplashCoord *)gmallocn(lineDashLength, sizeof(SplashCoord));
+    memcpy(lineDash, state->lineDash, lineDashLength * sizeof(SplashCoord));
+  } else {
+    lineDash = NULL;
+    lineDashLength = 0;
+  }
+  lineDashPhase = state->lineDashPhase;
+  strokeAdjust = state->strokeAdjust;
+  clip = state->clip->copy();
+  softMask = state->softMask;
+  deleteSoftMask = gFalse;
+  inNonIsolatedGroup = state->inNonIsolatedGroup;
+  next = NULL;
+}
+
+SplashState::~SplashState() {
+  delete strokePattern;
+  delete fillPattern;
+  delete screen;
+  gfree(lineDash);
+  delete clip;
+  if (deleteSoftMask && softMask) {
+    delete softMask;
+  }
+}
+
+void SplashState::setStrokePattern(SplashPattern *strokePatternA) {
+  delete strokePattern;
+  strokePattern = strokePatternA;
+}
+
+void SplashState::setFillPattern(SplashPattern *fillPatternA) {
+  delete fillPattern;
+  fillPattern = fillPatternA;
+}
+
+void SplashState::setScreen(SplashScreen *screenA) {
+  delete screen;
+  screen = screenA;
+}
+
+void SplashState::setLineDash(SplashCoord *lineDashA, int lineDashLengthA,
+			      SplashCoord lineDashPhaseA) {
+  gfree(lineDash);
+  lineDashLength = lineDashLengthA;
+  if (lineDashLength > 0) {
+    lineDash = (SplashCoord *)gmallocn(lineDashLength, sizeof(SplashCoord));
+    memcpy(lineDash, lineDashA, lineDashLength * sizeof(SplashCoord));
+  } else {
+    lineDash = NULL;
+  }
+  lineDashPhase = lineDashPhaseA;
+}
+
+void SplashState::setSoftMask(SplashBitmap *softMaskA) {
+  if (deleteSoftMask) {
+    delete softMask;
+  }
+  softMask = softMaskA;
+  deleteSoftMask = gTrue;
+}
diff --git a/xpdf/SplashState.h b/xpdf/SplashState.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashState.h
@@ -0,0 +1,103 @@
+//========================================================================
+//
+// SplashState.h
+//
+//========================================================================
+
+#ifndef SPLASHSTATE_H
+#define SPLASHSTATE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashTypes.h"
+
+class SplashPattern;
+class SplashScreen;
+class SplashClip;
+class SplashBitmap;
+
+//------------------------------------------------------------------------
+// line cap values
+//------------------------------------------------------------------------
+
+#define splashLineCapButt       0
+#define splashLineCapRound      1
+#define splashLineCapProjecting 2
+
+//------------------------------------------------------------------------
+// line join values
+//------------------------------------------------------------------------
+
+#define splashLineJoinMiter     0
+#define splashLineJoinRound     1
+#define splashLineJoinBevel     2
+
+//------------------------------------------------------------------------
+// SplashState
+//------------------------------------------------------------------------
+
+class SplashState {
+public:
+
+  // Create a new state object, initialized with default settings.
+  SplashState(int width, int height, GBool vectorAntialias,
+	      SplashScreenParams *screenParams);
+  SplashState(int width, int height, GBool vectorAntialias,
+	      SplashScreen *screenA);
+
+  // Copy a state object.
+  SplashState *copy() { return new SplashState(this); }
+
+  ~SplashState();
+
+  // Set the stroke pattern.  This does not copy <strokePatternA>.
+  void setStrokePattern(SplashPattern *strokePatternA);
+
+  // Set the fill pattern.  This does not copy <fillPatternA>.
+  void setFillPattern(SplashPattern *fillPatternA);
+
+  // Set the screen.  This does not copy <screenA>.
+  void setScreen(SplashScreen *screenA);
+
+  // Set the line dash pattern.  This copies the <lineDashA> array.
+  void setLineDash(SplashCoord *lineDashA, int lineDashLengthA,
+		   SplashCoord lineDashPhaseA);
+
+  // Set the soft mask bitmap.
+  void setSoftMask(SplashBitmap *softMaskA);
+
+private:
+
+  SplashState(SplashState *state);
+
+  SplashCoord matrix[6];
+  SplashPattern *strokePattern;
+  SplashPattern *fillPattern;
+  SplashScreen *screen;
+  SplashBlendFunc blendFunc;
+  SplashCoord strokeAlpha;
+  SplashCoord fillAlpha;
+  SplashCoord lineWidth;
+  int lineCap;
+  int lineJoin;
+  SplashCoord miterLimit;
+  SplashCoord flatness;
+  SplashCoord *lineDash;
+  int lineDashLength;
+  SplashCoord lineDashPhase;
+  GBool strokeAdjust;
+  SplashClip *clip;
+  SplashBitmap *softMask;
+  GBool deleteSoftMask;
+  GBool inNonIsolatedGroup;
+
+  SplashState *next;		// used by Splash class
+
+  friend class Splash;
+};
+
+#endif
diff --git a/xpdf/SplashT1Font.cc b/xpdf/SplashT1Font.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashT1Font.cc
@@ -0,0 +1,287 @@
+//========================================================================
+//
+// SplashT1Font.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#if HAVE_T1LIB_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <t1lib.h>
+#include "gmem.h"
+#include "SplashMath.h"
+#include "SplashGlyphBitmap.h"
+#include "SplashPath.h"
+#include "SplashT1FontEngine.h"
+#include "SplashT1FontFile.h"
+#include "SplashT1Font.h"
+
+//------------------------------------------------------------------------
+
+static Guchar bitReverse[256] = {
+  0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
+  0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+  0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
+  0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+  0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
+  0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+  0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
+  0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+  0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
+  0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+  0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
+  0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+  0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
+  0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+  0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
+  0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+  0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
+  0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+  0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
+  0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+  0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
+  0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+  0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
+  0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+  0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
+  0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+  0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
+  0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+  0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
+  0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+  0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
+  0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
+};
+
+//------------------------------------------------------------------------
+// SplashT1Font
+//------------------------------------------------------------------------
+
+SplashT1Font::SplashT1Font(SplashT1FontFile *fontFileA, SplashCoord *matA,
+			   SplashCoord *textMatA):
+  SplashFont(fontFileA, matA, textMatA, fontFileA->engine->aa)
+{
+  T1_TMATRIX matrix;
+  BBox bbox;
+  SplashCoord bbx0, bby0, bbx1, bby1;
+  int x, y;
+
+  t1libID = T1_CopyFont(fontFileA->t1libID);
+  outlineID = -1;
+
+  // compute font size
+  size = (float)splashSqrt(mat[2]*mat[2] + mat[3]*mat[3]);
+
+  // transform the four corners of the font bounding box -- the min
+  // and max values form the bounding box of the transformed font
+  bbox = T1_GetFontBBox(t1libID);
+  bbx0 = 0.001 * bbox.llx;
+  bby0 = 0.001 * bbox.lly;
+  bbx1 = 0.001 * bbox.urx;
+  bby1 = 0.001 * bbox.ury;
+  // some fonts are completely broken, so we fake it (with values
+  // large enough that most glyphs should fit)
+  if (bbx0 == 0 && bby0 == 0 && bbx1 == 0 && bby1 == 0) {
+    bbx0 = bby0 = -0.5;
+    bbx1 = bby1 = 1.5;
+  }
+  x = (int)(mat[0] * bbx0 + mat[2] * bby0);
+  xMin = xMax = x;
+  y = (int)(mat[1] * bbx0 + mat[3] * bby0);
+  yMin = yMax = y;
+  x = (int)(mat[0] * bbx0 + mat[2] * bby1);
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  y = (int)(mat[1] * bbx0 + mat[3] * bby1);
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  x = (int)(mat[0] * bbx1 + mat[2] * bby0);
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  y = (int)(mat[1] * bbx1 + mat[3] * bby0);
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  x = (int)(mat[0] * bbx1 + mat[2] * bby1);
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  y = (int)(mat[1] * bbx1 + mat[3] * bby1);
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  // This is a kludge: some buggy PDF generators embed fonts with
+  // zero bounding boxes.
+  if (xMax == xMin) {
+    xMin = 0;
+    xMax = (int)size;
+  }
+  if (yMax == yMin) {
+    yMin = 0;
+    yMax = (int)(1.2 * size);
+  }
+  // Another kludge: an unusually large xMin or yMin coordinate is
+  // probably wrong.
+  if (xMin > 0) {
+    xMin = 0;
+  }
+  if (yMin > 0) {
+    yMin = 0;
+  }
+  // Another kludge: t1lib doesn't correctly handle fonts with
+  // real (non-integer) bounding box coordinates.
+  if (xMax - xMin > 5000) {
+    xMin = 0;
+    xMax = (int)size;
+  }
+  if (yMax - yMin > 5000) {
+    yMin = 0;
+    yMax = (int)(1.2 * size);
+  }
+
+  // transform the font
+  matrix.cxx = (double)mat[0] / size;
+  matrix.cxy = (double)mat[1] / size;
+  matrix.cyx = (double)mat[2] / size;
+  matrix.cyy = (double)mat[3] / size;
+  T1_TransformFont(t1libID, &matrix);
+}
+
+SplashT1Font::~SplashT1Font() {
+  T1_DeleteFont(t1libID);
+  if (outlineID >= 0) {
+    T1_DeleteFont(outlineID);
+  }
+}
+
+GBool SplashT1Font::getGlyph(int c, int xFrac, int yFrac,
+			     SplashGlyphBitmap *bitmap) {
+  return SplashFont::getGlyph(c, 0, 0, bitmap);
+}
+
+GBool SplashT1Font::makeGlyph(int c, int xFrac, int yFrac,
+			      SplashGlyphBitmap *bitmap) {
+  GLYPH *glyph;
+  int n, i;
+
+  if (aa) {
+    glyph = T1_AASetChar(t1libID, c, size, NULL);
+  } else {
+    glyph = T1_SetChar(t1libID, c, size, NULL);
+  }
+  if (!glyph) {
+    return gFalse;
+  }
+
+  bitmap->x = -glyph->metrics.leftSideBearing;
+  bitmap->y = glyph->metrics.ascent;
+  bitmap->w = glyph->metrics.rightSideBearing - glyph->metrics.leftSideBearing;
+  bitmap->h = glyph->metrics.ascent - glyph->metrics.descent;
+  bitmap->aa = aa;
+  if (aa) {
+    bitmap->data = (Guchar *)glyph->bits;
+    bitmap->freeData = gFalse;
+  } else {
+    n = bitmap->h * ((bitmap->w + 7) >> 3);
+    bitmap->data = (Guchar *)gmalloc(n);
+    for (i = 0; i < n; ++i) {
+      bitmap->data[i] = bitReverse[glyph->bits[i] & 0xff];
+    }
+    bitmap->freeData = gTrue;
+  }
+
+  return gTrue;
+}
+
+SplashPath *SplashT1Font::getGlyphPath(int c) {
+  T1_TMATRIX matrix;
+  SplashPath *path;
+  T1_OUTLINE *outline;
+  T1_PATHSEGMENT *seg;
+  T1_BEZIERSEGMENT *bez;
+  SplashCoord x, y, x1, y1;
+  GBool needClose;
+
+  if (outlineID < 0) {
+    outlineID = T1_CopyFont(((SplashT1FontFile *)fontFile)->t1libID);
+    outlineSize = (float)splashSqrt(textMat[2]*textMat[2] +
+				    textMat[3]*textMat[3]);
+    matrix.cxx = (double)textMat[0] / outlineSize;
+    matrix.cxy = (double)textMat[1] / outlineSize;
+    matrix.cyx = (double)textMat[2] / outlineSize;
+    matrix.cyy = (double)textMat[3] / outlineSize;
+    // t1lib doesn't seem to handle small sizes correctly here, so set
+    // the size to 1000, and scale the resulting coordinates later
+    outlineMul = (float)(outlineSize / 65536000.0);
+    outlineSize = 1000;
+    T1_TransformFont(outlineID, &matrix);
+  }
+
+  path = new SplashPath();
+  if ((outline = T1_GetCharOutline(outlineID, c, outlineSize, NULL))) {
+    x = 0;
+    y = 0;
+    needClose = gFalse;
+    for (seg = outline; seg; seg = seg->link) {
+      switch (seg->type) {
+      case T1_PATHTYPE_MOVE:
+	if (needClose) {
+	  path->close();
+	  needClose = gFalse;
+	}
+	x += seg->dest.x * outlineMul;
+	y += seg->dest.y * outlineMul;
+	path->moveTo(x, -y);
+	break;
+      case T1_PATHTYPE_LINE:
+	x += seg->dest.x * outlineMul;
+	y += seg->dest.y * outlineMul;
+	path->lineTo(x, -y);
+	needClose = gTrue;
+	break;
+      case T1_PATHTYPE_BEZIER:
+	bez = (T1_BEZIERSEGMENT *)seg;
+	x1 = x + (SplashCoord)(bez->dest.x * outlineMul);
+	y1 = y + (SplashCoord)(bez->dest.y * outlineMul);
+	path->curveTo(x + (SplashCoord)(bez->B.x * outlineMul),
+		      -(y + (SplashCoord)(bez->B.y * outlineMul)),
+		      x + (SplashCoord)(bez->C.x * outlineMul),
+		      -(y + (SplashCoord)(bez->C.y * outlineMul)),
+		      x1, -y1);
+	x = x1;
+	y = y1;
+	needClose = gTrue;
+	break;
+      }
+    }
+    if (needClose) {
+      path->close();
+    }
+    T1_FreeOutline(outline);
+  }
+
+  return path;
+}
+
+#endif // HAVE_T1LIB_H
diff --git a/xpdf/SplashT1Font.h b/xpdf/SplashT1Font.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashT1Font.h
@@ -0,0 +1,57 @@
+//========================================================================
+//
+// SplashT1Font.h
+//
+//========================================================================
+
+#ifndef SPLASHT1FONT_H
+#define SPLASHT1FONT_H
+
+#include <aconf.h>
+
+#if HAVE_T1LIB_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashFont.h"
+
+class SplashT1FontFile;
+
+//------------------------------------------------------------------------
+// SplashT1Font
+//------------------------------------------------------------------------
+
+class SplashT1Font: public SplashFont {
+public:
+
+  SplashT1Font(SplashT1FontFile *fontFileA, SplashCoord *matA,
+	       SplashCoord *textMatA);
+
+  virtual ~SplashT1Font();
+
+  // Munge xFrac and yFrac before calling SplashFont::getGlyph.
+  virtual GBool getGlyph(int c, int xFrac, int yFrac,
+			 SplashGlyphBitmap *bitmap);
+
+  // Rasterize a glyph.  The <xFrac> and <yFrac> values are the same
+  // as described for getGlyph.
+  virtual GBool makeGlyph(int c, int xFrac, int yFrac,
+			  SplashGlyphBitmap *bitmap);
+
+  // Return the path for a glyph.
+  virtual SplashPath *getGlyphPath(int c);
+
+private:
+
+  int t1libID;			// t1lib font ID
+  int outlineID;		// t1lib font ID for glyph outlines
+  float size;
+  float outlineSize;		// size for glyph outlines
+  float outlineMul;
+};
+
+#endif // HAVE_T1LIB_H
+
+#endif
diff --git a/xpdf/SplashT1FontEngine.cc b/xpdf/SplashT1FontEngine.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashT1FontEngine.cc
@@ -0,0 +1,124 @@
+//========================================================================
+//
+// SplashT1FontEngine.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#if HAVE_T1LIB_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#ifndef WIN32
+#  include <unistd.h>
+#endif
+#include <t1lib.h>
+#include "GString.h"
+#include "gfile.h"
+#include "FoFiType1C.h"
+#include "SplashT1FontFile.h"
+#include "SplashT1FontEngine.h"
+
+#ifdef VMS
+#if (__VMS_VER < 70000000)
+extern "C" int unlink(char *filename);
+#endif
+#endif
+
+//------------------------------------------------------------------------
+
+int SplashT1FontEngine::t1libInitCount = 0;
+
+//------------------------------------------------------------------------
+
+static void fileWrite(void *stream, char *data, int len) {
+  fwrite(data, 1, len, (FILE *)stream);
+}
+
+//------------------------------------------------------------------------
+// SplashT1FontEngine
+//------------------------------------------------------------------------
+
+SplashT1FontEngine::SplashT1FontEngine(GBool aaA) {
+  aa = aaA;
+}
+
+SplashT1FontEngine *SplashT1FontEngine::init(GBool aaA) {
+  // grayVals[i] = round(i * 255 / 16)
+  static unsigned long grayVals[17] = {
+    0, 16, 32, 48, 64, 80, 96, 112, 128, 143, 159, 175, 191, 207, 223, 239, 255
+  };
+
+  //~ for multithreading: need a mutex here
+  if (t1libInitCount == 0) {
+    T1_SetBitmapPad(8);
+    if (!T1_InitLib(NO_LOGFILE | IGNORE_CONFIGFILE | IGNORE_FONTDATABASE |
+		    T1_NO_AFM)) {
+      return NULL;
+    }
+    if (aaA) {
+      T1_AASetBitsPerPixel(8);
+      T1_AASetLevel(T1_AA_HIGH);
+      T1_AAHSetGrayValues(grayVals);
+    } else {
+      T1_AANSetGrayValues(0, 1);
+    }
+  }
+  ++t1libInitCount;
+
+  return new SplashT1FontEngine(aaA);
+}
+
+SplashT1FontEngine::~SplashT1FontEngine() {
+  //~ for multithreading: need a mutex here
+  if (--t1libInitCount == 0) {
+    T1_CloseLib();
+  }
+}
+
+SplashFontFile *SplashT1FontEngine::loadType1Font(SplashFontFileID *idA,
+						  char *fileName,
+						  GBool deleteFile,
+						  char **enc) {
+  return SplashT1FontFile::loadType1Font(this, idA, fileName, deleteFile, enc);
+}
+
+SplashFontFile *SplashT1FontEngine::loadType1CFont(SplashFontFileID *idA,
+						   char *fileName,
+						   GBool deleteFile,
+						   char **enc) {
+  FoFiType1C *ff;
+  GString *tmpFileName;
+  FILE *tmpFile;
+  SplashFontFile *ret;
+
+  if (!(ff = FoFiType1C::load(fileName))) {
+    return NULL;
+  }
+  tmpFileName = NULL;
+  if (!openTempFile(&tmpFileName, &tmpFile, "wb", NULL)) {
+    delete ff;
+    return NULL;
+  }
+  ff->convertToType1(NULL, NULL, gTrue, &fileWrite, tmpFile);
+  delete ff;
+  fclose(tmpFile);
+  ret = SplashT1FontFile::loadType1Font(this, idA, tmpFileName->getCString(),
+					gTrue, enc);
+  if (ret) {
+    if (deleteFile) {
+      unlink(fileName);
+    }
+  } else {
+    unlink(tmpFileName->getCString());
+  }
+  delete tmpFileName;
+  return ret;
+}
+
+#endif // HAVE_T1LIB_H
diff --git a/xpdf/SplashT1FontEngine.h b/xpdf/SplashT1FontEngine.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashT1FontEngine.h
@@ -0,0 +1,53 @@
+//========================================================================
+//
+// SplashT1FontEngine.h
+//
+//========================================================================
+
+#ifndef SPLASHT1FONTENGINE_H
+#define SPLASHT1FONTENGINE_H
+
+#include <aconf.h>
+
+#if HAVE_T1LIB_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+
+class SplashFontFile;
+class SplashFontFileID;
+
+//------------------------------------------------------------------------
+// SplashT1FontEngine
+//------------------------------------------------------------------------
+
+class SplashT1FontEngine {
+public:
+
+  static SplashT1FontEngine *init(GBool aaA);
+
+  ~SplashT1FontEngine();
+
+  // Load fonts.
+  SplashFontFile *loadType1Font(SplashFontFileID *idA, char *fileName,
+				GBool deleteFile, char **enc);
+  SplashFontFile *loadType1CFont(SplashFontFileID *idA, char *fileName,
+				 GBool deleteFile, char **enc);
+
+private:
+
+  SplashT1FontEngine(GBool aaA);
+
+  static int t1libInitCount;
+  GBool aa;
+
+  friend class SplashT1FontFile;
+  friend class SplashT1Font;
+};
+
+#endif // HAVE_T1LIB_H
+
+#endif
diff --git a/xpdf/SplashT1FontFile.cc b/xpdf/SplashT1FontFile.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashT1FontFile.cc
@@ -0,0 +1,97 @@
+//========================================================================
+//
+// SplashT1FontFile.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#if HAVE_T1LIB_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <string.h>
+#include <t1lib.h>
+#include "gmem.h"
+#include "SplashT1FontEngine.h"
+#include "SplashT1Font.h"
+#include "SplashT1FontFile.h"
+
+//------------------------------------------------------------------------
+// SplashT1FontFile
+//------------------------------------------------------------------------
+
+SplashFontFile *SplashT1FontFile::loadType1Font(SplashT1FontEngine *engineA,
+						SplashFontFileID *idA,
+						char *fileNameA,
+						GBool deleteFileA,
+						char **encA) {
+  int t1libIDA;
+  char **encTmp;
+  char *encStrTmp;
+  int encStrSize;
+  char *encPtr;
+  int i;
+
+  // load the font file
+  if ((t1libIDA = T1_AddFont(fileNameA)) < 0) {
+    return NULL;
+  }
+  T1_LoadFont(t1libIDA);
+
+  // reencode it
+  encStrSize = 0;
+  for (i = 0; i < 256; ++i) {
+    if (encA[i]) {
+      encStrSize += strlen(encA[i]) + 1;
+    }
+  }
+  encTmp = (char **)gmallocn(257, sizeof(char *));
+  encStrTmp = (char *)gmallocn(encStrSize, sizeof(char));
+  encPtr = encStrTmp;
+  for (i = 0; i < 256; ++i) {
+    if (encA[i]) {
+      strcpy(encPtr, encA[i]);
+      encTmp[i] = encPtr;
+      encPtr += strlen(encPtr) + 1;
+    } else {
+      encTmp[i] = ".notdef";
+    }
+  }
+  encTmp[256] = "custom";
+  T1_ReencodeFont(t1libIDA, encTmp);
+
+  return new SplashT1FontFile(engineA, idA, fileNameA, deleteFileA,
+			      t1libIDA, encTmp, encStrTmp);
+}
+
+SplashT1FontFile::SplashT1FontFile(SplashT1FontEngine *engineA,
+				   SplashFontFileID *idA,
+				   char *fileNameA, GBool deleteFileA,
+				   int t1libIDA, char **encA, char *encStrA):
+  SplashFontFile(idA, fileNameA, deleteFileA)
+{
+  engine = engineA;
+  t1libID = t1libIDA;
+  enc = encA;
+  encStr = encStrA;
+}
+
+SplashT1FontFile::~SplashT1FontFile() {
+  gfree(encStr);
+  gfree(enc);
+  T1_DeleteFont(t1libID);
+}
+
+SplashFont *SplashT1FontFile::makeFont(SplashCoord *mat,
+				       SplashCoord *textMat) {
+  SplashFont *font;
+
+  font = new SplashT1Font(this, mat, textMat);
+  font->initCache();
+  return font;
+}
+
+#endif // HAVE_T1LIB_H
diff --git a/xpdf/SplashT1FontFile.h b/xpdf/SplashT1FontFile.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashT1FontFile.h
@@ -0,0 +1,58 @@
+//========================================================================
+//
+// SplashT1FontFile.h
+//
+//========================================================================
+
+#ifndef SPLASHT1FONTFILE_H
+#define SPLASHT1FONTFILE_H
+
+#include <aconf.h>
+
+#if HAVE_T1LIB_H
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashFontFile.h"
+
+class SplashT1FontEngine;
+
+//------------------------------------------------------------------------
+// SplashT1FontFile
+//------------------------------------------------------------------------
+
+class SplashT1FontFile: public SplashFontFile {
+public:
+
+  static SplashFontFile *loadType1Font(SplashT1FontEngine *engineA,
+				       SplashFontFileID *idA,
+				       char *fileNameA, GBool deleteFileA,
+				       char **encA);
+
+  virtual ~SplashT1FontFile();
+
+  // Create a new SplashT1Font, i.e., a scaled instance of this font
+  // file.
+  virtual SplashFont *makeFont(SplashCoord *mat,
+			       SplashCoord *textMat);
+
+private:
+
+  SplashT1FontFile(SplashT1FontEngine *engineA,
+		   SplashFontFileID *idA,
+		   char *fileNameA, GBool deleteFileA,
+		   int t1libIDA, char **encA, char *encStrA);
+
+  SplashT1FontEngine *engine;
+  int t1libID;			// t1lib font ID
+  char **enc;
+  char *encStr;
+
+  friend class SplashT1Font;
+};
+
+#endif // HAVE_T1LIB_H
+
+#endif
diff --git a/xpdf/SplashTypes.h b/xpdf/SplashTypes.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashTypes.h
@@ -0,0 +1,132 @@
+//========================================================================
+//
+// SplashTypes.h
+//
+//========================================================================
+
+#ifndef SPLASHTYPES_H
+#define SPLASHTYPES_H
+
+#include <aconf.h>
+#include "gtypes.h"
+
+//------------------------------------------------------------------------
+// coordinates
+//------------------------------------------------------------------------
+
+#if USE_FIXEDPOINT
+#include "FixedPoint.h"
+typedef FixedPoint SplashCoord;
+#else
+typedef double SplashCoord;
+#endif
+
+//------------------------------------------------------------------------
+// antialiasing
+//------------------------------------------------------------------------
+
+#define splashAASize 4
+
+//------------------------------------------------------------------------
+// colors
+//------------------------------------------------------------------------
+
+enum SplashColorMode {
+  splashModeMono1,		// 1 bit per component, 8 pixels per byte,
+				//   MSbit is on the left
+  splashModeMono8,		// 1 byte per component, 1 byte per pixel
+  splashModeRGB8,		// 1 byte per component, 3 bytes per pixel:
+				//   RGBRGB...
+  splashModeBGR8		// 1 byte per component, 3 bytes per pixel:
+				//   BGRBGR...
+
+#if SPLASH_CMYK
+  ,
+  splashModeCMYK8		// 1 byte per component, 4 bytes per pixel:
+				//   CMYKCMYK...
+#endif
+};
+
+// number of components in each color mode
+// (defined in SplashState.cc)
+extern int splashColorModeNComps[];
+
+// max number of components in any SplashColor
+#if SPLASH_CMYK
+#  define splashMaxColorComps 4
+#else
+#  define splashMaxColorComps 3
+#endif
+
+typedef Guchar SplashColor[splashMaxColorComps];
+typedef Guchar *SplashColorPtr;
+
+// RGB8
+static inline Guchar splashRGB8R(SplashColorPtr rgb8) { return rgb8[0]; }
+static inline Guchar splashRGB8G(SplashColorPtr rgb8) { return rgb8[1]; }
+static inline Guchar splashRGB8B(SplashColorPtr rgb8) { return rgb8[2]; }
+
+// BGR8
+static inline Guchar splashBGR8R(SplashColorPtr bgr8) { return bgr8[2]; }
+static inline Guchar splashBGR8G(SplashColorPtr bgr8) { return bgr8[1]; }
+static inline Guchar splashBGR8B(SplashColorPtr bgr8) { return bgr8[0]; }
+
+#if SPLASH_CMYK
+// CMYK8
+static inline Guchar splashCMYK8C(SplashColorPtr cmyk8) { return cmyk8[0]; }
+static inline Guchar splashCMYK8M(SplashColorPtr cmyk8) { return cmyk8[1]; }
+static inline Guchar splashCMYK8Y(SplashColorPtr cmyk8) { return cmyk8[2]; }
+static inline Guchar splashCMYK8K(SplashColorPtr cmyk8) { return cmyk8[3]; }
+#endif
+
+static inline void splashColorCopy(SplashColorPtr dest, SplashColorPtr src) {
+  dest[0] = src[0];
+  dest[1] = src[1];
+  dest[2] = src[2];
+#if SPLASH_CMYK
+  dest[3] = src[3];
+#endif
+}
+
+static inline void splashColorXor(SplashColorPtr dest, SplashColorPtr src) {
+  dest[0] ^= src[0];
+  dest[1] ^= src[1];
+  dest[2] ^= src[2];
+#if SPLASH_CMYK
+  dest[3] ^= src[3];
+#endif
+}
+
+//------------------------------------------------------------------------
+// blend functions
+//------------------------------------------------------------------------
+
+typedef void (*SplashBlendFunc)(SplashColorPtr src, SplashColorPtr dest,
+				SplashColorPtr blend, SplashColorMode cm);
+
+//------------------------------------------------------------------------
+// screen parameters
+//------------------------------------------------------------------------
+
+enum SplashScreenType {
+  splashScreenDispersed,
+  splashScreenClustered,
+  splashScreenStochasticClustered
+};
+
+struct SplashScreenParams {
+  SplashScreenType type;
+  int size;
+  int dotRadius;
+  SplashCoord gamma;
+  SplashCoord blackThreshold;
+  SplashCoord whiteThreshold;
+};
+
+//------------------------------------------------------------------------
+// error results
+//------------------------------------------------------------------------
+
+typedef int SplashError;
+
+#endif
diff --git a/xpdf/SplashXPath.cc b/xpdf/SplashXPath.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashXPath.cc
@@ -0,0 +1,438 @@
+//========================================================================
+//
+// SplashXPath.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "gmem.h"
+#include "SplashMath.h"
+#include "SplashPath.h"
+#include "SplashXPath.h"
+
+//------------------------------------------------------------------------
+
+struct SplashXPathPoint {
+  SplashCoord x, y;
+};
+
+struct SplashXPathAdjust {
+  int firstPt, lastPt;		// range of points
+  GBool vert;			// vertical or horizontal hint
+  SplashCoord x0a, x0b,		// hint boundaries
+              xma, xmb,
+              x1a, x1b;
+  SplashCoord x0, x1, xm;	// adjusted coordinates
+};
+
+//------------------------------------------------------------------------
+
+// Transform a point from user space to device space.
+inline void SplashXPath::transform(SplashCoord *matrix,
+				   SplashCoord xi, SplashCoord yi,
+				   SplashCoord *xo, SplashCoord *yo) {
+  //                          [ m[0] m[1] 0 ]
+  // [xo yo 1] = [xi yi 1] *  [ m[2] m[3] 0 ]
+  //                          [ m[4] m[5] 1 ]
+  *xo = xi * matrix[0] + yi * matrix[2] + matrix[4];
+  *yo = xi * matrix[1] + yi * matrix[3] + matrix[5];
+}
+
+//------------------------------------------------------------------------
+// SplashXPath
+//------------------------------------------------------------------------
+
+SplashXPath::SplashXPath() {
+  segs = NULL;
+  length = size = 0;
+}
+
+SplashXPath::SplashXPath(SplashPath *path, SplashCoord *matrix,
+			 SplashCoord flatness, GBool closeSubpaths) {
+  SplashPathHint *hint;
+  SplashXPathPoint *pts;
+  SplashXPathAdjust *adjusts, *adjust;
+  SplashCoord x0, y0, x1, y1, x2, y2, x3, y3, xsp, ysp;
+  SplashCoord adj0, adj1, w;
+  int ww;
+  int curSubpath, curSubpathX, i, j;
+
+  // transform the points
+  pts = (SplashXPathPoint *)gmallocn(path->length, sizeof(SplashXPathPoint));
+  for (i = 0; i < path->length; ++i) {
+    transform(matrix, path->pts[i].x, path->pts[i].y, &pts[i].x, &pts[i].y);
+  }
+
+  // set up the stroke adjustment hints
+  if (path->hints) {
+    adjusts = (SplashXPathAdjust *)gmallocn(path->hintsLength,
+					    sizeof(SplashXPathAdjust));
+    for (i = 0; i < path->hintsLength; ++i) {
+      hint = &path->hints[i];
+      x0 = pts[hint->ctrl0    ].x;    y0 = pts[hint->ctrl0    ].y;
+      x1 = pts[hint->ctrl0 + 1].x;    y1 = pts[hint->ctrl0 + 1].y;
+      x2 = pts[hint->ctrl1    ].x;    y2 = pts[hint->ctrl1    ].y;
+      x3 = pts[hint->ctrl1 + 1].x;    y3 = pts[hint->ctrl1 + 1].y;
+      if (x0 == x1 && x2 == x3) {
+	adjusts[i].vert = gTrue;
+	adj0 = x0;
+	adj1 = x2;
+      } else if (y0 == y1 && y2 == y3) {
+	adjusts[i].vert = gFalse;
+	adj0 = y0;
+	adj1 = y2;
+      } else {
+	gfree(adjusts);
+	adjusts = NULL;
+	break;
+      }
+      if (adj0 > adj1) {
+	x0 = adj0;
+	adj0 = adj1;
+	adj1 = x0;
+      }
+      w = adj1 - adj0;
+      ww = splashRound(w);
+      if (ww == 0) {
+	ww = 1;
+      }
+      adjusts[i].x0a = adj0 - 0.01;
+      adjusts[i].x0b = adj0 + 0.01;
+      adjusts[i].xma = (SplashCoord)0.5 * (adj0 + adj1) - 0.01;
+      adjusts[i].xmb = (SplashCoord)0.5 * (adj0 + adj1) + 0.01;
+      adjusts[i].x1a = adj1 - 0.01;
+      adjusts[i].x1b = adj1 + 0.01;
+      adjusts[i].x0 = (SplashCoord)splashRound(adj0);
+      adjusts[i].x1 = adjusts[i].x0 + ww - 0.01;
+      adjusts[i].xm = (SplashCoord)0.5 * (adjusts[i].x0 + adjusts[i].x1);
+      adjusts[i].firstPt = hint->firstPt;
+      adjusts[i].lastPt = hint->lastPt;
+    }
+
+  } else {
+    adjusts = NULL;
+  }
+
+  // perform stroke adjustment
+  if (adjusts) {
+    for (i = 0, adjust = adjusts; i < path->hintsLength; ++i, ++adjust) {
+      for (j = adjust->firstPt; j <= adjust->lastPt; ++j) {
+	strokeAdjust(adjust, &pts[j].x, &pts[j].y);
+      }
+    }
+    gfree(adjusts);
+  }
+
+  segs = NULL;
+  length = size = 0;
+
+  x0 = y0 = xsp = ysp = 0; // make gcc happy
+  adj0 = adj1 = 0; // make gcc happy
+  curSubpath = 0;
+  curSubpathX = 0;
+  i = 0;
+  while (i < path->length) {
+
+    // first point in subpath - skip it
+    if (path->flags[i] & splashPathFirst) {
+      x0 = pts[i].x;
+      y0 = pts[i].y;
+      xsp = x0;
+      ysp = y0;
+      curSubpath = i;
+      curSubpathX = length;
+      ++i;
+
+    } else {
+
+      // curve segment
+      if (path->flags[i] & splashPathCurve) {
+	x1 = pts[i].x;
+	y1 = pts[i].y;
+	x2 = pts[i+1].x;
+	y2 = pts[i+1].y;
+	x3 = pts[i+2].x;
+	y3 = pts[i+2].y;
+	addCurve(x0, y0, x1, y1, x2, y2, x3, y3,
+		 flatness,
+		 (path->flags[i-1] & splashPathFirst),
+		 (path->flags[i+2] & splashPathLast),
+		 !closeSubpaths &&
+		   (path->flags[i-1] & splashPathFirst) &&
+		   !(path->flags[i-1] & splashPathClosed),
+		 !closeSubpaths &&
+		   (path->flags[i+2] & splashPathLast) &&
+		   !(path->flags[i+2] & splashPathClosed));
+	x0 = x3;
+	y0 = y3;
+	i += 3;
+
+      // line segment
+      } else {
+	x1 = pts[i].x;
+	y1 = pts[i].y;
+	addSegment(x0, y0, x1, y1,
+		   path->flags[i-1] & splashPathFirst,
+		   path->flags[i] & splashPathLast,
+		   !closeSubpaths &&
+		     (path->flags[i-1] & splashPathFirst) &&
+		     !(path->flags[i-1] & splashPathClosed),
+		   !closeSubpaths &&
+		     (path->flags[i] & splashPathLast) &&
+		     !(path->flags[i] & splashPathClosed));
+	x0 = x1;
+	y0 = y1;
+	++i;
+      }
+
+      // close a subpath
+      if (closeSubpaths &&
+	  (path->flags[i-1] & splashPathLast) &&
+	  (pts[i-1].x != pts[curSubpath].x ||
+	   pts[i-1].y != pts[curSubpath].y)) {
+	addSegment(x0, y0, xsp, ysp,
+		   gFalse, gTrue, gFalse, gFalse);
+      }
+    }
+  }
+
+  gfree(pts);
+}
+
+// Apply the stroke adjust hints to point <pt>: (*<xp>, *<yp>).
+void SplashXPath::strokeAdjust(SplashXPathAdjust *adjust,
+			       SplashCoord *xp, SplashCoord *yp) {
+  SplashCoord x, y;
+
+  if (adjust->vert) {
+    x = *xp;
+    if (x > adjust->x0a && x < adjust->x0b) {
+      *xp = adjust->x0;
+    } else if (x > adjust->xma && x < adjust->xmb) {
+      *xp = adjust->xm;
+    } else if (x > adjust->x1a && x < adjust->x1b) {
+      *xp = adjust->x1;
+    }
+  } else {
+    y = *yp;
+    if (y > adjust->x0a && y < adjust->x0b) {
+      *yp = adjust->x0;
+    } else if (y > adjust->xma && y < adjust->xmb) {
+      *yp = adjust->xm;
+    } else if (y > adjust->x1a && y < adjust->x1b) {
+      *yp = adjust->x1;
+    }
+  }
+}
+
+SplashXPath::SplashXPath(SplashXPath *xPath) {
+  length = xPath->length;
+  size = xPath->size;
+  segs = (SplashXPathSeg *)gmallocn(size, sizeof(SplashXPathSeg));
+  memcpy(segs, xPath->segs, length * sizeof(SplashXPathSeg));
+}
+
+SplashXPath::~SplashXPath() {
+  gfree(segs);
+}
+
+// Add space for <nSegs> more segments
+void SplashXPath::grow(int nSegs) {
+  if (length + nSegs > size) {
+    if (size == 0) {
+      size = 32;
+    }
+    while (size < length + nSegs) {
+      size *= 2;
+    }
+    segs = (SplashXPathSeg *)greallocn(segs, size, sizeof(SplashXPathSeg));
+  }
+}
+
+void SplashXPath::addCurve(SplashCoord x0, SplashCoord y0,
+			   SplashCoord x1, SplashCoord y1,
+			   SplashCoord x2, SplashCoord y2,
+			   SplashCoord x3, SplashCoord y3,
+			   SplashCoord flatness,
+			   GBool first, GBool last, GBool end0, GBool end1) {
+  SplashCoord cx[splashMaxCurveSplits + 1][3];
+  SplashCoord cy[splashMaxCurveSplits + 1][3];
+  int cNext[splashMaxCurveSplits + 1];
+  SplashCoord xl0, xl1, xl2, xr0, xr1, xr2, xr3, xx1, xx2, xh;
+  SplashCoord yl0, yl1, yl2, yr0, yr1, yr2, yr3, yy1, yy2, yh;
+  SplashCoord dx, dy, mx, my, d1, d2, flatness2;
+  int p1, p2, p3;
+
+  flatness2 = flatness * flatness;
+
+  // initial segment
+  p1 = 0;
+  p2 = splashMaxCurveSplits;
+  cx[p1][0] = x0;  cy[p1][0] = y0;
+  cx[p1][1] = x1;  cy[p1][1] = y1;
+  cx[p1][2] = x2;  cy[p1][2] = y2;
+  cx[p2][0] = x3;  cy[p2][0] = y3;
+  cNext[p1] = p2;
+
+  while (p1 < splashMaxCurveSplits) {
+
+    // get the next segment
+    xl0 = cx[p1][0];  yl0 = cy[p1][0];
+    xx1 = cx[p1][1];  yy1 = cy[p1][1];
+    xx2 = cx[p1][2];  yy2 = cy[p1][2];
+    p2 = cNext[p1];
+    xr3 = cx[p2][0];  yr3 = cy[p2][0];
+
+    // compute the distances from the control points to the
+    // midpoint of the straight line (this is a bit of a hack, but
+    // it's much faster than computing the actual distances to the
+    // line)
+    mx = (xl0 + xr3) * 0.5;
+    my = (yl0 + yr3) * 0.5;
+    dx = xx1 - mx;
+    dy = yy1 - my;
+    d1 = dx*dx + dy*dy;
+    dx = xx2 - mx;
+    dy = yy2 - my;
+    d2 = dx*dx + dy*dy;
+
+    // if the curve is flat enough, or no more subdivisions are
+    // allowed, add the straight line segment
+    if (p2 - p1 == 1 || (d1 <= flatness2 && d2 <= flatness2)) {
+      addSegment(xl0, yl0, xr3, yr3,
+		 p1 == 0 && first,
+		 p2 == splashMaxCurveSplits && last,
+		 p1 == 0 && end0,
+		 p2 == splashMaxCurveSplits && end1);
+      p1 = p2;
+
+    // otherwise, subdivide the curve
+    } else {
+      xl1 = (xl0 + xx1) * 0.5;
+      yl1 = (yl0 + yy1) * 0.5;
+      xh = (xx1 + xx2) * 0.5;
+      yh = (yy1 + yy2) * 0.5;
+      xl2 = (xl1 + xh) * 0.5;
+      yl2 = (yl1 + yh) * 0.5;
+      xr2 = (xx2 + xr3) * 0.5;
+      yr2 = (yy2 + yr3) * 0.5;
+      xr1 = (xh + xr2) * 0.5;
+      yr1 = (yh + yr2) * 0.5;
+      xr0 = (xl2 + xr1) * 0.5;
+      yr0 = (yl2 + yr1) * 0.5;
+      // add the new subdivision points
+      p3 = (p1 + p2) / 2;
+      cx[p1][1] = xl1;  cy[p1][1] = yl1;
+      cx[p1][2] = xl2;  cy[p1][2] = yl2;
+      cNext[p1] = p3;
+      cx[p3][0] = xr0;  cy[p3][0] = yr0;
+      cx[p3][1] = xr1;  cy[p3][1] = yr1;
+      cx[p3][2] = xr2;  cy[p3][2] = yr2;
+      cNext[p3] = p2;
+    }
+  }
+}
+
+void SplashXPath::addSegment(SplashCoord x0, SplashCoord y0,
+			     SplashCoord x1, SplashCoord y1,
+			     GBool first, GBool last, GBool end0, GBool end1) {
+  grow(1);
+  segs[length].x0 = x0;
+  segs[length].y0 = y0;
+  segs[length].x1 = x1;
+  segs[length].y1 = y1;
+  segs[length].flags = 0;
+  if (first) {
+    segs[length].flags |= splashXPathFirst;
+  }
+  if (last) {
+    segs[length].flags |= splashXPathLast;
+  }
+  if (end0) {
+    segs[length].flags |= splashXPathEnd0;
+  }
+  if (end1) {
+    segs[length].flags |= splashXPathEnd1;
+  }
+  if (y1 == y0) {
+    segs[length].dxdy = segs[length].dydx = 0;
+    segs[length].flags |= splashXPathHoriz;
+    if (x1 == x0) {
+      segs[length].flags |= splashXPathVert;
+    }
+  } else if (x1 == x0) {
+    segs[length].dxdy = segs[length].dydx = 0;
+    segs[length].flags |= splashXPathVert;
+  } else {
+#if USE_FIXEDPOINT
+    if (FixedPoint::divCheck(x1 - x0, y1 - y0, &segs[length].dxdy)) {
+      segs[length].dydx = (SplashCoord)1 / segs[length].dxdy;
+    } else {
+      segs[length].dxdy = segs[length].dydx = 0;
+      if (splashAbs(x1 - x0) > splashAbs(y1 - y0)) {
+	segs[length].flags |= splashXPathHoriz;
+      } else {
+	segs[length].flags |= splashXPathVert;
+      }
+    }
+#else
+    segs[length].dxdy = (x1 - x0) / (y1 - y0);
+    segs[length].dydx = (SplashCoord)1 / segs[length].dxdy;
+#endif
+  }
+  if (y0 > y1) {
+    segs[length].flags |= splashXPathFlip;
+  }
+  ++length;
+}
+
+static int cmpXPathSegs(const void *arg0, const void *arg1) {
+  SplashXPathSeg *seg0 = (SplashXPathSeg *)arg0;
+  SplashXPathSeg *seg1 = (SplashXPathSeg *)arg1;
+  SplashCoord x0, y0, x1, y1;
+
+  if (seg0->flags & splashXPathFlip) {
+    x0 = seg0->x1;
+    y0 = seg0->y1;
+  } else {
+    x0 = seg0->x0;
+    y0 = seg0->y0;
+  }
+  if (seg1->flags & splashXPathFlip) {
+    x1 = seg1->x1;
+    y1 = seg1->y1;
+  } else {
+    x1 = seg1->x0;
+    y1 = seg1->y0;
+  }
+  if (y0 != y1) {
+    return (y0 > y1) ? 1 : -1;
+  }
+  if (x0 != x1) {
+    return (x0 > x1) ? 1 : -1;
+  }
+  return 0;
+}
+
+void SplashXPath::aaScale() {
+  SplashXPathSeg *seg;
+  int i;
+
+  for (i = 0, seg = segs; i < length; ++i, ++seg) {
+    seg->x0 *= splashAASize;
+    seg->y0 *= splashAASize;
+    seg->x1 *= splashAASize;
+    seg->y1 *= splashAASize;
+  }
+}
+
+void SplashXPath::sort() {
+  qsort(segs, length, sizeof(SplashXPathSeg), &cmpXPathSegs);
+}
diff --git a/xpdf/SplashXPath.h b/xpdf/SplashXPath.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashXPath.h
@@ -0,0 +1,100 @@
+//========================================================================
+//
+// SplashXPath.h
+//
+//========================================================================
+
+#ifndef SPLASHXPATH_H
+#define SPLASHXPATH_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashTypes.h"
+
+class SplashPath;
+struct SplashXPathAdjust;
+
+//------------------------------------------------------------------------
+
+#define splashMaxCurveSplits (1 << 10)
+
+//------------------------------------------------------------------------
+// SplashXPathSeg
+//------------------------------------------------------------------------
+
+struct SplashXPathSeg {
+  SplashCoord x0, y0;		// first endpoint
+  SplashCoord x1, y1;		// second endpoint
+  SplashCoord dxdy;		// slope: delta-x / delta-y
+  SplashCoord dydx;		// slope: delta-y / delta-x
+  Guint flags;
+};
+
+#define splashXPathFirst   0x01	// first segment of a subpath
+#define splashXPathLast    0x02	// last segment of a subpath
+#define splashXPathEnd0    0x04	// first endpoint is end of an open subpath
+#define splashXPathEnd1    0x08 // second endpoint is end of an open subpath
+#define splashXPathHoriz   0x10 // segment is vertical (y0 == y1)
+				//   (dxdy is undef)
+#define splashXPathVert    0x20 // segment is horizontal (x0 == x1)
+				//   (dydx is undef)
+#define splashXPathFlip	   0x40	// y0 > y1
+
+//------------------------------------------------------------------------
+// SplashXPath
+//------------------------------------------------------------------------
+
+class SplashXPath {
+public:
+
+  // Expands (converts to segments) and flattens (converts curves to
+  // lines) <path>.  Transforms all points from user space to device
+  // space, via <matrix>.  If <closeSubpaths> is true, closes all open
+  // subpaths.
+  SplashXPath(SplashPath *path, SplashCoord *matrix,
+	      SplashCoord flatness, GBool closeSubpaths);
+
+  // Copy an expanded path.
+  SplashXPath *copy() { return new SplashXPath(this); }
+
+  ~SplashXPath();
+
+  // Multiply all coordinates by splashAASize, in preparation for
+  // anti-aliased rendering.
+  void aaScale();
+
+  // Sort by upper coordinate (lower y), in y-major order.
+  void sort();
+
+private:
+
+  SplashXPath();
+  SplashXPath(SplashXPath *xPath);
+  void transform(SplashCoord *matrix, SplashCoord xi, SplashCoord yi,
+		 SplashCoord *xo, SplashCoord *yo);
+  void strokeAdjust(SplashXPathAdjust *adjust,
+		    SplashCoord *xp, SplashCoord *yp);
+  void grow(int nSegs);
+  void addCurve(SplashCoord x0, SplashCoord y0,
+		SplashCoord x1, SplashCoord y1,
+		SplashCoord x2, SplashCoord y2,
+		SplashCoord x3, SplashCoord y3,
+		SplashCoord flatness,
+		GBool first, GBool last, GBool end0, GBool end1);
+  void addSegment(SplashCoord x0, SplashCoord y0,
+		  SplashCoord x1, SplashCoord y1,
+		  GBool first, GBool last, GBool end0, GBool end1);
+
+  SplashXPathSeg *segs;
+  int length, size;		// length and size of segs array
+
+  friend class SplashXPathScanner;
+  friend class SplashClip;
+  friend class Splash;
+};
+
+#endif
diff --git a/xpdf/SplashXPathScanner.cc b/xpdf/SplashXPathScanner.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashXPathScanner.cc
@@ -0,0 +1,428 @@
+//========================================================================
+//
+// SplashXPathScanner.cc
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include "gmem.h"
+#include "SplashMath.h"
+#include "SplashXPath.h"
+#include "SplashBitmap.h"
+#include "SplashXPathScanner.h"
+
+//------------------------------------------------------------------------
+
+struct SplashIntersect {
+  int x0, x1;			// intersection of segment with [y, y+1)
+  int count;			// EO/NZWN counter increment
+};
+
+static int cmpIntersect(const void *p0, const void *p1) {
+  return ((SplashIntersect *)p0)->x0 - ((SplashIntersect *)p1)->x0;
+}
+
+//------------------------------------------------------------------------
+// SplashXPathScanner
+//------------------------------------------------------------------------
+
+SplashXPathScanner::SplashXPathScanner(SplashXPath *xPathA, GBool eoA) {
+  SplashXPathSeg *seg;
+  SplashCoord xMinFP, yMinFP, xMaxFP, yMaxFP;
+  int i;
+
+  xPath = xPathA;
+  eo = eoA;
+
+  // compute the bbox
+  if (xPath->length == 0) {
+    xMin = yMin = 1;
+    xMax = yMax = 0;
+  } else {
+    seg = &xPath->segs[0];
+    if (seg->x0 <= seg->x1) {
+      xMinFP = seg->x0;
+      xMaxFP = seg->x1;
+    } else {
+      xMinFP = seg->x1;
+      xMaxFP = seg->x0;
+    }
+    if (seg->flags & splashXPathFlip) {
+      yMinFP = seg->y1;
+      yMaxFP = seg->y0;
+    } else {
+      yMinFP = seg->y0;
+      yMaxFP = seg->y1;
+    }
+    for (i = 1; i < xPath->length; ++i) {
+      seg = &xPath->segs[i];
+      if (seg->x0 < xMinFP) {
+	xMinFP = seg->x0;
+      } else if (seg->x0 > xMaxFP) {
+	xMaxFP = seg->x0;
+      }
+      if (seg->x1 < xMinFP) {
+	xMinFP = seg->x1;
+      } else if (seg->x1 > xMaxFP) {
+	xMaxFP = seg->x1;
+      }
+      if (seg->flags & splashXPathFlip) {
+	if (seg->y0 > yMaxFP) {
+	  yMaxFP = seg->y0;
+	}
+      } else {
+	if (seg->y1 > yMaxFP) {
+	  yMaxFP = seg->y1;
+	}
+      }
+    }
+    xMin = splashFloor(xMinFP);
+    xMax = splashFloor(xMaxFP);
+    yMin = splashFloor(yMinFP);
+    yMax = splashFloor(yMaxFP);
+  }
+
+  interY = yMin - 1;
+  xPathIdx = 0;
+  inter = NULL;
+  interLen = interSize = 0;
+}
+
+SplashXPathScanner::~SplashXPathScanner() {
+  gfree(inter);
+}
+
+void SplashXPathScanner::getBBoxAA(int *xMinA, int *yMinA,
+				   int *xMaxA, int *yMaxA) {
+  *xMinA = xMin / splashAASize;
+  *yMinA = yMin / splashAASize;
+  *xMaxA = xMax / splashAASize;
+  *yMaxA = yMax / splashAASize;
+}
+
+void SplashXPathScanner::getSpanBounds(int y, int *spanXMin, int *spanXMax) {
+  if (interY != y) {
+    computeIntersections(y);
+  }
+  if (interLen > 0) {
+    *spanXMin = inter[0].x0;
+    *spanXMax = inter[interLen - 1].x1;
+  } else {
+    *spanXMin = xMax + 1;
+    *spanXMax = xMax;
+  }
+}
+
+GBool SplashXPathScanner::test(int x, int y) {
+  int count, i;
+
+  if (interY != y) {
+    computeIntersections(y);
+  }
+  count = 0;
+  for (i = 0; i < interLen && inter[i].x0 <= x; ++i) {
+    if (x <= inter[i].x1) {
+      return gTrue;
+    }
+    count += inter[i].count;
+  }
+  return eo ? (count & 1) : (count != 0);
+}
+
+GBool SplashXPathScanner::testSpan(int x0, int x1, int y) {
+  int count, xx1, i;
+
+  if (interY != y) {
+    computeIntersections(y);
+  }
+
+  count = 0;
+  for (i = 0; i < interLen && inter[i].x1 < x0; ++i) {
+    count += inter[i].count;
+  }
+
+  // invariant: the subspan [x0,xx1] is inside the path
+  xx1 = x0 - 1;
+  while (xx1 < x1) {
+    if (i >= interLen) {
+      return gFalse;
+    }
+    if (inter[i].x0 > xx1 + 1 &&
+	!(eo ? (count & 1) : (count != 0))) {
+      return gFalse;
+    }
+    if (inter[i].x1 > xx1) {
+      xx1 = inter[i].x1;
+    }
+    count += inter[i].count;
+    ++i;
+  }
+
+  return gTrue;
+}
+
+GBool SplashXPathScanner::getNextSpan(int y, int *x0, int *x1) {
+  int xx0, xx1;
+
+  if (interY != y) {
+    computeIntersections(y);
+  }
+  if (interIdx >= interLen) {
+    return gFalse;
+  }
+  xx0 = inter[interIdx].x0;
+  xx1 = inter[interIdx].x1;
+  interCount += inter[interIdx].count;
+  ++interIdx;
+  while (interIdx < interLen &&
+	 (inter[interIdx].x0 <= xx1 ||
+	  (eo ? (interCount & 1) : (interCount != 0)))) {
+    if (inter[interIdx].x1 > xx1) {
+      xx1 = inter[interIdx].x1;
+    }
+    interCount += inter[interIdx].count;
+    ++interIdx;
+  }
+  *x0 = xx0;
+  *x1 = xx1;
+  return gTrue;
+}
+
+void SplashXPathScanner::computeIntersections(int y) {
+  SplashCoord xSegMin, xSegMax, ySegMin, ySegMax, xx0, xx1;
+  SplashXPathSeg *seg;
+  int i, j;
+
+  // find the first segment that intersects [y, y+1)
+  i = (y >= interY) ? xPathIdx : 0;
+  while (i < xPath->length &&
+	 xPath->segs[i].y0 < y && xPath->segs[i].y1 < y) {
+    ++i;
+  }
+  xPathIdx = i;
+
+  // find all of the segments that intersect [y, y+1) and create an
+  // Intersect element for each one
+  interLen = 0;
+  for (j = i; j < xPath->length; ++j) {
+    seg = &xPath->segs[j];
+    if (seg->flags & splashXPathFlip) {
+      ySegMin = seg->y1;
+      ySegMax = seg->y0;
+    } else {
+      ySegMin = seg->y0;
+      ySegMax = seg->y1;
+    }
+
+    // ensure that:      ySegMin < y+1
+    //              y <= ySegMax
+    if (ySegMin >= y + 1) {
+      break;
+    }
+    if (ySegMax < y) {
+      continue;
+    }
+
+    if (interLen == interSize) {
+      if (interSize == 0) {
+	interSize = 16;
+      } else {
+	interSize *= 2;
+      }
+      inter = (SplashIntersect *)greallocn(inter, interSize,
+					   sizeof(SplashIntersect));
+    }
+
+    if (seg->flags & splashXPathHoriz) {
+      xx0 = seg->x0;
+      xx1 = seg->x1;
+    } else if (seg->flags & splashXPathVert) {
+      xx0 = xx1 = seg->x0;
+    } else {
+      if (seg->x0 < seg->x1) {
+	xSegMin = seg->x0;
+	xSegMax = seg->x1;
+      } else {
+	xSegMin = seg->x1;
+	xSegMax = seg->x0;
+      }
+      // intersection with top edge
+      xx0 = seg->x0 + ((SplashCoord)y - seg->y0) * seg->dxdy;
+      // intersection with bottom edge
+      xx1 = seg->x0 + ((SplashCoord)y + 1 - seg->y0) * seg->dxdy;
+      // the segment may not actually extend to the top and/or bottom edges
+      if (xx0 < xSegMin) {
+	xx0 = xSegMin;
+      } else if (xx0 > xSegMax) {
+	xx0 = xSegMax;
+      }
+      if (xx1 < xSegMin) {
+	xx1 = xSegMin;
+      } else if (xx1 > xSegMax) {
+	xx1 = xSegMax;
+      }
+    }
+    if (xx0 < xx1) {
+      inter[interLen].x0 = splashFloor(xx0);
+      inter[interLen].x1 = splashFloor(xx1);
+    } else {
+      inter[interLen].x0 = splashFloor(xx1);
+      inter[interLen].x1 = splashFloor(xx0);
+    }
+    if (ySegMin <= y &&
+	(SplashCoord)y < ySegMax &&
+	!(seg->flags & splashXPathHoriz)) {
+      inter[interLen].count = eo ? 1
+	                         : (seg->flags & splashXPathFlip) ? 1 : -1;
+    } else {
+      inter[interLen].count = 0;
+    }
+    ++interLen;
+  }
+
+  qsort(inter, interLen, sizeof(SplashIntersect), &cmpIntersect);
+
+  interY = y;
+  interIdx = 0;
+  interCount = 0;
+}
+
+void SplashXPathScanner::renderAALine(SplashBitmap *aaBuf,
+				      int *x0, int *x1, int y) {
+  int xx0, xx1, xx, xxMin, xxMax, yy;
+  Guchar mask;
+  SplashColorPtr p;
+
+  memset(aaBuf->getDataPtr(), 0, aaBuf->getRowSize() * aaBuf->getHeight());
+  xxMin = aaBuf->getWidth();
+  xxMax = -1;
+  for (yy = 0; yy < splashAASize; ++yy) {
+    computeIntersections(splashAASize * y + yy);
+    while (interIdx < interLen) {
+      xx0 = inter[interIdx].x0;
+      xx1 = inter[interIdx].x1;
+      interCount += inter[interIdx].count;
+      ++interIdx;
+      while (interIdx < interLen &&
+	     (inter[interIdx].x0 <= xx1 ||
+	      (eo ? (interCount & 1) : (interCount != 0)))) {
+	if (inter[interIdx].x1 > xx1) {
+	  xx1 = inter[interIdx].x1;
+	}
+	interCount += inter[interIdx].count;
+	++interIdx;
+      }
+      if (xx0 < 0) {
+	xx0 = 0;
+      }
+      ++xx1;
+      if (xx1 > aaBuf->getWidth()) {
+	xx1 = aaBuf->getWidth();
+      }
+      // set [xx0, xx1) to 1
+      if (xx0 < xx1) {
+	xx = xx0;
+	p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
+	if (xx & 7) {
+	  mask = 0xff >> (xx & 7);
+	  if ((xx & ~7) == (xx1 & ~7)) {
+	    mask &= (Guchar)(0xff00 >> (xx1 & 7));
+	  }
+	  *p++ |= mask;
+	  xx = (xx & ~7) + 8;
+	}
+	for (; xx + 7 < xx1; xx += 8) {
+	  *p++ |= 0xff;
+	}
+	if (xx < xx1) {
+	  *p |= (Guchar)(0xff00 >> (xx1 & 7));
+	}
+      }
+      if (xx0 < xxMin) {
+	xxMin = xx0;
+      }
+      if (xx1 > xxMax) {
+	xxMax = xx1;
+      }
+    }
+  }
+  *x0 = xxMin / splashAASize;
+  *x1 = (xxMax - 1) / splashAASize;
+}
+
+void SplashXPathScanner::clipAALine(SplashBitmap *aaBuf,
+				    int *x0, int *x1, int y) {
+  int xx0, xx1, xx, yy;
+  Guchar mask;
+  SplashColorPtr p;
+
+  for (yy = 0; yy < splashAASize; ++yy) {
+    xx = *x0 * splashAASize;
+    computeIntersections(splashAASize * y + yy);
+    while (interIdx < interLen && xx < (*x1 + 1) * splashAASize) {
+      xx0 = inter[interIdx].x0;
+      xx1 = inter[interIdx].x1;
+      interCount += inter[interIdx].count;
+      ++interIdx;
+      while (interIdx < interLen &&
+	     (inter[interIdx].x0 <= xx1 ||
+	      (eo ? (interCount & 1) : (interCount != 0)))) {
+	if (inter[interIdx].x1 > xx1) {
+	  xx1 = inter[interIdx].x1;
+	}
+	interCount += inter[interIdx].count;
+	++interIdx;
+      }
+      if (xx0 > aaBuf->getWidth()) {
+	xx0 = aaBuf->getWidth();
+      }
+      // set [xx, xx0) to 0
+      if (xx < xx0) {
+	p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
+	if (xx & 7) {
+	  mask = (Guchar)(0xff00 >> (xx & 7));
+	  if ((xx & ~7) == (xx0 & ~7)) {
+	    mask |= 0xff >> (xx0 & 7);
+	  }
+	  *p++ &= mask;
+	  xx = (xx & ~7) + 8;
+	}
+	for (; xx + 7 <= xx0; xx += 8) {
+	  *p++ = 0x00;
+	}
+	if (xx <= xx0) {
+	  *p &= 0xff >> (xx0 & 7);
+	}
+      }
+      if (xx1 >= xx) {
+	xx = xx1 + 1;
+      }
+    }
+    xx0 = (*x1 + 1) * splashAASize;
+    // set [xx, xx0) to 0
+    if (xx < xx0) {
+      p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3);
+      if (xx & 7) {
+	mask = (Guchar)(0xff00 >> (xx & 7));
+	if ((xx & ~7) == (xx0 & ~7)) {
+	  mask &= 0xff >> (xx0 & 7);
+	}
+	*p++ &= mask;
+	xx = (xx & ~7) + 8;
+      }
+      for (; xx + 7 <= xx0; xx += 8) {
+	*p++ = 0x00;
+      }
+      if (xx <= xx0) {
+	*p &= 0xff >> (xx0 & 7);
+      }
+    }
+  }
+}
diff --git a/xpdf/SplashXPathScanner.h b/xpdf/SplashXPathScanner.h
new file mode 100644
--- /dev/null
+++ b/xpdf/SplashXPathScanner.h
@@ -0,0 +1,87 @@
+//========================================================================
+//
+// SplashXPathScanner.h
+//
+//========================================================================
+
+#ifndef SPLASHXPATHSCANNER_H
+#define SPLASHXPATHSCANNER_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashTypes.h"
+
+class SplashXPath;
+class SplashBitmap;
+struct SplashIntersect;
+
+//------------------------------------------------------------------------
+// SplashXPathScanner
+//------------------------------------------------------------------------
+
+class SplashXPathScanner {
+public:
+
+  // Create a new SplashXPathScanner object.  <xPathA> must be sorted.
+  SplashXPathScanner(SplashXPath *xPathA, GBool eoA);
+
+  ~SplashXPathScanner();
+
+  // Return the path's bounding box.
+  void getBBox(int *xMinA, int *yMinA, int *xMaxA, int *yMaxA)
+    { *xMinA = xMin; *yMinA = yMin; *xMaxA = xMax; *yMaxA = yMax; }
+
+  // Return the path's bounding box.
+  void getBBoxAA(int *xMinA, int *yMinA, int *xMaxA, int *yMaxA);
+
+  // Return the min/max x values for the span at <y>.
+  void getSpanBounds(int y, int *spanXMin, int *spanXMax);
+
+  // Returns true if (<x>,<y>) is inside the path.
+  GBool test(int x, int y);
+
+  // Returns true if the entire span ([<x0>,<x1>], <y>) is inside the
+  // path.
+  GBool testSpan(int x0, int x1, int y);
+
+  // Returns the next span inside the path at <y>.  If <y> is
+  // different than the previous call to getNextSpan, this returns the
+  // first span at <y>; otherwise it returns the next span (relative
+  // to the previous call to getNextSpan).  Returns false if there are
+  // no more spans at <y>.
+  GBool getNextSpan(int y, int *x0, int *x1);
+
+  // Renders one anti-aliased line into <aaBuf>.  Returns the min and
+  // max x coordinates with non-zero pixels in <x0> and <x1>.
+  void renderAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y);
+
+  // Clips an anti-aliased line by setting pixels to zero.  On entry,
+  // all non-zero pixels are between <x0> and <x1>.  This function
+  // will update <x0> and <x1>.
+  void clipAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y);
+
+private:
+
+  void computeIntersections(int y);
+
+  SplashXPath *xPath;
+  GBool eo;
+  int xMin, yMin, xMax, yMax;
+
+  int interY;			// current y value
+  int interIdx;			// current index into <inter> - used by
+				//   getNextSpan 
+  int interCount;		// current EO/NZWN counter - used by
+				//   getNextSpan
+  int xPathIdx;			// current index into <xPath> - used by
+				//   computeIntersections
+  SplashIntersect *inter;	// intersections array for <interY>
+  int interLen;			// number of intersections in <inter>
+  int interSize;		// size of the <inter> array
+};
+
+#endif
diff --git a/xpdf/Stream-CCITT.h b/xpdf/Stream-CCITT.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Stream-CCITT.h
@@ -0,0 +1,459 @@
+//========================================================================
+//
+// Stream-CCITT.h
+//
+// Tables for CCITT Fax decoding.
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+struct CCITTCode {
+  short bits;
+  short n;
+};
+
+#define ccittEOL -2
+
+//------------------------------------------------------------------------
+// 2D codes
+//------------------------------------------------------------------------
+
+#define twoDimPass   0
+#define twoDimHoriz  1
+#define twoDimVert0  2
+#define twoDimVertR1 3
+#define twoDimVertL1 4
+#define twoDimVertR2 5
+#define twoDimVertL2 6
+#define twoDimVertR3 7
+#define twoDimVertL3 8
+
+// 1-7 bit codes
+static CCITTCode twoDimTab1[128] = {
+  {-1, -1}, {-1, -1},		        // 000000x
+  {7, twoDimVertL3},		        // 0000010
+  {7, twoDimVertR3},		        // 0000011
+  {6, twoDimVertL2}, {6, twoDimVertL2},	// 000010x
+  {6, twoDimVertR2}, {6, twoDimVertR2},	// 000011x
+  {4, twoDimPass}, {4, twoDimPass},     // 0001xxx
+    {4, twoDimPass}, {4, twoDimPass},
+    {4, twoDimPass}, {4, twoDimPass},
+    {4, twoDimPass}, {4, twoDimPass},
+  {3, twoDimHoriz}, {3, twoDimHoriz},	// 001xxxx
+    {3, twoDimHoriz}, {3, twoDimHoriz},
+    {3, twoDimHoriz}, {3, twoDimHoriz},
+    {3, twoDimHoriz}, {3, twoDimHoriz},
+    {3, twoDimHoriz}, {3, twoDimHoriz},
+    {3, twoDimHoriz}, {3, twoDimHoriz},
+    {3, twoDimHoriz}, {3, twoDimHoriz},
+    {3, twoDimHoriz}, {3, twoDimHoriz},
+  {3, twoDimVertL1}, {3, twoDimVertL1},	// 010xxxx
+    {3, twoDimVertL1}, {3, twoDimVertL1},
+    {3, twoDimVertL1}, {3, twoDimVertL1},
+    {3, twoDimVertL1}, {3, twoDimVertL1},
+    {3, twoDimVertL1}, {3, twoDimVertL1},
+    {3, twoDimVertL1}, {3, twoDimVertL1},
+    {3, twoDimVertL1}, {3, twoDimVertL1},
+    {3, twoDimVertL1}, {3, twoDimVertL1},
+  {3, twoDimVertR1}, {3, twoDimVertR1},	// 011xxxx
+    {3, twoDimVertR1}, {3, twoDimVertR1},
+    {3, twoDimVertR1}, {3, twoDimVertR1},
+    {3, twoDimVertR1}, {3, twoDimVertR1},
+    {3, twoDimVertR1}, {3, twoDimVertR1},
+    {3, twoDimVertR1}, {3, twoDimVertR1},
+    {3, twoDimVertR1}, {3, twoDimVertR1},
+    {3, twoDimVertR1}, {3, twoDimVertR1},
+  {1, twoDimVert0}, {1, twoDimVert0},	// 1xxxxxx
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0},
+    {1, twoDimVert0}, {1, twoDimVert0}
+};
+
+//------------------------------------------------------------------------
+// white run lengths
+//------------------------------------------------------------------------
+
+// 11-12 bit codes (upper 7 bits are 0)
+static CCITTCode whiteTab1[32] = {
+  {-1, -1},					// 00000
+  {12, ccittEOL},				// 00001
+  {-1, -1}, {-1, -1},				// 0001x
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},	// 001xx
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},	// 010xx
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},	// 011xx
+  {11, 1792}, {11, 1792},			// 1000x
+  {12, 1984},					// 10010
+  {12, 2048},					// 10011
+  {12, 2112},					// 10100
+  {12, 2176},					// 10101
+  {12, 2240},					// 10110
+  {12, 2304},					// 10111
+  {11, 1856}, {11, 1856},			// 1100x
+  {11, 1920}, {11, 1920},			// 1101x
+  {12, 2368},					// 11100
+  {12, 2432},					// 11101
+  {12, 2496},					// 11110
+  {12, 2560}					// 11111
+};
+
+// 1-9 bit codes
+static CCITTCode whiteTab2[512] = {
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},	// 0000000xx
+  {8, 29}, {8, 29},				// 00000010x
+  {8, 30}, {8, 30},				// 00000011x
+  {8, 45}, {8, 45},				// 00000100x
+  {8, 46}, {8, 46},				// 00000101x
+  {7, 22}, {7, 22}, {7, 22}, {7, 22},		// 0000011xx
+  {7, 23}, {7, 23}, {7, 23}, {7, 23},		// 0000100xx
+  {8, 47}, {8, 47},				// 00001010x
+  {8, 48}, {8, 48},				// 00001011x
+  {6, 13}, {6, 13}, {6, 13}, {6, 13},		// 000011xxx
+    {6, 13}, {6, 13}, {6, 13}, {6, 13},
+  {7, 20}, {7, 20}, {7, 20}, {7, 20},		// 0001000xx
+  {8, 33}, {8, 33},				// 00010010x
+  {8, 34}, {8, 34},				// 00010011x
+  {8, 35}, {8, 35},				// 00010100x
+  {8, 36}, {8, 36},				// 00010101x
+  {8, 37}, {8, 37},				// 00010110x
+  {8, 38}, {8, 38},				// 00010111x
+  {7, 19}, {7, 19}, {7, 19}, {7, 19},		// 0001100xx
+  {8, 31}, {8, 31},				// 00011010x
+  {8, 32}, {8, 32},				// 00011011x
+  {6, 1}, {6, 1}, {6, 1}, {6, 1},		// 000111xxx
+    {6, 1}, {6, 1}, {6, 1}, {6, 1},
+  {6, 12}, {6, 12}, {6, 12}, {6, 12},		// 001000xxx
+    {6, 12}, {6, 12}, {6, 12}, {6, 12},
+  {8, 53}, {8, 53},				// 00100100x
+  {8, 54}, {8, 54},				// 00100101x
+  {7, 26}, {7, 26}, {7, 26}, {7, 26},		// 0010011xx
+  {8, 39}, {8, 39},				// 00101000x
+  {8, 40}, {8, 40},				// 00101001x
+  {8, 41}, {8, 41},				// 00101010x
+  {8, 42}, {8, 42},				// 00101011x
+  {8, 43}, {8, 43},				// 00101100x
+  {8, 44}, {8, 44},				// 00101101x
+  {7, 21}, {7, 21}, {7, 21}, {7, 21},		// 0010111xx
+  {7, 28}, {7, 28}, {7, 28}, {7, 28},		// 0011000xx
+  {8, 61}, {8, 61},				// 00110010x
+  {8, 62}, {8, 62},				// 00110011x
+  {8, 63}, {8, 63},				// 00110100x
+  {8, 0}, {8, 0},				// 00110101x
+  {8, 320}, {8, 320},				// 00110110x
+  {8, 384}, {8, 384},				// 00110111x
+  {5, 10}, {5, 10}, {5, 10}, {5, 10},		// 00111xxxx
+    {5, 10}, {5, 10}, {5, 10}, {5, 10},
+    {5, 10}, {5, 10}, {5, 10}, {5, 10},
+    {5, 10}, {5, 10}, {5, 10}, {5, 10},
+  {5, 11}, {5, 11}, {5, 11}, {5, 11},		// 01000xxxx
+    {5, 11}, {5, 11}, {5, 11}, {5, 11},
+    {5, 11}, {5, 11}, {5, 11}, {5, 11},
+    {5, 11}, {5, 11}, {5, 11}, {5, 11},
+  {7, 27}, {7, 27}, {7, 27}, {7, 27},		// 0100100xx
+  {8, 59}, {8, 59},				// 01001010x
+  {8, 60}, {8, 60},				// 01001011x
+  {9, 1472},					// 010011000
+  {9, 1536},					// 010011001
+  {9, 1600},					// 010011010
+  {9, 1728},					// 010011011
+  {7, 18}, {7, 18}, {7, 18}, {7, 18},		// 0100111xx
+  {7, 24}, {7, 24}, {7, 24}, {7, 24},		// 0101000xx
+  {8, 49}, {8, 49},				// 01010010x
+  {8, 50}, {8, 50},				// 01010011x
+  {8, 51}, {8, 51},				// 01010100x
+  {8, 52}, {8, 52},				// 01010101x
+  {7, 25}, {7, 25}, {7, 25}, {7, 25},		// 0101011xx
+  {8, 55}, {8, 55},				// 01011000x
+  {8, 56}, {8, 56},				// 01011001x
+  {8, 57}, {8, 57},				// 01011010x
+  {8, 58}, {8, 58},				// 01011011x
+  {6, 192}, {6, 192}, {6, 192}, {6, 192},	// 010111xxx
+    {6, 192}, {6, 192}, {6, 192}, {6, 192},
+  {6, 1664}, {6, 1664}, {6, 1664}, {6, 1664},	// 011000xxx
+    {6, 1664}, {6, 1664}, {6, 1664}, {6, 1664},
+  {8, 448}, {8, 448},				// 01100100x
+  {8, 512}, {8, 512},				// 01100101x
+  {9, 704},					// 011001100
+  {9, 768},					// 011001101
+  {8, 640}, {8, 640},				// 01100111x
+  {8, 576}, {8, 576},				// 01101000x
+  {9, 832},					// 011010010
+  {9, 896},					// 011010011
+  {9, 960},					// 011010100
+  {9, 1024},					// 011010101
+  {9, 1088},					// 011010110
+  {9, 1152},					// 011010111
+  {9, 1216},					// 011011000
+  {9, 1280},					// 011011001
+  {9, 1344},					// 011011010
+  {9, 1408},					// 011011011
+  {7, 256}, {7, 256}, {7, 256}, {7, 256},	// 0110111xx
+  {4, 2}, {4, 2}, {4, 2}, {4, 2},		// 0111xxxxx
+    {4, 2}, {4, 2}, {4, 2}, {4, 2},
+    {4, 2}, {4, 2}, {4, 2}, {4, 2},
+    {4, 2}, {4, 2}, {4, 2}, {4, 2},
+    {4, 2}, {4, 2}, {4, 2}, {4, 2},
+    {4, 2}, {4, 2}, {4, 2}, {4, 2},
+    {4, 2}, {4, 2}, {4, 2}, {4, 2},
+    {4, 2}, {4, 2}, {4, 2}, {4, 2},
+  {4, 3}, {4, 3}, {4, 3}, {4, 3},		// 1000xxxxx
+    {4, 3}, {4, 3}, {4, 3}, {4, 3},
+    {4, 3}, {4, 3}, {4, 3}, {4, 3},
+    {4, 3}, {4, 3}, {4, 3}, {4, 3},
+    {4, 3}, {4, 3}, {4, 3}, {4, 3},
+    {4, 3}, {4, 3}, {4, 3}, {4, 3},
+    {4, 3}, {4, 3}, {4, 3}, {4, 3},
+    {4, 3}, {4, 3}, {4, 3}, {4, 3},
+  {5, 128}, {5, 128}, {5, 128}, {5, 128},	// 10010xxxx
+    {5, 128}, {5, 128}, {5, 128}, {5, 128},
+    {5, 128}, {5, 128}, {5, 128}, {5, 128},
+    {5, 128}, {5, 128}, {5, 128}, {5, 128},
+  {5, 8}, {5, 8}, {5, 8}, {5, 8},		// 10011xxxx
+    {5, 8}, {5, 8}, {5, 8}, {5, 8},
+    {5, 8}, {5, 8}, {5, 8}, {5, 8},
+    {5, 8}, {5, 8}, {5, 8}, {5, 8},
+  {5, 9}, {5, 9}, {5, 9}, {5, 9},		// 10100xxxx
+    {5, 9}, {5, 9}, {5, 9}, {5, 9},
+    {5, 9}, {5, 9}, {5, 9}, {5, 9},
+    {5, 9}, {5, 9}, {5, 9}, {5, 9},
+  {6, 16}, {6, 16}, {6, 16}, {6, 16},		// 101010xxx
+    {6, 16}, {6, 16}, {6, 16}, {6, 16},
+  {6, 17}, {6, 17}, {6, 17}, {6, 17},		// 101011xxx
+    {6, 17}, {6, 17}, {6, 17}, {6, 17},
+  {4, 4}, {4, 4}, {4, 4}, {4, 4},		// 1011xxxxx
+    {4, 4}, {4, 4}, {4, 4}, {4, 4},
+    {4, 4}, {4, 4}, {4, 4}, {4, 4},
+    {4, 4}, {4, 4}, {4, 4}, {4, 4},
+    {4, 4}, {4, 4}, {4, 4}, {4, 4},
+    {4, 4}, {4, 4}, {4, 4}, {4, 4},
+    {4, 4}, {4, 4}, {4, 4}, {4, 4},
+    {4, 4}, {4, 4}, {4, 4}, {4, 4},
+  {4, 5}, {4, 5}, {4, 5}, {4, 5},		// 1100xxxxx
+    {4, 5}, {4, 5}, {4, 5}, {4, 5},
+    {4, 5}, {4, 5}, {4, 5}, {4, 5},
+    {4, 5}, {4, 5}, {4, 5}, {4, 5},
+    {4, 5}, {4, 5}, {4, 5}, {4, 5},
+    {4, 5}, {4, 5}, {4, 5}, {4, 5},
+    {4, 5}, {4, 5}, {4, 5}, {4, 5},
+    {4, 5}, {4, 5}, {4, 5}, {4, 5},
+  {6, 14}, {6, 14}, {6, 14}, {6, 14},		// 110100xxx
+    {6, 14}, {6, 14}, {6, 14}, {6, 14},
+  {6, 15}, {6, 15}, {6, 15}, {6, 15},		// 110101xxx
+    {6, 15}, {6, 15}, {6, 15}, {6, 15},
+  {5, 64}, {5, 64}, {5, 64}, {5, 64},		// 11011xxxx
+    {5, 64}, {5, 64}, {5, 64}, {5, 64},
+    {5, 64}, {5, 64}, {5, 64}, {5, 64},
+    {5, 64}, {5, 64}, {5, 64}, {5, 64},
+  {4, 6}, {4, 6}, {4, 6}, {4, 6},		// 1110xxxxx
+    {4, 6}, {4, 6}, {4, 6}, {4, 6},
+    {4, 6}, {4, 6}, {4, 6}, {4, 6},
+    {4, 6}, {4, 6}, {4, 6}, {4, 6},
+    {4, 6}, {4, 6}, {4, 6}, {4, 6},
+    {4, 6}, {4, 6}, {4, 6}, {4, 6},
+    {4, 6}, {4, 6}, {4, 6}, {4, 6},
+    {4, 6}, {4, 6}, {4, 6}, {4, 6},
+  {4, 7}, {4, 7}, {4, 7}, {4, 7},		// 1111xxxxx
+    {4, 7}, {4, 7}, {4, 7}, {4, 7},
+    {4, 7}, {4, 7}, {4, 7}, {4, 7},
+    {4, 7}, {4, 7}, {4, 7}, {4, 7},
+    {4, 7}, {4, 7}, {4, 7}, {4, 7},
+    {4, 7}, {4, 7}, {4, 7}, {4, 7},
+    {4, 7}, {4, 7}, {4, 7}, {4, 7},
+    {4, 7}, {4, 7}, {4, 7}, {4, 7}
+};
+
+//------------------------------------------------------------------------
+// black run lengths
+//------------------------------------------------------------------------
+
+// 10-13 bit codes (upper 6 bits are 0)
+static CCITTCode blackTab1[128] = {
+  {-1, -1}, {-1, -1},					// 000000000000x
+  {12, ccittEOL}, {12, ccittEOL},			// 000000000001x
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},		// 00000000001xx
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},		// 00000000010xx
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},		// 00000000011xx
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},		// 00000000100xx
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},		// 00000000101xx
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},		// 00000000110xx
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},		// 00000000111xx
+  {11, 1792}, {11, 1792}, {11, 1792}, {11, 1792},	// 00000001000xx
+  {12, 1984}, {12, 1984},				// 000000010010x
+  {12, 2048}, {12, 2048},				// 000000010011x
+  {12, 2112}, {12, 2112},				// 000000010100x
+  {12, 2176}, {12, 2176},				// 000000010101x
+  {12, 2240}, {12, 2240},				// 000000010110x
+  {12, 2304}, {12, 2304},				// 000000010111x
+  {11, 1856}, {11, 1856}, {11, 1856}, {11, 1856},	// 00000001100xx
+  {11, 1920}, {11, 1920}, {11, 1920}, {11, 1920},	// 00000001101xx
+  {12, 2368}, {12, 2368},				// 000000011100x
+  {12, 2432}, {12, 2432},				// 000000011101x
+  {12, 2496}, {12, 2496},				// 000000011110x
+  {12, 2560}, {12, 2560},				// 000000011111x
+  {10, 18}, {10, 18}, {10, 18}, {10, 18},		// 0000001000xxx
+    {10, 18}, {10, 18}, {10, 18}, {10, 18},
+  {12, 52}, {12, 52},					// 000000100100x
+  {13, 640},						// 0000001001010
+  {13, 704},						// 0000001001011
+  {13, 768},						// 0000001001100
+  {13, 832},						// 0000001001101
+  {12, 55}, {12, 55},					// 000000100111x
+  {12, 56}, {12, 56},					// 000000101000x
+  {13, 1280},						// 0000001010010
+  {13, 1344},						// 0000001010011
+  {13, 1408},						// 0000001010100
+  {13, 1472},						// 0000001010101
+  {12, 59}, {12, 59},					// 000000101011x
+  {12, 60}, {12, 60},					// 000000101100x
+  {13, 1536},						// 0000001011010
+  {13, 1600},						// 0000001011011
+  {11, 24}, {11, 24}, {11, 24}, {11, 24},		// 00000010111xx
+  {11, 25}, {11, 25}, {11, 25}, {11, 25},		// 00000011000xx
+  {13, 1664},						// 0000001100100
+  {13, 1728},						// 0000001100101
+  {12, 320}, {12, 320},					// 000000110011x
+  {12, 384}, {12, 384},					// 000000110100x
+  {12, 448}, {12, 448},					// 000000110101x
+  {13, 512},						// 0000001101100
+  {13, 576},						// 0000001101101
+  {12, 53}, {12, 53},					// 000000110111x
+  {12, 54}, {12, 54},					// 000000111000x
+  {13, 896},						// 0000001110010
+  {13, 960},						// 0000001110011
+  {13, 1024},						// 0000001110100
+  {13, 1088},						// 0000001110101
+  {13, 1152},						// 0000001110110
+  {13, 1216},						// 0000001110111
+  {10, 64}, {10, 64}, {10, 64}, {10, 64},		// 0000001111xxx
+    {10, 64}, {10, 64}, {10, 64}, {10, 64}
+};
+
+// 7-12 bit codes (upper 4 bits are 0)
+static CCITTCode blackTab2[192] = {
+  {8, 13}, {8, 13}, {8, 13}, {8, 13},			// 00000100xxxx
+    {8, 13}, {8, 13}, {8, 13}, {8, 13},
+    {8, 13}, {8, 13}, {8, 13}, {8, 13},
+    {8, 13}, {8, 13}, {8, 13}, {8, 13},
+  {11, 23}, {11, 23},					// 00000101000x
+  {12, 50},						// 000001010010
+  {12, 51},						// 000001010011
+  {12, 44},						// 000001010100
+  {12, 45},						// 000001010101
+  {12, 46},						// 000001010110
+  {12, 47},						// 000001010111
+  {12, 57},						// 000001011000
+  {12, 58},						// 000001011001
+  {12, 61},						// 000001011010
+  {12, 256},						// 000001011011
+  {10, 16}, {10, 16}, {10, 16}, {10, 16},		// 0000010111xx
+  {10, 17}, {10, 17}, {10, 17}, {10, 17},		// 0000011000xx
+  {12, 48},						// 000001100100
+  {12, 49},						// 000001100101
+  {12, 62},						// 000001100110
+  {12, 63},						// 000001100111
+  {12, 30},						// 000001101000
+  {12, 31},						// 000001101001
+  {12, 32},						// 000001101010
+  {12, 33},						// 000001101011
+  {12, 40},						// 000001101100
+  {12, 41},						// 000001101101
+  {11, 22}, {11, 22},					// 00000110111x
+  {8, 14}, {8, 14}, {8, 14}, {8, 14},			// 00000111xxxx
+    {8, 14}, {8, 14}, {8, 14}, {8, 14},
+    {8, 14}, {8, 14}, {8, 14}, {8, 14},
+    {8, 14}, {8, 14}, {8, 14}, {8, 14},
+  {7, 10}, {7, 10}, {7, 10}, {7, 10},			// 0000100xxxxx
+    {7, 10}, {7, 10}, {7, 10}, {7, 10},
+    {7, 10}, {7, 10}, {7, 10}, {7, 10},
+    {7, 10}, {7, 10}, {7, 10}, {7, 10},
+    {7, 10}, {7, 10}, {7, 10}, {7, 10},
+    {7, 10}, {7, 10}, {7, 10}, {7, 10},
+    {7, 10}, {7, 10}, {7, 10}, {7, 10},
+    {7, 10}, {7, 10}, {7, 10}, {7, 10},
+  {7, 11}, {7, 11}, {7, 11}, {7, 11},			// 0000101xxxxx
+    {7, 11}, {7, 11}, {7, 11}, {7, 11},
+    {7, 11}, {7, 11}, {7, 11}, {7, 11},
+    {7, 11}, {7, 11}, {7, 11}, {7, 11},
+    {7, 11}, {7, 11}, {7, 11}, {7, 11},
+    {7, 11}, {7, 11}, {7, 11}, {7, 11},
+    {7, 11}, {7, 11}, {7, 11}, {7, 11},
+    {7, 11}, {7, 11}, {7, 11}, {7, 11},
+  {9, 15}, {9, 15}, {9, 15}, {9, 15},			// 000011000xxx
+    {9, 15}, {9, 15}, {9, 15}, {9, 15},
+  {12, 128},						// 000011001000
+  {12, 192},						// 000011001001
+  {12, 26},						// 000011001010
+  {12, 27},						// 000011001011
+  {12, 28},						// 000011001100
+  {12, 29},						// 000011001101
+  {11, 19}, {11, 19},					// 00001100111x
+  {11, 20}, {11, 20},					// 00001101000x
+  {12, 34},						// 000011010010
+  {12, 35},						// 000011010011
+  {12, 36},						// 000011010100
+  {12, 37},						// 000011010101
+  {12, 38},						// 000011010110
+  {12, 39},						// 000011010111
+  {11, 21}, {11, 21},					// 00001101100x
+  {12, 42},						// 000011011010
+  {12, 43},						// 000011011011
+  {10, 0}, {10, 0}, {10, 0}, {10, 0},			// 0000110111xx
+  {7, 12}, {7, 12}, {7, 12}, {7, 12},			// 0000111xxxxx
+    {7, 12}, {7, 12}, {7, 12}, {7, 12},
+    {7, 12}, {7, 12}, {7, 12}, {7, 12},
+    {7, 12}, {7, 12}, {7, 12}, {7, 12},
+    {7, 12}, {7, 12}, {7, 12}, {7, 12},
+    {7, 12}, {7, 12}, {7, 12}, {7, 12},
+    {7, 12}, {7, 12}, {7, 12}, {7, 12},
+    {7, 12}, {7, 12}, {7, 12}, {7, 12}
+};
+
+// 2-6 bit codes
+static CCITTCode blackTab3[64] = {
+  {-1, -1}, {-1, -1}, {-1, -1}, {-1, -1},		// 0000xx
+  {6, 9},						// 000100
+  {6, 8},						// 000101
+  {5, 7}, {5, 7},					// 00011x
+  {4, 6}, {4, 6}, {4, 6}, {4, 6},			// 0010xx
+  {4, 5}, {4, 5}, {4, 5}, {4, 5},			// 0011xx
+  {3, 1}, {3, 1}, {3, 1}, {3, 1},			// 010xxx
+    {3, 1}, {3, 1}, {3, 1}, {3, 1},
+  {3, 4}, {3, 4}, {3, 4}, {3, 4},			// 011xxx
+    {3, 4}, {3, 4}, {3, 4}, {3, 4},
+  {2, 3}, {2, 3}, {2, 3}, {2, 3},			// 10xxxx
+    {2, 3}, {2, 3}, {2, 3}, {2, 3},
+    {2, 3}, {2, 3}, {2, 3}, {2, 3},
+    {2, 3}, {2, 3}, {2, 3}, {2, 3},
+  {2, 2}, {2, 2}, {2, 2}, {2, 2},			// 11xxxx
+    {2, 2}, {2, 2}, {2, 2}, {2, 2},
+    {2, 2}, {2, 2}, {2, 2}, {2, 2},
+    {2, 2}, {2, 2}, {2, 2}, {2, 2}
+};
diff --git a/xpdf/Stream.cc b/xpdf/Stream.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/Stream.cc
@@ -0,0 +1,4686 @@
+//========================================================================
+//
+// Stream.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <limits.h>
+#ifndef WIN32
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <ctype.h>
+#include "gmem.h"
+#include "gfile.h"
+#include "config.h"
+#include "Error.h"
+#include "Object.h"
+#include "Lexer.h"
+#include "GfxState.h"
+#include "Stream.h"
+#include "JBIG2Stream.h"
+#include "JPXStream.h"
+#include "Stream-CCITT.h"
+
+#ifdef __DJGPP__
+static GBool setDJSYSFLAGS = gFalse;
+#endif
+
+#ifdef VMS
+#ifdef __GNUC__
+#define SEEK_SET 0
+#define SEEK_CUR 1
+#define SEEK_END 2
+#endif
+#endif
+
+//------------------------------------------------------------------------
+// Stream (base class)
+//------------------------------------------------------------------------
+
+Stream::Stream() {
+  ref = 1;
+}
+
+Stream::~Stream() {
+}
+
+void Stream::close() {
+}
+
+int Stream::getRawChar() {
+  error(-1, "Internal: called getRawChar() on non-predictor stream");
+  return EOF;
+}
+
+char *Stream::getLine(char *buf, int size) {
+  int i;
+  int c;
+
+  if (lookChar() == EOF)
+    return NULL;
+  for (i = 0; i < size - 1; ++i) {
+    c = getChar();
+    if (c == EOF || c == '\n')
+      break;
+    if (c == '\r') {
+      if ((c = lookChar()) == '\n')
+	getChar();
+      break;
+    }
+    buf[i] = c;
+  }
+  buf[i] = '\0';
+  return buf;
+}
+
+GString *Stream::getPSFilter(int psLevel, char *indent) {
+  return new GString();
+}
+
+Stream *Stream::addFilters(Object *dict) {
+  Object obj, obj2;
+  Object params, params2;
+  Stream *str;
+  int i;
+
+  str = this;
+  dict->dictLookup("Filter", &obj);
+  if (obj.isNull()) {
+    obj.free();
+    dict->dictLookup("F", &obj);
+  }
+  dict->dictLookup("DecodeParms", &params);
+  if (params.isNull()) {
+    params.free();
+    dict->dictLookup("DP", &params);
+  }
+  if (obj.isName()) {
+    str = makeFilter(obj.getName(), str, &params);
+  } else if (obj.isArray()) {
+    for (i = 0; i < obj.arrayGetLength(); ++i) {
+      obj.arrayGet(i, &obj2);
+      if (params.isArray())
+	params.arrayGet(i, &params2);
+      else
+	params2.initNull();
+      if (obj2.isName()) {
+	str = makeFilter(obj2.getName(), str, &params2);
+      } else {
+	error(getPos(), "Bad filter name");
+	str = new EOFStream(str);
+      }
+      obj2.free();
+      params2.free();
+    }
+  } else if (!obj.isNull()) {
+    error(getPos(), "Bad 'Filter' attribute in stream");
+  }
+  obj.free();
+  params.free();
+
+  return str;
+}
+
+Stream *Stream::makeFilter(char *name, Stream *str, Object *params) {
+  int pred;			// parameters
+  int colors;
+  int bits;
+  int early;
+  int encoding;
+  GBool endOfLine, byteAlign, endOfBlock, black;
+  int columns, rows;
+  int colorXform;
+  Object globals, obj;
+
+  if (!strcmp(name, "ASCIIHexDecode") || !strcmp(name, "AHx")) {
+    str = new ASCIIHexStream(str);
+  } else if (!strcmp(name, "ASCII85Decode") || !strcmp(name, "A85")) {
+    str = new ASCII85Stream(str);
+  } else if (!strcmp(name, "LZWDecode") || !strcmp(name, "LZW")) {
+    pred = 1;
+    columns = 1;
+    colors = 1;
+    bits = 8;
+    early = 1;
+    if (params->isDict()) {
+      params->dictLookup("Predictor", &obj);
+      if (obj.isInt())
+	pred = obj.getInt();
+      obj.free();
+      params->dictLookup("Columns", &obj);
+      if (obj.isInt())
+	columns = obj.getInt();
+      obj.free();
+      params->dictLookup("Colors", &obj);
+      if (obj.isInt())
+	colors = obj.getInt();
+      obj.free();
+      params->dictLookup("BitsPerComponent", &obj);
+      if (obj.isInt())
+	bits = obj.getInt();
+      obj.free();
+      params->dictLookup("EarlyChange", &obj);
+      if (obj.isInt())
+	early = obj.getInt();
+      obj.free();
+    }
+    str = new LZWStream(str, pred, columns, colors, bits, early);
+  } else if (!strcmp(name, "RunLengthDecode") || !strcmp(name, "RL")) {
+    str = new RunLengthStream(str);
+  } else if (!strcmp(name, "CCITTFaxDecode") || !strcmp(name, "CCF")) {
+    encoding = 0;
+    endOfLine = gFalse;
+    byteAlign = gFalse;
+    columns = 1728;
+    rows = 0;
+    endOfBlock = gTrue;
+    black = gFalse;
+    if (params->isDict()) {
+      params->dictLookup("K", &obj);
+      if (obj.isInt()) {
+	encoding = obj.getInt();
+      }
+      obj.free();
+      params->dictLookup("EndOfLine", &obj);
+      if (obj.isBool()) {
+	endOfLine = obj.getBool();
+      }
+      obj.free();
+      params->dictLookup("EncodedByteAlign", &obj);
+      if (obj.isBool()) {
+	byteAlign = obj.getBool();
+      }
+      obj.free();
+      params->dictLookup("Columns", &obj);
+      if (obj.isInt()) {
+	columns = obj.getInt();
+      }
+      obj.free();
+      params->dictLookup("Rows", &obj);
+      if (obj.isInt()) {
+	rows = obj.getInt();
+      }
+      obj.free();
+      params->dictLookup("EndOfBlock", &obj);
+      if (obj.isBool()) {
+	endOfBlock = obj.getBool();
+      }
+      obj.free();
+      params->dictLookup("BlackIs1", &obj);
+      if (obj.isBool()) {
+	black = obj.getBool();
+      }
+      obj.free();
+    }
+    str = new CCITTFaxStream(str, encoding, endOfLine, byteAlign,
+			     columns, rows, endOfBlock, black);
+  } else if (!strcmp(name, "DCTDecode") || !strcmp(name, "DCT")) {
+    colorXform = -1;
+    if (params->isDict()) {
+      if (params->dictLookup("ColorTransform", &obj)->isInt()) {
+	colorXform = obj.getInt();
+      }
+      obj.free();
+    }
+    str = new DCTStream(str, colorXform);
+  } else if (!strcmp(name, "FlateDecode") || !strcmp(name, "Fl")) {
+    pred = 1;
+    columns = 1;
+    colors = 1;
+    bits = 8;
+    if (params->isDict()) {
+      params->dictLookup("Predictor", &obj);
+      if (obj.isInt())
+	pred = obj.getInt();
+      obj.free();
+      params->dictLookup("Columns", &obj);
+      if (obj.isInt())
+	columns = obj.getInt();
+      obj.free();
+      params->dictLookup("Colors", &obj);
+      if (obj.isInt())
+	colors = obj.getInt();
+      obj.free();
+      params->dictLookup("BitsPerComponent", &obj);
+      if (obj.isInt())
+	bits = obj.getInt();
+      obj.free();
+    }
+    str = new FlateStream(str, pred, columns, colors, bits);
+  } else if (!strcmp(name, "JBIG2Decode")) {
+    if (params->isDict()) {
+      params->dictLookup("JBIG2Globals", &globals);
+    }
+    str = new JBIG2Stream(str, &globals);
+    globals.free();
+  } else if (!strcmp(name, "JPXDecode")) {
+    str = new JPXStream(str);
+  } else {
+    error(getPos(), "Unknown filter '%s'", name);
+    str = new EOFStream(str);
+  }
+  return str;
+}
+
+//------------------------------------------------------------------------
+// BaseStream
+//------------------------------------------------------------------------
+
+BaseStream::BaseStream(Object *dictA) {
+  dict = *dictA;
+}
+
+BaseStream::~BaseStream() {
+  dict.free();
+}
+
+//------------------------------------------------------------------------
+// FilterStream
+//------------------------------------------------------------------------
+
+FilterStream::FilterStream(Stream *strA) {
+  str = strA;
+}
+
+FilterStream::~FilterStream() {
+}
+
+void FilterStream::close() {
+  str->close();
+}
+
+void FilterStream::setPos(Guint pos, int dir) {
+  error(-1, "Internal: called setPos() on FilterStream");
+}
+
+//------------------------------------------------------------------------
+// ImageStream
+//------------------------------------------------------------------------
+
+ImageStream::ImageStream(Stream *strA, int widthA, int nCompsA, int nBitsA) {
+  int imgLineSize;
+
+  str = strA;
+  width = widthA;
+  nComps = nCompsA;
+  nBits = nBitsA;
+
+  nVals = width * nComps;
+  if (nBits == 1) {
+    imgLineSize = (nVals + 7) & ~7;
+  } else {
+    imgLineSize = nVals;
+  }
+  imgLine = (Guchar *)gmallocn(imgLineSize, sizeof(Guchar));
+  imgIdx = nVals;
+}
+
+ImageStream::~ImageStream() {
+  gfree(imgLine);
+}
+
+void ImageStream::reset() {
+  str->reset();
+}
+
+GBool ImageStream::getPixel(Guchar *pix) {
+  int i;
+
+  if (imgIdx >= nVals) {
+    getLine();
+    imgIdx = 0;
+  }
+  for (i = 0; i < nComps; ++i) {
+    pix[i] = imgLine[imgIdx++];
+  }
+  return gTrue;
+}
+
+Guchar *ImageStream::getLine() {
+  Gulong buf, bitMask;
+  int bits;
+  int c;
+  int i;
+
+  if (nBits == 1) {
+    for (i = 0; i < nVals; i += 8) {
+      c = str->getChar();
+      imgLine[i+0] = (Guchar)((c >> 7) & 1);
+      imgLine[i+1] = (Guchar)((c >> 6) & 1);
+      imgLine[i+2] = (Guchar)((c >> 5) & 1);
+      imgLine[i+3] = (Guchar)((c >> 4) & 1);
+      imgLine[i+4] = (Guchar)((c >> 3) & 1);
+      imgLine[i+5] = (Guchar)((c >> 2) & 1);
+      imgLine[i+6] = (Guchar)((c >> 1) & 1);
+      imgLine[i+7] = (Guchar)(c & 1);
+    }
+  } else if (nBits == 8) {
+    for (i = 0; i < nVals; ++i) {
+      imgLine[i] = str->getChar();
+    }
+  } else {
+    bitMask = (1 << nBits) - 1;
+    buf = 0;
+    bits = 0;
+    for (i = 0; i < nVals; ++i) {
+      if (bits < nBits) {
+	buf = (buf << 8) | (str->getChar() & 0xff);
+	bits += 8;
+      }
+      imgLine[i] = (Guchar)((buf >> (bits - nBits)) & bitMask);
+      bits -= nBits;
+    }
+  }
+  return imgLine;
+}
+
+void ImageStream::skipLine() {
+  int n, i;
+
+  n = (nVals * nBits + 7) >> 3;
+  for (i = 0; i < n; ++i) {
+    str->getChar();
+  }
+}
+
+//------------------------------------------------------------------------
+// StreamPredictor
+//------------------------------------------------------------------------
+
+StreamPredictor::StreamPredictor(Stream *strA, int predictorA,
+				 int widthA, int nCompsA, int nBitsA) {
+  str = strA;
+  predictor = predictorA;
+  width = widthA;
+  nComps = nCompsA;
+  nBits = nBitsA;
+  predLine = NULL;
+  ok = gFalse;
+
+  nVals = width * nComps;
+  pixBytes = (nComps * nBits + 7) >> 3;
+  rowBytes = ((nVals * nBits + 7) >> 3) + pixBytes;
+  if (width <= 0 || nComps <= 0 || nBits <= 0 ||
+      nComps > gfxColorMaxComps ||
+      nBits > 16 ||
+      width >= INT_MAX / nComps ||      // check for overflow in nVals 
+      nVals >= (INT_MAX - 7) / nBits) { // check for overflow in rowBytes
+    return;
+  }
+  predLine = (Guchar *)gmalloc(rowBytes);
+  memset(predLine, 0, rowBytes);
+  predIdx = rowBytes;
+
+  ok = gTrue;
+}
+
+StreamPredictor::~StreamPredictor() {
+  gfree(predLine);
+}
+
+int StreamPredictor::lookChar() {
+  if (predIdx >= rowBytes) {
+    if (!getNextLine()) {
+      return EOF;
+    }
+  }
+  return predLine[predIdx];
+}
+
+int StreamPredictor::getChar() {
+  if (predIdx >= rowBytes) {
+    if (!getNextLine()) {
+      return EOF;
+    }
+  }
+  return predLine[predIdx++];
+}
+
+GBool StreamPredictor::getNextLine() {
+  int curPred;
+  Guchar upLeftBuf[gfxColorMaxComps * 2 + 1];
+  int left, up, upLeft, p, pa, pb, pc;
+  int c;
+  Gulong inBuf, outBuf, bitMask;
+  int inBits, outBits;
+  int i, j, k, kk;
+
+  // get PNG optimum predictor number
+  if (predictor >= 10) {
+    if ((curPred = str->getRawChar()) == EOF) {
+      return gFalse;
+    }
+    curPred += 10;
+  } else {
+    curPred = predictor;
+  }
+
+  // read the raw line, apply PNG (byte) predictor
+  memset(upLeftBuf, 0, pixBytes + 1);
+  for (i = pixBytes; i < rowBytes; ++i) {
+    for (j = pixBytes; j > 0; --j) {
+      upLeftBuf[j] = upLeftBuf[j-1];
+    }
+    upLeftBuf[0] = predLine[i];
+    if ((c = str->getRawChar()) == EOF) {
+      if (i > pixBytes) {
+	// this ought to return false, but some (broken) PDF files
+	// contain truncated image data, and Adobe apparently reads the
+	// last partial line
+	break;
+      }
+      return gFalse;
+    }
+    switch (curPred) {
+    case 11:			// PNG sub
+      predLine[i] = predLine[i - pixBytes] + (Guchar)c;
+      break;
+    case 12:			// PNG up
+      predLine[i] = predLine[i] + (Guchar)c;
+      break;
+    case 13:			// PNG average
+      predLine[i] = ((predLine[i - pixBytes] + predLine[i]) >> 1) +
+	            (Guchar)c;
+      break;
+    case 14:			// PNG Paeth
+      left = predLine[i - pixBytes];
+      up = predLine[i];
+      upLeft = upLeftBuf[pixBytes];
+      p = left + up - upLeft;
+      if ((pa = p - left) < 0)
+	pa = -pa;
+      if ((pb = p - up) < 0)
+	pb = -pb;
+      if ((pc = p - upLeft) < 0)
+	pc = -pc;
+      if (pa <= pb && pa <= pc)
+	predLine[i] = left + (Guchar)c;
+      else if (pb <= pc)
+	predLine[i] = up + (Guchar)c;
+      else
+	predLine[i] = upLeft + (Guchar)c;
+      break;
+    case 10:			// PNG none
+    default:			// no predictor or TIFF predictor
+      predLine[i] = (Guchar)c;
+      break;
+    }
+  }
+
+  // apply TIFF (component) predictor
+  if (predictor == 2) {
+    if (nBits == 1) {
+      inBuf = predLine[pixBytes - 1];
+      for (i = pixBytes; i < rowBytes; i += 8) {
+	// 1-bit add is just xor
+	inBuf = (inBuf << 8) | predLine[i];
+	predLine[i] ^= inBuf >> nComps;
+      }
+    } else if (nBits == 8) {
+      for (i = pixBytes; i < rowBytes; ++i) {
+	predLine[i] += predLine[i - nComps];
+      }
+    } else {
+      memset(upLeftBuf, 0, nComps + 1);
+      bitMask = (1 << nBits) - 1;
+      inBuf = outBuf = 0;
+      inBits = outBits = 0;
+      j = k = pixBytes;
+      for (i = 0; i < width; ++i) {
+	for (kk = 0; kk < nComps; ++kk) {
+	  if (inBits < nBits) {
+	    inBuf = (inBuf << 8) | (predLine[j++] & 0xff);
+	    inBits += 8;
+	  }
+	  upLeftBuf[kk] = (Guchar)((upLeftBuf[kk] +
+				    (inBuf >> (inBits - nBits))) & bitMask);
+	  inBits -= nBits;
+	  outBuf = (outBuf << nBits) | upLeftBuf[kk];
+	  outBits += nBits;
+	  if (outBits >= 8) {
+	    predLine[k++] = (Guchar)(outBuf >> (outBits - 8));
+	    outBits -= 8;
+	  }
+	}
+      }
+      if (outBits > 0) {
+	predLine[k++] = (Guchar)((outBuf << (8 - outBits)) +
+				 (inBuf & ((1 << (8 - outBits)) - 1)));
+      }
+    }
+  }
+
+  // reset to start of line
+  predIdx = pixBytes;
+
+  return gTrue;
+}
+
+//------------------------------------------------------------------------
+// FileStream
+//------------------------------------------------------------------------
+
+FileStream::FileStream(FILE *fA, Guint startA, GBool limitedA,
+		       Guint lengthA, Object *dictA):
+    BaseStream(dictA) {
+  f = fA;
+  start = startA;
+  limited = limitedA;
+  length = lengthA;
+  bufPtr = bufEnd = buf;
+  bufPos = start;
+  savePos = 0;
+  saved = gFalse;
+}
+
+FileStream::~FileStream() {
+  close();
+}
+
+Stream *FileStream::makeSubStream(Guint startA, GBool limitedA,
+				  Guint lengthA, Object *dictA) {
+  return new FileStream(f, startA, limitedA, lengthA, dictA);
+}
+
+void FileStream::reset() {
+#if HAVE_FSEEKO
+  savePos = (Guint)ftello(f);
+  fseeko(f, start, SEEK_SET);
+#elif HAVE_FSEEK64
+  savePos = (Guint)ftell64(f);
+  fseek64(f, start, SEEK_SET);
+#else
+  savePos = (Guint)ftell(f);
+  fseek(f, start, SEEK_SET);
+#endif
+  saved = gTrue;
+  bufPtr = bufEnd = buf;
+  bufPos = start;
+}
+
+void FileStream::close() {
+  if (saved) {
+#if HAVE_FSEEKO
+    fseeko(f, savePos, SEEK_SET);
+#elif HAVE_FSEEK64
+    fseek64(f, savePos, SEEK_SET);
+#else
+    fseek(f, savePos, SEEK_SET);
+#endif
+    saved = gFalse;
+  }
+}
+
+GBool FileStream::fillBuf() {
+  int n;
+
+  bufPos += bufEnd - buf;
+  bufPtr = bufEnd = buf;
+  if (limited && bufPos >= start + length) {
+    return gFalse;
+  }
+  if (limited && bufPos + fileStreamBufSize > start + length) {
+    n = start + length - bufPos;
+  } else {
+    n = fileStreamBufSize;
+  }
+  n = fread(buf, 1, n, f);
+  bufEnd = buf + n;
+  if (bufPtr >= bufEnd) {
+    return gFalse;
+  }
+  return gTrue;
+}
+
+void FileStream::setPos(Guint pos, int dir) {
+  Guint size;
+
+  if (dir >= 0) {
+#if HAVE_FSEEKO
+    fseeko(f, pos, SEEK_SET);
+#elif HAVE_FSEEK64
+    fseek64(f, pos, SEEK_SET);
+#else
+    fseek(f, pos, SEEK_SET);
+#endif
+    bufPos = pos;
+  } else {
+#if HAVE_FSEEKO
+    fseeko(f, 0, SEEK_END);
+    size = (Guint)ftello(f);
+#elif HAVE_FSEEK64
+    fseek64(f, 0, SEEK_END);
+    size = (Guint)ftell64(f);
+#else
+    fseek(f, 0, SEEK_END);
+    size = (Guint)ftell(f);
+#endif
+    if (pos > size)
+      pos = (Guint)size;
+#ifdef __CYGWIN32__
+    //~ work around a bug in cygwin's implementation of fseek
+    rewind(f);
+#endif
+#if HAVE_FSEEKO
+    fseeko(f, -(int)pos, SEEK_END);
+    bufPos = (Guint)ftello(f);
+#elif HAVE_FSEEK64
+    fseek64(f, -(int)pos, SEEK_END);
+    bufPos = (Guint)ftell64(f);
+#else
+    fseek(f, -(int)pos, SEEK_END);
+    bufPos = (Guint)ftell(f);
+#endif
+  }
+  bufPtr = bufEnd = buf;
+}
+
+void FileStream::moveStart(int delta) {
+  start += delta;
+  bufPtr = bufEnd = buf;
+  bufPos = start;
+}
+
+//------------------------------------------------------------------------
+// MemStream
+//------------------------------------------------------------------------
+
+MemStream::MemStream(char *bufA, Guint startA, Guint lengthA, Object *dictA):
+    BaseStream(dictA) {
+  buf = bufA;
+  start = startA;
+  length = lengthA;
+  bufEnd = buf + start + length;
+  bufPtr = buf + start;
+  needFree = gFalse;
+}
+
+MemStream::~MemStream() {
+  if (needFree) {
+    gfree(buf);
+  }
+}
+
+Stream *MemStream::makeSubStream(Guint startA, GBool limited,
+				 Guint lengthA, Object *dictA) {
+  MemStream *subStr;
+  Guint newLength;
+
+  if (!limited || startA + lengthA > start + length) {
+    newLength = start + length - startA;
+  } else {
+    newLength = lengthA;
+  }
+  subStr = new MemStream(buf, startA, newLength, dictA);
+  return subStr;
+}
+
+void MemStream::reset() {
+  bufPtr = buf + start;
+}
+
+void MemStream::close() {
+}
+
+void MemStream::setPos(Guint pos, int dir) {
+  Guint i;
+
+  if (dir >= 0) {
+    i = pos;
+  } else {
+    i = start + length - pos;
+  }
+  if (i < start) {
+    i = start;
+  } else if (i > start + length) {
+    i = start + length;
+  }
+  bufPtr = buf + i;
+}
+
+void MemStream::moveStart(int delta) {
+  start += delta;
+  length -= delta;
+  bufPtr = buf + start;
+}
+
+//------------------------------------------------------------------------
+// EmbedStream
+//------------------------------------------------------------------------
+
+EmbedStream::EmbedStream(Stream *strA, Object *dictA,
+			 GBool limitedA, Guint lengthA):
+    BaseStream(dictA) {
+  str = strA;
+  limited = limitedA;
+  length = lengthA;
+}
+
+EmbedStream::~EmbedStream() {
+}
+
+Stream *EmbedStream::makeSubStream(Guint start, GBool limitedA,
+				   Guint lengthA, Object *dictA) {
+  error(-1, "Internal: called makeSubStream() on EmbedStream");
+  return NULL;
+}
+
+int EmbedStream::getChar() {
+  if (limited && !length) {
+    return EOF;
+  }
+  --length;
+  return str->getChar();
+}
+
+int EmbedStream::lookChar() {
+  if (limited && !length) {
+    return EOF;
+  }
+  return str->lookChar();
+}
+
+void EmbedStream::setPos(Guint pos, int dir) {
+  error(-1, "Internal: called setPos() on EmbedStream");
+}
+
+Guint EmbedStream::getStart() {
+  error(-1, "Internal: called getStart() on EmbedStream");
+  return 0;
+}
+
+void EmbedStream::moveStart(int delta) {
+  error(-1, "Internal: called moveStart() on EmbedStream");
+}
+
+//------------------------------------------------------------------------
+// ASCIIHexStream
+//------------------------------------------------------------------------
+
+ASCIIHexStream::ASCIIHexStream(Stream *strA):
+    FilterStream(strA) {
+  buf = EOF;
+  eof = gFalse;
+}
+
+ASCIIHexStream::~ASCIIHexStream() {
+  delete str;
+}
+
+void ASCIIHexStream::reset() {
+  str->reset();
+  buf = EOF;
+  eof = gFalse;
+}
+
+int ASCIIHexStream::lookChar() {
+  int c1, c2, x;
+
+  if (buf != EOF)
+    return buf;
+  if (eof) {
+    buf = EOF;
+    return EOF;
+  }
+  do {
+    c1 = str->getChar();
+  } while (isspace(c1));
+  if (c1 == '>') {
+    eof = gTrue;
+    buf = EOF;
+    return buf;
+  }
+  do {
+    c2 = str->getChar();
+  } while (isspace(c2));
+  if (c2 == '>') {
+    eof = gTrue;
+    c2 = '0';
+  }
+  if (c1 >= '0' && c1 <= '9') {
+    x = (c1 - '0') << 4;
+  } else if (c1 >= 'A' && c1 <= 'F') {
+    x = (c1 - 'A' + 10) << 4;
+  } else if (c1 >= 'a' && c1 <= 'f') {
+    x = (c1 - 'a' + 10) << 4;
+  } else if (c1 == EOF) {
+    eof = gTrue;
+    x = 0;
+  } else {
+    error(getPos(), "Illegal character <%02x> in ASCIIHex stream", c1);
+    x = 0;
+  }
+  if (c2 >= '0' && c2 <= '9') {
+    x += c2 - '0';
+  } else if (c2 >= 'A' && c2 <= 'F') {
+    x += c2 - 'A' + 10;
+  } else if (c2 >= 'a' && c2 <= 'f') {
+    x += c2 - 'a' + 10;
+  } else if (c2 == EOF) {
+    eof = gTrue;
+    x = 0;
+  } else {
+    error(getPos(), "Illegal character <%02x> in ASCIIHex stream", c2);
+  }
+  buf = x & 0xff;
+  return buf;
+}
+
+GString *ASCIIHexStream::getPSFilter(int psLevel, char *indent) {
+  GString *s;
+
+  if (psLevel < 2) {
+    return NULL;
+  }
+  if (!(s = str->getPSFilter(psLevel, indent))) {
+    return NULL;
+  }
+  s->append(indent)->append("/ASCIIHexDecode filter\n");
+  return s;
+}
+
+GBool ASCIIHexStream::isBinary(GBool last) {
+  return str->isBinary(gFalse);
+}
+
+//------------------------------------------------------------------------
+// ASCII85Stream
+//------------------------------------------------------------------------
+
+ASCII85Stream::ASCII85Stream(Stream *strA):
+    FilterStream(strA) {
+  index = n = 0;
+  eof = gFalse;
+}
+
+ASCII85Stream::~ASCII85Stream() {
+  delete str;
+}
+
+void ASCII85Stream::reset() {
+  str->reset();
+  index = n = 0;
+  eof = gFalse;
+}
+
+int ASCII85Stream::lookChar() {
+  int k;
+  Gulong t;
+
+  if (index >= n) {
+    if (eof)
+      return EOF;
+    index = 0;
+    do {
+      c[0] = str->getChar();
+    } while (Lexer::isSpace(c[0]));
+    if (c[0] == '~' || c[0] == EOF) {
+      eof = gTrue;
+      n = 0;
+      return EOF;
+    } else if (c[0] == 'z') {
+      b[0] = b[1] = b[2] = b[3] = 0;
+      n = 4;
+    } else {
+      for (k = 1; k < 5; ++k) {
+	do {
+	  c[k] = str->getChar();
+	} while (Lexer::isSpace(c[k]));
+	if (c[k] == '~' || c[k] == EOF)
+	  break;
+      }
+      n = k - 1;
+      if (k < 5 && (c[k] == '~' || c[k] == EOF)) {
+	for (++k; k < 5; ++k)
+	  c[k] = 0x21 + 84;
+	eof = gTrue;
+      }
+      t = 0;
+      for (k = 0; k < 5; ++k)
+	t = t * 85 + (c[k] - 0x21);
+      for (k = 3; k >= 0; --k) {
+	b[k] = (int)(t & 0xff);
+	t >>= 8;
+      }
+    }
+  }
+  return b[index];
+}
+
+GString *ASCII85Stream::getPSFilter(int psLevel, char *indent) {
+  GString *s;
+
+  if (psLevel < 2) {
+    return NULL;
+  }
+  if (!(s = str->getPSFilter(psLevel, indent))) {
+    return NULL;
+  }
+  s->append(indent)->append("/ASCII85Decode filter\n");
+  return s;
+}
+
+GBool ASCII85Stream::isBinary(GBool last) {
+  return str->isBinary(gFalse);
+}
+
+//------------------------------------------------------------------------
+// LZWStream
+//------------------------------------------------------------------------
+
+LZWStream::LZWStream(Stream *strA, int predictor, int columns, int colors,
+		     int bits, int earlyA):
+    FilterStream(strA) {
+  if (predictor != 1) {
+    pred = new StreamPredictor(this, predictor, columns, colors, bits);
+    if (!pred->isOk()) {
+      delete pred;
+      pred = NULL;
+    }
+  } else {
+    pred = NULL;
+  }
+  early = earlyA;
+  eof = gFalse;
+  inputBits = 0;
+  clearTable();
+}
+
+LZWStream::~LZWStream() {
+  if (pred) {
+    delete pred;
+  }
+  delete str;
+}
+
+int LZWStream::getChar() {
+  if (pred) {
+    return pred->getChar();
+  }
+  if (eof) {
+    return EOF;
+  }
+  if (seqIndex >= seqLength) {
+    if (!processNextCode()) {
+      return EOF;
+    }
+  }
+  return seqBuf[seqIndex++];
+}
+
+int LZWStream::lookChar() {
+  if (pred) {
+    return pred->lookChar();
+  }
+  if (eof) {
+    return EOF;
+  }
+  if (seqIndex >= seqLength) {
+    if (!processNextCode()) {
+      return EOF;
+    }
+  }
+  return seqBuf[seqIndex];
+}
+
+int LZWStream::getRawChar() {
+  if (eof) {
+    return EOF;
+  }
+  if (seqIndex >= seqLength) {
+    if (!processNextCode()) {
+      return EOF;
+    }
+  }
+  return seqBuf[seqIndex++];
+}
+
+void LZWStream::reset() {
+  str->reset();
+  eof = gFalse;
+  inputBits = 0;
+  clearTable();
+}
+
+GBool LZWStream::processNextCode() {
+  int code;
+  int nextLength;
+  int i, j;
+
+  // check for EOF
+  if (eof) {
+    return gFalse;
+  }
+
+  // check for eod and clear-table codes
+ start:
+  code = getCode();
+  if (code == EOF || code == 257) {
+    eof = gTrue;
+    return gFalse;
+  }
+  if (code == 256) {
+    clearTable();
+    goto start;
+  }
+  if (nextCode >= 4097) {
+    error(getPos(), "Bad LZW stream - expected clear-table code");
+    clearTable();
+  }
+
+  // process the next code
+  nextLength = seqLength + 1;
+  if (code < 256) {
+    seqBuf[0] = code;
+    seqLength = 1;
+  } else if (code < nextCode) {
+    seqLength = table[code].length;
+    for (i = seqLength - 1, j = code; i > 0; --i) {
+      seqBuf[i] = table[j].tail;
+      j = table[j].head;
+    }
+    seqBuf[0] = j;
+  } else if (code == nextCode) {
+    seqBuf[seqLength] = newChar;
+    ++seqLength;
+  } else {
+    error(getPos(), "Bad LZW stream - unexpected code");
+    eof = gTrue;
+    return gFalse;
+  }
+  newChar = seqBuf[0];
+  if (first) {
+    first = gFalse;
+  } else {
+    table[nextCode].length = nextLength;
+    table[nextCode].head = prevCode;
+    table[nextCode].tail = newChar;
+    ++nextCode;
+    if (nextCode + early == 512)
+      nextBits = 10;
+    else if (nextCode + early == 1024)
+      nextBits = 11;
+    else if (nextCode + early == 2048)
+      nextBits = 12;
+  }
+  prevCode = code;
+
+  // reset buffer
+  seqIndex = 0;
+
+  return gTrue;
+}
+
+void LZWStream::clearTable() {
+  nextCode = 258;
+  nextBits = 9;
+  seqIndex = seqLength = 0;
+  first = gTrue;
+}
+
+int LZWStream::getCode() {
+  int c;
+  int code;
+
+  while (inputBits < nextBits) {
+    if ((c = str->getChar()) == EOF)
+      return EOF;
+    inputBuf = (inputBuf << 8) | (c & 0xff);
+    inputBits += 8;
+  }
+  code = (inputBuf >> (inputBits - nextBits)) & ((1 << nextBits) - 1);
+  inputBits -= nextBits;
+  return code;
+}
+
+GString *LZWStream::getPSFilter(int psLevel, char *indent) {
+  GString *s;
+
+  if (psLevel < 2 || pred) {
+    return NULL;
+  }
+  if (!(s = str->getPSFilter(psLevel, indent))) {
+    return NULL;
+  }
+  s->append(indent)->append("<< ");
+  if (!early) {
+    s->append("/EarlyChange 0 ");
+  }
+  s->append(">> /LZWDecode filter\n");
+  return s;
+}
+
+GBool LZWStream::isBinary(GBool last) {
+  return str->isBinary(gTrue);
+}
+
+//------------------------------------------------------------------------
+// RunLengthStream
+//------------------------------------------------------------------------
+
+RunLengthStream::RunLengthStream(Stream *strA):
+    FilterStream(strA) {
+  bufPtr = bufEnd = buf;
+  eof = gFalse;
+}
+
+RunLengthStream::~RunLengthStream() {
+  delete str;
+}
+
+void RunLengthStream::reset() {
+  str->reset();
+  bufPtr = bufEnd = buf;
+  eof = gFalse;
+}
+
+GString *RunLengthStream::getPSFilter(int psLevel, char *indent) {
+  GString *s;
+
+  if (psLevel < 2) {
+    return NULL;
+  }
+  if (!(s = str->getPSFilter(psLevel, indent))) {
+    return NULL;
+  }
+  s->append(indent)->append("/RunLengthDecode filter\n");
+  return s;
+}
+
+GBool RunLengthStream::isBinary(GBool last) {
+  return str->isBinary(gTrue);
+}
+
+GBool RunLengthStream::fillBuf() {
+  int c;
+  int n, i;
+
+  if (eof)
+    return gFalse;
+  c = str->getChar();
+  if (c == 0x80 || c == EOF) {
+    eof = gTrue;
+    return gFalse;
+  }
+  if (c < 0x80) {
+    n = c + 1;
+    for (i = 0; i < n; ++i)
+      buf[i] = (char)str->getChar();
+  } else {
+    n = 0x101 - c;
+    c = str->getChar();
+    for (i = 0; i < n; ++i)
+      buf[i] = (char)c;
+  }
+  bufPtr = buf;
+  bufEnd = buf + n;
+  return gTrue;
+}
+
+//------------------------------------------------------------------------
+// CCITTFaxStream
+//------------------------------------------------------------------------
+
+CCITTFaxStream::CCITTFaxStream(Stream *strA, int encodingA, GBool endOfLineA,
+			       GBool byteAlignA, int columnsA, int rowsA,
+			       GBool endOfBlockA, GBool blackA):
+    FilterStream(strA) {
+  encoding = encodingA;
+  endOfLine = endOfLineA;
+  byteAlign = byteAlignA;
+  columns = columnsA;
+  if (columns < 1) {
+    columns = 1;
+  } else if (columns > INT_MAX - 2) {
+    columns = INT_MAX - 2;
+  }
+  rows = rowsA;
+  endOfBlock = endOfBlockA;
+  black = blackA;
+  // 0 <= codingLine[0] < codingLine[1] < ... < codingLine[n] = columns
+  // ---> max codingLine size = columns + 1
+  // refLine has one extra guard entry at the end
+  // ---> max refLine size = columns + 2
+  codingLine = (int *)gmallocn(columns + 1, sizeof(int));
+  refLine = (int *)gmallocn(columns + 2, sizeof(int));
+
+  eof = gFalse;
+  row = 0;
+  nextLine2D = encoding < 0;
+  inputBits = 0;
+  codingLine[0] = columns;
+  a0i = 0;
+  outputBits = 0;
+
+  buf = EOF;
+}
+
+CCITTFaxStream::~CCITTFaxStream() {
+  delete str;
+  gfree(refLine);
+  gfree(codingLine);
+}
+
+void CCITTFaxStream::reset() {
+  short code1;
+
+  str->reset();
+  eof = gFalse;
+  row = 0;
+  nextLine2D = encoding < 0;
+  inputBits = 0;
+  codingLine[0] = columns;
+  a0i = 0;
+  outputBits = 0;
+  buf = EOF;
+
+  // skip any initial zero bits and end-of-line marker, and get the 2D
+  // encoding tag
+  while ((code1 = lookBits(12)) == 0) {
+    eatBits(1);
+  }
+  if (code1 == 0x001) {
+    eatBits(12);
+  }
+  if (encoding > 0) {
+    nextLine2D = !lookBits(1);
+    eatBits(1);
+  }
+}
+
+inline void CCITTFaxStream::addPixels(int a1, int blackPixels) {
+  if (a1 > codingLine[a0i]) {
+    if (a1 > columns) {
+      error(getPos(), "CCITTFax row is wrong length (%d)", a1);
+      err = gTrue;
+      a1 = columns;
+    }
+    if ((a0i & 1) ^ blackPixels) {
+      ++a0i;
+    }
+    codingLine[a0i] = a1;
+  }
+}
+
+inline void CCITTFaxStream::addPixelsNeg(int a1, int blackPixels) {
+  if (a1 > codingLine[a0i]) {
+    if (a1 > columns) {
+      error(getPos(), "CCITTFax row is wrong length (%d)", a1);
+      err = gTrue;
+      a1 = columns;
+    }
+    if ((a0i & 1) ^ blackPixels) {
+      ++a0i;
+    }
+    codingLine[a0i] = a1;
+  } else if (a1 < codingLine[a0i]) {
+    if (a1 < 0) {
+      error(getPos(), "Invalid CCITTFax code");
+      err = gTrue;
+      a1 = 0;
+    }
+    while (a0i > 0 && a1 <= codingLine[a0i - 1]) {
+      --a0i;
+    }
+    codingLine[a0i] = a1;
+  }
+}
+
+int CCITTFaxStream::lookChar() {
+  short code1, code2, code3;
+  int b1i, blackPixels, i, bits;
+  GBool gotEOL;
+
+  if (buf != EOF) {
+    return buf;
+  }
+
+  // read the next row
+  if (outputBits == 0) {
+
+    // if at eof just return EOF
+    if (eof) {
+      return EOF;
+    }
+
+    err = gFalse;
+
+    // 2-D encoding
+    if (nextLine2D) {
+      for (i = 0; codingLine[i] < columns; ++i) {
+	refLine[i] = codingLine[i];
+      }
+      refLine[i++] = columns;
+      refLine[i] = columns;
+      codingLine[0] = 0;
+      a0i = 0;
+      b1i = 0;
+      blackPixels = 0;
+      // invariant:
+      // refLine[b1i-1] <= codingLine[a0i] < refLine[b1i] < refLine[b1i+1]
+      //                                                             <= columns
+      // exception at left edge:
+      //   codingLine[a0i = 0] = refLine[b1i = 0] = 0 is possible
+      // exception at right edge:
+      //   refLine[b1i] = refLine[b1i+1] = columns is possible
+      while (codingLine[a0i] < columns) {
+	code1 = getTwoDimCode();
+	switch (code1) {
+	case twoDimPass:
+	  addPixels(refLine[b1i + 1], blackPixels);
+	  if (refLine[b1i + 1] < columns) {
+	    b1i += 2;
+	  }
+	  break;
+	case twoDimHoriz:
+	  code1 = code2 = 0;
+	  if (blackPixels) {
+	    do {
+	      code1 += code3 = getBlackCode();
+	    } while (code3 >= 64);
+	    do {
+	      code2 += code3 = getWhiteCode();
+	    } while (code3 >= 64);
+	  } else {
+	    do {
+	      code1 += code3 = getWhiteCode();
+	    } while (code3 >= 64);
+	    do {
+	      code2 += code3 = getBlackCode();
+	    } while (code3 >= 64);
+	  }
+	  addPixels(codingLine[a0i] + code1, blackPixels);
+	  if (codingLine[a0i] < columns) {
+	    addPixels(codingLine[a0i] + code2, blackPixels ^ 1);
+	  }
+	  while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
+	    b1i += 2;
+	  }
+	  break;
+	case twoDimVertR3:
+	  addPixels(refLine[b1i] + 3, blackPixels);
+	  blackPixels ^= 1;
+	  if (codingLine[a0i] < columns) {
+	    ++b1i;
+	    while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
+	      b1i += 2;
+	    }
+	  }
+	  break;
+	case twoDimVertR2:
+	  addPixels(refLine[b1i] + 2, blackPixels);
+	  blackPixels ^= 1;
+	  if (codingLine[a0i] < columns) {
+	    ++b1i;
+	    while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
+	      b1i += 2;
+	    }
+	  }
+	  break;
+	case twoDimVertR1:
+	  addPixels(refLine[b1i] + 1, blackPixels);
+	  blackPixels ^= 1;
+	  if (codingLine[a0i] < columns) {
+	    ++b1i;
+	    while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
+	      b1i += 2;
+	    }
+	  }
+	  break;
+	case twoDimVert0:
+	  addPixels(refLine[b1i], blackPixels);
+	  blackPixels ^= 1;
+	  if (codingLine[a0i] < columns) {
+	    ++b1i;
+	    while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
+	      b1i += 2;
+	    }
+	  }
+	  break;
+	case twoDimVertL3:
+	  addPixelsNeg(refLine[b1i] - 3, blackPixels);
+	  blackPixels ^= 1;
+	  if (codingLine[a0i] < columns) {
+	    if (b1i > 0) {
+	      --b1i;
+	    } else {
+	      ++b1i;
+	    }
+	    while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
+	      b1i += 2;
+	    }
+	  }
+	  break;
+	case twoDimVertL2:
+	  addPixelsNeg(refLine[b1i] - 2, blackPixels);
+	  blackPixels ^= 1;
+	  if (codingLine[a0i] < columns) {
+	    if (b1i > 0) {
+	      --b1i;
+	    } else {
+	      ++b1i;
+	    }
+	    while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
+	      b1i += 2;
+	    }
+	  }
+	  break;
+	case twoDimVertL1:
+	  addPixelsNeg(refLine[b1i] - 1, blackPixels);
+	  blackPixels ^= 1;
+	  if (codingLine[a0i] < columns) {
+	    if (b1i > 0) {
+	      --b1i;
+	    } else {
+	      ++b1i;
+	    }
+	    while (refLine[b1i] <= codingLine[a0i] && refLine[b1i] < columns) {
+	      b1i += 2;
+	    }
+	  }
+	  break;
+	case EOF:
+	  addPixels(columns, 0);
+	  eof = gTrue;
+	  break;
+	default:
+	  error(getPos(), "Bad 2D code %04x in CCITTFax stream", code1);
+	  addPixels(columns, 0);
+	  err = gTrue;
+	  break;
+	}
+      }
+
+    // 1-D encoding
+    } else {
+      codingLine[0] = 0;
+      a0i = 0;
+      blackPixels = 0;
+      while (codingLine[a0i] < columns) {
+	code1 = 0;
+	if (blackPixels) {
+	  do {
+	    code1 += code3 = getBlackCode();
+	  } while (code3 >= 64);
+	} else {
+	  do {
+	    code1 += code3 = getWhiteCode();
+	  } while (code3 >= 64);
+	}
+	addPixels(codingLine[a0i] + code1, blackPixels);
+	blackPixels ^= 1;
+      }
+    }
+
+    // byte-align the row
+    if (byteAlign) {
+      inputBits &= ~7;
+    }
+
+    // check for end-of-line marker, skipping over any extra zero bits
+    gotEOL = gFalse;
+    if (!endOfBlock && row == rows - 1) {
+      eof = gTrue;
+    } else {
+      code1 = lookBits(12);
+      while (code1 == 0) {
+	eatBits(1);
+	code1 = lookBits(12);
+      }
+      if (code1 == 0x001) {
+	eatBits(12);
+	gotEOL = gTrue;
+      } else if (code1 == EOF) {
+	eof = gTrue;
+      }
+    }
+
+    // get 2D encoding tag
+    if (!eof && encoding > 0) {
+      nextLine2D = !lookBits(1);
+      eatBits(1);
+    }
+
+    // check for end-of-block marker
+    if (endOfBlock && gotEOL) {
+      code1 = lookBits(12);
+      if (code1 == 0x001) {
+	eatBits(12);
+	if (encoding > 0) {
+	  lookBits(1);
+	  eatBits(1);
+	}
+	if (encoding >= 0) {
+	  for (i = 0; i < 4; ++i) {
+	    code1 = lookBits(12);
+	    if (code1 != 0x001) {
+	      error(getPos(), "Bad RTC code in CCITTFax stream");
+	    }
+	    eatBits(12);
+	    if (encoding > 0) {
+	      lookBits(1);
+	      eatBits(1);
+	    }
+	  }
+	}
+	eof = gTrue;
+      }
+
+    // look for an end-of-line marker after an error -- we only do
+    // this if we know the stream contains end-of-line markers because
+    // the "just plow on" technique tends to work better otherwise
+    } else if (err && endOfLine) {
+      while (1) {
+	code1 = lookBits(13);
+	if (code1 == EOF) {
+	  eof = gTrue;
+	  return EOF;
+	}
+	if ((code1 >> 1) == 0x001) {
+	  break;
+	}
+	eatBits(1);
+      }
+      eatBits(12); 
+      if (encoding > 0) {
+	eatBits(1);
+	nextLine2D = !(code1 & 1);
+      }
+    }
+
+    // set up for output
+    if (codingLine[0] > 0) {
+      outputBits = codingLine[a0i = 0];
+    } else {
+      outputBits = codingLine[a0i = 1];
+    }
+
+    ++row;
+  }
+
+  // get a byte
+  if (outputBits >= 8) {
+    buf = (a0i & 1) ? 0x00 : 0xff;
+    outputBits -= 8;
+    if (outputBits == 0 && codingLine[a0i] < columns) {
+      ++a0i;
+      outputBits = codingLine[a0i] - codingLine[a0i - 1];
+    }
+  } else {
+    bits = 8;
+    buf = 0;
+    do {
+      if (outputBits > bits) {
+	buf <<= bits;
+	if (!(a0i & 1)) {
+	  buf |= 0xff >> (8 - bits);
+	}
+	outputBits -= bits;
+	bits = 0;
+      } else {
+	buf <<= outputBits;
+	if (!(a0i & 1)) {
+	  buf |= 0xff >> (8 - outputBits);
+	}
+	bits -= outputBits;
+	outputBits = 0;
+	if (codingLine[a0i] < columns) {
+	  ++a0i;
+	  outputBits = codingLine[a0i] - codingLine[a0i - 1];
+	} else if (bits > 0) {
+	  buf <<= bits;
+	  bits = 0;
+	}
+      }
+    } while (bits);
+  }
+  if (black) {
+    buf ^= 0xff;
+  }
+  return buf;
+}
+
+short CCITTFaxStream::getTwoDimCode() {
+  short code;
+  CCITTCode *p;
+  int n;
+
+  code = 0; // make gcc happy
+  if (endOfBlock) {
+    code = lookBits(7);
+    p = &twoDimTab1[code];
+    if (p->bits > 0) {
+      eatBits(p->bits);
+      return p->n;
+    }
+  } else {
+    for (n = 1; n <= 7; ++n) {
+      code = lookBits(n);
+      if (n < 7) {
+	code <<= 7 - n;
+      }
+      p = &twoDimTab1[code];
+      if (p->bits == n) {
+	eatBits(n);
+	return p->n;
+      }
+    }
+  }
+  error(getPos(), "Bad two dim code (%04x) in CCITTFax stream", code);
+  return EOF;
+}
+
+short CCITTFaxStream::getWhiteCode() {
+  short code;
+  CCITTCode *p;
+  int n;
+
+  code = 0; // make gcc happy
+  if (endOfBlock) {
+    code = lookBits(12);
+    if (code == EOF) {
+      return 1;
+    }
+    if ((code >> 5) == 0) {
+      p = &whiteTab1[code];
+    } else {
+      p = &whiteTab2[code >> 3];
+    }
+    if (p->bits > 0) {
+      eatBits(p->bits);
+      return p->n;
+    }
+  } else {
+    for (n = 1; n <= 9; ++n) {
+      code = lookBits(n);
+      if (code == EOF) {
+	return 1;
+      }
+      if (n < 9) {
+	code <<= 9 - n;
+      }
+      p = &whiteTab2[code];
+      if (p->bits == n) {
+	eatBits(n);
+	return p->n;
+      }
+    }
+    for (n = 11; n <= 12; ++n) {
+      code = lookBits(n);
+      if (code == EOF) {
+	return 1;
+      }
+      if (n < 12) {
+	code <<= 12 - n;
+      }
+      p = &whiteTab1[code];
+      if (p->bits == n) {
+	eatBits(n);
+	return p->n;
+      }
+    }
+  }
+  error(getPos(), "Bad white code (%04x) in CCITTFax stream", code);
+  // eat a bit and return a positive number so that the caller doesn't
+  // go into an infinite loop
+  eatBits(1);
+  return 1;
+}
+
+short CCITTFaxStream::getBlackCode() {
+  short code;
+  CCITTCode *p;
+  int n;
+
+  code = 0; // make gcc happy
+  if (endOfBlock) {
+    code = lookBits(13);
+    if (code == EOF) {
+      return 1;
+    }
+    if ((code >> 7) == 0) {
+      p = &blackTab1[code];
+    } else if ((code >> 9) == 0 && (code >> 7) != 0) {
+      p = &blackTab2[(code >> 1) - 64];
+    } else {
+      p = &blackTab3[code >> 7];
+    }
+    if (p->bits > 0) {
+      eatBits(p->bits);
+      return p->n;
+    }
+  } else {
+    for (n = 2; n <= 6; ++n) {
+      code = lookBits(n);
+      if (code == EOF) {
+	return 1;
+      }
+      if (n < 6) {
+	code <<= 6 - n;
+      }
+      p = &blackTab3[code];
+      if (p->bits == n) {
+	eatBits(n);
+	return p->n;
+      }
+    }
+    for (n = 7; n <= 12; ++n) {
+      code = lookBits(n);
+      if (code == EOF) {
+	return 1;
+      }
+      if (n < 12) {
+	code <<= 12 - n;
+      }
+      if (code >= 64) {
+	p = &blackTab2[code - 64];
+	if (p->bits == n) {
+	  eatBits(n);
+	  return p->n;
+	}
+      }
+    }
+    for (n = 10; n <= 13; ++n) {
+      code = lookBits(n);
+      if (code == EOF) {
+	return 1;
+      }
+      if (n < 13) {
+	code <<= 13 - n;
+      }
+      p = &blackTab1[code];
+      if (p->bits == n) {
+	eatBits(n);
+	return p->n;
+      }
+    }
+  }
+  error(getPos(), "Bad black code (%04x) in CCITTFax stream", code);
+  // eat a bit and return a positive number so that the caller doesn't
+  // go into an infinite loop
+  eatBits(1);
+  return 1;
+}
+
+short CCITTFaxStream::lookBits(int n) {
+  int c;
+
+  while (inputBits < n) {
+    if ((c = str->getChar()) == EOF) {
+      if (inputBits == 0) {
+	return EOF;
+      }
+      // near the end of the stream, the caller may ask for more bits
+      // than are available, but there may still be a valid code in
+      // however many bits are available -- we need to return correct
+      // data in this case
+      return (inputBuf << (n - inputBits)) & (0xffff >> (16 - n));
+    }
+    inputBuf = (inputBuf << 8) + c;
+    inputBits += 8;
+  }
+  return (inputBuf >> (inputBits - n)) & (0xffff >> (16 - n));
+}
+
+GString *CCITTFaxStream::getPSFilter(int psLevel, char *indent) {
+  GString *s;
+  char s1[50];
+
+  if (psLevel < 2) {
+    return NULL;
+  }
+  if (!(s = str->getPSFilter(psLevel, indent))) {
+    return NULL;
+  }
+  s->append(indent)->append("<< ");
+  if (encoding != 0) {
+    sprintf(s1, "/K %d ", encoding);
+    s->append(s1);
+  }
+  if (endOfLine) {
+    s->append("/EndOfLine true ");
+  }
+  if (byteAlign) {
+    s->append("/EncodedByteAlign true ");
+  }
+  sprintf(s1, "/Columns %d ", columns);
+  s->append(s1);
+  if (rows != 0) {
+    sprintf(s1, "/Rows %d ", rows);
+    s->append(s1);
+  }
+  if (!endOfBlock) {
+    s->append("/EndOfBlock false ");
+  }
+  if (black) {
+    s->append("/BlackIs1 true ");
+  }
+  s->append(">> /CCITTFaxDecode filter\n");
+  return s;
+}
+
+GBool CCITTFaxStream::isBinary(GBool last) {
+  return str->isBinary(gTrue);
+}
+
+//------------------------------------------------------------------------
+// DCTStream
+//------------------------------------------------------------------------
+
+// IDCT constants (20.12 fixed point format)
+#define dctCos1    4017		// cos(pi/16)
+#define dctSin1     799		// sin(pi/16)
+#define dctCos3    3406		// cos(3*pi/16)
+#define dctSin3    2276		// sin(3*pi/16)
+#define dctCos6    1567		// cos(6*pi/16)
+#define dctSin6    3784		// sin(6*pi/16)
+#define dctSqrt2   5793		// sqrt(2)
+#define dctSqrt1d2 2896		// sqrt(2) / 2
+
+// color conversion parameters (16.16 fixed point format)
+#define dctCrToR   91881	//  1.4020
+#define dctCbToG  -22553	// -0.3441363
+#define dctCrToG  -46802	// -0.71413636
+#define dctCbToB  116130	//  1.772
+
+// clip [-256,511] --> [0,255]
+#define dctClipOffset 256
+static Guchar dctClip[768];
+static int dctClipInit = 0;
+
+// zig zag decode map
+static int dctZigZag[64] = {
+   0,
+   1,  8,
+  16,  9,  2,
+   3, 10, 17, 24,
+  32, 25, 18, 11, 4,
+   5, 12, 19, 26, 33, 40,
+  48, 41, 34, 27, 20, 13,  6,
+   7, 14, 21, 28, 35, 42, 49, 56,
+  57, 50, 43, 36, 29, 22, 15,
+  23, 30, 37, 44, 51, 58,
+  59, 52, 45, 38, 31,
+  39, 46, 53, 60,
+  61, 54, 47,
+  55, 62,
+  63
+};
+
+DCTStream::DCTStream(Stream *strA, GBool colorXformA):
+    FilterStream(strA) {
+  int i, j;
+
+  colorXform = colorXformA;
+  progressive = interleaved = gFalse;
+  width = height = 0;
+  mcuWidth = mcuHeight = 0;
+  numComps = 0;
+  comp = 0;
+  x = y = dy = 0;
+  for (i = 0; i < 4; ++i) {
+    for (j = 0; j < 32; ++j) {
+      rowBuf[i][j] = NULL;
+    }
+    frameBuf[i] = NULL;
+  }
+
+  if (!dctClipInit) {
+    for (i = -256; i < 0; ++i)
+      dctClip[dctClipOffset + i] = 0;
+    for (i = 0; i < 256; ++i)
+      dctClip[dctClipOffset + i] = i;
+    for (i = 256; i < 512; ++i)
+      dctClip[dctClipOffset + i] = 255;
+    dctClipInit = 1;
+  }
+}
+
+DCTStream::~DCTStream() {
+  close();
+  delete str;
+}
+
+void DCTStream::reset() {
+  int i, j;
+
+  str->reset();
+
+  progressive = interleaved = gFalse;
+  width = height = 0;
+  numComps = 0;
+  numQuantTables = 0;
+  numDCHuffTables = 0;
+  numACHuffTables = 0;
+  gotJFIFMarker = gFalse;
+  gotAdobeMarker = gFalse;
+  restartInterval = 0;
+
+  if (!readHeader()) {
+    y = height;
+    return;
+  }
+
+  // compute MCU size
+  if (numComps == 1) {
+    compInfo[0].hSample = compInfo[0].vSample = 1;
+  }
+  mcuWidth = compInfo[0].hSample;
+  mcuHeight = compInfo[0].vSample;
+  for (i = 1; i < numComps; ++i) {
+    if (compInfo[i].hSample > mcuWidth) {
+      mcuWidth = compInfo[i].hSample;
+    }
+    if (compInfo[i].vSample > mcuHeight) {
+      mcuHeight = compInfo[i].vSample;
+    }
+  }
+  mcuWidth *= 8;
+  mcuHeight *= 8;
+
+  // figure out color transform
+  if (colorXform == -1) {
+    if (numComps == 3) {
+      if (gotJFIFMarker) {
+	colorXform = 1;
+      } else if (compInfo[0].id == 82 && compInfo[1].id == 71 &&
+		 compInfo[2].id == 66) { // ASCII "RGB"
+	colorXform = 0;
+      } else {
+	colorXform = 1;
+      }
+    } else {
+      colorXform = 0;
+    }
+  }
+
+  if (progressive || !interleaved) {
+
+    // allocate a buffer for the whole image
+    bufWidth = ((width + mcuWidth - 1) / mcuWidth) * mcuWidth;
+    bufHeight = ((height + mcuHeight - 1) / mcuHeight) * mcuHeight;
+    if (bufWidth <= 0 || bufHeight <= 0 ||
+	bufWidth > INT_MAX / bufWidth / (int)sizeof(int)) {
+      error(getPos(), "Invalid image size in DCT stream");
+      y = height;
+      return;
+    }
+    for (i = 0; i < numComps; ++i) {
+      frameBuf[i] = (int *)gmallocn(bufWidth * bufHeight, sizeof(int));
+      memset(frameBuf[i], 0, bufWidth * bufHeight * sizeof(int));
+    }
+
+    // read the image data
+    do {
+      restartMarker = 0xd0;
+      restart();
+      readScan();
+    } while (readHeader());
+
+    // decode
+    decodeImage();
+
+    // initialize counters
+    comp = 0;
+    x = 0;
+    y = 0;
+
+  } else {
+
+    // allocate a buffer for one row of MCUs
+    bufWidth = ((width + mcuWidth - 1) / mcuWidth) * mcuWidth;
+    for (i = 0; i < numComps; ++i) {
+      for (j = 0; j < mcuHeight; ++j) {
+	rowBuf[i][j] = (Guchar *)gmallocn(bufWidth, sizeof(Guchar));
+      }
+    }
+
+    // initialize counters
+    comp = 0;
+    x = 0;
+    y = 0;
+    dy = mcuHeight;
+
+    restartMarker = 0xd0;
+    restart();
+  }
+}
+
+void DCTStream::close() {
+  int i, j;
+
+  for (i = 0; i < 4; ++i) {
+    for (j = 0; j < 32; ++j) {
+      gfree(rowBuf[i][j]);
+      rowBuf[i][j] = NULL;
+    }
+    gfree(frameBuf[i]);
+    frameBuf[i] = NULL;
+  }
+  FilterStream::close();
+}
+
+int DCTStream::getChar() {
+  int c;
+
+  if (y >= height) {
+    return EOF;
+  }
+  if (progressive || !interleaved) {
+    c = frameBuf[comp][y * bufWidth + x];
+    if (++comp == numComps) {
+      comp = 0;
+      if (++x == width) {
+	x = 0;
+	++y;
+      }
+    }
+  } else {
+    if (dy >= mcuHeight) {
+      if (!readMCURow()) {
+	y = height;
+	return EOF;
+      }
+      comp = 0;
+      x = 0;
+      dy = 0;
+    }
+    c = rowBuf[comp][dy][x];
+    if (++comp == numComps) {
+      comp = 0;
+      if (++x == width) {
+	x = 0;
+	++y;
+	++dy;
+	if (y == height) {
+	  readTrailer();
+	}
+      }
+    }
+  }
+  return c;
+}
+
+int DCTStream::lookChar() {
+  if (y >= height) {
+    return EOF;
+  }
+  if (progressive || !interleaved) {
+    return frameBuf[comp][y * bufWidth + x];
+  } else {
+    if (dy >= mcuHeight) {
+      if (!readMCURow()) {
+	y = height;
+	return EOF;
+      }
+      comp = 0;
+      x = 0;
+      dy = 0;
+    }
+    return rowBuf[comp][dy][x];
+  }
+}
+
+void DCTStream::restart() {
+  int i;
+
+  inputBits = 0;
+  restartCtr = restartInterval;
+  for (i = 0; i < numComps; ++i) {
+    compInfo[i].prevDC = 0;
+  }
+  eobRun = 0;
+}
+
+// Read one row of MCUs from a sequential JPEG stream.
+GBool DCTStream::readMCURow() {
+  int data1[64];
+  Guchar data2[64];
+  Guchar *p1, *p2;
+  int pY, pCb, pCr, pR, pG, pB;
+  int h, v, horiz, vert, hSub, vSub;
+  int x1, x2, y2, x3, y3, x4, y4, x5, y5, cc, i;
+  int c;
+
+  for (x1 = 0; x1 < width; x1 += mcuWidth) {
+
+    // deal with restart marker
+    if (restartInterval > 0 && restartCtr == 0) {
+      c = readMarker();
+      if (c != restartMarker) {
+	error(getPos(), "Bad DCT data: incorrect restart marker");
+	return gFalse;
+      }
+      if (++restartMarker == 0xd8)
+	restartMarker = 0xd0;
+      restart();
+    }
+
+    // read one MCU
+    for (cc = 0; cc < numComps; ++cc) {
+      h = compInfo[cc].hSample;
+      v = compInfo[cc].vSample;
+      horiz = mcuWidth / h;
+      vert = mcuHeight / v;
+      hSub = horiz / 8;
+      vSub = vert / 8;
+      for (y2 = 0; y2 < mcuHeight; y2 += vert) {
+	for (x2 = 0; x2 < mcuWidth; x2 += horiz) {
+	  if (!readDataUnit(&dcHuffTables[scanInfo.dcHuffTable[cc]],
+			    &acHuffTables[scanInfo.acHuffTable[cc]],
+			    &compInfo[cc].prevDC,
+			    data1)) {
+	    return gFalse;
+	  }
+	  transformDataUnit(quantTables[compInfo[cc].quantTable],
+			    data1, data2);
+	  if (hSub == 1 && vSub == 1) {
+	    for (y3 = 0, i = 0; y3 < 8; ++y3, i += 8) {
+	      p1 = &rowBuf[cc][y2+y3][x1+x2];
+	      p1[0] = data2[i];
+	      p1[1] = data2[i+1];
+	      p1[2] = data2[i+2];
+	      p1[3] = data2[i+3];
+	      p1[4] = data2[i+4];
+	      p1[5] = data2[i+5];
+	      p1[6] = data2[i+6];
+	      p1[7] = data2[i+7];
+	    }
+	  } else if (hSub == 2 && vSub == 2) {
+	    for (y3 = 0, i = 0; y3 < 16; y3 += 2, i += 8) {
+	      p1 = &rowBuf[cc][y2+y3][x1+x2];
+	      p2 = &rowBuf[cc][y2+y3+1][x1+x2];
+	      p1[0] = p1[1] = p2[0] = p2[1] = data2[i];
+	      p1[2] = p1[3] = p2[2] = p2[3] = data2[i+1];
+	      p1[4] = p1[5] = p2[4] = p2[5] = data2[i+2];
+	      p1[6] = p1[7] = p2[6] = p2[7] = data2[i+3];
+	      p1[8] = p1[9] = p2[8] = p2[9] = data2[i+4];
+	      p1[10] = p1[11] = p2[10] = p2[11] = data2[i+5];
+	      p1[12] = p1[13] = p2[12] = p2[13] = data2[i+6];
+	      p1[14] = p1[15] = p2[14] = p2[15] = data2[i+7];
+	    }
+	  } else {
+	    i = 0;
+	    for (y3 = 0, y4 = 0; y3 < 8; ++y3, y4 += vSub) {
+	      for (x3 = 0, x4 = 0; x3 < 8; ++x3, x4 += hSub) {
+		for (y5 = 0; y5 < vSub; ++y5)
+		  for (x5 = 0; x5 < hSub; ++x5)
+		    rowBuf[cc][y2+y4+y5][x1+x2+x4+x5] = data2[i];
+		++i;
+	      }
+	    }
+	  }
+	}
+      }
+    }
+    --restartCtr;
+
+    // color space conversion
+    if (colorXform) {
+      // convert YCbCr to RGB
+      if (numComps == 3) {
+	for (y2 = 0; y2 < mcuHeight; ++y2) {
+	  for (x2 = 0; x2 < mcuWidth; ++x2) {
+	    pY = rowBuf[0][y2][x1+x2];
+	    pCb = rowBuf[1][y2][x1+x2] - 128;
+	    pCr = rowBuf[2][y2][x1+x2] - 128;
+	    pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16;
+	    rowBuf[0][y2][x1+x2] = dctClip[dctClipOffset + pR];
+	    pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16;
+	    rowBuf[1][y2][x1+x2] = dctClip[dctClipOffset + pG];
+	    pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16;
+	    rowBuf[2][y2][x1+x2] = dctClip[dctClipOffset + pB];
+	  }
+	}
+      // convert YCbCrK to CMYK (K is passed through unchanged)
+      } else if (numComps == 4) {
+	for (y2 = 0; y2 < mcuHeight; ++y2) {
+	  for (x2 = 0; x2 < mcuWidth; ++x2) {
+	    pY = rowBuf[0][y2][x1+x2];
+	    pCb = rowBuf[1][y2][x1+x2] - 128;
+	    pCr = rowBuf[2][y2][x1+x2] - 128;
+	    pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16;
+	    rowBuf[0][y2][x1+x2] = 255 - dctClip[dctClipOffset + pR];
+	    pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16;
+	    rowBuf[1][y2][x1+x2] = 255 - dctClip[dctClipOffset + pG];
+	    pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16;
+	    rowBuf[2][y2][x1+x2] = 255 - dctClip[dctClipOffset + pB];
+	  }
+	}
+      }
+    }
+  }
+  return gTrue;
+}
+
+// Read one scan from a progressive or non-interleaved JPEG stream.
+void DCTStream::readScan() {
+  int data[64];
+  int x1, y1, dx1, dy1, x2, y2, y3, cc, i;
+  int h, v, horiz, vert, vSub;
+  int *p1;
+  int c;
+
+  if (scanInfo.numComps == 1) {
+    for (cc = 0; cc < numComps; ++cc) {
+      if (scanInfo.comp[cc]) {
+	break;
+      }
+    }
+    dx1 = mcuWidth / compInfo[cc].hSample;
+    dy1 = mcuHeight / compInfo[cc].vSample;
+  } else {
+    dx1 = mcuWidth;
+    dy1 = mcuHeight;
+  }
+
+  for (y1 = 0; y1 < height; y1 += dy1) {
+    for (x1 = 0; x1 < width; x1 += dx1) {
+
+      // deal with restart marker
+      if (restartInterval > 0 && restartCtr == 0) {
+	c = readMarker();
+	if (c != restartMarker) {
+	  error(getPos(), "Bad DCT data: incorrect restart marker");
+	  return;
+	}
+	if (++restartMarker == 0xd8) {
+	  restartMarker = 0xd0;
+	}
+	restart();
+      }
+
+      // read one MCU
+      for (cc = 0; cc < numComps; ++cc) {
+	if (!scanInfo.comp[cc]) {
+	  continue;
+	}
+
+	h = compInfo[cc].hSample;
+	v = compInfo[cc].vSample;
+	horiz = mcuWidth / h;
+	vert = mcuHeight / v;
+	vSub = vert / 8;
+	for (y2 = 0; y2 < dy1; y2 += vert) {
+	  for (x2 = 0; x2 < dx1; x2 += horiz) {
+
+	    // pull out the current values
+	    p1 = &frameBuf[cc][(y1+y2) * bufWidth + (x1+x2)];
+	    for (y3 = 0, i = 0; y3 < 8; ++y3, i += 8) {
+	      data[i] = p1[0];
+	      data[i+1] = p1[1];
+	      data[i+2] = p1[2];
+	      data[i+3] = p1[3];
+	      data[i+4] = p1[4];
+	      data[i+5] = p1[5];
+	      data[i+6] = p1[6];
+	      data[i+7] = p1[7];
+	      p1 += bufWidth * vSub;
+	    }
+
+	    // read one data unit
+	    if (progressive) {
+	      if (!readProgressiveDataUnit(
+		       &dcHuffTables[scanInfo.dcHuffTable[cc]],
+		       &acHuffTables[scanInfo.acHuffTable[cc]],
+		       &compInfo[cc].prevDC,
+		       data)) {
+		return;
+	      }
+	    } else {
+	      if (!readDataUnit(&dcHuffTables[scanInfo.dcHuffTable[cc]],
+				&acHuffTables[scanInfo.acHuffTable[cc]],
+				&compInfo[cc].prevDC,
+				data)) {
+		return;
+	      }
+	    }
+
+	    // add the data unit into frameBuf
+	    p1 = &frameBuf[cc][(y1+y2) * bufWidth + (x1+x2)];
+	    for (y3 = 0, i = 0; y3 < 8; ++y3, i += 8) {
+	      p1[0] = data[i];
+	      p1[1] = data[i+1];
+	      p1[2] = data[i+2];
+	      p1[3] = data[i+3];
+	      p1[4] = data[i+4];
+	      p1[5] = data[i+5];
+	      p1[6] = data[i+6];
+	      p1[7] = data[i+7];
+	      p1 += bufWidth * vSub;
+	    }
+	  }
+	}
+      }
+      --restartCtr;
+    }
+  }
+}
+
+// Read one data unit from a sequential JPEG stream.
+GBool DCTStream::readDataUnit(DCTHuffTable *dcHuffTable,
+			      DCTHuffTable *acHuffTable,
+			      int *prevDC, int data[64]) {
+  int run, size, amp;
+  int c;
+  int i, j;
+
+  if ((size = readHuffSym(dcHuffTable)) == 9999) {
+    return gFalse;
+  }
+  if (size > 0) {
+    if ((amp = readAmp(size)) == 9999) {
+      return gFalse;
+    }
+  } else {
+    amp = 0;
+  }
+  data[0] = *prevDC += amp;
+  for (i = 1; i < 64; ++i) {
+    data[i] = 0;
+  }
+  i = 1;
+  while (i < 64) {
+    run = 0;
+    while ((c = readHuffSym(acHuffTable)) == 0xf0 && run < 0x30) {
+      run += 0x10;
+    }
+    if (c == 9999) {
+      return gFalse;
+    }
+    if (c == 0x00) {
+      break;
+    } else {
+      run += (c >> 4) & 0x0f;
+      size = c & 0x0f;
+      amp = readAmp(size);
+      if (amp == 9999) {
+	return gFalse;
+      }
+      i += run;
+      if (i < 64) {
+	j = dctZigZag[i++];
+	data[j] = amp;
+      }
+    }
+  }
+  return gTrue;
+}
+
+// Read one data unit from a sequential JPEG stream.
+GBool DCTStream::readProgressiveDataUnit(DCTHuffTable *dcHuffTable,
+					 DCTHuffTable *acHuffTable,
+					 int *prevDC, int data[64]) {
+  int run, size, amp, bit, c;
+  int i, j, k;
+
+  // get the DC coefficient
+  i = scanInfo.firstCoeff;
+  if (i == 0) {
+    if (scanInfo.ah == 0) {
+      if ((size = readHuffSym(dcHuffTable)) == 9999) {
+	return gFalse;
+      }
+      if (size > 0) {
+	if ((amp = readAmp(size)) == 9999) {
+	  return gFalse;
+	}
+      } else {
+	amp = 0;
+      }
+      data[0] += (*prevDC += amp) << scanInfo.al;
+    } else {
+      if ((bit = readBit()) == 9999) {
+	return gFalse;
+      }
+      data[0] += bit << scanInfo.al;
+    }
+    ++i;
+  }
+  if (scanInfo.lastCoeff == 0) {
+    return gTrue;
+  }
+
+  // check for an EOB run
+  if (eobRun > 0) {
+    while (i <= scanInfo.lastCoeff) {
+      j = dctZigZag[i++];
+      if (data[j] != 0) {
+	if ((bit = readBit()) == EOF) {
+	  return gFalse;
+	}
+	if (bit) {
+	  data[j] += 1 << scanInfo.al;
+	}
+      }
+    }
+    --eobRun;
+    return gTrue;
+  }
+
+  // read the AC coefficients
+  while (i <= scanInfo.lastCoeff) {
+    if ((c = readHuffSym(acHuffTable)) == 9999) {
+      return gFalse;
+    }
+
+    // ZRL
+    if (c == 0xf0) {
+      k = 0;
+      while (k < 16) {
+	j = dctZigZag[i++];
+	if (data[j] == 0) {
+	  ++k;
+	} else {
+	  if ((bit = readBit()) == EOF) {
+	    return gFalse;
+	  }
+	  if (bit) {
+	    data[j] += 1 << scanInfo.al;
+	  }
+	}
+      }
+
+    // EOB run
+    } else if ((c & 0x0f) == 0x00) {
+      j = c >> 4;
+      eobRun = 0;
+      for (k = 0; k < j; ++k) {
+	if ((bit = readBit()) == EOF) {
+	  return gFalse;
+	}
+	eobRun = (eobRun << 1) | bit;
+      }
+      eobRun += 1 << j;
+      while (i <= scanInfo.lastCoeff) {
+	j = dctZigZag[i++];
+	if (data[j] != 0) {
+	  if ((bit = readBit()) == EOF) {
+	    return gFalse;
+	  }
+	  if (bit) {
+	    data[j] += 1 << scanInfo.al;
+	  }
+	}
+      }
+      --eobRun;
+      break;
+
+    // zero run and one AC coefficient
+    } else {
+      run = (c >> 4) & 0x0f;
+      size = c & 0x0f;
+      if ((amp = readAmp(size)) == 9999) {
+	return gFalse;
+      }
+      k = 0;
+      do {
+	j = dctZigZag[i++];
+	while (data[j] != 0) {
+	  if ((bit = readBit()) == EOF) {
+	    return gFalse;
+	  }
+	  if (bit) {
+	    data[j] += 1 << scanInfo.al;
+	  }
+	  j = dctZigZag[i++];
+	}
+	++k;
+      } while (k <= run);
+      data[j] = amp << scanInfo.al;
+    }
+  }
+
+  return gTrue;
+}
+
+// Decode a progressive JPEG image.
+void DCTStream::decodeImage() {
+  int dataIn[64];
+  Guchar dataOut[64];
+  Gushort *quantTable;
+  int pY, pCb, pCr, pR, pG, pB;
+  int x1, y1, x2, y2, x3, y3, x4, y4, x5, y5, cc, i;
+  int h, v, horiz, vert, hSub, vSub;
+  int *p0, *p1, *p2;
+
+  for (y1 = 0; y1 < bufHeight; y1 += mcuHeight) {
+    for (x1 = 0; x1 < bufWidth; x1 += mcuWidth) {
+      for (cc = 0; cc < numComps; ++cc) {
+	quantTable = quantTables[compInfo[cc].quantTable];
+	h = compInfo[cc].hSample;
+	v = compInfo[cc].vSample;
+	horiz = mcuWidth / h;
+	vert = mcuHeight / v;
+	hSub = horiz / 8;
+	vSub = vert / 8;
+	for (y2 = 0; y2 < mcuHeight; y2 += vert) {
+	  for (x2 = 0; x2 < mcuWidth; x2 += horiz) {
+
+	    // pull out the coded data unit
+	    p1 = &frameBuf[cc][(y1+y2) * bufWidth + (x1+x2)];
+	    for (y3 = 0, i = 0; y3 < 8; ++y3, i += 8) {
+	      dataIn[i]   = p1[0];
+	      dataIn[i+1] = p1[1];
+	      dataIn[i+2] = p1[2];
+	      dataIn[i+3] = p1[3];
+	      dataIn[i+4] = p1[4];
+	      dataIn[i+5] = p1[5];
+	      dataIn[i+6] = p1[6];
+	      dataIn[i+7] = p1[7];
+	      p1 += bufWidth * vSub;
+	    }
+
+	    // transform
+	    transformDataUnit(quantTable, dataIn, dataOut);
+
+	    // store back into frameBuf, doing replication for
+	    // subsampled components
+	    p1 = &frameBuf[cc][(y1+y2) * bufWidth + (x1+x2)];
+	    if (hSub == 1 && vSub == 1) {
+	      for (y3 = 0, i = 0; y3 < 8; ++y3, i += 8) {
+		p1[0] = dataOut[i] & 0xff;
+		p1[1] = dataOut[i+1] & 0xff;
+		p1[2] = dataOut[i+2] & 0xff;
+		p1[3] = dataOut[i+3] & 0xff;
+		p1[4] = dataOut[i+4] & 0xff;
+		p1[5] = dataOut[i+5] & 0xff;
+		p1[6] = dataOut[i+6] & 0xff;
+		p1[7] = dataOut[i+7] & 0xff;
+		p1 += bufWidth;
+	      }
+	    } else if (hSub == 2 && vSub == 2) {
+	      p2 = p1 + bufWidth;
+	      for (y3 = 0, i = 0; y3 < 16; y3 += 2, i += 8) {
+		p1[0] = p1[1] = p2[0] = p2[1] = dataOut[i] & 0xff;
+		p1[2] = p1[3] = p2[2] = p2[3] = dataOut[i+1] & 0xff;
+		p1[4] = p1[5] = p2[4] = p2[5] = dataOut[i+2] & 0xff;
+		p1[6] = p1[7] = p2[6] = p2[7] = dataOut[i+3] & 0xff;
+		p1[8] = p1[9] = p2[8] = p2[9] = dataOut[i+4] & 0xff;
+		p1[10] = p1[11] = p2[10] = p2[11] = dataOut[i+5] & 0xff;
+		p1[12] = p1[13] = p2[12] = p2[13] = dataOut[i+6] & 0xff;
+		p1[14] = p1[15] = p2[14] = p2[15] = dataOut[i+7] & 0xff;
+		p1 += bufWidth * 2;
+		p2 += bufWidth * 2;
+	      }
+	    } else {
+	      i = 0;
+	      for (y3 = 0, y4 = 0; y3 < 8; ++y3, y4 += vSub) {
+		for (x3 = 0, x4 = 0; x3 < 8; ++x3, x4 += hSub) {
+		  p2 = p1 + x4;
+		  for (y5 = 0; y5 < vSub; ++y5) {
+		    for (x5 = 0; x5 < hSub; ++x5) {
+		      p2[x5] = dataOut[i] & 0xff;
+		    }
+		    p2 += bufWidth;
+		  }
+		  ++i;
+		}
+		p1 += bufWidth * vSub;
+	      }
+	    }
+	  }
+	}
+      }
+
+      // color space conversion
+      if (colorXform) {
+	// convert YCbCr to RGB
+	if (numComps == 3) {
+	  for (y2 = 0; y2 < mcuHeight; ++y2) {
+	    p0 = &frameBuf[0][(y1+y2) * bufWidth + x1];
+	    p1 = &frameBuf[1][(y1+y2) * bufWidth + x1];
+	    p2 = &frameBuf[2][(y1+y2) * bufWidth + x1];
+	    for (x2 = 0; x2 < mcuWidth; ++x2) {
+	      pY = *p0;
+	      pCb = *p1 - 128;
+	      pCr = *p2 - 128;
+	      pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16;
+	      *p0++ = dctClip[dctClipOffset + pR];
+	      pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr +
+		    32768) >> 16;
+	      *p1++ = dctClip[dctClipOffset + pG];
+	      pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16;
+	      *p2++ = dctClip[dctClipOffset + pB];
+	    }
+	  }
+	// convert YCbCrK to CMYK (K is passed through unchanged)
+	} else if (numComps == 4) {
+	  for (y2 = 0; y2 < mcuHeight; ++y2) {
+	    p0 = &frameBuf[0][(y1+y2) * bufWidth + x1];
+	    p1 = &frameBuf[1][(y1+y2) * bufWidth + x1];
+	    p2 = &frameBuf[2][(y1+y2) * bufWidth + x1];
+	    for (x2 = 0; x2 < mcuWidth; ++x2) {
+	      pY = *p0;
+	      pCb = *p1 - 128;
+	      pCr = *p2 - 128;
+	      pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16;
+	      *p0++ = 255 - dctClip[dctClipOffset + pR];
+	      pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr +
+		    32768) >> 16;
+	      *p1++ = 255 - dctClip[dctClipOffset + pG];
+	      pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16;
+	      *p2++ = 255 - dctClip[dctClipOffset + pB];
+	    }
+	  }
+	}
+      }
+    }
+  }
+}
+
+// Transform one data unit -- this performs the dequantization and
+// IDCT steps.  This IDCT algorithm is taken from:
+//   Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
+//   "Practical Fast 1-D DCT Algorithms with 11 Multiplications",
+//   IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
+//   988-991.
+// The stage numbers mentioned in the comments refer to Figure 1 in this
+// paper.
+void DCTStream::transformDataUnit(Gushort *quantTable,
+				  int dataIn[64], Guchar dataOut[64]) {
+  int v0, v1, v2, v3, v4, v5, v6, v7, t;
+  int *p;
+  int i;
+
+  // dequant
+  for (i = 0; i < 64; ++i) {
+    dataIn[i] *= quantTable[i];
+  }
+
+  // inverse DCT on rows
+  for (i = 0; i < 64; i += 8) {
+    p = dataIn + i;
+
+    // check for all-zero AC coefficients
+    if (p[1] == 0 && p[2] == 0 && p[3] == 0 &&
+	p[4] == 0 && p[5] == 0 && p[6] == 0 && p[7] == 0) {
+      t = (dctSqrt2 * p[0] + 512) >> 10;
+      p[0] = t;
+      p[1] = t;
+      p[2] = t;
+      p[3] = t;
+      p[4] = t;
+      p[5] = t;
+      p[6] = t;
+      p[7] = t;
+      continue;
+    }
+
+    // stage 4
+    v0 = (dctSqrt2 * p[0] + 128) >> 8;
+    v1 = (dctSqrt2 * p[4] + 128) >> 8;
+    v2 = p[2];
+    v3 = p[6];
+    v4 = (dctSqrt1d2 * (p[1] - p[7]) + 128) >> 8;
+    v7 = (dctSqrt1d2 * (p[1] + p[7]) + 128) >> 8;
+    v5 = p[3] << 4;
+    v6 = p[5] << 4;
+
+    // stage 3
+    t = (v0 - v1+ 1) >> 1;
+    v0 = (v0 + v1 + 1) >> 1;
+    v1 = t;
+    t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8;
+    v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8;
+    v3 = t;
+    t = (v4 - v6 + 1) >> 1;
+    v4 = (v4 + v6 + 1) >> 1;
+    v6 = t;
+    t = (v7 + v5 + 1) >> 1;
+    v5 = (v7 - v5 + 1) >> 1;
+    v7 = t;
+
+    // stage 2
+    t = (v0 - v3 + 1) >> 1;
+    v0 = (v0 + v3 + 1) >> 1;
+    v3 = t;
+    t = (v1 - v2 + 1) >> 1;
+    v1 = (v1 + v2 + 1) >> 1;
+    v2 = t;
+    t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+    v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+    v7 = t;
+    t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+    v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+    v6 = t;
+
+    // stage 1
+    p[0] = v0 + v7;
+    p[7] = v0 - v7;
+    p[1] = v1 + v6;
+    p[6] = v1 - v6;
+    p[2] = v2 + v5;
+    p[5] = v2 - v5;
+    p[3] = v3 + v4;
+    p[4] = v3 - v4;
+  }
+
+  // inverse DCT on columns
+  for (i = 0; i < 8; ++i) {
+    p = dataIn + i;
+
+    // check for all-zero AC coefficients
+    if (p[1*8] == 0 && p[2*8] == 0 && p[3*8] == 0 &&
+	p[4*8] == 0 && p[5*8] == 0 && p[6*8] == 0 && p[7*8] == 0) {
+      t = (dctSqrt2 * dataIn[i+0] + 8192) >> 14;
+      p[0*8] = t;
+      p[1*8] = t;
+      p[2*8] = t;
+      p[3*8] = t;
+      p[4*8] = t;
+      p[5*8] = t;
+      p[6*8] = t;
+      p[7*8] = t;
+      continue;
+    }
+
+    // stage 4
+    v0 = (dctSqrt2 * p[0*8] + 2048) >> 12;
+    v1 = (dctSqrt2 * p[4*8] + 2048) >> 12;
+    v2 = p[2*8];
+    v3 = p[6*8];
+    v4 = (dctSqrt1d2 * (p[1*8] - p[7*8]) + 2048) >> 12;
+    v7 = (dctSqrt1d2 * (p[1*8] + p[7*8]) + 2048) >> 12;
+    v5 = p[3*8];
+    v6 = p[5*8];
+
+    // stage 3
+    t = (v0 - v1 + 1) >> 1;
+    v0 = (v0 + v1 + 1) >> 1;
+    v1 = t;
+    t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12;
+    v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12;
+    v3 = t;
+    t = (v4 - v6 + 1) >> 1;
+    v4 = (v4 + v6 + 1) >> 1;
+    v6 = t;
+    t = (v7 + v5 + 1) >> 1;
+    v5 = (v7 - v5 + 1) >> 1;
+    v7 = t;
+
+    // stage 2
+    t = (v0 - v3 + 1) >> 1;
+    v0 = (v0 + v3 + 1) >> 1;
+    v3 = t;
+    t = (v1 - v2 + 1) >> 1;
+    v1 = (v1 + v2 + 1) >> 1;
+    v2 = t;
+    t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+    v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+    v7 = t;
+    t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+    v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+    v6 = t;
+
+    // stage 1
+    p[0*8] = v0 + v7;
+    p[7*8] = v0 - v7;
+    p[1*8] = v1 + v6;
+    p[6*8] = v1 - v6;
+    p[2*8] = v2 + v5;
+    p[5*8] = v2 - v5;
+    p[3*8] = v3 + v4;
+    p[4*8] = v3 - v4;
+  }
+
+  // convert to 8-bit integers
+  for (i = 0; i < 64; ++i) {
+    dataOut[i] = dctClip[dctClipOffset + 128 + ((dataIn[i] + 8) >> 4)];
+  }
+}
+
+int DCTStream::readHuffSym(DCTHuffTable *table) {
+  Gushort code;
+  int bit;
+  int codeBits;
+
+  code = 0;
+  codeBits = 0;
+  do {
+    // add a bit to the code
+    if ((bit = readBit()) == EOF)
+      return 9999;
+    code = (code << 1) + bit;
+    ++codeBits;
+
+    // look up code
+    if (code - table->firstCode[codeBits] < table->numCodes[codeBits]) {
+      code -= table->firstCode[codeBits];
+      return table->sym[table->firstSym[codeBits] + code];
+    }
+  } while (codeBits < 16);
+
+  error(getPos(), "Bad Huffman code in DCT stream");
+  return 9999;
+}
+
+int DCTStream::readAmp(int size) {
+  int amp, bit;
+  int bits;
+
+  amp = 0;
+  for (bits = 0; bits < size; ++bits) {
+    if ((bit = readBit()) == EOF)
+      return 9999;
+    amp = (amp << 1) + bit;
+  }
+  if (amp < (1 << (size - 1)))
+    amp -= (1 << size) - 1;
+  return amp;
+}
+
+int DCTStream::readBit() {
+  int bit;
+  int c, c2;
+
+  if (inputBits == 0) {
+    if ((c = str->getChar()) == EOF)
+      return EOF;
+    if (c == 0xff) {
+      do {
+	c2 = str->getChar();
+      } while (c2 == 0xff);
+      if (c2 != 0x00) {
+	error(getPos(), "Bad DCT data: missing 00 after ff");
+	return EOF;
+      }
+    }
+    inputBuf = c;
+    inputBits = 8;
+  }
+  bit = (inputBuf >> (inputBits - 1)) & 1;
+  --inputBits;
+  return bit;
+}
+
+GBool DCTStream::readHeader() {
+  GBool doScan;
+  int n;
+  int c = 0;
+  int i;
+
+  // read headers
+  doScan = gFalse;
+  while (!doScan) {
+    c = readMarker();
+    switch (c) {
+    case 0xc0:			// SOF0 (sequential)
+    case 0xc1:			// SOF1 (extended sequential)
+      if (!readBaselineSOF()) {
+	return gFalse;
+      }
+      break;
+    case 0xc2:			// SOF2 (progressive)
+      if (!readProgressiveSOF()) {
+	return gFalse;
+      }
+      break;
+    case 0xc4:			// DHT
+      if (!readHuffmanTables()) {
+	return gFalse;
+      }
+      break;
+    case 0xd8:			// SOI
+      break;
+    case 0xd9:			// EOI
+      return gFalse;
+    case 0xda:			// SOS
+      if (!readScanInfo()) {
+	return gFalse;
+      }
+      doScan = gTrue;
+      break;
+    case 0xdb:			// DQT
+      if (!readQuantTables()) {
+	return gFalse;
+      }
+      break;
+    case 0xdd:			// DRI
+      if (!readRestartInterval()) {
+	return gFalse;
+      }
+      break;
+    case 0xe0:			// APP0
+      if (!readJFIFMarker()) {
+	return gFalse;
+      }
+      break;
+    case 0xee:			// APP14
+      if (!readAdobeMarker()) {
+	return gFalse;
+      }
+      break;
+    case EOF:
+      error(getPos(), "Bad DCT header");
+      return gFalse;
+    default:
+      // skip APPn / COM / etc.
+      if (c >= 0xe0) {
+	n = read16() - 2;
+	for (i = 0; i < n; ++i) {
+	  str->getChar();
+	}
+      } else {
+	error(getPos(), "Unknown DCT marker <%02x>", c);
+	return gFalse;
+      }
+      break;
+    }
+  }
+
+  return gTrue;
+}
+
+GBool DCTStream::readBaselineSOF() {
+  int length;
+  int prec;
+  int i;
+  int c;
+
+  length = read16();
+  prec = str->getChar();
+  height = read16();
+  width = read16();
+  numComps = str->getChar();
+  if (numComps <= 0 || numComps > 4) {
+    error(getPos(), "Bad number of components in DCT stream");
+    numComps = 0;
+    return gFalse;
+  }
+  if (prec != 8) {
+    error(getPos(), "Bad DCT precision %d", prec);
+    return gFalse;
+  }
+  for (i = 0; i < numComps; ++i) {
+    compInfo[i].id = str->getChar();
+    c = str->getChar();
+    compInfo[i].hSample = (c >> 4) & 0x0f;
+    compInfo[i].vSample = c & 0x0f;
+    compInfo[i].quantTable = str->getChar();
+  }
+  progressive = gFalse;
+  return gTrue;
+}
+
+GBool DCTStream::readProgressiveSOF() {
+  int length;
+  int prec;
+  int i;
+  int c;
+
+  length = read16();
+  prec = str->getChar();
+  height = read16();
+  width = read16();
+  numComps = str->getChar();
+  if (numComps <= 0 || numComps > 4) {
+    error(getPos(), "Bad number of components in DCT stream");
+    numComps = 0;
+    return gFalse;
+  }
+  if (prec != 8) {
+    error(getPos(), "Bad DCT precision %d", prec);
+    return gFalse;
+  }
+  for (i = 0; i < numComps; ++i) {
+    compInfo[i].id = str->getChar();
+    c = str->getChar();
+    compInfo[i].hSample = (c >> 4) & 0x0f;
+    compInfo[i].vSample = c & 0x0f;
+    compInfo[i].quantTable = str->getChar();
+  }
+  progressive = gTrue;
+  return gTrue;
+}
+
+GBool DCTStream::readScanInfo() {
+  int length;
+  int id, c;
+  int i, j;
+
+  length = read16() - 2;
+  scanInfo.numComps = str->getChar();
+  if (scanInfo.numComps <= 0 || scanInfo.numComps > 4) {
+    error(getPos(), "Bad number of components in DCT stream");
+    scanInfo.numComps = 0;
+    return gFalse;
+  }
+  --length;
+  if (length != 2 * scanInfo.numComps + 3) {
+    error(getPos(), "Bad DCT scan info block");
+    return gFalse;
+  }
+  interleaved = scanInfo.numComps == numComps;
+  for (j = 0; j < numComps; ++j) {
+    scanInfo.comp[j] = gFalse;
+  }
+  for (i = 0; i < scanInfo.numComps; ++i) {
+    id = str->getChar();
+    // some (broken) DCT streams reuse ID numbers, but at least they
+    // keep the components in order, so we check compInfo[i] first to
+    // work around the problem
+    if (id == compInfo[i].id) {
+      j = i;
+    } else {
+      for (j = 0; j < numComps; ++j) {
+	if (id == compInfo[j].id) {
+	  break;
+	}
+      }
+      if (j == numComps) {
+	error(getPos(), "Bad DCT component ID in scan info block");
+	return gFalse;
+      }
+    }
+    scanInfo.comp[j] = gTrue;
+    c = str->getChar();
+    scanInfo.dcHuffTable[j] = (c >> 4) & 0x0f;
+    scanInfo.acHuffTable[j] = c & 0x0f;
+  }
+  scanInfo.firstCoeff = str->getChar();
+  scanInfo.lastCoeff = str->getChar();
+  if (scanInfo.firstCoeff < 0 || scanInfo.lastCoeff > 63 ||
+      scanInfo.firstCoeff > scanInfo.lastCoeff) {
+    error(getPos(), "Bad DCT coefficient numbers in scan info block");
+    return gFalse;
+  }
+  c = str->getChar();
+  scanInfo.ah = (c >> 4) & 0x0f;
+  scanInfo.al = c & 0x0f;
+  return gTrue;
+}
+
+GBool DCTStream::readQuantTables() {
+  int length, prec, i, index;
+
+  length = read16() - 2;
+  while (length > 0) {
+    index = str->getChar();
+    prec = (index >> 4) & 0x0f;
+    index &= 0x0f;
+    if (prec > 1 || index >= 4) {
+      error(getPos(), "Bad DCT quantization table");
+      return gFalse;
+    }
+    if (index == numQuantTables) {
+      numQuantTables = index + 1;
+    }
+    for (i = 0; i < 64; ++i) {
+      if (prec) {
+	quantTables[index][dctZigZag[i]] = read16();
+      } else {
+	quantTables[index][dctZigZag[i]] = str->getChar();
+      }
+    }
+    if (prec) {
+      length -= 129;
+    } else {
+      length -= 65;
+    }
+  }
+  return gTrue;
+}
+
+GBool DCTStream::readHuffmanTables() {
+  DCTHuffTable *tbl;
+  int length;
+  int index;
+  Gushort code;
+  Guchar sym;
+  int i;
+  int c;
+
+  length = read16() - 2;
+  while (length > 0) {
+    index = str->getChar();
+    --length;
+    if ((index & 0x0f) >= 4) {
+      error(getPos(), "Bad DCT Huffman table");
+      return gFalse;
+    }
+    if (index & 0x10) {
+      index &= 0x0f;
+      if (index >= numACHuffTables)
+	numACHuffTables = index+1;
+      tbl = &acHuffTables[index];
+    } else {
+      index &= 0x0f;
+      if (index >= numDCHuffTables)
+	numDCHuffTables = index+1;
+      tbl = &dcHuffTables[index];
+    }
+    sym = 0;
+    code = 0;
+    for (i = 1; i <= 16; ++i) {
+      c = str->getChar();
+      tbl->firstSym[i] = sym;
+      tbl->firstCode[i] = code;
+      tbl->numCodes[i] = c;
+      sym += c;
+      code = (code + c) << 1;
+    }
+    length -= 16;
+    for (i = 0; i < sym; ++i)
+      tbl->sym[i] = str->getChar();
+    length -= sym;
+  }
+  return gTrue;
+}
+
+GBool DCTStream::readRestartInterval() {
+  int length;
+
+  length = read16();
+  if (length != 4) {
+    error(getPos(), "Bad DCT restart interval");
+    return gFalse;
+  }
+  restartInterval = read16();
+  return gTrue;
+}
+
+GBool DCTStream::readJFIFMarker() {
+  int length, i;
+  char buf[5];
+  int c;
+
+  length = read16();
+  length -= 2;
+  if (length >= 5) {
+    for (i = 0; i < 5; ++i) {
+      if ((c = str->getChar()) == EOF) {
+	error(getPos(), "Bad DCT APP0 marker");
+	return gFalse;
+      }
+      buf[i] = c;
+    }
+    length -= 5;
+    if (!memcmp(buf, "JFIF\0", 5)) {
+      gotJFIFMarker = gTrue;
+    }
+  }
+  while (length > 0) {
+    if (str->getChar() == EOF) {
+      error(getPos(), "Bad DCT APP0 marker");
+      return gFalse;
+    }
+    --length;
+  }
+  return gTrue;
+}
+
+GBool DCTStream::readAdobeMarker() {
+  int length, i;
+  char buf[12];
+  int c;
+
+  length = read16();
+  if (length < 14) {
+    goto err;
+  }
+  for (i = 0; i < 12; ++i) {
+    if ((c = str->getChar()) == EOF) {
+      goto err;
+    }
+    buf[i] = c;
+  }
+  if (strncmp(buf, "Adobe", 5)) {
+    goto err;
+  }
+  colorXform = buf[11];
+  gotAdobeMarker = gTrue;
+  for (i = 14; i < length; ++i) {
+    if (str->getChar() == EOF) {
+      goto err;
+    }
+  }
+  return gTrue;
+
+ err:
+  error(getPos(), "Bad DCT Adobe APP14 marker");
+  return gFalse;
+}
+
+GBool DCTStream::readTrailer() {
+  int c;
+
+  c = readMarker();
+  if (c != 0xd9) {		// EOI
+    error(getPos(), "Bad DCT trailer");
+    return gFalse;
+  }
+  return gTrue;
+}
+
+int DCTStream::readMarker() {
+  int c;
+
+  do {
+    do {
+      c = str->getChar();
+    } while (c != 0xff && c != EOF);
+    do {
+      c = str->getChar();
+    } while (c == 0xff);
+  } while (c == 0x00);
+  return c;
+}
+
+int DCTStream::read16() {
+  int c1, c2;
+
+  if ((c1 = str->getChar()) == EOF)
+    return EOF;
+  if ((c2 = str->getChar()) == EOF)
+    return EOF;
+  return (c1 << 8) + c2;
+}
+
+GString *DCTStream::getPSFilter(int psLevel, char *indent) {
+  GString *s;
+
+  if (psLevel < 2) {
+    return NULL;
+  }
+  if (!(s = str->getPSFilter(psLevel, indent))) {
+    return NULL;
+  }
+  s->append(indent)->append("<< >> /DCTDecode filter\n");
+  return s;
+}
+
+GBool DCTStream::isBinary(GBool last) {
+  return str->isBinary(gTrue);
+}
+
+//------------------------------------------------------------------------
+// FlateStream
+//------------------------------------------------------------------------
+
+int FlateStream::codeLenCodeMap[flateMaxCodeLenCodes] = {
+  16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
+};
+
+FlateDecode FlateStream::lengthDecode[flateMaxLitCodes-257] = {
+  {0,   3},
+  {0,   4},
+  {0,   5},
+  {0,   6},
+  {0,   7},
+  {0,   8},
+  {0,   9},
+  {0,  10},
+  {1,  11},
+  {1,  13},
+  {1,  15},
+  {1,  17},
+  {2,  19},
+  {2,  23},
+  {2,  27},
+  {2,  31},
+  {3,  35},
+  {3,  43},
+  {3,  51},
+  {3,  59},
+  {4,  67},
+  {4,  83},
+  {4,  99},
+  {4, 115},
+  {5, 131},
+  {5, 163},
+  {5, 195},
+  {5, 227},
+  {0, 258},
+  {0, 258},
+  {0, 258}
+};
+
+FlateDecode FlateStream::distDecode[flateMaxDistCodes] = {
+  { 0,     1},
+  { 0,     2},
+  { 0,     3},
+  { 0,     4},
+  { 1,     5},
+  { 1,     7},
+  { 2,     9},
+  { 2,    13},
+  { 3,    17},
+  { 3,    25},
+  { 4,    33},
+  { 4,    49},
+  { 5,    65},
+  { 5,    97},
+  { 6,   129},
+  { 6,   193},
+  { 7,   257},
+  { 7,   385},
+  { 8,   513},
+  { 8,   769},
+  { 9,  1025},
+  { 9,  1537},
+  {10,  2049},
+  {10,  3073},
+  {11,  4097},
+  {11,  6145},
+  {12,  8193},
+  {12, 12289},
+  {13, 16385},
+  {13, 24577}
+};
+
+static FlateCode flateFixedLitCodeTabCodes[512] = {
+  {7, 0x0100},
+  {8, 0x0050},
+  {8, 0x0010},
+  {8, 0x0118},
+  {7, 0x0110},
+  {8, 0x0070},
+  {8, 0x0030},
+  {9, 0x00c0},
+  {7, 0x0108},
+  {8, 0x0060},
+  {8, 0x0020},
+  {9, 0x00a0},
+  {8, 0x0000},
+  {8, 0x0080},
+  {8, 0x0040},
+  {9, 0x00e0},
+  {7, 0x0104},
+  {8, 0x0058},
+  {8, 0x0018},
+  {9, 0x0090},
+  {7, 0x0114},
+  {8, 0x0078},
+  {8, 0x0038},
+  {9, 0x00d0},
+  {7, 0x010c},
+  {8, 0x0068},
+  {8, 0x0028},
+  {9, 0x00b0},
+  {8, 0x0008},
+  {8, 0x0088},
+  {8, 0x0048},
+  {9, 0x00f0},
+  {7, 0x0102},
+  {8, 0x0054},
+  {8, 0x0014},
+  {8, 0x011c},
+  {7, 0x0112},
+  {8, 0x0074},
+  {8, 0x0034},
+  {9, 0x00c8},
+  {7, 0x010a},
+  {8, 0x0064},
+  {8, 0x0024},
+  {9, 0x00a8},
+  {8, 0x0004},
+  {8, 0x0084},
+  {8, 0x0044},
+  {9, 0x00e8},
+  {7, 0x0106},
+  {8, 0x005c},
+  {8, 0x001c},
+  {9, 0x0098},
+  {7, 0x0116},
+  {8, 0x007c},
+  {8, 0x003c},
+  {9, 0x00d8},
+  {7, 0x010e},
+  {8, 0x006c},
+  {8, 0x002c},
+  {9, 0x00b8},
+  {8, 0x000c},
+  {8, 0x008c},
+  {8, 0x004c},
+  {9, 0x00f8},
+  {7, 0x0101},
+  {8, 0x0052},
+  {8, 0x0012},
+  {8, 0x011a},
+  {7, 0x0111},
+  {8, 0x0072},
+  {8, 0x0032},
+  {9, 0x00c4},
+  {7, 0x0109},
+  {8, 0x0062},
+  {8, 0x0022},
+  {9, 0x00a4},
+  {8, 0x0002},
+  {8, 0x0082},
+  {8, 0x0042},
+  {9, 0x00e4},
+  {7, 0x0105},
+  {8, 0x005a},
+  {8, 0x001a},
+  {9, 0x0094},
+  {7, 0x0115},
+  {8, 0x007a},
+  {8, 0x003a},
+  {9, 0x00d4},
+  {7, 0x010d},
+  {8, 0x006a},
+  {8, 0x002a},
+  {9, 0x00b4},
+  {8, 0x000a},
+  {8, 0x008a},
+  {8, 0x004a},
+  {9, 0x00f4},
+  {7, 0x0103},
+  {8, 0x0056},
+  {8, 0x0016},
+  {8, 0x011e},
+  {7, 0x0113},
+  {8, 0x0076},
+  {8, 0x0036},
+  {9, 0x00cc},
+  {7, 0x010b},
+  {8, 0x0066},
+  {8, 0x0026},
+  {9, 0x00ac},
+  {8, 0x0006},
+  {8, 0x0086},
+  {8, 0x0046},
+  {9, 0x00ec},
+  {7, 0x0107},
+  {8, 0x005e},
+  {8, 0x001e},
+  {9, 0x009c},
+  {7, 0x0117},
+  {8, 0x007e},
+  {8, 0x003e},
+  {9, 0x00dc},
+  {7, 0x010f},
+  {8, 0x006e},
+  {8, 0x002e},
+  {9, 0x00bc},
+  {8, 0x000e},
+  {8, 0x008e},
+  {8, 0x004e},
+  {9, 0x00fc},
+  {7, 0x0100},
+  {8, 0x0051},
+  {8, 0x0011},
+  {8, 0x0119},
+  {7, 0x0110},
+  {8, 0x0071},
+  {8, 0x0031},
+  {9, 0x00c2},
+  {7, 0x0108},
+  {8, 0x0061},
+  {8, 0x0021},
+  {9, 0x00a2},
+  {8, 0x0001},
+  {8, 0x0081},
+  {8, 0x0041},
+  {9, 0x00e2},
+  {7, 0x0104},
+  {8, 0x0059},
+  {8, 0x0019},
+  {9, 0x0092},
+  {7, 0x0114},
+  {8, 0x0079},
+  {8, 0x0039},
+  {9, 0x00d2},
+  {7, 0x010c},
+  {8, 0x0069},
+  {8, 0x0029},
+  {9, 0x00b2},
+  {8, 0x0009},
+  {8, 0x0089},
+  {8, 0x0049},
+  {9, 0x00f2},
+  {7, 0x0102},
+  {8, 0x0055},
+  {8, 0x0015},
+  {8, 0x011d},
+  {7, 0x0112},
+  {8, 0x0075},
+  {8, 0x0035},
+  {9, 0x00ca},
+  {7, 0x010a},
+  {8, 0x0065},
+  {8, 0x0025},
+  {9, 0x00aa},
+  {8, 0x0005},
+  {8, 0x0085},
+  {8, 0x0045},
+  {9, 0x00ea},
+  {7, 0x0106},
+  {8, 0x005d},
+  {8, 0x001d},
+  {9, 0x009a},
+  {7, 0x0116},
+  {8, 0x007d},
+  {8, 0x003d},
+  {9, 0x00da},
+  {7, 0x010e},
+  {8, 0x006d},
+  {8, 0x002d},
+  {9, 0x00ba},
+  {8, 0x000d},
+  {8, 0x008d},
+  {8, 0x004d},
+  {9, 0x00fa},
+  {7, 0x0101},
+  {8, 0x0053},
+  {8, 0x0013},
+  {8, 0x011b},
+  {7, 0x0111},
+  {8, 0x0073},
+  {8, 0x0033},
+  {9, 0x00c6},
+  {7, 0x0109},
+  {8, 0x0063},
+  {8, 0x0023},
+  {9, 0x00a6},
+  {8, 0x0003},
+  {8, 0x0083},
+  {8, 0x0043},
+  {9, 0x00e6},
+  {7, 0x0105},
+  {8, 0x005b},
+  {8, 0x001b},
+  {9, 0x0096},
+  {7, 0x0115},
+  {8, 0x007b},
+  {8, 0x003b},
+  {9, 0x00d6},
+  {7, 0x010d},
+  {8, 0x006b},
+  {8, 0x002b},
+  {9, 0x00b6},
+  {8, 0x000b},
+  {8, 0x008b},
+  {8, 0x004b},
+  {9, 0x00f6},
+  {7, 0x0103},
+  {8, 0x0057},
+  {8, 0x0017},
+  {8, 0x011f},
+  {7, 0x0113},
+  {8, 0x0077},
+  {8, 0x0037},
+  {9, 0x00ce},
+  {7, 0x010b},
+  {8, 0x0067},
+  {8, 0x0027},
+  {9, 0x00ae},
+  {8, 0x0007},
+  {8, 0x0087},
+  {8, 0x0047},
+  {9, 0x00ee},
+  {7, 0x0107},
+  {8, 0x005f},
+  {8, 0x001f},
+  {9, 0x009e},
+  {7, 0x0117},
+  {8, 0x007f},
+  {8, 0x003f},
+  {9, 0x00de},
+  {7, 0x010f},
+  {8, 0x006f},
+  {8, 0x002f},
+  {9, 0x00be},
+  {8, 0x000f},
+  {8, 0x008f},
+  {8, 0x004f},
+  {9, 0x00fe},
+  {7, 0x0100},
+  {8, 0x0050},
+  {8, 0x0010},
+  {8, 0x0118},
+  {7, 0x0110},
+  {8, 0x0070},
+  {8, 0x0030},
+  {9, 0x00c1},
+  {7, 0x0108},
+  {8, 0x0060},
+  {8, 0x0020},
+  {9, 0x00a1},
+  {8, 0x0000},
+  {8, 0x0080},
+  {8, 0x0040},
+  {9, 0x00e1},
+  {7, 0x0104},
+  {8, 0x0058},
+  {8, 0x0018},
+  {9, 0x0091},
+  {7, 0x0114},
+  {8, 0x0078},
+  {8, 0x0038},
+  {9, 0x00d1},
+  {7, 0x010c},
+  {8, 0x0068},
+  {8, 0x0028},
+  {9, 0x00b1},
+  {8, 0x0008},
+  {8, 0x0088},
+  {8, 0x0048},
+  {9, 0x00f1},
+  {7, 0x0102},
+  {8, 0x0054},
+  {8, 0x0014},
+  {8, 0x011c},
+  {7, 0x0112},
+  {8, 0x0074},
+  {8, 0x0034},
+  {9, 0x00c9},
+  {7, 0x010a},
+  {8, 0x0064},
+  {8, 0x0024},
+  {9, 0x00a9},
+  {8, 0x0004},
+  {8, 0x0084},
+  {8, 0x0044},
+  {9, 0x00e9},
+  {7, 0x0106},
+  {8, 0x005c},
+  {8, 0x001c},
+  {9, 0x0099},
+  {7, 0x0116},
+  {8, 0x007c},
+  {8, 0x003c},
+  {9, 0x00d9},
+  {7, 0x010e},
+  {8, 0x006c},
+  {8, 0x002c},
+  {9, 0x00b9},
+  {8, 0x000c},
+  {8, 0x008c},
+  {8, 0x004c},
+  {9, 0x00f9},
+  {7, 0x0101},
+  {8, 0x0052},
+  {8, 0x0012},
+  {8, 0x011a},
+  {7, 0x0111},
+  {8, 0x0072},
+  {8, 0x0032},
+  {9, 0x00c5},
+  {7, 0x0109},
+  {8, 0x0062},
+  {8, 0x0022},
+  {9, 0x00a5},
+  {8, 0x0002},
+  {8, 0x0082},
+  {8, 0x0042},
+  {9, 0x00e5},
+  {7, 0x0105},
+  {8, 0x005a},
+  {8, 0x001a},
+  {9, 0x0095},
+  {7, 0x0115},
+  {8, 0x007a},
+  {8, 0x003a},
+  {9, 0x00d5},
+  {7, 0x010d},
+  {8, 0x006a},
+  {8, 0x002a},
+  {9, 0x00b5},
+  {8, 0x000a},
+  {8, 0x008a},
+  {8, 0x004a},
+  {9, 0x00f5},
+  {7, 0x0103},
+  {8, 0x0056},
+  {8, 0x0016},
+  {8, 0x011e},
+  {7, 0x0113},
+  {8, 0x0076},
+  {8, 0x0036},
+  {9, 0x00cd},
+  {7, 0x010b},
+  {8, 0x0066},
+  {8, 0x0026},
+  {9, 0x00ad},
+  {8, 0x0006},
+  {8, 0x0086},
+  {8, 0x0046},
+  {9, 0x00ed},
+  {7, 0x0107},
+  {8, 0x005e},
+  {8, 0x001e},
+  {9, 0x009d},
+  {7, 0x0117},
+  {8, 0x007e},
+  {8, 0x003e},
+  {9, 0x00dd},
+  {7, 0x010f},
+  {8, 0x006e},
+  {8, 0x002e},
+  {9, 0x00bd},
+  {8, 0x000e},
+  {8, 0x008e},
+  {8, 0x004e},
+  {9, 0x00fd},
+  {7, 0x0100},
+  {8, 0x0051},
+  {8, 0x0011},
+  {8, 0x0119},
+  {7, 0x0110},
+  {8, 0x0071},
+  {8, 0x0031},
+  {9, 0x00c3},
+  {7, 0x0108},
+  {8, 0x0061},
+  {8, 0x0021},
+  {9, 0x00a3},
+  {8, 0x0001},
+  {8, 0x0081},
+  {8, 0x0041},
+  {9, 0x00e3},
+  {7, 0x0104},
+  {8, 0x0059},
+  {8, 0x0019},
+  {9, 0x0093},
+  {7, 0x0114},
+  {8, 0x0079},
+  {8, 0x0039},
+  {9, 0x00d3},
+  {7, 0x010c},
+  {8, 0x0069},
+  {8, 0x0029},
+  {9, 0x00b3},
+  {8, 0x0009},
+  {8, 0x0089},
+  {8, 0x0049},
+  {9, 0x00f3},
+  {7, 0x0102},
+  {8, 0x0055},
+  {8, 0x0015},
+  {8, 0x011d},
+  {7, 0x0112},
+  {8, 0x0075},
+  {8, 0x0035},
+  {9, 0x00cb},
+  {7, 0x010a},
+  {8, 0x0065},
+  {8, 0x0025},
+  {9, 0x00ab},
+  {8, 0x0005},
+  {8, 0x0085},
+  {8, 0x0045},
+  {9, 0x00eb},
+  {7, 0x0106},
+  {8, 0x005d},
+  {8, 0x001d},
+  {9, 0x009b},
+  {7, 0x0116},
+  {8, 0x007d},
+  {8, 0x003d},
+  {9, 0x00db},
+  {7, 0x010e},
+  {8, 0x006d},
+  {8, 0x002d},
+  {9, 0x00bb},
+  {8, 0x000d},
+  {8, 0x008d},
+  {8, 0x004d},
+  {9, 0x00fb},
+  {7, 0x0101},
+  {8, 0x0053},
+  {8, 0x0013},
+  {8, 0x011b},
+  {7, 0x0111},
+  {8, 0x0073},
+  {8, 0x0033},
+  {9, 0x00c7},
+  {7, 0x0109},
+  {8, 0x0063},
+  {8, 0x0023},
+  {9, 0x00a7},
+  {8, 0x0003},
+  {8, 0x0083},
+  {8, 0x0043},
+  {9, 0x00e7},
+  {7, 0x0105},
+  {8, 0x005b},
+  {8, 0x001b},
+  {9, 0x0097},
+  {7, 0x0115},
+  {8, 0x007b},
+  {8, 0x003b},
+  {9, 0x00d7},
+  {7, 0x010d},
+  {8, 0x006b},
+  {8, 0x002b},
+  {9, 0x00b7},
+  {8, 0x000b},
+  {8, 0x008b},
+  {8, 0x004b},
+  {9, 0x00f7},
+  {7, 0x0103},
+  {8, 0x0057},
+  {8, 0x0017},
+  {8, 0x011f},
+  {7, 0x0113},
+  {8, 0x0077},
+  {8, 0x0037},
+  {9, 0x00cf},
+  {7, 0x010b},
+  {8, 0x0067},
+  {8, 0x0027},
+  {9, 0x00af},
+  {8, 0x0007},
+  {8, 0x0087},
+  {8, 0x0047},
+  {9, 0x00ef},
+  {7, 0x0107},
+  {8, 0x005f},
+  {8, 0x001f},
+  {9, 0x009f},
+  {7, 0x0117},
+  {8, 0x007f},
+  {8, 0x003f},
+  {9, 0x00df},
+  {7, 0x010f},
+  {8, 0x006f},
+  {8, 0x002f},
+  {9, 0x00bf},
+  {8, 0x000f},
+  {8, 0x008f},
+  {8, 0x004f},
+  {9, 0x00ff}
+};
+
+FlateHuffmanTab FlateStream::fixedLitCodeTab = {
+  flateFixedLitCodeTabCodes, 9
+};
+
+static FlateCode flateFixedDistCodeTabCodes[32] = {
+  {5, 0x0000},
+  {5, 0x0010},
+  {5, 0x0008},
+  {5, 0x0018},
+  {5, 0x0004},
+  {5, 0x0014},
+  {5, 0x000c},
+  {5, 0x001c},
+  {5, 0x0002},
+  {5, 0x0012},
+  {5, 0x000a},
+  {5, 0x001a},
+  {5, 0x0006},
+  {5, 0x0016},
+  {5, 0x000e},
+  {0, 0x0000},
+  {5, 0x0001},
+  {5, 0x0011},
+  {5, 0x0009},
+  {5, 0x0019},
+  {5, 0x0005},
+  {5, 0x0015},
+  {5, 0x000d},
+  {5, 0x001d},
+  {5, 0x0003},
+  {5, 0x0013},
+  {5, 0x000b},
+  {5, 0x001b},
+  {5, 0x0007},
+  {5, 0x0017},
+  {5, 0x000f},
+  {0, 0x0000}
+};
+
+FlateHuffmanTab FlateStream::fixedDistCodeTab = {
+  flateFixedDistCodeTabCodes, 5
+};
+
+FlateStream::FlateStream(Stream *strA, int predictor, int columns,
+			 int colors, int bits):
+    FilterStream(strA) {
+  if (predictor != 1) {
+    pred = new StreamPredictor(this, predictor, columns, colors, bits);
+    if (!pred->isOk()) {
+      delete pred;
+      pred = NULL;
+    }
+  } else {
+    pred = NULL;
+  }
+  litCodeTab.codes = NULL;
+  distCodeTab.codes = NULL;
+  memset(buf, 0, flateWindow);
+}
+
+FlateStream::~FlateStream() {
+  if (litCodeTab.codes != fixedLitCodeTab.codes) {
+    gfree(litCodeTab.codes);
+  }
+  if (distCodeTab.codes != fixedDistCodeTab.codes) {
+    gfree(distCodeTab.codes);
+  }
+  if (pred) {
+    delete pred;
+  }
+  delete str;
+}
+
+void FlateStream::reset() {
+  int cmf, flg;
+
+  index = 0;
+  remain = 0;
+  codeBuf = 0;
+  codeSize = 0;
+  compressedBlock = gFalse;
+  endOfBlock = gTrue;
+  eof = gTrue;
+
+  str->reset();
+
+  // read header
+  //~ need to look at window size?
+  endOfBlock = eof = gTrue;
+  cmf = str->getChar();
+  flg = str->getChar();
+  if (cmf == EOF || flg == EOF)
+    return;
+  if ((cmf & 0x0f) != 0x08) {
+    error(getPos(), "Unknown compression method in flate stream");
+    return;
+  }
+  if ((((cmf << 8) + flg) % 31) != 0) {
+    error(getPos(), "Bad FCHECK in flate stream");
+    return;
+  }
+  if (flg & 0x20) {
+    error(getPos(), "FDICT bit set in flate stream");
+    return;
+  }
+
+  eof = gFalse;
+}
+
+int FlateStream::getChar() {
+  int c;
+
+  if (pred) {
+    return pred->getChar();
+  }
+  while (remain == 0) {
+    if (endOfBlock && eof)
+      return EOF;
+    readSome();
+  }
+  c = buf[index];
+  index = (index + 1) & flateMask;
+  --remain;
+  return c;
+}
+
+int FlateStream::lookChar() {
+  int c;
+
+  if (pred) {
+    return pred->lookChar();
+  }
+  while (remain == 0) {
+    if (endOfBlock && eof)
+      return EOF;
+    readSome();
+  }
+  c = buf[index];
+  return c;
+}
+
+int FlateStream::getRawChar() {
+  int c;
+
+  while (remain == 0) {
+    if (endOfBlock && eof)
+      return EOF;
+    readSome();
+  }
+  c = buf[index];
+  index = (index + 1) & flateMask;
+  --remain;
+  return c;
+}
+
+GString *FlateStream::getPSFilter(int psLevel, char *indent) {
+  GString *s;
+
+  if (psLevel < 3 || pred) {
+    return NULL;
+  }
+  if (!(s = str->getPSFilter(psLevel, indent))) {
+    return NULL;
+  }
+  s->append(indent)->append("<< >> /FlateDecode filter\n");
+  return s;
+}
+
+GBool FlateStream::isBinary(GBool last) {
+  return str->isBinary(gTrue);
+}
+
+void FlateStream::readSome() {
+  int code1, code2;
+  int len, dist;
+  int i, j, k;
+  int c;
+
+  if (endOfBlock) {
+    if (!startBlock())
+      return;
+  }
+
+  if (compressedBlock) {
+    if ((code1 = getHuffmanCodeWord(&litCodeTab)) == EOF)
+      goto err;
+    if (code1 < 256) {
+      buf[index] = code1;
+      remain = 1;
+    } else if (code1 == 256) {
+      endOfBlock = gTrue;
+      remain = 0;
+    } else {
+      code1 -= 257;
+      code2 = lengthDecode[code1].bits;
+      if (code2 > 0 && (code2 = getCodeWord(code2)) == EOF)
+	goto err;
+      len = lengthDecode[code1].first + code2;
+      if ((code1 = getHuffmanCodeWord(&distCodeTab)) == EOF)
+	goto err;
+      code2 = distDecode[code1].bits;
+      if (code2 > 0 && (code2 = getCodeWord(code2)) == EOF)
+	goto err;
+      dist = distDecode[code1].first + code2;
+      i = index;
+      j = (index - dist) & flateMask;
+      for (k = 0; k < len; ++k) {
+	buf[i] = buf[j];
+	i = (i + 1) & flateMask;
+	j = (j + 1) & flateMask;
+      }
+      remain = len;
+    }
+
+  } else {
+    len = (blockLen < flateWindow) ? blockLen : flateWindow;
+    for (i = 0, j = index; i < len; ++i, j = (j + 1) & flateMask) {
+      if ((c = str->getChar()) == EOF) {
+	endOfBlock = eof = gTrue;
+	break;
+      }
+      buf[j] = c & 0xff;
+    }
+    remain = i;
+    blockLen -= len;
+    if (blockLen == 0)
+      endOfBlock = gTrue;
+  }
+
+  return;
+
+err:
+  error(getPos(), "Unexpected end of file in flate stream");
+  endOfBlock = eof = gTrue;
+  remain = 0;
+}
+
+GBool FlateStream::startBlock() {
+  int blockHdr;
+  int c;
+  int check;
+
+  // free the code tables from the previous block
+  if (litCodeTab.codes != fixedLitCodeTab.codes) {
+    gfree(litCodeTab.codes);
+  }
+  litCodeTab.codes = NULL;
+  if (distCodeTab.codes != fixedDistCodeTab.codes) {
+    gfree(distCodeTab.codes);
+  }
+  distCodeTab.codes = NULL;
+
+  // read block header
+  blockHdr = getCodeWord(3);
+  if (blockHdr & 1)
+    eof = gTrue;
+  blockHdr >>= 1;
+
+  // uncompressed block
+  if (blockHdr == 0) {
+    compressedBlock = gFalse;
+    if ((c = str->getChar()) == EOF)
+      goto err;
+    blockLen = c & 0xff;
+    if ((c = str->getChar()) == EOF)
+      goto err;
+    blockLen |= (c & 0xff) << 8;
+    if ((c = str->getChar()) == EOF)
+      goto err;
+    check = c & 0xff;
+    if ((c = str->getChar()) == EOF)
+      goto err;
+    check |= (c & 0xff) << 8;
+    if (check != (~blockLen & 0xffff))
+      error(getPos(), "Bad uncompressed block length in flate stream");
+    codeBuf = 0;
+    codeSize = 0;
+
+  // compressed block with fixed codes
+  } else if (blockHdr == 1) {
+    compressedBlock = gTrue;
+    loadFixedCodes();
+
+  // compressed block with dynamic codes
+  } else if (blockHdr == 2) {
+    compressedBlock = gTrue;
+    if (!readDynamicCodes()) {
+      goto err;
+    }
+
+  // unknown block type
+  } else {
+    goto err;
+  }
+
+  endOfBlock = gFalse;
+  return gTrue;
+
+err:
+  error(getPos(), "Bad block header in flate stream");
+  endOfBlock = eof = gTrue;
+  return gFalse;
+}
+
+void FlateStream::loadFixedCodes() {
+  litCodeTab.codes = fixedLitCodeTab.codes;
+  litCodeTab.maxLen = fixedLitCodeTab.maxLen;
+  distCodeTab.codes = fixedDistCodeTab.codes;
+  distCodeTab.maxLen = fixedDistCodeTab.maxLen;
+}
+
+GBool FlateStream::readDynamicCodes() {
+  int numCodeLenCodes;
+  int numLitCodes;
+  int numDistCodes;
+  int codeLenCodeLengths[flateMaxCodeLenCodes];
+  FlateHuffmanTab codeLenCodeTab;
+  int len, repeat, code;
+  int i;
+
+  codeLenCodeTab.codes = NULL;
+
+  // read lengths
+  if ((numLitCodes = getCodeWord(5)) == EOF) {
+    goto err;
+  }
+  numLitCodes += 257;
+  if ((numDistCodes = getCodeWord(5)) == EOF) {
+    goto err;
+  }
+  numDistCodes += 1;
+  if ((numCodeLenCodes = getCodeWord(4)) == EOF) {
+    goto err;
+  }
+  numCodeLenCodes += 4;
+  if (numLitCodes > flateMaxLitCodes ||
+      numDistCodes > flateMaxDistCodes ||
+      numCodeLenCodes > flateMaxCodeLenCodes) {
+    goto err;
+  }
+
+  // build the code length code table
+  for (i = 0; i < flateMaxCodeLenCodes; ++i) {
+    codeLenCodeLengths[i] = 0;
+  }
+  for (i = 0; i < numCodeLenCodes; ++i) {
+    if ((codeLenCodeLengths[codeLenCodeMap[i]] = getCodeWord(3)) == -1) {
+      goto err;
+    }
+  }
+  compHuffmanCodes(codeLenCodeLengths, flateMaxCodeLenCodes, &codeLenCodeTab);
+
+  // build the literal and distance code tables
+  len = 0;
+  repeat = 0;
+  i = 0;
+  while (i < numLitCodes + numDistCodes) {
+    if ((code = getHuffmanCodeWord(&codeLenCodeTab)) == EOF) {
+      goto err;
+    }
+    if (code == 16) {
+      if ((repeat = getCodeWord(2)) == EOF) {
+	goto err;
+      }
+      repeat += 3;
+      if (i + repeat > numLitCodes + numDistCodes) {
+	goto err;
+      }
+      for (; repeat > 0; --repeat) {
+	codeLengths[i++] = len;
+      }
+    } else if (code == 17) {
+      if ((repeat = getCodeWord(3)) == EOF) {
+	goto err;
+      }
+      repeat += 3;
+      if (i + repeat > numLitCodes + numDistCodes) {
+	goto err;
+      }
+      len = 0;
+      for (; repeat > 0; --repeat) {
+	codeLengths[i++] = 0;
+      }
+    } else if (code == 18) {
+      if ((repeat = getCodeWord(7)) == EOF) {
+	goto err;
+      }
+      repeat += 11;
+      if (i + repeat > numLitCodes + numDistCodes) {
+	goto err;
+      }
+      len = 0;
+      for (; repeat > 0; --repeat) {
+	codeLengths[i++] = 0;
+      }
+    } else {
+      codeLengths[i++] = len = code;
+    }
+  }
+  compHuffmanCodes(codeLengths, numLitCodes, &litCodeTab);
+  compHuffmanCodes(codeLengths + numLitCodes, numDistCodes, &distCodeTab);
+
+  gfree(codeLenCodeTab.codes);
+  return gTrue;
+
+err:
+  error(getPos(), "Bad dynamic code table in flate stream");
+  gfree(codeLenCodeTab.codes);
+  return gFalse;
+}
+
+// Convert an array <lengths> of <n> lengths, in value order, into a
+// Huffman code lookup table.
+void FlateStream::compHuffmanCodes(int *lengths, int n, FlateHuffmanTab *tab) {
+  int tabSize, len, code, code2, skip, val, i, t;
+
+  // find max code length
+  tab->maxLen = 0;
+  for (val = 0; val < n; ++val) {
+    if (lengths[val] > tab->maxLen) {
+      tab->maxLen = lengths[val];
+    }
+  }
+
+  // allocate the table
+  tabSize = 1 << tab->maxLen;
+  tab->codes = (FlateCode *)gmallocn(tabSize, sizeof(FlateCode));
+
+  // clear the table
+  for (i = 0; i < tabSize; ++i) {
+    tab->codes[i].len = 0;
+    tab->codes[i].val = 0;
+  }
+
+  // build the table
+  for (len = 1, code = 0, skip = 2;
+       len <= tab->maxLen;
+       ++len, code <<= 1, skip <<= 1) {
+    for (val = 0; val < n; ++val) {
+      if (lengths[val] == len) {
+
+	// bit-reverse the code
+	code2 = 0;
+	t = code;
+	for (i = 0; i < len; ++i) {
+	  code2 = (code2 << 1) | (t & 1);
+	  t >>= 1;
+	}
+
+	// fill in the table entries
+	for (i = code2; i < tabSize; i += skip) {
+	  tab->codes[i].len = (Gushort)len;
+	  tab->codes[i].val = (Gushort)val;
+	}
+
+	++code;
+      }
+    }
+  }
+}
+
+int FlateStream::getHuffmanCodeWord(FlateHuffmanTab *tab) {
+  FlateCode *code;
+  int c;
+
+  while (codeSize < tab->maxLen) {
+    if ((c = str->getChar()) == EOF) {
+      break;
+    }
+    codeBuf |= (c & 0xff) << codeSize;
+    codeSize += 8;
+  }
+  code = &tab->codes[codeBuf & ((1 << tab->maxLen) - 1)];
+  if (codeSize == 0 || codeSize < code->len || code->len == 0) {
+    return EOF;
+  }
+  codeBuf >>= code->len;
+  codeSize -= code->len;
+  return (int)code->val;
+}
+
+int FlateStream::getCodeWord(int bits) {
+  int c;
+
+  while (codeSize < bits) {
+    if ((c = str->getChar()) == EOF)
+      return EOF;
+    codeBuf |= (c & 0xff) << codeSize;
+    codeSize += 8;
+  }
+  c = codeBuf & ((1 << bits) - 1);
+  codeBuf >>= bits;
+  codeSize -= bits;
+  return c;
+}
+
+//------------------------------------------------------------------------
+// EOFStream
+//------------------------------------------------------------------------
+
+EOFStream::EOFStream(Stream *strA):
+    FilterStream(strA) {
+}
+
+EOFStream::~EOFStream() {
+  delete str;
+}
+
+//------------------------------------------------------------------------
+// FixedLengthEncoder
+//------------------------------------------------------------------------
+
+FixedLengthEncoder::FixedLengthEncoder(Stream *strA, int lengthA):
+    FilterStream(strA) {
+  length = lengthA;
+  count = 0;
+}
+
+FixedLengthEncoder::~FixedLengthEncoder() {
+  if (str->isEncoder())
+    delete str;
+}
+
+void FixedLengthEncoder::reset() {
+  str->reset();
+  count = 0;
+}
+
+int FixedLengthEncoder::getChar() {
+  if (length >= 0 && count >= length)
+    return EOF;
+  ++count;
+  return str->getChar();
+}
+
+int FixedLengthEncoder::lookChar() {
+  if (length >= 0 && count >= length)
+    return EOF;
+  return str->getChar();
+}
+
+GBool FixedLengthEncoder::isBinary(GBool last) {
+  return str->isBinary(gTrue);
+}
+
+//------------------------------------------------------------------------
+// ASCIIHexEncoder
+//------------------------------------------------------------------------
+
+ASCIIHexEncoder::ASCIIHexEncoder(Stream *strA):
+    FilterStream(strA) {
+  bufPtr = bufEnd = buf;
+  lineLen = 0;
+  eof = gFalse;
+}
+
+ASCIIHexEncoder::~ASCIIHexEncoder() {
+  if (str->isEncoder()) {
+    delete str;
+  }
+}
+
+void ASCIIHexEncoder::reset() {
+  str->reset();
+  bufPtr = bufEnd = buf;
+  lineLen = 0;
+  eof = gFalse;
+}
+
+GBool ASCIIHexEncoder::fillBuf() {
+  static char *hex = "0123456789abcdef";
+  int c;
+
+  if (eof) {
+    return gFalse;
+  }
+  bufPtr = bufEnd = buf;
+  if ((c = str->getChar()) == EOF) {
+    *bufEnd++ = '>';
+    eof = gTrue;
+  } else {
+    if (lineLen >= 64) {
+      *bufEnd++ = '\n';
+      lineLen = 0;
+    }
+    *bufEnd++ = hex[(c >> 4) & 0x0f];
+    *bufEnd++ = hex[c & 0x0f];
+    lineLen += 2;
+  }
+  return gTrue;
+}
+
+//------------------------------------------------------------------------
+// ASCII85Encoder
+//------------------------------------------------------------------------
+
+ASCII85Encoder::ASCII85Encoder(Stream *strA):
+    FilterStream(strA) {
+  bufPtr = bufEnd = buf;
+  lineLen = 0;
+  eof = gFalse;
+}
+
+ASCII85Encoder::~ASCII85Encoder() {
+  if (str->isEncoder())
+    delete str;
+}
+
+void ASCII85Encoder::reset() {
+  str->reset();
+  bufPtr = bufEnd = buf;
+  lineLen = 0;
+  eof = gFalse;
+}
+
+GBool ASCII85Encoder::fillBuf() {
+  Gulong t;
+  char buf1[5];
+  int c0, c1, c2, c3;
+  int n, i;
+
+  if (eof) {
+    return gFalse;
+  }
+  c0 = str->getChar();
+  c1 = str->getChar();
+  c2 = str->getChar();
+  c3 = str->getChar();
+  bufPtr = bufEnd = buf;
+  if (c3 == EOF) {
+    if (c0 == EOF) {
+      n = 0;
+      t = 0;
+    } else {
+      if (c1 == EOF) {
+	n = 1;
+	t = c0 << 24;
+      } else if (c2 == EOF) {
+	n = 2;
+	t = (c0 << 24) | (c1 << 16);
+      } else {
+	n = 3;
+	t = (c0 << 24) | (c1 << 16) | (c2 << 8);
+      }
+      for (i = 4; i >= 0; --i) {
+	buf1[i] = (char)(t % 85 + 0x21);
+	t /= 85;
+      }
+      for (i = 0; i <= n; ++i) {
+	*bufEnd++ = buf1[i];
+	if (++lineLen == 65) {
+	  *bufEnd++ = '\n';
+	  lineLen = 0;
+	}
+      }
+    }
+    *bufEnd++ = '~';
+    *bufEnd++ = '>';
+    eof = gTrue;
+  } else {
+    t = (c0 << 24) | (c1 << 16) | (c2 << 8) | c3;
+    if (t == 0) {
+      *bufEnd++ = 'z';
+      if (++lineLen == 65) {
+	*bufEnd++ = '\n';
+	lineLen = 0;
+      }
+    } else {
+      for (i = 4; i >= 0; --i) {
+	buf1[i] = (char)(t % 85 + 0x21);
+	t /= 85;
+      }
+      for (i = 0; i <= 4; ++i) {
+	*bufEnd++ = buf1[i];
+	if (++lineLen == 65) {
+	  *bufEnd++ = '\n';
+	  lineLen = 0;
+	}
+      }
+    }
+  }
+  return gTrue;
+}
+
+//------------------------------------------------------------------------
+// RunLengthEncoder
+//------------------------------------------------------------------------
+
+RunLengthEncoder::RunLengthEncoder(Stream *strA):
+    FilterStream(strA) {
+  bufPtr = bufEnd = nextEnd = buf;
+  eof = gFalse;
+}
+
+RunLengthEncoder::~RunLengthEncoder() {
+  if (str->isEncoder())
+    delete str;
+}
+
+void RunLengthEncoder::reset() {
+  str->reset();
+  bufPtr = bufEnd = nextEnd = buf;
+  eof = gFalse;
+}
+
+//
+// When fillBuf finishes, buf[] looks like this:
+//   +-----+--------------+-----------------+--
+//   + tag | ... data ... | next 0, 1, or 2 |
+//   +-----+--------------+-----------------+--
+//    ^                    ^                 ^
+//    bufPtr               bufEnd            nextEnd
+//
+GBool RunLengthEncoder::fillBuf() {
+  int c, c1, c2;
+  int n;
+
+  // already hit EOF?
+  if (eof)
+    return gFalse;
+
+  // grab two bytes
+  if (nextEnd < bufEnd + 1) {
+    if ((c1 = str->getChar()) == EOF) {
+      eof = gTrue;
+      return gFalse;
+    }
+  } else {
+    c1 = bufEnd[0] & 0xff;
+  }
+  if (nextEnd < bufEnd + 2) {
+    if ((c2 = str->getChar()) == EOF) {
+      eof = gTrue;
+      buf[0] = 0;
+      buf[1] = c1;
+      bufPtr = buf;
+      bufEnd = &buf[2];
+      return gTrue;
+    }
+  } else {
+    c2 = bufEnd[1] & 0xff;
+  }
+
+  // check for repeat
+  c = 0; // make gcc happy
+  if (c1 == c2) {
+    n = 2;
+    while (n < 128 && (c = str->getChar()) == c1)
+      ++n;
+    buf[0] = (char)(257 - n);
+    buf[1] = c1;
+    bufEnd = &buf[2];
+    if (c == EOF) {
+      eof = gTrue;
+    } else if (n < 128) {
+      buf[2] = c;
+      nextEnd = &buf[3];
+    } else {
+      nextEnd = bufEnd;
+    }
+
+  // get up to 128 chars
+  } else {
+    buf[1] = c1;
+    buf[2] = c2;
+    n = 2;
+    while (n < 128) {
+      if ((c = str->getChar()) == EOF) {
+	eof = gTrue;
+	break;
+      }
+      ++n;
+      buf[n] = c;
+      if (buf[n] == buf[n-1])
+	break;
+    }
+    if (buf[n] == buf[n-1]) {
+      buf[0] = (char)(n-2-1);
+      bufEnd = &buf[n-1];
+      nextEnd = &buf[n+1];
+    } else {
+      buf[0] = (char)(n-1);
+      bufEnd = nextEnd = &buf[n+1];
+    }
+  }
+  bufPtr = buf;
+  return gTrue;
+}
diff --git a/xpdf/Stream.h b/xpdf/Stream.h
new file mode 100644
--- /dev/null
+++ b/xpdf/Stream.h
@@ -0,0 +1,860 @@
+//========================================================================
+//
+// Stream.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef STREAM_H
+#define STREAM_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <stdio.h>
+#include "gtypes.h"
+#include "Object.h"
+
+class BaseStream;
+
+//------------------------------------------------------------------------
+
+enum StreamKind {
+  strFile,
+  strASCIIHex,
+  strASCII85,
+  strLZW,
+  strRunLength,
+  strCCITTFax,
+  strDCT,
+  strFlate,
+  strJBIG2,
+  strJPX,
+  strWeird			// internal-use stream types
+};
+
+enum StreamColorSpaceMode {
+  streamCSNone,
+  streamCSDeviceGray,
+  streamCSDeviceRGB,
+  streamCSDeviceCMYK
+};
+
+//------------------------------------------------------------------------
+
+// This is in Stream.h instead of Decrypt.h to avoid really annoying
+// include file dependency loops.
+enum CryptAlgorithm {
+  cryptRC4,
+  cryptAES
+};
+
+//------------------------------------------------------------------------
+// Stream (base class)
+//------------------------------------------------------------------------
+
+class Stream {
+public:
+
+  // Constructor.
+  Stream();
+
+  // Destructor.
+  virtual ~Stream();
+
+  // Reference counting.
+  int incRef() { return ++ref; }
+  int decRef() { return --ref; }
+
+  // Get kind of stream.
+  virtual StreamKind getKind() = 0;
+
+  // Reset stream to beginning.
+  virtual void reset() = 0;
+
+  // Close down the stream.
+  virtual void close();
+
+  // Get next char from stream.
+  virtual int getChar() = 0;
+
+  // Peek at next char in stream.
+  virtual int lookChar() = 0;
+
+  // Get next char from stream without using the predictor.
+  // This is only used by StreamPredictor.
+  virtual int getRawChar();
+
+  // Get next line from stream.
+  virtual char *getLine(char *buf, int size);
+
+  // Get current position in file.
+  virtual int getPos() = 0;
+
+  // Go to a position in the stream.  If <dir> is negative, the
+  // position is from the end of the file; otherwise the position is
+  // from the start of the file.
+  virtual void setPos(Guint pos, int dir = 0) = 0;
+
+  // Get PostScript command for the filter(s).
+  virtual GString *getPSFilter(int psLevel, char *indent);
+
+  // Does this stream type potentially contain non-printable chars?
+  virtual GBool isBinary(GBool last = gTrue) = 0;
+
+  // Get the BaseStream of this stream.
+  virtual BaseStream *getBaseStream() = 0;
+
+  // Get the stream after the last decoder (this may be a BaseStream
+  // or a DecryptStream).
+  virtual Stream *getUndecodedStream() = 0;
+
+  // Get the dictionary associated with this stream.
+  virtual Dict *getDict() = 0;
+
+  // Is this an encoding filter?
+  virtual GBool isEncoder() { return gFalse; }
+
+  // Get image parameters which are defined by the stream contents.
+  virtual void getImageParams(int *bitsPerComponent,
+			      StreamColorSpaceMode *csMode) {}
+
+  // Return the next stream in the "stack".
+  virtual Stream *getNextStream() { return NULL; }
+
+  // Add filters to this stream according to the parameters in <dict>.
+  // Returns the new stream.
+  Stream *addFilters(Object *dict);
+
+private:
+
+  Stream *makeFilter(char *name, Stream *str, Object *params);
+
+  int ref;			// reference count
+};
+
+//------------------------------------------------------------------------
+// BaseStream
+//
+// This is the base class for all streams that read directly from a file.
+//------------------------------------------------------------------------
+
+class BaseStream: public Stream {
+public:
+
+  BaseStream(Object *dictA);
+  virtual ~BaseStream();
+  virtual Stream *makeSubStream(Guint start, GBool limited,
+				Guint length, Object *dict) = 0;
+  virtual void setPos(Guint pos, int dir = 0) = 0;
+  virtual GBool isBinary(GBool last = gTrue) { return last; }
+  virtual BaseStream *getBaseStream() { return this; }
+  virtual Stream *getUndecodedStream() { return this; }
+  virtual Dict *getDict() { return dict.getDict(); }
+  virtual GString *getFileName() { return NULL; }
+
+  // Get/set position of first byte of stream within the file.
+  virtual Guint getStart() = 0;
+  virtual void moveStart(int delta) = 0;
+
+private:
+
+  Object dict;
+};
+
+//------------------------------------------------------------------------
+// FilterStream
+//
+// This is the base class for all streams that filter another stream.
+//------------------------------------------------------------------------
+
+class FilterStream: public Stream {
+public:
+
+  FilterStream(Stream *strA);
+  virtual ~FilterStream();
+  virtual void close();
+  virtual int getPos() { return str->getPos(); }
+  virtual void setPos(Guint pos, int dir = 0);
+  virtual BaseStream *getBaseStream() { return str->getBaseStream(); }
+  virtual Stream *getUndecodedStream() { return str->getUndecodedStream(); }
+  virtual Dict *getDict() { return str->getDict(); }
+  virtual Stream *getNextStream() { return str; }
+
+protected:
+
+  Stream *str;
+};
+
+//------------------------------------------------------------------------
+// ImageStream
+//------------------------------------------------------------------------
+
+class ImageStream {
+public:
+
+  // Create an image stream object for an image with the specified
+  // parameters.  Note that these are the actual image parameters,
+  // which may be different from the predictor parameters.
+  ImageStream(Stream *strA, int widthA, int nCompsA, int nBitsA);
+
+  ~ImageStream();
+
+  // Reset the stream.
+  void reset();
+
+  // Gets the next pixel from the stream.  <pix> should be able to hold
+  // at least nComps elements.  Returns false at end of file.
+  GBool getPixel(Guchar *pix);
+
+  // Returns a pointer to the next line of pixels.  Returns NULL at
+  // end of file.
+  Guchar *getLine();
+
+  // Skip an entire line from the image.
+  void skipLine();
+
+private:
+
+  Stream *str;			// base stream
+  int width;			// pixels per line
+  int nComps;			// components per pixel
+  int nBits;			// bits per component
+  int nVals;			// components per line
+  Guchar *imgLine;		// line buffer
+  int imgIdx;			// current index in imgLine
+};
+
+//------------------------------------------------------------------------
+// StreamPredictor
+//------------------------------------------------------------------------
+
+class StreamPredictor {
+public:
+
+  // Create a predictor object.  Note that the parameters are for the
+  // predictor, and may not match the actual image parameters.
+  StreamPredictor(Stream *strA, int predictorA,
+		  int widthA, int nCompsA, int nBitsA);
+
+  ~StreamPredictor();
+
+  GBool isOk() { return ok; }
+
+  int lookChar();
+  int getChar();
+
+private:
+
+  GBool getNextLine();
+
+  Stream *str;			// base stream
+  int predictor;		// predictor
+  int width;			// pixels per line
+  int nComps;			// components per pixel
+  int nBits;			// bits per component
+  int nVals;			// components per line
+  int pixBytes;			// bytes per pixel
+  int rowBytes;			// bytes per line
+  Guchar *predLine;		// line buffer
+  int predIdx;			// current index in predLine
+  GBool ok;
+};
+
+//------------------------------------------------------------------------
+// FileStream
+//------------------------------------------------------------------------
+
+#define fileStreamBufSize 256
+
+class FileStream: public BaseStream {
+public:
+
+  FileStream(FILE *fA, Guint startA, GBool limitedA,
+	     Guint lengthA, Object *dictA);
+  virtual ~FileStream();
+  virtual Stream *makeSubStream(Guint startA, GBool limitedA,
+				Guint lengthA, Object *dictA);
+  virtual StreamKind getKind() { return strFile; }
+  virtual void reset();
+  virtual void close();
+  virtual int getChar()
+    { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
+  virtual int lookChar()
+    { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
+  virtual int getPos() { return bufPos + (bufPtr - buf); }
+  virtual void setPos(Guint pos, int dir = 0);
+  virtual Guint getStart() { return start; }
+  virtual void moveStart(int delta);
+
+private:
+
+  GBool fillBuf();
+
+  FILE *f;
+  Guint start;
+  GBool limited;
+  Guint length;
+  char buf[fileStreamBufSize];
+  char *bufPtr;
+  char *bufEnd;
+  Guint bufPos;
+  int savePos;
+  GBool saved;
+};
+
+//------------------------------------------------------------------------
+// MemStream
+//------------------------------------------------------------------------
+
+class MemStream: public BaseStream {
+public:
+
+  MemStream(char *bufA, Guint startA, Guint lengthA, Object *dictA);
+  virtual ~MemStream();
+  virtual Stream *makeSubStream(Guint start, GBool limited,
+				Guint lengthA, Object *dictA);
+  virtual StreamKind getKind() { return strWeird; }
+  virtual void reset();
+  virtual void close();
+  virtual int getChar()
+    { return (bufPtr < bufEnd) ? (*bufPtr++ & 0xff) : EOF; }
+  virtual int lookChar()
+    { return (bufPtr < bufEnd) ? (*bufPtr & 0xff) : EOF; }
+  virtual int getPos() { return (int)(bufPtr - buf); }
+  virtual void setPos(Guint pos, int dir = 0);
+  virtual Guint getStart() { return start; }
+  virtual void moveStart(int delta);
+
+private:
+
+  char *buf;
+  Guint start;
+  Guint length;
+  char *bufEnd;
+  char *bufPtr;
+  GBool needFree;
+};
+
+//------------------------------------------------------------------------
+// EmbedStream
+//
+// This is a special stream type used for embedded streams (inline
+// images).  It reads directly from the base stream -- after the
+// EmbedStream is deleted, reads from the base stream will proceed where
+// the BaseStream left off.  Note that this is very different behavior
+// that creating a new FileStream (using makeSubStream).
+//------------------------------------------------------------------------
+
+class EmbedStream: public BaseStream {
+public:
+
+  EmbedStream(Stream *strA, Object *dictA, GBool limitedA, Guint lengthA);
+  virtual ~EmbedStream();
+  virtual Stream *makeSubStream(Guint start, GBool limitedA,
+				Guint lengthA, Object *dictA);
+  virtual StreamKind getKind() { return str->getKind(); }
+  virtual void reset() {}
+  virtual int getChar();
+  virtual int lookChar();
+  virtual int getPos() { return str->getPos(); }
+  virtual void setPos(Guint pos, int dir = 0);
+  virtual Guint getStart();
+  virtual void moveStart(int delta);
+
+private:
+
+  Stream *str;
+  GBool limited;
+  Guint length;
+};
+
+//------------------------------------------------------------------------
+// ASCIIHexStream
+//------------------------------------------------------------------------
+
+class ASCIIHexStream: public FilterStream {
+public:
+
+  ASCIIHexStream(Stream *strA);
+  virtual ~ASCIIHexStream();
+  virtual StreamKind getKind() { return strASCIIHex; }
+  virtual void reset();
+  virtual int getChar()
+    { int c = lookChar(); buf = EOF; return c; }
+  virtual int lookChar();
+  virtual GString *getPSFilter(int psLevel, char *indent);
+  virtual GBool isBinary(GBool last = gTrue);
+
+private:
+
+  int buf;
+  GBool eof;
+};
+
+//------------------------------------------------------------------------
+// ASCII85Stream
+//------------------------------------------------------------------------
+
+class ASCII85Stream: public FilterStream {
+public:
+
+  ASCII85Stream(Stream *strA);
+  virtual ~ASCII85Stream();
+  virtual StreamKind getKind() { return strASCII85; }
+  virtual void reset();
+  virtual int getChar()
+    { int ch = lookChar(); ++index; return ch; }
+  virtual int lookChar();
+  virtual GString *getPSFilter(int psLevel, char *indent);
+  virtual GBool isBinary(GBool last = gTrue);
+
+private:
+
+  int c[5];
+  int b[4];
+  int index, n;
+  GBool eof;
+};
+
+//------------------------------------------------------------------------
+// LZWStream
+//------------------------------------------------------------------------
+
+class LZWStream: public FilterStream {
+public:
+
+  LZWStream(Stream *strA, int predictor, int columns, int colors,
+	    int bits, int earlyA);
+  virtual ~LZWStream();
+  virtual StreamKind getKind() { return strLZW; }
+  virtual void reset();
+  virtual int getChar();
+  virtual int lookChar();
+  virtual int getRawChar();
+  virtual GString *getPSFilter(int psLevel, char *indent);
+  virtual GBool isBinary(GBool last = gTrue);
+
+private:
+
+  StreamPredictor *pred;	// predictor
+  int early;			// early parameter
+  GBool eof;			// true if at eof
+  int inputBuf;			// input buffer
+  int inputBits;		// number of bits in input buffer
+  struct {			// decoding table
+    int length;
+    int head;
+    Guchar tail;
+  } table[4097];
+  int nextCode;			// next code to be used
+  int nextBits;			// number of bits in next code word
+  int prevCode;			// previous code used in stream
+  int newChar;			// next char to be added to table
+  Guchar seqBuf[4097];		// buffer for current sequence
+  int seqLength;		// length of current sequence
+  int seqIndex;			// index into current sequence
+  GBool first;			// first code after a table clear
+
+  GBool processNextCode();
+  void clearTable();
+  int getCode();
+};
+
+//------------------------------------------------------------------------
+// RunLengthStream
+//------------------------------------------------------------------------
+
+class RunLengthStream: public FilterStream {
+public:
+
+  RunLengthStream(Stream *strA);
+  virtual ~RunLengthStream();
+  virtual StreamKind getKind() { return strRunLength; }
+  virtual void reset();
+  virtual int getChar()
+    { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
+  virtual int lookChar()
+    { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
+  virtual GString *getPSFilter(int psLevel, char *indent);
+  virtual GBool isBinary(GBool last = gTrue);
+
+private:
+
+  char buf[128];		// buffer
+  char *bufPtr;			// next char to read
+  char *bufEnd;			// end of buffer
+  GBool eof;
+
+  GBool fillBuf();
+};
+
+//------------------------------------------------------------------------
+// CCITTFaxStream
+//------------------------------------------------------------------------
+
+struct CCITTCodeTable;
+
+class CCITTFaxStream: public FilterStream {
+public:
+
+  CCITTFaxStream(Stream *strA, int encodingA, GBool endOfLineA,
+		 GBool byteAlignA, int columnsA, int rowsA,
+		 GBool endOfBlockA, GBool blackA);
+  virtual ~CCITTFaxStream();
+  virtual StreamKind getKind() { return strCCITTFax; }
+  virtual void reset();
+  virtual int getChar()
+    { int c = lookChar(); buf = EOF; return c; }
+  virtual int lookChar();
+  virtual GString *getPSFilter(int psLevel, char *indent);
+  virtual GBool isBinary(GBool last = gTrue);
+
+private:
+
+  int encoding;			// 'K' parameter
+  GBool endOfLine;		// 'EndOfLine' parameter
+  GBool byteAlign;		// 'EncodedByteAlign' parameter
+  int columns;			// 'Columns' parameter
+  int rows;			// 'Rows' parameter
+  GBool endOfBlock;		// 'EndOfBlock' parameter
+  GBool black;			// 'BlackIs1' parameter
+  GBool eof;			// true if at eof
+  GBool nextLine2D;		// true if next line uses 2D encoding
+  int row;			// current row
+  int inputBuf;			// input buffer
+  int inputBits;		// number of bits in input buffer
+  int *codingLine;		// coding line changing elements
+  int *refLine;			// reference line changing elements
+  int a0i;			// index into codingLine
+  GBool err;			// error on current line
+  int outputBits;		// remaining ouput bits
+  int buf;			// character buffer
+
+  void addPixels(int a1, int black);
+  void addPixelsNeg(int a1, int black);
+  short getTwoDimCode();
+  short getWhiteCode();
+  short getBlackCode();
+  short lookBits(int n);
+  void eatBits(int n) { if ((inputBits -= n) < 0) inputBits = 0; }
+};
+
+//------------------------------------------------------------------------
+// DCTStream
+//------------------------------------------------------------------------
+
+// DCT component info
+struct DCTCompInfo {
+  int id;			// component ID
+  int hSample, vSample;		// horiz/vert sampling resolutions
+  int quantTable;		// quantization table number
+  int prevDC;			// DC coefficient accumulator
+};
+
+struct DCTScanInfo {
+  GBool comp[4];		// comp[i] is set if component i is
+				//   included in this scan
+  int numComps;			// number of components in the scan
+  int dcHuffTable[4];		// DC Huffman table numbers
+  int acHuffTable[4];		// AC Huffman table numbers
+  int firstCoeff, lastCoeff;	// first and last DCT coefficient
+  int ah, al;			// successive approximation parameters
+};
+
+// DCT Huffman decoding table
+struct DCTHuffTable {
+  Guchar firstSym[17];		// first symbol for this bit length
+  Gushort firstCode[17];	// first code for this bit length
+  Gushort numCodes[17];		// number of codes of this bit length
+  Guchar sym[256];		// symbols
+};
+
+class DCTStream: public FilterStream {
+public:
+
+  DCTStream(Stream *strA, int colorXformA);
+  virtual ~DCTStream();
+  virtual StreamKind getKind() { return strDCT; }
+  virtual void reset();
+  virtual void close();
+  virtual int getChar();
+  virtual int lookChar();
+  virtual GString *getPSFilter(int psLevel, char *indent);
+  virtual GBool isBinary(GBool last = gTrue);
+  Stream *getRawStream() { return str; }
+
+private:
+
+  GBool progressive;		// set if in progressive mode
+  GBool interleaved;		// set if in interleaved mode
+  int width, height;		// image size
+  int mcuWidth, mcuHeight;	// size of min coding unit, in data units
+  int bufWidth, bufHeight;	// frameBuf size
+  DCTCompInfo compInfo[4];	// info for each component
+  DCTScanInfo scanInfo;		// info for the current scan
+  int numComps;			// number of components in image
+  int colorXform;		// color transform: -1 = unspecified
+				//                   0 = none
+				//                   1 = YUV/YUVK -> RGB/CMYK
+  GBool gotJFIFMarker;		// set if APP0 JFIF marker was present
+  GBool gotAdobeMarker;		// set if APP14 Adobe marker was present
+  int restartInterval;		// restart interval, in MCUs
+  Gushort quantTables[4][64];	// quantization tables
+  int numQuantTables;		// number of quantization tables
+  DCTHuffTable dcHuffTables[4];	// DC Huffman tables
+  DCTHuffTable acHuffTables[4];	// AC Huffman tables
+  int numDCHuffTables;		// number of DC Huffman tables
+  int numACHuffTables;		// number of AC Huffman tables
+  Guchar *rowBuf[4][32];	// buffer for one MCU (non-progressive mode)
+  int *frameBuf[4];		// buffer for frame (progressive mode)
+  int comp, x, y, dy;		// current position within image/MCU
+  int restartCtr;		// MCUs left until restart
+  int restartMarker;		// next restart marker
+  int eobRun;			// number of EOBs left in the current run
+  int inputBuf;			// input buffer for variable length codes
+  int inputBits;		// number of valid bits in input buffer
+
+  void restart();
+  GBool readMCURow();
+  void readScan();
+  GBool readDataUnit(DCTHuffTable *dcHuffTable,
+		     DCTHuffTable *acHuffTable,
+		     int *prevDC, int data[64]);
+  GBool readProgressiveDataUnit(DCTHuffTable *dcHuffTable,
+				DCTHuffTable *acHuffTable,
+				int *prevDC, int data[64]);
+  void decodeImage();
+  void transformDataUnit(Gushort *quantTable,
+			 int dataIn[64], Guchar dataOut[64]);
+  int readHuffSym(DCTHuffTable *table);
+  int readAmp(int size);
+  int readBit();
+  GBool readHeader();
+  GBool readBaselineSOF();
+  GBool readProgressiveSOF();
+  GBool readScanInfo();
+  GBool readQuantTables();
+  GBool readHuffmanTables();
+  GBool readRestartInterval();
+  GBool readJFIFMarker();
+  GBool readAdobeMarker();
+  GBool readTrailer();
+  int readMarker();
+  int read16();
+};
+
+//------------------------------------------------------------------------
+// FlateStream
+//------------------------------------------------------------------------
+
+#define flateWindow          32768    // buffer size
+#define flateMask            (flateWindow-1)
+#define flateMaxHuffman         15    // max Huffman code length
+#define flateMaxCodeLenCodes    19    // max # code length codes
+#define flateMaxLitCodes       288    // max # literal codes
+#define flateMaxDistCodes       30    // max # distance codes
+
+// Huffman code table entry
+struct FlateCode {
+  Gushort len;			// code length, in bits
+  Gushort val;			// value represented by this code
+};
+
+struct FlateHuffmanTab {
+  FlateCode *codes;
+  int maxLen;
+};
+
+// Decoding info for length and distance code words
+struct FlateDecode {
+  int bits;			// # extra bits
+  int first;			// first length/distance
+};
+
+class FlateStream: public FilterStream {
+public:
+
+  FlateStream(Stream *strA, int predictor, int columns,
+	      int colors, int bits);
+  virtual ~FlateStream();
+  virtual StreamKind getKind() { return strFlate; }
+  virtual void reset();
+  virtual int getChar();
+  virtual int lookChar();
+  virtual int getRawChar();
+  virtual GString *getPSFilter(int psLevel, char *indent);
+  virtual GBool isBinary(GBool last = gTrue);
+
+private:
+
+  StreamPredictor *pred;	// predictor
+  Guchar buf[flateWindow];	// output data buffer
+  int index;			// current index into output buffer
+  int remain;			// number valid bytes in output buffer
+  int codeBuf;			// input buffer
+  int codeSize;			// number of bits in input buffer
+  int				// literal and distance code lengths
+    codeLengths[flateMaxLitCodes + flateMaxDistCodes];
+  FlateHuffmanTab litCodeTab;	// literal code table
+  FlateHuffmanTab distCodeTab;	// distance code table
+  GBool compressedBlock;	// set if reading a compressed block
+  int blockLen;			// remaining length of uncompressed block
+  GBool endOfBlock;		// set when end of block is reached
+  GBool eof;			// set when end of stream is reached
+
+  static int			// code length code reordering
+    codeLenCodeMap[flateMaxCodeLenCodes];
+  static FlateDecode		// length decoding info
+    lengthDecode[flateMaxLitCodes-257];
+  static FlateDecode		// distance decoding info
+    distDecode[flateMaxDistCodes];
+  static FlateHuffmanTab	// fixed literal code table
+    fixedLitCodeTab;
+  static FlateHuffmanTab	// fixed distance code table
+    fixedDistCodeTab;
+
+  void readSome();
+  GBool startBlock();
+  void loadFixedCodes();
+  GBool readDynamicCodes();
+  void compHuffmanCodes(int *lengths, int n, FlateHuffmanTab *tab);
+  int getHuffmanCodeWord(FlateHuffmanTab *tab);
+  int getCodeWord(int bits);
+};
+
+//------------------------------------------------------------------------
+// EOFStream
+//------------------------------------------------------------------------
+
+class EOFStream: public FilterStream {
+public:
+
+  EOFStream(Stream *strA);
+  virtual ~EOFStream();
+  virtual StreamKind getKind() { return strWeird; }
+  virtual void reset() {}
+  virtual int getChar() { return EOF; }
+  virtual int lookChar() { return EOF; }
+  virtual GString *getPSFilter(int psLevel, char *indent)  { return NULL; }
+  virtual GBool isBinary(GBool last = gTrue) { return gFalse; }
+};
+
+//------------------------------------------------------------------------
+// FixedLengthEncoder
+//------------------------------------------------------------------------
+
+class FixedLengthEncoder: public FilterStream {
+public:
+
+  FixedLengthEncoder(Stream *strA, int lengthA);
+  ~FixedLengthEncoder();
+  virtual StreamKind getKind() { return strWeird; }
+  virtual void reset();
+  virtual int getChar();
+  virtual int lookChar();
+  virtual GString *getPSFilter(int psLevel, char *indent) { return NULL; }
+  virtual GBool isBinary(GBool last = gTrue);
+  virtual GBool isEncoder() { return gTrue; }
+
+private:
+
+  int length;
+  int count;
+};
+
+//------------------------------------------------------------------------
+// ASCIIHexEncoder
+//------------------------------------------------------------------------
+
+class ASCIIHexEncoder: public FilterStream {
+public:
+
+  ASCIIHexEncoder(Stream *strA);
+  virtual ~ASCIIHexEncoder();
+  virtual StreamKind getKind() { return strWeird; }
+  virtual void reset();
+  virtual int getChar()
+    { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
+  virtual int lookChar()
+    { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
+  virtual GString *getPSFilter(int psLevel, char *indent) { return NULL; }
+  virtual GBool isBinary(GBool last = gTrue) { return gFalse; }
+  virtual GBool isEncoder() { return gTrue; }
+
+private:
+
+  char buf[4];
+  char *bufPtr;
+  char *bufEnd;
+  int lineLen;
+  GBool eof;
+
+  GBool fillBuf();
+};
+
+//------------------------------------------------------------------------
+// ASCII85Encoder
+//------------------------------------------------------------------------
+
+class ASCII85Encoder: public FilterStream {
+public:
+
+  ASCII85Encoder(Stream *strA);
+  virtual ~ASCII85Encoder();
+  virtual StreamKind getKind() { return strWeird; }
+  virtual void reset();
+  virtual int getChar()
+    { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
+  virtual int lookChar()
+    { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
+  virtual GString *getPSFilter(int psLevel, char *indent) { return NULL; }
+  virtual GBool isBinary(GBool last = gTrue) { return gFalse; }
+  virtual GBool isEncoder() { return gTrue; }
+
+private:
+
+  char buf[8];
+  char *bufPtr;
+  char *bufEnd;
+  int lineLen;
+  GBool eof;
+
+  GBool fillBuf();
+};
+
+//------------------------------------------------------------------------
+// RunLengthEncoder
+//------------------------------------------------------------------------
+
+class RunLengthEncoder: public FilterStream {
+public:
+
+  RunLengthEncoder(Stream *strA);
+  virtual ~RunLengthEncoder();
+  virtual StreamKind getKind() { return strWeird; }
+  virtual void reset();
+  virtual int getChar()
+    { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr++ & 0xff); }
+  virtual int lookChar()
+    { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); }
+  virtual GString *getPSFilter(int psLevel, char *indent) { return NULL; }
+  virtual GBool isBinary(GBool last = gTrue) { return gTrue; }
+  virtual GBool isEncoder() { return gTrue; }
+
+private:
+
+  char buf[131];
+  char *bufPtr;
+  char *bufEnd;
+  char *nextEnd;
+  GBool eof;
+
+  GBool fillBuf();
+};
+
+#endif
diff --git a/xpdf/TextOutputDev.cc b/xpdf/TextOutputDev.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/TextOutputDev.cc
@@ -0,0 +1,4103 @@
+//========================================================================
+//
+// TextOutputDev.cc
+//
+// Copyright 1997-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <math.h>
+#include <ctype.h>
+#ifdef WIN32
+#include <fcntl.h> // for O_BINARY
+#include <io.h>    // for setmode
+#endif
+#include "gmem.h"
+#include "GString.h"
+#include "GList.h"
+#include "config.h"
+#include "Error.h"
+#include "GlobalParams.h"
+#include "UnicodeMap.h"
+#include "UnicodeTypeTable.h"
+#include "GfxState.h"
+#include "Link.h"
+#include "TextOutputDev.h"
+
+#ifdef MACOS
+// needed for setting type/creator of MacOS files
+#include "ICSupport.h"
+#endif
+
+//------------------------------------------------------------------------
+// parameters
+//------------------------------------------------------------------------
+
+// Each bucket in a text pool includes baselines within a range of
+// this many points.
+#define textPoolStep 4
+
+// Inter-character space width which will cause addChar to start a new
+// word.
+#define minWordBreakSpace 0.1
+
+// Negative inter-character space width, i.e., overlap, which will
+// cause addChar to start a new word.
+#define minDupBreakOverlap 0.2
+
+// Max distance between baselines of two lines within a block, as a
+// fraction of the font size.
+#define maxLineSpacingDelta 1.5
+
+// Max difference in primary font sizes on two lines in the same
+// block.  Delta1 is used when examining new lines above and below the
+// current block; delta2 is used when examining text that overlaps the
+// current block; delta3 is used when examining text to the left and
+// right of the current block.
+#define maxBlockFontSizeDelta1 0.05
+#define maxBlockFontSizeDelta2 0.6
+#define maxBlockFontSizeDelta3 0.2
+
+// Max difference in font sizes inside a word.
+#define maxWordFontSizeDelta 0.05
+
+// Maximum distance between baselines of two words on the same line,
+// e.g., distance between subscript or superscript and the primary
+// baseline, as a fraction of the font size.
+#define maxIntraLineDelta 0.5
+
+// Minimum inter-word spacing, as a fraction of the font size.  (Only
+// used for raw ordering.)
+#define minWordSpacing 0.15
+
+// Maximum inter-word spacing, as a fraction of the font size.
+#define maxWordSpacing 1.5
+
+// Maximum horizontal spacing which will allow a word to be pulled
+// into a block.
+#define minColSpacing1 0.3
+
+// Minimum spacing between columns, as a fraction of the font size.
+#define minColSpacing2 1.0
+
+// Maximum vertical spacing between blocks within a flow, as a
+// multiple of the font size.
+#define maxBlockSpacing 2.5
+
+// Minimum spacing between characters within a word, as a fraction of
+// the font size.
+#define minCharSpacing -0.2
+
+// Maximum spacing between characters within a word, as a fraction of
+// the font size, when there is no obvious extra-wide character
+// spacing.
+#define maxCharSpacing 0.03
+
+// When extra-wide character spacing is detected, the inter-character
+// space threshold is set to the minimum inter-character space
+// multiplied by this constant.
+#define maxWideCharSpacingMul 1.3
+
+// Upper limit on spacing between characters in a word.
+#define maxWideCharSpacing 0.4
+
+// Max difference in primary,secondary coordinates (as a fraction of
+// the font size) allowed for duplicated text (fake boldface, drop
+// shadows) which is to be discarded.
+#define dupMaxPriDelta 0.1
+#define dupMaxSecDelta 0.2
+
+// Max width of underlines (in points).
+#define maxUnderlineWidth 3
+
+// Min distance between baseline and underline (in points).
+//~ this should be font-size-dependent
+#define minUnderlineGap -2
+
+// Max distance between baseline and underline (in points).
+//~ this should be font-size-dependent
+#define maxUnderlineGap 4
+
+// Max horizontal distance between edge of word and start of underline
+// (in points).
+//~ this should be font-size-dependent
+#define underlineSlack 1
+
+// Max distance between edge of text and edge of link border
+#define hyperlinkSlack 2
+
+//------------------------------------------------------------------------
+// TextUnderline
+//------------------------------------------------------------------------
+
+class TextUnderline {
+public:
+
+  TextUnderline(double x0A, double y0A, double x1A, double y1A)
+    { x0 = x0A; y0 = y0A; x1 = x1A; y1 = y1A; horiz = y0 == y1; }
+  ~TextUnderline() {}
+
+  double x0, y0, x1, y1;
+  GBool horiz;
+};
+
+//------------------------------------------------------------------------
+// TextLink
+//------------------------------------------------------------------------
+
+class TextLink {
+public:
+
+  TextLink(int xMinA, int yMinA, int xMaxA, int yMaxA, Link *linkA)
+    { xMin = xMinA; yMin = yMinA; xMax = xMaxA; yMax = yMaxA; link = linkA; }
+  ~TextLink() {}
+
+  int xMin, yMin, xMax, yMax;
+  Link *link;
+};
+
+//------------------------------------------------------------------------
+// TextFontInfo
+//------------------------------------------------------------------------
+
+TextFontInfo::TextFontInfo(GfxState *state) {
+  gfxFont = state->getFont();
+#if TEXTOUT_WORD_LIST
+  fontName = (gfxFont && gfxFont->getOrigName())
+                 ? gfxFont->getOrigName()->copy()
+                 : (GString *)NULL;
+  flags = gfxFont ? gfxFont->getFlags() : 0;
+#endif
+}
+
+TextFontInfo::~TextFontInfo() {
+#if TEXTOUT_WORD_LIST
+  if (fontName) {
+    delete fontName;
+  }
+#endif
+}
+
+GBool TextFontInfo::matches(GfxState *state) {
+  return state->getFont() == gfxFont;
+}
+
+//------------------------------------------------------------------------
+// TextWord
+//------------------------------------------------------------------------
+
+TextWord::TextWord(GfxState *state, int rotA, double x0, double y0,
+		   int charPosA, TextFontInfo *fontA, double fontSizeA) {
+  GfxFont *gfxFont;
+  double x, y, ascent, descent;
+
+  rot = rotA;
+  charPos = charPosA;
+  charLen = 0;
+  font = fontA;
+  fontSize = fontSizeA;
+  state->transform(x0, y0, &x, &y);
+  if ((gfxFont = font->gfxFont)) {
+    ascent = gfxFont->getAscent() * fontSize;
+    descent = gfxFont->getDescent() * fontSize;
+  } else {
+    // this means that the PDF file draws text without a current font,
+    // which should never happen
+    ascent = 0.95 * fontSize;
+    descent = -0.35 * fontSize;
+  }
+  switch (rot) {
+  case 0:
+    yMin = y - ascent;
+    yMax = y - descent;
+    if (yMin == yMax) {
+      // this is a sanity check for a case that shouldn't happen -- but
+      // if it does happen, we want to avoid dividing by zero later
+      yMin = y;
+      yMax = y + 1;
+    }
+    base = y;
+    break;
+  case 1:
+    xMin = x + descent;
+    xMax = x + ascent;
+    if (xMin == xMax) {
+      // this is a sanity check for a case that shouldn't happen -- but
+      // if it does happen, we want to avoid dividing by zero later
+      xMin = x;
+      xMax = x + 1;
+    }
+    base = x;
+    break;
+  case 2:
+    yMin = y + descent;
+    yMax = y + ascent;
+    if (yMin == yMax) {
+      // this is a sanity check for a case that shouldn't happen -- but
+      // if it does happen, we want to avoid dividing by zero later
+      yMin = y;
+      yMax = y + 1;
+    }
+    base = y;
+    break;
+  case 3:
+    xMin = x - ascent;
+    xMax = x - descent;
+    if (xMin == xMax) {
+      // this is a sanity check for a case that shouldn't happen -- but
+      // if it does happen, we want to avoid dividing by zero later
+      xMin = x;
+      xMax = x + 1;
+    }
+    base = x;
+    break;
+  }
+  text = NULL;
+  edge = NULL;
+  len = size = 0;
+  spaceAfter = gFalse;
+  next = NULL;
+
+#if TEXTOUT_WORD_LIST
+  GfxRGB rgb;
+
+  if ((state->getRender() & 3) == 1) {
+    state->getStrokeRGB(&rgb);
+  } else {
+    state->getFillRGB(&rgb);
+  }
+  colorR = colToDbl(rgb.r);
+  colorG = colToDbl(rgb.g);
+  colorB = colToDbl(rgb.b);
+#endif
+
+  underlined = gFalse;
+  link = NULL;
+}
+
+TextWord::~TextWord() {
+  gfree(text);
+  gfree(edge);
+}
+
+void TextWord::addChar(GfxState *state, double x, double y,
+		       double dx, double dy, Unicode u) {
+  if (len == size) {
+    size += 16;
+    text = (Unicode *)greallocn(text, size, sizeof(Unicode));
+    edge = (double *)greallocn(edge, size + 1, sizeof(double));
+  }
+  text[len] = u;
+  switch (rot) {
+  case 0:
+    if (len == 0) {
+      xMin = x;
+    }
+    edge[len] = x;
+    xMax = edge[len+1] = x + dx;
+    break;
+  case 1:
+    if (len == 0) {
+      yMin = y;
+    }
+    edge[len] = y;
+    yMax = edge[len+1] = y + dy;
+    break;
+  case 2:
+    if (len == 0) {
+      xMax = x;
+    }
+    edge[len] = x;
+    xMin = edge[len+1] = x + dx;
+    break;
+  case 3:
+    if (len == 0) {
+      yMax = y;
+    }
+    edge[len] = y;
+    yMin = edge[len+1] = y + dy;
+    break;
+  }
+  ++len;
+}
+
+void TextWord::merge(TextWord *word) {
+  int i;
+
+  if (word->xMin < xMin) {
+    xMin = word->xMin;
+  }
+  if (word->yMin < yMin) {
+    yMin = word->yMin;
+  }
+  if (word->xMax > xMax) {
+    xMax = word->xMax;
+  }
+  if (word->yMax > yMax) {
+    yMax = word->yMax;
+  }
+  if (len + word->len > size) {
+    size = len + word->len;
+    text = (Unicode *)greallocn(text, size, sizeof(Unicode));
+    edge = (double *)greallocn(edge, size + 1, sizeof(double));
+  }
+  for (i = 0; i < word->len; ++i) {
+    text[len + i] = word->text[i];
+    edge[len + i] = word->edge[i];
+  }
+  edge[len + word->len] = word->edge[word->len];
+  len += word->len;
+  charLen += word->charLen;
+}
+
+inline int TextWord::primaryCmp(TextWord *word) {
+  double cmp;
+
+  cmp = 0; // make gcc happy
+  switch (rot) {
+  case 0:
+    cmp = xMin - word->xMin;
+    break;
+  case 1:
+    cmp = yMin - word->yMin;
+    break;
+  case 2:
+    cmp = word->xMax - xMax;
+    break;
+  case 3:
+    cmp = word->yMax - yMax;
+    break;
+  }
+  return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+}
+
+double TextWord::primaryDelta(TextWord *word) {
+  double delta;
+
+  delta = 0; // make gcc happy
+  switch (rot) {
+  case 0:
+    delta = word->xMin - xMax;
+    break;
+  case 1:
+    delta = word->yMin - yMax;
+    break;
+  case 2:
+    delta = xMin - word->xMax;
+    break;
+  case 3:
+    delta = yMin - word->yMax;
+    break;
+  }
+  return delta;
+}
+
+int TextWord::cmpYX(const void *p1, const void *p2) {
+  TextWord *word1 = *(TextWord **)p1;
+  TextWord *word2 = *(TextWord **)p2;
+  double cmp;
+
+  cmp = word1->yMin - word2->yMin;
+  if (cmp == 0) {
+    cmp = word1->xMin - word2->xMin;
+  }
+  return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+}
+
+#if TEXTOUT_WORD_LIST
+
+GString *TextWord::getText() {
+  GString *s;
+  UnicodeMap *uMap;
+  char buf[8];
+  int n, i;
+
+  s = new GString();
+  if (!(uMap = globalParams->getTextEncoding())) {
+    return s;
+  }
+  for (i = 0; i < len; ++i) {
+    n = uMap->mapUnicode(text[i], buf, sizeof(buf));
+    s->append(buf, n);
+  }
+  uMap->decRefCnt();
+  return s;
+}
+
+void TextWord::getCharBBox(int charIdx, double *xMinA, double *yMinA,
+			   double *xMaxA, double *yMaxA) {
+  if (charIdx < 0 || charIdx >= len) {
+    return;
+  }
+  switch (rot) {
+  case 0:
+    *xMinA = edge[charIdx];
+    *xMaxA = edge[charIdx + 1];
+    *yMinA = yMin;
+    *yMaxA = yMax;
+    break;
+  case 1:
+    *xMinA = xMin;
+    *xMaxA = xMax;
+    *yMinA = edge[charIdx];
+    *yMaxA = edge[charIdx + 1];
+    break;
+  case 2:
+    *xMinA = edge[charIdx + 1];
+    *xMaxA = edge[charIdx];
+    *yMinA = yMin;
+    *yMaxA = yMax;
+    break;
+  case 3:
+    *xMinA = xMin;
+    *xMaxA = xMax;
+    *yMinA = edge[charIdx + 1];
+    *yMaxA = edge[charIdx];
+    break;
+  }
+}
+
+#endif // TEXTOUT_WORD_LIST
+
+//------------------------------------------------------------------------
+// TextPool
+//------------------------------------------------------------------------
+
+TextPool::TextPool() {
+  minBaseIdx = 0;
+  maxBaseIdx = -1;
+  pool = NULL;
+  cursor = NULL;
+  cursorBaseIdx = -1;
+}
+
+TextPool::~TextPool() {
+  int baseIdx;
+  TextWord *word, *word2;
+
+  for (baseIdx = minBaseIdx; baseIdx <= maxBaseIdx; ++baseIdx) {
+    for (word = pool[baseIdx - minBaseIdx]; word; word = word2) {
+      word2 = word->next;
+      delete word;
+    }
+  }
+  gfree(pool);
+}
+
+int TextPool::getBaseIdx(double base) {
+  int baseIdx;
+
+  baseIdx = (int)(base / textPoolStep);
+  if (baseIdx < minBaseIdx) {
+    return minBaseIdx;
+  }
+  if (baseIdx > maxBaseIdx) {
+    return maxBaseIdx;
+  }
+  return baseIdx;
+}
+
+void TextPool::addWord(TextWord *word) {
+  TextWord **newPool;
+  int wordBaseIdx, newMinBaseIdx, newMaxBaseIdx, baseIdx;
+  TextWord *w0, *w1;
+
+  // expand the array if needed
+  wordBaseIdx = (int)(word->base / textPoolStep);
+  if (minBaseIdx > maxBaseIdx) {
+    minBaseIdx = wordBaseIdx - 128;
+    maxBaseIdx = wordBaseIdx + 128;
+    pool = (TextWord **)gmallocn(maxBaseIdx - minBaseIdx + 1,
+				 sizeof(TextWord *));
+    for (baseIdx = minBaseIdx; baseIdx <= maxBaseIdx; ++baseIdx) {
+      pool[baseIdx - minBaseIdx] = NULL;
+    }
+  } else if (wordBaseIdx < minBaseIdx) {
+    newMinBaseIdx = wordBaseIdx - 128;
+    newPool = (TextWord **)gmallocn(maxBaseIdx - newMinBaseIdx + 1,
+				    sizeof(TextWord *));
+    for (baseIdx = newMinBaseIdx; baseIdx < minBaseIdx; ++baseIdx) {
+      newPool[baseIdx - newMinBaseIdx] = NULL;
+    }
+    memcpy(&newPool[minBaseIdx - newMinBaseIdx], pool,
+	   (maxBaseIdx - minBaseIdx + 1) * sizeof(TextWord *));
+    gfree(pool);
+    pool = newPool;
+    minBaseIdx = newMinBaseIdx;
+  } else if (wordBaseIdx > maxBaseIdx) {
+    newMaxBaseIdx = wordBaseIdx + 128;
+    pool = (TextWord **)greallocn(pool, newMaxBaseIdx - minBaseIdx + 1,
+				  sizeof(TextWord *));
+    for (baseIdx = maxBaseIdx + 1; baseIdx <= newMaxBaseIdx; ++baseIdx) {
+      pool[baseIdx - minBaseIdx] = NULL;
+    }
+    maxBaseIdx = newMaxBaseIdx;
+  }
+
+  // insert the new word
+  if (cursor && wordBaseIdx == cursorBaseIdx &&
+      word->primaryCmp(cursor) > 0) {
+    w0 = cursor;
+    w1 = cursor->next;
+  } else {
+    w0 = NULL;
+    w1 = pool[wordBaseIdx - minBaseIdx];
+  }
+  for (; w1 && word->primaryCmp(w1) > 0; w0 = w1, w1 = w1->next) ;
+  word->next = w1;
+  if (w0) {
+    w0->next = word;
+  } else {
+    pool[wordBaseIdx - minBaseIdx] = word;
+  }
+  cursor = word;
+  cursorBaseIdx = wordBaseIdx;
+}
+
+//------------------------------------------------------------------------
+// TextLine
+//------------------------------------------------------------------------
+
+TextLine::TextLine(TextBlock *blkA, int rotA, double baseA) {
+  blk = blkA;
+  rot = rotA;
+  xMin = yMin = 0;
+  xMax = yMax = -1;
+  base = baseA;
+  words = lastWord = NULL;
+  text = NULL;
+  edge = NULL;
+  col = NULL;
+  len = 0;
+  convertedLen = 0;
+  hyphenated = gFalse;
+  next = NULL;
+}
+
+TextLine::~TextLine() {
+  TextWord *word;
+
+  while (words) {
+    word = words;
+    words = words->next;
+    delete word;
+  }
+  gfree(text);
+  gfree(edge);
+  gfree(col);
+}
+
+void TextLine::addWord(TextWord *word) {
+  if (lastWord) {
+    lastWord->next = word;
+  } else {
+    words = word;
+  }
+  lastWord = word;
+
+  if (xMin > xMax) {
+    xMin = word->xMin;
+    xMax = word->xMax;
+    yMin = word->yMin;
+    yMax = word->yMax;
+  } else {
+    if (word->xMin < xMin) {
+      xMin = word->xMin;
+    }
+    if (word->xMax > xMax) {
+      xMax = word->xMax;
+    }
+    if (word->yMin < yMin) {
+      yMin = word->yMin;
+    }
+    if (word->yMax > yMax) {
+      yMax = word->yMax;
+    }
+  }
+}
+
+double TextLine::primaryDelta(TextLine *line) {
+  double delta;
+
+  delta = 0; // make gcc happy
+  switch (rot) {
+  case 0:
+    delta = line->xMin - xMax;
+    break;
+  case 1:
+    delta = line->yMin - yMax;
+    break;
+  case 2:
+    delta = xMin - line->xMax;
+    break;
+  case 3:
+    delta = yMin - line->yMax;
+    break;
+  }
+  return delta;
+}
+
+int TextLine::primaryCmp(TextLine *line) {
+  double cmp;
+
+  cmp = 0; // make gcc happy
+  switch (rot) {
+  case 0:
+    cmp = xMin - line->xMin;
+    break;
+  case 1:
+    cmp = yMin - line->yMin;
+    break;
+  case 2:
+    cmp = line->xMax - xMax;
+    break;
+  case 3:
+    cmp = line->yMax - yMax;
+    break;
+  }
+  return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+}
+
+int TextLine::secondaryCmp(TextLine *line) {
+  double cmp;
+
+  cmp = (rot == 0 || rot == 3) ? base - line->base : line->base - base;
+  return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+}
+
+int TextLine::cmpYX(TextLine *line) {
+  int cmp;
+
+  if ((cmp = secondaryCmp(line))) {
+    return cmp;
+  }
+  return primaryCmp(line);
+}
+
+int TextLine::cmpXY(const void *p1, const void *p2) {
+  TextLine *line1 = *(TextLine **)p1;
+  TextLine *line2 = *(TextLine **)p2;
+  int cmp;
+
+  if ((cmp = line1->primaryCmp(line2))) {
+    return cmp;
+  }
+  return line1->secondaryCmp(line2);
+}
+
+void TextLine::coalesce(UnicodeMap *uMap) {
+  TextWord *word0, *word1;
+  double space, delta, minSpace;
+  GBool isUnicode;
+  char buf[8];
+  int i, j;
+
+  if (words->next) {
+
+    // compute the inter-word space threshold
+    if (words->len > 1 || words->next->len > 1) {
+      minSpace = 0;
+    } else {
+      minSpace = words->primaryDelta(words->next);
+      for (word0 = words->next, word1 = word0->next;
+	   word1 && minSpace > 0;
+	   word0 = word1, word1 = word0->next) {
+	if (word1->len > 1) {
+	  minSpace = 0;
+	}
+	delta = word0->primaryDelta(word1);
+	if (delta < minSpace) {
+	  minSpace = delta;
+	}
+      }
+    }
+    if (minSpace <= 0) {
+      space = maxCharSpacing * words->fontSize;
+    } else {
+      space = maxWideCharSpacingMul * minSpace;
+      if (space > maxWideCharSpacing * words->fontSize) {
+	space = maxWideCharSpacing * words->fontSize;
+      }
+    }
+
+    // merge words
+    word0 = words;
+    word1 = words->next;
+    while (word1) {
+      if (word0->primaryDelta(word1) >= space) {
+	word0->spaceAfter = gTrue;
+	word0 = word1;
+	word1 = word1->next;
+      } else if (word0->font == word1->font &&
+		 word0->underlined == word1->underlined &&
+		 fabs(word0->fontSize - word1->fontSize) <
+		   maxWordFontSizeDelta * words->fontSize &&
+		 word1->charPos == word0->charPos + word0->charLen) {
+	word0->merge(word1);
+	word0->next = word1->next;
+	delete word1;
+	word1 = word0->next;
+      } else {
+	word0 = word1;
+	word1 = word1->next;
+      }
+    }
+  }
+
+  // build the line text
+  isUnicode = uMap ? uMap->isUnicode() : gFalse;
+  len = 0;
+  for (word1 = words; word1; word1 = word1->next) {
+    len += word1->len;
+    if (word1->spaceAfter) {
+      ++len;
+    }
+  }
+  text = (Unicode *)gmallocn(len, sizeof(Unicode));
+  edge = (double *)gmallocn(len + 1, sizeof(double));
+  i = 0;
+  for (word1 = words; word1; word1 = word1->next) {
+    for (j = 0; j < word1->len; ++j) {
+      text[i] = word1->text[j];
+      edge[i] = word1->edge[j];
+      ++i;
+    }
+    edge[i] = word1->edge[word1->len];
+    if (word1->spaceAfter) {
+      text[i] = (Unicode)0x0020;
+      ++i;
+    }
+  }
+
+  // compute convertedLen and set up the col array
+  col = (int *)gmallocn(len + 1, sizeof(int));
+  convertedLen = 0;
+  for (i = 0; i < len; ++i) {
+    col[i] = convertedLen;
+    if (isUnicode) {
+      ++convertedLen;
+    } else if (uMap) {
+      convertedLen += uMap->mapUnicode(text[i], buf, sizeof(buf));
+    }
+  }
+  col[len] = convertedLen;
+
+  // check for hyphen at end of line
+  //~ need to check for other chars used as hyphens
+  hyphenated = text[len - 1] == (Unicode)'-';
+}
+
+//------------------------------------------------------------------------
+// TextLineFrag
+//------------------------------------------------------------------------
+
+class TextLineFrag {
+public:
+
+  TextLine *line;		// the line object
+  int start, len;		// offset and length of this fragment
+				//   (in Unicode chars)
+  double xMin, xMax;		// bounding box coordinates
+  double yMin, yMax;
+  double base;			// baseline virtual coordinate
+  int col;			// first column
+
+  void init(TextLine *lineA, int startA, int lenA);
+  void computeCoords(GBool oneRot);
+
+  static int cmpYXPrimaryRot(const void *p1, const void *p2);
+  static int cmpYXLineRot(const void *p1, const void *p2);
+  static int cmpXYLineRot(const void *p1, const void *p2);
+  static int cmpXYColumnPrimaryRot(const void *p1, const void *p2);
+  static int cmpXYColumnLineRot(const void *p1, const void *p2);
+};
+
+void TextLineFrag::init(TextLine *lineA, int startA, int lenA) {
+  line = lineA;
+  start = startA;
+  len = lenA;
+  col = line->col[start];
+}
+
+void TextLineFrag::computeCoords(GBool oneRot) {
+  TextBlock *blk;
+  double d0, d1, d2, d3, d4;
+
+  if (oneRot) {
+
+    switch (line->rot) {
+    case 0:
+      xMin = line->edge[start];
+      xMax = line->edge[start + len];
+      yMin = line->yMin;
+      yMax = line->yMax;
+      break;
+    case 1:
+      xMin = line->xMin;
+      xMax = line->xMax;
+      yMin = line->edge[start];
+      yMax = line->edge[start + len];
+      break;
+    case 2:
+      xMin = line->edge[start + len];
+      xMax = line->edge[start];
+      yMin = line->yMin;
+      yMax = line->yMax;
+      break;
+    case 3:
+      xMin = line->xMin;
+      xMax = line->xMax;
+      yMin = line->edge[start + len];
+      yMax = line->edge[start];
+      break;
+    }
+    base = line->base;
+
+  } else {
+
+    if (line->rot == 0 && line->blk->page->primaryRot == 0) {
+
+      xMin = line->edge[start];
+      xMax = line->edge[start + len];
+      yMin = line->yMin;
+      yMax = line->yMax;
+      base = line->base;
+
+    } else {
+
+      blk = line->blk;
+      d0 = line->edge[start];
+      d1 = line->edge[start + len];
+      d2 = d3 = d4 = 0; // make gcc happy
+
+      switch (line->rot) {
+      case 0:
+	d2 = line->yMin;
+	d3 = line->yMax;
+	d4 = line->base;
+	d0 = (d0 - blk->xMin) / (blk->xMax - blk->xMin);
+	d1 = (d1 - blk->xMin) / (blk->xMax - blk->xMin);
+	d2 = (d2 - blk->yMin) / (blk->yMax - blk->yMin);
+	d3 = (d3 - blk->yMin) / (blk->yMax - blk->yMin);
+	d4 = (d4 - blk->yMin) / (blk->yMax - blk->yMin);
+	break;
+      case 1:
+	d2 = line->xMax;
+	d3 = line->xMin;
+	d4 = line->base;
+	d0 = (d0 - blk->yMin) / (blk->yMax - blk->yMin);
+	d1 = (d1 - blk->yMin) / (blk->yMax - blk->yMin);
+	d2 = (blk->xMax - d2) / (blk->xMax - blk->xMin);
+	d3 = (blk->xMax - d3) / (blk->xMax - blk->xMin);
+	d4 = (blk->xMax - d4) / (blk->xMax - blk->xMin);
+	break;
+      case 2:
+	d2 = line->yMax;
+	d3 = line->yMin;
+	d4 = line->base;
+	d0 = (blk->xMax - d0) / (blk->xMax - blk->xMin);
+	d1 = (blk->xMax - d1) / (blk->xMax - blk->xMin);
+	d2 = (blk->yMax - d2) / (blk->yMax - blk->yMin);
+	d3 = (blk->yMax - d3) / (blk->yMax - blk->yMin);
+	d4 = (blk->yMax - d4) / (blk->yMax - blk->yMin);
+	break;
+      case 3:
+	d2 = line->xMin;
+	d3 = line->xMax;
+	d4 = line->base;
+	d0 = (blk->yMax - d0) / (blk->yMax - blk->yMin);
+	d1 = (blk->yMax - d1) / (blk->yMax - blk->yMin);
+	d2 = (d2 - blk->xMin) / (blk->xMax - blk->xMin);
+	d3 = (d3 - blk->xMin) / (blk->xMax - blk->xMin);
+	d4 = (d4 - blk->xMin) / (blk->xMax - blk->xMin);
+	break;
+      }
+
+      switch (line->blk->page->primaryRot) {
+      case 0:
+	xMin = blk->xMin + d0 * (blk->xMax - blk->xMin);
+	xMax = blk->xMin + d1 * (blk->xMax - blk->xMin);
+	yMin = blk->yMin + d2 * (blk->yMax - blk->yMin);
+	yMax = blk->yMin + d3 * (blk->yMax - blk->yMin);
+	base = blk->yMin + base * (blk->yMax - blk->yMin);
+	break;
+      case 1:
+	xMin = blk->xMax - d3 * (blk->xMax - blk->xMin);
+	xMax = blk->xMax - d2 * (blk->xMax - blk->xMin);
+	yMin = blk->yMin + d0 * (blk->yMax - blk->yMin);
+	yMax = blk->yMin + d1 * (blk->yMax - blk->yMin);
+	base = blk->xMax - d4 * (blk->xMax - blk->xMin);
+	break;
+      case 2:
+	xMin = blk->xMax - d1 * (blk->xMax - blk->xMin);
+	xMax = blk->xMax - d0 * (blk->xMax - blk->xMin);
+	yMin = blk->yMax - d3 * (blk->yMax - blk->yMin);
+	yMax = blk->yMax - d2 * (blk->yMax - blk->yMin);
+	base = blk->yMax - d4 * (blk->yMax - blk->yMin);
+	break;
+      case 3:
+	xMin = blk->xMin + d2 * (blk->xMax - blk->xMin);
+	xMax = blk->xMin + d3 * (blk->xMax - blk->xMin);
+	yMin = blk->yMax - d1 * (blk->yMax - blk->yMin);
+	yMax = blk->yMax - d0 * (blk->yMax - blk->yMin);
+	base = blk->xMin + d4 * (blk->xMax - blk->xMin);
+	break;
+      }
+
+    }
+  }
+}
+
+int TextLineFrag::cmpYXPrimaryRot(const void *p1, const void *p2) {
+  TextLineFrag *frag1 = (TextLineFrag *)p1;
+  TextLineFrag *frag2 = (TextLineFrag *)p2;
+  double cmp;
+
+  cmp = 0; // make gcc happy
+  switch (frag1->line->blk->page->primaryRot) {
+  case 0:
+    if (fabs(cmp = frag1->yMin - frag2->yMin) < 0.01) {
+      cmp = frag1->xMin - frag2->xMin;
+    }
+    break;
+  case 1:
+    if (fabs(cmp = frag2->xMax - frag1->xMax) < 0.01) {
+      cmp = frag1->yMin - frag2->yMin;
+    }
+    break;
+  case 2:
+    if (fabs(cmp = frag2->yMin - frag1->yMin) < 0.01) {
+      cmp = frag2->xMax - frag1->xMax;
+    }
+    break;
+  case 3:
+    if (fabs(cmp = frag1->xMax - frag2->xMax) < 0.01) {
+      cmp = frag2->yMax - frag1->yMax;
+    }
+    break;
+  }
+  return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+}
+
+int TextLineFrag::cmpYXLineRot(const void *p1, const void *p2) {
+  TextLineFrag *frag1 = (TextLineFrag *)p1;
+  TextLineFrag *frag2 = (TextLineFrag *)p2;
+  double cmp;
+
+  cmp = 0; // make gcc happy
+  switch (frag1->line->rot) {
+  case 0:
+    if ((cmp = frag1->yMin - frag2->yMin) == 0) {
+      cmp = frag1->xMin - frag2->xMin;
+    }
+    break;
+  case 1:
+    if ((cmp = frag2->xMax - frag1->xMax) == 0) {
+      cmp = frag1->yMin - frag2->yMin;
+    }
+    break;
+  case 2:
+    if ((cmp = frag2->yMin - frag1->yMin) == 0) {
+      cmp = frag2->xMax - frag1->xMax;
+    }
+    break;
+  case 3:
+    if ((cmp = frag1->xMax - frag2->xMax) == 0) {
+      cmp = frag2->yMax - frag1->yMax;
+    }
+    break;
+  }
+  return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+}
+
+int TextLineFrag::cmpXYLineRot(const void *p1, const void *p2) {
+  TextLineFrag *frag1 = (TextLineFrag *)p1;
+  TextLineFrag *frag2 = (TextLineFrag *)p2;
+  double cmp;
+
+  cmp = 0; // make gcc happy
+  switch (frag1->line->rot) {
+  case 0:
+    if ((cmp = frag1->xMin - frag2->xMin) == 0) {
+      cmp = frag1->yMin - frag2->yMin;
+    }
+    break;
+  case 1:
+    if ((cmp = frag1->yMin - frag2->yMin) == 0) {
+      cmp = frag2->xMax - frag1->xMax;
+    }
+    break;
+  case 2:
+    if ((cmp = frag2->xMax - frag1->xMax) == 0) {
+      cmp = frag2->yMin - frag1->yMin;
+    }
+    break;
+  case 3:
+    if ((cmp = frag2->yMax - frag1->yMax) == 0) {
+      cmp = frag1->xMax - frag2->xMax;
+    }
+    break;
+  }
+  return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+}
+
+int TextLineFrag::cmpXYColumnPrimaryRot(const void *p1, const void *p2) {
+  TextLineFrag *frag1 = (TextLineFrag *)p1;
+  TextLineFrag *frag2 = (TextLineFrag *)p2;
+  double cmp;
+
+  // if columns overlap, compare y values
+  if (frag1->col < frag2->col + (frag2->line->col[frag2->start + frag2->len] -
+				 frag2->line->col[frag2->start]) &&
+      frag2->col < frag1->col + (frag1->line->col[frag1->start + frag1->len] -
+				 frag1->line->col[frag1->start])) {
+    cmp = 0; // make gcc happy
+    switch (frag1->line->blk->page->primaryRot) {
+    case 0: cmp = frag1->yMin - frag2->yMin; break;
+    case 1: cmp = frag2->xMax - frag1->xMax; break;
+    case 2: cmp = frag2->yMin - frag1->yMin; break;
+    case 3: cmp = frag1->xMax - frag2->xMax; break;
+    }
+    return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+  }
+
+  // otherwise, compare starting column
+  return frag1->col - frag2->col;
+}
+
+int TextLineFrag::cmpXYColumnLineRot(const void *p1, const void *p2) {
+  TextLineFrag *frag1 = (TextLineFrag *)p1;
+  TextLineFrag *frag2 = (TextLineFrag *)p2;
+  double cmp;
+
+  // if columns overlap, compare y values
+  if (frag1->col < frag2->col + (frag2->line->col[frag2->start + frag2->len] -
+				 frag2->line->col[frag2->start]) &&
+      frag2->col < frag1->col + (frag1->line->col[frag1->start + frag1->len] -
+				 frag1->line->col[frag1->start])) {
+    cmp = 0; // make gcc happy
+    switch (frag1->line->rot) {
+    case 0: cmp = frag1->yMin - frag2->yMin; break;
+    case 1: cmp = frag2->xMax - frag1->xMax; break;
+    case 2: cmp = frag2->yMin - frag1->yMin; break;
+    case 3: cmp = frag1->xMax - frag2->xMax; break;
+    }
+    return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+  }
+
+  // otherwise, compare starting column
+  return frag1->col - frag2->col;
+}
+
+//------------------------------------------------------------------------
+// TextBlock
+//------------------------------------------------------------------------
+
+TextBlock::TextBlock(TextPage *pageA, int rotA) {
+  page = pageA;
+  rot = rotA;
+  xMin = yMin = 0;
+  xMax = yMax = -1;
+  priMin = 0;
+  priMax = page->pageWidth;
+  pool = new TextPool();
+  lines = NULL;
+  curLine = NULL;
+  next = NULL;
+  stackNext = NULL;
+}
+
+TextBlock::~TextBlock() {
+  TextLine *line;
+
+  delete pool;
+  while (lines) {
+    line = lines;
+    lines = lines->next;
+    delete line;
+  }
+}
+
+void TextBlock::addWord(TextWord *word) {
+  pool->addWord(word);
+  if (xMin > xMax) {
+    xMin = word->xMin;
+    xMax = word->xMax;
+    yMin = word->yMin;
+    yMax = word->yMax;
+  } else {
+    if (word->xMin < xMin) {
+      xMin = word->xMin;
+    }
+    if (word->xMax > xMax) {
+      xMax = word->xMax;
+    }
+    if (word->yMin < yMin) {
+      yMin = word->yMin;
+    }
+    if (word->yMax > yMax) {
+      yMax = word->yMax;
+    }
+  }
+}
+
+void TextBlock::coalesce(UnicodeMap *uMap) {
+  TextWord *word0, *word1, *word2, *bestWord0, *bestWord1, *lastWord;
+  TextLine *line, *line0, *line1;
+  int poolMinBaseIdx, startBaseIdx, minBaseIdx, maxBaseIdx;
+  int baseIdx, bestWordBaseIdx, idx0, idx1;
+  double minBase, maxBase;
+  double fontSize, delta, priDelta, secDelta;
+  TextLine **lineArray;
+  GBool found;
+  int col1, col2;
+  int i, j, k;
+
+  // discard duplicated text (fake boldface, drop shadows)
+  for (idx0 = pool->minBaseIdx; idx0 <= pool->maxBaseIdx; ++idx0) {
+    word0 = pool->getPool(idx0);
+    while (word0) {
+      priDelta = dupMaxPriDelta * word0->fontSize;
+      secDelta = dupMaxSecDelta * word0->fontSize;
+      if (rot == 0 || rot == 3) {
+	maxBaseIdx = pool->getBaseIdx(word0->base + secDelta);
+      } else {
+	maxBaseIdx = pool->getBaseIdx(word0->base - secDelta);
+      }
+      found = gFalse;
+      word1 = word2 = NULL; // make gcc happy
+      for (idx1 = idx0; idx1 <= maxBaseIdx; ++idx1) {
+	if (idx1 == idx0) {
+	  word1 = word0;
+	  word2 = word0->next;
+	} else {
+	  word1 = NULL;
+	  word2 = pool->getPool(idx1);
+	}
+	for (; word2; word1 = word2, word2 = word2->next) {
+	  if (word2->len == word0->len &&
+	      !memcmp(word2->text, word0->text,
+		      word0->len * sizeof(Unicode))) {
+	    switch (rot) {
+	    case 0:
+	    case 2:
+	      found = fabs(word0->xMin - word2->xMin) < priDelta &&
+		      fabs(word0->xMax - word2->xMax) < priDelta &&
+		      fabs(word0->yMin - word2->yMin) < secDelta &&
+		      fabs(word0->yMax - word2->yMax) < secDelta;
+	      break;
+	    case 1:
+	    case 3:
+	      found = fabs(word0->xMin - word2->xMin) < secDelta &&
+		      fabs(word0->xMax - word2->xMax) < secDelta &&
+		      fabs(word0->yMin - word2->yMin) < priDelta &&
+		      fabs(word0->yMax - word2->yMax) < priDelta;
+	      break;
+	    }
+	  }
+	  if (found) {
+	    break;
+	  }
+	}
+	if (found) {
+	  break;
+	}
+      }
+      if (found) {
+	if (word1) {
+	  word1->next = word2->next;
+	} else {
+	  pool->setPool(idx1, word2->next);
+	}
+	delete word2;
+      } else {
+	word0 = word0->next;
+      }
+    }
+  }
+
+  // build the lines
+  curLine = NULL;
+  poolMinBaseIdx = pool->minBaseIdx;
+  charCount = 0;
+  nLines = 0;
+  while (1) {
+
+    // find the first non-empty line in the pool
+    for (;
+	 poolMinBaseIdx <= pool->maxBaseIdx && !pool->getPool(poolMinBaseIdx);
+	 ++poolMinBaseIdx) ;
+    if (poolMinBaseIdx > pool->maxBaseIdx) {
+      break;
+    }
+
+    // look for the left-most word in the first four lines of the
+    // pool -- this avoids starting with a superscript word
+    startBaseIdx = poolMinBaseIdx;
+    for (baseIdx = poolMinBaseIdx + 1;
+	 baseIdx < poolMinBaseIdx + 4 && baseIdx <= pool->maxBaseIdx;
+	 ++baseIdx) {
+      if (!pool->getPool(baseIdx)) {
+	continue;
+      }
+      if (pool->getPool(baseIdx)->primaryCmp(pool->getPool(startBaseIdx))
+	  < 0) {
+	startBaseIdx = baseIdx;
+      }
+    }
+
+    // create a new line
+    word0 = pool->getPool(startBaseIdx);
+    pool->setPool(startBaseIdx, word0->next);
+    word0->next = NULL;
+    line = new TextLine(this, word0->rot, word0->base);
+    line->addWord(word0);
+    lastWord = word0;
+
+    // compute the search range
+    fontSize = word0->fontSize;
+    minBase = word0->base - maxIntraLineDelta * fontSize;
+    maxBase = word0->base + maxIntraLineDelta * fontSize;
+    minBaseIdx = pool->getBaseIdx(minBase);
+    maxBaseIdx = pool->getBaseIdx(maxBase);
+
+    // find the rest of the words in this line
+    while (1) {
+
+      // find the left-most word whose baseline is in the range for
+      // this line
+      bestWordBaseIdx = 0;
+      bestWord0 = bestWord1 = NULL;
+      for (baseIdx = minBaseIdx; baseIdx <= maxBaseIdx; ++baseIdx) {
+	for (word0 = NULL, word1 = pool->getPool(baseIdx);
+	     word1;
+	     word0 = word1, word1 = word1->next) {
+	  if (word1->base >= minBase &&
+	      word1->base <= maxBase &&
+	      (delta = lastWord->primaryDelta(word1)) >=
+	        minCharSpacing * fontSize) {
+	    if (delta < maxWordSpacing * fontSize &&
+		(!bestWord1 || word1->primaryCmp(bestWord1) < 0)) {
+	      bestWordBaseIdx = baseIdx;
+	      bestWord0 = word0;
+	      bestWord1 = word1;
+	    }
+	    break;
+	  }
+	}
+      }
+      if (!bestWord1) {
+	break;
+      }
+
+      // remove it from the pool, and add it to the line
+      if (bestWord0) {
+	bestWord0->next = bestWord1->next;
+      } else {
+	pool->setPool(bestWordBaseIdx, bestWord1->next);
+      }
+      bestWord1->next = NULL;
+      line->addWord(bestWord1);
+      lastWord = bestWord1;
+    }
+
+    // add the line
+    if (curLine && line->cmpYX(curLine) > 0) {
+      line0 = curLine;
+      line1 = curLine->next;
+    } else {
+      line0 = NULL;
+      line1 = lines;
+    }
+    for (;
+	 line1 && line->cmpYX(line1) > 0;
+	 line0 = line1, line1 = line1->next) ;
+    if (line0) {
+      line0->next = line;
+    } else {
+      lines = line;
+    }
+    line->next = line1;
+    curLine = line;
+    line->coalesce(uMap);
+    charCount += line->len;
+    ++nLines;
+  }
+
+  // sort lines into xy order for column assignment
+  lineArray = (TextLine **)gmallocn(nLines, sizeof(TextLine *));
+  for (line = lines, i = 0; line; line = line->next, ++i) {
+    lineArray[i] = line;
+  }
+  qsort(lineArray, nLines, sizeof(TextLine *), &TextLine::cmpXY);
+
+  // column assignment
+  nColumns = 0;
+  for (i = 0; i < nLines; ++i) {
+    line0 = lineArray[i];
+    col1 = 0;
+    for (j = 0; j < i; ++j) {
+      line1 = lineArray[j];
+      if (line1->primaryDelta(line0) >= 0) {
+	col2 = line1->col[line1->len] + 1;
+      } else {
+	k = 0; // make gcc happy
+	switch (rot) {
+	case 0:
+	  for (k = 0;
+	       k < line1->len &&
+		 line0->xMin >= 0.5 * (line1->edge[k] + line1->edge[k+1]);
+	       ++k) ;
+	  break;
+	case 1:
+	  for (k = 0;
+	       k < line1->len &&
+		 line0->yMin >= 0.5 * (line1->edge[k] + line1->edge[k+1]);
+	       ++k) ;
+	  break;
+	case 2:
+	  for (k = 0;
+	       k < line1->len &&
+		 line0->xMax <= 0.5 * (line1->edge[k] + line1->edge[k+1]);
+	       ++k) ;
+	  break;
+	case 3:
+	  for (k = 0;
+	       k < line1->len &&
+		 line0->yMax <= 0.5 * (line1->edge[k] + line1->edge[k+1]);
+	       ++k) ;
+	  break;
+	}
+	col2 = line1->col[k];
+      }
+      if (col2 > col1) {
+	col1 = col2;
+      }
+    }
+    for (k = 0; k <= line0->len; ++k) {
+      line0->col[k] += col1;
+    }
+    if (line0->col[line0->len] > nColumns) {
+      nColumns = line0->col[line0->len];
+    }
+  }
+  gfree(lineArray);
+}
+
+void TextBlock::updatePriMinMax(TextBlock *blk) {
+  double newPriMin, newPriMax;
+  GBool gotPriMin, gotPriMax;
+
+  gotPriMin = gotPriMax = gFalse;
+  newPriMin = newPriMax = 0; // make gcc happy
+  switch (page->primaryRot) {
+  case 0:
+  case 2:
+    if (blk->yMin < yMax && blk->yMax > yMin) {
+      if (blk->xMin < xMin) {
+	newPriMin = blk->xMax;
+	gotPriMin = gTrue;
+      }
+      if (blk->xMax > xMax) {
+	newPriMax = blk->xMin;
+	gotPriMax = gTrue;
+      }
+    }
+    break;
+  case 1:
+  case 3:
+    if (blk->xMin < xMax && blk->xMax > xMin) {
+      if (blk->yMin < yMin) {
+	newPriMin = blk->yMax;
+	gotPriMin = gTrue;
+      }
+      if (blk->yMax > yMax) {
+	newPriMax = blk->yMin;
+	gotPriMax = gTrue;
+      }
+    }
+    break;
+  }
+  if (gotPriMin) {
+    if (newPriMin > xMin) {
+      newPriMin = xMin;
+    }
+    if (newPriMin > priMin) {
+      priMin = newPriMin;
+    }
+  }
+  if (gotPriMax) {
+    if (newPriMax < xMax) {
+      newPriMax = xMax;
+    }
+    if (newPriMax < priMax) {
+      priMax = newPriMax;
+    }
+  }
+}
+
+int TextBlock::cmpXYPrimaryRot(const void *p1, const void *p2) {
+  TextBlock *blk1 = *(TextBlock **)p1;
+  TextBlock *blk2 = *(TextBlock **)p2;
+  double cmp;
+
+  cmp = 0; // make gcc happy
+  switch (blk1->page->primaryRot) {
+  case 0:
+    if ((cmp = blk1->xMin - blk2->xMin) == 0) {
+      cmp = blk1->yMin - blk2->yMin;
+    }
+    break;
+  case 1:
+    if ((cmp = blk1->yMin - blk2->yMin) == 0) {
+      cmp = blk2->xMax - blk1->xMax;
+    }
+    break;
+  case 2:
+    if ((cmp = blk2->xMax - blk1->xMax) == 0) {
+      cmp = blk2->yMin - blk1->yMin;
+    }
+    break;
+  case 3:
+    if ((cmp = blk2->yMax - blk1->yMax) == 0) {
+      cmp = blk1->xMax - blk2->xMax;
+    }
+    break;
+  }
+  return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+}
+
+int TextBlock::cmpYXPrimaryRot(const void *p1, const void *p2) {
+  TextBlock *blk1 = *(TextBlock **)p1;
+  TextBlock *blk2 = *(TextBlock **)p2;
+  double cmp;
+
+  cmp = 0; // make gcc happy
+  switch (blk1->page->primaryRot) {
+  case 0:
+    if ((cmp = blk1->yMin - blk2->yMin) == 0) {
+      cmp = blk1->xMin - blk2->xMin;
+    }
+    break;
+  case 1:
+    if ((cmp = blk2->xMax - blk1->xMax) == 0) {
+      cmp = blk1->yMin - blk2->yMin;
+    }
+    break;
+  case 2:
+    if ((cmp = blk2->yMin - blk1->yMin) == 0) {
+      cmp = blk2->xMax - blk1->xMax;
+    }
+    break;
+  case 3:
+    if ((cmp = blk1->xMax - blk2->xMax) == 0) {
+      cmp = blk2->yMax - blk1->yMax;
+    }
+    break;
+  }
+  return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+}
+
+int TextBlock::primaryCmp(TextBlock *blk) {
+  double cmp;
+
+  cmp = 0; // make gcc happy
+  switch (rot) {
+  case 0:
+    cmp = xMin - blk->xMin;
+    break;
+  case 1:
+    cmp = yMin - blk->yMin;
+    break;
+  case 2:
+    cmp = blk->xMax - xMax;
+    break;
+  case 3:
+    cmp = blk->yMax - yMax;
+    break;
+  }
+  return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
+}
+
+double TextBlock::secondaryDelta(TextBlock *blk) {
+  double delta;
+
+  delta = 0; // make gcc happy
+  switch (rot) {
+  case 0:
+    delta = blk->yMin - yMax;
+    break;
+  case 1:
+    delta = xMin - blk->xMax;
+    break;
+  case 2:
+    delta = yMin - blk->yMax;
+    break;
+  case 3:
+    delta = blk->xMin - xMax;
+    break;
+  }
+  return delta;
+}
+
+GBool TextBlock::isBelow(TextBlock *blk) {
+  GBool below;
+
+  below = gFalse; // make gcc happy
+  switch (page->primaryRot) {
+  case 0:
+    below = xMin >= blk->priMin && xMax <= blk->priMax &&
+            yMin > blk->yMin;
+    break;
+  case 1:
+    below = yMin >= blk->priMin && yMax <= blk->priMax &&
+            xMax < blk->xMax;
+    break;
+  case 2:
+    below = xMin >= blk->priMin && xMax <= blk->priMax &&
+            yMax < blk->yMax;
+    break;
+  case 3:
+    below = yMin >= blk->priMin && yMax <= blk->priMax &&
+            xMin > blk->xMin;
+    break;
+  }
+
+  return below;
+}
+
+//------------------------------------------------------------------------
+// TextFlow
+//------------------------------------------------------------------------
+
+TextFlow::TextFlow(TextPage *pageA, TextBlock *blk) {
+  page = pageA;
+  xMin = blk->xMin;
+  xMax = blk->xMax;
+  yMin = blk->yMin;
+  yMax = blk->yMax;
+  priMin = blk->priMin;
+  priMax = blk->priMax;
+  blocks = lastBlk = blk;
+  next = NULL;
+}
+
+TextFlow::~TextFlow() {
+  TextBlock *blk;
+
+  while (blocks) {
+    blk = blocks;
+    blocks = blocks->next;
+    delete blk;
+  }
+}
+
+void TextFlow::addBlock(TextBlock *blk) {
+  if (lastBlk) {
+    lastBlk->next = blk;
+  } else {
+    blocks = blk;
+  }
+  lastBlk = blk;
+  if (blk->xMin < xMin) {
+    xMin = blk->xMin;
+  }
+  if (blk->xMax > xMax) {
+    xMax = blk->xMax;
+  }
+  if (blk->yMin < yMin) {
+    yMin = blk->yMin;
+  }
+  if (blk->yMax > yMax) {
+    yMax = blk->yMax;
+  }
+}
+
+GBool TextFlow::blockFits(TextBlock *blk, TextBlock *prevBlk) {
+  GBool fits;
+
+  // lower blocks must use smaller fonts
+  if (blk->lines->words->fontSize > lastBlk->lines->words->fontSize) {
+    return gFalse;
+  }
+
+  fits = gFalse; // make gcc happy
+  switch (page->primaryRot) {
+  case 0:
+    fits = blk->xMin >= priMin && blk->xMax <= priMax;
+    break;
+  case 1:
+    fits = blk->yMin >= priMin && blk->yMax <= priMax;
+    break;
+  case 2:
+    fits = blk->xMin >= priMin && blk->xMax <= priMax;
+    break;
+  case 3:
+    fits = blk->yMin >= priMin && blk->yMax <= priMax;
+    break;
+  }
+  return fits;
+}
+
+#if TEXTOUT_WORD_LIST
+
+//------------------------------------------------------------------------
+// TextWordList
+//------------------------------------------------------------------------
+
+TextWordList::TextWordList(TextPage *text, GBool physLayout) {
+  TextFlow *flow;
+  TextBlock *blk;
+  TextLine *line;
+  TextWord *word;
+  TextWord **wordArray;
+  int nWords, i;
+
+  words = new GList();
+
+  if (text->rawOrder) {
+    for (word = text->rawWords; word; word = word->next) {
+      words->append(word);
+    }
+
+  } else if (physLayout) {
+    // this is inefficient, but it's also the least useful of these
+    // three cases
+    nWords = 0;
+    for (flow = text->flows; flow; flow = flow->next) {
+      for (blk = flow->blocks; blk; blk = blk->next) {
+	for (line = blk->lines; line; line = line->next) {
+	  for (word = line->words; word; word = word->next) {
+	    ++nWords;
+	  }
+	}
+      }
+    }
+    wordArray = (TextWord **)gmallocn(nWords, sizeof(TextWord *));
+    i = 0;
+    for (flow = text->flows; flow; flow = flow->next) {
+      for (blk = flow->blocks; blk; blk = blk->next) {
+	for (line = blk->lines; line; line = line->next) {
+	  for (word = line->words; word; word = word->next) {
+	    wordArray[i++] = word;
+	  }
+	}
+      }
+    }
+    qsort(wordArray, nWords, sizeof(TextWord *), &TextWord::cmpYX);
+    for (i = 0; i < nWords; ++i) {
+      words->append(wordArray[i]);
+    }
+    gfree(wordArray);
+
+  } else {
+    for (flow = text->flows; flow; flow = flow->next) {
+      for (blk = flow->blocks; blk; blk = blk->next) {
+	for (line = blk->lines; line; line = line->next) {
+	  for (word = line->words; word; word = word->next) {
+	    words->append(word);
+	  }
+	}
+      }
+    }
+  }
+}
+
+TextWordList::~TextWordList() {
+  delete words;
+}
+
+int TextWordList::getLength() {
+  return words->getLength();
+}
+
+TextWord *TextWordList::get(int idx) {
+  if (idx < 0 || idx >= words->getLength()) {
+    return NULL;
+  }
+  return (TextWord *)words->get(idx);
+}
+
+#endif // TEXTOUT_WORD_LIST
+
+//------------------------------------------------------------------------
+// TextPage
+//------------------------------------------------------------------------
+
+TextPage::TextPage(GBool rawOrderA) {
+  int rot;
+
+  rawOrder = rawOrderA;
+  curWord = NULL;
+  charPos = 0;
+  curFont = NULL;
+  curFontSize = 0;
+  nest = 0;
+  nTinyChars = 0;
+  lastCharOverlap = gFalse;
+  if (!rawOrder) {
+    for (rot = 0; rot < 4; ++rot) {
+      pools[rot] = new TextPool();
+    }
+  }
+  flows = NULL;
+  blocks = NULL;
+  rawWords = NULL;
+  rawLastWord = NULL;
+  fonts = new GList();
+  lastFindXMin = lastFindYMin = 0;
+  haveLastFind = gFalse;
+  underlines = new GList();
+  links = new GList();
+}
+
+TextPage::~TextPage() {
+  int rot;
+
+  clear();
+  if (!rawOrder) {
+    for (rot = 0; rot < 4; ++rot) {
+      delete pools[rot];
+    }
+  }
+  delete fonts;
+  deleteGList(underlines, TextUnderline);
+  deleteGList(links, TextLink);
+}
+
+void TextPage::startPage(GfxState *state) {
+  clear();
+  if (state) {
+    pageWidth = state->getPageWidth();
+    pageHeight = state->getPageHeight();
+  } else {
+    pageWidth = pageHeight = 0;
+  }
+}
+
+void TextPage::endPage() {
+  if (curWord) {
+    endWord();
+  }
+}
+
+void TextPage::clear() {
+  int rot;
+  TextFlow *flow;
+  TextWord *word;
+
+  if (curWord) {
+    delete curWord;
+    curWord = NULL;
+  }
+  if (rawOrder) {
+    while (rawWords) {
+      word = rawWords;
+      rawWords = rawWords->next;
+      delete word;
+    }
+  } else {
+    for (rot = 0; rot < 4; ++rot) {
+      delete pools[rot];
+    }
+    while (flows) {
+      flow = flows;
+      flows = flows->next;
+      delete flow;
+    }
+    gfree(blocks);
+  }
+  deleteGList(fonts, TextFontInfo);
+
+  curWord = NULL;
+  charPos = 0;
+  curFont = NULL;
+  curFontSize = 0;
+  nest = 0;
+  nTinyChars = 0;
+  if (!rawOrder) {
+    for (rot = 0; rot < 4; ++rot) {
+      pools[rot] = new TextPool();
+    }
+  }
+  flows = NULL;
+  blocks = NULL;
+  rawWords = NULL;
+  rawLastWord = NULL;
+  fonts = new GList();
+}
+
+void TextPage::updateFont(GfxState *state) {
+  GfxFont *gfxFont;
+  double *fm;
+  char *name;
+  int code, mCode, letterCode, anyCode;
+  double w;
+  int i;
+
+  // get the font info object
+  curFont = NULL;
+  for (i = 0; i < fonts->getLength(); ++i) {
+    curFont = (TextFontInfo *)fonts->get(i);
+    if (curFont->matches(state)) {
+      break;
+    }
+    curFont = NULL;
+  }
+  if (!curFont) {
+    curFont = new TextFontInfo(state);
+    fonts->append(curFont);
+  }
+
+  // adjust the font size
+  gfxFont = state->getFont();
+  curFontSize = state->getTransformedFontSize();
+  if (gfxFont && gfxFont->getType() == fontType3) {
+    // This is a hack which makes it possible to deal with some Type 3
+    // fonts.  The problem is that it's impossible to know what the
+    // base coordinate system used in the font is without actually
+    // rendering the font.  This code tries to guess by looking at the
+    // width of the character 'm' (which breaks if the font is a
+    // subset that doesn't contain 'm').
+    mCode = letterCode = anyCode = -1;
+    for (code = 0; code < 256; ++code) {
+      name = ((Gfx8BitFont *)gfxFont)->getCharName(code);
+      if (name && name[0] == 'm' && name[1] == '\0') {
+	mCode = code;
+      }
+      if (letterCode < 0 && name && name[1] == '\0' &&
+	  ((name[0] >= 'A' && name[0] <= 'Z') ||
+	   (name[0] >= 'a' && name[0] <= 'z'))) {
+	letterCode = code;
+      }
+      if (anyCode < 0 && name &&
+	  ((Gfx8BitFont *)gfxFont)->getWidth(code) > 0) {
+	anyCode = code;
+      }
+    }
+    if (mCode >= 0 &&
+	(w = ((Gfx8BitFont *)gfxFont)->getWidth(mCode)) > 0) {
+      // 0.6 is a generic average 'm' width -- yes, this is a hack
+      curFontSize *= w / 0.6;
+    } else if (letterCode >= 0 &&
+	       (w = ((Gfx8BitFont *)gfxFont)->getWidth(letterCode)) > 0) {
+      // even more of a hack: 0.5 is a generic letter width
+      curFontSize *= w / 0.5;
+    } else if (anyCode >= 0 &&
+	       (w = ((Gfx8BitFont *)gfxFont)->getWidth(anyCode)) > 0) {
+      // better than nothing: 0.5 is a generic character width
+      curFontSize *= w / 0.5;
+    }
+    fm = gfxFont->getFontMatrix();
+    if (fm[0] != 0) {
+      curFontSize *= fabs(fm[3] / fm[0]);
+    }
+  }
+}
+
+void TextPage::beginWord(GfxState *state, double x0, double y0) {
+  double *fontm;
+  double m[4], m2[4];
+  int rot;
+
+  // This check is needed because Type 3 characters can contain
+  // text-drawing operations (when TextPage is being used via
+  // {X,Win}SplashOutputDev rather than TextOutputDev).
+  if (curWord) {
+    ++nest;
+    return;
+  }
+
+  // compute the rotation
+  state->getFontTransMat(&m[0], &m[1], &m[2], &m[3]);
+  if (state->getFont()->getType() == fontType3) {
+    fontm = state->getFont()->getFontMatrix();
+    m2[0] = fontm[0] * m[0] + fontm[1] * m[2];
+    m2[1] = fontm[0] * m[1] + fontm[1] * m[3];
+    m2[2] = fontm[2] * m[0] + fontm[3] * m[2];
+    m2[3] = fontm[2] * m[1] + fontm[3] * m[3];
+    m[0] = m2[0];
+    m[1] = m2[1];
+    m[2] = m2[2];
+    m[3] = m2[3];
+  }
+  if (fabs(m[0] * m[3]) > fabs(m[1] * m[2])) {
+    rot = (m[3] < 0) ? 0 : 2;
+  } else {
+    rot = (m[2] > 0) ? 1 : 3;
+  }
+
+  curWord = new TextWord(state, rot, x0, y0, charPos, curFont, curFontSize);
+}
+
+void TextPage::addChar(GfxState *state, double x, double y,
+		       double dx, double dy,
+		       CharCode c, int nBytes, Unicode *u, int uLen) {
+  double x1, y1, w1, h1, dx2, dy2, base, sp, delta;
+  GBool overlap;
+  int i;
+
+  // subtract char and word spacing from the dx,dy values
+  sp = state->getCharSpace();
+  if (c == (CharCode)0x20) {
+    sp += state->getWordSpace();
+  }
+  state->textTransformDelta(sp * state->getHorizScaling(), 0, &dx2, &dy2);
+  dx -= dx2;
+  dy -= dy2;
+  state->transformDelta(dx, dy, &w1, &h1);
+
+  // throw away chars that aren't inside the page bounds
+  // (and also do a sanity check on the character size)
+  state->transform(x, y, &x1, &y1);
+  if (x1 + w1 < 0 || x1 > pageWidth ||
+      y1 + h1 < 0 || y1 > pageHeight ||
+      w1 > pageWidth || h1 > pageHeight) {
+    charPos += nBytes;
+    return;
+  }
+
+  // check the tiny chars limit
+  if (!globalParams->getTextKeepTinyChars() &&
+      fabs(w1) < 3 && fabs(h1) < 3) {
+    if (++nTinyChars > 50000) {
+      charPos += nBytes;
+      return;
+    }
+  }
+
+  // break words at space character
+  if (uLen == 1 && u[0] == (Unicode)0x20) {
+    if (curWord) {
+      ++curWord->charLen;
+    }
+    charPos += nBytes;
+    endWord();
+    return;
+  }
+
+  // start a new word if:
+  // (1) this character doesn't fall in the right place relative to
+  //     the end of the previous word (this places upper and lower
+  //     constraints on the position deltas along both the primary
+  //     and secondary axes), or
+  // (2) this character overlaps the previous one (duplicated text), or
+  // (3) the previous character was an overlap (we want each duplicated
+  //     character to be in a word by itself at this stage),
+  // (4) the font size has changed
+  if (curWord && curWord->len > 0) {
+    base = sp = delta = 0; // make gcc happy
+    switch (curWord->rot) {
+    case 0:
+      base = y1;
+      sp = x1 - curWord->xMax;
+      delta = x1 - curWord->edge[curWord->len - 1];
+      break;
+    case 1:
+      base = x1;
+      sp = y1 - curWord->yMax;
+      delta = y1 - curWord->edge[curWord->len - 1];
+      break;
+    case 2:
+      base = y1;
+      sp = curWord->xMin - x1;
+      delta = curWord->edge[curWord->len - 1] - x1;
+      break;
+    case 3:
+      base = x1;
+      sp = curWord->yMin - y1;
+      delta = curWord->edge[curWord->len - 1] - y1;
+      break;
+    }
+    overlap = fabs(delta) < dupMaxPriDelta * curWord->fontSize &&
+              fabs(base - curWord->base) < dupMaxSecDelta * curWord->fontSize;
+    if (overlap || lastCharOverlap ||
+	sp < -minDupBreakOverlap * curWord->fontSize ||
+	sp > minWordBreakSpace * curWord->fontSize ||
+	fabs(base - curWord->base) > 0.5 ||
+	curFontSize != curWord->fontSize) {
+      endWord();
+    }
+    lastCharOverlap = overlap;
+  } else {
+    lastCharOverlap = gFalse;
+  }
+
+  if (uLen != 0) {
+    // start a new word if needed
+    if (!curWord) {
+      beginWord(state, x, y);
+    }
+
+    // page rotation and/or transform matrices can cause text to be
+    // drawn in reverse order -- in this case, swap the begin/end
+    // coordinates and break text into individual chars
+    if ((curWord->rot == 0 && w1 < 0) ||
+	(curWord->rot == 1 && h1 < 0) ||
+	(curWord->rot == 2 && w1 > 0) ||
+	(curWord->rot == 3 && h1 > 0)) {
+      endWord();
+      beginWord(state, x + dx, y + dy);
+      x1 += w1;
+      y1 += h1;
+      w1 = -w1;
+      h1 = -h1;
+    }
+
+    // add the characters to the current word
+    w1 /= uLen;
+    h1 /= uLen;
+    for (i = 0; i < uLen; ++i) {
+      curWord->addChar(state, x1 + i*w1, y1 + i*h1, w1, h1, u[i]);
+    }
+  }
+  if (curWord) {
+    curWord->charLen += nBytes;
+  }
+  charPos += nBytes;
+}
+
+void TextPage::endWord() {
+  // This check is needed because Type 3 characters can contain
+  // text-drawing operations (when TextPage is being used via
+  // {X,Win}SplashOutputDev rather than TextOutputDev).
+  if (nest > 0) {
+    --nest;
+    return;
+  }
+
+  if (curWord) {
+    addWord(curWord);
+    curWord = NULL;
+  }
+}
+
+void TextPage::addWord(TextWord *word) {
+  // throw away zero-length words -- they don't have valid xMin/xMax
+  // values, and they're useless anyway
+  if (word->len == 0) {
+    delete word;
+    return;
+  }
+
+  if (rawOrder) {
+    if (rawLastWord) {
+      rawLastWord->next = word;
+    } else {
+      rawWords = word;
+    }
+    rawLastWord = word;
+  } else {
+    pools[word->rot]->addWord(word);
+  }
+}
+
+void TextPage::addUnderline(double x0, double y0, double x1, double y1) {
+  underlines->append(new TextUnderline(x0, y0, x1, y1));
+}
+
+void TextPage::addLink(int xMin, int yMin, int xMax, int yMax, Link *link) {
+  links->append(new TextLink(xMin, yMin, xMax, yMax, link));
+}
+
+void TextPage::coalesce(GBool physLayout, GBool doHTML) {
+  UnicodeMap *uMap;
+  TextPool *pool;
+  TextWord *word0, *word1, *word2;
+  TextLine *line;
+  TextBlock *blkList, *blkStack, *blk, *lastBlk, *blk0, *blk1;
+  TextBlock **blkArray;
+  TextFlow *flow, *lastFlow;
+  TextUnderline *underline;
+  TextLink *link;
+  int rot, poolMinBaseIdx, baseIdx, startBaseIdx, endBaseIdx;
+  double minBase, maxBase, newMinBase, newMaxBase;
+  double fontSize, colSpace1, colSpace2, lineSpace, intraLineSpace, blkSpace;
+  GBool found;
+  int count[4];
+  int lrCount;
+  int firstBlkIdx, nBlocksLeft;
+  int col1, col2;
+  int i, j, n;
+
+  if (rawOrder) {
+    primaryRot = 0;
+    primaryLR = gTrue;
+    return;
+  }
+
+  uMap = globalParams->getTextEncoding();
+  blkList = NULL;
+  lastBlk = NULL;
+  nBlocks = 0;
+  primaryRot = -1;
+
+  for (rot = 0; rot < 4; ++rot) {
+      /* XXX */
+    char buf[8];
+    int n;
+    pool = pools[rot];
+    for (baseIdx = pool->minBaseIdx; baseIdx <= pool->maxBaseIdx; ++baseIdx) {
+      for (word0 = pool->getPool(baseIdx); word0; word0 = word0->next) {
+        GString *s = new GString();
+	for (i = 0; i < word0->len; ++i) {
+	  n = uMap->mapUnicode(word0->text[i], buf, sizeof(buf));
+	  s->append(buf, n);
+	}
+
+	printf("l=%06d r=%06d u=%06d d=%06d b=%06d f=%06d r=%01d s=%04d ",
+	       lrint(word0->xMin*100),
+               lrint(word0->xMax*100),
+               lrint(word0->yMin*100),
+               lrint(word0->yMax*100),
+	       lrint(word0->base*100),
+               lrint(word0->fontSize*100),
+               rot*90,
+               s->getLength());
+        fwrite(s->getCString(), 1, s->getLength(), stdout);
+	printf("\n");
+      }
+    }
+  }
+  printf("\n");
+  return;
+
+#if 0 //~ for debugging
+  for (i = 0; i < underlines->getLength(); ++i) {
+    underline = (TextUnderline *)underlines->get(i);
+    printf("underline: x=%g..%g y=%g..%g horiz=%d\n",
+	   underline->x0, underline->x1, underline->y0, underline->y1,
+	   underline->horiz);
+  }
+#endif
+
+  if (doHTML) {
+
+    //----- handle underlining
+    for (i = 0; i < underlines->getLength(); ++i) {
+      underline = (TextUnderline *)underlines->get(i);
+      if (underline->horiz) {
+	// rot = 0
+	if (pools[0]->minBaseIdx <= pools[0]->maxBaseIdx) {
+	  startBaseIdx = pools[0]->getBaseIdx(underline->y0 + minUnderlineGap);
+	  endBaseIdx = pools[0]->getBaseIdx(underline->y0 + maxUnderlineGap);
+	  for (j = startBaseIdx; j <= endBaseIdx; ++j) {
+	    for (word0 = pools[0]->getPool(j); word0; word0 = word0->next) {
+	      //~ need to check the y value against the word baseline
+	      if (underline->x0 < word0->xMin + underlineSlack &&
+		  word0->xMax - underlineSlack < underline->x1) {
+		word0->underlined = gTrue;
+	      }
+	    }
+	  }
+	}
+
+	// rot = 2
+	if (pools[2]->minBaseIdx <= pools[2]->maxBaseIdx) {
+	  startBaseIdx = pools[2]->getBaseIdx(underline->y0 - maxUnderlineGap);
+	  endBaseIdx = pools[2]->getBaseIdx(underline->y0 - minUnderlineGap);
+	  for (j = startBaseIdx; j <= endBaseIdx; ++j) {
+	    for (word0 = pools[2]->getPool(j); word0; word0 = word0->next) {
+	      if (underline->x0 < word0->xMin + underlineSlack &&
+		  word0->xMax - underlineSlack < underline->x1) {
+		word0->underlined = gTrue;
+	      }
+	    }
+	  }
+	}
+      } else {
+	// rot = 1
+	if (pools[1]->minBaseIdx <= pools[1]->maxBaseIdx) {
+	  startBaseIdx = pools[1]->getBaseIdx(underline->x0 - maxUnderlineGap);
+	  endBaseIdx = pools[1]->getBaseIdx(underline->x0 - minUnderlineGap);
+	  for (j = startBaseIdx; j <= endBaseIdx; ++j) {
+	    for (word0 = pools[1]->getPool(j); word0; word0 = word0->next) {
+	      if (underline->y0 < word0->yMin + underlineSlack &&
+		  word0->yMax - underlineSlack < underline->y1) {
+		word0->underlined = gTrue;
+	      }
+	    }
+	  }
+	}
+
+	// rot = 3
+	if (pools[3]->minBaseIdx <= pools[3]->maxBaseIdx) {
+	  startBaseIdx = pools[3]->getBaseIdx(underline->x0 + minUnderlineGap);
+	  endBaseIdx = pools[3]->getBaseIdx(underline->x0 + maxUnderlineGap);
+	  for (j = startBaseIdx; j <= endBaseIdx; ++j) {
+	    for (word0 = pools[3]->getPool(j); word0; word0 = word0->next) {
+	      if (underline->y0 < word0->yMin + underlineSlack &&
+		  word0->yMax - underlineSlack < underline->y1) {
+		word0->underlined = gTrue;
+	      }
+	    }
+	  }
+	}
+      }
+    }
+
+    //----- handle links
+    for (i = 0; i < links->getLength(); ++i) {
+      link = (TextLink *)links->get(i);
+
+      // rot = 0
+      if (pools[0]->minBaseIdx <= pools[0]->maxBaseIdx) {
+	startBaseIdx = pools[0]->getBaseIdx(link->yMin);
+	endBaseIdx = pools[0]->getBaseIdx(link->yMax);
+	for (j = startBaseIdx; j <= endBaseIdx; ++j) {
+	  for (word0 = pools[0]->getPool(j); word0; word0 = word0->next) {
+	    if (link->xMin < word0->xMin + hyperlinkSlack &&
+		word0->xMax - hyperlinkSlack < link->xMax &&
+		link->yMin < word0->yMin + hyperlinkSlack &&
+		word0->yMax - hyperlinkSlack < link->yMax) {
+	      word0->link = link->link;
+	    }
+	  }
+	}
+      }
+
+      // rot = 2
+      if (pools[2]->minBaseIdx <= pools[2]->maxBaseIdx) {
+	startBaseIdx = pools[2]->getBaseIdx(link->yMin);
+	endBaseIdx = pools[2]->getBaseIdx(link->yMax);
+	for (j = startBaseIdx; j <= endBaseIdx; ++j) {
+	  for (word0 = pools[2]->getPool(j); word0; word0 = word0->next) {
+	    if (link->xMin < word0->xMin + hyperlinkSlack &&
+		word0->xMax - hyperlinkSlack < link->xMax &&
+		link->yMin < word0->yMin + hyperlinkSlack &&
+		word0->yMax - hyperlinkSlack < link->yMax) {
+	      word0->link = link->link;
+	    }
+	  }
+	}
+      }
+
+      // rot = 1
+      if (pools[1]->minBaseIdx <= pools[1]->maxBaseIdx) {
+	startBaseIdx = pools[1]->getBaseIdx(link->xMin);
+	endBaseIdx = pools[1]->getBaseIdx(link->xMax);
+	for (j = startBaseIdx; j <= endBaseIdx; ++j) {
+	  for (word0 = pools[1]->getPool(j); word0; word0 = word0->next) {
+	    if (link->yMin < word0->yMin + hyperlinkSlack &&
+		word0->yMax - hyperlinkSlack < link->yMax &&
+		link->xMin < word0->xMin + hyperlinkSlack &&
+		word0->xMax - hyperlinkSlack < link->xMax) {
+	      word0->link = link->link;
+	    }
+	  }
+	}
+      }
+
+      // rot = 3
+      if (pools[3]->minBaseIdx <= pools[3]->maxBaseIdx) {
+	startBaseIdx = pools[3]->getBaseIdx(link->xMin);
+	endBaseIdx = pools[3]->getBaseIdx(link->xMax);
+	for (j = startBaseIdx; j <= endBaseIdx; ++j) {
+	  for (word0 = pools[3]->getPool(j); word0; word0 = word0->next) {
+	    if (link->yMin < word0->yMin + hyperlinkSlack &&
+		word0->yMax - hyperlinkSlack < link->yMax &&
+		link->xMin < word0->xMin + hyperlinkSlack &&
+		word0->xMax - hyperlinkSlack < link->xMax) {
+	      word0->link = link->link;
+	    }
+	  }
+	}
+      }
+    }
+  }
+
+  //----- assemble the blocks
+
+  //~ add an outer loop for writing mode (vertical text)
+
+  // build blocks for each rotation value
+  for (rot = 0; rot < 4; ++rot) {
+    pool = pools[rot];
+    poolMinBaseIdx = pool->minBaseIdx;
+    count[rot] = 0;
+
+    // add blocks until no more words are left
+    while (1) {
+
+      // find the first non-empty line in the pool
+      for (;
+	   poolMinBaseIdx <= pool->maxBaseIdx &&
+	     !pool->getPool(poolMinBaseIdx);
+	   ++poolMinBaseIdx) ;
+      if (poolMinBaseIdx > pool->maxBaseIdx) {
+	break;
+      }
+
+      // look for the left-most word in the first four lines of the
+      // pool -- this avoids starting with a superscript word
+      startBaseIdx = poolMinBaseIdx;
+      for (baseIdx = poolMinBaseIdx + 1;
+	   baseIdx < poolMinBaseIdx + 4 && baseIdx <= pool->maxBaseIdx;
+	   ++baseIdx) {
+	if (!pool->getPool(baseIdx)) {
+	  continue;
+	}
+	if (pool->getPool(baseIdx)->primaryCmp(pool->getPool(startBaseIdx))
+	    < 0) {
+	  startBaseIdx = baseIdx;
+	}
+      }
+
+      // create a new block
+      word0 = pool->getPool(startBaseIdx);
+      pool->setPool(startBaseIdx, word0->next);
+      word0->next = NULL;
+      blk = new TextBlock(this, rot);
+      blk->addWord(word0);
+
+      fontSize = word0->fontSize;
+      minBase = maxBase = word0->base;
+      colSpace1 = minColSpacing1 * fontSize;
+      colSpace2 = minColSpacing2 * fontSize;
+      lineSpace = maxLineSpacingDelta * fontSize;
+      intraLineSpace = maxIntraLineDelta * fontSize;
+
+      // add words to the block
+      do {
+	found = gFalse;
+
+	// look for words on the line above the current top edge of
+	// the block
+	newMinBase = minBase;
+	for (baseIdx = pool->getBaseIdx(minBase);
+	     baseIdx >= pool->getBaseIdx(minBase - lineSpace);
+	     --baseIdx) {
+	  word0 = NULL;
+	  word1 = pool->getPool(baseIdx);
+	  while (word1) {
+	    if (word1->base < minBase &&
+		word1->base >= minBase - lineSpace &&
+		((rot == 0 || rot == 2)
+		 ? (word1->xMin < blk->xMax && word1->xMax > blk->xMin)
+		 : (word1->yMin < blk->yMax && word1->yMax > blk->yMin)) &&
+		fabs(word1->fontSize - fontSize) <
+		  maxBlockFontSizeDelta1 * fontSize) {
+	      word2 = word1;
+	      if (word0) {
+		word0->next = word1->next;
+	      } else {
+		pool->setPool(baseIdx, word1->next);
+	      }
+	      word1 = word1->next;
+	      word2->next = NULL;
+	      blk->addWord(word2);
+	      found = gTrue;
+	      newMinBase = word2->base;
+	    } else {
+	      word0 = word1;
+	      word1 = word1->next;
+	    }
+	  }
+	}
+	minBase = newMinBase;
+
+	// look for words on the line below the current bottom edge of
+	// the block
+	newMaxBase = maxBase;
+	for (baseIdx = pool->getBaseIdx(maxBase);
+	     baseIdx <= pool->getBaseIdx(maxBase + lineSpace);
+	     ++baseIdx) {
+	  word0 = NULL;
+	  word1 = pool->getPool(baseIdx);
+	  while (word1) {
+	    if (word1->base > maxBase &&
+		word1->base <= maxBase + lineSpace &&
+		((rot == 0 || rot == 2)
+		 ? (word1->xMin < blk->xMax && word1->xMax > blk->xMin)
+		 : (word1->yMin < blk->yMax && word1->yMax > blk->yMin)) &&
+		fabs(word1->fontSize - fontSize) <
+		  maxBlockFontSizeDelta1 * fontSize) {
+	      word2 = word1;
+	      if (word0) {
+		word0->next = word1->next;
+	      } else {
+		pool->setPool(baseIdx, word1->next);
+	      }
+	      word1 = word1->next;
+	      word2->next = NULL;
+	      blk->addWord(word2);
+	      found = gTrue;
+	      newMaxBase = word2->base;
+	    } else {
+	      word0 = word1;
+	      word1 = word1->next;
+	    }
+	  }
+	}
+	maxBase = newMaxBase;
+
+	// look for words that are on lines already in the block, and
+	// that overlap the block horizontally
+	for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace);
+	     baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace);
+	     ++baseIdx) {
+	  word0 = NULL;
+	  word1 = pool->getPool(baseIdx);
+	  while (word1) {
+	    if (word1->base >= minBase - intraLineSpace &&
+		word1->base <= maxBase + intraLineSpace &&
+		((rot == 0 || rot == 2)
+		 ? (word1->xMin < blk->xMax + colSpace1 &&
+		    word1->xMax > blk->xMin - colSpace1)
+		 : (word1->yMin < blk->yMax + colSpace1 &&
+		    word1->yMax > blk->yMin - colSpace1)) &&
+		fabs(word1->fontSize - fontSize) <
+		  maxBlockFontSizeDelta2 * fontSize) {
+	      word2 = word1;
+	      if (word0) {
+		word0->next = word1->next;
+	      } else {
+		pool->setPool(baseIdx, word1->next);
+	      }
+	      word1 = word1->next;
+	      word2->next = NULL;
+	      blk->addWord(word2);
+	      found = gTrue;
+	    } else {
+	      word0 = word1;
+	      word1 = word1->next;
+	    }
+	  }
+	}
+
+	// only check for outlying words (the next two chunks of code)
+	// if we didn't find anything else
+	if (found) {
+	  continue;
+	}
+
+	// scan down the left side of the block, looking for words
+	// that are near (but not overlapping) the block; if there are
+	// three or fewer, add them to the block
+	n = 0;
+	for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace);
+	     baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace);
+	     ++baseIdx) {
+	  word1 = pool->getPool(baseIdx);
+	  while (word1) {
+	    if (word1->base >= minBase - intraLineSpace &&
+		word1->base <= maxBase + intraLineSpace &&
+		((rot == 0 || rot == 2)
+		 ? (word1->xMax <= blk->xMin &&
+		    word1->xMax > blk->xMin - colSpace2)
+		 : (word1->yMax <= blk->yMin &&
+		    word1->yMax > blk->yMin - colSpace2)) &&
+		fabs(word1->fontSize - fontSize) <
+		  maxBlockFontSizeDelta3 * fontSize) {
+	      ++n;
+	      break;
+	    }
+	    word1 = word1->next;
+	  }
+	}
+	if (n > 0 && n <= 3) {
+	  for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace);
+	       baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace);
+	       ++baseIdx) {
+	    word0 = NULL;
+	    word1 = pool->getPool(baseIdx);
+	    while (word1) {
+	      if (word1->base >= minBase - intraLineSpace &&
+		  word1->base <= maxBase + intraLineSpace &&
+		  ((rot == 0 || rot == 2)
+		   ? (word1->xMax <= blk->xMin &&
+		      word1->xMax > blk->xMin - colSpace2)
+		   : (word1->yMax <= blk->yMin &&
+		      word1->yMax > blk->yMin - colSpace2)) &&
+		  fabs(word1->fontSize - fontSize) <
+		    maxBlockFontSizeDelta3 * fontSize) {
+		word2 = word1;
+		if (word0) {
+		  word0->next = word1->next;
+		} else {
+		  pool->setPool(baseIdx, word1->next);
+		}
+		word1 = word1->next;
+		word2->next = NULL;
+		blk->addWord(word2);
+		if (word2->base < minBase) {
+		  minBase = word2->base;
+		} else if (word2->base > maxBase) {
+		  maxBase = word2->base;
+		}
+		found = gTrue;
+		break;
+	      } else {
+		word0 = word1;
+		word1 = word1->next;
+	      }
+	    }
+	  }
+	}
+
+	// scan down the right side of the block, looking for words
+	// that are near (but not overlapping) the block; if there are
+	// three or fewer, add them to the block
+	n = 0;
+	for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace);
+	     baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace);
+	     ++baseIdx) {
+	  word1 = pool->getPool(baseIdx);
+	  while (word1) {
+	    if (word1->base >= minBase - intraLineSpace &&
+		word1->base <= maxBase + intraLineSpace &&
+		((rot == 0 || rot == 2)
+		 ? (word1->xMin >= blk->xMax &&
+		    word1->xMin < blk->xMax + colSpace2)
+		 : (word1->yMin >= blk->yMax &&
+		    word1->yMin < blk->yMax + colSpace2)) &&
+		fabs(word1->fontSize - fontSize) <
+		  maxBlockFontSizeDelta3 * fontSize) {
+	      ++n;
+	      break;
+	    }
+	    word1 = word1->next;
+	  }
+	}
+	if (n > 0 && n <= 3) {
+	  for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace);
+	       baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace);
+	       ++baseIdx) {
+	    word0 = NULL;
+	    word1 = pool->getPool(baseIdx);
+	    while (word1) {
+	      if (word1->base >= minBase - intraLineSpace &&
+		  word1->base <= maxBase + intraLineSpace &&
+		  ((rot == 0 || rot == 2)
+		   ? (word1->xMin >= blk->xMax &&
+		      word1->xMin < blk->xMax + colSpace2)
+		   : (word1->yMin >= blk->yMax &&
+		      word1->yMin < blk->yMax + colSpace2)) &&
+		  fabs(word1->fontSize - fontSize) <
+		    maxBlockFontSizeDelta3 * fontSize) {
+		word2 = word1;
+		if (word0) {
+		  word0->next = word1->next;
+		} else {
+		  pool->setPool(baseIdx, word1->next);
+		}
+		word1 = word1->next;
+		word2->next = NULL;
+		blk->addWord(word2);
+		if (word2->base < minBase) {
+		  minBase = word2->base;
+		} else if (word2->base > maxBase) {
+		  maxBase = word2->base;
+		}
+		found = gTrue;
+		break;
+	      } else {
+		word0 = word1;
+		word1 = word1->next;
+	      }
+	    }
+	  }
+	}
+
+      } while (found);
+
+      //~ need to compute the primary writing mode (horiz/vert) in
+      //~ addition to primary rotation
+
+      // coalesce the block, and add it to the list
+      blk->coalesce(uMap);
+      if (lastBlk) {
+	lastBlk->next = blk;
+      } else {
+	blkList = blk;
+      }
+      lastBlk = blk;
+      count[rot] += blk->charCount;
+      if (primaryRot < 0 || count[rot] > count[primaryRot]) {
+	primaryRot = rot;
+      }
+      ++nBlocks;
+    }
+  }
+
+#if 0 // for debugging 
+  printf("*** rotation ***\n");
+  for (rot = 0; rot < 4; ++rot) {
+    printf("  %d: %6d\n", rot, count[rot]);
+  }
+  printf("  primary rot = %d\n", primaryRot);
+  printf("\n");
+#endif
+
+#if 0 // for debugging
+  printf("*** blocks ***\n");
+  for (blk = blkList; blk; blk = blk->next) {
+    printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f\n",
+	   blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax);
+    for (line = blk->lines; line; line = line->next) {
+      printf("  line: x=%.2f..%.2f y=%.2f..%.2f base=%.2f\n",
+	     line->xMin, line->xMax, line->yMin, line->yMax, line->base);
+      for (word0 = line->words; word0; word0 = word0->next) {
+	printf("    word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '",
+	       word0->xMin, word0->xMax, word0->yMin, word0->yMax,
+	       word0->base, word0->fontSize, word0->spaceAfter);
+	for (i = 0; i < word0->len; ++i) {
+	  fputc(word0->text[i] & 0xff, stdout);
+	}
+	printf("'\n");
+      }
+    }
+  }
+  printf("\n");
+#endif
+
+  // determine the primary direction
+  lrCount = 0;
+  for (blk = blkList; blk; blk = blk->next) {
+    for (line = blk->lines; line; line = line->next) {
+      for (word0 = line->words; word0; word0 = word0->next) {
+	for (i = 0; i < word0->len; ++i) {
+	  if (unicodeTypeL(word0->text[i])) {
+	    ++lrCount;
+	  } else if (unicodeTypeR(word0->text[i])) {
+	    --lrCount;
+	  }
+	}
+      }
+    }
+  }
+  primaryLR = lrCount >= 0;
+
+#if 0 // for debugging
+  printf("*** direction ***\n");
+  printf("lrCount = %d\n", lrCount);
+  printf("primaryLR = %d\n", primaryLR);
+#endif
+
+  //----- column assignment
+
+  // sort blocks into xy order for column assignment
+  blocks = (TextBlock **)gmallocn(nBlocks, sizeof(TextBlock *));
+  for (blk = blkList, i = 0; blk; blk = blk->next, ++i) {
+    blocks[i] = blk;
+  }
+  qsort(blocks, nBlocks, sizeof(TextBlock *), &TextBlock::cmpXYPrimaryRot);
+
+  // column assignment
+  for (i = 0; i < nBlocks; ++i) {
+    blk0 = blocks[i];
+    col1 = 0;
+    for (j = 0; j < i; ++j) {
+      blk1 = blocks[j];
+      col2 = 0; // make gcc happy
+      switch (primaryRot) {
+      case 0:
+	if (blk0->xMin > blk1->xMax) {
+	  col2 = blk1->col + blk1->nColumns + 3;
+	} else if (blk1->xMax == blk1->xMin) {
+	  col2 = blk1->col;
+	} else {
+	  col2 = blk1->col + (int)(((blk0->xMin - blk1->xMin) /
+				    (blk1->xMax - blk1->xMin)) *
+				   blk1->nColumns);
+	}
+	break;
+      case 1:
+	if (blk0->yMin > blk1->yMax) {
+	  col2 = blk1->col + blk1->nColumns + 3;
+	} else if (blk1->yMax == blk1->yMin) {
+	  col2 = blk1->col;
+	} else {
+	  col2 = blk1->col + (int)(((blk0->yMin - blk1->yMin) /
+				    (blk1->yMax - blk1->yMin)) *
+				   blk1->nColumns);
+	}
+	break;
+      case 2:
+	if (blk0->xMax < blk1->xMin) {
+	  col2 = blk1->col + blk1->nColumns + 3;
+	} else if (blk1->xMin == blk1->xMax) {
+	  col2 = blk1->col;
+	} else {
+	  col2 = blk1->col + (int)(((blk0->xMax - blk1->xMax) /
+				    (blk1->xMin - blk1->xMax)) *
+				   blk1->nColumns);
+	}
+	break;
+      case 3:
+	if (blk0->yMax < blk1->yMin) {
+	  col2 = blk1->col + blk1->nColumns + 3;
+	} else if (blk1->yMin == blk1->yMax) {
+	  col2 = blk1->col;
+	} else {
+	  col2 = blk1->col + (int)(((blk0->yMax - blk1->yMax) /
+				    (blk1->yMin - blk1->yMax)) *
+				   blk1->nColumns);
+	}
+	break;
+      }
+      if (col2 > col1) {
+	col1 = col2;
+      }
+    }
+    blk0->col = col1;
+    for (line = blk0->lines; line; line = line->next) {
+      for (j = 0; j <= line->len; ++j) {
+	line->col[j] += col1;
+      }
+    }
+  }
+
+#if 0 // for debugging
+  printf("*** blocks, after column assignment ***\n");
+  for (blk = blkList; blk; blk = blk->next) {
+    printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f col=%d nCols=%d\n",
+	   blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax, blk->col,
+	   blk->nColumns);
+    for (line = blk->lines; line; line = line->next) {
+      printf("  line:\n");
+      for (word0 = line->words; word0; word0 = word0->next) {
+	printf("    word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '",
+	       word0->xMin, word0->xMax, word0->yMin, word0->yMax,
+	       word0->base, word0->fontSize, word0->spaceAfter);
+	for (i = 0; i < word0->len; ++i) {
+	  fputc(word0->text[i] & 0xff, stdout);
+	}
+	printf("'\n");
+      }
+    }
+  }
+  printf("\n");
+#endif
+
+  //----- reading order sort
+
+  // sort blocks into yx order (in preparation for reading order sort)
+  qsort(blocks, nBlocks, sizeof(TextBlock *), &TextBlock::cmpYXPrimaryRot);
+
+  // compute space on left and right sides of each block
+  for (i = 0; i < nBlocks; ++i) {
+    blk0 = blocks[i];
+    for (j = 0; j < nBlocks; ++j) {
+      blk1 = blocks[j];
+      if (blk1 != blk0) {
+	blk0->updatePriMinMax(blk1);
+      }
+    }
+  }
+
+#if 0 // for debugging
+  printf("*** blocks, after yx sort ***\n");
+  for (i = 0; i < nBlocks; ++i) {
+    blk = blocks[i];
+    printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f space=%.2f..%.2f\n",
+	   blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax,
+	   blk->priMin, blk->priMax);
+    for (line = blk->lines; line; line = line->next) {
+      printf("  line:\n");
+      for (word0 = line->words; word0; word0 = word0->next) {
+	printf("    word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '",
+	       word0->xMin, word0->xMax, word0->yMin, word0->yMax,
+	       word0->base, word0->fontSize, word0->spaceAfter);
+	for (j = 0; j < word0->len; ++j) {
+	  fputc(word0->text[j] & 0xff, stdout);
+	}
+	printf("'\n");
+      }
+    }
+  }
+  printf("\n");
+#endif
+
+  // build the flows
+  //~ this needs to be adjusted for writing mode (vertical text)
+  //~ this also needs to account for right-to-left column ordering
+  blkArray = (TextBlock **)gmallocn(nBlocks, sizeof(TextBlock *));
+  memcpy(blkArray, blocks, nBlocks * sizeof(TextBlock *));
+  flows = lastFlow = NULL;
+  firstBlkIdx = 0;
+  nBlocksLeft = nBlocks;
+  while (nBlocksLeft > 0) {
+
+    // find the upper-left-most block
+    for (; !blkArray[firstBlkIdx]; ++firstBlkIdx) ;
+    i = firstBlkIdx;
+    blk = blkArray[i];
+    for (j = firstBlkIdx + 1; j < nBlocks; ++j) {
+      blk1 = blkArray[j];
+      if (blk1) {
+	if (blk && blk->secondaryDelta(blk1) > 0) {
+	  break;
+	}
+	if (blk1->primaryCmp(blk) < 0) {
+	  i = j;
+	  blk = blk1;
+	}
+      }
+    }
+    blkArray[i] = NULL;
+    --nBlocksLeft;
+    blk->next = NULL;
+
+    // create a new flow, starting with the upper-left-most block
+    flow = new TextFlow(this, blk);
+    if (lastFlow) {
+      lastFlow->next = flow;
+    } else {
+      flows = flow;
+    }
+    lastFlow = flow;
+    fontSize = blk->lines->words->fontSize;
+
+    // push the upper-left-most block on the stack
+    blk->stackNext = NULL;
+    blkStack = blk;
+
+    // find the other blocks in this flow
+    while (blkStack) {
+
+      // find the upper-left-most block under (but within
+      // maxBlockSpacing of) the top block on the stack
+      blkSpace = maxBlockSpacing * blkStack->lines->words->fontSize;
+      blk = NULL;
+      i = -1;
+      for (j = firstBlkIdx; j < nBlocks; ++j) {
+	blk1 = blkArray[j];
+	if (blk1) {
+	  if (blkStack->secondaryDelta(blk1) > blkSpace) {
+	    break;
+	  }
+	  if (blk && blk->secondaryDelta(blk1) > 0) {
+	    break;
+	  }
+	  if (blk1->isBelow(blkStack) &&
+	      (!blk || blk1->primaryCmp(blk) < 0)) {
+	    i = j;
+	    blk = blk1;
+	  }
+	}
+      }
+
+      // if a suitable block was found, add it to the flow and push it
+      // onto the stack
+      if (blk && flow->blockFits(blk, blkStack)) {
+	blkArray[i] = NULL;
+	--nBlocksLeft;
+	blk->next = NULL;
+	flow->addBlock(blk);
+	fontSize = blk->lines->words->fontSize;
+	blk->stackNext = blkStack;
+	blkStack = blk;
+
+      // otherwise (if there is no block under the top block or the
+      // block is not suitable), pop the stack
+      } else {
+	blkStack = blkStack->stackNext;
+      }
+    }
+  }
+  gfree(blkArray);
+
+#if 0 // for debugging
+  printf("*** flows ***\n");
+  for (flow = flows; flow; flow = flow->next) {
+    printf("flow: x=%.2f..%.2f y=%.2f..%.2f pri:%.2f..%.2f\n",
+	   flow->xMin, flow->xMax, flow->yMin, flow->yMax,
+	   flow->priMin, flow->priMax);
+    for (blk = flow->blocks; blk; blk = blk->next) {
+      printf("  block: rot=%d x=%.2f..%.2f y=%.2f..%.2f pri=%.2f..%.2f\n",
+	     blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax,
+	     blk->priMin, blk->priMax);
+      for (line = blk->lines; line; line = line->next) {
+	printf("    line:\n");
+	for (word0 = line->words; word0; word0 = word0->next) {
+	  printf("      word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '",
+		 word0->xMin, word0->xMax, word0->yMin, word0->yMax,
+		 word0->base, word0->fontSize, word0->spaceAfter);
+	  for (i = 0; i < word0->len; ++i) {
+	    fputc(word0->text[i] & 0xff, stdout);
+	  }
+	  printf("'\n");
+	}
+      }
+    }
+  }
+  printf("\n");
+#endif
+
+  if (uMap) {
+    uMap->decRefCnt();
+  }
+}
+
+GBool TextPage::findText(Unicode *s, int len,
+			 GBool startAtTop, GBool stopAtBottom,
+			 GBool startAtLast, GBool stopAtLast,
+			 GBool caseSensitive, GBool backward,
+			 double *xMin, double *yMin,
+			 double *xMax, double *yMax) {
+  TextBlock *blk;
+  TextLine *line;
+  Unicode *s2, *txt;
+  Unicode *p;
+  int txtSize, m, i, j, k;
+  double xStart, yStart, xStop, yStop;
+  double xMin0, yMin0, xMax0, yMax0;
+  double xMin1, yMin1, xMax1, yMax1;
+  GBool found;
+
+  //~ needs to handle right-to-left text
+
+  if (rawOrder) {
+    return gFalse;
+  }
+
+  // convert the search string to uppercase
+  if (!caseSensitive) {
+    s2 = (Unicode *)gmallocn(len, sizeof(Unicode));
+    for (i = 0; i < len; ++i) {
+      s2[i] = unicodeToUpper(s[i]);
+    }
+  } else {
+    s2 = s;
+  }
+
+  txt = NULL;
+  txtSize = 0;
+
+  xStart = yStart = xStop = yStop = 0;
+  if (startAtLast && haveLastFind) {
+    xStart = lastFindXMin;
+    yStart = lastFindYMin;
+  } else if (!startAtTop) {
+    xStart = *xMin;
+    yStart = *yMin;
+  }
+  if (stopAtLast && haveLastFind) {
+    xStop = lastFindXMin;
+    yStop = lastFindYMin;
+  } else if (!stopAtBottom) {
+    xStop = *xMax;
+    yStop = *yMax;
+  }
+
+  found = gFalse;
+  xMin0 = xMax0 = yMin0 = yMax0 = 0; // make gcc happy
+  xMin1 = xMax1 = yMin1 = yMax1 = 0; // make gcc happy
+
+  for (i = backward ? nBlocks - 1 : 0;
+       backward ? i >= 0 : i < nBlocks;
+       i += backward ? -1 : 1) {
+    blk = blocks[i];
+
+    // check: is the block above the top limit?
+    if (!startAtTop && (backward ? blk->yMin > yStart : blk->yMax < yStart)) {
+      continue;
+    }
+
+    // check: is the block below the bottom limit?
+    if (!stopAtBottom && (backward ? blk->yMax < yStop : blk->yMin > yStop)) {
+      break;
+    }
+
+    for (line = blk->lines; line; line = line->next) {
+
+      // check: is the line above the top limit?
+      if (!startAtTop &&
+	  (backward ? line->yMin > yStart : line->yMin < yStart)) {
+	continue;
+      }
+
+      // check: is the line below the bottom limit?
+      if (!stopAtBottom &&
+	  (backward ? line->yMin < yStop : line->yMin > yStop)) {
+	continue;
+      }
+
+      // convert the line to uppercase
+      m = line->len;
+      if (!caseSensitive) {
+	if (m > txtSize) {
+	  txt = (Unicode *)greallocn(txt, m, sizeof(Unicode));
+	  txtSize = m;
+	}
+	for (k = 0; k < m; ++k) {
+	  txt[k] = unicodeToUpper(line->text[k]);
+	}
+      } else {
+	txt = line->text;
+      }
+
+      // search each position in this line
+      j = backward ? m - len : 0;
+      p = txt + j;
+      while (backward ? j >= 0 : j <= m - len) {
+
+	// compare the strings
+	for (k = 0; k < len; ++k) {
+	  if (p[k] != s2[k]) {
+	    break;
+	  }
+	}
+
+	// found it
+	if (k == len) {
+	  switch (line->rot) {
+	  case 0:
+	    xMin1 = line->edge[j];
+	    xMax1 = line->edge[j + len];
+	    yMin1 = line->yMin;
+	    yMax1 = line->yMax;
+	    break;
+	  case 1:
+	    xMin1 = line->xMin;
+	    xMax1 = line->xMax;
+	    yMin1 = line->edge[j];
+	    yMax1 = line->edge[j + len];
+	    break;
+	  case 2:
+	    xMin1 = line->edge[j + len];
+	    xMax1 = line->edge[j];
+	    yMin1 = line->yMin;
+	    yMax1 = line->yMax;
+	    break;
+	  case 3:
+	    xMin1 = line->xMin;
+	    xMax1 = line->xMax;
+	    yMin1 = line->edge[j + len];
+	    yMax1 = line->edge[j];
+	    break;
+	  }
+	  if (backward) {
+	    if ((startAtTop ||
+		 yMin1 < yStart || (yMin1 == yStart && xMin1 < xStart)) &&
+		(stopAtBottom ||
+		 yMin1 > yStop || (yMin1 == yStop && xMin1 > xStop))) {
+	      if (!found ||
+		  yMin1 > yMin0 || (yMin1 == yMin0 && xMin1 > xMin0)) {
+		xMin0 = xMin1;
+		xMax0 = xMax1;
+		yMin0 = yMin1;
+		yMax0 = yMax1;
+		found = gTrue;
+	      }
+	    }
+	  } else {
+	    if ((startAtTop ||
+		 yMin1 > yStart || (yMin1 == yStart && xMin1 > xStart)) &&
+		(stopAtBottom ||
+		 yMin1 < yStop || (yMin1 == yStop && xMin1 < xStop))) {
+	      if (!found ||
+		  yMin1 < yMin0 || (yMin1 == yMin0 && xMin1 < xMin0)) {
+		xMin0 = xMin1;
+		xMax0 = xMax1;
+		yMin0 = yMin1;
+		yMax0 = yMax1;
+		found = gTrue;
+	      }
+	    }
+	  }
+	}
+	if (backward) {
+	  --j;
+	  --p;
+	} else {
+	  ++j;
+	  ++p;
+	}
+      }
+    }
+  }
+
+  if (!caseSensitive) {
+    gfree(s2);
+    gfree(txt);
+  }
+
+  if (found) {
+    *xMin = xMin0;
+    *xMax = xMax0;
+    *yMin = yMin0;
+    *yMax = yMax0;
+    lastFindXMin = xMin0;
+    lastFindYMin = yMin0;
+    haveLastFind = gTrue;
+    return gTrue;
+  }
+
+  return gFalse;
+}
+
+GString *TextPage::getText(double xMin, double yMin,
+			   double xMax, double yMax) {
+  GString *s;
+  UnicodeMap *uMap;
+  GBool isUnicode;
+  TextBlock *blk;
+  TextLine *line;
+  TextLineFrag *frags;
+  int nFrags, fragsSize;
+  TextLineFrag *frag;
+  char space[8], eol[16];
+  int spaceLen, eolLen;
+  int lastRot;
+  double x, y, delta;
+  int col, idx0, idx1, i, j;
+  GBool multiLine, oneRot;
+
+  s = new GString();
+
+  if (rawOrder) {
+    return s;
+  }
+
+  // get the output encoding
+  if (!(uMap = globalParams->getTextEncoding())) {
+    return s;
+  }
+  isUnicode = uMap->isUnicode();
+  spaceLen = uMap->mapUnicode(0x20, space, sizeof(space));
+  eolLen = 0; // make gcc happy
+  switch (globalParams->getTextEOL()) {
+  case eolUnix:
+    eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol));
+    break;
+  case eolDOS:
+    eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
+    eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen);
+    break;
+  case eolMac:
+    eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
+    break;
+  }
+
+  //~ writing mode (horiz/vert)
+
+  // collect the line fragments that are in the rectangle
+  fragsSize = 256;
+  frags = (TextLineFrag *)gmallocn(fragsSize, sizeof(TextLineFrag));
+  nFrags = 0;
+  lastRot = -1;
+  oneRot = gTrue;
+  for (i = 0; i < nBlocks; ++i) {
+    blk = blocks[i];
+    if (xMin < blk->xMax && blk->xMin < xMax &&
+	yMin < blk->yMax && blk->yMin < yMax) {
+      for (line = blk->lines; line; line = line->next) {
+	if (xMin < line->xMax && line->xMin < xMax &&
+	    yMin < line->yMax && line->yMin < yMax) {
+	  idx0 = idx1 = -1;
+	  switch (line->rot) {
+	  case 0:
+	    y = 0.5 * (line->yMin + line->yMax);
+	    if (yMin < y && y < yMax) {
+	      j = 0;
+	      while (j < line->len) {
+		if (0.5 * (line->edge[j] + line->edge[j+1]) > xMin) {
+		  idx0 = j;
+		  break;
+		}
+		++j;
+	      }
+	      j = line->len - 1;
+	      while (j >= 0) {
+		if (0.5 * (line->edge[j] + line->edge[j+1]) < xMax) {
+		  idx1 = j;
+		  break;
+		}
+		--j;
+	      }
+	    }
+	    break;
+	  case 1:
+	    x = 0.5 * (line->xMin + line->xMax);
+	    if (xMin < x && x < xMax) {
+	      j = 0;
+	      while (j < line->len) {
+		if (0.5 * (line->edge[j] + line->edge[j+1]) > yMin) {
+		  idx0 = j;
+		  break;
+		}
+		++j;
+	      }
+	      j = line->len - 1;
+	      while (j >= 0) {
+		if (0.5 * (line->edge[j] + line->edge[j+1]) < yMax) {
+		  idx1 = j;
+		  break;
+		}
+		--j;
+	      }
+	    }
+	    break;
+	  case 2:
+	    y = 0.5 * (line->yMin + line->yMax);
+	    if (yMin < y && y < yMax) {
+	      j = 0;
+	      while (j < line->len) {
+		if (0.5 * (line->edge[j] + line->edge[j+1]) < xMax) {
+		  idx0 = j;
+		  break;
+		}
+		++j;
+	      }
+	      j = line->len - 1;
+	      while (j >= 0) {
+		if (0.5 * (line->edge[j] + line->edge[j+1]) > xMin) {
+		  idx1 = j;
+		  break;
+		}
+		--j;
+	      }
+	    }
+	    break;
+	  case 3:
+	    x = 0.5 * (line->xMin + line->xMax);
+	    if (xMin < x && x < xMax) {
+	      j = 0;
+	      while (j < line->len) {
+		if (0.5 * (line->edge[j] + line->edge[j+1]) < yMax) {
+		  idx0 = j;
+		  break;
+		}
+		++j;
+	      }
+	      j = line->len - 1;
+	      while (j >= 0) {
+		if (0.5 * (line->edge[j] + line->edge[j+1]) > yMin) {
+		  idx1 = j;
+		  break;
+		}
+		--j;
+	      }
+	    }
+	    break;
+	  }
+	  if (idx0 >= 0 && idx1 >= 0) {
+	    if (nFrags == fragsSize) {
+	      fragsSize *= 2;
+	      frags = (TextLineFrag *)
+		          greallocn(frags, fragsSize, sizeof(TextLineFrag));
+	    }
+	    frags[nFrags].init(line, idx0, idx1 - idx0 + 1);
+	    ++nFrags;
+	    if (lastRot >= 0 && line->rot != lastRot) {
+	      oneRot = gFalse;
+	    }
+	    lastRot = line->rot;
+	  }
+	}
+      }
+    }
+  }
+
+  // sort the fragments and generate the string
+  if (nFrags > 0) {
+
+    for (i = 0; i < nFrags; ++i) {
+      frags[i].computeCoords(oneRot);
+    }
+    assignColumns(frags, nFrags, oneRot);
+
+    // if all lines in the region have the same rotation, use it;
+    // otherwise, use the page's primary rotation
+    if (oneRot) {
+      qsort(frags, nFrags, sizeof(TextLineFrag),
+	    &TextLineFrag::cmpYXLineRot);
+    } else {
+      qsort(frags, nFrags, sizeof(TextLineFrag),
+	    &TextLineFrag::cmpYXPrimaryRot);
+    }
+    i = 0;
+    while (i < nFrags) {
+      delta = maxIntraLineDelta * frags[i].line->words->fontSize;
+      for (j = i+1;
+	   j < nFrags && fabs(frags[j].base - frags[i].base) < delta;
+	   ++j) ;
+      qsort(frags + i, j - i, sizeof(TextLineFrag),
+	    oneRot ? &TextLineFrag::cmpXYColumnLineRot
+	           : &TextLineFrag::cmpXYColumnPrimaryRot);
+      i = j;
+    }
+
+    col = 0;
+    multiLine = gFalse;
+    for (i = 0; i < nFrags; ++i) {
+      frag = &frags[i];
+
+      // insert a return
+      if (frag->col < col ||
+	  (i > 0 && fabs(frag->base - frags[i-1].base) >
+	              maxIntraLineDelta * frags[i-1].line->words->fontSize)) {
+	s->append(eol, eolLen);
+	col = 0;
+	multiLine = gTrue;
+      }
+
+      // column alignment
+      for (; col < frag->col; ++col) {
+	s->append(space, spaceLen);
+      }
+
+      // get the fragment text
+      col += dumpFragment(frag->line->text + frag->start, frag->len, uMap, s);
+    }
+
+    if (multiLine) {
+      s->append(eol, eolLen);
+    }
+  }
+
+  gfree(frags);
+  uMap->decRefCnt();
+
+  return s;
+}
+
+GBool TextPage::findCharRange(int pos, int length,
+			      double *xMin, double *yMin,
+			      double *xMax, double *yMax) {
+  TextBlock *blk;
+  TextLine *line;
+  TextWord *word;
+  double xMin0, xMax0, yMin0, yMax0;
+  double xMin1, xMax1, yMin1, yMax1;
+  GBool first;
+  int i, j0, j1;
+
+  if (rawOrder) {
+    return gFalse;
+  }
+
+  //~ this doesn't correctly handle:
+  //~ - ranges split across multiple lines (the highlighted region
+  //~   is the bounding box of all the parts of the range)
+  //~ - cases where characters don't convert one-to-one into Unicode
+  first = gTrue;
+  xMin0 = xMax0 = yMin0 = yMax0 = 0; // make gcc happy
+  xMin1 = xMax1 = yMin1 = yMax1 = 0; // make gcc happy
+  for (i = 0; i < nBlocks; ++i) {
+    blk = blocks[i];
+    for (line = blk->lines; line; line = line->next) {
+      for (word = line->words; word; word = word->next) {
+	if (pos < word->charPos + word->charLen &&
+	    word->charPos < pos + length) {
+	  j0 = pos - word->charPos;
+	  if (j0 < 0) {
+	    j0 = 0;
+	  }
+	  j1 = pos + length - 1 - word->charPos;
+	  if (j1 >= word->len) {
+	    j1 = word->len - 1;
+	  }
+	  switch (line->rot) {
+	  case 0:
+	    xMin1 = word->edge[j0];
+	    xMax1 = word->edge[j1 + 1];
+	    yMin1 = word->yMin;
+	    yMax1 = word->yMax;
+	    break;
+	  case 1:
+	    xMin1 = word->xMin;
+	    xMax1 = word->xMax;
+	    yMin1 = word->edge[j0];
+	    yMax1 = word->edge[j1 + 1];
+	    break;
+	  case 2:
+	    xMin1 = word->edge[j1 + 1];
+	    xMax1 = word->edge[j0];
+	    yMin1 = word->yMin;
+	    yMax1 = word->yMax;
+	    break;
+	  case 3:
+	    xMin1 = word->xMin;
+	    xMax1 = word->xMax;
+	    yMin1 = word->edge[j1 + 1];
+	    yMax1 = word->edge[j0];
+	    break;
+	  }
+	  if (first || xMin1 < xMin0) {
+	    xMin0 = xMin1;
+	  }
+	  if (first || xMax1 > xMax0) {
+	    xMax0 = xMax1;
+	  }
+	  if (first || yMin1 < yMin0) {
+	    yMin0 = yMin1;
+	  }
+	  if (first || yMax1 > yMax0) {
+	    yMax0 = yMax1;
+	  }
+	  first = gFalse;
+	}
+      }
+    }
+  }
+  if (!first) {
+    *xMin = xMin0;
+    *xMax = xMax0;
+    *yMin = yMin0;
+    *yMax = yMax0;
+    return gTrue;
+  }
+  return gFalse;
+}
+
+void TextPage::dump(void *outputStream, TextOutputFunc outputFunc,
+		    GBool physLayout) {
+  UnicodeMap *uMap;
+  TextFlow *flow;
+  TextBlock *blk;
+  TextLine *line;
+  TextLineFrag *frags;
+  TextWord *word;
+  int nFrags, fragsSize;
+  TextLineFrag *frag;
+  char space[8], eol[16], eop[8];
+  int spaceLen, eolLen, eopLen;
+  GBool pageBreaks;
+  GString *s;
+  double delta;
+  int col, i, j, d, n;
+
+  // get the output encoding
+  if (!(uMap = globalParams->getTextEncoding())) {
+    return;
+  }
+  spaceLen = uMap->mapUnicode(0x20, space, sizeof(space));
+  eolLen = 0; // make gcc happy
+  switch (globalParams->getTextEOL()) {
+  case eolUnix:
+    eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol));
+    break;
+  case eolDOS:
+    eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
+    eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen);
+    break;
+  case eolMac:
+    eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol));
+    break;
+  }
+  eopLen = uMap->mapUnicode(0x0c, eop, sizeof(eop));
+  pageBreaks = globalParams->getTextPageBreaks();
+
+  //~ writing mode (horiz/vert)
+
+  // output the page in raw (content stream) order
+  if (rawOrder) {
+
+    for (word = rawWords; word; word = word->next) {
+      s = new GString();
+      dumpFragment(word->text, word->len, uMap, s);
+      (*outputFunc)(outputStream, s->getCString(), s->getLength());
+      delete s;
+      if (word->next &&
+	  fabs(word->next->base - word->base) <
+	    maxIntraLineDelta * word->fontSize) {
+	if (word->next->xMin > word->xMax + minWordSpacing * word->fontSize) {
+	  (*outputFunc)(outputStream, space, spaceLen);
+	}
+      } else {
+	(*outputFunc)(outputStream, eol, eolLen);
+      }
+    }
+
+  // output the page, maintaining the original physical layout
+  } else if (physLayout) {
+
+    // collect the line fragments for the page and sort them
+    fragsSize = 256;
+    frags = (TextLineFrag *)gmallocn(fragsSize, sizeof(TextLineFrag));
+    nFrags = 0;
+    for (i = 0; i < nBlocks; ++i) {
+      blk = blocks[i];
+      for (line = blk->lines; line; line = line->next) {
+	if (nFrags == fragsSize) {
+	  fragsSize *= 2;
+	  frags = (TextLineFrag *)greallocn(frags,
+					    fragsSize, sizeof(TextLineFrag));
+	}
+	frags[nFrags].init(line, 0, line->len);
+	frags[nFrags].computeCoords(gTrue);
+	++nFrags;
+      }
+    }
+    qsort(frags, nFrags, sizeof(TextLineFrag), &TextLineFrag::cmpYXPrimaryRot);
+    i = 0;
+    while (i < nFrags) {
+      delta = maxIntraLineDelta * frags[i].line->words->fontSize;
+      for (j = i+1;
+	   j < nFrags && fabs(frags[j].base - frags[i].base) < delta;
+	   ++j) ;
+      qsort(frags + i, j - i, sizeof(TextLineFrag),
+	    &TextLineFrag::cmpXYColumnPrimaryRot);
+      i = j;
+    }
+
+#if 0 // for debugging
+    printf("*** line fragments ***\n");
+    for (i = 0; i < nFrags; ++i) {
+      frag = &frags[i];
+      printf("frag: x=%.2f..%.2f y=%.2f..%.2f base=%.2f '",
+	     frag->xMin, frag->xMax, frag->yMin, frag->yMax, frag->base);
+      for (n = 0; n < frag->len; ++n) {
+	fputc(frag->line->text[frag->start + n] & 0xff, stdout);
+      }
+      printf("'\n");
+    }
+    printf("\n");
+#endif
+
+    // generate output
+    col = 0;
+    for (i = 0; i < nFrags; ++i) {
+      frag = &frags[i];
+
+      // column alignment
+      for (; col < frag->col; ++col) {
+	(*outputFunc)(outputStream, space, spaceLen);
+      }
+
+      // print the line
+      s = new GString();
+      col += dumpFragment(frag->line->text + frag->start, frag->len, uMap, s);
+      (*outputFunc)(outputStream, s->getCString(), s->getLength());
+      delete s;
+
+      // print one or more returns if necessary
+      if (i == nFrags - 1 ||
+	  frags[i+1].col < col ||
+	  fabs(frags[i+1].base - frag->base) >
+	    maxIntraLineDelta * frag->line->words->fontSize) {
+	if (i < nFrags - 1) {
+	  d = (int)((frags[i+1].base - frag->base) /
+		    frag->line->words->fontSize);
+	  if (d < 1) {
+	    d = 1;
+	  } else if (d > 5) {
+	    d = 5;
+	  }
+	} else {
+	  d = 1;
+	}
+	for (; d > 0; --d) {
+	  (*outputFunc)(outputStream, eol, eolLen);
+	}
+	col = 0;
+      }
+    }
+
+    gfree(frags);
+
+  // output the page, "undoing" the layout
+  } else {
+    for (flow = flows; flow; flow = flow->next) {
+      for (blk = flow->blocks; blk; blk = blk->next) {
+	for (line = blk->lines; line; line = line->next) {
+	  n = line->len;
+	  if (line->hyphenated && (line->next || blk->next)) {
+	    --n;
+	  }
+	  s = new GString();
+	  dumpFragment(line->text, n, uMap, s);
+	  (*outputFunc)(outputStream, s->getCString(), s->getLength());
+	  delete s;
+	  if (!line->hyphenated) {
+	    if (line->next) {
+	      (*outputFunc)(outputStream, space, spaceLen);
+	    } else if (blk->next) {
+	      //~ this is a bit of a kludge - we should really do a more
+	      //~ intelligent determination of paragraphs
+	      if (blk->next->lines->words->fontSize ==
+		  blk->lines->words->fontSize) {
+		(*outputFunc)(outputStream, space, spaceLen);
+	      } else {
+		(*outputFunc)(outputStream, eol, eolLen);
+	      }
+	    }
+	  }
+	}
+      }
+      (*outputFunc)(outputStream, eol, eolLen);
+      (*outputFunc)(outputStream, eol, eolLen);
+    }
+  }
+
+  // end of page
+  if (pageBreaks) {
+    (*outputFunc)(outputStream, eop, eopLen);
+  }
+
+  uMap->decRefCnt();
+}
+
+void TextPage::assignColumns(TextLineFrag *frags, int nFrags, GBool oneRot) {
+  TextLineFrag *frag0, *frag1;
+  int rot, col1, col2, i, j, k;
+
+  // all text in the region has the same rotation -- recompute the
+  // column numbers based only on the text in the region
+  if (oneRot) {
+    qsort(frags, nFrags, sizeof(TextLineFrag), &TextLineFrag::cmpXYLineRot);
+    rot = frags[0].line->rot;
+    for (i = 0; i < nFrags; ++i) {
+      frag0 = &frags[i];
+      col1 = 0;
+      for (j = 0; j < i; ++j) {
+	frag1 = &frags[j];
+	col2 = 0; // make gcc happy
+	switch (rot) {
+	case 0:
+	  if (frag0->xMin >= frag1->xMax) {
+	    col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] -
+				 frag1->line->col[frag1->start]) + 1;
+	  } else {
+	    for (k = frag1->start;
+		 k < frag1->start + frag1->len &&
+		   frag0->xMin >= 0.5 * (frag1->line->edge[k] +
+					 frag1->line->edge[k+1]);
+		 ++k) ;
+	    col2 = frag1->col +
+	           frag1->line->col[k] - frag1->line->col[frag1->start];
+	  }
+	  break;
+	case 1:
+	  if (frag0->yMin >= frag1->yMax) {
+	    col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] -
+				 frag1->line->col[frag1->start]) + 1;
+	  } else {
+	    for (k = frag1->start;
+		 k < frag1->start + frag1->len &&
+		   frag0->yMin >= 0.5 * (frag1->line->edge[k] +
+					 frag1->line->edge[k+1]);
+		 ++k) ;
+	    col2 = frag1->col +
+	           frag1->line->col[k] - frag1->line->col[frag1->start];
+	  }
+	  break;
+	case 2:
+	  if (frag0->xMax <= frag1->xMin) {
+	    col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] -
+				 frag1->line->col[frag1->start]) + 1;
+	  } else {
+	    for (k = frag1->start;
+		 k < frag1->start + frag1->len &&
+		   frag0->xMax <= 0.5 * (frag1->line->edge[k] +
+					 frag1->line->edge[k+1]);
+		 ++k) ;
+	    col2 = frag1->col +
+	           frag1->line->col[k] - frag1->line->col[frag1->start];
+	  }
+	  break;
+	case 3:
+	  if (frag0->yMax <= frag1->yMin) {
+	    col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] -
+				 frag1->line->col[frag1->start]) + 1;
+	  } else {
+	    for (k = frag1->start;
+		 k < frag1->start + frag1->len &&
+		   frag0->yMax <= 0.5 * (frag1->line->edge[k] +
+					 frag1->line->edge[k+1]);
+		 ++k) ;
+	    col2 = frag1->col +
+	           frag1->line->col[k] - frag1->line->col[frag1->start];
+	  }
+	  break;
+	}
+        printf("*** col: %i/%i ***\n", col1, col2);
+	if (col2 > col1) {
+	  col1 = col2;
+	}
+      }
+      frag0->col = col1;
+    }
+
+  // the region includes text at different rotations -- use the
+  // globally assigned column numbers, offset by the minimum column
+  // number (i.e., shift everything over to column 0)
+  } else {
+    col1 = frags[0].col;
+    for (i = 1; i < nFrags; ++i) {
+      if (frags[i].col < col1) {
+	col1 = frags[i].col;
+      }
+    }
+    for (i = 0; i < nFrags; ++i) {
+      frags[i].col -= col1;
+    }
+  }
+}
+
+int TextPage::dumpFragment(Unicode *text, int len, UnicodeMap *uMap,
+			   GString *s) {
+  char lre[8], rle[8], popdf[8], buf[8];
+  int lreLen, rleLen, popdfLen, n;
+  int nCols, i, j, k;
+
+  nCols = 0;
+
+  if (uMap->isUnicode()) {
+
+    lreLen = uMap->mapUnicode(0x202a, lre, sizeof(lre));
+    rleLen = uMap->mapUnicode(0x202b, rle, sizeof(rle));
+    popdfLen = uMap->mapUnicode(0x202c, popdf, sizeof(popdf));
+
+    if (primaryLR) {
+
+      i = 0;
+      while (i < len) {
+	// output a left-to-right section
+	for (j = i; j < len && !unicodeTypeR(text[j]); ++j) ;
+	for (k = i; k < j; ++k) {
+	  n = uMap->mapUnicode(text[k], buf, sizeof(buf));
+	  s->append(buf, n);
+	  ++nCols;
+	}
+	i = j;
+	// output a right-to-left section
+	for (j = i; j < len && !unicodeTypeL(text[j]); ++j) ;
+	if (j > i) {
+	  s->append(rle, rleLen);
+	  for (k = j - 1; k >= i; --k) {
+	    n = uMap->mapUnicode(text[k], buf, sizeof(buf));
+	    s->append(buf, n);
+	    ++nCols;
+	  }
+	  s->append(popdf, popdfLen);
+	  i = j;
+	}
+      }
+
+    } else {
+
+      s->append(rle, rleLen);
+      i = len - 1;
+      while (i >= 0) {
+	// output a right-to-left section
+	for (j = i; j >= 0 && !unicodeTypeL(text[j]); --j) ;
+	for (k = i; k > j; --k) {
+	  n = uMap->mapUnicode(text[k], buf, sizeof(buf));
+	  s->append(buf, n);
+	  ++nCols;
+	}
+	i = j;
+	// output a left-to-right section
+	for (j = i; j >= 0 && !unicodeTypeR(text[j]); --j) ;
+	if (j < i) {
+	  s->append(lre, lreLen);
+	  for (k = j + 1; k <= i; ++k) {
+	    n = uMap->mapUnicode(text[k], buf, sizeof(buf));
+	    s->append(buf, n);
+	    ++nCols;
+	  }
+	  s->append(popdf, popdfLen);
+	  i = j;
+	}
+      }
+      s->append(popdf, popdfLen);
+
+    }
+
+  } else {
+    for (i = 0; i < len; ++i) {
+      n = uMap->mapUnicode(text[i], buf, sizeof(buf));
+      s->append(buf, n);
+      nCols += n;
+    }
+  }
+
+  return nCols;
+}
+
+#if TEXTOUT_WORD_LIST
+TextWordList *TextPage::makeWordList(GBool physLayout) {
+  return new TextWordList(this, physLayout);
+}
+#endif
+
+//------------------------------------------------------------------------
+// TextOutputDev
+//------------------------------------------------------------------------
+
+static void outputToFile(void *stream, char *text, int len) {
+  fwrite(text, 1, len, (FILE *)stream);
+}
+
+TextOutputDev::TextOutputDev(char *fileName, GBool physLayoutA,
+			     GBool rawOrderA, GBool append) {
+  text = NULL;
+  physLayout = physLayoutA;
+  rawOrder = rawOrderA;
+  doHTML = gFalse;
+  ok = gTrue;
+
+  // open file
+  needClose = gFalse;
+  if (fileName) {
+    if (!strcmp(fileName, "-")) {
+      outputStream = stdout;
+#ifdef WIN32
+      // keep DOS from munging the end-of-line characters
+      setmode(fileno(stdout), O_BINARY);
+#endif
+    } else if ((outputStream = fopen(fileName, append ? "ab" : "wb"))) {
+      needClose = gTrue;
+    } else {
+      error(-1, "Couldn't open text file '%s'", fileName);
+      ok = gFalse;
+      return;
+    }
+    outputFunc = &outputToFile;
+  } else {
+    outputStream = NULL;
+  }
+
+  // set up text object
+  text = new TextPage(rawOrderA);
+}
+
+TextOutputDev::TextOutputDev(TextOutputFunc func, void *stream,
+			     GBool physLayoutA, GBool rawOrderA) {
+  outputFunc = func;
+  outputStream = stream;
+  needClose = gFalse;
+  physLayout = physLayoutA;
+  rawOrder = rawOrderA;
+  doHTML = gFalse;
+  text = new TextPage(rawOrderA);
+  ok = gTrue;
+}
+
+TextOutputDev::~TextOutputDev() {
+  if (needClose) {
+#ifdef MACOS
+    ICS_MapRefNumAndAssign((short)((FILE *)outputStream)->handle);
+#endif
+    fclose((FILE *)outputStream);
+  }
+  if (text) {
+    delete text;
+  }
+}
+
+void TextOutputDev::startPage(int pageNum, GfxState *state) {
+  text->startPage(state);
+}
+
+void TextOutputDev::endPage() {
+  text->endPage();
+  text->coalesce(physLayout, doHTML);
+  return;
+  if (outputStream) {
+    text->dump(outputStream, outputFunc, physLayout);
+  }
+}
+
+void TextOutputDev::updateFont(GfxState *state) {
+  text->updateFont(state);
+}
+
+void TextOutputDev::beginString(GfxState *state, GString *s) {
+}
+
+void TextOutputDev::endString(GfxState *state) {
+}
+
+void TextOutputDev::drawChar(GfxState *state, double x, double y,
+			     double dx, double dy,
+			     double originX, double originY,
+			     CharCode c, int nBytes, Unicode *u, int uLen) {
+  text->addChar(state, x, y, dx, dy, c, nBytes, u, uLen);
+}
+
+void TextOutputDev::stroke(GfxState *state) {
+  GfxPath *path;
+  GfxSubpath *subpath;
+  double x[2], y[2];
+
+  if (!doHTML) {
+    return;
+  }
+  path = state->getPath();
+  if (path->getNumSubpaths() != 1) {
+    return;
+  }
+  subpath = path->getSubpath(0);
+  if (subpath->getNumPoints() != 2) {
+    return;
+  }
+  state->transform(subpath->getX(0), subpath->getY(0), &x[0], &y[0]);
+  state->transform(subpath->getX(1), subpath->getY(1), &x[1], &y[1]);
+
+  // look for a vertical or horizontal line
+  if (x[0] == x[1] || y[0] == y[1]) {
+    text->addUnderline(x[0], y[0], x[1], y[1]);
+  }
+}
+
+void TextOutputDev::fill(GfxState *state) {
+  GfxPath *path;
+  GfxSubpath *subpath;
+  double x[5], y[5];
+  double rx0, ry0, rx1, ry1, t;
+  int i;
+
+  if (!doHTML) {
+    return;
+  }
+  path = state->getPath();
+  if (path->getNumSubpaths() != 1) {
+    return;
+  }
+  subpath = path->getSubpath(0);
+  if (subpath->getNumPoints() != 5) {
+    return;
+  }
+  for (i = 0; i < 5; ++i) {
+    if (subpath->getCurve(i)) {
+      return;
+    }
+    state->transform(subpath->getX(i), subpath->getY(i), &x[i], &y[i]);
+  }
+
+  // look for a rectangle
+  if (x[0] == x[1] && y[1] == y[2] && x[2] == x[3] && y[3] == y[4] &&
+      x[0] == x[4] && y[0] == y[4]) {
+    rx0 = x[0];
+    ry0 = y[0];
+    rx1 = x[2];
+    ry1 = y[1];
+  } else if (y[0] == y[1] && x[1] == x[2] && y[2] == y[3] && x[3] == x[4] &&
+	     x[0] == x[4] && y[0] == y[4]) {
+    rx0 = x[0];
+    ry0 = y[0];
+    rx1 = x[1];
+    ry1 = y[2];
+  } else {
+    return;
+  }
+  if (rx1 < rx0) {
+    t = rx0;
+    rx0 = rx1;
+    rx1 = t;
+  }
+  if (ry1 < ry0) {
+    t = ry0;
+    ry0 = ry1;
+    ry1 = t;
+  }
+
+  // skinny horizontal rectangle
+  if (ry1 - ry0 < rx1 - rx0) {
+    if (ry1 - ry0 < maxUnderlineWidth) {
+      ry0 = 0.5 * (ry0 + ry1);
+      text->addUnderline(rx0, ry0, rx1, ry0);
+    }
+
+  // skinny vertical rectangle
+  } else {
+    if (rx1 - rx0 < maxUnderlineWidth) {
+      rx0 = 0.5 * (rx0 + rx1);
+      text->addUnderline(rx0, ry0, rx0, ry1);
+    }
+  }
+}
+
+void TextOutputDev::eoFill(GfxState *state) {
+  if (!doHTML) {
+    return;
+  }
+  fill(state);
+}
+
+void TextOutputDev::processLink(Link *link, Catalog *catalog) {
+  double x1, y1, x2, y2;
+  int xMin, yMin, xMax, yMax, x, y;
+
+  if (!doHTML) {
+    return;
+  }
+  link->getRect(&x1, &y1, &x2, &y2);
+  cvtUserToDev(x1, y1, &x, &y);
+  xMin = xMax = x;
+  yMin = yMax = y;
+  cvtUserToDev(x1, y2, &x, &y);
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  cvtUserToDev(x2, y1, &x, &y);
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  cvtUserToDev(x2, y2, &x, &y);
+  if (x < xMin) {
+    xMin = x;
+  } else if (x > xMax) {
+    xMax = x;
+  }
+  if (y < yMin) {
+    yMin = y;
+  } else if (y > yMax) {
+    yMax = y;
+  }
+  text->addLink(xMin, yMin, xMax, yMax, link);
+}
+
+GBool TextOutputDev::findText(Unicode *s, int len,
+			      GBool startAtTop, GBool stopAtBottom,
+			      GBool startAtLast, GBool stopAtLast,
+			      GBool caseSensitive, GBool backward,
+			      double *xMin, double *yMin,
+			      double *xMax, double *yMax) {
+  return text->findText(s, len, startAtTop, stopAtBottom,
+			startAtLast, stopAtLast, caseSensitive, backward,
+			xMin, yMin, xMax, yMax);
+}
+
+GString *TextOutputDev::getText(double xMin, double yMin,
+				double xMax, double yMax) {
+  return text->getText(xMin, yMin, xMax, yMax);
+}
+
+GBool TextOutputDev::findCharRange(int pos, int length,
+				   double *xMin, double *yMin,
+				   double *xMax, double *yMax) {
+  return text->findCharRange(pos, length, xMin, yMin, xMax, yMax);
+}
+
+#if TEXTOUT_WORD_LIST
+TextWordList *TextOutputDev::makeWordList() {
+  return text->makeWordList(physLayout);
+}
+#endif
+
+TextPage *TextOutputDev::takeText() {
+  TextPage *ret;
+
+  ret = text;
+  text = new TextPage(rawOrder);
+  return ret;
+}
diff --git a/xpdf/TextOutputDev.h b/xpdf/TextOutputDev.h
new file mode 100644
--- /dev/null
+++ b/xpdf/TextOutputDev.h
@@ -0,0 +1,661 @@
+//========================================================================
+//
+// TextOutputDev.h
+//
+// Copyright 1997-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef TEXTOUTPUTDEV_H
+#define TEXTOUTPUTDEV_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include <stdio.h>
+#include "gtypes.h"
+#include "GfxFont.h"
+#include "OutputDev.h"
+
+class GString;
+class GList;
+class GfxFont;
+class GfxState;
+class UnicodeMap;
+class Link;
+
+class TextWord;
+class TextPool;
+class TextLine;
+class TextLineFrag;
+class TextBlock;
+class TextFlow;
+class TextWordList;
+class TextPage;
+
+//------------------------------------------------------------------------
+
+typedef void (*TextOutputFunc)(void *stream, char *text, int len);
+
+//------------------------------------------------------------------------
+// TextFontInfo
+//------------------------------------------------------------------------
+
+class TextFontInfo {
+public:
+
+  TextFontInfo(GfxState *state);
+  ~TextFontInfo();
+
+  GBool matches(GfxState *state);
+
+#if TEXTOUT_WORD_LIST
+  // Get the font name (which may be NULL).
+  GString *getFontName() { return fontName; }
+
+  // Get font descriptor flags.
+  GBool isFixedWidth() { return flags & fontFixedWidth; }
+  GBool isSerif() { return flags & fontSerif; }
+  GBool isSymbolic() { return flags & fontSymbolic; }
+  GBool isItalic() { return flags & fontItalic; }
+  GBool isBold() { return flags & fontBold; }
+#endif
+
+private:
+
+  GfxFont *gfxFont;
+#if TEXTOUT_WORD_LIST
+  GString *fontName;
+  int flags;
+#endif
+
+  friend class TextWord;
+  friend class TextPage;
+};
+
+//------------------------------------------------------------------------
+// TextWord
+//------------------------------------------------------------------------
+
+class TextWord {
+public:
+
+  // Constructor.
+  TextWord(GfxState *state, int rotA, double x0, double y0,
+	   int charPosA, TextFontInfo *fontA, double fontSize);
+
+  // Destructor.
+  ~TextWord();
+
+  // Add a character to the word.
+  void addChar(GfxState *state, double x, double y,
+	       double dx, double dy, Unicode u);
+
+  // Merge <word> onto the end of <this>.
+  void merge(TextWord *word);
+
+  // Compares <this> to <word>, returning -1 (<), 0 (=), or +1 (>),
+  // based on a primary-axis comparison, e.g., x ordering if rot=0.
+  int primaryCmp(TextWord *word);
+
+  // Return the distance along the primary axis between <this> and
+  // <word>.
+  double primaryDelta(TextWord *word);
+
+  static int cmpYX(const void *p1, const void *p2);
+
+  // Get the TextFontInfo object associated with this word.
+  TextFontInfo *getFontInfo() { return font; }
+
+  // Get the next TextWord on the linked list.
+  TextWord *getNext() { return next; }
+
+#if TEXTOUT_WORD_LIST
+  int getLength() { return len; }
+  Unicode getChar(int idx) { return text[idx]; }
+  GString *getText();
+  GString *getFontName() { return font->fontName; }
+  void getColor(double *r, double *g, double *b)
+    { *r = colorR; *g = colorG; *b = colorB; }
+  void getBBox(double *xMinA, double *yMinA, double *xMaxA, double *yMaxA)
+    { *xMinA = xMin; *yMinA = yMin; *xMaxA = xMax; *yMaxA = yMax; }
+  void getCharBBox(int charIdx, double *xMinA, double *yMinA,
+		   double *xMaxA, double *yMaxA);
+  double getFontSize() { return fontSize; }
+  int getRotation() { return rot; }
+  int getCharPos() { return charPos; }
+  int getCharLen() { return charLen; }
+  GBool getSpaceAfter() { return spaceAfter; }
+#endif
+
+  GBool isUnderlined() { return underlined; }
+  Link *getLink() { return link; }
+
+private:
+
+  int rot;			// rotation, multiple of 90 degrees
+				//   (0, 1, 2, or 3)
+  double xMin, xMax;		// bounding box x coordinates
+  double yMin, yMax;		// bounding box y coordinates
+  double base;			// baseline x or y coordinate
+  Unicode *text;		// the text
+  double *edge;			// "near" edge x or y coord of each char
+				//   (plus one extra entry for the last char)
+  int len;			// length of text and edge arrays
+  int size;			// size of text and edge arrays
+  int charPos;                  // character position (within content stream)
+  int charLen;                  // number of content stream characters in
+                                //   this word
+  TextFontInfo *font;		// font information
+  double fontSize;		// font size
+  GBool spaceAfter;		// set if there is a space between this
+				//   word and the next word on the line
+  TextWord *next;		// next word in line
+
+#if TEXTOUT_WORD_LIST
+  double colorR,		// word color
+         colorG,
+         colorB;
+#endif
+
+  GBool underlined;
+  Link *link;
+
+  friend class TextPool;
+  friend class TextLine;
+  friend class TextBlock;
+  friend class TextFlow;
+  friend class TextWordList;
+  friend class TextPage;
+};
+
+//------------------------------------------------------------------------
+// TextPool
+//------------------------------------------------------------------------
+
+class TextPool {
+public:
+
+  TextPool();
+  ~TextPool();
+
+  TextWord *getPool(int baseIdx) { return pool[baseIdx - minBaseIdx]; }
+  void setPool(int baseIdx, TextWord *p) { pool[baseIdx - minBaseIdx] = p; }
+
+  int getBaseIdx(double base);
+
+  void addWord(TextWord *word);
+
+private:
+
+  int minBaseIdx;		// min baseline bucket index
+  int maxBaseIdx;		// max baseline bucket index
+  TextWord **pool;		// array of linked lists, one for each
+				//   baseline value (multiple of 4 pts)
+  TextWord *cursor;		// pointer to last-accessed word
+  int cursorBaseIdx;		// baseline bucket index of last-accessed word
+
+  friend class TextBlock;
+  friend class TextPage;
+};
+
+//------------------------------------------------------------------------
+// TextLine
+//------------------------------------------------------------------------
+
+class TextLine {
+public:
+
+  TextLine(TextBlock *blkA, int rotA, double baseA);
+  ~TextLine();
+
+  void addWord(TextWord *word);
+
+  // Return the distance along the primary axis between <this> and
+  // <line>.
+  double primaryDelta(TextLine *line);
+
+  // Compares <this> to <line>, returning -1 (<), 0 (=), or +1 (>),
+  // based on a primary-axis comparison, e.g., x ordering if rot=0.
+  int primaryCmp(TextLine *line);
+
+  // Compares <this> to <line>, returning -1 (<), 0 (=), or +1 (>),
+  // based on a secondary-axis comparison of the baselines, e.g., y
+  // ordering if rot=0.
+  int secondaryCmp(TextLine *line);
+
+  int cmpYX(TextLine *line);
+
+  static int cmpXY(const void *p1, const void *p2);
+
+  void coalesce(UnicodeMap *uMap);
+
+  // Get the head of the linked list of TextWords.
+  TextWord *getWords() { return words; }
+
+  // Get the next TextLine on the linked list.
+  TextLine *getNext() { return next; }
+
+  // Returns true if the last char of the line is a hyphen.
+  GBool isHyphenated() { return hyphenated; }
+
+private:
+
+  TextBlock *blk;		// parent block
+  int rot;			// text rotation
+  double xMin, xMax;		// bounding box x coordinates
+  double yMin, yMax;		// bounding box y coordinates
+  double base;			// baseline x or y coordinate
+  TextWord *words;		// words in this line
+  TextWord *lastWord;		// last word in this line
+  Unicode *text;		// Unicode text of the line, including
+				//   spaces between words
+  double *edge;			// "near" edge x or y coord of each char
+				//   (plus one extra entry for the last char)
+  int *col;			// starting column number of each Unicode char
+  int len;			// number of Unicode chars
+  int convertedLen;		// total number of converted characters
+  GBool hyphenated;		// set if last char is a hyphen
+  TextLine *next;		// next line in block
+
+  friend class TextLineFrag;
+  friend class TextBlock;
+  friend class TextFlow;
+  friend class TextWordList;
+  friend class TextPage;
+};
+
+//------------------------------------------------------------------------
+// TextBlock
+//------------------------------------------------------------------------
+
+class TextBlock {
+public:
+
+  TextBlock(TextPage *pageA, int rotA);
+  ~TextBlock();
+
+  void addWord(TextWord *word);
+
+  void coalesce(UnicodeMap *uMap);
+
+  // Update this block's priMin and priMax values, looking at <blk>.
+  void updatePriMinMax(TextBlock *blk);
+
+  static int cmpXYPrimaryRot(const void *p1, const void *p2);
+
+  static int cmpYXPrimaryRot(const void *p1, const void *p2);
+
+  int primaryCmp(TextBlock *blk);
+
+  double secondaryDelta(TextBlock *blk);
+
+  // Returns true if <this> is below <blk>, relative to the page's
+  // primary rotation.
+  GBool isBelow(TextBlock *blk);
+
+  // Get the head of the linked list of TextLines.
+  TextLine *getLines() { return lines; }
+
+  // Get the next TextBlock on the linked list.
+  TextBlock *getNext() { return next; }
+
+private:
+
+  TextPage *page;		// the parent page
+  int rot;			// text rotation
+  double xMin, xMax;		// bounding box x coordinates
+  double yMin, yMax;		// bounding box y coordinates
+  double priMin, priMax;	// whitespace bounding box along primary axis
+
+  TextPool *pool;		// pool of words (used only until lines
+				//   are built)
+  TextLine *lines;		// linked list of lines
+  TextLine *curLine;		// most recently added line
+  int nLines;			// number of lines
+  int charCount;		// number of characters in the block
+  int col;			// starting column
+  int nColumns;			// number of columns in the block
+
+  TextBlock *next;
+  TextBlock *stackNext;
+
+  friend class TextLine;
+  friend class TextLineFrag;
+  friend class TextFlow;
+  friend class TextWordList;
+  friend class TextPage;
+};
+
+//------------------------------------------------------------------------
+// TextFlow
+//------------------------------------------------------------------------
+
+class TextFlow {
+public:
+
+  TextFlow(TextPage *pageA, TextBlock *blk);
+  ~TextFlow();
+
+  // Add a block to the end of this flow.
+  void addBlock(TextBlock *blk);
+
+  // Returns true if <blk> fits below <prevBlk> in the flow, i.e., (1)
+  // it uses a font no larger than the last block added to the flow,
+  // and (2) it fits within the flow's [priMin, priMax] along the
+  // primary axis.
+  GBool blockFits(TextBlock *blk, TextBlock *prevBlk);
+
+  // Get the head of the linked list of TextBlocks.
+  TextBlock *getBlocks() { return blocks; }
+
+  // Get the next TextFlow on the linked list.
+  TextFlow *getNext() { return next; }
+
+private:
+
+  TextPage *page;		// the parent page
+  double xMin, xMax;		// bounding box x coordinates
+  double yMin, yMax;		// bounding box y coordinates
+  double priMin, priMax;	// whitespace bounding box along primary axis
+  TextBlock *blocks;		// blocks in flow
+  TextBlock *lastBlk;		// last block in this flow
+  TextFlow *next;
+
+  friend class TextWordList;
+  friend class TextPage;
+};
+
+#if TEXTOUT_WORD_LIST
+
+//------------------------------------------------------------------------
+// TextWordList
+//------------------------------------------------------------------------
+
+class TextWordList {
+public:
+
+  // Build a flat word list, in content stream order (if
+  // text->rawOrder is true), physical layout order (if <physLayout>
+  // is true and text->rawOrder is false), or reading order (if both
+  // flags are false).
+  TextWordList(TextPage *text, GBool physLayout);
+
+  ~TextWordList();
+
+  // Return the number of words on the list.
+  int getLength();
+
+  // Return the <idx>th word from the list.
+  TextWord *get(int idx);
+
+private:
+
+  GList *words;			// [TextWord]
+};
+
+#endif // TEXTOUT_WORD_LIST
+
+//------------------------------------------------------------------------
+// TextPage
+//------------------------------------------------------------------------
+
+class TextPage {
+public:
+
+  // Constructor.
+  TextPage(GBool rawOrderA);
+
+  // Destructor.
+  ~TextPage();
+
+  // Start a new page.
+  void startPage(GfxState *state);
+
+  // End the current page.
+  void endPage();
+
+  // Update the current font.
+  void updateFont(GfxState *state);
+
+  // Begin a new word.
+  void beginWord(GfxState *state, double x0, double y0);
+
+  // Add a character to the current word.
+  void addChar(GfxState *state, double x, double y,
+	       double dx, double dy,
+	       CharCode c, int nBytes, Unicode *u, int uLen);
+
+  // End the current word, sorting it into the list of words.
+  void endWord();
+
+  // Add a word, sorting it into the list of words.
+  void addWord(TextWord *word);
+
+  // Add a (potential) underline.
+  void addUnderline(double x0, double y0, double x1, double y1);
+
+  // Add a hyperlink.
+  void addLink(int xMin, int yMin, int xMax, int yMax, Link *link);
+
+  // Coalesce strings that look like parts of the same line.
+  void coalesce(GBool physLayout, GBool doHTML);
+
+  // Find a string.  If <startAtTop> is true, starts looking at the
+  // top of the page; else if <startAtLast> is true, starts looking
+  // immediately after the last find result; else starts looking at
+  // <xMin>,<yMin>.  If <stopAtBottom> is true, stops looking at the
+  // bottom of the page; else if <stopAtLast> is true, stops looking
+  // just before the last find result; else stops looking at
+  // <xMax>,<yMax>.
+  GBool findText(Unicode *s, int len,
+		 GBool startAtTop, GBool stopAtBottom,
+		 GBool startAtLast, GBool stopAtLast,
+		 GBool caseSensitive, GBool backward,
+		 double *xMin, double *yMin,
+		 double *xMax, double *yMax);
+
+  // Get the text which is inside the specified rectangle.
+  GString *getText(double xMin, double yMin,
+		   double xMax, double yMax);
+
+  // Find a string by character position and length.  If found, sets
+  // the text bounding rectangle and returns true; otherwise returns
+  // false.
+  GBool findCharRange(int pos, int length,
+		      double *xMin, double *yMin,
+		      double *xMax, double *yMax);
+
+  // Dump contents of page to a file.
+  void dump(void *outputStream, TextOutputFunc outputFunc,
+	    GBool physLayout);
+
+  // Get the head of the linked list of TextFlows.
+  TextFlow *getFlows() { return flows; }
+
+#if TEXTOUT_WORD_LIST
+  // Build a flat word list, in content stream order (if
+  // this->rawOrder is true), physical layout order (if <physLayout>
+  // is true and this->rawOrder is false), or reading order (if both
+  // flags are false).
+  TextWordList *makeWordList(GBool physLayout);
+#endif
+
+private:
+
+  void clear();
+  void assignColumns(TextLineFrag *frags, int nFrags, int rot);
+  int dumpFragment(Unicode *text, int len, UnicodeMap *uMap, GString *s);
+
+  GBool rawOrder;		// keep text in content stream order
+
+  double pageWidth, pageHeight;	// width and height of current page
+  TextWord *curWord;		// currently active string
+  int charPos;			// next character position (within content
+				//   stream)
+  TextFontInfo *curFont;	// current font
+  double curFontSize;		// current font size
+  int nest;			// current nesting level (for Type 3 fonts)
+  int nTinyChars;		// number of "tiny" chars seen so far
+  GBool lastCharOverlap;	// set if the last added char overlapped the
+				//   previous char
+
+  TextPool *pools[4];		// a "pool" of TextWords for each rotation
+  TextFlow *flows;		// linked list of flows
+  TextBlock **blocks;		// array of blocks, in yx order
+  int nBlocks;			// number of blocks
+  int primaryRot;		// primary rotation
+  GBool primaryLR;		// primary direction (true means L-to-R,
+				//   false means R-to-L)
+  TextWord *rawWords;		// list of words, in raw order (only if
+				//   rawOrder is set)
+  TextWord *rawLastWord;	// last word on rawWords list
+
+  GList *fonts;			// all font info objects used on this
+				//   page [TextFontInfo]
+
+  double lastFindXMin,		// coordinates of the last "find" result
+         lastFindYMin;
+  GBool haveLastFind;
+
+  GList *underlines;		// [TextUnderline]
+  GList *links;			// [TextLink]
+
+  friend class TextLine;
+  friend class TextLineFrag;
+  friend class TextBlock;
+  friend class TextFlow;
+  friend class TextWordList;
+};
+
+//------------------------------------------------------------------------
+// TextOutputDev
+//------------------------------------------------------------------------
+
+class TextOutputDev: public OutputDev {
+public:
+
+  // Open a text output file.  If <fileName> is NULL, no file is
+  // written (this is useful, e.g., for searching text).  If
+  // <physLayoutA> is true, the original physical layout of the text
+  // is maintained.  If <rawOrder> is true, the text is kept in
+  // content stream order.
+  TextOutputDev(char *fileName, GBool physLayoutA,
+		GBool rawOrderA, GBool append);
+
+  // Create a TextOutputDev which will write to a generic stream.  If
+  // <physLayoutA> is true, the original physical layout of the text
+  // is maintained.  If <rawOrder> is true, the text is kept in
+  // content stream order.
+  TextOutputDev(TextOutputFunc func, void *stream,
+		GBool physLayoutA, GBool rawOrderA);
+
+  // Destructor.
+  virtual ~TextOutputDev();
+
+  // Check if file was successfully created.
+  virtual GBool isOk() { return ok; }
+
+  //---- get info about output device
+
+  // Does this device use upside-down coordinates?
+  // (Upside-down means (0,0) is the top left corner of the page.)
+  virtual GBool upsideDown() { return gTrue; }
+
+  // Does this device use drawChar() or drawString()?
+  virtual GBool useDrawChar() { return gTrue; }
+
+  // Does this device use beginType3Char/endType3Char?  Otherwise,
+  // text in Type 3 fonts will be drawn with drawChar/drawString.
+  virtual GBool interpretType3Chars() { return gFalse; }
+
+  // Does this device need non-text content?
+  virtual GBool needNonText() { return gFalse; }
+
+  //----- initialization and control
+
+  // Start a page.
+  virtual void startPage(int pageNum, GfxState *state);
+
+  // End a page.
+  virtual void endPage();
+
+  //----- update text state
+  virtual void updateFont(GfxState *state);
+
+  //----- text drawing
+  virtual void beginString(GfxState *state, GString *s);
+  virtual void endString(GfxState *state);
+  virtual void drawChar(GfxState *state, double x, double y,
+			double dx, double dy,
+			double originX, double originY,
+			CharCode c, int nBytes, Unicode *u, int uLen);
+
+  //----- path painting
+  virtual void stroke(GfxState *state);
+  virtual void fill(GfxState *state);
+  virtual void eoFill(GfxState *state);
+
+  //----- link borders
+  virtual void processLink(Link *link, Catalog *catalog);
+
+  //----- special access
+
+  // Find a string.  If <startAtTop> is true, starts looking at the
+  // top of the page; else if <startAtLast> is true, starts looking
+  // immediately after the last find result; else starts looking at
+  // <xMin>,<yMin>.  If <stopAtBottom> is true, stops looking at the
+  // bottom of the page; else if <stopAtLast> is true, stops looking
+  // just before the last find result; else stops looking at
+  // <xMax>,<yMax>.
+  GBool findText(Unicode *s, int len,
+		 GBool startAtTop, GBool stopAtBottom,
+		 GBool startAtLast, GBool stopAtLast,
+		 GBool caseSensitive, GBool backward,
+		 double *xMin, double *yMin,
+		 double *xMax, double *yMax);
+
+  // Get the text which is inside the specified rectangle.
+  GString *getText(double xMin, double yMin,
+		   double xMax, double yMax);
+
+  // Find a string by character position and length.  If found, sets
+  // the text bounding rectangle and returns true; otherwise returns
+  // false.
+  GBool findCharRange(int pos, int length,
+		      double *xMin, double *yMin,
+		      double *xMax, double *yMax);
+
+#if TEXTOUT_WORD_LIST
+  // Build a flat word list, in content stream order (if
+  // this->rawOrder is true), physical layout order (if
+  // this->physLayout is true and this->rawOrder is false), or reading
+  // order (if both flags are false).
+  TextWordList *makeWordList();
+#endif
+
+  // Returns the TextPage object for the last rasterized page,
+  // transferring ownership to the caller.
+  TextPage *takeText();
+
+  // Turn extra processing for HTML conversion on or off.
+  void enableHTMLExtras(GBool doHTMLA) { doHTML = doHTMLA; }
+
+private:
+
+  TextOutputFunc outputFunc;	// output function
+  void *outputStream;		// output stream
+  GBool needClose;		// need to close the output file?
+				//   (only if outputStream is a FILE*)
+  TextPage *text;		// text for the current page
+  GBool physLayout;		// maintain original physical layout when
+				//   dumping text
+  GBool rawOrder;		// keep text in content stream order
+  GBool doHTML;			// extra processing for HTML conversion
+  GBool ok;			// set up ok?
+};
+
+#endif
diff --git a/xpdf/UTF8.h b/xpdf/UTF8.h
new file mode 100644
--- /dev/null
+++ b/xpdf/UTF8.h
@@ -0,0 +1,56 @@
+//========================================================================
+//
+// UTF8.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+static int mapUTF8(Unicode u, char *buf, int bufSize) {
+  if        (u <= 0x0000007f) {
+    if (bufSize < 1) {
+      return 0;
+    }
+    buf[0] = (char)u;
+    return 1;
+  } else if (u <= 0x000007ff) {
+    if (bufSize < 2) {
+      return 0;
+    }
+    buf[0] = (char)(0xc0 + (u >> 6));
+    buf[1] = (char)(0x80 + (u & 0x3f));
+    return 2;
+  } else if (u <= 0x0000ffff) {
+    if (bufSize < 3) {
+      return 0;
+    }
+    buf[0] = (char)(0xe0 + (u >> 12));
+    buf[1] = (char)(0x80 + ((u >> 6) & 0x3f));
+    buf[2] = (char)(0x80 + (u & 0x3f));
+    return 3;
+  } else if (u <= 0x0010ffff) {
+    if (bufSize < 4) {
+      return 0;
+    }
+    buf[0] = (char)(0xf0 + (u >> 18));
+    buf[1] = (char)(0x80 + ((u >> 12) & 0x3f));
+    buf[2] = (char)(0x80 + ((u >> 6) & 0x3f));
+    buf[3] = (char)(0x80 + (u & 0x3f));
+    return 4;
+  } else {
+    return 0;
+  }
+}
+
+static int mapUCS2(Unicode u, char *buf, int bufSize) {
+  if (u <= 0xffff) {
+    if (bufSize < 2) {
+      return 0;
+    }
+    buf[0] = (char)((u >> 8) & 0xff);
+    buf[1] = (char)(u & 0xff);
+    return 2;
+  } else {
+    return 0;
+  }
+}
diff --git a/xpdf/UnicodeMap.cc b/xpdf/UnicodeMap.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/UnicodeMap.cc
@@ -0,0 +1,293 @@
+//========================================================================
+//
+// UnicodeMap.cc
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include "gmem.h"
+#include "gfile.h"
+#include "GString.h"
+#include "GList.h"
+#include "Error.h"
+#include "GlobalParams.h"
+#include "UnicodeMap.h"
+
+//------------------------------------------------------------------------
+
+#define maxExtCode 16
+
+struct UnicodeMapExt {
+  Unicode u;			// Unicode char
+  char code[maxExtCode];
+  Guint nBytes;
+};
+
+//------------------------------------------------------------------------
+
+UnicodeMap *UnicodeMap::parse(GString *encodingNameA) {
+  FILE *f;
+  UnicodeMap *map;
+  UnicodeMapRange *range;
+  UnicodeMapExt *eMap;
+  int size, eMapsSize;
+  char buf[256];
+  int line, nBytes, i, x;
+  char *tok1, *tok2, *tok3;
+
+  if (!(f = globalParams->getUnicodeMapFile(encodingNameA))) {
+    error(-1, "Couldn't find unicodeMap file for the '%s' encoding",
+	  encodingNameA->getCString());
+    return NULL;
+  }
+
+  map = new UnicodeMap(encodingNameA->copy());
+
+  size = 8;
+  map->ranges = (UnicodeMapRange *)gmallocn(size, sizeof(UnicodeMapRange));
+  eMapsSize = 0;
+
+  line = 1;
+  while (getLine(buf, sizeof(buf), f)) {
+    if ((tok1 = strtok(buf, " \t\r\n")) &&
+	(tok2 = strtok(NULL, " \t\r\n"))) {
+      if (!(tok3 = strtok(NULL, " \t\r\n"))) {
+	tok3 = tok2;
+	tok2 = tok1;
+      }
+      nBytes = strlen(tok3) / 2;
+      if (nBytes <= 4) {
+	if (map->len == size) {
+	  size *= 2;
+	  map->ranges = (UnicodeMapRange *)
+	    greallocn(map->ranges, size, sizeof(UnicodeMapRange));
+	}
+	range = &map->ranges[map->len];
+	sscanf(tok1, "%x", &range->start);
+	sscanf(tok2, "%x", &range->end);
+	sscanf(tok3, "%x", &range->code);
+	range->nBytes = nBytes;
+	++map->len;
+      } else if (tok2 == tok1) {
+	if (map->eMapsLen == eMapsSize) {
+	  eMapsSize += 16;
+	  map->eMaps = (UnicodeMapExt *)
+	    greallocn(map->eMaps, eMapsSize, sizeof(UnicodeMapExt));
+	}
+	eMap = &map->eMaps[map->eMapsLen];
+	sscanf(tok1, "%x", &eMap->u);
+	for (i = 0; i < nBytes; ++i) {
+	  sscanf(tok3 + i*2, "%2x", &x);
+	  eMap->code[i] = (char)x;
+	}
+	eMap->nBytes = nBytes;
+	++map->eMapsLen;
+      } else {
+	error(-1, "Bad line (%d) in unicodeMap file for the '%s' encoding",
+	      line, encodingNameA->getCString());
+      }
+    } else {
+      error(-1, "Bad line (%d) in unicodeMap file for the '%s' encoding",
+	    line, encodingNameA->getCString());
+    }
+    ++line;
+  }
+
+  fclose(f);
+
+  return map;
+}
+
+UnicodeMap::UnicodeMap(GString *encodingNameA) {
+  encodingName = encodingNameA;
+  unicodeOut = gFalse;
+  kind = unicodeMapUser;
+  ranges = NULL;
+  len = 0;
+  eMaps = NULL;
+  eMapsLen = 0;
+  refCnt = 1;
+#if MULTITHREADED
+  gInitMutex(&mutex);
+#endif
+}
+
+UnicodeMap::UnicodeMap(char *encodingNameA, GBool unicodeOutA,
+		       UnicodeMapRange *rangesA, int lenA) {
+  encodingName = new GString(encodingNameA);
+  unicodeOut = unicodeOutA;
+  kind = unicodeMapResident;
+  ranges = rangesA;
+  len = lenA;
+  eMaps = NULL;
+  eMapsLen = 0;
+  refCnt = 1;
+#if MULTITHREADED
+  gInitMutex(&mutex);
+#endif
+}
+
+UnicodeMap::UnicodeMap(char *encodingNameA, GBool unicodeOutA,
+		       UnicodeMapFunc funcA) {
+  encodingName = new GString(encodingNameA);
+  unicodeOut = unicodeOutA;
+  kind = unicodeMapFunc;
+  func = funcA;
+  eMaps = NULL;
+  eMapsLen = 0;
+  refCnt = 1;
+#if MULTITHREADED
+  gInitMutex(&mutex);
+#endif
+}
+
+UnicodeMap::~UnicodeMap() {
+  delete encodingName;
+  if (kind == unicodeMapUser && ranges) {
+    gfree(ranges);
+  }
+  if (eMaps) {
+    gfree(eMaps);
+  }
+#if MULTITHREADED
+  gDestroyMutex(&mutex);
+#endif
+}
+
+void UnicodeMap::incRefCnt() {
+#if MULTITHREADED
+  gLockMutex(&mutex);
+#endif
+  ++refCnt;
+#if MULTITHREADED
+  gUnlockMutex(&mutex);
+#endif
+}
+
+void UnicodeMap::decRefCnt() {
+  GBool done;
+
+#if MULTITHREADED
+  gLockMutex(&mutex);
+#endif
+  done = --refCnt == 0;
+#if MULTITHREADED
+  gUnlockMutex(&mutex);
+#endif
+  if (done) {
+    delete this;
+  }
+}
+
+GBool UnicodeMap::match(GString *encodingNameA) {
+  return !encodingName->cmp(encodingNameA);
+}
+
+int UnicodeMap::mapUnicode(Unicode u, char *buf, int bufSize) {
+  int a, b, m, n, i, j;
+  Guint code;
+
+  if (kind == unicodeMapFunc) {
+    return (*func)(u, buf, bufSize);
+  }
+
+  a = 0;
+  b = len;
+  if (u >= ranges[a].start) {
+    // invariant: ranges[a].start <= u < ranges[b].start
+    while (b - a > 1) {
+      m = (a + b) / 2;
+      if (u >= ranges[m].start) {
+	a = m;
+      } else if (u < ranges[m].start) {
+	b = m;
+      }
+    }
+    if (u <= ranges[a].end) {
+      n = ranges[a].nBytes;
+      if (n > bufSize) {
+	return 0;
+      }
+      code = ranges[a].code + (u - ranges[a].start);
+      for (i = n - 1; i >= 0; --i) {
+	buf[i] = (char)(code & 0xff);
+	code >>= 8;
+      }
+      return n;
+    }
+  }
+
+  for (i = 0; i < eMapsLen; ++i) {
+    if (eMaps[i].u == u) {
+      n = eMaps[i].nBytes;
+      for (j = 0; j < n; ++j) {
+	buf[j] = eMaps[i].code[j];
+      }
+      return n;
+    }
+  }
+
+  return 0;
+}
+
+//------------------------------------------------------------------------
+
+UnicodeMapCache::UnicodeMapCache() {
+  int i;
+
+  for (i = 0; i < unicodeMapCacheSize; ++i) {
+    cache[i] = NULL;
+  }
+}
+
+UnicodeMapCache::~UnicodeMapCache() {
+  int i;
+
+  for (i = 0; i < unicodeMapCacheSize; ++i) {
+    if (cache[i]) {
+      cache[i]->decRefCnt();
+    }
+  }
+}
+
+UnicodeMap *UnicodeMapCache::getUnicodeMap(GString *encodingName) {
+  UnicodeMap *map;
+  int i, j;
+
+  if (cache[0] && cache[0]->match(encodingName)) {
+    cache[0]->incRefCnt();
+    return cache[0];
+  }
+  for (i = 1; i < unicodeMapCacheSize; ++i) {
+    if (cache[i] && cache[i]->match(encodingName)) {
+      map = cache[i];
+      for (j = i; j >= 1; --j) {
+	cache[j] = cache[j - 1];
+      }
+      cache[0] = map;
+      map->incRefCnt();
+      return map;
+    }
+  }
+  if ((map = UnicodeMap::parse(encodingName))) {
+    if (cache[unicodeMapCacheSize - 1]) {
+      cache[unicodeMapCacheSize - 1]->decRefCnt();
+    }
+    for (j = unicodeMapCacheSize - 1; j >= 1; --j) {
+      cache[j] = cache[j - 1];
+    }
+    cache[0] = map;
+    map->incRefCnt();
+    return map;
+  }
+  return NULL;
+}
diff --git a/xpdf/UnicodeMap.h b/xpdf/UnicodeMap.h
new file mode 100644
--- /dev/null
+++ b/xpdf/UnicodeMap.h
@@ -0,0 +1,123 @@
+//========================================================================
+//
+// UnicodeMap.h
+//
+// Mapping from Unicode to an encoding.
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef UNICODEMAP_H
+#define UNICODEMAP_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "CharTypes.h"
+
+#if MULTITHREADED
+#include "GMutex.h"
+#endif
+
+class GString;
+
+//------------------------------------------------------------------------
+
+enum UnicodeMapKind {
+  unicodeMapUser,		// read from a file
+  unicodeMapResident,		// static list of ranges
+  unicodeMapFunc		// function pointer
+};
+
+typedef int (*UnicodeMapFunc)(Unicode u, char *buf, int bufSize);
+
+struct UnicodeMapRange {
+  Unicode start, end;		// range of Unicode chars
+  Guint code, nBytes;		// first output code
+};
+
+struct UnicodeMapExt;
+
+//------------------------------------------------------------------------
+
+class UnicodeMap {
+public:
+
+  // Create the UnicodeMap specified by <encodingName>.  Sets the
+  // initial reference count to 1.  Returns NULL on failure.
+  static UnicodeMap *parse(GString *encodingNameA);
+
+  // Create a resident UnicodeMap.
+  UnicodeMap(char *encodingNameA, GBool unicodeOutA,
+	     UnicodeMapRange *rangesA, int lenA);
+
+  // Create a resident UnicodeMap that uses a function instead of a
+  // list of ranges.
+  UnicodeMap(char *encodingNameA, GBool unicodeOutA,
+	     UnicodeMapFunc funcA);
+
+  ~UnicodeMap();
+
+  void incRefCnt();
+  void decRefCnt();
+
+  GString *getEncodingName() { return encodingName; }
+
+  GBool isUnicode() { return unicodeOut; }
+
+  // Return true if this UnicodeMap matches the specified
+  // <encodingNameA>.
+  GBool match(GString *encodingNameA);
+
+  // Map Unicode to the target encoding.  Fills in <buf> with the
+  // output and returns the number of bytes used.  Output will be
+  // truncated at <bufSize> bytes.  No string terminator is written.
+  // Returns 0 if no mapping is found.
+  int mapUnicode(Unicode u, char *buf, int bufSize);
+
+private:
+
+  UnicodeMap(GString *encodingNameA);
+
+  GString *encodingName;
+  UnicodeMapKind kind;
+  GBool unicodeOut;
+  union {
+    UnicodeMapRange *ranges;	// (user, resident)
+    UnicodeMapFunc func;	// (func)
+  };
+  int len;			// (user, resident)
+  UnicodeMapExt *eMaps;		// (user)
+  int eMapsLen;			// (user)
+  int refCnt;
+#if MULTITHREADED
+  GMutex mutex;
+#endif
+};
+
+//------------------------------------------------------------------------
+
+#define unicodeMapCacheSize 4
+
+class UnicodeMapCache {
+public:
+
+  UnicodeMapCache();
+  ~UnicodeMapCache();
+
+  // Get the UnicodeMap for <encodingName>.  Increments its reference
+  // count; there will be one reference for the cache plus one for the
+  // caller of this function.  Returns NULL on failure.
+  UnicodeMap *getUnicodeMap(GString *encodingName);
+
+private:
+
+  UnicodeMap *cache[unicodeMapCacheSize];
+};
+
+#endif
diff --git a/xpdf/UnicodeMapTables.h b/xpdf/UnicodeMapTables.h
new file mode 100644
--- /dev/null
+++ b/xpdf/UnicodeMapTables.h
@@ -0,0 +1,361 @@
+//========================================================================
+//
+// UnicodeMapTables.h
+//
+// Copyright 2001-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+static UnicodeMapRange latin1UnicodeMapRanges[] = {
+  { 0x000a, 0x000a, 0x0a, 1 },
+  { 0x000c, 0x000d, 0x0c, 1 },
+  { 0x0020, 0x007e, 0x20, 1 },
+  { 0x00a0, 0x00a0, 0x20, 1 },
+  { 0x00a1, 0x00ac, 0xa1, 1 },
+  { 0x00ae, 0x00ff, 0xae, 1 },
+  { 0x010c, 0x010c, 0x43, 1 },
+  { 0x010d, 0x010d, 0x63, 1 },
+  { 0x0131, 0x0131, 0x69, 1 },
+  { 0x0141, 0x0141, 0x4c, 1 },
+  { 0x0142, 0x0142, 0x6c, 1 },
+  { 0x0152, 0x0152, 0x4f45, 2 },
+  { 0x0153, 0x0153, 0x6f65, 2 },
+  { 0x0160, 0x0160, 0x53, 1 },
+  { 0x0161, 0x0161, 0x73, 1 },
+  { 0x0178, 0x0178, 0x59, 1 },
+  { 0x017d, 0x017d, 0x5a, 1 },
+  { 0x017e, 0x017e, 0x7a, 1 },
+  { 0x02c6, 0x02c6, 0x5e, 1 },
+  { 0x02da, 0x02da, 0xb0, 1 },
+  { 0x02dc, 0x02dc, 0x7e, 1 },
+  { 0x2013, 0x2013, 0xad, 1 },
+  { 0x2014, 0x2014, 0x2d2d, 2 },
+  { 0x2018, 0x2018, 0x60, 1 },
+  { 0x2019, 0x2019, 0x27, 1 },
+  { 0x201a, 0x201a, 0x2c, 1 },
+  { 0x201c, 0x201c, 0x22, 1 },
+  { 0x201d, 0x201d, 0x22, 1 },
+  { 0x201e, 0x201e, 0x2c2c, 2 },
+  { 0x2022, 0x2022, 0xb7, 1 },
+  { 0x2026, 0x2026, 0x2e2e2e, 3 },
+  { 0x2039, 0x2039, 0x3c, 1 },
+  { 0x203a, 0x203a, 0x3e, 1 },
+  { 0x2044, 0x2044, 0x2f, 1 },
+  { 0x2122, 0x2122, 0x544d, 2 },
+  { 0x2212, 0x2212, 0x2d, 1 },
+  { 0xf6f9, 0xf6f9, 0x4c, 1 },
+  { 0xf6fa, 0xf6fa, 0x4f45, 2 },
+  { 0xf6fc, 0xf6fc, 0xb0, 1 },
+  { 0xf6fd, 0xf6fd, 0x53, 1 },
+  { 0xf6fe, 0xf6fe, 0x7e, 1 },
+  { 0xf6ff, 0xf6ff, 0x5a, 1 },
+  { 0xf721, 0xf721, 0x21, 1 },
+  { 0xf724, 0xf724, 0x24, 1 },
+  { 0xf726, 0xf726, 0x26, 1 },
+  { 0xf730, 0xf739, 0x30, 1 },
+  { 0xf73f, 0xf73f, 0x3f, 1 },
+  { 0xf761, 0xf77a, 0x41, 1 },
+  { 0xf7a1, 0xf7a2, 0xa1, 1 },
+  { 0xf7bf, 0xf7bf, 0xbf, 1 },
+  { 0xf7e0, 0xf7f6, 0xc0, 1 },
+  { 0xf7f8, 0xf7fe, 0xd8, 1 },
+  { 0xf7ff, 0xf7ff, 0x59, 1 },
+  { 0xfb00, 0xfb00, 0x6666, 2 },
+  { 0xfb01, 0xfb01, 0x6669, 2 },
+  { 0xfb02, 0xfb02, 0x666c, 2 },
+  { 0xfb03, 0xfb03, 0x666669, 3 },
+  { 0xfb04, 0xfb04, 0x66666c, 3 }
+};
+#define latin1UnicodeMapLen (sizeof(latin1UnicodeMapRanges) / sizeof(UnicodeMapRange))
+
+static UnicodeMapRange ascii7UnicodeMapRanges[] = {
+  { 0x000a, 0x000a, 0x0a, 1 },
+  { 0x000c, 0x000d, 0x0c, 1 },
+  { 0x0020, 0x005f, 0x20, 1 },
+  { 0x0061, 0x007e, 0x61, 1 },
+  { 0x00a6, 0x00a6, 0x7c, 1 },
+  { 0x00a9, 0x00a9, 0x286329, 3 },
+  { 0x00ae, 0x00ae, 0x285229, 3 },
+  { 0x00b7, 0x00b7, 0x2a, 1 },
+  { 0x00bc, 0x00bc, 0x312f34, 3 },
+  { 0x00bd, 0x00bd, 0x312f32, 3 },
+  { 0x00be, 0x00be, 0x332f34, 3 },
+  { 0x00c0, 0x00c0, 0x41, 1 },
+  { 0x00c1, 0x00c1, 0x41, 1 },
+  { 0x00c2, 0x00c2, 0x41, 1 },
+  { 0x00c3, 0x00c3, 0x41, 1 },
+  { 0x00c4, 0x00c4, 0x41, 1 },
+  { 0x00c5, 0x00c5, 0x41, 1 },
+  { 0x00c6, 0x00c6, 0x4145, 2 },
+  { 0x00c7, 0x00c7, 0x43, 1 },
+  { 0x00c8, 0x00c8, 0x45, 1 },
+  { 0x00c9, 0x00c9, 0x45, 1 },
+  { 0x00ca, 0x00ca, 0x45, 1 },
+  { 0x00cb, 0x00cb, 0x45, 1 },
+  { 0x00cc, 0x00cc, 0x49, 1 },
+  { 0x00cd, 0x00cd, 0x49, 1 },
+  { 0x00ce, 0x00ce, 0x49, 1 },
+  { 0x00cf, 0x00cf, 0x49, 1 },
+  { 0x00d1, 0x00d2, 0x4e, 1 },
+  { 0x00d3, 0x00d3, 0x4f, 1 },
+  { 0x00d4, 0x00d4, 0x4f, 1 },
+  { 0x00d5, 0x00d5, 0x4f, 1 },
+  { 0x00d6, 0x00d6, 0x4f, 1 },
+  { 0x00d7, 0x00d7, 0x78, 1 },
+  { 0x00d8, 0x00d8, 0x4f, 1 },
+  { 0x00d9, 0x00d9, 0x55, 1 },
+  { 0x00da, 0x00da, 0x55, 1 },
+  { 0x00db, 0x00db, 0x55, 1 },
+  { 0x00dc, 0x00dc, 0x55, 1 },
+  { 0x00dd, 0x00dd, 0x59, 1 },
+  { 0x00e0, 0x00e0, 0x61, 1 },
+  { 0x00e1, 0x00e1, 0x61, 1 },
+  { 0x00e2, 0x00e2, 0x61, 1 },
+  { 0x00e3, 0x00e3, 0x61, 1 },
+  { 0x00e4, 0x00e4, 0x61, 1 },
+  { 0x00e5, 0x00e5, 0x61, 1 },
+  { 0x00e6, 0x00e6, 0x6165, 2 },
+  { 0x00e7, 0x00e7, 0x63, 1 },
+  { 0x00e8, 0x00e8, 0x65, 1 },
+  { 0x00e9, 0x00e9, 0x65, 1 },
+  { 0x00ea, 0x00ea, 0x65, 1 },
+  { 0x00eb, 0x00eb, 0x65, 1 },
+  { 0x00ec, 0x00ec, 0x69, 1 },
+  { 0x00ed, 0x00ed, 0x69, 1 },
+  { 0x00ee, 0x00ee, 0x69, 1 },
+  { 0x00ef, 0x00ef, 0x69, 1 },
+  { 0x00f1, 0x00f2, 0x6e, 1 },
+  { 0x00f3, 0x00f3, 0x6f, 1 },
+  { 0x00f4, 0x00f4, 0x6f, 1 },
+  { 0x00f5, 0x00f5, 0x6f, 1 },
+  { 0x00f6, 0x00f6, 0x6f, 1 },
+  { 0x00f7, 0x00f7, 0x2f, 1 },
+  { 0x00f8, 0x00f8, 0x6f, 1 },
+  { 0x00f9, 0x00f9, 0x75, 1 },
+  { 0x00fa, 0x00fa, 0x75, 1 },
+  { 0x00fb, 0x00fb, 0x75, 1 },
+  { 0x00fc, 0x00fc, 0x75, 1 },
+  { 0x00fd, 0x00fd, 0x79, 1 },
+  { 0x00ff, 0x00ff, 0x79, 1 },
+  { 0x0131, 0x0131, 0x69, 1 },
+  { 0x0141, 0x0141, 0x4c, 1 },
+  { 0x0152, 0x0152, 0x4f45, 2 },
+  { 0x0153, 0x0153, 0x6f65, 2 },
+  { 0x0160, 0x0160, 0x53, 1 },
+  { 0x0178, 0x0178, 0x59, 1 },
+  { 0x017d, 0x017d, 0x5a, 1 },
+  { 0x2013, 0x2013, 0x2d, 1 },
+  { 0x2014, 0x2014, 0x2d2d, 2 },
+  { 0x2018, 0x2018, 0x60, 1 },
+  { 0x2019, 0x2019, 0x27, 1 },
+  { 0x201c, 0x201c, 0x22, 1 },
+  { 0x201d, 0x201d, 0x22, 1 },
+  { 0x2022, 0x2022, 0x2a, 1 },
+  { 0x2026, 0x2026, 0x2e2e2e, 3 },
+  { 0x2122, 0x2122, 0x544d, 2 },
+  { 0x2212, 0x2212, 0x2d, 1 },
+  { 0xf6f9, 0xf6f9, 0x4c, 1 },
+  { 0xf6fa, 0xf6fa, 0x4f45, 2 },
+  { 0xf6fd, 0xf6fd, 0x53, 1 },
+  { 0xf6fe, 0xf6fe, 0x7e, 1 },
+  { 0xf6ff, 0xf6ff, 0x5a, 1 },
+  { 0xf721, 0xf721, 0x21, 1 },
+  { 0xf724, 0xf724, 0x24, 1 },
+  { 0xf726, 0xf726, 0x26, 1 },
+  { 0xf730, 0xf739, 0x30, 1 },
+  { 0xf73f, 0xf73f, 0x3f, 1 },
+  { 0xf761, 0xf77a, 0x41, 1 },
+  { 0xf7e0, 0xf7e0, 0x41, 1 },
+  { 0xf7e1, 0xf7e1, 0x41, 1 },
+  { 0xf7e2, 0xf7e2, 0x41, 1 },
+  { 0xf7e3, 0xf7e3, 0x41, 1 },
+  { 0xf7e4, 0xf7e4, 0x41, 1 },
+  { 0xf7e5, 0xf7e5, 0x41, 1 },
+  { 0xf7e6, 0xf7e6, 0x4145, 2 },
+  { 0xf7e7, 0xf7e7, 0x43, 1 },
+  { 0xf7e8, 0xf7e8, 0x45, 1 },
+  { 0xf7e9, 0xf7e9, 0x45, 1 },
+  { 0xf7ea, 0xf7ea, 0x45, 1 },
+  { 0xf7eb, 0xf7eb, 0x45, 1 },
+  { 0xf7ec, 0xf7ec, 0x49, 1 },
+  { 0xf7ed, 0xf7ed, 0x49, 1 },
+  { 0xf7ee, 0xf7ee, 0x49, 1 },
+  { 0xf7ef, 0xf7ef, 0x49, 1 },
+  { 0xf7f1, 0xf7f2, 0x4e, 1 },
+  { 0xf7f3, 0xf7f3, 0x4f, 1 },
+  { 0xf7f4, 0xf7f4, 0x4f, 1 },
+  { 0xf7f5, 0xf7f5, 0x4f, 1 },
+  { 0xf7f6, 0xf7f6, 0x4f, 1 },
+  { 0xf7f8, 0xf7f8, 0x4f, 1 },
+  { 0xf7f9, 0xf7f9, 0x55, 1 },
+  { 0xf7fa, 0xf7fa, 0x55, 1 },
+  { 0xf7fb, 0xf7fb, 0x55, 1 },
+  { 0xf7fc, 0xf7fc, 0x55, 1 },
+  { 0xf7fd, 0xf7fd, 0x59, 1 },
+  { 0xf7ff, 0xf7ff, 0x59, 1 },
+  { 0xfb00, 0xfb00, 0x6666, 2 },
+  { 0xfb01, 0xfb01, 0x6669, 2 },
+  { 0xfb02, 0xfb02, 0x666c, 2 },
+  { 0xfb03, 0xfb03, 0x666669, 3 },
+  { 0xfb04, 0xfb04, 0x66666c, 3 }
+};
+#define ascii7UnicodeMapLen (sizeof(ascii7UnicodeMapRanges) / sizeof(UnicodeMapRange))
+
+static UnicodeMapRange symbolUnicodeMapRanges[] = {
+  { 0x0020, 0x0021, 0x20, 1 },
+  { 0x0023, 0x0023, 0x23, 1 },
+  { 0x0025, 0x0026, 0x25, 1 },
+  { 0x0028, 0x0029, 0x28, 1 },
+  { 0x002b, 0x002c, 0x2b, 1 },
+  { 0x002e, 0x003f, 0x2e, 1 },
+  { 0x005b, 0x005b, 0x5b, 1 },
+  { 0x005d, 0x005d, 0x5d, 1 },
+  { 0x005f, 0x005f, 0x5f, 1 },
+  { 0x007b, 0x007d, 0x7b, 1 },
+  { 0x00ac, 0x00ac, 0xd8, 1 },
+  { 0x00b0, 0x00b1, 0xb0, 1 },
+  { 0x00b5, 0x00b5, 0x6d, 1 },
+  { 0x00d7, 0x00d7, 0xb4, 1 },
+  { 0x00f7, 0x00f7, 0xb8, 1 },
+  { 0x0192, 0x0192, 0xa6, 1 },
+  { 0x0391, 0x0392, 0x41, 1 },
+  { 0x0393, 0x0393, 0x47, 1 },
+  { 0x0395, 0x0395, 0x45, 1 },
+  { 0x0396, 0x0396, 0x5a, 1 },
+  { 0x0397, 0x0397, 0x48, 1 },
+  { 0x0398, 0x0398, 0x51, 1 },
+  { 0x0399, 0x0399, 0x49, 1 },
+  { 0x039a, 0x039d, 0x4b, 1 },
+  { 0x039e, 0x039e, 0x58, 1 },
+  { 0x039f, 0x03a0, 0x4f, 1 },
+  { 0x03a1, 0x03a1, 0x52, 1 },
+  { 0x03a3, 0x03a5, 0x53, 1 },
+  { 0x03a6, 0x03a6, 0x46, 1 },
+  { 0x03a7, 0x03a7, 0x43, 1 },
+  { 0x03a8, 0x03a8, 0x59, 1 },
+  { 0x03b1, 0x03b2, 0x61, 1 },
+  { 0x03b3, 0x03b3, 0x67, 1 },
+  { 0x03b4, 0x03b5, 0x64, 1 },
+  { 0x03b6, 0x03b6, 0x7a, 1 },
+  { 0x03b7, 0x03b7, 0x68, 1 },
+  { 0x03b8, 0x03b8, 0x71, 1 },
+  { 0x03b9, 0x03b9, 0x69, 1 },
+  { 0x03ba, 0x03bb, 0x6b, 1 },
+  { 0x03bd, 0x03bd, 0x6e, 1 },
+  { 0x03be, 0x03be, 0x78, 1 },
+  { 0x03bf, 0x03c0, 0x6f, 1 },
+  { 0x03c1, 0x03c1, 0x72, 1 },
+  { 0x03c2, 0x03c2, 0x56, 1 },
+  { 0x03c3, 0x03c5, 0x73, 1 },
+  { 0x03c6, 0x03c6, 0x66, 1 },
+  { 0x03c7, 0x03c7, 0x63, 1 },
+  { 0x03c8, 0x03c8, 0x79, 1 },
+  { 0x03c9, 0x03c9, 0x77, 1 },
+  { 0x03d1, 0x03d1, 0x4a, 1 },
+  { 0x03d2, 0x03d2, 0xa1, 1 },
+  { 0x03d5, 0x03d5, 0x6a, 1 },
+  { 0x03d6, 0x03d6, 0x76, 1 },
+  { 0x2022, 0x2022, 0xb7, 1 },
+  { 0x2026, 0x2026, 0xbc, 1 },
+  { 0x2032, 0x2032, 0xa2, 1 },
+  { 0x2033, 0x2033, 0xb2, 1 },
+  { 0x2044, 0x2044, 0xa4, 1 },
+  { 0x2111, 0x2111, 0xc1, 1 },
+  { 0x2118, 0x2118, 0xc3, 1 },
+  { 0x211c, 0x211c, 0xc2, 1 },
+  { 0x2126, 0x2126, 0x57, 1 },
+  { 0x2135, 0x2135, 0xc0, 1 },
+  { 0x2190, 0x2193, 0xac, 1 },
+  { 0x2194, 0x2194, 0xab, 1 },
+  { 0x21b5, 0x21b5, 0xbf, 1 },
+  { 0x21d0, 0x21d3, 0xdc, 1 },
+  { 0x21d4, 0x21d4, 0xdb, 1 },
+  { 0x2200, 0x2200, 0x22, 1 },
+  { 0x2202, 0x2202, 0xb6, 1 },
+  { 0x2203, 0x2203, 0x24, 1 },
+  { 0x2205, 0x2205, 0xc6, 1 },
+  { 0x2206, 0x2206, 0x44, 1 },
+  { 0x2207, 0x2207, 0xd1, 1 },
+  { 0x2208, 0x2209, 0xce, 1 },
+  { 0x220b, 0x220b, 0x27, 1 },
+  { 0x220f, 0x220f, 0xd5, 1 },
+  { 0x2211, 0x2211, 0xe5, 1 },
+  { 0x2212, 0x2212, 0x2d, 1 },
+  { 0x2217, 0x2217, 0x2a, 1 },
+  { 0x221a, 0x221a, 0xd6, 1 },
+  { 0x221d, 0x221d, 0xb5, 1 },
+  { 0x221e, 0x221e, 0xa5, 1 },
+  { 0x2220, 0x2220, 0xd0, 1 },
+  { 0x2227, 0x2228, 0xd9, 1 },
+  { 0x2229, 0x222a, 0xc7, 1 },
+  { 0x222b, 0x222b, 0xf2, 1 },
+  { 0x2234, 0x2234, 0x5c, 1 },
+  { 0x223c, 0x223c, 0x7e, 1 },
+  { 0x2245, 0x2245, 0x40, 1 },
+  { 0x2248, 0x2248, 0xbb, 1 },
+  { 0x2260, 0x2261, 0xb9, 1 },
+  { 0x2264, 0x2264, 0xa3, 1 },
+  { 0x2265, 0x2265, 0xb3, 1 },
+  { 0x2282, 0x2282, 0xcc, 1 },
+  { 0x2283, 0x2283, 0xc9, 1 },
+  { 0x2284, 0x2284, 0xcb, 1 },
+  { 0x2286, 0x2286, 0xcd, 1 },
+  { 0x2287, 0x2287, 0xca, 1 },
+  { 0x2295, 0x2295, 0xc5, 1 },
+  { 0x2297, 0x2297, 0xc4, 1 },
+  { 0x22a5, 0x22a5, 0x5e, 1 },
+  { 0x22c5, 0x22c5, 0xd7, 1 },
+  { 0x2320, 0x2320, 0xf3, 1 },
+  { 0x2321, 0x2321, 0xf5, 1 },
+  { 0x2329, 0x2329, 0xe1, 1 },
+  { 0x232a, 0x232a, 0xf1, 1 },
+  { 0x25ca, 0x25ca, 0xe0, 1 },
+  { 0x2660, 0x2660, 0xaa, 1 },
+  { 0x2663, 0x2663, 0xa7, 1 },
+  { 0x2665, 0x2665, 0xa9, 1 },
+  { 0x2666, 0x2666, 0xa8, 1 },
+  { 0xf6d9, 0xf6d9, 0xd3, 1 },
+  { 0xf6da, 0xf6da, 0xd2, 1 },
+  { 0xf6db, 0xf6db, 0xd4, 1 },
+  { 0xf8e5, 0xf8e5, 0x60, 1 },
+  { 0xf8e6, 0xf8e7, 0xbd, 1 },
+  { 0xf8e8, 0xf8ea, 0xe2, 1 },
+  { 0xf8eb, 0xf8f4, 0xe6, 1 },
+  { 0xf8f5, 0xf8f5, 0xf4, 1 },
+  { 0xf8f6, 0xf8fe, 0xf6, 1 }
+};
+#define symbolUnicodeMapLen (sizeof(symbolUnicodeMapRanges) / sizeof(UnicodeMapRange))
+
+static UnicodeMapRange zapfDingbatsUnicodeMapRanges[] = {
+  { 0x0020, 0x0020, 0x20, 1 },
+  { 0x2192, 0x2192, 0xd5, 1 },
+  { 0x2194, 0x2195, 0xd6, 1 },
+  { 0x2460, 0x2469, 0xac, 1 },
+  { 0x25a0, 0x25a0, 0x6e, 1 },
+  { 0x25b2, 0x25b2, 0x73, 1 },
+  { 0x25bc, 0x25bc, 0x74, 1 },
+  { 0x25c6, 0x25c6, 0x75, 1 },
+  { 0x25cf, 0x25cf, 0x6c, 1 },
+  { 0x25d7, 0x25d7, 0x77, 1 },
+  { 0x2605, 0x2605, 0x48, 1 },
+  { 0x260e, 0x260e, 0x25, 1 },
+  { 0x261b, 0x261b, 0x2a, 1 },
+  { 0x261e, 0x261e, 0x2b, 1 },
+  { 0x2660, 0x2660, 0xab, 1 },
+  { 0x2663, 0x2663, 0xa8, 1 },
+  { 0x2665, 0x2665, 0xaa, 1 },
+  { 0x2666, 0x2666, 0xa9, 1 },
+  { 0x2701, 0x2704, 0x21, 1 },
+  { 0x2706, 0x2709, 0x26, 1 },
+  { 0x270c, 0x2727, 0x2c, 1 },
+  { 0x2729, 0x274b, 0x49, 1 },
+  { 0x274d, 0x274d, 0x6d, 1 },
+  { 0x274f, 0x2752, 0x6f, 1 },
+  { 0x2756, 0x2756, 0x76, 1 },
+  { 0x2758, 0x275e, 0x78, 1 },
+  { 0x2761, 0x2767, 0xa1, 1 },
+  { 0x2776, 0x2794, 0xb6, 1 },
+  { 0x2798, 0x27af, 0xd8, 1 },
+  { 0x27b1, 0x27be, 0xf1, 1 }
+};
+#define zapfDingbatsUnicodeMapLen (sizeof(zapfDingbatsUnicodeMapRanges) / sizeof(UnicodeMapRange))
diff --git a/xpdf/UnicodeTypeTable.cc b/xpdf/UnicodeTypeTable.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/UnicodeTypeTable.cc
@@ -0,0 +1,949 @@
+//========================================================================
+//
+// UnicodeTypeTable.cc
+//
+// Copyright 2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <stdlib.h>
+#include "CharTypes.h"
+#include "UnicodeTypeTable.h"
+
+struct UnicodeMapTableEntry {
+  char *vector;
+  char type;
+};
+
+struct UnicodeCaseTableVector {
+  Unicode codes[256];
+};
+
+static UnicodeMapTableEntry typeTable[256] = {
+  { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLNNNNNNNNNNLNNNNLNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL", 'X' },
+  { NULL, 'L' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLNNNNNNNNNNNNNNLLNNNNNNNNNNNNNNLLLLLNNNNNNNNNLNNNNNNNNNNNNNNNNN", 'X' },
+  { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLNNNNNNNNNNNLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLL", 'X' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNRNRNNRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR", 'X' },
+  { "RRRRNNNNNNNNNRNNNNNNNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNRRRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNRNNNNNNNRRNNNNNNNRRNNNNNNNNNNRRRRRR", 'X' },
+  { "RRRRRRRRRRRRRRNNRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNNNNNNNNNNNNNNNNNNNNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNNNNNRNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' },
+  { NULL, 'N' },
+  { "NNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNLLLLNLLLNNNNLLLLLLLLLLLLLNNLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNLLLLLLLLNLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLNNLLLLLLLNNNNN", 'X' },
+  { "NNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNLLLLNLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLNNNNNNNNNNNNNNNN", 'X' },
+  { "NNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLNLNNNLLLLLLLLLNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNLLLLL", 'X' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNLLLLNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNLLLLLLLNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLNNNNNNNNNNNNLLLLLLLNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLNNNNNNNNNLLLLLLLLLLNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLNLNLNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNLNNNNNLNNLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNLNNNNNNLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' },
+  { "LLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNLLLLLLLLNLLNNNNNNNNNNNLLLLLLLNLNLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNN", 'X' },
+  { "NNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLNNNNNLLLLLLNLLLLLLNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLNNNLLLLLLLLLLLNNNLLLLLLLLLLLLNNNNLLLLLLLLLLLLLNNNLLLLLLLLLLLLLNNN", 'X' },
+  { "NNNNNNNNNNNNNNLRNNNNNNNNNNNNNNNNNNNNNNNNNNLRNLRNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLNNNNNNNNNNNNNLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' },
+  { "NNLNNNNLNNLLLLLLLLLLNLNNNLLLLLNNNNNNLNLNLNLLLLNLLLNLLLLLLLNNLLLLNNNNNLLLLLNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' },
+  { NULL, 'N' },
+  { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' },
+  { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNN", 'X' },
+  { NULL, 'N' },
+  { NULL, 'N' },
+  { NULL, 'N' },
+  { NULL, 'L' },
+  { NULL, 'N' },
+  { NULL, 'N' },
+  { NULL, 'N' },
+  { NULL, 'N' },
+  { NULL, 'N' },
+  { NULL, 'N' },
+  { NULL, 'N' },
+  { "NNNNNLLLNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLNNNNNNNLLLLLNNLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLL", 'X' },
+  { NULL, 'L' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNLLLLLLLLLLLLNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLN", 'X' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { NULL, 'L' },
+  { "LLLLLLLLLLLLLLLLLLLLLLLLRRRRRRNRRRRRRRRRRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR", 'X' },
+  { NULL, 'R' },
+  { "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNN", 'X' },
+  { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNN", 'X' },
+  { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLL", 'X' }
+};
+
+static UnicodeCaseTableVector caseTable00 = {{
+  0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+  0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+  0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+  0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+  0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+  0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+  0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+  0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+  0x0040, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+  0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+  0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+  0x0078, 0x0079, 0x007a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+  0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+  0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+  0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+  0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
+  0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+  0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+  0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+  0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+  0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+  0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+  0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x03bc, 0x00b6, 0x00b7,
+  0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+  0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+  0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+  0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00d7,
+  0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00df,
+  0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+  0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+  0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
+  0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
+}};
+static UnicodeCaseTableVector caseTable01 = {{
+  0x0101, 0x0101, 0x0103, 0x0103, 0x0105, 0x0105, 0x0107, 0x0107,
+  0x0109, 0x0109, 0x010b, 0x010b, 0x010d, 0x010d, 0x010f, 0x010f,
+  0x0111, 0x0111, 0x0113, 0x0113, 0x0115, 0x0115, 0x0117, 0x0117,
+  0x0119, 0x0119, 0x011b, 0x011b, 0x011d, 0x011d, 0x011f, 0x011f,
+  0x0121, 0x0121, 0x0123, 0x0123, 0x0125, 0x0125, 0x0127, 0x0127,
+  0x0129, 0x0129, 0x012b, 0x012b, 0x012d, 0x012d, 0x012f, 0x012f,
+  0x0130, 0x0131, 0x0133, 0x0133, 0x0135, 0x0135, 0x0137, 0x0137,
+  0x0138, 0x013a, 0x013a, 0x013c, 0x013c, 0x013e, 0x013e, 0x0140,
+  0x0140, 0x0142, 0x0142, 0x0144, 0x0144, 0x0146, 0x0146, 0x0148,
+  0x0148, 0x0149, 0x014b, 0x014b, 0x014d, 0x014d, 0x014f, 0x014f,
+  0x0151, 0x0151, 0x0153, 0x0153, 0x0155, 0x0155, 0x0157, 0x0157,
+  0x0159, 0x0159, 0x015b, 0x015b, 0x015d, 0x015d, 0x015f, 0x015f,
+  0x0161, 0x0161, 0x0163, 0x0163, 0x0165, 0x0165, 0x0167, 0x0167,
+  0x0169, 0x0169, 0x016b, 0x016b, 0x016d, 0x016d, 0x016f, 0x016f,
+  0x0171, 0x0171, 0x0173, 0x0173, 0x0175, 0x0175, 0x0177, 0x0177,
+  0x00ff, 0x017a, 0x017a, 0x017c, 0x017c, 0x017e, 0x017e, 0x0073,
+  0x0180, 0x0253, 0x0183, 0x0183, 0x0185, 0x0185, 0x0254, 0x0188,
+  0x0188, 0x0256, 0x0257, 0x018c, 0x018c, 0x018d, 0x01dd, 0x0259,
+  0x025b, 0x0192, 0x0192, 0x0260, 0x0263, 0x0195, 0x0269, 0x0268,
+  0x0199, 0x0199, 0x019a, 0x019b, 0x026f, 0x0272, 0x019e, 0x0275,
+  0x01a1, 0x01a1, 0x01a3, 0x01a3, 0x01a5, 0x01a5, 0x0280, 0x01a8,
+  0x01a8, 0x0283, 0x01aa, 0x01ab, 0x01ad, 0x01ad, 0x0288, 0x01b0,
+  0x01b0, 0x028a, 0x028b, 0x01b4, 0x01b4, 0x01b6, 0x01b6, 0x0292,
+  0x01b9, 0x01b9, 0x01ba, 0x01bb, 0x01bd, 0x01bd, 0x01be, 0x01bf,
+  0x01c0, 0x01c1, 0x01c2, 0x01c3, 0x01c6, 0x01c6, 0x01c6, 0x01c9,
+  0x01c9, 0x01c9, 0x01cc, 0x01cc, 0x01cc, 0x01ce, 0x01ce, 0x01d0,
+  0x01d0, 0x01d2, 0x01d2, 0x01d4, 0x01d4, 0x01d6, 0x01d6, 0x01d8,
+  0x01d8, 0x01da, 0x01da, 0x01dc, 0x01dc, 0x01dd, 0x01df, 0x01df,
+  0x01e1, 0x01e1, 0x01e3, 0x01e3, 0x01e5, 0x01e5, 0x01e7, 0x01e7,
+  0x01e9, 0x01e9, 0x01eb, 0x01eb, 0x01ed, 0x01ed, 0x01ef, 0x01ef,
+  0x01f0, 0x01f3, 0x01f3, 0x01f3, 0x01f5, 0x01f5, 0x0195, 0x01bf,
+  0x01f9, 0x01f9, 0x01fb, 0x01fb, 0x01fd, 0x01fd, 0x01ff, 0x01ff
+}};
+static UnicodeCaseTableVector caseTable02 = {{
+  0x0201, 0x0201, 0x0203, 0x0203, 0x0205, 0x0205, 0x0207, 0x0207,
+  0x0209, 0x0209, 0x020b, 0x020b, 0x020d, 0x020d, 0x020f, 0x020f,
+  0x0211, 0x0211, 0x0213, 0x0213, 0x0215, 0x0215, 0x0217, 0x0217,
+  0x0219, 0x0219, 0x021b, 0x021b, 0x021d, 0x021d, 0x021f, 0x021f,
+  0x019e, 0x0221, 0x0223, 0x0223, 0x0225, 0x0225, 0x0227, 0x0227,
+  0x0229, 0x0229, 0x022b, 0x022b, 0x022d, 0x022d, 0x022f, 0x022f,
+  0x0231, 0x0231, 0x0233, 0x0233, 0x0234, 0x0235, 0x0236, 0x0237,
+  0x0238, 0x0239, 0x023a, 0x023b, 0x023c, 0x023d, 0x023e, 0x023f,
+  0x0240, 0x0241, 0x0242, 0x0243, 0x0244, 0x0245, 0x0246, 0x0247,
+  0x0248, 0x0249, 0x024a, 0x024b, 0x024c, 0x024d, 0x024e, 0x024f,
+  0x0250, 0x0251, 0x0252, 0x0253, 0x0254, 0x0255, 0x0256, 0x0257,
+  0x0258, 0x0259, 0x025a, 0x025b, 0x025c, 0x025d, 0x025e, 0x025f,
+  0x0260, 0x0261, 0x0262, 0x0263, 0x0264, 0x0265, 0x0266, 0x0267,
+  0x0268, 0x0269, 0x026a, 0x026b, 0x026c, 0x026d, 0x026e, 0x026f,
+  0x0270, 0x0271, 0x0272, 0x0273, 0x0274, 0x0275, 0x0276, 0x0277,
+  0x0278, 0x0279, 0x027a, 0x027b, 0x027c, 0x027d, 0x027e, 0x027f,
+  0x0280, 0x0281, 0x0282, 0x0283, 0x0284, 0x0285, 0x0286, 0x0287,
+  0x0288, 0x0289, 0x028a, 0x028b, 0x028c, 0x028d, 0x028e, 0x028f,
+  0x0290, 0x0291, 0x0292, 0x0293, 0x0294, 0x0295, 0x0296, 0x0297,
+  0x0298, 0x0299, 0x029a, 0x029b, 0x029c, 0x029d, 0x029e, 0x029f,
+  0x02a0, 0x02a1, 0x02a2, 0x02a3, 0x02a4, 0x02a5, 0x02a6, 0x02a7,
+  0x02a8, 0x02a9, 0x02aa, 0x02ab, 0x02ac, 0x02ad, 0x02ae, 0x02af,
+  0x02b0, 0x02b1, 0x02b2, 0x02b3, 0x02b4, 0x02b5, 0x02b6, 0x02b7,
+  0x02b8, 0x02b9, 0x02ba, 0x02bb, 0x02bc, 0x02bd, 0x02be, 0x02bf,
+  0x02c0, 0x02c1, 0x02c2, 0x02c3, 0x02c4, 0x02c5, 0x02c6, 0x02c7,
+  0x02c8, 0x02c9, 0x02ca, 0x02cb, 0x02cc, 0x02cd, 0x02ce, 0x02cf,
+  0x02d0, 0x02d1, 0x02d2, 0x02d3, 0x02d4, 0x02d5, 0x02d6, 0x02d7,
+  0x02d8, 0x02d9, 0x02da, 0x02db, 0x02dc, 0x02dd, 0x02de, 0x02df,
+  0x02e0, 0x02e1, 0x02e2, 0x02e3, 0x02e4, 0x02e5, 0x02e6, 0x02e7,
+  0x02e8, 0x02e9, 0x02ea, 0x02eb, 0x02ec, 0x02ed, 0x02ee, 0x02ef,
+  0x02f0, 0x02f1, 0x02f2, 0x02f3, 0x02f4, 0x02f5, 0x02f6, 0x02f7,
+  0x02f8, 0x02f9, 0x02fa, 0x02fb, 0x02fc, 0x02fd, 0x02fe, 0x02ff
+}};
+static UnicodeCaseTableVector caseTable03 = {{
+  0x0300, 0x0301, 0x0302, 0x0303, 0x0304, 0x0305, 0x0306, 0x0307,
+  0x0308, 0x0309, 0x030a, 0x030b, 0x030c, 0x030d, 0x030e, 0x030f,
+  0x0310, 0x0311, 0x0312, 0x0313, 0x0314, 0x0315, 0x0316, 0x0317,
+  0x0318, 0x0319, 0x031a, 0x031b, 0x031c, 0x031d, 0x031e, 0x031f,
+  0x0320, 0x0321, 0x0322, 0x0323, 0x0324, 0x0325, 0x0326, 0x0327,
+  0x0328, 0x0329, 0x032a, 0x032b, 0x032c, 0x032d, 0x032e, 0x032f,
+  0x0330, 0x0331, 0x0332, 0x0333, 0x0334, 0x0335, 0x0336, 0x0337,
+  0x0338, 0x0339, 0x033a, 0x033b, 0x033c, 0x033d, 0x033e, 0x033f,
+  0x0340, 0x0341, 0x0342, 0x0343, 0x0344, 0x03b9, 0x0346, 0x0347,
+  0x0348, 0x0349, 0x034a, 0x034b, 0x034c, 0x034d, 0x034e, 0x034f,
+  0x0350, 0x0351, 0x0352, 0x0353, 0x0354, 0x0355, 0x0356, 0x0357,
+  0x0358, 0x0359, 0x035a, 0x035b, 0x035c, 0x035d, 0x035e, 0x035f,
+  0x0360, 0x0361, 0x0362, 0x0363, 0x0364, 0x0365, 0x0366, 0x0367,
+  0x0368, 0x0369, 0x036a, 0x036b, 0x036c, 0x036d, 0x036e, 0x036f,
+  0x0370, 0x0371, 0x0372, 0x0373, 0x0374, 0x0375, 0x0376, 0x0377,
+  0x0378, 0x0379, 0x037a, 0x037b, 0x037c, 0x037d, 0x037e, 0x037f,
+  0x0380, 0x0381, 0x0382, 0x0383, 0x0384, 0x0385, 0x03ac, 0x0387,
+  0x03ad, 0x03ae, 0x03af, 0x038b, 0x03cc, 0x038d, 0x03cd, 0x03ce,
+  0x0390, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7,
+  0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf,
+  0x03c0, 0x03c1, 0x03a2, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7,
+  0x03c8, 0x03c9, 0x03ca, 0x03cb, 0x03ac, 0x03ad, 0x03ae, 0x03af,
+  0x03b0, 0x03b1, 0x03b2, 0x03b3, 0x03b4, 0x03b5, 0x03b6, 0x03b7,
+  0x03b8, 0x03b9, 0x03ba, 0x03bb, 0x03bc, 0x03bd, 0x03be, 0x03bf,
+  0x03c0, 0x03c1, 0x03c3, 0x03c3, 0x03c4, 0x03c5, 0x03c6, 0x03c7,
+  0x03c8, 0x03c9, 0x03ca, 0x03cb, 0x03cc, 0x03cd, 0x03ce, 0x03cf,
+  0x03b2, 0x03b8, 0x03d2, 0x03d3, 0x03d4, 0x03c6, 0x03c0, 0x03d7,
+  0x03d9, 0x03d9, 0x03db, 0x03db, 0x03dd, 0x03dd, 0x03df, 0x03df,
+  0x03e1, 0x03e1, 0x03e3, 0x03e3, 0x03e5, 0x03e5, 0x03e7, 0x03e7,
+  0x03e9, 0x03e9, 0x03eb, 0x03eb, 0x03ed, 0x03ed, 0x03ef, 0x03ef,
+  0x03ba, 0x03c1, 0x03f2, 0x03f3, 0x03b8, 0x03b5, 0x03f6, 0x03f8,
+  0x03f8, 0x03f2, 0x03fb, 0x03fb, 0x03fc, 0x03fd, 0x03fe, 0x03ff
+}};
+static UnicodeCaseTableVector caseTable04 = {{
+  0x0450, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457,
+  0x0458, 0x0459, 0x045a, 0x045b, 0x045c, 0x045d, 0x045e, 0x045f,
+  0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
+  0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
+  0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+  0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f,
+  0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437,
+  0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, 0x043f,
+  0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447,
+  0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f,
+  0x0450, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457,
+  0x0458, 0x0459, 0x045a, 0x045b, 0x045c, 0x045d, 0x045e, 0x045f,
+  0x0461, 0x0461, 0x0463, 0x0463, 0x0465, 0x0465, 0x0467, 0x0467,
+  0x0469, 0x0469, 0x046b, 0x046b, 0x046d, 0x046d, 0x046f, 0x046f,
+  0x0471, 0x0471, 0x0473, 0x0473, 0x0475, 0x0475, 0x0477, 0x0477,
+  0x0479, 0x0479, 0x047b, 0x047b, 0x047d, 0x047d, 0x047f, 0x047f,
+  0x0481, 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487,
+  0x0488, 0x0489, 0x048b, 0x048b, 0x048d, 0x048d, 0x048f, 0x048f,
+  0x0491, 0x0491, 0x0493, 0x0493, 0x0495, 0x0495, 0x0497, 0x0497,
+  0x0499, 0x0499, 0x049b, 0x049b, 0x049d, 0x049d, 0x049f, 0x049f,
+  0x04a1, 0x04a1, 0x04a3, 0x04a3, 0x04a5, 0x04a5, 0x04a7, 0x04a7,
+  0x04a9, 0x04a9, 0x04ab, 0x04ab, 0x04ad, 0x04ad, 0x04af, 0x04af,
+  0x04b1, 0x04b1, 0x04b3, 0x04b3, 0x04b5, 0x04b5, 0x04b7, 0x04b7,
+  0x04b9, 0x04b9, 0x04bb, 0x04bb, 0x04bd, 0x04bd, 0x04bf, 0x04bf,
+  0x04c0, 0x04c2, 0x04c2, 0x04c4, 0x04c4, 0x04c6, 0x04c6, 0x04c8,
+  0x04c8, 0x04ca, 0x04ca, 0x04cc, 0x04cc, 0x04ce, 0x04ce, 0x04cf,
+  0x04d1, 0x04d1, 0x04d3, 0x04d3, 0x04d5, 0x04d5, 0x04d7, 0x04d7,
+  0x04d9, 0x04d9, 0x04db, 0x04db, 0x04dd, 0x04dd, 0x04df, 0x04df,
+  0x04e1, 0x04e1, 0x04e3, 0x04e3, 0x04e5, 0x04e5, 0x04e7, 0x04e7,
+  0x04e9, 0x04e9, 0x04eb, 0x04eb, 0x04ed, 0x04ed, 0x04ef, 0x04ef,
+  0x04f1, 0x04f1, 0x04f3, 0x04f3, 0x04f5, 0x04f5, 0x04f6, 0x04f7,
+  0x04f9, 0x04f9, 0x04fa, 0x04fb, 0x04fc, 0x04fd, 0x04fe, 0x04ff
+}};
+static UnicodeCaseTableVector caseTable05 = {{
+  0x0501, 0x0501, 0x0503, 0x0503, 0x0505, 0x0505, 0x0507, 0x0507,
+  0x0509, 0x0509, 0x050b, 0x050b, 0x050d, 0x050d, 0x050f, 0x050f,
+  0x0510, 0x0511, 0x0512, 0x0513, 0x0514, 0x0515, 0x0516, 0x0517,
+  0x0518, 0x0519, 0x051a, 0x051b, 0x051c, 0x051d, 0x051e, 0x051f,
+  0x0520, 0x0521, 0x0522, 0x0523, 0x0524, 0x0525, 0x0526, 0x0527,
+  0x0528, 0x0529, 0x052a, 0x052b, 0x052c, 0x052d, 0x052e, 0x052f,
+  0x0530, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567,
+  0x0568, 0x0569, 0x056a, 0x056b, 0x056c, 0x056d, 0x056e, 0x056f,
+  0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577,
+  0x0578, 0x0579, 0x057a, 0x057b, 0x057c, 0x057d, 0x057e, 0x057f,
+  0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0557,
+  0x0558, 0x0559, 0x055a, 0x055b, 0x055c, 0x055d, 0x055e, 0x055f,
+  0x0560, 0x0561, 0x0562, 0x0563, 0x0564, 0x0565, 0x0566, 0x0567,
+  0x0568, 0x0569, 0x056a, 0x056b, 0x056c, 0x056d, 0x056e, 0x056f,
+  0x0570, 0x0571, 0x0572, 0x0573, 0x0574, 0x0575, 0x0576, 0x0577,
+  0x0578, 0x0579, 0x057a, 0x057b, 0x057c, 0x057d, 0x057e, 0x057f,
+  0x0580, 0x0581, 0x0582, 0x0583, 0x0584, 0x0585, 0x0586, 0x0587,
+  0x0588, 0x0589, 0x058a, 0x058b, 0x058c, 0x058d, 0x058e, 0x058f,
+  0x0590, 0x0591, 0x0592, 0x0593, 0x0594, 0x0595, 0x0596, 0x0597,
+  0x0598, 0x0599, 0x059a, 0x059b, 0x059c, 0x059d, 0x059e, 0x059f,
+  0x05a0, 0x05a1, 0x05a2, 0x05a3, 0x05a4, 0x05a5, 0x05a6, 0x05a7,
+  0x05a8, 0x05a9, 0x05aa, 0x05ab, 0x05ac, 0x05ad, 0x05ae, 0x05af,
+  0x05b0, 0x05b1, 0x05b2, 0x05b3, 0x05b4, 0x05b5, 0x05b6, 0x05b7,
+  0x05b8, 0x05b9, 0x05ba, 0x05bb, 0x05bc, 0x05bd, 0x05be, 0x05bf,
+  0x05c0, 0x05c1, 0x05c2, 0x05c3, 0x05c4, 0x05c5, 0x05c6, 0x05c7,
+  0x05c8, 0x05c9, 0x05ca, 0x05cb, 0x05cc, 0x05cd, 0x05ce, 0x05cf,
+  0x05d0, 0x05d1, 0x05d2, 0x05d3, 0x05d4, 0x05d5, 0x05d6, 0x05d7,
+  0x05d8, 0x05d9, 0x05da, 0x05db, 0x05dc, 0x05dd, 0x05de, 0x05df,
+  0x05e0, 0x05e1, 0x05e2, 0x05e3, 0x05e4, 0x05e5, 0x05e6, 0x05e7,
+  0x05e8, 0x05e9, 0x05ea, 0x05eb, 0x05ec, 0x05ed, 0x05ee, 0x05ef,
+  0x05f0, 0x05f1, 0x05f2, 0x05f3, 0x05f4, 0x05f5, 0x05f6, 0x05f7,
+  0x05f8, 0x05f9, 0x05fa, 0x05fb, 0x05fc, 0x05fd, 0x05fe, 0x05ff
+}};
+static UnicodeCaseTableVector caseTable1e = {{
+  0x1e01, 0x1e01, 0x1e03, 0x1e03, 0x1e05, 0x1e05, 0x1e07, 0x1e07,
+  0x1e09, 0x1e09, 0x1e0b, 0x1e0b, 0x1e0d, 0x1e0d, 0x1e0f, 0x1e0f,
+  0x1e11, 0x1e11, 0x1e13, 0x1e13, 0x1e15, 0x1e15, 0x1e17, 0x1e17,
+  0x1e19, 0x1e19, 0x1e1b, 0x1e1b, 0x1e1d, 0x1e1d, 0x1e1f, 0x1e1f,
+  0x1e21, 0x1e21, 0x1e23, 0x1e23, 0x1e25, 0x1e25, 0x1e27, 0x1e27,
+  0x1e29, 0x1e29, 0x1e2b, 0x1e2b, 0x1e2d, 0x1e2d, 0x1e2f, 0x1e2f,
+  0x1e31, 0x1e31, 0x1e33, 0x1e33, 0x1e35, 0x1e35, 0x1e37, 0x1e37,
+  0x1e39, 0x1e39, 0x1e3b, 0x1e3b, 0x1e3d, 0x1e3d, 0x1e3f, 0x1e3f,
+  0x1e41, 0x1e41, 0x1e43, 0x1e43, 0x1e45, 0x1e45, 0x1e47, 0x1e47,
+  0x1e49, 0x1e49, 0x1e4b, 0x1e4b, 0x1e4d, 0x1e4d, 0x1e4f, 0x1e4f,
+  0x1e51, 0x1e51, 0x1e53, 0x1e53, 0x1e55, 0x1e55, 0x1e57, 0x1e57,
+  0x1e59, 0x1e59, 0x1e5b, 0x1e5b, 0x1e5d, 0x1e5d, 0x1e5f, 0x1e5f,
+  0x1e61, 0x1e61, 0x1e63, 0x1e63, 0x1e65, 0x1e65, 0x1e67, 0x1e67,
+  0x1e69, 0x1e69, 0x1e6b, 0x1e6b, 0x1e6d, 0x1e6d, 0x1e6f, 0x1e6f,
+  0x1e71, 0x1e71, 0x1e73, 0x1e73, 0x1e75, 0x1e75, 0x1e77, 0x1e77,
+  0x1e79, 0x1e79, 0x1e7b, 0x1e7b, 0x1e7d, 0x1e7d, 0x1e7f, 0x1e7f,
+  0x1e81, 0x1e81, 0x1e83, 0x1e83, 0x1e85, 0x1e85, 0x1e87, 0x1e87,
+  0x1e89, 0x1e89, 0x1e8b, 0x1e8b, 0x1e8d, 0x1e8d, 0x1e8f, 0x1e8f,
+  0x1e91, 0x1e91, 0x1e93, 0x1e93, 0x1e95, 0x1e95, 0x1e96, 0x1e97,
+  0x1e98, 0x1e99, 0x1e9a, 0x1e61, 0x1e9c, 0x1e9d, 0x1e9e, 0x1e9f,
+  0x1ea1, 0x1ea1, 0x1ea3, 0x1ea3, 0x1ea5, 0x1ea5, 0x1ea7, 0x1ea7,
+  0x1ea9, 0x1ea9, 0x1eab, 0x1eab, 0x1ead, 0x1ead, 0x1eaf, 0x1eaf,
+  0x1eb1, 0x1eb1, 0x1eb3, 0x1eb3, 0x1eb5, 0x1eb5, 0x1eb7, 0x1eb7,
+  0x1eb9, 0x1eb9, 0x1ebb, 0x1ebb, 0x1ebd, 0x1ebd, 0x1ebf, 0x1ebf,
+  0x1ec1, 0x1ec1, 0x1ec3, 0x1ec3, 0x1ec5, 0x1ec5, 0x1ec7, 0x1ec7,
+  0x1ec9, 0x1ec9, 0x1ecb, 0x1ecb, 0x1ecd, 0x1ecd, 0x1ecf, 0x1ecf,
+  0x1ed1, 0x1ed1, 0x1ed3, 0x1ed3, 0x1ed5, 0x1ed5, 0x1ed7, 0x1ed7,
+  0x1ed9, 0x1ed9, 0x1edb, 0x1edb, 0x1edd, 0x1edd, 0x1edf, 0x1edf,
+  0x1ee1, 0x1ee1, 0x1ee3, 0x1ee3, 0x1ee5, 0x1ee5, 0x1ee7, 0x1ee7,
+  0x1ee9, 0x1ee9, 0x1eeb, 0x1eeb, 0x1eed, 0x1eed, 0x1eef, 0x1eef,
+  0x1ef1, 0x1ef1, 0x1ef3, 0x1ef3, 0x1ef5, 0x1ef5, 0x1ef7, 0x1ef7,
+  0x1ef9, 0x1ef9, 0x1efa, 0x1efb, 0x1efc, 0x1efd, 0x1efe, 0x1eff
+}};
+static UnicodeCaseTableVector caseTable1f = {{
+  0x1f00, 0x1f01, 0x1f02, 0x1f03, 0x1f04, 0x1f05, 0x1f06, 0x1f07,
+  0x1f00, 0x1f01, 0x1f02, 0x1f03, 0x1f04, 0x1f05, 0x1f06, 0x1f07,
+  0x1f10, 0x1f11, 0x1f12, 0x1f13, 0x1f14, 0x1f15, 0x1f16, 0x1f17,
+  0x1f10, 0x1f11, 0x1f12, 0x1f13, 0x1f14, 0x1f15, 0x1f1e, 0x1f1f,
+  0x1f20, 0x1f21, 0x1f22, 0x1f23, 0x1f24, 0x1f25, 0x1f26, 0x1f27,
+  0x1f20, 0x1f21, 0x1f22, 0x1f23, 0x1f24, 0x1f25, 0x1f26, 0x1f27,
+  0x1f30, 0x1f31, 0x1f32, 0x1f33, 0x1f34, 0x1f35, 0x1f36, 0x1f37,
+  0x1f30, 0x1f31, 0x1f32, 0x1f33, 0x1f34, 0x1f35, 0x1f36, 0x1f37,
+  0x1f40, 0x1f41, 0x1f42, 0x1f43, 0x1f44, 0x1f45, 0x1f46, 0x1f47,
+  0x1f40, 0x1f41, 0x1f42, 0x1f43, 0x1f44, 0x1f45, 0x1f4e, 0x1f4f,
+  0x1f50, 0x1f51, 0x1f52, 0x1f53, 0x1f54, 0x1f55, 0x1f56, 0x1f57,
+  0x1f58, 0x1f51, 0x1f5a, 0x1f53, 0x1f5c, 0x1f55, 0x1f5e, 0x1f57,
+  0x1f60, 0x1f61, 0x1f62, 0x1f63, 0x1f64, 0x1f65, 0x1f66, 0x1f67,
+  0x1f60, 0x1f61, 0x1f62, 0x1f63, 0x1f64, 0x1f65, 0x1f66, 0x1f67,
+  0x1f70, 0x1f71, 0x1f72, 0x1f73, 0x1f74, 0x1f75, 0x1f76, 0x1f77,
+  0x1f78, 0x1f79, 0x1f7a, 0x1f7b, 0x1f7c, 0x1f7d, 0x1f7e, 0x1f7f,
+  0x1f80, 0x1f81, 0x1f82, 0x1f83, 0x1f84, 0x1f85, 0x1f86, 0x1f87,
+  0x1f80, 0x1f81, 0x1f82, 0x1f83, 0x1f84, 0x1f85, 0x1f86, 0x1f87,
+  0x1f90, 0x1f91, 0x1f92, 0x1f93, 0x1f94, 0x1f95, 0x1f96, 0x1f97,
+  0x1f90, 0x1f91, 0x1f92, 0x1f93, 0x1f94, 0x1f95, 0x1f96, 0x1f97,
+  0x1fa0, 0x1fa1, 0x1fa2, 0x1fa3, 0x1fa4, 0x1fa5, 0x1fa6, 0x1fa7,
+  0x1fa0, 0x1fa1, 0x1fa2, 0x1fa3, 0x1fa4, 0x1fa5, 0x1fa6, 0x1fa7,
+  0x1fb0, 0x1fb1, 0x1fb2, 0x1fb3, 0x1fb4, 0x1fb5, 0x1fb6, 0x1fb7,
+  0x1fb0, 0x1fb1, 0x1f70, 0x1f71, 0x1fb3, 0x1fbd, 0x03b9, 0x1fbf,
+  0x1fc0, 0x1fc1, 0x1fc2, 0x1fc3, 0x1fc4, 0x1fc5, 0x1fc6, 0x1fc7,
+  0x1f72, 0x1f73, 0x1f74, 0x1f75, 0x1fc3, 0x1fcd, 0x1fce, 0x1fcf,
+  0x1fd0, 0x1fd1, 0x1fd2, 0x1fd3, 0x1fd4, 0x1fd5, 0x1fd6, 0x1fd7,
+  0x1fd0, 0x1fd1, 0x1f76, 0x1f77, 0x1fdc, 0x1fdd, 0x1fde, 0x1fdf,
+  0x1fe0, 0x1fe1, 0x1fe2, 0x1fe3, 0x1fe4, 0x1fe5, 0x1fe6, 0x1fe7,
+  0x1fe0, 0x1fe1, 0x1f7a, 0x1f7b, 0x1fe5, 0x1fed, 0x1fee, 0x1fef,
+  0x1ff0, 0x1ff1, 0x1ff2, 0x1ff3, 0x1ff4, 0x1ff5, 0x1ff6, 0x1ff7,
+  0x1f78, 0x1f79, 0x1f7c, 0x1f7d, 0x1ff3, 0x1ffd, 0x1ffe, 0x1fff
+}};
+static UnicodeCaseTableVector caseTable21 = {{
+  0x2100, 0x2101, 0x2102, 0x2103, 0x2104, 0x2105, 0x2106, 0x2107,
+  0x2108, 0x2109, 0x210a, 0x210b, 0x210c, 0x210d, 0x210e, 0x210f,
+  0x2110, 0x2111, 0x2112, 0x2113, 0x2114, 0x2115, 0x2116, 0x2117,
+  0x2118, 0x2119, 0x211a, 0x211b, 0x211c, 0x211d, 0x211e, 0x211f,
+  0x2120, 0x2121, 0x2122, 0x2123, 0x2124, 0x2125, 0x03c9, 0x2127,
+  0x2128, 0x2129, 0x006b, 0x00e5, 0x212c, 0x212d, 0x212e, 0x212f,
+  0x2130, 0x2131, 0x2132, 0x2133, 0x2134, 0x2135, 0x2136, 0x2137,
+  0x2138, 0x2139, 0x213a, 0x213b, 0x213c, 0x213d, 0x213e, 0x213f,
+  0x2140, 0x2141, 0x2142, 0x2143, 0x2144, 0x2145, 0x2146, 0x2147,
+  0x2148, 0x2149, 0x214a, 0x214b, 0x214c, 0x214d, 0x214e, 0x214f,
+  0x2150, 0x2151, 0x2152, 0x2153, 0x2154, 0x2155, 0x2156, 0x2157,
+  0x2158, 0x2159, 0x215a, 0x215b, 0x215c, 0x215d, 0x215e, 0x215f,
+  0x2170, 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, 0x2176, 0x2177,
+  0x2178, 0x2179, 0x217a, 0x217b, 0x217c, 0x217d, 0x217e, 0x217f,
+  0x2170, 0x2171, 0x2172, 0x2173, 0x2174, 0x2175, 0x2176, 0x2177,
+  0x2178, 0x2179, 0x217a, 0x217b, 0x217c, 0x217d, 0x217e, 0x217f,
+  0x2180, 0x2181, 0x2182, 0x2183, 0x2184, 0x2185, 0x2186, 0x2187,
+  0x2188, 0x2189, 0x218a, 0x218b, 0x218c, 0x218d, 0x218e, 0x218f,
+  0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x2196, 0x2197,
+  0x2198, 0x2199, 0x219a, 0x219b, 0x219c, 0x219d, 0x219e, 0x219f,
+  0x21a0, 0x21a1, 0x21a2, 0x21a3, 0x21a4, 0x21a5, 0x21a6, 0x21a7,
+  0x21a8, 0x21a9, 0x21aa, 0x21ab, 0x21ac, 0x21ad, 0x21ae, 0x21af,
+  0x21b0, 0x21b1, 0x21b2, 0x21b3, 0x21b4, 0x21b5, 0x21b6, 0x21b7,
+  0x21b8, 0x21b9, 0x21ba, 0x21bb, 0x21bc, 0x21bd, 0x21be, 0x21bf,
+  0x21c0, 0x21c1, 0x21c2, 0x21c3, 0x21c4, 0x21c5, 0x21c6, 0x21c7,
+  0x21c8, 0x21c9, 0x21ca, 0x21cb, 0x21cc, 0x21cd, 0x21ce, 0x21cf,
+  0x21d0, 0x21d1, 0x21d2, 0x21d3, 0x21d4, 0x21d5, 0x21d6, 0x21d7,
+  0x21d8, 0x21d9, 0x21da, 0x21db, 0x21dc, 0x21dd, 0x21de, 0x21df,
+  0x21e0, 0x21e1, 0x21e2, 0x21e3, 0x21e4, 0x21e5, 0x21e6, 0x21e7,
+  0x21e8, 0x21e9, 0x21ea, 0x21eb, 0x21ec, 0x21ed, 0x21ee, 0x21ef,
+  0x21f0, 0x21f1, 0x21f2, 0x21f3, 0x21f4, 0x21f5, 0x21f6, 0x21f7,
+  0x21f8, 0x21f9, 0x21fa, 0x21fb, 0x21fc, 0x21fd, 0x21fe, 0x21ff
+}};
+static UnicodeCaseTableVector caseTable24 = {{
+  0x2400, 0x2401, 0x2402, 0x2403, 0x2404, 0x2405, 0x2406, 0x2407,
+  0x2408, 0x2409, 0x240a, 0x240b, 0x240c, 0x240d, 0x240e, 0x240f,
+  0x2410, 0x2411, 0x2412, 0x2413, 0x2414, 0x2415, 0x2416, 0x2417,
+  0x2418, 0x2419, 0x241a, 0x241b, 0x241c, 0x241d, 0x241e, 0x241f,
+  0x2420, 0x2421, 0x2422, 0x2423, 0x2424, 0x2425, 0x2426, 0x2427,
+  0x2428, 0x2429, 0x242a, 0x242b, 0x242c, 0x242d, 0x242e, 0x242f,
+  0x2430, 0x2431, 0x2432, 0x2433, 0x2434, 0x2435, 0x2436, 0x2437,
+  0x2438, 0x2439, 0x243a, 0x243b, 0x243c, 0x243d, 0x243e, 0x243f,
+  0x2440, 0x2441, 0x2442, 0x2443, 0x2444, 0x2445, 0x2446, 0x2447,
+  0x2448, 0x2449, 0x244a, 0x244b, 0x244c, 0x244d, 0x244e, 0x244f,
+  0x2450, 0x2451, 0x2452, 0x2453, 0x2454, 0x2455, 0x2456, 0x2457,
+  0x2458, 0x2459, 0x245a, 0x245b, 0x245c, 0x245d, 0x245e, 0x245f,
+  0x2460, 0x2461, 0x2462, 0x2463, 0x2464, 0x2465, 0x2466, 0x2467,
+  0x2468, 0x2469, 0x246a, 0x246b, 0x246c, 0x246d, 0x246e, 0x246f,
+  0x2470, 0x2471, 0x2472, 0x2473, 0x2474, 0x2475, 0x2476, 0x2477,
+  0x2478, 0x2479, 0x247a, 0x247b, 0x247c, 0x247d, 0x247e, 0x247f,
+  0x2480, 0x2481, 0x2482, 0x2483, 0x2484, 0x2485, 0x2486, 0x2487,
+  0x2488, 0x2489, 0x248a, 0x248b, 0x248c, 0x248d, 0x248e, 0x248f,
+  0x2490, 0x2491, 0x2492, 0x2493, 0x2494, 0x2495, 0x2496, 0x2497,
+  0x2498, 0x2499, 0x249a, 0x249b, 0x249c, 0x249d, 0x249e, 0x249f,
+  0x24a0, 0x24a1, 0x24a2, 0x24a3, 0x24a4, 0x24a5, 0x24a6, 0x24a7,
+  0x24a8, 0x24a9, 0x24aa, 0x24ab, 0x24ac, 0x24ad, 0x24ae, 0x24af,
+  0x24b0, 0x24b1, 0x24b2, 0x24b3, 0x24b4, 0x24b5, 0x24d0, 0x24d1,
+  0x24d2, 0x24d3, 0x24d4, 0x24d5, 0x24d6, 0x24d7, 0x24d8, 0x24d9,
+  0x24da, 0x24db, 0x24dc, 0x24dd, 0x24de, 0x24df, 0x24e0, 0x24e1,
+  0x24e2, 0x24e3, 0x24e4, 0x24e5, 0x24e6, 0x24e7, 0x24e8, 0x24e9,
+  0x24d0, 0x24d1, 0x24d2, 0x24d3, 0x24d4, 0x24d5, 0x24d6, 0x24d7,
+  0x24d8, 0x24d9, 0x24da, 0x24db, 0x24dc, 0x24dd, 0x24de, 0x24df,
+  0x24e0, 0x24e1, 0x24e2, 0x24e3, 0x24e4, 0x24e5, 0x24e6, 0x24e7,
+  0x24e8, 0x24e9, 0x24ea, 0x24eb, 0x24ec, 0x24ed, 0x24ee, 0x24ef,
+  0x24f0, 0x24f1, 0x24f2, 0x24f3, 0x24f4, 0x24f5, 0x24f6, 0x24f7,
+  0x24f8, 0x24f9, 0x24fa, 0x24fb, 0x24fc, 0x24fd, 0x24fe, 0x24ff
+}};
+static UnicodeCaseTableVector caseTableff = {{
+  0xff00, 0xff01, 0xff02, 0xff03, 0xff04, 0xff05, 0xff06, 0xff07,
+  0xff08, 0xff09, 0xff0a, 0xff0b, 0xff0c, 0xff0d, 0xff0e, 0xff0f,
+  0xff10, 0xff11, 0xff12, 0xff13, 0xff14, 0xff15, 0xff16, 0xff17,
+  0xff18, 0xff19, 0xff1a, 0xff1b, 0xff1c, 0xff1d, 0xff1e, 0xff1f,
+  0xff20, 0xff41, 0xff42, 0xff43, 0xff44, 0xff45, 0xff46, 0xff47,
+  0xff48, 0xff49, 0xff4a, 0xff4b, 0xff4c, 0xff4d, 0xff4e, 0xff4f,
+  0xff50, 0xff51, 0xff52, 0xff53, 0xff54, 0xff55, 0xff56, 0xff57,
+  0xff58, 0xff59, 0xff5a, 0xff3b, 0xff3c, 0xff3d, 0xff3e, 0xff3f,
+  0xff40, 0xff41, 0xff42, 0xff43, 0xff44, 0xff45, 0xff46, 0xff47,
+  0xff48, 0xff49, 0xff4a, 0xff4b, 0xff4c, 0xff4d, 0xff4e, 0xff4f,
+  0xff50, 0xff51, 0xff52, 0xff53, 0xff54, 0xff55, 0xff56, 0xff57,
+  0xff58, 0xff59, 0xff5a, 0xff5b, 0xff5c, 0xff5d, 0xff5e, 0xff5f,
+  0xff60, 0xff61, 0xff62, 0xff63, 0xff64, 0xff65, 0xff66, 0xff67,
+  0xff68, 0xff69, 0xff6a, 0xff6b, 0xff6c, 0xff6d, 0xff6e, 0xff6f,
+  0xff70, 0xff71, 0xff72, 0xff73, 0xff74, 0xff75, 0xff76, 0xff77,
+  0xff78, 0xff79, 0xff7a, 0xff7b, 0xff7c, 0xff7d, 0xff7e, 0xff7f,
+  0xff80, 0xff81, 0xff82, 0xff83, 0xff84, 0xff85, 0xff86, 0xff87,
+  0xff88, 0xff89, 0xff8a, 0xff8b, 0xff8c, 0xff8d, 0xff8e, 0xff8f,
+  0xff90, 0xff91, 0xff92, 0xff93, 0xff94, 0xff95, 0xff96, 0xff97,
+  0xff98, 0xff99, 0xff9a, 0xff9b, 0xff9c, 0xff9d, 0xff9e, 0xff9f,
+  0xffa0, 0xffa1, 0xffa2, 0xffa3, 0xffa4, 0xffa5, 0xffa6, 0xffa7,
+  0xffa8, 0xffa9, 0xffaa, 0xffab, 0xffac, 0xffad, 0xffae, 0xffaf,
+  0xffb0, 0xffb1, 0xffb2, 0xffb3, 0xffb4, 0xffb5, 0xffb6, 0xffb7,
+  0xffb8, 0xffb9, 0xffba, 0xffbb, 0xffbc, 0xffbd, 0xffbe, 0xffbf,
+  0xffc0, 0xffc1, 0xffc2, 0xffc3, 0xffc4, 0xffc5, 0xffc6, 0xffc7,
+  0xffc8, 0xffc9, 0xffca, 0xffcb, 0xffcc, 0xffcd, 0xffce, 0xffcf,
+  0xffd0, 0xffd1, 0xffd2, 0xffd3, 0xffd4, 0xffd5, 0xffd6, 0xffd7,
+  0xffd8, 0xffd9, 0xffda, 0xffdb, 0xffdc, 0xffdd, 0xffde, 0xffdf,
+  0xffe0, 0xffe1, 0xffe2, 0xffe3, 0xffe4, 0xffe5, 0xffe6, 0xffe7,
+  0xffe8, 0xffe9, 0xffea, 0xffeb, 0xffec, 0xffed, 0xffee, 0xffef,
+  0xfff0, 0xfff1, 0xfff2, 0xfff3, 0xfff4, 0xfff5, 0xfff6, 0xfff7,
+  0xfff8, 0xfff9, 0xfffa, 0xfffb, 0xfffc, 0xfffd, 0xfffe, 0xffff
+}};
+static UnicodeCaseTableVector *caseTable[256] = {
+  &caseTable00,
+  &caseTable01,
+  &caseTable02,
+  &caseTable03,
+  &caseTable04,
+  &caseTable05,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  &caseTable1e,
+  &caseTable1f,
+  NULL,
+  &caseTable21,
+  NULL,
+  NULL,
+  &caseTable24,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  NULL,
+  &caseTableff
+};
+
+static inline char getType(Unicode c) {
+  int i;
+  char type;
+
+  if (c > 0xffff) {
+    type = 'X';
+  } else {
+    i = (c >> 8) & 0xff;
+    if ((type = typeTable[i].type) == 'X') {
+      type = typeTable[i].vector[c & 0xff];
+    }
+  }
+  return type;
+}
+
+GBool unicodeTypeL(Unicode c) {
+  return getType(c) == 'L';
+}
+
+GBool unicodeTypeR(Unicode c) {
+  return getType(c) == 'R';
+}
+
+Unicode unicodeToUpper(Unicode c) {
+  int i;
+
+  if (c > 0xffff) {
+    return c;
+  }
+  i = (c >> 8) & 0xff;
+  if (caseTable[i]) {
+    return caseTable[i]->codes[c & 0xff];
+  }
+  return c;
+}
+
diff --git a/xpdf/UnicodeTypeTable.h b/xpdf/UnicodeTypeTable.h
new file mode 100644
--- /dev/null
+++ b/xpdf/UnicodeTypeTable.h
@@ -0,0 +1,20 @@
+//========================================================================
+//
+// UnicodeTypeTable.h
+//
+// Copyright 2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef UNICODETYPETABLE_H
+#define UNICODETYPETABLE_H
+
+#include "gtypes.h"
+
+extern GBool unicodeTypeL(Unicode c);
+
+extern GBool unicodeTypeR(Unicode c);
+
+extern Unicode unicodeToUpper(Unicode c);
+
+#endif
diff --git a/xpdf/XRef.cc b/xpdf/XRef.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/XRef.cc
@@ -0,0 +1,896 @@
+//========================================================================
+//
+// XRef.cc
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <ctype.h>
+#include "gmem.h"
+#include "Object.h"
+#include "Stream.h"
+#include "Lexer.h"
+#include "Parser.h"
+#include "Dict.h"
+#include "Error.h"
+#include "ErrorCodes.h"
+#include "XRef.h"
+
+//------------------------------------------------------------------------
+
+#define xrefSearchSize 1024	// read this many bytes at end of file
+				//   to look for 'startxref'
+
+//------------------------------------------------------------------------
+// Permission bits
+//------------------------------------------------------------------------
+
+#define permPrint    (1<<2)
+#define permChange   (1<<3)
+#define permCopy     (1<<4)
+#define permNotes    (1<<5)
+#define defPermFlags 0xfffc
+
+//------------------------------------------------------------------------
+// ObjectStream
+//------------------------------------------------------------------------
+
+class ObjectStream {
+public:
+
+  // Create an object stream, using object number <objStrNum>,
+  // generation 0.
+  ObjectStream(XRef *xref, int objStrNumA);
+
+  ~ObjectStream();
+
+  // Return the object number of this object stream.
+  int getObjStrNum() { return objStrNum; }
+
+  // Get the <objIdx>th object from this stream, which should be
+  // object number <objNum>, generation 0.
+  Object *getObject(int objIdx, int objNum, Object *obj);
+
+private:
+
+  int objStrNum;		// object number of the object stream
+  int nObjects;			// number of objects in the stream
+  Object *objs;			// the objects (length = nObjects)
+  int *objNums;			// the object numbers (length = nObjects)
+};
+
+ObjectStream::ObjectStream(XRef *xref, int objStrNumA) {
+  Stream *str;
+  Parser *parser;
+  int *offsets;
+  Object objStr, obj1, obj2;
+  int first, i;
+
+  objStrNum = objStrNumA;
+  nObjects = 0;
+  objs = NULL;
+  objNums = NULL;
+
+  if (!xref->fetch(objStrNum, 0, &objStr)->isStream()) {
+    goto err1;
+  }
+
+  if (!objStr.streamGetDict()->lookup("N", &obj1)->isInt()) {
+    obj1.free();
+    goto err1;
+  }
+  nObjects = obj1.getInt();
+  obj1.free();
+  if (nObjects <= 0) {
+    goto err1;
+  }
+
+  if (!objStr.streamGetDict()->lookup("First", &obj1)->isInt()) {
+    obj1.free();
+    goto err1;
+  }
+  first = obj1.getInt();
+  obj1.free();
+  if (first < 0) {
+    goto err1;
+  }
+
+  objs = new Object[nObjects];
+  objNums = (int *)gmallocn(nObjects, sizeof(int));
+  offsets = (int *)gmallocn(nObjects, sizeof(int));
+
+  // parse the header: object numbers and offsets
+  objStr.streamReset();
+  obj1.initNull();
+  str = new EmbedStream(objStr.getStream(), &obj1, gTrue, first);
+  parser = new Parser(xref, new Lexer(xref, str), gFalse);
+  for (i = 0; i < nObjects; ++i) {
+    parser->getObj(&obj1);
+    parser->getObj(&obj2);
+    if (!obj1.isInt() || !obj2.isInt()) {
+      obj1.free();
+      obj2.free();
+      delete parser;
+      gfree(offsets);
+      goto err1;
+    }
+    objNums[i] = obj1.getInt();
+    offsets[i] = obj2.getInt();
+    obj1.free();
+    obj2.free();
+    if (objNums[i] < 0 || offsets[i] < 0 ||
+	(i > 0 && offsets[i] < offsets[i-1])) {
+      delete parser;
+      gfree(offsets);
+      goto err1;
+    }
+  }
+  while (str->getChar() != EOF) ;
+  delete parser;
+
+  // skip to the first object - this shouldn't be necessary because
+  // the First key is supposed to be equal to offsets[0], but just in
+  // case...
+  for (i = first; i < offsets[0]; ++i) {
+    objStr.getStream()->getChar();
+  }
+
+  // parse the objects
+  for (i = 0; i < nObjects; ++i) {
+    obj1.initNull();
+    if (i == nObjects - 1) {
+      str = new EmbedStream(objStr.getStream(), &obj1, gFalse, 0);
+    } else {
+      str = new EmbedStream(objStr.getStream(), &obj1, gTrue,
+			    offsets[i+1] - offsets[i]);
+    }
+    parser = new Parser(xref, new Lexer(xref, str), gFalse);
+    parser->getObj(&objs[i]);
+    while (str->getChar() != EOF) ;
+    delete parser;
+  }
+
+  gfree(offsets);
+
+ err1:
+  objStr.free();
+  return;
+}
+
+ObjectStream::~ObjectStream() {
+  int i;
+
+  if (objs) {
+    for (i = 0; i < nObjects; ++i) {
+      objs[i].free();
+    }
+    delete[] objs;
+  }
+  gfree(objNums);
+}
+
+Object *ObjectStream::getObject(int objIdx, int objNum, Object *obj) {
+  if (objIdx < 0 || objIdx >= nObjects || objNum != objNums[objIdx]) {
+    return obj->initNull();
+  }
+  return objs[objIdx].copy(obj);
+}
+
+//------------------------------------------------------------------------
+// XRef
+//------------------------------------------------------------------------
+
+XRef::XRef(BaseStream *strA) {
+  Guint pos;
+  Object obj;
+
+  ok = gTrue;
+  errCode = errNone;
+  size = 0;
+  entries = NULL;
+  streamEnds = NULL;
+  streamEndsLen = 0;
+  objStr = NULL;
+
+  encrypted = gFalse;
+  permFlags = defPermFlags;
+  ownerPasswordOk = gFalse;
+
+  // read the trailer
+  str = strA;
+  start = str->getStart();
+  pos = getStartXref();
+
+  // if there was a problem with the 'startxref' position, try to
+  // reconstruct the xref table
+  if (pos == 0) {
+    if (!(ok = constructXRef())) {
+      errCode = errDamaged;
+      return;
+    }
+
+  // read the xref table
+  } else {
+    while (readXRef(&pos)) ;
+
+    // if there was a problem with the xref table,
+    // try to reconstruct it
+    if (!ok) {
+      if (!(ok = constructXRef())) {
+	errCode = errDamaged;
+	return;
+      }
+    }
+  }
+
+  // get the root dictionary (catalog) object
+  trailerDict.dictLookupNF("Root", &obj);
+  if (obj.isRef()) {
+    rootNum = obj.getRefNum();
+    rootGen = obj.getRefGen();
+    obj.free();
+  } else {
+    obj.free();
+    if (!(ok = constructXRef())) {
+      errCode = errDamaged;
+      return;
+    }
+  }
+
+  // now set the trailer dictionary's xref pointer so we can fetch
+  // indirect objects from it
+  trailerDict.getDict()->setXRef(this);
+}
+
+XRef::~XRef() {
+  gfree(entries);
+  trailerDict.free();
+  if (streamEnds) {
+    gfree(streamEnds);
+  }
+  if (objStr) {
+    delete objStr;
+  }
+}
+
+// Read the 'startxref' position.
+Guint XRef::getStartXref() {
+  char buf[xrefSearchSize+1];
+  char *p;
+  int c, n, i;
+
+  // read last xrefSearchSize bytes
+  str->setPos(xrefSearchSize, -1);
+  for (n = 0; n < xrefSearchSize; ++n) {
+    if ((c = str->getChar()) == EOF) {
+      break;
+    }
+    buf[n] = c;
+  }
+  buf[n] = '\0';
+
+  // find startxref
+  for (i = n - 9; i >= 0; --i) {
+    if (!strncmp(&buf[i], "startxref", 9)) {
+      break;
+    }
+  }
+  if (i < 0) {
+    return 0;
+  }
+  for (p = &buf[i+9]; isspace(*p); ++p) ;
+  lastXRefPos = strToUnsigned(p);
+
+  return lastXRefPos;
+}
+
+// Read one xref table section.  Also reads the associated trailer
+// dictionary, and returns the prev pointer (if any).
+GBool XRef::readXRef(Guint *pos) {
+  Parser *parser;
+  Object obj;
+  GBool more;
+
+  // start up a parser, parse one token
+  obj.initNull();
+  parser = new Parser(NULL,
+	     new Lexer(NULL,
+	       str->makeSubStream(start + *pos, gFalse, 0, &obj)),
+	     gTrue);
+  parser->getObj(&obj);
+
+  // parse an old-style xref table
+  if (obj.isCmd("xref")) {
+    obj.free();
+    more = readXRefTable(parser, pos);
+
+  // parse an xref stream
+  } else if (obj.isInt()) {
+    obj.free();
+    if (!parser->getObj(&obj)->isInt()) {
+      goto err1;
+    }
+    obj.free();
+    if (!parser->getObj(&obj)->isCmd("obj")) {
+      goto err1;
+    }
+    obj.free();
+    if (!parser->getObj(&obj)->isStream()) {
+      goto err1;
+    }
+    more = readXRefStream(obj.getStream(), pos);
+    obj.free();
+
+  } else {
+    goto err1;
+  }
+
+  delete parser;
+  return more;
+
+ err1:
+  obj.free();
+  delete parser;
+  ok = gFalse;
+  return gFalse;
+}
+
+GBool XRef::readXRefTable(Parser *parser, Guint *pos) {
+  XRefEntry entry;
+  GBool more;
+  Object obj, obj2;
+  Guint pos2;
+  int first, n, newSize, i;
+
+  while (1) {
+    parser->getObj(&obj);
+    if (obj.isCmd("trailer")) {
+      obj.free();
+      break;
+    }
+    if (!obj.isInt()) {
+      goto err1;
+    }
+    first = obj.getInt();
+    obj.free();
+    if (!parser->getObj(&obj)->isInt()) {
+      goto err1;
+    }
+    n = obj.getInt();
+    obj.free();
+    if (first < 0 || n < 0 || first + n < 0) {
+      goto err1;
+    }
+    if (first + n > size) {
+      for (newSize = size ? 2 * size : 1024;
+	   first + n > newSize && newSize > 0;
+	   newSize <<= 1) ;
+      if (newSize < 0) {
+	goto err1;
+      }
+      entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry));
+      for (i = size; i < newSize; ++i) {
+	entries[i].offset = 0xffffffff;
+	entries[i].type = xrefEntryFree;
+      }
+      size = newSize;
+    }
+    for (i = first; i < first + n; ++i) {
+      if (!parser->getObj(&obj)->isInt()) {
+	goto err1;
+      }
+      entry.offset = (Guint)obj.getInt();
+      obj.free();
+      if (!parser->getObj(&obj)->isInt()) {
+	goto err1;
+      }
+      entry.gen = obj.getInt();
+      obj.free();
+      parser->getObj(&obj);
+      if (obj.isCmd("n")) {
+	entry.type = xrefEntryUncompressed;
+      } else if (obj.isCmd("f")) {
+	entry.type = xrefEntryFree;
+      } else {
+	goto err1;
+      }
+      obj.free();
+      if (entries[i].offset == 0xffffffff) {
+	entries[i] = entry;
+	// PDF files of patents from the IBM Intellectual Property
+	// Network have a bug: the xref table claims to start at 1
+	// instead of 0.
+	if (i == 1 && first == 1 &&
+	    entries[1].offset == 0 && entries[1].gen == 65535 &&
+	    entries[1].type == xrefEntryFree) {
+	  i = first = 0;
+	  entries[0] = entries[1];
+	  entries[1].offset = 0xffffffff;
+	}
+      }
+    }
+  }
+
+  // read the trailer dictionary
+  if (!parser->getObj(&obj)->isDict()) {
+    goto err1;
+  }
+
+  // get the 'Prev' pointer
+  obj.getDict()->lookupNF("Prev", &obj2);
+  if (obj2.isInt()) {
+    *pos = (Guint)obj2.getInt();
+    more = gTrue;
+  } else if (obj2.isRef()) {
+    // certain buggy PDF generators generate "/Prev NNN 0 R" instead
+    // of "/Prev NNN"
+    *pos = (Guint)obj2.getRefNum();
+    more = gTrue;
+  } else {
+    more = gFalse;
+  }
+  obj2.free();
+
+  // save the first trailer dictionary
+  if (trailerDict.isNone()) {
+    obj.copy(&trailerDict);
+  }
+
+  // check for an 'XRefStm' key
+  if (obj.getDict()->lookup("XRefStm", &obj2)->isInt()) {
+    pos2 = (Guint)obj2.getInt();
+    readXRef(&pos2);
+    if (!ok) {
+      obj2.free();
+      goto err1;
+    }
+  }
+  obj2.free();
+
+  obj.free();
+  return more;
+
+ err1:
+  obj.free();
+  ok = gFalse;
+  return gFalse;
+}
+
+GBool XRef::readXRefStream(Stream *xrefStr, Guint *pos) {
+  Dict *dict;
+  int w[3];
+  GBool more;
+  Object obj, obj2, idx;
+  int newSize, first, n, i;
+
+  dict = xrefStr->getDict();
+
+  if (!dict->lookupNF("Size", &obj)->isInt()) {
+    goto err1;
+  }
+  newSize = obj.getInt();
+  obj.free();
+  if (newSize < 0) {
+    goto err1;
+  }
+  if (newSize > size) {
+    entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry));
+    for (i = size; i < newSize; ++i) {
+      entries[i].offset = 0xffffffff;
+      entries[i].type = xrefEntryFree;
+    }
+    size = newSize;
+  }
+
+  if (!dict->lookupNF("W", &obj)->isArray() ||
+      obj.arrayGetLength() < 3) {
+    goto err1;
+  }
+  for (i = 0; i < 3; ++i) {
+    if (!obj.arrayGet(i, &obj2)->isInt()) {
+      obj2.free();
+      goto err1;
+    }
+    w[i] = obj2.getInt();
+    obj2.free();
+    if (w[i] < 0 || w[i] > 4) {
+      goto err1;
+    }
+  }
+  obj.free();
+
+  xrefStr->reset();
+  dict->lookupNF("Index", &idx);
+  if (idx.isArray()) {
+    for (i = 0; i+1 < idx.arrayGetLength(); i += 2) {
+      if (!idx.arrayGet(i, &obj)->isInt()) {
+	idx.free();
+	goto err1;
+      }
+      first = obj.getInt();
+      obj.free();
+      if (!idx.arrayGet(i+1, &obj)->isInt()) {
+	idx.free();
+	goto err1;
+      }
+      n = obj.getInt();
+      obj.free();
+      if (first < 0 || n < 0 ||
+	  !readXRefStreamSection(xrefStr, w, first, n)) {
+	idx.free();
+	goto err0;
+      }
+    }
+  } else {
+    if (!readXRefStreamSection(xrefStr, w, 0, newSize)) {
+      idx.free();
+      goto err0;
+    }
+  }
+  idx.free();
+
+  dict->lookupNF("Prev", &obj);
+  if (obj.isInt()) {
+    *pos = (Guint)obj.getInt();
+    more = gTrue;
+  } else {
+    more = gFalse;
+  }
+  obj.free();
+  if (trailerDict.isNone()) {
+    trailerDict.initDict(dict);
+  }
+
+  return more;
+
+ err1:
+  obj.free();
+ err0:
+  ok = gFalse;
+  return gFalse;
+}
+
+GBool XRef::readXRefStreamSection(Stream *xrefStr, int *w, int first, int n) {
+  Guint offset;
+  int type, gen, c, newSize, i, j;
+
+  if (first + n < 0) {
+    return gFalse;
+  }
+  if (first + n > size) {
+    for (newSize = size ? 2 * size : 1024;
+	 first + n > newSize && newSize > 0;
+	 newSize <<= 1) ;
+    if (newSize < 0) {
+      return gFalse;
+    }
+    entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry));
+    for (i = size; i < newSize; ++i) {
+      entries[i].offset = 0xffffffff;
+      entries[i].type = xrefEntryFree;
+    }
+    size = newSize;
+  }
+  for (i = first; i < first + n; ++i) {
+    if (w[0] == 0) {
+      type = 1;
+    } else {
+      for (type = 0, j = 0; j < w[0]; ++j) {
+	if ((c = xrefStr->getChar()) == EOF) {
+	  return gFalse;
+	}
+	type = (type << 8) + c;
+      }
+    }
+    for (offset = 0, j = 0; j < w[1]; ++j) {
+      if ((c = xrefStr->getChar()) == EOF) {
+	return gFalse;
+      }
+      offset = (offset << 8) + c;
+    }
+    for (gen = 0, j = 0; j < w[2]; ++j) {
+      if ((c = xrefStr->getChar()) == EOF) {
+	return gFalse;
+      }
+      gen = (gen << 8) + c;
+    }
+    if (entries[i].offset == 0xffffffff) {
+      switch (type) {
+      case 0:
+	entries[i].offset = offset;
+	entries[i].gen = gen;
+	entries[i].type = xrefEntryFree;
+	break;
+      case 1:
+	entries[i].offset = offset;
+	entries[i].gen = gen;
+	entries[i].type = xrefEntryUncompressed;
+	break;
+      case 2:
+	entries[i].offset = offset;
+	entries[i].gen = gen;
+	entries[i].type = xrefEntryCompressed;
+	break;
+      default:
+	return gFalse;
+      }
+    }
+  }
+
+  return gTrue;
+}
+
+// Attempt to construct an xref table for a damaged file.
+GBool XRef::constructXRef() {
+  Parser *parser;
+  Object newTrailerDict, obj;
+  char buf[256];
+  Guint pos;
+  int num, gen;
+  int newSize;
+  int streamEndsSize;
+  char *p;
+  int i;
+  GBool gotRoot;
+
+  gfree(entries);
+  size = 0;
+  entries = NULL;
+
+  error(-1, "PDF file is damaged - attempting to reconstruct xref table...");
+  gotRoot = gFalse;
+  streamEndsLen = streamEndsSize = 0;
+
+  str->reset();
+  while (1) {
+    pos = str->getPos();
+    if (!str->getLine(buf, 256)) {
+      break;
+    }
+    p = buf;
+
+    // skip whitespace
+    while (*p && Lexer::isSpace(*p & 0xff)) ++p;
+
+    // got trailer dictionary
+    if (!strncmp(p, "trailer", 7)) {
+      obj.initNull();
+      parser = new Parser(NULL,
+		 new Lexer(NULL,
+		   str->makeSubStream(pos + 7, gFalse, 0, &obj)),
+		 gFalse);
+      parser->getObj(&newTrailerDict);
+      if (newTrailerDict.isDict()) {
+	newTrailerDict.dictLookupNF("Root", &obj);
+	if (obj.isRef()) {
+	  rootNum = obj.getRefNum();
+	  rootGen = obj.getRefGen();
+	  if (!trailerDict.isNone()) {
+	    trailerDict.free();
+	  }
+	  newTrailerDict.copy(&trailerDict);
+	  gotRoot = gTrue;
+	}
+	obj.free();
+      }
+      newTrailerDict.free();
+      delete parser;
+
+    // look for object
+    } else if (isdigit(*p)) {
+      num = atoi(p);
+      if (num > 0) {
+	do {
+	  ++p;
+	} while (*p && isdigit(*p));
+	if (isspace(*p)) {
+	  do {
+	    ++p;
+	  } while (*p && isspace(*p));
+	  if (isdigit(*p)) {
+	    gen = atoi(p);
+	    do {
+	      ++p;
+	    } while (*p && isdigit(*p));
+	    if (isspace(*p)) {
+	      do {
+		++p;
+	      } while (*p && isspace(*p));
+	      if (!strncmp(p, "obj", 3)) {
+		if (num >= size) {
+		  newSize = (num + 1 + 255) & ~255;
+		  if (newSize < 0) {
+		    error(-1, "Bad object number");
+		    return gFalse;
+		  }
+		  entries = (XRefEntry *)
+		      greallocn(entries, newSize, sizeof(XRefEntry));
+		  for (i = size; i < newSize; ++i) {
+		    entries[i].offset = 0xffffffff;
+		    entries[i].type = xrefEntryFree;
+		  }
+		  size = newSize;
+		}
+		if (entries[num].type == xrefEntryFree ||
+		    gen >= entries[num].gen) {
+		  entries[num].offset = pos - start;
+		  entries[num].gen = gen;
+		  entries[num].type = xrefEntryUncompressed;
+		}
+	      }
+	    }
+	  }
+	}
+      }
+
+    } else if (!strncmp(p, "endstream", 9)) {
+      if (streamEndsLen == streamEndsSize) {
+	streamEndsSize += 64;
+	streamEnds = (Guint *)greallocn(streamEnds,
+					streamEndsSize, sizeof(int));
+      }
+      streamEnds[streamEndsLen++] = pos;
+    }
+  }
+
+  if (gotRoot)
+    return gTrue;
+
+  error(-1, "Couldn't find trailer dictionary");
+  return gFalse;
+}
+
+void XRef::setEncryption(int permFlagsA, GBool ownerPasswordOkA,
+			 Guchar *fileKeyA, int keyLengthA, int encVersionA,
+			 CryptAlgorithm encAlgorithmA) {
+  int i;
+
+  encrypted = gTrue;
+  permFlags = permFlagsA;
+  ownerPasswordOk = ownerPasswordOkA;
+  if (keyLengthA <= 16) {
+    keyLength = keyLengthA;
+  } else {
+    keyLength = 16;
+  }
+  for (i = 0; i < keyLength; ++i) {
+    fileKey[i] = fileKeyA[i];
+  }
+  encVersion = encVersionA;
+  encAlgorithm = encAlgorithmA;
+}
+
+GBool XRef::okToPrint(GBool ignoreOwnerPW) {
+  return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permPrint);
+}
+
+GBool XRef::okToChange(GBool ignoreOwnerPW) {
+  return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permChange);
+}
+
+GBool XRef::okToCopy(GBool ignoreOwnerPW) {
+  return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permCopy);
+}
+
+GBool XRef::okToAddNotes(GBool ignoreOwnerPW) {
+  return (!ignoreOwnerPW && ownerPasswordOk) || (permFlags & permNotes);
+}
+
+Object *XRef::fetch(int num, int gen, Object *obj) {
+  XRefEntry *e;
+  Parser *parser;
+  Object obj1, obj2, obj3;
+
+  // check for bogus ref - this can happen in corrupted PDF files
+  if (num < 0 || num >= size) {
+    goto err;
+  }
+
+  e = &entries[num];
+  switch (e->type) {
+
+  case xrefEntryUncompressed:
+    if (e->gen != gen) {
+      goto err;
+    }
+    obj1.initNull();
+    parser = new Parser(this,
+	       new Lexer(this,
+		 str->makeSubStream(start + e->offset, gFalse, 0, &obj1)),
+	       gTrue);
+    parser->getObj(&obj1);
+    parser->getObj(&obj2);
+    parser->getObj(&obj3);
+    if (!obj1.isInt() || obj1.getInt() != num ||
+	!obj2.isInt() || obj2.getInt() != gen ||
+	!obj3.isCmd("obj")) {
+      obj1.free();
+      obj2.free();
+      obj3.free();
+      delete parser;
+      goto err;
+    }
+    parser->getObj(obj, encrypted ? fileKey : (Guchar *)NULL,
+		   encAlgorithm, keyLength, num, gen);
+    obj1.free();
+    obj2.free();
+    obj3.free();
+    delete parser;
+    break;
+
+  case xrefEntryCompressed:
+    if (gen != 0) {
+      goto err;
+    }
+    if (!objStr || objStr->getObjStrNum() != (int)e->offset) {
+      if (objStr) {
+	delete objStr;
+      }
+      objStr = new ObjectStream(this, e->offset);
+    }
+    objStr->getObject(e->gen, num, obj);
+    break;
+
+  default:
+    goto err;
+  }
+
+  return obj;
+
+ err:
+  return obj->initNull();
+}
+
+Object *XRef::getDocInfo(Object *obj) {
+  return trailerDict.dictLookup("Info", obj);
+}
+
+// Added for the pdftex project.
+Object *XRef::getDocInfoNF(Object *obj) {
+  return trailerDict.dictLookupNF("Info", obj);
+}
+
+GBool XRef::getStreamEnd(Guint streamStart, Guint *streamEnd) {
+  int a, b, m;
+
+  if (streamEndsLen == 0 ||
+      streamStart > streamEnds[streamEndsLen - 1]) {
+    return gFalse;
+  }
+
+  a = -1;
+  b = streamEndsLen - 1;
+  // invariant: streamEnds[a] < streamStart <= streamEnds[b]
+  while (b - a > 1) {
+    m = (a + b) / 2;
+    if (streamStart <= streamEnds[m]) {
+      b = m;
+    } else {
+      a = m;
+    }
+  }
+  *streamEnd = streamEnds[b];
+  return gTrue;
+}
+
+Guint XRef::strToUnsigned(char *s) {
+  Guint x;
+  char *p;
+  int i;
+
+  x = 0;
+  for (p = s, i = 0; *p && isdigit(*p) && i < 10; ++p, ++i) {
+    x = 10 * x + (*p - '0');
+  }
+  return x;
+}
diff --git a/xpdf/XRef.h b/xpdf/XRef.h
new file mode 100644
--- /dev/null
+++ b/xpdf/XRef.h
@@ -0,0 +1,133 @@
+//========================================================================
+//
+// XRef.h
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef XREF_H
+#define XREF_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "gtypes.h"
+#include "Object.h"
+
+class Dict;
+class Stream;
+class Parser;
+class ObjectStream;
+
+//------------------------------------------------------------------------
+// XRef
+//------------------------------------------------------------------------
+
+enum XRefEntryType {
+  xrefEntryFree,
+  xrefEntryUncompressed,
+  xrefEntryCompressed
+};
+
+struct XRefEntry {
+  Guint offset;
+  int gen;
+  XRefEntryType type;
+};
+
+class XRef {
+public:
+
+  // Constructor.  Read xref table from stream.
+  XRef(BaseStream *strA);
+
+  // Destructor.
+  ~XRef();
+
+  // Is xref table valid?
+  GBool isOk() { return ok; }
+
+  // Get the error code (if isOk() returns false).
+  int getErrorCode() { return errCode; }
+
+  // Set the encryption parameters.
+  void setEncryption(int permFlagsA, GBool ownerPasswordOkA,
+		     Guchar *fileKeyA, int keyLengthA, int encVersionA,
+		     CryptAlgorithm encAlgorithmA);
+
+  // Is the file encrypted?
+  GBool isEncrypted() { return encrypted; }
+
+  // Check various permissions.
+  GBool okToPrint(GBool ignoreOwnerPW = gFalse);
+  GBool okToChange(GBool ignoreOwnerPW = gFalse);
+  GBool okToCopy(GBool ignoreOwnerPW = gFalse);
+  GBool okToAddNotes(GBool ignoreOwnerPW = gFalse);
+
+  // Get catalog object.
+  Object *getCatalog(Object *obj) { return fetch(rootNum, rootGen, obj); }
+
+  // Fetch an indirect reference.
+  Object *fetch(int num, int gen, Object *obj);
+
+  // Return the document's Info dictionary (if any).
+  Object *getDocInfo(Object *obj);
+  Object *getDocInfoNF(Object *obj);
+
+  // Return the number of objects in the xref table.
+  int getNumObjects() { return size; }
+
+  // Return the offset of the last xref table.
+  Guint getLastXRefPos() { return lastXRefPos; }
+
+  // Return the catalog object reference.
+  int getRootNum() { return rootNum; }
+  int getRootGen() { return rootGen; }
+
+  // Get end position for a stream in a damaged file.
+  // Returns false if unknown or file is not damaged.
+  GBool getStreamEnd(Guint streamStart, Guint *streamEnd);
+
+  // Direct access.
+  int getSize() { return size; }
+  XRefEntry *getEntry(int i) { return &entries[i]; }
+  Object *getTrailerDict() { return &trailerDict; }
+
+private:
+
+  BaseStream *str;		// input stream
+  Guint start;			// offset in file (to allow for garbage
+				//   at beginning of file)
+  XRefEntry *entries;		// xref entries
+  int size;			// size of <entries> array
+  int rootNum, rootGen;		// catalog dict
+  GBool ok;			// true if xref table is valid
+  int errCode;			// error code (if <ok> is false)
+  Object trailerDict;		// trailer dictionary
+  Guint lastXRefPos;		// offset of last xref table
+  Guint *streamEnds;		// 'endstream' positions - only used in
+				//   damaged files
+  int streamEndsLen;		// number of valid entries in streamEnds
+  ObjectStream *objStr;		// cached object stream
+  GBool encrypted;		// true if file is encrypted
+  int permFlags;		// permission bits
+  GBool ownerPasswordOk;	// true if owner password is correct
+  Guchar fileKey[16];		// file decryption key
+  int keyLength;		// length of key, in bytes
+  int encVersion;		// encryption version
+  CryptAlgorithm encAlgorithm;	// encryption algorithm
+
+  Guint getStartXref();
+  GBool readXRef(Guint *pos);
+  GBool readXRefTable(Parser *parser, Guint *pos);
+  GBool readXRefStreamSection(Stream *xrefStr, int *w, int first, int n);
+  GBool readXRefStream(Stream *xrefStr, Guint *pos);
+  GBool constructXRef();
+  Guint strToUnsigned(char *s);
+};
+
+#endif
diff --git a/xpdf/XpdfPluginAPI.cc b/xpdf/XpdfPluginAPI.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/XpdfPluginAPI.cc
@@ -0,0 +1,262 @@
+//========================================================================
+//
+// XpdfPluginAPI.cc
+//
+// Copyright 2004 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include "aconf.h"
+
+#ifdef ENABLE_PLUGINS
+
+#include "gmem.h"
+#include "GlobalParams.h"
+#include "Object.h"
+#include "PDFDoc.h"
+#ifdef WIN32
+#include "WinPDFCore.h"
+#else
+#include "XPDFCore.h"
+#endif
+#include "XpdfPluginAPI.h"
+
+//------------------------------------------------------------------------
+
+//~ This should use a pool of Objects; change xpdfFreeObj to match.
+static Object *allocObj() {
+  return (Object *)gmalloc(sizeof(Object));
+}
+
+//------------------------------------------------------------------------
+// Document access functions
+//------------------------------------------------------------------------
+
+XpdfObject _xpdfGetInfoDict(XpdfDoc doc) {
+  Object *obj;
+
+  obj = allocObj();
+  return (XpdfObject)((PDFDoc *)doc)->getDocInfo(obj);
+}
+
+XpdfObject _xpdfGetCatalog(XpdfDoc doc) {
+  Object *obj;
+
+  obj = allocObj();
+  return (XpdfObject)((PDFDoc *)doc)->getXRef()->getCatalog(obj);
+}
+
+#ifdef _WIN32
+
+HWND _xpdfWin32GetWindow(XpdfDoc doc) {
+  WinPDFCore *core;
+
+  if (!(core = (WinPDFCore *)((PDFDoc *)doc)->getGUIData())) {
+    return NULL;
+  }
+  return core->getDrawFrame();
+}
+
+#else
+
+Widget _xpdfXGetWindow(XpdfDoc doc) {
+  XPDFCore *core;
+
+  if (!(core = (XPDFCore *)((PDFDoc *)doc)->getGUIData())) {
+    return NULL;
+  }
+  return core->getWidget();
+}
+
+#endif
+
+//------------------------------------------------------------------------
+// Object access functions.
+//------------------------------------------------------------------------
+
+XpdfBool _xpdfObjIsBool(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->isBool();
+}
+
+XpdfBool _xpdfObjIsInt(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->isInt();
+}
+
+XpdfBool _xpdfObjIsReal(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->isReal();
+}
+
+XpdfBool _xpdfObjIsNumber(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->isNum();
+}
+
+XpdfBool _xpdfObjIsString(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->isString();
+}
+
+XpdfBool _xpdfObjIsName(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->isName();
+}
+
+XpdfBool _xpdfObjIsNull(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->isNull();
+}
+
+XpdfBool _xpdfObjIsArray(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->isArray();
+}
+
+XpdfBool _xpdfObjIsDict(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->isDict();
+}
+
+XpdfBool _xpdfObjIsStream(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->isStream();
+}
+
+XpdfBool _xpdfObjIsRef(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->isRef();
+}
+
+XpdfBool _xpdfBoolValue(XpdfObject obj) {
+  return (XpdfBool)((Object *)obj)->getBool();
+}
+
+int _xpdfIntValue(XpdfObject obj) {
+  if (!((Object *)obj)->isInt()) {
+    return 0;
+  }
+  return ((Object *)obj)->getInt();
+}
+
+double _xpdfRealValue(XpdfObject obj) {
+  if (!((Object *)obj)->isReal()) {
+    return 0;
+  }
+  return ((Object *)obj)->getReal();
+}
+
+double _xpdfNumberValue(XpdfObject obj) {
+  if (!((Object *)obj)->isNum()) {
+    return 0;
+  }
+  return ((Object *)obj)->getNum();
+}
+
+int _xpdfStringLength(XpdfObject obj) {
+  if (!((Object *)obj)->isString()) {
+    return 0;
+  }
+  return ((Object *)obj)->getString()->getLength();
+}
+
+char *_xpdfStringValue(XpdfObject obj) {
+  if (!((Object *)obj)->isString()) {
+    return 0;
+  }
+  return ((Object *)obj)->getString()->getCString();
+}
+
+char *_xpdfNameValue(XpdfObject obj) {
+  if (!((Object *)obj)->isName()) {
+    return NULL;
+  }
+  return ((Object *)obj)->getName();
+}
+
+int _xpdfArrayLength(XpdfObject obj) {
+  if (!((Object *)obj)->isArray()) {
+    return 0;
+  }
+  return ((Object *)obj)->arrayGetLength();
+}
+
+XpdfObject _xpdfArrayGet(XpdfObject obj, int idx) {
+  Object *elem;
+
+  elem = allocObj();
+  if (!((Object *)obj)->isArray()) {
+    return (XpdfObject)elem->initNull();
+  }
+  return (XpdfObject)((Object *)obj)->arrayGet(idx, elem);
+}
+
+XpdfObject _xpdfDictGet(XpdfObject obj, char *key) {
+  Object *elem;
+
+  elem = allocObj();
+  if (!((Object *)obj)->isDict()) {
+    return (XpdfObject)elem->initNull();
+  }
+  return (XpdfObject)((Object *)obj)->dictLookup(key, elem);
+}
+
+void _xpdfFreeObj(XpdfObject obj) {
+  ((Object *)obj)->free();
+  gfree(obj);
+}
+
+//------------------------------------------------------------------------
+// Memory allocation functions
+//------------------------------------------------------------------------
+
+void *_xpdfMalloc(int size) {
+  return gmalloc(size);
+}
+
+void *_xpdfRealloc(void *p, int size) {
+  return grealloc(p, size);
+}
+
+void _xpdfFree(void *p) {
+  gfree(p);
+}
+
+//------------------------------------------------------------------------
+// Security handlers
+//------------------------------------------------------------------------
+
+void _xpdfRegisterSecurityHandler(XpdfSecurityHandler *handler) {
+  if (handler->version <= xpdfPluginAPIVersion) {
+    globalParams->addSecurityHandler(handler);
+  }
+}
+
+//------------------------------------------------------------------------
+
+XpdfPluginVecTable xpdfPluginVecTable = {
+  xpdfPluginAPIVersion,
+  &_xpdfGetInfoDict,
+  &_xpdfGetCatalog,
+#ifdef _WIN32
+  &_xpdfWin32GetWindow,
+#else
+  &_xpdfXGetWindow,
+#endif
+  &_xpdfObjIsBool,
+  &_xpdfObjIsInt,
+  &_xpdfObjIsReal,
+  &_xpdfObjIsString,
+  &_xpdfObjIsName,
+  &_xpdfObjIsNull,
+  &_xpdfObjIsArray,
+  &_xpdfObjIsDict,
+  &_xpdfObjIsStream,
+  &_xpdfObjIsRef,
+  &_xpdfBoolValue,
+  &_xpdfIntValue,
+  &_xpdfRealValue,
+  &_xpdfStringLength,
+  &_xpdfStringValue,
+  &_xpdfNameValue,
+  &_xpdfArrayLength,
+  &_xpdfArrayGet,
+  &_xpdfDictGet,
+  &_xpdfFreeObj,
+  &_xpdfMalloc,
+  &_xpdfRealloc,
+  &_xpdfFree,
+  &_xpdfRegisterSecurityHandler,
+};
+
+#endif // ENABLE_PLUGINS
diff --git a/xpdf/XpdfPluginAPI.h b/xpdf/XpdfPluginAPI.h
new file mode 100644
--- /dev/null
+++ b/xpdf/XpdfPluginAPI.h
@@ -0,0 +1,341 @@
+/*
+ * XpdfPluginAPI.h
+ *
+ * Copyright 2004 Glyph & Cog, LLC
+ */
+
+#ifndef XPDFPLUGINAPI_H
+#define XPDFPLUGINAPI_H
+
+#ifdef _WIN32
+#include <windows.h>
+#else
+#define Object XtObject
+#include <X11/Intrinsic.h>
+#undef Object
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*------------------------------------------------------------------------
+ * Macros
+ *------------------------------------------------------------------------*/
+
+/*
+ * The current API version.
+ */
+#define xpdfPluginAPIVersion 1
+
+#ifdef _WIN32
+#  ifdef __cplusplus
+#    define PLUGINFUNC(retType) extern "C" __declspec(dllexport) retType
+#  else
+#    define PLUGINFUNC(retType) extern __declspec(dllexport) retType
+#  endif
+#else
+#  ifdef __cplusplus
+#    define PLUGINFUNC(retType) extern "C" retType
+#  else
+#    define PLUGINFUNC(retType) extern retType
+#  endif
+#endif
+
+/*------------------------------------------------------------------------
+ * Plugin setup/cleanup
+ *------------------------------------------------------------------------*/
+
+/*
+ * All plugins are required to implement two functions:
+ *
+ * -- Initialize the plugin.  Returns non-zero if successful.
+ * PLUGINFUNC(XpdfBool) xpdfInitPlugin(void);
+ *
+ * -- Free the plugin.
+ * PLUGINFUNC(void) xpdfFreePlugin(void);
+ */
+
+/*------------------------------------------------------------------------
+ * Types
+ *------------------------------------------------------------------------*/
+
+/*
+ * Standard C boolean -- zero = false, non-zero = true.
+ */
+typedef int XpdfBool;
+#define xpdfTrue  1
+#define xpdfFalse 0
+
+/*
+ * PDF document handle.
+ */
+typedef struct _XpdfDoc *XpdfDoc;
+
+/*
+ * PDF object handle.
+ */
+typedef struct _XpdfObject *XpdfObject;
+
+/*
+ * Document access permissions.  Any of these can be bitwise 'or'ed
+ * together.  If xpdfPermissionOpen is not included, the document
+ * cannot be opened at all, and the other bits are ignored.
+ */
+typedef unsigned int XpdfPermission;
+#define xpdfPermissionOpen     (1 << 0)
+#define xpdfPermissionPrint    (1 << 2)
+#define xpdfPermissionChange   (1 << 3)
+#define xpdfPermissionCopy     (1 << 4)
+#define xpdfPermissionNotes    (1 << 5)
+
+/*------------------------------------------------------------------------
+ * Security handler
+ *------------------------------------------------------------------------*/
+
+/*
+ * XpdfSecurityHandler - a security handler plugin should create one
+ * of these and pass it to xpdfRegisterSecurityHandler.
+ */
+#ifdef __cplusplus
+struct XpdfSecurityHandler {
+#else
+typedef struct {
+#endif
+
+  /*
+   * Version of the security handler spec (this document) -- use
+   * xpdfPluginAPIVersion.
+   */
+  int version;
+
+  /*
+   * Security handler name.
+   */
+  char *name;
+
+  /*
+   * Any global data the security handler needs.  XpdfViewer will pass
+   * this pointer to all handler functions as the <handlerData>
+   * argument.
+   */
+  void *handlerData;
+
+  /*
+   * Allocate and initialize data for a new document.  XpdfViewer will
+   * pass the returned pointer to all other handler functions as the
+   * <docData> argument.  Returns non-zero if successful.
+   */
+  XpdfBool (*newDoc)(void *handlerData, XpdfDoc doc,
+		     XpdfObject encryptDict, void **docData);
+
+  /*
+   * Free the data allocated by newDoc.
+   */
+  void (*freeDoc)(void *handlerData, void *docData);
+
+  /*
+   * Construct authorization data based on the supplied owner and user
+   * passwords (either or both of which may be NULL).  This function
+   * is called in "batch" mode, i.e., if the password was supplied on
+   * the command line or via an Xpdf library API.  It should not
+   * generate any user interaction (e.g., a password dialog).  It is
+   * not required to support this function: the makeAuthData function
+   * pointer can be set to NULL.  Returns non-zero if successful.
+   */
+  XpdfBool (*makeAuthData)(void *handlerData, void *docData,
+			   char *ownerPassword, char *userPassword,
+			   void **authData);
+
+  /*
+   * Request any needed information (e.g., a password) from the user,
+   * and construct an authorization data object.  Returns non-zero if
+   * successful.
+   */
+  XpdfBool (*getAuthData)(void *handlerData, void *docData,
+			  void **authData);
+
+  /*
+   * Free the data allocated by getAuthData.
+   */
+  void (*freeAuthData)(void *handlerData, void *docData,
+		       void *authData);
+
+  /*
+   * Request permission to access the document.  This returns all
+   * permissions granted by authData.
+   */
+  XpdfPermission (*authorize)(void *handlerData, void *docData,
+			      void *authData);
+
+  /*
+   * Get the decryption key and algorithm version associated with the
+   * document.  Returns non-zero if successful.
+   */
+  XpdfBool (*getKey)(void *handlerData, void *docData,
+		     char **key, int *keyLen, int *cryptVersion);
+
+  /*
+   * Free the data allocated by getKey.
+   */
+  void (*freeKey)(void *handlerData, void *docData,
+		  char *key, int keyLen);
+
+#ifdef __cplusplus
+};
+#else
+} XpdfSecurityHandler;
+#endif
+
+/*------------------------------------------------------------------------*/
+
+typedef struct {
+  int version;
+
+/*------------------------------------------------------------------------
+ * Document access functions
+ *------------------------------------------------------------------------*/
+
+/*
+ * Get a document's info dictionary.  (The returned object must be
+ * freed with xpdfFreeObj.)
+ */
+XpdfObject (*_xpdfGetInfoDict)(XpdfDoc doc);
+
+/*
+ * Get a document's catalog ("root") dictionary.  (The returned object
+ * must be freed with xpdfFreeObj.)
+ */
+XpdfObject (*_xpdfGetCatalog)(XpdfDoc doc);
+
+#ifdef _WIN32
+
+/*
+ * Get the handle for the viewer window associated with the specified
+ * document.  [Win32 only]
+ */
+HWND (*_xpdfWin32GetWindow)(XpdfDoc doc);
+
+#else
+
+/*
+ * Get the Motif widget for the viewer window associated with the
+ * specified document.  [X only]
+ */
+Widget (*_xpdfXGetWindow)(XpdfDoc doc);
+
+#endif
+
+/*------------------------------------------------------------------------
+ * Object access functions
+ *------------------------------------------------------------------------*/
+
+/*
+ * Check an object's type.
+ */
+XpdfBool (*_xpdfObjIsBool)(XpdfObject obj);
+XpdfBool (*_xpdfObjIsInt)(XpdfObject obj);
+XpdfBool (*_xpdfObjIsReal)(XpdfObject obj);
+XpdfBool (*_xpdfObjIsString)(XpdfObject obj);
+XpdfBool (*_xpdfObjIsName)(XpdfObject obj);
+XpdfBool (*_xpdfObjIsNull)(XpdfObject obj);
+XpdfBool (*_xpdfObjIsArray)(XpdfObject obj);
+XpdfBool (*_xpdfObjIsDict)(XpdfObject obj);
+XpdfBool (*_xpdfObjIsStream)(XpdfObject obj);
+XpdfBool (*_xpdfObjIsRef)(XpdfObject obj);
+
+/*
+ * Value access.
+ * (Objects returned by xpdfArrayGet and xpdfDictGet must be freed
+ * with xpdfFreeObj.)
+ */
+XpdfBool (*_xpdfBoolValue)(XpdfObject obj);
+int (*_xpdfIntValue)(XpdfObject obj);
+double (*_xpdfRealValue)(XpdfObject obj);
+int (*_xpdfStringLength)(XpdfObject obj);
+char *(*_xpdfStringValue)(XpdfObject obj);
+char *(*_xpdfNameValue)(XpdfObject obj);
+int (*_xpdfArrayLength)(XpdfObject obj);
+XpdfObject (*_xpdfArrayGet)(XpdfObject obj, int idx);
+XpdfObject (*_xpdfDictGet)(XpdfObject obj, char *key);
+
+/*
+ * Object destruction.  NB: *all* objects must be freed after use.
+ */
+void (*_xpdfFreeObj)(XpdfObject obj);
+
+/*------------------------------------------------------------------------
+ * Memory allocation functions
+ *------------------------------------------------------------------------*/
+
+void *(*_xpdfMalloc)(int size);
+void *(*_xpdfRealloc)(void *p, int size);
+void (*_xpdfFree)(void *p);
+
+/*------------------------------------------------------------------------
+ * Security handler functions
+ *------------------------------------------------------------------------*/
+
+/*
+ * Register a new security handler.
+ */
+void (*_xpdfRegisterSecurityHandler)(XpdfSecurityHandler *handler);
+
+/*------------------------------------------------------------------------*/
+
+} XpdfPluginVecTable;
+
+#ifdef _WIN32
+
+extern __declspec(dllexport) XpdfPluginVecTable xpdfPluginVecTable;
+
+#define xpdfPluginSetup \
+  extern __declspec(dllexport) \
+  XpdfPluginVecTable xpdfPluginVecTable = {xpdfPluginAPIVersion};
+
+#else
+
+extern XpdfPluginVecTable xpdfPluginVecTable;
+
+#define xpdfPluginSetup \
+  XpdfPluginVecTable xpdfPluginVecTable = {xpdfPluginAPIVersion};
+
+#endif
+
+#define xpdfGetInfoDict (*xpdfPluginVecTable._xpdfGetInfoDict)
+#define xpdfGetCatalog (*xpdfPluginVecTable._xpdfGetCatalog)
+#ifdef _WIN32
+#define xpdfWin32GetWindow (*xpdfPluginVecTable._xpdfWin32GetWindow)
+#else
+#define xpdfXGetWindow (*xpdfPluginVecTable._xpdfXGetWindow)
+#endif
+#define xpdfObjIsBool (*xpdfPluginVecTable._xpdfObjIsBool)
+#define xpdfObjIsInt (*xpdfPluginVecTable._xpdfObjIsInt)
+#define xpdfObjIsReal (*xpdfPluginVecTable._xpdfObjIsReal)
+#define xpdfObjIsString (*xpdfPluginVecTable._xpdfObjIsString)
+#define xpdfObjIsName (*xpdfPluginVecTable._xpdfObjIsName)
+#define xpdfObjIsNull (*xpdfPluginVecTable._xpdfObjIsNull)
+#define xpdfObjIsArray (*xpdfPluginVecTable._xpdfObjIsArray)
+#define xpdfObjIsDict (*xpdfPluginVecTable._xpdfObjIsDict)
+#define xpdfObjIsStream (*xpdfPluginVecTable._xpdfObjIsStream)
+#define xpdfObjIsRef (*xpdfPluginVecTable._xpdfObjIsRef)
+#define xpdfBoolValue (*xpdfPluginVecTable._xpdfBoolValue)
+#define xpdfIntValue (*xpdfPluginVecTable._xpdfIntValue)
+#define xpdfRealValue (*xpdfPluginVecTable._xpdfRealValue)
+#define xpdfStringLength (*xpdfPluginVecTable._xpdfStringLength)
+#define xpdfStringValue (*xpdfPluginVecTable._xpdfStringValue)
+#define xpdfNameValue (*xpdfPluginVecTable._xpdfNameValue)
+#define xpdfArrayLength (*xpdfPluginVecTable._xpdfArrayLength)
+#define xpdfArrayGet (*xpdfPluginVecTable._xpdfArrayGet)
+#define xpdfDictGet (*xpdfPluginVecTable._xpdfDictGet)
+#define xpdfFreeObj (*xpdfPluginVecTable._xpdfFreeObj)
+#define xpdfMalloc (*xpdfPluginVecTable._xpdfMalloc)
+#define xpdfRealloc (*xpdfPluginVecTable._xpdfRealloc)
+#define xpdfFree (*xpdfPluginVecTable._xpdfFree)
+#define xpdfRegisterSecurityHandler (*xpdfPluginVecTable._xpdfRegisterSecurityHandler)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/xpdf/about-text.h b/xpdf/about-text.h
new file mode 100644
--- /dev/null
+++ b/xpdf/about-text.h
@@ -0,0 +1,48 @@
+//========================================================================
+//
+// about-text.h
+//
+// Copyright 2002-2007 Glyph & Cog, LLC
+//
+//========================================================================
+
+static char *aboutWinText[] = {
+  "http://www.foolabs.com/xpdf/",
+  "derekn@foolabs.com",
+  " ",
+  "Licensed under the GNU General Public License (GPL).",
+  "See the 'COPYING' file for details.",
+  " ",
+  "Supports PDF version " supportedPDFVersionStr ".",
+  " ",
+  "The PDF data structures, operators, and specification",
+  "are copyright 1985-2006 Adobe Systems Inc.",
+  " ",
+  "Mouse bindings:",
+  "  button 1: select text / follow link",
+  "  button 2: pan window",
+  "  button 3: menu",
+  " ",
+  "Key bindings:",
+  "  o              = open file",
+  "  r              = reload",
+  "  f / ctrl-F     = find text",
+  "  ctrl-G         = find next",
+  "  ctrl-P         = print",
+  "  n              = next page",
+  "  p              = previous page",
+  "  <PgDn> = <space>                = scroll down",
+  "  <PgUp> = <backspace> = <delete> = scroll up",
+  "  v              = forward (history path)",
+  "  b              = backward (history path)",
+  "  0 / + / -      = zoom zero / in / out",
+  "  z / w          = zoom page / page width",
+  "  alt-F          = toggle full-screen mode",
+  "  ctrl-L         = redraw",
+  "  q              = quit",
+  "  <home> / <end> = top / bottom of page",
+  "  <arrows>       = scroll",
+  " ",
+  "For more information, please read the xpdf(1) man page.",
+  NULL
+};
diff --git a/xpdf/aconf.h b/xpdf/aconf.h
new file mode 100644
--- /dev/null
+++ b/xpdf/aconf.h
@@ -0,0 +1,116 @@
+/* aconf.h.  Generated by configure.  */
+/*
+ * aconf.h
+ *
+ * Copyright 2002-2003 Glyph & Cog, LLC
+ */
+
+#ifndef ACONF_H
+#define ACONF_H
+
+#include <aconf2.h>
+
+/*
+ * Use A4 paper size instead of Letter for PostScript output.
+ */
+/* #undef A4_PAPER */
+
+/*
+ * Do not allow text selection.
+ */
+/* #undef NO_TEXT_SELECT */
+
+/*
+ * Include support for OPI comments.
+ */
+/* #undef OPI_SUPPORT */
+
+/*
+ * Enable multithreading support.
+ */
+/* #undef MULTITHREADED */
+
+/*
+ * Enable C++ exceptions.
+ */
+/* #undef USE_EXCEPTIONS */
+
+/*
+ * Enable word list support.
+ */
+/* #undef TEXTOUT_WORD_LIST */
+
+/*
+ * Use fixed point (instead of floating point) arithmetic.
+ */
+/* #undef USE_FIXEDPOINT */
+
+/*
+ * Directory with the Xpdf app-defaults file.
+ */
+/* #undef APPDEFDIR */
+
+/*
+ * Full path for the system-wide xpdfrc file.
+ */
+#define SYSTEM_XPDFRC "/usr/local/etc/xpdfrc"
+
+/*
+ * Various include files and functions.
+ */
+#define HAVE_DIRENT_H 1
+/* #undef HAVE_SYS_NDIR_H */
+/* #undef HAVE_SYS_DIR_H */
+/* #undef HAVE_NDIR_H */
+/* #undef HAVE_SYS_SELECT_H */
+/* #undef HAVE_SYS_BSDTYPES_H */
+/* #undef HAVE_STRINGS_H */
+/* #undef HAVE_BSTRING_H */
+#define HAVE_POPEN 1
+#define HAVE_MKSTEMP 1
+#define HAVE_MKSTEMPS 1
+/* #undef SELECT_TAKES_INT */
+#define HAVE_FSEEKO 1
+/* #undef HAVE_FSEEK64 */
+/* #undef _FILE_OFFSET_BITS */
+/* #undef _LARGE_FILES */
+/* #undef _LARGEFILE_SOURCE */
+/* #undef HAVE_XTAPPSETEXITFLAG */
+
+/*
+ * This is defined if using libXpm.
+ */
+/* #undef HAVE_X11_XPM_H */
+
+/*
+ * This is defined if using t1lib.
+ */
+/* #undef HAVE_T1LIB_H */
+
+/*
+ * One of these is defined if using FreeType 2.
+ */
+/* #undef HAVE_FREETYPE_H */
+/* #undef HAVE_FREETYPE_FREETYPE_H */
+
+/*
+ * This is defined if using libpaper.
+ */
+/* #undef HAVE_PAPER_H */
+
+/*
+ * Enable support for loading plugins.
+ */
+/* #undef ENABLE_PLUGINS */
+
+/*
+ * Defined if the Splash library is avaiable.
+ */
+/* #undef HAVE_SPLASH */
+
+/*
+ * Enable support for CMYK output.
+ */
+/* #undef SPLASH_CMYK */
+
+#endif
diff --git a/xpdf/aconf2.h b/xpdf/aconf2.h
new file mode 100644
--- /dev/null
+++ b/xpdf/aconf2.h
@@ -0,0 +1,38 @@
+/*
+ * aconf2.h
+ *
+ * This gets included by aconf.h, and contains miscellaneous global
+ * settings not directly controlled by autoconf.  This is a separate
+ * file because otherwise the configure script will munge any
+ * #define/#undef constructs.
+ *
+ * Copyright 2002-2003 Glyph & Cog, LLC
+ */
+
+#ifndef ACONF2_H
+#define ACONF2_H
+
+/*
+ * This controls the use of the interface/implementation pragmas.
+ */
+#ifdef __GNUC__
+#define USE_GCC_PRAGMAS
+#endif
+/* There is a bug in the version of gcc which ships with MacOS X 10.2 */
+#if defined(__APPLE__) && defined(__MACH__)
+#  include <AvailabilityMacros.h>
+#endif
+#ifdef MAC_OS_X_VERSION_MAX_ALLOWED
+#  if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_2
+#    undef USE_GCC_PRAGMAS
+#  endif
+#endif
+
+/*
+ * Make sure WIN32 is defined if appropriate.
+ */
+#if defined(_WIN32) && !defined(WIN32)
+#  define WIN32
+#endif
+
+#endif
diff --git a/xpdf/config.h b/xpdf/config.h
new file mode 100644
--- /dev/null
+++ b/xpdf/config.h
@@ -0,0 +1,112 @@
+//========================================================================
+//
+// config.h
+//
+// Copyright 1996-2007 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef CONFIG_H
+#define CONFIG_H
+
+//------------------------------------------------------------------------
+// version
+//------------------------------------------------------------------------
+
+// xpdf version
+#define xpdfVersion          "3.02"
+#define xpdfVersionNum       3.02
+#define xpdfMajorVersion     3
+#define xpdfMinorVersion     2
+#define xpdfUpdateVersion    0
+#define xpdfMajorVersionStr  "3"
+#define xpdfMinorVersionStr  "2"
+#define xpdfUpdateVersionStr "0"
+
+// supported PDF version
+#define supportedPDFVersionStr "1.7"
+#define supportedPDFVersionNum 1.7
+
+// copyright notice
+#define xpdfCopyright "Copyright 1996-2007 Glyph & Cog, LLC"
+
+// Windows resource file stuff
+#define winxpdfVersion "WinXpdf 3.02"
+#define xpdfCopyrightAmp "Copyright 1996-2007 Glyph && Cog, LLC"
+
+//------------------------------------------------------------------------
+// paper size
+//------------------------------------------------------------------------
+
+// default paper size (in points) for PostScript output
+#ifdef A4_PAPER
+#define defPaperWidth  595    // ISO A4 (210x297 mm)
+#define defPaperHeight 842
+#else
+#define defPaperWidth  612    // American letter (8.5x11")
+#define defPaperHeight 792
+#endif
+
+//------------------------------------------------------------------------
+// config file (xpdfrc) path
+//------------------------------------------------------------------------
+
+// user config file name, relative to the user's home directory
+#if defined(VMS) || (defined(WIN32) && !defined(__CYGWIN32__))
+#define xpdfUserConfigFile "xpdfrc"
+#else
+#define xpdfUserConfigFile ".xpdfrc"
+#endif
+
+// system config file name (set via the configure script)
+#ifdef SYSTEM_XPDFRC
+#define xpdfSysConfigFile SYSTEM_XPDFRC
+#else
+// under Windows, we get the directory with the executable and then
+// append this file name
+#define xpdfSysConfigFile "xpdfrc"
+#endif
+
+//------------------------------------------------------------------------
+// X-related constants
+//------------------------------------------------------------------------
+
+// default maximum size of color cube to allocate
+#define defaultRGBCube 5
+
+// number of fonts (combined t1lib, FreeType, X server) to cache
+#define xOutFontCacheSize 64
+
+// number of Type 3 fonts to cache
+#define xOutT3FontCacheSize 8
+
+//------------------------------------------------------------------------
+// popen
+//------------------------------------------------------------------------
+
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+#define popen _popen
+#define pclose _pclose
+#endif
+
+#if defined(VMS) || defined(VMCMS) || defined(DOS) || defined(OS2) || defined(__EMX__) || defined(WIN32) || defined(__DJGPP__) || defined(MACOS)
+#define POPEN_READ_MODE "rb"
+#else
+#define POPEN_READ_MODE "r"
+#endif
+
+//------------------------------------------------------------------------
+// Win32 stuff
+//------------------------------------------------------------------------
+
+#ifdef CDECL
+#undef CDECL
+#endif
+
+#if defined(_MSC_VER) || defined(__BORLANDC__)
+#define CDECL __cdecl
+#else
+#define CDECL
+#endif
+
+#endif
diff --git a/xpdf/gfile.cc b/xpdf/gfile.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/gfile.cc
@@ -0,0 +1,731 @@
+//========================================================================
+//
+// gfile.cc
+//
+// Miscellaneous file and directory name manipulation.
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef WIN32
+#  include <time.h>
+#else
+#  if defined(MACOS)
+#    include <sys/stat.h>
+#  elif !defined(ACORN)
+#    include <sys/types.h>
+#    include <sys/stat.h>
+#    include <fcntl.h>
+#  endif
+#  include <limits.h>
+#  include <string.h>
+#  if !defined(VMS) && !defined(ACORN) && !defined(MACOS)
+#    include <pwd.h>
+#  endif
+#  if defined(VMS) && (__DECCXX_VER < 50200000)
+#    include <unixlib.h>
+#  endif
+#endif // WIN32
+#include "GString.h"
+#include "gfile.h"
+
+// Some systems don't define this, so just make it something reasonably
+// large.
+#ifndef PATH_MAX
+#define PATH_MAX 1024
+#endif
+
+//------------------------------------------------------------------------
+
+GString *getHomeDir() {
+#ifdef VMS
+  //---------- VMS ----------
+  return new GString("SYS$LOGIN:");
+
+#elif defined(__EMX__) || defined(WIN32)
+  //---------- OS/2+EMX and Win32 ----------
+  char *s;
+  GString *ret;
+
+  if ((s = getenv("HOME")))
+    ret = new GString(s);
+  else
+    ret = new GString(".");
+  return ret;
+
+#elif defined(ACORN)
+  //---------- RISCOS ----------
+  return new GString("@");
+
+#elif defined(MACOS)
+  //---------- MacOS ----------
+  return new GString(":");
+
+#else
+  //---------- Unix ----------
+  char *s;
+  struct passwd *pw;
+  GString *ret;
+
+  if ((s = getenv("HOME"))) {
+    ret = new GString(s);
+  } else {
+    if ((s = getenv("USER")))
+      pw = getpwnam(s);
+    else
+      pw = getpwuid(getuid());
+    if (pw)
+      ret = new GString(pw->pw_dir);
+    else
+      ret = new GString(".");
+  }
+  return ret;
+#endif
+}
+
+GString *getCurrentDir() {
+  char buf[PATH_MAX+1];
+
+#if defined(__EMX__)
+  if (_getcwd2(buf, sizeof(buf)))
+#elif defined(WIN32)
+  if (GetCurrentDirectory(sizeof(buf), buf))
+#elif defined(ACORN)
+  if (strcpy(buf, "@"))
+#elif defined(MACOS)
+  if (strcpy(buf, ":"))
+#else
+  if (getcwd(buf, sizeof(buf)))
+#endif
+    return new GString(buf);
+  return new GString();
+}
+
+GString *appendToPath(GString *path, char *fileName) {
+#if defined(VMS)
+  //---------- VMS ----------
+  //~ this should handle everything necessary for file
+  //~ requesters, but it's certainly not complete
+  char *p0, *p1, *p2;
+  char *q1;
+
+  p0 = path->getCString();
+  p1 = p0 + path->getLength() - 1;
+  if (!strcmp(fileName, "-")) {
+    if (*p1 == ']') {
+      for (p2 = p1; p2 > p0 && *p2 != '.' && *p2 != '['; --p2) ;
+      if (*p2 == '[')
+	++p2;
+      path->del(p2 - p0, p1 - p2);
+    } else if (*p1 == ':') {
+      path->append("[-]");
+    } else {
+      path->clear();
+      path->append("[-]");
+    }
+  } else if ((q1 = strrchr(fileName, '.')) && !strncmp(q1, ".DIR;", 5)) {
+    if (*p1 == ']') {
+      path->insert(p1 - p0, '.');
+      path->insert(p1 - p0 + 1, fileName, q1 - fileName);
+    } else if (*p1 == ':') {
+      path->append('[');
+      path->append(']');
+      path->append(fileName, q1 - fileName);
+    } else {
+      path->clear();
+      path->append(fileName, q1 - fileName);
+    }
+  } else {
+    if (*p1 != ']' && *p1 != ':')
+      path->clear();
+    path->append(fileName);
+  }
+  return path;
+
+#elif defined(WIN32)
+  //---------- Win32 ----------
+  GString *tmp;
+  char buf[256];
+  char *fp;
+
+  tmp = new GString(path);
+  tmp->append('/');
+  tmp->append(fileName);
+  GetFullPathName(tmp->getCString(), sizeof(buf), buf, &fp);
+  delete tmp;
+  path->clear();
+  path->append(buf);
+  return path;
+
+#elif defined(ACORN)
+  //---------- RISCOS ----------
+  char *p;
+  int i;
+
+  path->append(".");
+  i = path->getLength();
+  path->append(fileName);
+  for (p = path->getCString() + i; *p; ++p) {
+    if (*p == '/') {
+      *p = '.';
+    } else if (*p == '.') {
+      *p = '/';
+    }
+  }
+  return path;
+
+#elif defined(MACOS)
+  //---------- MacOS ----------
+  char *p;
+  int i;
+
+  path->append(":");
+  i = path->getLength();
+  path->append(fileName);
+  for (p = path->getCString() + i; *p; ++p) {
+    if (*p == '/') {
+      *p = ':';
+    } else if (*p == '.') {
+      *p = ':';
+    }
+  }
+  return path;
+
+#elif defined(__EMX__)
+  //---------- OS/2+EMX ----------
+  int i;
+
+  // appending "." does nothing
+  if (!strcmp(fileName, "."))
+    return path;
+
+  // appending ".." goes up one directory
+  if (!strcmp(fileName, "..")) {
+    for (i = path->getLength() - 2; i >= 0; --i) {
+      if (path->getChar(i) == '/' || path->getChar(i) == '\\' ||
+	  path->getChar(i) == ':')
+	break;
+    }
+    if (i <= 0) {
+      if (path->getChar(0) == '/' || path->getChar(0) == '\\') {
+	path->del(1, path->getLength() - 1);
+      } else if (path->getLength() >= 2 && path->getChar(1) == ':') {
+	path->del(2, path->getLength() - 2);
+      } else {
+	path->clear();
+	path->append("..");
+      }
+    } else {
+      if (path->getChar(i-1) == ':')
+	++i;
+      path->del(i, path->getLength() - i);
+    }
+    return path;
+  }
+
+  // otherwise, append "/" and new path component
+  if (path->getLength() > 0 &&
+      path->getChar(path->getLength() - 1) != '/' &&
+      path->getChar(path->getLength() - 1) != '\\')
+    path->append('/');
+  path->append(fileName);
+  return path;
+
+#else
+  //---------- Unix ----------
+  int i;
+
+  // appending "." does nothing
+  if (!strcmp(fileName, "."))
+    return path;
+
+  // appending ".." goes up one directory
+  if (!strcmp(fileName, "..")) {
+    for (i = path->getLength() - 2; i >= 0; --i) {
+      if (path->getChar(i) == '/')
+	break;
+    }
+    if (i <= 0) {
+      if (path->getChar(0) == '/') {
+	path->del(1, path->getLength() - 1);
+      } else {
+	path->clear();
+	path->append("..");
+      }
+    } else {
+      path->del(i, path->getLength() - i);
+    }
+    return path;
+  }
+
+  // otherwise, append "/" and new path component
+  if (path->getLength() > 0 &&
+      path->getChar(path->getLength() - 1) != '/')
+    path->append('/');
+  path->append(fileName);
+  return path;
+#endif
+}
+
+GString *grabPath(char *fileName) {
+#ifdef VMS
+  //---------- VMS ----------
+  char *p;
+
+  if ((p = strrchr(fileName, ']')))
+    return new GString(fileName, p + 1 - fileName);
+  if ((p = strrchr(fileName, ':')))
+    return new GString(fileName, p + 1 - fileName);
+  return new GString();
+
+#elif defined(__EMX__) || defined(WIN32)
+  //---------- OS/2+EMX and Win32 ----------
+  char *p;
+
+  if ((p = strrchr(fileName, '/')))
+    return new GString(fileName, p - fileName);
+  if ((p = strrchr(fileName, '\\')))
+    return new GString(fileName, p - fileName);
+  if ((p = strrchr(fileName, ':')))
+    return new GString(fileName, p + 1 - fileName);
+  return new GString();
+
+#elif defined(ACORN)
+  //---------- RISCOS ----------
+  char *p;
+
+  if ((p = strrchr(fileName, '.')))
+    return new GString(fileName, p - fileName);
+  return new GString();
+
+#elif defined(MACOS)
+  //---------- MacOS ----------
+  char *p;
+
+  if ((p = strrchr(fileName, ':')))
+    return new GString(fileName, p - fileName);
+  return new GString();
+
+#else
+  //---------- Unix ----------
+  char *p;
+
+  if ((p = strrchr(fileName, '/')))
+    return new GString(fileName, p - fileName);
+  return new GString();
+#endif
+}
+
+GBool isAbsolutePath(char *path) {
+#ifdef VMS
+  //---------- VMS ----------
+  return strchr(path, ':') ||
+	 (path[0] == '[' && path[1] != '.' && path[1] != '-');
+
+#elif defined(__EMX__) || defined(WIN32)
+  //---------- OS/2+EMX and Win32 ----------
+  return path[0] == '/' || path[0] == '\\' || path[1] == ':';
+
+#elif defined(ACORN)
+  //---------- RISCOS ----------
+  return path[0] == '$';
+
+#elif defined(MACOS)
+  //---------- MacOS ----------
+  return path[0] != ':';
+
+#else
+  //---------- Unix ----------
+  return path[0] == '/';
+#endif
+}
+
+GString *makePathAbsolute(GString *path) {
+#ifdef VMS
+  //---------- VMS ----------
+  char buf[PATH_MAX+1];
+
+  if (!isAbsolutePath(path->getCString())) {
+    if (getcwd(buf, sizeof(buf))) {
+      path->insert(0, buf);
+    }
+  }
+  return path;
+
+#elif defined(WIN32)
+  //---------- Win32 ----------
+  char buf[_MAX_PATH];
+  char *fp;
+
+  buf[0] = '\0';
+  if (!GetFullPathName(path->getCString(), _MAX_PATH, buf, &fp)) {
+    path->clear();
+    return path;
+  }
+  path->clear();
+  path->append(buf);
+  return path;
+
+#elif defined(ACORN)
+  //---------- RISCOS ----------
+  path->insert(0, '@');
+  return path;
+
+#elif defined(MACOS)
+  //---------- MacOS ----------
+  path->del(0, 1);
+  return path;
+
+#else
+  //---------- Unix and OS/2+EMX ----------
+  struct passwd *pw;
+  char buf[PATH_MAX+1];
+  GString *s;
+  char *p1, *p2;
+  int n;
+
+  if (path->getChar(0) == '~') {
+    if (path->getChar(1) == '/' ||
+#ifdef __EMX__
+	path->getChar(1) == '\\' ||
+#endif
+	path->getLength() == 1) {
+      path->del(0, 1);
+      s = getHomeDir();
+      path->insert(0, s);
+      delete s;
+    } else {
+      p1 = path->getCString() + 1;
+#ifdef __EMX__
+      for (p2 = p1; *p2 && *p2 != '/' && *p2 != '\\'; ++p2) ;
+#else
+      for (p2 = p1; *p2 && *p2 != '/'; ++p2) ;
+#endif
+      if ((n = p2 - p1) > PATH_MAX)
+	n = PATH_MAX;
+      strncpy(buf, p1, n);
+      buf[n] = '\0';
+      if ((pw = getpwnam(buf))) {
+	path->del(0, p2 - p1 + 1);
+	path->insert(0, pw->pw_dir);
+      }
+    }
+  } else if (!isAbsolutePath(path->getCString())) {
+    if (getcwd(buf, sizeof(buf))) {
+#ifndef __EMX__
+      path->insert(0, '/');
+#endif
+      path->insert(0, buf);
+    }
+  }
+  return path;
+#endif
+}
+
+time_t getModTime(char *fileName) {
+#ifdef WIN32
+  //~ should implement this, but it's (currently) only used in xpdf
+  return 0;
+#else
+  struct stat statBuf;
+
+  if (stat(fileName, &statBuf)) {
+    return 0;
+  }
+  return statBuf.st_mtime;
+#endif
+}
+
+GBool openTempFile(GString **name, FILE **f, char *mode, char *ext) {
+#if defined(WIN32)
+  //---------- Win32 ----------
+  char *tempDir;
+  GString *s, *s2;
+  char buf[32];
+  FILE *f2;
+  int t, i;
+
+  // this has the standard race condition problem, but I haven't found
+  // a better way to generate temp file names with extensions on
+  // Windows
+  if ((tempDir = getenv("TEMP"))) {
+    s = new GString(tempDir);
+    s->append('\\');
+  } else {
+    s = new GString();
+  }
+  s->append("x");
+  t = (int)time(NULL);
+  for (i = 0; i < 1000; ++i) {
+    sprintf(buf, "%d", t + i);
+    s2 = s->copy()->append(buf);
+    if (ext) {
+      s2->append(ext);
+    }
+    if (!(f2 = fopen(s2->getCString(), "r"))) {
+      if (!(f2 = fopen(s2->getCString(), mode))) {
+	delete s2;
+	delete s;
+	return gFalse;
+      }
+      *name = s2;
+      *f = f2;
+      delete s;
+      return gTrue;
+    }
+    fclose(f2);
+    delete s2;
+  }
+  delete s;
+  return gFalse;
+#elif defined(VMS) || defined(__EMX__) || defined(ACORN) || defined(MACOS)
+  //---------- non-Unix ----------
+  char *s;
+
+  // There is a security hole here: an attacker can create a symlink
+  // with this file name after the tmpnam call and before the fopen
+  // call.  I will happily accept fixes to this function for non-Unix
+  // OSs.
+  if (!(s = tmpnam(NULL))) {
+    return gFalse;
+  }
+  *name = new GString(s);
+  if (ext) {
+    (*name)->append(ext);
+  }
+  if (!(*f = fopen((*name)->getCString(), mode))) {
+    delete (*name);
+    return gFalse;
+  }
+  return gTrue;
+#else
+  //---------- Unix ----------
+  char *s;
+  int fd;
+
+  if (ext) {
+#if HAVE_MKSTEMPS
+    if ((s = getenv("TMPDIR"))) {
+      *name = new GString(s);
+    } else {
+      *name = new GString("/tmp");
+    }
+    (*name)->append("/XXXXXX")->append(ext);
+    fd = mkstemps((*name)->getCString(), strlen(ext));
+#else
+    if (!(s = tmpnam(NULL))) {
+      return gFalse;
+    }
+    *name = new GString(s);
+    (*name)->append(ext);
+    fd = open((*name)->getCString(), O_WRONLY | O_CREAT | O_EXCL, 0600);
+#endif
+  } else {
+#if HAVE_MKSTEMP
+    if ((s = getenv("TMPDIR"))) {
+      *name = new GString(s);
+    } else {
+      *name = new GString("/tmp");
+    }
+    (*name)->append("/XXXXXX");
+    fd = mkstemp((*name)->getCString());
+#else // HAVE_MKSTEMP
+    if (!(s = tmpnam(NULL))) {
+      return gFalse;
+    }
+    *name = new GString(s);
+    fd = open((*name)->getCString(), O_WRONLY | O_CREAT | O_EXCL, 0600);
+#endif // HAVE_MKSTEMP
+  }
+  if (fd < 0 || !(*f = fdopen(fd, mode))) {
+    delete *name;
+    return gFalse;
+  }
+  return gTrue;
+#endif
+}
+
+GBool executeCommand(char *cmd) {
+#ifdef VMS
+  return system(cmd) ? gTrue : gFalse;
+#else
+  return system(cmd) ? gFalse : gTrue;
+#endif
+}
+
+char *getLine(char *buf, int size, FILE *f) {
+  int c, i;
+
+  i = 0;
+  while (i < size - 1) {
+    if ((c = fgetc(f)) == EOF) {
+      break;
+    }
+    buf[i++] = (char)c;
+    if (c == '\x0a') {
+      break;
+    }
+    if (c == '\x0d') {
+      c = fgetc(f);
+      if (c == '\x0a' && i < size - 1) {
+	buf[i++] = (char)c;
+      } else if (c != EOF) {
+	ungetc(c, f);
+      }
+      break;
+    }
+  }
+  buf[i] = '\0';
+  if (i == 0) {
+    return NULL;
+  }
+  return buf;
+}
+
+//------------------------------------------------------------------------
+// GDir and GDirEntry
+//------------------------------------------------------------------------
+
+GDirEntry::GDirEntry(char *dirPath, char *nameA, GBool doStat) {
+#ifdef VMS
+  char *p;
+#elif defined(WIN32)
+  int fa;
+  GString *s;
+#elif defined(ACORN)
+#else
+  struct stat st;
+  GString *s;
+#endif
+
+  name = new GString(nameA);
+  dir = gFalse;
+  if (doStat) {
+#ifdef VMS
+    if (!strcmp(nameA, "-") ||
+	((p = strrchr(nameA, '.')) && !strncmp(p, ".DIR;", 5)))
+      dir = gTrue;
+#elif defined(ACORN)
+#else
+    s = new GString(dirPath);
+    appendToPath(s, nameA);
+#ifdef WIN32
+    fa = GetFileAttributes(s->getCString());
+    dir = (fa != 0xFFFFFFFF && (fa & FILE_ATTRIBUTE_DIRECTORY));
+#else
+    if (stat(s->getCString(), &st) == 0)
+      dir = S_ISDIR(st.st_mode);
+#endif
+    delete s;
+#endif
+  }
+}
+
+GDirEntry::~GDirEntry() {
+  delete name;
+}
+
+GDir::GDir(char *name, GBool doStatA) {
+  path = new GString(name);
+  doStat = doStatA;
+#if defined(WIN32)
+  GString *tmp;
+
+  tmp = path->copy();
+  tmp->append("/*.*");
+  hnd = FindFirstFile(tmp->getCString(), &ffd);
+  delete tmp;
+#elif defined(ACORN)
+#elif defined(MACOS)
+#else
+  dir = opendir(name);
+#ifdef VMS
+  needParent = strchr(name, '[') != NULL;
+#endif
+#endif
+}
+
+GDir::~GDir() {
+  delete path;
+#if defined(WIN32)
+  if (hnd) {
+    FindClose(hnd);
+    hnd = NULL;
+  }
+#elif defined(ACORN)
+#elif defined(MACOS)
+#else
+  if (dir)
+    closedir(dir);
+#endif
+}
+
+GDirEntry *GDir::getNextEntry() {
+  GDirEntry *e;
+
+#if defined(WIN32)
+  if (hnd) {
+    e = new GDirEntry(path->getCString(), ffd.cFileName, doStat);
+    if (hnd  && !FindNextFile(hnd, &ffd)) {
+      FindClose(hnd);
+      hnd = NULL;
+    }
+  } else {
+    e = NULL;
+  }
+#elif defined(ACORN)
+#elif defined(MACOS)
+#elif defined(VMS)
+  struct dirent *ent;
+  e = NULL;
+  if (dir) {
+    if (needParent) {
+      e = new GDirEntry(path->getCString(), "-", doStat);
+      needParent = gFalse;
+      return e;
+    }
+    ent = readdir(dir);
+    if (ent) {
+      e = new GDirEntry(path->getCString(), ent->d_name, doStat);
+    }
+  }
+#else
+  struct dirent *ent;
+  e = NULL;
+  if (dir) {
+    ent = (struct dirent *)readdir(dir);
+    if (ent && !strcmp(ent->d_name, ".")) {
+      ent = (struct dirent *)readdir(dir);
+    }
+    if (ent) {
+      e = new GDirEntry(path->getCString(), ent->d_name, doStat);
+    }
+  }
+#endif
+
+  return e;
+}
+
+void GDir::rewind() {
+#ifdef WIN32
+  GString *tmp;
+
+  if (hnd)
+    FindClose(hnd);
+  tmp = path->copy();
+  tmp->append("/*.*");
+  hnd = FindFirstFile(tmp->getCString(), &ffd);
+  delete tmp;
+#elif defined(ACORN)
+#elif defined(MACOS)
+#else
+  if (dir)
+    rewinddir(dir);
+#ifdef VMS
+  needParent = strchr(path->getCString(), '[') != NULL;
+#endif
+#endif
+}
diff --git a/xpdf/gfile.h b/xpdf/gfile.h
new file mode 100644
--- /dev/null
+++ b/xpdf/gfile.h
@@ -0,0 +1,138 @@
+//========================================================================
+//
+// gfile.h
+//
+// Miscellaneous file and directory name manipulation.
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef GFILE_H
+#define GFILE_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#if defined(WIN32)
+#  include <sys/stat.h>
+#  ifdef FPTEX
+#    include <win32lib.h>
+#  else
+#    include <windows.h>
+#  endif
+#elif defined(ACORN)
+#elif defined(MACOS)
+#  include <ctime.h>
+#else
+#  include <unistd.h>
+#  include <sys/types.h>
+#  ifdef VMS
+#    include "vms_dirent.h"
+#  elif HAVE_DIRENT_H
+#    include <dirent.h>
+#    define NAMLEN(d) strlen((d)->d_name)
+#  else
+#    define dirent direct
+#    define NAMLEN(d) (d)->d_namlen
+#    if HAVE_SYS_NDIR_H
+#      include <sys/ndir.h>
+#    endif
+#    if HAVE_SYS_DIR_H
+#      include <sys/dir.h>
+#    endif
+#    if HAVE_NDIR_H
+#      include <ndir.h>
+#    endif
+#  endif
+#endif
+#include "gtypes.h"
+
+class GString;
+
+//------------------------------------------------------------------------
+
+// Get home directory path.
+extern GString *getHomeDir();
+
+// Get current directory.
+extern GString *getCurrentDir();
+
+// Append a file name to a path string.  <path> may be an empty
+// string, denoting the current directory).  Returns <path>.
+extern GString *appendToPath(GString *path, char *fileName);
+
+// Grab the path from the front of the file name.  If there is no
+// directory component in <fileName>, returns an empty string.
+extern GString *grabPath(char *fileName);
+
+// Is this an absolute path or file name?
+extern GBool isAbsolutePath(char *path);
+
+// Make this path absolute by prepending current directory (if path is
+// relative) or prepending user's directory (if path starts with '~').
+extern GString *makePathAbsolute(GString *path);
+
+// Get the modification time for <fileName>.  Returns 0 if there is an
+// error.
+extern time_t getModTime(char *fileName);
+
+// Create a temporary file and open it for writing.  If <ext> is not
+// NULL, it will be used as the file name extension.  Returns both the
+// name and the file pointer.  For security reasons, all writing
+// should be done to the returned file pointer; the file may be
+// reopened later for reading, but not for writing.  The <mode> string
+// should be "w" or "wb".  Returns true on success.
+extern GBool openTempFile(GString **name, FILE **f, char *mode, char *ext);
+
+// Execute <command>.  Returns true on success.
+extern GBool executeCommand(char *cmd);
+
+// Just like fgets, but handles Unix, Mac, and/or DOS end-of-line
+// conventions.
+extern char *getLine(char *buf, int size, FILE *f);
+
+//------------------------------------------------------------------------
+// GDir and GDirEntry
+//------------------------------------------------------------------------
+
+class GDirEntry {
+public:
+
+  GDirEntry(char *dirPath, char *nameA, GBool doStat);
+  ~GDirEntry();
+  GString *getName() { return name; }
+  GBool isDir() { return dir; }
+
+private:
+
+  GString *name;		// dir/file name
+  GBool dir;			// is it a directory?
+};
+
+class GDir {
+public:
+
+  GDir(char *name, GBool doStatA = gTrue);
+  ~GDir();
+  GDirEntry *getNextEntry();
+  void rewind();
+
+private:
+
+  GString *path;		// directory path
+  GBool doStat;			// call stat() for each entry?
+#if defined(WIN32)
+  WIN32_FIND_DATA ffd;
+  HANDLE hnd;
+#elif defined(ACORN)
+#elif defined(MACOS)
+#else
+  DIR *dir;			// the DIR structure from opendir()
+#ifdef VMS
+  GBool needParent;		// need to return an entry for [-]
+#endif
+#endif
+};
+
+#endif
diff --git a/xpdf/gmem.cc b/xpdf/gmem.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/gmem.cc
@@ -0,0 +1,264 @@
+/*
+ * gmem.c
+ *
+ * Memory routines with out-of-memory checking.
+ *
+ * Copyright 1996-2003 Glyph & Cog, LLC
+ */
+
+#include <aconf.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include <limits.h>
+#include "gmem.h"
+
+#ifdef DEBUG_MEM
+
+typedef struct _GMemHdr {
+  unsigned int magic;
+  int size;
+  int index;
+  struct _GMemHdr *next, *prev;
+} GMemHdr;
+
+#define gMemHdrSize ((sizeof(GMemHdr) + 7) & ~7)
+#define gMemTrlSize (sizeof(long))
+
+#define gMemMagic 0xabcd9999
+
+#if gmemTrlSize==8
+#define gMemDeadVal 0xdeadbeefdeadbeefUL
+#else
+#define gMemDeadVal 0xdeadbeefUL
+#endif
+
+/* round data size so trailer will be aligned */
+#define gMemDataSize(size) \
+  ((((size) + gMemTrlSize - 1) / gMemTrlSize) * gMemTrlSize)
+
+static GMemHdr *gMemHead = NULL;
+static GMemHdr *gMemTail = NULL;
+
+static int gMemIndex = 0;
+static int gMemAlloc = 0;
+static int gMemInUse = 0;
+
+#endif /* DEBUG_MEM */
+
+void *gmalloc(int size) GMEM_EXCEP {
+#ifdef DEBUG_MEM
+  int size1;
+  char *mem;
+  GMemHdr *hdr;
+  void *data;
+  unsigned long *trl, *p;
+
+  if (size <= 0) {
+    return NULL;
+  }
+  size1 = gMemDataSize(size);
+  if (!(mem = (char *)malloc(size1 + gMemHdrSize + gMemTrlSize))) {
+#if USE_EXCEPTIONS
+    throw GMemException();
+#else
+    fprintf(stderr, "Out of memory\n");
+    exit(1);
+#endif
+  }
+  hdr = (GMemHdr *)mem;
+  data = (void *)(mem + gMemHdrSize);
+  trl = (unsigned long *)(mem + gMemHdrSize + size1);
+  hdr->magic = gMemMagic;
+  hdr->size = size;
+  hdr->index = gMemIndex++;
+  if (gMemTail) {
+    gMemTail->next = hdr;
+    hdr->prev = gMemTail;
+    gMemTail = hdr;
+  } else {
+    hdr->prev = NULL;
+    gMemHead = gMemTail = hdr;
+  }
+  hdr->next = NULL;
+  ++gMemAlloc;
+  gMemInUse += size;
+  for (p = (unsigned long *)data; p <= trl; ++p) {
+    *p = gMemDeadVal;
+  }
+  return data;
+#else
+  void *p;
+
+  if (size <= 0) {
+    return NULL;
+  }
+  if (!(p = malloc(size))) {
+#if USE_EXCEPTIONS
+    throw GMemException();
+#else
+    fprintf(stderr, "Out of memory\n");
+    exit(1);
+#endif
+  }
+  return p;
+#endif
+}
+
+void *grealloc(void *p, int size) GMEM_EXCEP {
+#ifdef DEBUG_MEM
+  GMemHdr *hdr;
+  void *q;
+  int oldSize;
+
+  if (size <= 0) {
+    if (p) {
+      gfree(p);
+    }
+    return NULL;
+  }
+  if (p) {
+    hdr = (GMemHdr *)((char *)p - gMemHdrSize);
+    oldSize = hdr->size;
+    q = gmalloc(size);
+    memcpy(q, p, size < oldSize ? size : oldSize);
+    gfree(p);
+  } else {
+    q = gmalloc(size);
+  }
+  return q;
+#else
+  void *q;
+
+  if (size <= 0) {
+    if (p) {
+      free(p);
+    }
+    return NULL;
+  }
+  if (p) {
+    q = realloc(p, size);
+  } else {
+    q = malloc(size);
+  }
+  if (!q) {
+#if USE_EXCEPTIONS
+    throw GMemException();
+#else
+    fprintf(stderr, "Out of memory\n");
+    exit(1);
+#endif
+  }
+  return q;
+#endif
+}
+
+void *gmallocn(int nObjs, int objSize) GMEM_EXCEP {
+  int n;
+
+  if (nObjs == 0) {
+    return NULL;
+  }
+  n = nObjs * objSize;
+  if (objSize <= 0 || nObjs < 0 || nObjs >= INT_MAX / objSize) {
+#if USE_EXCEPTIONS
+    throw GMemException();
+#else
+    fprintf(stderr, "Bogus memory allocation size\n");
+    exit(1);
+#endif
+  }
+  return gmalloc(n);
+}
+
+void *greallocn(void *p, int nObjs, int objSize) GMEM_EXCEP {
+  int n;
+
+  if (nObjs == 0) {
+    if (p) {
+      gfree(p);
+    }
+    return NULL;
+  }
+  n = nObjs * objSize;
+  if (objSize <= 0 || nObjs < 0 || nObjs >= INT_MAX / objSize) {
+#if USE_EXCEPTIONS
+    throw GMemException();
+#else
+    fprintf(stderr, "Bogus memory allocation size\n");
+    exit(1);
+#endif
+  }
+  return grealloc(p, n);
+}
+
+void gfree(void *p) {
+#ifdef DEBUG_MEM
+  int size;
+  GMemHdr *hdr;
+  unsigned long *trl, *clr;
+
+  if (p) {
+    hdr = (GMemHdr *)((char *)p - gMemHdrSize);
+    if (hdr->magic == gMemMagic &&
+	((hdr->prev == NULL) == (hdr == gMemHead)) &&
+	((hdr->next == NULL) == (hdr == gMemTail))) {
+      if (hdr->prev) {
+	hdr->prev->next = hdr->next;
+      } else {
+	gMemHead = hdr->next;
+      }
+      if (hdr->next) {
+	hdr->next->prev = hdr->prev;
+      } else {
+	gMemTail = hdr->prev;
+      }
+      --gMemAlloc;
+      gMemInUse -= hdr->size;
+      size = gMemDataSize(hdr->size);
+      trl = (unsigned long *)((char *)hdr + gMemHdrSize + size);
+      if (*trl != gMemDeadVal) {
+	fprintf(stderr, "Overwrite past end of block %d at address %p\n",
+		hdr->index, p);
+      }
+      for (clr = (unsigned long *)hdr; clr <= trl; ++clr) {
+	*clr = gMemDeadVal;
+      }
+      free(hdr);
+    } else {
+      fprintf(stderr, "Attempted to free bad address %p\n", p);
+    }
+  }
+#else
+  if (p) {
+    free(p);
+  }
+#endif
+}
+
+#ifdef DEBUG_MEM
+void gMemReport(FILE *f) {
+  GMemHdr *p;
+
+  fprintf(f, "%d memory allocations in all\n", gMemIndex);
+  if (gMemAlloc > 0) {
+    fprintf(f, "%d memory blocks left allocated:\n", gMemAlloc);
+    fprintf(f, " index     size\n");
+    fprintf(f, "-------- --------\n");
+    for (p = gMemHead; p; p = p->next) {
+      fprintf(f, "%8d %8d\n", p->index, p->size);
+    }
+  } else {
+    fprintf(f, "No memory blocks left allocated\n");
+  }
+}
+#endif
+
+char *copyString(char *s) {
+  char *s1;
+
+  s1 = (char *)gmalloc(strlen(s) + 1);
+  strcpy(s1, s);
+  return s1;
+}
diff --git a/xpdf/gmem.h b/xpdf/gmem.h
new file mode 100644
--- /dev/null
+++ b/xpdf/gmem.h
@@ -0,0 +1,79 @@
+/*
+ * gmem.h
+ *
+ * Memory routines with out-of-memory checking.
+ *
+ * Copyright 1996-2003 Glyph & Cog, LLC
+ */
+
+#ifndef GMEM_H
+#define GMEM_H
+
+#include <stdio.h>
+#include <aconf.h>
+
+#if USE_EXCEPTIONS
+
+class GMemException {
+public:
+  GMemException() {}
+  ~GMemException() {}
+};
+
+#define GMEM_EXCEP throw(GMemException)
+
+#else // USE_EXCEPTIONS
+
+#define GMEM_EXCEP
+
+#endif // USE_EXCEPTIONS
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Same as malloc, but prints error message and exits if malloc()
+ * returns NULL.
+ */
+extern void *gmalloc(int size) GMEM_EXCEP;
+
+/*
+ * Same as realloc, but prints error message and exits if realloc()
+ * returns NULL.  If <p> is NULL, calls malloc instead of realloc().
+ */
+extern void *grealloc(void *p, int size) GMEM_EXCEP;
+
+/*
+ * These are similar to gmalloc and grealloc, but take an object count
+ * and size.  The result is similar to allocating nObjs * objSize
+ * bytes, but there is an additional error check that the total size
+ * doesn't overflow an int.
+ */
+extern void *gmallocn(int nObjs, int objSize) GMEM_EXCEP;
+extern void *greallocn(void *p, int nObjs, int objSize) GMEM_EXCEP;
+
+/*
+ * Same as free, but checks for and ignores NULL pointers.
+ */
+extern void gfree(void *p);
+
+#ifdef DEBUG_MEM
+/*
+ * Report on unfreed memory.
+ */
+extern void gMemReport(FILE *f);
+#else
+#define gMemReport(f)
+#endif
+
+/*
+ * Allocate memory and copy a string into it.
+ */
+extern char *copyString(char *s);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/xpdf/gmempp.cc b/xpdf/gmempp.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/gmempp.cc
@@ -0,0 +1,32 @@
+//========================================================================
+//
+// gmempp.cc
+//
+// Use gmalloc/gfree for C++ new/delete operators.
+//
+// Copyright 1996-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+#include "gmem.h"
+
+#ifdef DEBUG_MEM
+
+void *operator new(size_t size) {
+  return gmalloc((int)size);
+}
+
+void *operator new[](size_t size) {
+  return gmalloc((int)size);
+}
+
+void operator delete(void *p) {
+  gfree(p);
+}
+
+void operator delete[](void *p) {
+  gfree(p);
+}
+
+#endif
diff --git a/xpdf/gtypes.h b/xpdf/gtypes.h
new file mode 100644
--- /dev/null
+++ b/xpdf/gtypes.h
@@ -0,0 +1,29 @@
+/*
+ * gtypes.h
+ *
+ * Some useful simple types.
+ *
+ * Copyright 1996-2003 Glyph & Cog, LLC
+ */
+
+#ifndef GTYPES_H
+#define GTYPES_H
+
+/*
+ * These have stupid names to avoid conflicts with some (but not all)
+ * C++ compilers which define them.
+ */
+typedef int GBool;
+#define gTrue 1
+#define gFalse 0
+
+/*
+ * These have stupid names to avoid conflicts with <sys/types.h>,
+ * which on various systems defines some random subset of these.
+ */
+typedef unsigned char Guchar;
+typedef unsigned short Gushort;
+typedef unsigned int Guint;
+typedef unsigned long Gulong;
+
+#endif
diff --git a/xpdf/parseargs.c b/xpdf/parseargs.c
new file mode 100644
--- /dev/null
+++ b/xpdf/parseargs.c
@@ -0,0 +1,190 @@
+/*
+ * parseargs.h
+ *
+ * Command line argument parser.
+ *
+ * Copyright 1996-2003 Glyph & Cog, LLC
+ */
+
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "parseargs.h"
+
+static ArgDesc *findArg(ArgDesc *args, char *arg);
+static GBool grabArg(ArgDesc *arg, int i, int *argc, char *argv[]);
+
+GBool parseArgs(ArgDesc *args, int *argc, char *argv[]) {
+  ArgDesc *arg;
+  int i, j;
+  GBool ok;
+
+  ok = gTrue;
+  i = 1;
+  while (i < *argc) {
+    if (!strcmp(argv[i], "--")) {
+      --*argc;
+      for (j = i; j < *argc; ++j)
+	argv[j] = argv[j+1];
+      break;
+    } else if ((arg = findArg(args, argv[i]))) {
+      if (!grabArg(arg, i, argc, argv))
+	ok = gFalse;
+    } else {
+      ++i;
+    }
+  }
+  return ok;
+}
+
+void printUsage(char *program, char *otherArgs, ArgDesc *args) {
+  ArgDesc *arg;
+  char *typ;
+  int w, w1;
+
+  w = 0;
+  for (arg = args; arg->arg; ++arg) {
+    if ((w1 = strlen(arg->arg)) > w)
+      w = w1;
+  }
+
+  fprintf(stderr, "Usage: %s [options]", program);
+  if (otherArgs)
+    fprintf(stderr, " %s", otherArgs);
+  fprintf(stderr, "\n");
+
+  for (arg = args; arg->arg; ++arg) {
+    fprintf(stderr, "  %s", arg->arg);
+    w1 = 9 + w - strlen(arg->arg);
+    switch (arg->kind) {
+    case argInt:
+    case argIntDummy:
+      typ = " <int>";
+      break;
+    case argFP:
+    case argFPDummy:
+      typ = " <fp>";
+      break;
+    case argString:
+    case argStringDummy:
+      typ = " <string>";
+      break;
+    case argFlag:
+    case argFlagDummy:
+    default:
+      typ = "";
+      break;
+    }
+    fprintf(stderr, "%-*s", w1, typ);
+    if (arg->usage)
+      fprintf(stderr, ": %s", arg->usage);
+    fprintf(stderr, "\n");
+  }
+}
+
+static ArgDesc *findArg(ArgDesc *args, char *arg) {
+  ArgDesc *p;
+
+  for (p = args; p->arg; ++p) {
+    if (p->kind < argFlagDummy && !strcmp(p->arg, arg))
+      return p;
+  }
+  return NULL;
+}
+
+static GBool grabArg(ArgDesc *arg, int i, int *argc, char *argv[]) {
+  int n;
+  int j;
+  GBool ok;
+
+  ok = gTrue;
+  n = 0;
+  switch (arg->kind) {
+  case argFlag:
+    *(GBool *)arg->val = gTrue;
+    n = 1;
+    break;
+  case argInt:
+    if (i + 1 < *argc && isInt(argv[i+1])) {
+      *(int *)arg->val = atoi(argv[i+1]);
+      n = 2;
+    } else {
+      ok = gFalse;
+      n = 1;
+    }
+    break;
+  case argFP:
+    if (i + 1 < *argc && isFP(argv[i+1])) {
+      *(double *)arg->val = atof(argv[i+1]);
+      n = 2;
+    } else {
+      ok = gFalse;
+      n = 1;
+    }
+    break;
+  case argString:
+    if (i + 1 < *argc) {
+      strncpy((char *)arg->val, argv[i+1], arg->size - 1);
+      ((char *)arg->val)[arg->size - 1] = '\0';
+      n = 2;
+    } else {
+      ok = gFalse;
+      n = 1;
+    }
+    break;
+  default:
+    fprintf(stderr, "Internal error in arg table\n");
+    n = 1;
+    break;
+  }
+  if (n > 0) {
+    *argc -= n;
+    for (j = i; j < *argc; ++j)
+      argv[j] = argv[j+n];
+  }
+  return ok;
+}
+
+GBool isInt(char *s) {
+  if (*s == '-' || *s == '+')
+    ++s;
+  while (isdigit(*s & 0xff))
+    ++s;
+  if (*s)
+    return gFalse;
+  return gTrue;
+}
+
+GBool isFP(char *s) {
+  int n;
+
+  if (*s == '-' || *s == '+')
+    ++s;
+  n = 0;
+  while (isdigit(*s & 0xff)) {
+    ++s;
+    ++n;
+  }
+  if (*s == '.')
+    ++s;
+  while (isdigit(*s & 0xff)) {
+    ++s;
+    ++n;
+  }
+  if (n > 0 && (*s == 'e' || *s == 'E')) {
+    ++s;
+    if (*s == '-' || *s == '+')
+      ++s;
+    n = 0;
+    if (!isdigit(*s & 0xff))
+      return gFalse;
+    do {
+      ++s;
+    } while (isdigit(*s & 0xff));
+  }
+  if (*s)
+    return gFalse;
+  return gTrue;
+}
diff --git a/xpdf/parseargs.h b/xpdf/parseargs.h
new file mode 100644
--- /dev/null
+++ b/xpdf/parseargs.h
@@ -0,0 +1,71 @@
+/*
+ * parseargs.h
+ *
+ * Command line argument parser.
+ *
+ * Copyright 1996-2003 Glyph & Cog, LLC
+ */
+
+#ifndef PARSEARGS_H
+#define PARSEARGS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "gtypes.h"
+
+/*
+ * Argument kinds.
+ */
+typedef enum {
+  argFlag,			/* flag (present / not-present) */
+				/*   [val: GBool *]             */
+  argInt,			/* integer arg    */
+				/*   [val: int *] */
+  argFP,			/* floating point arg */
+				/*   [val: double *]  */
+  argString,			/* string arg      */
+				/*   [val: char *] */
+  /* dummy entries -- these show up in the usage listing only; */
+  /* useful for X args, for example                            */
+  argFlagDummy,
+  argIntDummy,
+  argFPDummy,
+  argStringDummy
+} ArgKind;
+
+/*
+ * Argument descriptor.
+ */
+typedef struct {
+  char *arg;			/* the command line switch */
+  ArgKind kind;			/* kind of arg */
+  void *val;			/* place to store value */
+  int size;			/* for argString: size of string */
+  char *usage;			/* usage string */
+} ArgDesc;
+
+/*
+ * Parse command line.  Removes all args which are found in the arg
+ * descriptor list <args>.  Stops parsing if "--" is found (and removes
+ * it).  Returns gFalse if there was an error.
+ */
+extern GBool parseArgs(ArgDesc *args, int *argc, char *argv[]);
+
+/*
+ * Print usage message, based on arg descriptor list.
+ */
+extern void printUsage(char *program, char *otherArgs, ArgDesc *args);
+
+/*
+ * Check if a string is a valid integer or floating point number.
+ */
+extern GBool isInt(char *s);
+extern GBool isFP(char *s);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/xpdf/pdfdump.cc b/xpdf/pdfdump.cc
new file mode 100644
--- /dev/null
+++ b/xpdf/pdfdump.cc
@@ -0,0 +1,326 @@
+//========================================================================
+//
+// pdftotext.cc
+//
+// Copyright 1997-2003 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <string.h>
+#include "parseargs.h"
+#include "GString.h"
+#include "gmem.h"
+#include "GlobalParams.h"
+#include "Object.h"
+#include "Stream.h"
+#include "Array.h"
+#include "Dict.h"
+#include "XRef.h"
+#include "Catalog.h"
+#include "Page.h"
+#include "PDFDoc.h"
+#include "TextOutputDev.h"
+#include "CharTypes.h"
+#include "UnicodeMap.h"
+#include "Error.h"
+#include "config.h"
+
+static void printInfoString(FILE *f, Dict *infoDict, char *key,
+			    char *text1, char *text2, UnicodeMap *uMap);
+static void printInfoDate(FILE *f, Dict *infoDict, char *key, char *fmt);
+
+static int firstPage = 1;
+static int lastPage = 0;
+static GBool physLayout = gFalse;
+static GBool rawOrder = gFalse;
+static GBool htmlMeta = gFalse;
+static char textEncName[128] = "";
+static char textEOL[16] = "";
+static GBool noPageBreaks = gFalse;
+static char ownerPassword[33] = "\001";
+static char userPassword[33] = "\001";
+static GBool quiet = gFalse;
+static char cfgFileName[256] = "";
+static GBool printVersion = gFalse;
+static GBool printHelp = gFalse;
+
+static ArgDesc argDesc[] = {
+  {"-f",       argInt,      &firstPage,     0,
+   "first page to convert"},
+  {"-l",       argInt,      &lastPage,      0,
+   "last page to convert"},
+  {"-layout",  argFlag,     &physLayout,    0,
+   "maintain original physical layout"},
+  {"-raw",     argFlag,     &rawOrder,      0,
+   "keep strings in content stream order"},
+  {"-htmlmeta", argFlag,   &htmlMeta,       0,
+   "generate a simple HTML file, including the meta information"},
+  {"-enc",     argString,   textEncName,    sizeof(textEncName),
+   "output text encoding name"},
+  {"-eol",     argString,   textEOL,        sizeof(textEOL),
+   "output end-of-line convention (unix, dos, or mac)"},
+  {"-nopgbrk", argFlag,     &noPageBreaks,  0,
+   "don't insert page breaks between pages"},
+  {"-opw",     argString,   ownerPassword,  sizeof(ownerPassword),
+   "owner password (for encrypted files)"},
+  {"-upw",     argString,   userPassword,   sizeof(userPassword),
+   "user password (for encrypted files)"},
+  {"-q",       argFlag,     &quiet,         0,
+   "don't print any messages or errors"},
+  {"-cfg",     argString,   cfgFileName,    sizeof(cfgFileName),
+   "configuration file to use in place of .xpdfrc"},
+  {"-v",       argFlag,     &printVersion,  0,
+   "print copyright and version info"},
+  {"-h",       argFlag,     &printHelp,     0,
+   "print usage information"},
+  {"-help",    argFlag,     &printHelp,     0,
+   "print usage information"},
+  {"--help",   argFlag,     &printHelp,     0,
+   "print usage information"},
+  {"-?",       argFlag,     &printHelp,     0,
+   "print usage information"},
+  {NULL}
+};
+
+int main(int argc, char *argv[]) {
+  PDFDoc *doc;
+  GString *fileName;
+  GString *textFileName;
+  GString *ownerPW, *userPW;
+  TextOutputDev *textOut;
+  FILE *f;
+  UnicodeMap *uMap;
+  Object info;
+  GBool ok;
+  char *p;
+  int exitCode;
+
+  exitCode = 99;
+
+  // parse args
+  ok = parseArgs(argDesc, &argc, argv);
+  if (!ok || argc < 2 || argc > 3 || printVersion || printHelp) {
+    fprintf(stderr, "pdftotext version %s\n", xpdfVersion);
+    fprintf(stderr, "%s\n", xpdfCopyright);
+    if (!printVersion) {
+      printUsage("pdftotext", "<PDF-file> [<text-file>]", argDesc);
+    }
+    goto err0;
+  }
+  fileName = new GString(argv[1]);
+
+  // read config file
+  globalParams = new GlobalParams(cfgFileName);
+  if (textEncName[0]) {
+    globalParams->setTextEncoding(textEncName);
+  }
+  if (textEOL[0]) {
+    if (!globalParams->setTextEOL(textEOL)) {
+      fprintf(stderr, "Bad '-eol' value on command line\n");
+    }
+  }
+  if (noPageBreaks) {
+    globalParams->setTextPageBreaks(gFalse);
+  }
+  if (quiet) {
+    globalParams->setErrQuiet(quiet);
+  }
+
+  // get mapping to output encoding
+  if (!(uMap = globalParams->getTextEncoding())) {
+    error(-1, "Couldn't get text encoding");
+    delete fileName;
+    goto err1;
+  }
+
+  // open PDF file
+  if (ownerPassword[0] != '\001') {
+    ownerPW = new GString(ownerPassword);
+  } else {
+    ownerPW = NULL;
+  }
+  if (userPassword[0] != '\001') {
+    userPW = new GString(userPassword);
+  } else {
+    userPW = NULL;
+  }
+  doc = new PDFDoc(fileName, ownerPW, userPW);
+  if (userPW) {
+    delete userPW;
+  }
+  if (ownerPW) {
+    delete ownerPW;
+  }
+  if (!doc->isOk()) {
+    exitCode = 1;
+    goto err2;
+  }
+
+  // construct text file name
+  if (argc == 3) {
+    textFileName = new GString(argv[2]);
+  } else {
+    p = fileName->getCString() + fileName->getLength() - 4;
+    if (!strcmp(p, ".pdf") || !strcmp(p, ".PDF")) {
+      textFileName = new GString(fileName->getCString(),
+				 fileName->getLength() - 4);
+    } else {
+      textFileName = fileName->copy();
+    }
+    textFileName->append(htmlMeta ? ".html" : ".txt");
+  }
+
+  // get page range
+  if (firstPage < 1) {
+    firstPage = 1;
+  }
+  if (lastPage < 1 || lastPage > doc->getNumPages()) {
+    lastPage = doc->getNumPages();
+  }
+
+  // write HTML header
+  if (htmlMeta) {
+    if (!textFileName->cmp("-")) {
+      f = stdout;
+    } else {
+      if (!(f = fopen(textFileName->getCString(), "wb"))) {
+	error(-1, "Couldn't open text file '%s'", textFileName->getCString());
+	exitCode = 2;
+	goto err3;
+      }
+    }
+    fputs("<html>\n", f);
+    fputs("<head>\n", f);
+    doc->getDocInfo(&info);
+    if (info.isDict()) {
+      printInfoString(f, info.getDict(), "Title", "<title>", "</title>\n",
+		      uMap);
+      printInfoString(f, info.getDict(), "Subject",
+		      "<meta name=\"Subject\" content=\"", "\">\n", uMap);
+      printInfoString(f, info.getDict(), "Keywords",
+		      "<meta name=\"Keywords\" content=\"", "\">\n", uMap);
+      printInfoString(f, info.getDict(), "Author",
+		      "<meta name=\"Author\" content=\"", "\">\n", uMap);
+      printInfoString(f, info.getDict(), "Creator",
+		      "<meta name=\"Creator\" content=\"", "\">\n", uMap);
+      printInfoString(f, info.getDict(), "Producer",
+		      "<meta name=\"Producer\" content=\"", "\">\n", uMap);
+      printInfoDate(f, info.getDict(), "CreationDate",
+		    "<meta name=\"CreationDate\" content=\"%s\">\n");
+      printInfoDate(f, info.getDict(), "LastModifiedDate",
+		    "<meta name=\"ModDate\" content=\"%s\">\n");
+    }
+    info.free();
+    fputs("</head>\n", f);
+    fputs("<body>\n", f);
+    fputs("<pre>\n", f);
+    if (f != stdout) {
+      fclose(f);
+    }
+  }
+
+  // write text file
+  textOut = new TextOutputDev(textFileName->getCString(),
+			      physLayout, rawOrder, htmlMeta);
+  if (textOut->isOk()) {
+    doc->displayPages(textOut, firstPage, lastPage, 72, 72, 0,
+		      gFalse, gTrue, gFalse);
+  } else {
+    delete textOut;
+    exitCode = 2;
+    goto err3;
+  }
+  delete textOut;
+
+  // write end of HTML file
+  if (htmlMeta) {
+    if (!textFileName->cmp("-")) {
+      f = stdout;
+    } else {
+      if (!(f = fopen(textFileName->getCString(), "ab"))) {
+	error(-1, "Couldn't open text file '%s'", textFileName->getCString());
+	exitCode = 2;
+	goto err3;
+      }
+    }
+    fputs("</pre>\n", f);
+    fputs("</body>\n", f);
+    fputs("</html>\n", f);
+    if (f != stdout) {
+      fclose(f);
+    }
+  }
+
+  exitCode = 0;
+
+  // clean up
+ err3:
+  delete textFileName;
+ err2:
+  delete doc;
+  uMap->decRefCnt();
+ err1:
+  delete globalParams;
+ err0:
+
+  // check for memory leaks
+  Object::memCheck(stderr);
+  gMemReport(stderr);
+
+  return exitCode;
+}
+
+static void printInfoString(FILE *f, Dict *infoDict, char *key,
+			    char *text1, char *text2, UnicodeMap *uMap) {
+  Object obj;
+  GString *s1;
+  GBool isUnicode;
+  Unicode u;
+  char buf[8];
+  int i, n;
+
+  if (infoDict->lookup(key, &obj)->isString()) {
+    fputs(text1, f);
+    s1 = obj.getString();
+    if ((s1->getChar(0) & 0xff) == 0xfe &&
+	(s1->getChar(1) & 0xff) == 0xff) {
+      isUnicode = gTrue;
+      i = 2;
+    } else {
+      isUnicode = gFalse;
+      i = 0;
+    }
+    while (i < obj.getString()->getLength()) {
+      if (isUnicode) {
+	u = ((s1->getChar(i) & 0xff) << 8) |
+	    (s1->getChar(i+1) & 0xff);
+	i += 2;
+      } else {
+	u = s1->getChar(i) & 0xff;
+	++i;
+      }
+      n = uMap->mapUnicode(u, buf, sizeof(buf));
+      fwrite(buf, 1, n, f);
+    }
+    fputs(text2, f);
+  }
+  obj.free();
+}
+
+static void printInfoDate(FILE *f, Dict *infoDict, char *key, char *fmt) {
+  Object obj;
+  char *s;
+
+  if (infoDict->lookup(key, &obj)->isString()) {
+    s = obj.getString()->getCString();
+    if (s[0] == 'D' && s[1] == ':') {
+      s += 2;
+    }
+    fprintf(f, fmt, s);
+  }
+  obj.free();
+}
