texlive[57588] Build/source/libs: xpdf 4.03

commits+kakuto at tug.org commits+kakuto at tug.org
Mon Feb 1 07:11:19 CET 2021


Revision: 57588
          http://tug.org/svn/texlive?view=revision&revision=57588
Author:   kakuto
Date:     2021-02-01 07:11:18 +0100 (Mon, 01 Feb 2021)
Log Message:
-----------
xpdf 4.03

Modified Paths:
--------------
    trunk/Build/source/libs/README
    trunk/Build/source/libs/xpdf/ChangeLog
    trunk/Build/source/libs/xpdf/Makefile.am
    trunk/Build/source/libs/xpdf/Makefile.in
    trunk/Build/source/libs/xpdf/TLpatches/ChangeLog
    trunk/Build/source/libs/xpdf/TLpatches/TL-Changes
    trunk/Build/source/libs/xpdf/TLpatches/patch-bunched
    trunk/Build/source/libs/xpdf/configure
    trunk/Build/source/libs/xpdf/version.ac
    trunk/Build/source/libs/xpdf/xpdf-src/ANNOUNCE
    trunk/Build/source/libs/xpdf/xpdf-src/CHANGES
    trunk/Build/source/libs/xpdf/xpdf-src/INSTALL
    trunk/Build/source/libs/xpdf/xpdf-src/README
    trunk/Build/source/libs/xpdf/xpdf-src/cmake-config.txt
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.1
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.cat
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.1
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.cat
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.1
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.cat
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.1
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.cat
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.1
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.cat
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.1
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.cat
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.1
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.cat
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftops.1
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftops.cat
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.1
    trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.cat
    trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdf.1
    trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdf.cat
    trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.5
    trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.cat
    trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiIdentifier.cc
    trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiTrueType.cc
    trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1.cc
    trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.cc
    trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.h
    trunk/Build/source/libs/xpdf/xpdf-src/goo/CMakeLists.txt
    trunk/Build/source/libs/xpdf/xpdf-src/goo/FixedPoint.h
    trunk/Build/source/libs/xpdf/xpdf-src/goo/gfile.cc
    trunk/Build/source/libs/xpdf/xpdf-src/goo/gfile.h
    trunk/Build/source/libs/xpdf/xpdf-src/goo/gmem.h
    trunk/Build/source/libs/xpdf/xpdf-src/splash/Splash.cc
    trunk/Build/source/libs/xpdf/xpdf-src/splash/Splash.h
    trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.cc
    trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.h
    trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFont.cc
    trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.cc
    trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.h
    trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashXPath.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Annot.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CMakeLists.txt
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/HTMLGen.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Lexer.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Object.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OptionalContent.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Page.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Page.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/WebFont.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/config.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfdetach.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdffonts.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfimages.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfinfo.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftohtml.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftopng.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftoppm.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftops.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftotext.cc

Added Paths:
-----------
    trunk/Build/source/libs/xpdf/xpdf-src/goo/Trace.cc
    trunk/Build/source/libs/xpdf/xpdf-src/goo/Trace.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.h

Removed Paths:
-------------
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Form.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Form.h
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.cc
    trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.h

Modified: trunk/Build/source/libs/README
===================================================================
--- trunk/Build/source/libs/README	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/README	2021-02-01 06:11:18 UTC (rev 57588)
@@ -58,7 +58,7 @@
 teckit 2.5.10 - checked 06may20
   https://github.com/silnrsi/teckit/archive/2.5.10.tar.gz
 
-xpdf 4.02 - checked 29sep19
+xpdf 4.03 - checked 01feb21
   http://www.xpdfreader.com/download.html
   with modifications for pdftex
 

Modified: trunk/Build/source/libs/xpdf/ChangeLog
===================================================================
--- trunk/Build/source/libs/xpdf/ChangeLog	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/ChangeLog	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,3 +1,8 @@
+2021-02-01  Akira Kakuto  <kakuto at w32tex.org>
+
+	* Import xpdf-4.03.
+	* version.ac, Makefile.am: Adjust.
+
 2020-05-14  Karl Berry  <karl at freefriends.org>
 
 	* ac/xpdf.ac: doc change for poppler no longer supported in TL.

Modified: trunk/Build/source/libs/xpdf/Makefile.am
===================================================================
--- trunk/Build/source/libs/xpdf/Makefile.am	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/Makefile.am	2021-02-01 06:11:18 UTC (rev 57588)
@@ -62,6 +62,7 @@
 	@XPDF_TREE@/goo/GHash.cc \
 	@XPDF_TREE@/goo/GList.cc \
 	@XPDF_TREE@/goo/GString.cc \
+	@XPDF_TREE@/goo/Trace.cc \
 	@XPDF_TREE@/goo/gfile.cc \
 	@XPDF_TREE@/goo/gmem.cc \
 	@XPDF_TREE@/goo/gmempp.cc
@@ -87,7 +88,6 @@
 	@XPDF_TREE@/xpdf/Dict.cc \
 	@XPDF_TREE@/xpdf/Error.cc \
 	@XPDF_TREE@/xpdf/FontEncodingTables.cc \
-	@XPDF_TREE@/xpdf/Form.cc \
 	@XPDF_TREE@/xpdf/Function.cc \
 	@XPDF_TREE@/xpdf/Gfx.cc \
 	@XPDF_TREE@/xpdf/GfxFont.cc \
@@ -115,7 +115,7 @@
 	@XPDF_TREE@/xpdf/UnicodeMap.cc \
 	@XPDF_TREE@/xpdf/UnicodeRemapping.cc \
 	@XPDF_TREE@/xpdf/UTF8.cc \
-	@XPDF_TREE@/xpdf/XFAForm.cc \
+	@XPDF_TREE@/xpdf/XFAScanner.cc \
 	@XPDF_TREE@/xpdf/XRef.cc \
 	@XPDF_TREE@/xpdf/Zoox.cc
 

Modified: trunk/Build/source/libs/xpdf/Makefile.in
===================================================================
--- trunk/Build/source/libs/xpdf/Makefile.in	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/Makefile.in	2021-02-01 06:11:18 UTC (rev 57588)
@@ -115,6 +115,7 @@
 	@XPDF_TREE@/goo/GHash.$(OBJEXT) \
 	@XPDF_TREE@/goo/GList.$(OBJEXT) \
 	@XPDF_TREE@/goo/GString.$(OBJEXT) \
+	@XPDF_TREE@/goo/Trace.$(OBJEXT) \
 	@XPDF_TREE@/goo/gfile.$(OBJEXT) @XPDF_TREE@/goo/gmem.$(OBJEXT) \
 	@XPDF_TREE@/goo/gmempp.$(OBJEXT)
 am__objects_2 = @XPDF_TREE@/fofi/FoFiBase.$(OBJEXT) \
@@ -135,7 +136,6 @@
 	@XPDF_TREE@/xpdf/Dict.$(OBJEXT) \
 	@XPDF_TREE@/xpdf/Error.$(OBJEXT) \
 	@XPDF_TREE@/xpdf/FontEncodingTables.$(OBJEXT) \
-	@XPDF_TREE@/xpdf/Form.$(OBJEXT) \
 	@XPDF_TREE@/xpdf/Function.$(OBJEXT) \
 	@XPDF_TREE@/xpdf/Gfx.$(OBJEXT) \
 	@XPDF_TREE@/xpdf/GfxFont.$(OBJEXT) \
@@ -163,7 +163,7 @@
 	@XPDF_TREE@/xpdf/UnicodeMap.$(OBJEXT) \
 	@XPDF_TREE@/xpdf/UnicodeRemapping.$(OBJEXT) \
 	@XPDF_TREE@/xpdf/UTF8.$(OBJEXT) \
-	@XPDF_TREE@/xpdf/XFAForm.$(OBJEXT) \
+	@XPDF_TREE@/xpdf/XFAScanner.$(OBJEXT) \
 	@XPDF_TREE@/xpdf/XRef.$(OBJEXT) \
 	@XPDF_TREE@/xpdf/Zoox.$(OBJEXT)
 nodist_libxpdf_a_OBJECTS = $(am__objects_1) $(am__objects_2) \
@@ -194,6 +194,7 @@
 	@XPDF_TREE@/goo/$(DEPDIR)/GHash.Po \
 	@XPDF_TREE@/goo/$(DEPDIR)/GList.Po \
 	@XPDF_TREE@/goo/$(DEPDIR)/GString.Po \
+	@XPDF_TREE@/goo/$(DEPDIR)/Trace.Po \
 	@XPDF_TREE@/goo/$(DEPDIR)/gfile.Po \
 	@XPDF_TREE@/goo/$(DEPDIR)/gmem.Po \
 	@XPDF_TREE@/goo/$(DEPDIR)/gmempp.Po \
@@ -209,7 +210,6 @@
 	@XPDF_TREE@/xpdf/$(DEPDIR)/Dict.Po \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/Error.Po \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/FontEncodingTables.Po \
-	@XPDF_TREE@/xpdf/$(DEPDIR)/Form.Po \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/Function.Po \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/Gfx.Po \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/GfxFont.Po \
@@ -237,7 +237,7 @@
 	@XPDF_TREE@/xpdf/$(DEPDIR)/UTF8.Po \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/UnicodeMap.Po \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/UnicodeRemapping.Po \
-	@XPDF_TREE@/xpdf/$(DEPDIR)/XFAForm.Po \
+	@XPDF_TREE@/xpdf/$(DEPDIR)/XFAScanner.Po \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/XRef.Po \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/Zoox.Po
 am__mv = mv -f
@@ -483,6 +483,7 @@
 	@XPDF_TREE@/goo/GHash.cc \
 	@XPDF_TREE@/goo/GList.cc \
 	@XPDF_TREE@/goo/GString.cc \
+	@XPDF_TREE@/goo/Trace.cc \
 	@XPDF_TREE@/goo/gfile.cc \
 	@XPDF_TREE@/goo/gmem.cc \
 	@XPDF_TREE@/goo/gmempp.cc
@@ -508,7 +509,6 @@
 	@XPDF_TREE@/xpdf/Dict.cc \
 	@XPDF_TREE@/xpdf/Error.cc \
 	@XPDF_TREE@/xpdf/FontEncodingTables.cc \
-	@XPDF_TREE@/xpdf/Form.cc \
 	@XPDF_TREE@/xpdf/Function.cc \
 	@XPDF_TREE@/xpdf/Gfx.cc \
 	@XPDF_TREE@/xpdf/GfxFont.cc \
@@ -536,7 +536,7 @@
 	@XPDF_TREE@/xpdf/UnicodeMap.cc \
 	@XPDF_TREE@/xpdf/UnicodeRemapping.cc \
 	@XPDF_TREE@/xpdf/UTF8.cc \
-	@XPDF_TREE@/xpdf/XFAForm.cc \
+	@XPDF_TREE@/xpdf/XFAScanner.cc \
 	@XPDF_TREE@/xpdf/XRef.cc \
 	@XPDF_TREE@/xpdf/Zoox.cc
 
@@ -616,6 +616,8 @@
 	@XPDF_TREE@/goo/$(DEPDIR)/$(am__dirstamp)
 @XPDF_TREE@/goo/GString.$(OBJEXT): @XPDF_TREE@/goo/$(am__dirstamp) \
 	@XPDF_TREE@/goo/$(DEPDIR)/$(am__dirstamp)
+ at XPDF_TREE@/goo/Trace.$(OBJEXT): @XPDF_TREE@/goo/$(am__dirstamp) \
+	@XPDF_TREE@/goo/$(DEPDIR)/$(am__dirstamp)
 @XPDF_TREE@/goo/gfile.$(OBJEXT): @XPDF_TREE@/goo/$(am__dirstamp) \
 	@XPDF_TREE@/goo/$(DEPDIR)/$(am__dirstamp)
 @XPDF_TREE@/goo/gmem.$(OBJEXT): @XPDF_TREE@/goo/$(am__dirstamp) \
@@ -679,8 +681,6 @@
 @XPDF_TREE@/xpdf/FontEncodingTables.$(OBJEXT):  \
 	@XPDF_TREE@/xpdf/$(am__dirstamp) \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/$(am__dirstamp)
- at XPDF_TREE@/xpdf/Form.$(OBJEXT): @XPDF_TREE@/xpdf/$(am__dirstamp) \
-	@XPDF_TREE@/xpdf/$(DEPDIR)/$(am__dirstamp)
 @XPDF_TREE@/xpdf/Function.$(OBJEXT): @XPDF_TREE@/xpdf/$(am__dirstamp) \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/$(am__dirstamp)
 @XPDF_TREE@/xpdf/Gfx.$(OBJEXT): @XPDF_TREE@/xpdf/$(am__dirstamp) \
@@ -749,7 +749,8 @@
 	@XPDF_TREE@/xpdf/$(DEPDIR)/$(am__dirstamp)
 @XPDF_TREE@/xpdf/UTF8.$(OBJEXT): @XPDF_TREE@/xpdf/$(am__dirstamp) \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/$(am__dirstamp)
- at XPDF_TREE@/xpdf/XFAForm.$(OBJEXT): @XPDF_TREE@/xpdf/$(am__dirstamp) \
+ at XPDF_TREE@/xpdf/XFAScanner.$(OBJEXT):  \
+	@XPDF_TREE@/xpdf/$(am__dirstamp) \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/$(am__dirstamp)
 @XPDF_TREE@/xpdf/XRef.$(OBJEXT): @XPDF_TREE@/xpdf/$(am__dirstamp) \
 	@XPDF_TREE@/xpdf/$(DEPDIR)/$(am__dirstamp)
@@ -780,6 +781,7 @@
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/goo/$(DEPDIR)/GHash.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/goo/$(DEPDIR)/GList.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/goo/$(DEPDIR)/GString.Po at am__quote@ # am--include-marker
+ at AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/goo/$(DEPDIR)/Trace.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/goo/$(DEPDIR)/gfile.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/goo/$(DEPDIR)/gmem.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/goo/$(DEPDIR)/gmempp.Po at am__quote@ # am--include-marker
@@ -795,7 +797,6 @@
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/Dict.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/Error.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/FontEncodingTables.Po at am__quote@ # am--include-marker
- at AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/Form.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/Function.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/Gfx.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/GfxFont.Po at am__quote@ # am--include-marker
@@ -823,7 +824,7 @@
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/UTF8.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/UnicodeMap.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/UnicodeRemapping.Po at am__quote@ # am--include-marker
- at AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/XFAForm.Po at am__quote@ # am--include-marker
+ at AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/XFAScanner.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/XRef.Po at am__quote@ # am--include-marker
 @AMDEP_TRUE@@am__include@ @am__quote@@XPDF_TREE@/xpdf/$(DEPDIR)/Zoox.Po at am__quote@ # am--include-marker
 
@@ -1213,6 +1214,7 @@
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/GHash.Po
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/GList.Po
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/GString.Po
+	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/Trace.Po
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/gfile.Po
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/gmem.Po
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/gmempp.Po
@@ -1228,7 +1230,6 @@
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Dict.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Error.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/FontEncodingTables.Po
-	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Form.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Function.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Gfx.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/GfxFont.Po
@@ -1256,7 +1257,7 @@
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/UTF8.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/UnicodeMap.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/UnicodeRemapping.Po
-	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/XFAForm.Po
+	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/XFAScanner.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/XRef.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Zoox.Po
 	-rm -f Makefile
@@ -1316,6 +1317,7 @@
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/GHash.Po
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/GList.Po
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/GString.Po
+	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/Trace.Po
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/gfile.Po
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/gmem.Po
 	-rm -f @XPDF_TREE@/goo/$(DEPDIR)/gmempp.Po
@@ -1331,7 +1333,6 @@
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Dict.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Error.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/FontEncodingTables.Po
-	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Form.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Function.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Gfx.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/GfxFont.Po
@@ -1359,7 +1360,7 @@
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/UTF8.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/UnicodeMap.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/UnicodeRemapping.Po
-	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/XFAForm.Po
+	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/XFAScanner.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/XRef.Po
 	-rm -f @XPDF_TREE@/xpdf/$(DEPDIR)/Zoox.Po
 	-rm -f Makefile

Modified: trunk/Build/source/libs/xpdf/TLpatches/ChangeLog
===================================================================
--- trunk/Build/source/libs/xpdf/TLpatches/ChangeLog	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/TLpatches/ChangeLog	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,3 +1,7 @@
+2021-02-01  Akira Kakuto  <kakuto at w32tex.org>
+
+	* patch-bunched: Adjust.
+
 2019-09-29  Akira Kakuto  <kakuto at w32tex.org>
 
 	* patch-bunched: Adjust.

Modified: trunk/Build/source/libs/xpdf/TLpatches/TL-Changes
===================================================================
--- trunk/Build/source/libs/xpdf/TLpatches/TL-Changes	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/TLpatches/TL-Changes	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,4 +1,4 @@
-Changes applied to the xpdf-4.02/ tree as obtained from:
+Changes applied to the xpdf-4.03/ tree as obtained from:
 	http://www.xpdfreader.com/download.html
 
 Removed:

Modified: trunk/Build/source/libs/xpdf/TLpatches/patch-bunched
===================================================================
--- trunk/Build/source/libs/xpdf/TLpatches/patch-bunched	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/TLpatches/patch-bunched	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,6 +1,6 @@
-diff -ur xpdf-4.02/goo/gfile.cc xpdf-src/goo/gfile.cc
---- xpdf-4.02/goo/gfile.cc	Thu Sep 26 04:54:33 2019
-+++ xpdf-src/goo/gfile.cc	Sun Sep 29 11:23:53 2019
+diff -ur xpdf-4.03/goo/gfile.cc xpdf-src/goo/gfile.cc
+--- xpdf-4.03/goo/gfile.cc	Thu Jan 28 07:23:51 2021
++++ xpdf-src/goo/gfile.cc	Mon Feb 01 13:13:50 2021
 @@ -7,6 +7,9 @@
  // Copyright 1996-2003 Glyph & Cog, LLC
  //
@@ -11,7 +11,7 @@
  
  #include <aconf.h>
  
-@@ -54,7 +57,11 @@
+@@ -56,7 +59,11 @@
    char *s;
    GString *ret;
  
@@ -23,7 +23,7 @@
      ret = new GString(s);
    else
      ret = new GString(".");
-@@ -403,6 +410,7 @@
+@@ -405,6 +412,7 @@
  #endif
  }
  
@@ -31,7 +31,7 @@
  GBool openTempFile(GString **name, FILE **f,
  		   const char *mode, const char *ext) {
  #if defined(_WIN32)
-@@ -517,10 +525,11 @@
+@@ -519,10 +527,11 @@
    return gTrue;
  #endif
  }
@@ -44,41 +44,76 @@
  #else
    return !mkdir(path, mode);
  #endif
-@@ -574,6 +583,8 @@
+@@ -602,6 +611,8 @@
  
  FILE *openFile(const char *path, const char *mode) {
  #if defined(_WIN32)
 +  return fopen(path, mode);
 +#if 0
-   OSVERSIONINFO version;
    wchar_t wPath[_MAX_PATH + 1];
-   char nPath[_MAX_PATH + 1];
-@@ -630,6 +641,7 @@
-     nPath[i] = '\0';
-     return fopen(nPath, mode);
-   }
+   wchar_t wMode[8];
+   int i;
+@@ -613,6 +624,7 @@
+   wMode[i] = (wchar_t)0;
+   readWindowsShortcut(wPath, _MAX_PATH + 1);
+   return _wfopen(wPath, wMode);
 +#endif /* 0 */
  #elif defined(VMS)
    return fopen(path, mode, "ctx=stm");
  #else
-@@ -690,6 +702,7 @@
+@@ -620,6 +632,7 @@
  #endif
  }
  
++#if 0
+ #ifdef _WIN32
+ void readWindowsShortcut(wchar_t *wPath, size_t wPathSize) {
+   size_t n = wcslen(wPath);
+@@ -665,11 +678,11 @@
+   wcscpy(wPath, target);
+ }
+ #endif
++#endif /* 0 */
+ 
+ int makeDir(const char *path, int mode) {
+ #ifdef _WIN32
+-  wchar_t wPath[_MAX_PATH + 1];
+-  return _wmkdir(fileNameToUCS2(path, wPath, sizeof(wPath) / sizeof(wchar_t)));
++  return _mkdir(path);
+ #else
+   return mkdir(path, (mode_t)mode);
+ #endif
+@@ -728,6 +741,7 @@
+ #endif
+ }
+ 
 +#ifndef PDF_PARSER_ONLY
  void fixCommandLine(int *argc, char **argv[]) {
  #ifdef _WIN32
    int argcw;
-@@ -715,3 +728,4 @@
+@@ -753,3 +767,4 @@
    LocalFree(argvw);
  #endif
  }
 +#endif /* !PDF_PARSER_ONLY */
-diff -ur xpdf-4.02/goo/gfile.h xpdf-src/goo/gfile.h
---- xpdf-4.02/goo/gfile.h	Thu Sep 26 04:54:33 2019
-+++ xpdf-src/goo/gfile.h	Sun Sep 29 11:25:19 2019
-@@ -115,6 +115,8 @@
+diff -ur xpdf-4.03/goo/gfile.h xpdf-src/goo/gfile.h
+--- xpdf-4.03/goo/gfile.h	Thu Jan 28 07:23:51 2021
++++ xpdf-src/goo/gfile.h	Mon Feb 01 13:14:50 2021
+@@ -91,12 +91,6 @@
+ // UCS-2 and calls _wfopen().  On other OSes, this simply calls fopen().
+ extern FILE *openFile(const char *path, const char *mode);
  
+-#ifdef _WIN32
+-// If [wPath] is a Windows shortcut (.lnk file), read the target path
+-// and store it back into [wPath].
+-extern void readWindowsShortcut(wchar_t *wPath, size_t wPathSize);
+-#endif
+-
+ // Create a directory.  On Windows, this converts the path from UTF-8
+ // to UCS-2 and calls _wmkdir(), ignoring the mode argument.  On other
+ // OSes, this simply calls mkdir().
+@@ -130,6 +124,8 @@
+ 
  // On Windows, this gets the Unicode command line and converts it to
  // UTF-8.  On other systems, this is a nop.
 +#ifndef PDF_PARSER_ONLY
@@ -86,9 +121,9 @@
 +#endif /* !PDF_PARSER_ONLY */
  
  #endif
-diff -ur xpdf-4.02/xpdf/GlobalParams.cc xpdf-src/xpdf/GlobalParams.cc
---- xpdf-4.02/xpdf/GlobalParams.cc	Thu Sep 26 04:54:33 2019
-+++ xpdf-src/xpdf/GlobalParams.cc	Sun Sep 29 11:32:58 2019
+diff -ur xpdf-4.03/xpdf/GlobalParams.cc xpdf-src/xpdf/GlobalParams.cc
+--- xpdf-4.03/xpdf/GlobalParams.cc	Thu Jan 28 07:23:51 2021
++++ xpdf-src/xpdf/GlobalParams.cc	Mon Feb 01 12:20:24 2021
 @@ -5,6 +5,9 @@
  // Copyright 2001-2003 Glyph & Cog, LLC
  //
@@ -114,7 +149,7 @@
  #endif
  
  #if MULTITHREADED
-@@ -803,6 +810,7 @@
+@@ -787,6 +794,7 @@
    f = NULL;
    fileName = NULL;
    if (cfgFileName && cfgFileName[0]) {
@@ -122,7 +157,7 @@
      fileName = new GString(cfgFileName);
      if (!(f = fopen(fileName->getCString(), "r"))) {
        delete fileName;
-@@ -835,6 +843,7 @@
+@@ -819,6 +827,7 @@
      parseFile(fileName, f);
      delete fileName;
      fclose(f);
@@ -130,7 +165,7 @@
    }
  }
  
-@@ -2265,8 +2274,11 @@
+@@ -2358,8 +2367,11 @@
  				   base14->fontNum,
  				   displayFontTab[i].obliqueFactor));
        } else {
@@ -142,9 +177,9 @@
        }
      }
    }
-diff -ur xpdf-4.02/xpdf/GlobalParams.h xpdf-src/xpdf/GlobalParams.h
---- xpdf-4.02/xpdf/GlobalParams.h	Thu Sep 26 04:54:33 2019
-+++ xpdf-src/xpdf/GlobalParams.h	Sun Sep 29 11:34:12 2019
+diff -ur xpdf-4.03/xpdf/GlobalParams.h xpdf-src/xpdf/GlobalParams.h
+--- xpdf-4.03/xpdf/GlobalParams.h	Thu Jan 28 07:23:51 2021
++++ xpdf-src/xpdf/GlobalParams.h	Mon Feb 01 12:23:07 2021
 @@ -5,6 +5,9 @@
  // Copyright 2001-2003 Glyph & Cog, LLC
  //
@@ -155,7 +190,7 @@
  
  #ifndef GLOBALPARAMS_H
  #define GLOBALPARAMS_H
-@@ -219,7 +222,7 @@
+@@ -237,7 +240,7 @@
  
    // Initialize the global parameters by attempting to read a config
    // file.
@@ -164,11 +199,23 @@
  
    ~GlobalParams();
  
-diff -ur xpdf-4.02/xpdf/PDFDoc.cc xpdf-src/xpdf/PDFDoc.cc
---- xpdf-4.02/xpdf/PDFDoc.cc	Thu Sep 26 04:54:33 2019
-+++ xpdf-src/xpdf/PDFDoc.cc	Sun Sep 29 11:41:09 2019
-@@ -156,20 +156,25 @@
+diff -ur xpdf-4.03/xpdf/PDFDoc.cc xpdf-src/xpdf/PDFDoc.cc
+--- xpdf-4.03/xpdf/PDFDoc.cc	Thu Jan 28 07:23:51 2021
++++ xpdf-src/xpdf/PDFDoc.cc	Mon Feb 01 13:01:20 2021
+@@ -114,6 +114,7 @@
+   ok = setup(ownerPassword, userPassword);
+ }
  
++#if 0
+ #ifdef _WIN32
+ PDFDoc::PDFDoc(wchar_t *fileNameA, int fileNameLen, GString *ownerPassword,
+ 	       GString *userPassword, PDFCore *coreA) {
+@@ -161,23 +162,29 @@
+   ok = setup(ownerPassword, userPassword);
+ }
+ #endif
++#endif /* 0 */
+ 
  PDFDoc::PDFDoc(char *fileNameA, GString *ownerPassword,
  	       GString *userPassword, PDFCore *coreA) {
 +/*
@@ -180,7 +227,7 @@
 +/*
  #ifdef _WIN32
    Unicode u;
-   int n, i, j;
+   int i, j;
  #endif
 +*/
  
@@ -190,10 +237,10 @@
  
  #if defined(_WIN32)
 +#if 0
-   n = 0;
+   wchar_t wPath[MAX_PATH + 1];
    i = 0;
-   while (getUTF8(fileName, &i, &u)) {
-@@ -187,8 +192,11 @@
+   j = 0;
+@@ -197,8 +204,11 @@
    if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) {
      file = _wfopen(fileNameU, wfopenReadMode);
    } else {
@@ -205,7 +252,7 @@
  #elif defined(VMS)
    file = fopen(fileName->getCString(), fopenReadMode, "ctx=stm");
  #else
-@@ -581,6 +589,7 @@
+@@ -603,6 +613,7 @@
    GBool ret;
  
    // NB: _wfopen is only available in NT
@@ -213,7 +260,7 @@
    version.dwOSVersionInfoSize = sizeof(version);
    GetVersionEx(&version);
    if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) {
-@@ -590,12 +599,15 @@
+@@ -612,12 +623,15 @@
      path2w[i] = 0;
      f = _wfopen(path2w, L"wb");
    } else {
@@ -229,10 +276,10 @@
    if (!f) {
      return gFalse;
    }
-diff -ur xpdf-4.02/xpdf/Page.cc xpdf-src/xpdf/Page.cc
---- xpdf-4.02/xpdf/Page.cc	Thu Sep 26 04:54:33 2019
-+++ xpdf-src/xpdf/Page.cc	Sun Sep 29 11:43:12 2019
-@@ -480,9 +480,9 @@
+diff -ur xpdf-4.03/xpdf/Page.cc xpdf-src/xpdf/Page.cc
+--- xpdf-4.03/xpdf/Page.cc	Thu Jan 28 07:23:51 2021
++++ xpdf-src/xpdf/Page.cc	Mon Feb 01 12:31:27 2021
+@@ -485,9 +485,9 @@
    delete links;
  }
  
@@ -243,7 +290,7 @@
    GfxState *state;
    int i;
  
-@@ -499,5 +499,5 @@
+@@ -504,5 +504,5 @@
      ctm[i] = state->getCTM()[i];
    }
    delete state;
@@ -250,26 +297,10 @@
 -}
  #endif
 +}
-diff -ur xpdf-4.02/xpdf/XFAForm.cc xpdf-src/xpdf/XFAForm.cc
---- xpdf-4.02/xpdf/XFAForm.cc	Thu Sep 26 04:54:33 2019
-+++ xpdf-src/xpdf/XFAForm.cc	Sun Sep 29 11:44:10 2019
-@@ -29,8 +29,10 @@
- #include "XFAForm.h"
- 
- #ifdef _WIN32
--#  define strcasecmp stricmp
--#  define strncasecmp strnicmp
-+#  undef strcasecmp
-+#  undef strncasecmp
-+#  define strcasecmp _stricmp
-+#  define strncasecmp _strnicmp
- #endif
- 
- //------------------------------------------------------------------------
-diff -ur xpdf-4.02/xpdf/config.h xpdf-src/xpdf/config.h
---- xpdf-4.02/xpdf/config.h	Thu Sep 26 04:54:33 2019
-+++ xpdf-src/xpdf/config.h	Sun Sep 29 11:45:39 2019
-@@ -78,11 +78,6 @@
+diff -ur xpdf-4.03/xpdf/config.h xpdf-src/xpdf/config.h
+--- xpdf-4.03/xpdf/config.h	Thu Jan 28 07:23:52 2021
++++ xpdf-src/xpdf/config.h	Mon Feb 01 12:34:28 2021
+@@ -80,11 +80,6 @@
  // popen
  //------------------------------------------------------------------------
  

Modified: trunk/Build/source/libs/xpdf/configure
===================================================================
--- trunk/Build/source/libs/xpdf/configure	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/configure	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,6 +1,6 @@
 #! /bin/sh
 # Guess values for system-dependent variables and create Makefiles.
-# Generated by GNU Autoconf 2.69 for xpdf (TeX Live) 4.02.
+# Generated by GNU Autoconf 2.69 for xpdf (TeX Live) 4.03.
 #
 # Report bugs to <tex-k at tug.org>.
 #
@@ -580,8 +580,8 @@
 # Identity of this package.
 PACKAGE_NAME='xpdf (TeX Live)'
 PACKAGE_TARNAME='xpdf--tex-live-'
-PACKAGE_VERSION='4.02'
-PACKAGE_STRING='xpdf (TeX Live) 4.02'
+PACKAGE_VERSION='4.03'
+PACKAGE_STRING='xpdf (TeX Live) 4.03'
 PACKAGE_BUGREPORT='tex-k at tug.org'
 PACKAGE_URL=''
 
@@ -1290,7 +1290,7 @@
   # Omit some internal or obsolete options to make the list less imposing.
   # This message is too long to be a string in the A/UX 3.1 sh.
   cat <<_ACEOF
-\`configure' configures xpdf (TeX Live) 4.02 to adapt to many kinds of systems.
+\`configure' configures xpdf (TeX Live) 4.03 to adapt to many kinds of systems.
 
 Usage: $0 [OPTION]... [VAR=VALUE]...
 
@@ -1356,7 +1356,7 @@
 
 if test -n "$ac_init_help"; then
   case $ac_init_help in
-     short | recursive ) echo "Configuration of xpdf (TeX Live) 4.02:";;
+     short | recursive ) echo "Configuration of xpdf (TeX Live) 4.03:";;
    esac
   cat <<\_ACEOF
 
@@ -1460,7 +1460,7 @@
 test -n "$ac_init_help" && exit $ac_status
 if $ac_init_version; then
   cat <<\_ACEOF
-xpdf (TeX Live) configure 4.02
+xpdf (TeX Live) configure 4.03
 generated by GNU Autoconf 2.69
 
 Copyright (C) 2012 Free Software Foundation, Inc.
@@ -1867,7 +1867,7 @@
 This file contains any messages produced by compilers while
 running configure, to aid debugging if configure makes a mistake.
 
-It was created by xpdf (TeX Live) $as_me 4.02, which was
+It was created by xpdf (TeX Live) $as_me 4.03, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   $ $0 $@
@@ -3786,7 +3786,7 @@
 
 # Define the identity of the package.
  PACKAGE='xpdf--tex-live-'
- VERSION='4.02'
+ VERSION='4.03'
 
 
 # Some tools Automake needs.
@@ -6699,7 +6699,7 @@
 # report actual input values of CONFIG_FILES etc. instead of their
 # values after options handling.
 ac_log="
-This file was extended by xpdf (TeX Live) $as_me 4.02, which was
+This file was extended by xpdf (TeX Live) $as_me 4.03, which was
 generated by GNU Autoconf 2.69.  Invocation command line was
 
   CONFIG_FILES    = $CONFIG_FILES
@@ -6769,7 +6769,7 @@
 cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1
 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`"
 ac_cs_version="\\
-xpdf (TeX Live) config.status 4.02
+xpdf (TeX Live) config.status 4.03
 configured by $0, generated by GNU Autoconf 2.69,
   with options \\"\$ac_cs_config\\"
 

Modified: trunk/Build/source/libs/xpdf/version.ac
===================================================================
--- trunk/Build/source/libs/xpdf/version.ac	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/version.ac	2021-02-01 06:11:18 UTC (rev 57588)
@@ -8,4 +8,4 @@
 dnl --------------------------------------------------------
 dnl
 dnl  m4-include this file to define the current xpdf version
-m4_define([xpdf_version], [4.02])
+m4_define([xpdf_version], [4.03])

Modified: trunk/Build/source/libs/xpdf/xpdf-src/ANNOUNCE
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/ANNOUNCE	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/ANNOUNCE	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,4 +1,4 @@
-Subject: ANNOUNCE: Xpdf 4.02 - a PDF viewer and related tools
+Subject: ANNOUNCE: Xpdf 4.03 - a PDF viewer and related tools
 
 Glyph & Cog, LLC is pleased to announce a new version of Xpdf, the
 open source Portable Document Format (PDF) viewer.  The Xpdf project
@@ -10,19 +10,17 @@
 Unix, Windows, MacOSX, and pretty much any other system with a decent
 C++ compiler.
 
-4.02 is primarily a bug fix release.  There are some new features:
+4.03 is primarily a bug fix release.  There are some new features:
 
-* Pdftohtml now extracts embedded fonts.
+* XpdfReader improvements:
+  - Implemented selection extension via shift-click, and word/line
+    selection via double/triple click.
+  - Added default bindings for ctrl-mousewheel-up/down to zoom in/out.
+  - Added a help menu item that shows all of the key bindings.
 
-* Various user interface tweaks in XpdfReader: added a button to
-  toggle sidebar visibilty, added menu items to toggle the sidebar and
-  toolbar, added the 'initialDisplayMode', 'initialToolbarState', and
-  'initialSelectMode' xpdfrc settings.
+* Various new command line options for pdftotext, pdftohtml, pdftoppm,
+  pdftopng, and xpdf.
 
-* If XpdfReader is already running, double-clicking on a PDF file (or
-  dragging-and-dropping it on the XpdfReader icon) opens the file in a
-  new tab (via the new '-open' command line switch).
-
 See the `CHANGES' file for a complete list of changes.
 
 Source (C++ and C) is available, and it should be fairly easy to

Modified: trunk/Build/source/libs/xpdf/xpdf-src/CHANGES
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/CHANGES	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/CHANGES	2021-02-01 06:11:18 UTC (rev 57588)
@@ -2645,4 +2645,98 @@
 Function objects weren't checking their input/output counts properly.
   [Thanks to Pangu Lab for the bug report.]
 TextPage::findGaps() wasn't checking the x/y min/max values for int
-  overflow.  [Thanks to Taolaw for the bugf report.]
+  overflow.  [Thanks to Taolaw for the bug report.]
+
+4.03 (2021-jan-28)
+------------------
+Implemented selection extension via shift-click, and word/line
+  selection via double/triple click.
+Added default bindings for ctrl-mousewheel-up/down to zoom in/out.
+Added the "-nofonts" option to pdftohtml.
+Added the "simple2" mode to pdftotext.
+Added the "-rot" flag to xpdf, pdftoppm, and pdftopng.
+Added the "-listencodings" flag to pdftotext.
+Added the 'copyLinkTarget' command.
+Added the 'selectionColor' xpdfrc setting.
+Added the 'initialSidebarWidth' xpdfrc setting.
+Added support for @"..." strings in xpdfrc files.  This includes using
+  '%' as an escape character, and also the '${DATADIR}' variable.
+Added a help menu item that shows all of the key bindings.
+Reorganized the gradient shading rasterization code to avoid
+  transparency artifacts between triangles/patches.
+Use interpolation rather than subdivision to rasterize Gouraud
+  triangle shadings.
+Allow escaping (via the \x01 character) in command args, so that "xpdf
+  -open foo(123).pdf" works correctly.
+Extended the damaged file repair code to handle PDF files that use
+  xref streams and object streams.
+Modified pdfinfo to print the encryption algorithm used (if any).
+Handle Windows shortcut (.lnk) files in Xpdf and in the command line
+  tools.
+Display an error dialog when trying to copy text from a protected
+  file.
+Updated all four of the CJK language support packages with the latest
+  Adobe data.
+Modified pdftohtml to draw 'invisible' text over non-horizontal words.
+Catalog.pageLabels wasn't being properly initialized to NULL.  [Thanks
+  to Dhiraj for the bug report.]
+Added a sanity check for JBIG2 symbol width.  [Reported by Marc
+  Schoenefeld.]
+Fixed a bug in the fontconfig calls that was causing Xpdf to crash.
+The Type 3 font cache code wasn't correctly handling the case where a
+  Type 3 char refers to another char in the same T3 font.  [Thanks to
+  Pangu Lab for the bug report.]
+Xpdf was crashing if 'view page labels' was enabled without any
+  document open.  [Thanks to TeamSeri0us for the bug report.]
+The Type 1C-to-Type 1 font converter wasn't checking for
+  divide-by-zero with rational numbers.  [Thanks to TeamSeri0us for
+  the bug report.]
+Fixed a bug in the TrueType font parser - zero-length loca tables
+  weren't handled correctly.  [Thanks to TeamSeri0us for the bug
+  report.]
+Fixed an integer overflow bug in the transparency group setup code.
+  [Thanks to TeamSeri0us for the bug report.]
+Pdfinfo with the "-box" option was crashing on zero-page PDF files.
+  [Thanks to TeamSeri0us for the bug report.]
+The code that caches scaled images wasn't checking the rendering
+  intent.  [Thanks to Martin Muskens at Ergosoft for the bug report.]
+The JBIG2 decoder now checks for gibberish values in various segment
+  headers, but keeps any data decoded prior to that.
+Fixed two bugs in the FoFi code that handles CFF fonts.  [Thanks to
+  dsmic for the bug report.]
+Redesigned the image scaling code, for performance.
+Cleaned up the code that sanity-checks font sizes to avoid trying to
+  allocate too much memory for the font cache.
+Implemented precincts in the JPEG 2000 decoder.
+The page-up/down snapping code was calling TileMap::getPageTopY() with
+  invalid page numbers.  [Thanks to dfandrich for the bug report.]
+Added missing error-checking when parsing an annotation appearance
+  stream's bbox.
+Handled UTF-16 sequences in ToUnicode maps.
+Rewrote the code that draws static XFA forms: combined it with the
+  AcroForm code, to match Adobe's behavior.  Removed the enableXFA
+  xpdfrc setting.
+Check for indirect references in content streams and report an error.
+Doing overprint preview in tiling patterns requires tracking the
+  overprint mask at each pixel.
+Check for "decompression bombs" in Flate and LZW streams.
+The nextPageNoScroll and prevPageNoScroll commands weren't working
+  correctly in continuous mode.
+Disable stroke adjustment when drawing Type 3 characters.
+The "xpdf -open" flag now constructs an absolute path, so it's not
+  dependent on the current directory when xpdf was started.
+Check for infinite loops in Type 1C charstring subroutines.  [Thanks
+  to blbi for the bug report.]
+Fixed an incomplete test for headless CFF files.  [Thanks to Chengbin
+  for the bug report.]
+Tweaked the code that computes segment slope in SplashXPath.cc to
+  avoid problems with extremely small coordinate values.  [Thanks to
+  Chengbin for the bug report.]
+Check for an invalid segment length in PSOutputDev's PFB parse.
+  [Thanks to Chengbin for the bug report.]
+Check for JPEG 2000 segments that are only allowed in the first
+  tile-part of a tile.  [Thanks to Chengbin for the bug report.]
+Check for invalid AcroForm ListBox field top index.  [Thanks to
+  Chengbin for the bug report.]
+Check for invalid mesh shading parameters.  [Thanks to Chengbin for
+  the bug report.]

Modified: trunk/Build/source/libs/xpdf/xpdf-src/INSTALL
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/INSTALL	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/INSTALL	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,11 +1,11 @@
 Xpdf
 ====
 
-version 4.02
-2019-sep-25
+version 4.03
+2021-jan-28
 
 The Xpdf software and documentation are
-copyright 1996-2019 Glyph & Cog, LLC.
+copyright 1996-2021 Glyph & Cog, LLC.
 
 Email: xpdf at xpdfreader.com
 WWW: http://www.xpdfreader.com/
@@ -115,25 +115,29 @@
     -DSYSTEM_XPDFRC="/etc/xpdfrc"
         Look for a system-wide xpdfrc config file in this directory.
 
-     -DCMAKE_DISABLE_FIND_PACKAGE_Qt4=1
-     -DCMAKE_DISABLE_FIND_PACKAGE_Qt5Widgets=1
+    -DXPDFRC_DATADIR="/usr/share/xpdf"
+        The ${DATADIR} variable in xpdfrc config files will expand to
+        this string.
+
+    -DCMAKE_DISABLE_FIND_PACKAGE_Qt4=1
+    -DCMAKE_DISABLE_FIND_PACKAGE_Qt5Widgets=1
         Do not search for the Qt4/Qt5 libraries.  This will disable
         building the GUI viewer (xpdf).  Cmake will look for a "qmake"
         binary -- make sure the first qmake binary on your executable
         search path matches the desired version of Qt.
 
-     -DCMAKE_C_FLAGS="..."
-     -DCMAKE_CXX_FLAGS="..."
+    -DCMAKE_C_FLAGS="..."
+    -DCMAKE_CXX_FLAGS="..."
         Set additional options to pass to the C and/or C++ compilers.
 
-     -DCMAKE_EXE_LINKER_FLAGS="..."
+    -DCMAKE_EXE_LINKER_FLAGS="..."
         Set additional options to pass to the linker.
 
-     -DCMAKE_INSTALL_BINDIR
+    -DCMAKE_INSTALL_BINDIR
         Set the bin directory, relative to CMAKE_INSTALL_PREFIX
         (typically "bin").
 
-     -DCMAKE_INSTALL_MANDIR
+    -DCMAKE_INSTALL_MANDIR
         Set the man directory, relative to CMAKE_INSTALL_PREFIX
         (typically "man" or "share/man").
 

Modified: trunk/Build/source/libs/xpdf/xpdf-src/README
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/README	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/README	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,17 +1,17 @@
 Xpdf
 ====
 
-version 4.02
-2019-sep-25
+version 4.03
+2021-jan-28
 
 The Xpdf software and documentation are
-copyright 1996-2019 Glyph & Cog, LLC.
+copyright 1996-2021 Glyph & Cog, LLC.
 
 Email: xpdf at xpdfreader.com
 WWW: http://www.xpdfreader.com/
 
 The PDF data structures, operators, and specification are
-documented in ISO 32000-2:2017.
+documented in ISO 32000-2:2020.
 
 
 What is Xpdf?
@@ -326,8 +326,8 @@
 published spec.]
 
 ISO, International Standard -- Document Management - Portable document
-format - Part 2: PDF 2.0.  ISO 32000-2:2017.
-[The manual for PDF version 2.0.]
+format - Part 2: PDF 2.0.  ISO 32000-2:2020.
+[The specification for PDF version 2.0.]
 
 ITU, "Standardization of Group 3 facsimile terminals for document
 transmission", ITU-T Recommendation T.4, 1999.

Modified: trunk/Build/source/libs/xpdf/xpdf-src/cmake-config.txt
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/cmake-config.txt	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/cmake-config.txt	2021-02-01 06:11:18 UTC (rev 57588)
@@ -8,6 +8,17 @@
 #
 #========================================================================
 
+if (POLICY CMP0074)
+  cmake_policy(SET CMP0074 NEW)
+endif ()
+
+if (APPLE)
+  if (POLICY CMP0042)
+    cmake_policy(SET CMP0042 NEW)
+  endif ()
+  set(CMAKE_MACOSX_RPATH 1)
+endif ()
+
 include(CheckFunctionExists)
 include(CheckCXXSourceCompiles)
 include(GNUInstallDirs)
@@ -41,12 +52,12 @@
     CMAKE_CXX_FLAGS_PROFILING
     CMAKE_EXE_LINKER_FLAGS_PROFILING)
   set(CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}" CACHE STRING
-      "Choose build mode - options are: None Debug Release RelWithDebInfo MinSiz
-eRel Profiling")
+      "Choose build mode - options are: None Debug Release RelWithDebInfo MinSizeRel Profiling")
 endif ()
 
 #--- set default C/C++ compiler flags for Windows
 if (WIN32)
+  option(USE_MT_IN_DEBUG "use /MT instead of /MTd in debug builds" OFF)
   foreach (var CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG
                CMAKE_C_FLAGS_RELEASE CMAKE_CXX_FLAGS_RELEASE
                CMAKE_C_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_MINSIZEREL
@@ -54,8 +65,13 @@
     # note: this converts /MD to /MT and /MDd to /MTd
     string(REGEX REPLACE "/MD" "/MT" ${var} "${${var}}")
   endforeach ()
-  set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/NODEFAULTLIB:libcmt ${CMAKE_EXE_LINKER_FLAGS_DEBUG}")
-  set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "/NODEFAULTLIB:libcmt ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}")
+  if (USE_MT_IN_DEBUG)
+    string(REPLACE "/MTd" "/MT" CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
+    string(REPLACE "/MTd" "/MT" CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
+  else ()
+    set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/NODEFAULTLIB:libcmt ${CMAKE_EXE_LINKER_FLAGS_DEBUG}")
+    set(CMAKE_SHARED_LINKER_FLAGS_DEBUG "/NODEFAULTLIB:libcmt ${CMAKE_SHARED_LINKER_FLAGS_DEBUG}")
+  endif ()
   foreach (var CMAKE_C_FLAGS_DEBUG
                CMAKE_C_FLAGS_RELEASE
                CMAKE_C_FLAGS_MINSIZEREL
@@ -88,6 +104,12 @@
 else ()
   set(SYSTEM_XPDFRC_DEFINE "/* #undef SYSTEM_XPDFRC */")
 endif ()
+option(XPDFRC_DATADIR "directory to use for the DATADIR xpdfrc variable" "")
+if (XPDFRC_DATADIR)
+  set(XPDFRC_DATADIR_DEFINE "#define XPDFRC_DATADIR \"${XPDFRC_DATADIR}\"")
+else ()
+  set(XPDFRC_DATADIR_DEFINE "/* #undef SYSTEM_XPDFRC */")
+endif ()
 if (WIN32)
   option(XPDFWIDGET_PRINTING "include printing support in XpdfWidget" OFF)
 else ()
@@ -194,7 +216,7 @@
   find_package(Qt5Network)
   find_package(Qt5PrintSupport)
 else ()
-  find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork REQUIRED)
+  find_package(Qt4 COMPONENTS QtCore QtGui QtNetwork)
 endif ()
 if(Qt5Widgets_FOUND)
   message(STATUS "Qt5 found")

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.1
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.1	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.1	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,8 +1,8 @@
-.\" Copyright 2013-2019 Glyph & Cog, LLC
-.TH pdfdetach 1 "25 Sep 2019"
+.\" Copyright 2013-2021 Glyph & Cog, LLC
+.TH pdfdetach 1 "28 Jan 2021"
 .SH NAME
 pdfdetach \- Portable Document Format (PDF) document embedded file
-extractor (version 4.02)
+extractor (version 4.03)
 .SH SYNOPSIS
 .B pdfdetach
 [options]
@@ -90,7 +90,7 @@
 99
 Other error.
 .SH AUTHOR
-The pdfinfo software and documentation are copyright 1996-2019 Glyph &
+The pdfinfo software and documentation are copyright 1996-2021 Glyph &
 Cog, LLC.
 .SH "SEE ALSO"
 .BR xpdf (1),

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.cat
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.cat	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfdetach.cat	2021-02-01 06:11:18 UTC (rev 57588)
@@ -4,7 +4,7 @@
 
 NAME
        pdfdetach  -  Portable  Document  Format  (PDF)  document embedded file
-       extractor (version 4.02)
+       extractor (version 4.03)
 
 SYNOPSIS
        pdfdetach [options] [PDF-file]
@@ -79,7 +79,7 @@
        99     Other error.
 
 AUTHOR
-       The pdfinfo software and documentation are copyright 1996-2019 Glyph  &
+       The pdfinfo software and documentation are copyright 1996-2021 Glyph  &
        Cog, LLC.
 
 SEE ALSO
@@ -89,4 +89,4 @@
 
 
 
-                                  25 Sep 2019                     pdfdetach(1)
+                                  28 Jan 2021                     pdfdetach(1)

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.1
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.1	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.1	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,8 +1,8 @@
-.\" Copyright 1999-2019 Glyph & Cog, LLC
-.TH pdffonts 1 "25 Sep 2019"
+.\" Copyright 1999-2021 Glyph & Cog, LLC
+.TH pdffonts 1 "28 Jan 2021"
 .SH NAME
 pdffonts \- Portable Document Format (PDF) font analyzer (version
-4.02)
+4.03)
 .SH SYNOPSIS
 .B pdffonts
 [options]
@@ -147,7 +147,7 @@
 99
 Other error.
 .SH AUTHOR
-The pdffonts software and documentation are copyright 1996-2019 Glyph
+The pdffonts software and documentation are copyright 1996-2021 Glyph
 & Cog, LLC.
 .SH "SEE ALSO"
 .BR xpdf (1),

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.cat
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.cat	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdffonts.cat	2021-02-01 06:11:18 UTC (rev 57588)
@@ -3,7 +3,7 @@
 
 
 NAME
-       pdffonts - Portable Document Format (PDF) font analyzer (version 4.02)
+       pdffonts - Portable Document Format (PDF) font analyzer (version 4.03)
 
 SYNOPSIS
        pdffonts [options] [PDF-file]
@@ -105,7 +105,7 @@
        99     Other error.
 
 AUTHOR
-       The pdffonts software and documentation are copyright 1996-2019 Glyph &
+       The pdffonts software and documentation are copyright 1996-2021 Glyph &
        Cog, LLC.
 
 SEE ALSO
@@ -115,4 +115,4 @@
 
 
 
-                                  25 Sep 2019                      pdffonts(1)
+                                  28 Jan 2021                      pdffonts(1)

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.1
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.1	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.1	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,8 +1,8 @@
-.\" Copyright 1998-2019 Glyph & Cog, LLC
-.TH pdfimages 1 "25 Sep 2019"
+.\" Copyright 1998-2021 Glyph & Cog, LLC
+.TH pdfimages 1 "28 Jan 2021"
 .SH NAME
 pdfimages \- Portable Document Format (PDF) image extractor
-(version 4.02)
+(version 4.03)
 .SH SYNOPSIS
 .B pdfimages
 [options]
@@ -102,7 +102,7 @@
 99
 Other error.
 .SH AUTHOR
-The pdfimages software and documentation are copyright 1998-2019 Glyph
+The pdfimages software and documentation are copyright 1998-2021 Glyph
 & Cog, LLC.
 .SH "SEE ALSO"
 .BR xpdf (1),

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.cat
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.cat	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfimages.cat	2021-02-01 06:11:18 UTC (rev 57588)
@@ -4,7 +4,7 @@
 
 NAME
        pdfimages  -  Portable  Document  Format (PDF) image extractor (version
-       4.02)
+       4.03)
 
 SYNOPSIS
        pdfimages [options] PDF-file image-root
@@ -85,7 +85,7 @@
        99     Other error.
 
 AUTHOR
-       The  pdfimages software and documentation are copyright 1998-2019 Glyph
+       The  pdfimages software and documentation are copyright 1998-2021 Glyph
        & Cog, LLC.
 
 SEE ALSO
@@ -95,4 +95,4 @@
 
 
 
-                                  25 Sep 2019                     pdfimages(1)
+                                  28 Jan 2021                     pdfimages(1)

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.1
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.1	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.1	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,8 +1,8 @@
-.\" Copyright 1999-2019 Glyph & Cog, LLC
-.TH pdfinfo 1 "25 Sep 2019"
+.\" Copyright 1999-2021 Glyph & Cog, LLC
+.TH pdfinfo 1 "28 Jan 2021"
 .SH NAME
 pdfinfo \- Portable Document Format (PDF) document information
-extractor (version 4.02)
+extractor (version 4.03)
 .SH SYNOPSIS
 .B pdfinfo
 [options]
@@ -150,7 +150,7 @@
 99
 Other error.
 .SH AUTHOR
-The pdfinfo software and documentation are copyright 1996-2019 Glyph &
+The pdfinfo software and documentation are copyright 1996-2021 Glyph &
 Cog, LLC.
 .SH "SEE ALSO"
 .BR xpdf (1),

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.cat
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.cat	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdfinfo.cat	2021-02-01 06:11:18 UTC (rev 57588)
@@ -4,7 +4,7 @@
 
 NAME
        pdfinfo - Portable Document Format (PDF) document information extractor
-       (version 4.02)
+       (version 4.03)
 
 SYNOPSIS
        pdfinfo [options] [PDF-file]
@@ -104,7 +104,7 @@
        99     Other error.
 
 AUTHOR
-       The  pdfinfo software and documentation are copyright 1996-2019 Glyph &
+       The  pdfinfo software and documentation are copyright 1996-2021 Glyph &
        Cog, LLC.
 
 SEE ALSO
@@ -114,4 +114,4 @@
 
 
 
-                                  25 Sep 2019                       pdfinfo(1)
+                                  28 Jan 2021                       pdfinfo(1)

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.1
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.1	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.1	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,8 +1,8 @@
-.\" Copyright 1997-2019 Glyph & Cog, LLC
-.TH pdftohtml 1 "25 Sep 2019"
+.\" Copyright 1997-2021 Glyph & Cog, LLC
+.TH pdftohtml 1 "28 Jan 2021"
 .SH NAME
 pdftohtml \- Portable Document Format (PDF) to HTML converter
-(version 4.02)
+(version 4.03)
 .SH SYNOPSIS
 .B pdftohtml
 [options]
@@ -50,6 +50,11 @@
 \'-r' value will allow the viewer to zoom in farther without upscaling
 artifacts in the background.
 .TP
+.B \-nofonts
+Disable extraction of embedded fonts.  By default, pdftohtml extracts
+TrueType and OpenType fonts.  Disabling extraction can work around
+problems with buggy fonts.
+.TP
 .B \-skipinvisible
 Don't draw invisible text.  By default, invisible text (commonly used
 in OCR'ed PDF files) is drawn as transparent (alpha=0) HTML text.
@@ -109,7 +114,7 @@
 99
 Other error.
 .SH AUTHOR
-The pdftohtml software and documentation are copyright 1996-2019 Glyph
+The pdftohtml software and documentation are copyright 1996-2021 Glyph
 & Cog, LLC.
 .SH "SEE ALSO"
 .BR xpdf (1),

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.cat
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.cat	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftohtml.cat	2021-02-01 06:11:18 UTC (rev 57588)
@@ -4,7 +4,7 @@
 
 NAME
        pdftohtml  -  Portable Document Format (PDF) to HTML converter (version
-       4.02)
+       4.03)
 
 SYNOPSIS
        pdftohtml [options] PDF-file HTML-dir
@@ -48,6 +48,11 @@
               larger '-r' value will allow the viewer to zoom in farther with-
               out upscaling artifacts in the background.
 
+       -nofonts
+              Disable extraction of embedded  fonts.   By  default,  pdftohtml
+              extracts  TrueType and OpenType fonts.  Disabling extraction can
+              work around problems with buggy fonts.
+
        -skipinvisible
               Don't draw invisible text.  By default, invisible text (commonly
               used in OCR'ed PDF files) is drawn as transparent (alpha=0) HTML
@@ -97,7 +102,7 @@
        99     Other error.
 
 AUTHOR
-       The  pdftohtml software and documentation are copyright 1996-2019 Glyph
+       The  pdftohtml software and documentation are copyright 1996-2021 Glyph
        & Cog, LLC.
 
 SEE ALSO
@@ -107,4 +112,4 @@
 
 
 
-                                  25 Sep 2019                     pdftohtml(1)
+                                  28 Jan 2021                     pdftohtml(1)

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.1
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.1	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.1	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,8 +1,8 @@
-.\" Copyright 2017-2019 Glyph & Cog, LLC
-.TH pdftopng 1 "25 Sep 2019"
+.\" Copyright 2017-2021 Glyph & Cog, LLC
+.TH pdftopng 1 "28 Jan 2021"
 .SH NAME
 pdftopng \- Portable Document Format (PDF) to Portable Network Graphics
-(PNG) converter (version 4.02)
+(PNG) converter (version 4.03)
 .SH SYNOPSIS
 .B pdftopng
 [options]
@@ -56,6 +56,9 @@
 PDF files that have been constructed with a transparent background.
 The \-alpha flag cannot be used with \-mono.
 .TP
+.BI \-rot " angle"
+Rotate pages by 0 (the default), 90, 180, or 270 degrees.
+.TP
 .BI \-freetype " yes | no"
 Enable or disable FreeType (a TrueType / Type 1 font rasterizer).
 This defaults to "yes".
@@ -107,7 +110,7 @@
 99
 Other error.
 .SH AUTHOR
-The pdftopng software and documentation are copyright 1996-2019 Glyph
+The pdftopng software and documentation are copyright 1996-2021 Glyph
 & Cog, LLC.
 .SH "SEE ALSO"
 .BR xpdf (1),

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.cat
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.cat	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftopng.cat	2021-02-01 06:11:18 UTC (rev 57588)
@@ -4,7 +4,7 @@
 
 NAME
        pdftopng  - Portable Document Format (PDF) to Portable Network Graphics
-       (PNG) converter (version 4.02)
+       (PNG) converter (version 4.03)
 
 SYNOPSIS
        pdftopng [options] PDF-file PNG-root
@@ -48,6 +48,9 @@
               with PDF files that have been  constructed  with  a  transparent
               background.  The -alpha flag cannot be used with -mono.
 
+       -rot angle
+              Rotate pages by 0 (the default), 90, 180, or 270 degrees.
+
        -freetype yes | no
               Enable  or  disable  FreeType  (a TrueType / Type 1 font raster-
               izer).  This defaults to "yes".  [config file: enableFreeType]
@@ -87,7 +90,7 @@
        99     Other error.
 
 AUTHOR
-       The pdftopng software and documentation are copyright 1996-2019 Glyph &
+       The pdftopng software and documentation are copyright 1996-2021 Glyph &
        Cog, LLC.
 
 SEE ALSO
@@ -97,4 +100,4 @@
 
 
 
-                                  25 Sep 2019                      pdftopng(1)
+                                  28 Jan 2021                      pdftopng(1)

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.1
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.1	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.1	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,8 +1,8 @@
-.\" Copyright 2005-2019 Glyph & Cog, LLC
-.TH pdftoppm 1 "25 Sep 2019"
+.\" Copyright 2005-2021 Glyph & Cog, LLC
+.TH pdftoppm 1 "28 Jan 2021"
 .SH NAME
 pdftoppm \- Portable Document Format (PDF) to Portable Pixmap (PPM)
-converter (version 4.02)
+converter (version 4.03)
 .SH SYNOPSIS
 .B pdftoppm
 [options]
@@ -56,6 +56,9 @@
 .B \-cmyk
 Generate a CMYK PAM file (instead of an RGB PPM file).
 .TP
+.BI \-rot " angle"
+Rotate pages by 0 (the default), 90, 180, or 270 degrees.
+.TP
 .BI \-freetype " yes | no"
 Enable or disable FreeType (a TrueType / Type 1 font rasterizer).
 This defaults to "yes".
@@ -107,7 +110,7 @@
 99
 Other error.
 .SH AUTHOR
-The pdftoppm software and documentation are copyright 1996-2019 Glyph
+The pdftoppm software and documentation are copyright 1996-2021 Glyph
 & Cog, LLC.
 .SH "SEE ALSO"
 .BR xpdf (1),

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.cat
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.cat	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftoppm.cat	2021-02-01 06:11:18 UTC (rev 57588)
@@ -4,7 +4,7 @@
 
 NAME
        pdftoppm - Portable Document Format (PDF) to Portable Pixmap (PPM) con-
-       verter (version 4.02)
+       verter (version 4.03)
 
 SYNOPSIS
        pdftoppm [options] PDF-file PPM-root
@@ -47,6 +47,9 @@
 
        -cmyk  Generate a CMYK PAM file (instead of an RGB PPM file).
 
+       -rot angle
+              Rotate pages by 0 (the default), 90, 180, or 270 degrees.
+
        -freetype yes | no
               Enable or disable FreeType (a TrueType /  Type  1  font  raster-
               izer).  This defaults to "yes".  [config file: enableFreeType]
@@ -86,7 +89,7 @@
        99     Other error.
 
 AUTHOR
-       The pdftoppm software and documentation are copyright 1996-2019 Glyph &
+       The pdftoppm software and documentation are copyright 1996-2021 Glyph &
        Cog, LLC.
 
 SEE ALSO
@@ -96,4 +99,4 @@
 
 
 
-                                  25 Sep 2019                      pdftoppm(1)
+                                  28 Jan 2021                      pdftoppm(1)

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftops.1
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftops.1	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftops.1	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,8 +1,8 @@
-.\" Copyright 1996-2019 Glyph & Cog, LLC
-.TH pdftops 1 "25 Sep 2019"
+.\" Copyright 1996-2021 Glyph & Cog, LLC
+.TH pdftops 1 "28 Jan 2021"
 .SH NAME
 pdftops \- Portable Document Format (PDF) to PostScript converter
-(version 4.02)
+(version 4.03)
 .SH SYNOPSIS
 .B pdftops
 [options]
@@ -51,7 +51,7 @@
 significantly larger (if they contain images), but will print on Level
 1 printers.  This also converts all images to black and white.  No
 more than one of the PostScript level options (\-level1, \-level1sep,
-\-level2, \-level2sep, \-level3, \-level3Sep) may be given.
+\-level2, \-level2sep, \-level3, \-level3sep) may be given.
 .RB "[config file: " psLevel ]
 .TP
 .B \-level1sep
@@ -86,9 +86,9 @@
 are converted to grayscale.
 .RB "[config file: " psLevel ]
 .TP
-.B \-level3Sep
+.B \-level3sep
 Generate Level 3 separable PostScript.  The separation handling is the
-same as for \-level2Sep.
+same as for \-level2sep.
 .RB "[config file: " psLevel ]
 .TP
 .B \-eps
@@ -236,7 +236,7 @@
 99
 Other error.
 .SH AUTHOR
-The pdftops software and documentation are copyright 1996-2019 Glyph &
+The pdftops software and documentation are copyright 1996-2021 Glyph &
 Cog, LLC.
 .SH "SEE ALSO"
 .BR xpdf (1),

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftops.cat
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftops.cat	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftops.cat	2021-02-01 06:11:18 UTC (rev 57588)
@@ -4,7 +4,7 @@
 
 NAME
        pdftops  - Portable Document Format (PDF) to PostScript converter (ver-
-       sion 4.02)
+       sion 4.03)
 
 SYNOPSIS
        pdftops [options] [PDF-file [PS-file]]
@@ -42,7 +42,7 @@
               print on Level 1 printers.  This also  converts  all  images  to
               black  and  white.   No  more  than  one of the PostScript level
               options  (-level1,  -level1sep,  -level2,  -level2sep,  -level3,
-              -level3Sep) may be given.  [config file: psLevel]
+              -level3sep) may be given.  [config file: psLevel]
 
        -level1sep
               Generate Level 1 separable PostScript.  All colors are converted
@@ -72,9 +72,9 @@
               Generate grayscale Level 3 PostScript.   All  colors,  including
               images, are converted to grayscale.  [config file: psLevel]
 
-       -level3Sep
+       -level3sep
               Generate  Level 3 separable PostScript.  The separation handling
-              is the same as for -level2Sep.  [config file: psLevel]
+              is the same as for -level2sep.  [config file: psLevel]
 
        -eps   Generate an Encapsulated PostScript (EPS)  file.   An  EPS  file
               contains a single image, so if you use this option with a multi-
@@ -201,7 +201,7 @@
        99     Other error.
 
 AUTHOR
-       The  pdftops software and documentation are copyright 1996-2019 Glyph &
+       The  pdftops software and documentation are copyright 1996-2021 Glyph &
        Cog, LLC.
 
 SEE ALSO
@@ -211,4 +211,4 @@
 
 
 
-                                  25 Sep 2019                       pdftops(1)
+                                  28 Jan 2021                       pdftops(1)

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.1
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.1	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.1	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,8 +1,8 @@
-.\" Copyright 1997-2019 Glyph & Cog, LLC
-.TH pdftotext 1 "25 Sep 2019"
+.\" Copyright 1997-2021 Glyph & Cog, LLC
+.TH pdftotext 1 "28 Jan 2021"
 .SH NAME
 pdftotext \- Portable Document Format (PDF) to text converter
-(version 4.02)
+(version 4.03)
 .SH SYNOPSIS
 .B pdftotext
 [options]
@@ -59,6 +59,12 @@
 job of maintaining horizontal spacing, but it will only work properly
 with a single column of text.
 .TP
+.B \-simple2
+Similar to
+.BR \-simple ,
+but handles slightly rotated text (e.g., OCR output) better.  Only works
+for pages with a single column of text.
+.TP
 .B \-table
 Table mode is similar to physical layout mode, but optimized for
 tabular data, with the goal of keeping rows and columns aligned (at
@@ -161,11 +167,14 @@
 .I config-file
 in place of ~/.xpdfrc or the system-wide config file.
 .TP
+.B \-listencodings
+List all available text output encodings, then exit.
+.TP
 .B \-v
-Print copyright and version information.
+Print copyright and version information, then exit.
 .TP
 .B \-h
-Print usage information.
+Print usage information, then exit.
 .RB ( \-help
 and
 .B \-\-help
@@ -192,7 +201,7 @@
 99
 Other error.
 .SH AUTHOR
-The pdftotext software and documentation are copyright 1996-2019 Glyph
+The pdftotext software and documentation are copyright 1996-2021 Glyph
 & Cog, LLC.
 .SH "SEE ALSO"
 .BR xpdf (1),

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.cat
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.cat	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/pdftotext.cat	2021-02-01 06:11:18 UTC (rev 57588)
@@ -4,7 +4,7 @@
 
 NAME
        pdftotext  -  Portable Document Format (PDF) to text converter (version
-       4.02)
+       4.03)
 
 SYNOPSIS
        pdftotext [options] [PDF-file [text-file]]
@@ -47,6 +47,11 @@
               ing, but it will only work properly  with  a  single  column  of
               text.
 
+       -simple2
+              Similar to -simple, but handles slightly rotated text (e.g., OCR
+              output) better.  Only works for pages with a  single  column  of
+              text.
+
        -table Table mode is similar to physical layout mode, but optimized for
               tabular data, with the goal of keeping rows and columns  aligned
               (at  the  expense of inserting extra whitespace).  If the -fixed
@@ -135,12 +140,16 @@
               Read config-file in place of ~/.xpdfrc or the system-wide config
               file.
 
-       -v     Print copyright and version information.
+       -listencodings
+              List all available text output encodings, then exit.
 
-       -h     Print usage information.  (-help and --help are equivalent.)
+       -v     Print copyright and version information, then exit.
 
+       -h     Print  usage  information,  then  exit.   (-help  and --help are
+              equivalent.)
+
 BUGS
-       Some  PDF  files contain fonts whose encodings have been mangled beyond
+       Some PDF files contain fonts whose encodings have been  mangled  beyond
        recognition.  There is no way (short of OCR) to extract text from these
        files.
 
@@ -158,7 +167,7 @@
        99     Other error.
 
 AUTHOR
-       The  pdftotext software and documentation are copyright 1996-2019 Glyph
+       The pdftotext software and documentation are copyright 1996-2021  Glyph
        & Cog, LLC.
 
 SEE ALSO
@@ -168,4 +177,4 @@
 
 
 
-                                  25 Sep 2019                     pdftotext(1)
+                                  28 Jan 2021                     pdftotext(1)

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdf.1
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdf.1	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdf.1	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,7 +1,7 @@
-.\" Copyright 1996-2019 Glyph & Cog, LLC
-.TH xpdf 1 "25 Sep 2019"
+.\" Copyright 1996-2021 Glyph & Cog, LLC
+.TH xpdf 1 "28 Jan 2021"
 .SH NAME
-xpdf \- Portable Document Format (PDF) file viewer (version 4.02)
+xpdf \- Portable Document Format (PDF) file viewer (version 4.03)
 .SH SYNOPSIS
 .B xpdf
 [options]
@@ -118,6 +118,9 @@
 width.
 .RB "[config file: " initialZoom ]
 .TP
+.BI \-rot " angle"
+Rotate pages by 0 (the default), 90, 180, or 270 degrees.
+.TP
 .BI \-aa " yes | no"
 Enable or disable font anti-aliasing.  This defaults to "yes".
 .RB "[config file: " antialias ]
@@ -152,6 +155,10 @@
 Print commands as they're executed (useful for debugging).
 .RB "[config file: " printCommands ]
 .TP
+.BI \-tabstate " tabstate-file"
+Sets the file used by the loadTabState and saveTabState commands.
+.RB "[config file: " tabStateFile ]
+.TP
 .BI \-cfg " config-file"
 Read
 .I config-file
@@ -240,11 +247,19 @@
 attachments.
 .PP
 .SS Text selection
-Dragging the mouse with the left button held down will highlight an
-arbitrary rectangle.  Selected text can be copied to the clipboard
-(with the edit/copy menu item).  On X11, selected text will be
-available in the X selection buffer.
+In block selection mode, dragging the mouse with the left button held
+down will highlight an arbitrary rectangle.  Shift-clicking will
+extend the selection.
 .PP
+In linear selection mode, dragging with the left button will highlight
+text in reading order.  Double-clicking or triple-clicking will select
+a word or a line, respectively.  Shift-clicking will extend the
+selection.
+.PP
+Selected text can be copied to the clipboard (with the edit/copy menu
+item).  On X11, selected text will be available in the X selection
+buffer.
+.PP
 .SS Links
 When the mouse is over a hyperlink, the link target will be shown in a
 popup near the bottom of the window.
@@ -400,6 +415,12 @@
 The bind command allows you to bind a key or mouse button to a
 sequence of one or more commands.
 .PP
+In commands that take arguments (inside parentheses), special
+characters (namely '(', ')', ',', and '\\x01') can be escaped by
+preceding them with a '\\x01' character.  This is mostly useful in
+things like scripts that need to be able to open arbitrary PDF files,
+using the \'openFile' command.
+.PP
 The following commands are supported:
 .TP
 .B about
@@ -452,6 +473,9 @@
 .B copy
 Copy selected text to the clipboard.
 .TP
+.B copyLinkTarget
+Copy the target of the link under the mouse cursor to the clipboard.
+.TP
 .B endPan
 End a pan operation.
 .TP
@@ -626,6 +650,9 @@
 .B quit
 Quit from xpdf.
 .TP
+.B raise
+Bring the xpdf window to the front.
+.TP
 .B reload
 Reload the current PDF file.
 .TP
@@ -751,9 +778,18 @@
 .I n
 pixels, moving to the previous page if appropriate.
 .TP
+.B selectLine
+Set the selection to the line at the current mouse position.
+.TP
+.B selectWord
+Set the selection to the word at the current mouse position.
+.TP
 .BI setSelection( pg , ulx , uly , lrx , lry )
 Set the selection to the specified coordinates on the specified page.
 .TP
+.B showKeyBindings
+Open the key bindings dialog.
+.TP
 .B showToolbar
 Show the toolbar.
 .TP
@@ -772,6 +808,10 @@
 .B singlePageMode
 Switch to single-page view mode.
 .TP
+.B startExtendedSelection
+Extend the selection to the current mouse position, and continue
+extending as the mouse moves.
+.TP
 .B startPan
 Start a pan operation at the current mouse position, which will scroll
 the document as the mouse moves.
@@ -868,7 +908,7 @@
 99
 Other error.
 .SH AUTHOR
-The xpdf software and documentation are copyright 1996-2019 Glyph &
+The xpdf software and documentation are copyright 1996-2021 Glyph &
 Cog, LLC.
 .SH "SEE ALSO"
 .BR pdftops (1),

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdf.cat
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdf.cat	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdf.cat	2021-02-01 06:11:18 UTC (rev 57588)
@@ -3,7 +3,7 @@
 
 
 NAME
-       xpdf - Portable Document Format (PDF) file viewer (version 4.02)
+       xpdf - Portable Document Format (PDF) file viewer (version 4.03)
 
 SYNOPSIS
        xpdf [options] [PDF-file [:page | +dest]] ...
@@ -98,6 +98,9 @@
               fit the page to the window size, or 'width',  to  fit  the  page
               width to the window width.  [config file: initialZoom]
 
+       -rot angle
+              Rotate pages by 0 (the default), 90, 180, or 270 degrees.
+
        -aa yes | no
               Enable  or  disable font anti-aliasing.  This defaults to "yes".
               [config file: antialias]
@@ -130,6 +133,10 @@
        -cmd   Print  commands  as  they're  executed  (useful  for debugging).
               [config file: printCommands]
 
+       -tabstate tabstate-file
+              Sets the file used by the  loadTabState  and  saveTabState  com-
+              mands.  [config file: tabStateFile]
+
        -cfg config-file
               Read config-file in place of ~/.xpdfrc or the system-wide config
               file.
@@ -144,18 +151,18 @@
               Toggles (i.e., shows or hides) the sidebar.
 
        status indicator
-              This  icon is animated while Xpdf is rendering a page.  It turns
-              red when an error or warning has been issued.   Clicking  on  it
+              This icon is animated while Xpdf is rendering a page.  It  turns
+              red  when  an  error or warning has been issued.  Clicking on it
               opens the error dialog.
 
        selection mode
-              This  icon is an "I-beam" in linear selection mode, and an arrow
+              This icon is an "I-beam" in linear selection mode, and an  arrow
               in block selection mode.  Clicking on it toggles between the two
               selection modes.
 
        page number entry box
-              Move  to  a  specific page number.  Click in the box to activate
-              it, type the page number, then hit return.   This  will  instead
+              Move to a specific page number.  Click in the  box  to  activate
+              it,  type  the  page number, then hit return.  This will instead
               display and accept page labels, if the "view - page labels" menu
               item is checked.
 
@@ -166,11 +173,11 @@
               Zoom out or in (i.e., change magnification) incrementally.
 
        zoom popup menu
-              Change the zoom factor (see the description  of  the  -z  option
+              Change  the  zoom  factor  (see the description of the -z option
               above).
 
        fit width button
-              Change  the  zoom  factor  to  fit  the page width to the window
+              Change the zoom factor to fit  the  page  width  to  the  window
               width.
 
        fit page button
@@ -177,7 +184,7 @@
               Change the zoom factor to fit the page to the window size.
 
        find entry box
-              Find a text string.  Click in the box to  activate  it,  type  a
+              Find  a  text  string.   Click in the box to activate it, type a
               search string, then hit return.
 
        find next button
@@ -191,7 +198,7 @@
               whole words (on/off).
 
    Menu bar
-       The menu bar is above the toolbar.  The  menu  items  should  be  self-
+       The  menu  bar  is  above  the toolbar.  The menu items should be self-
        explanatory.
 
    Tab list
@@ -199,33 +206,41 @@
        tabs.
 
    Outline/layers/attachments pane
-       This pane is on the left, below the tab list.  The popup allows you  to
+       This  pane is on the left, below the tab list.  The popup allows you to
        select from outline, layers, or attachments.
 
-       The  outline  is  a tree-like structure of bookmarks that allows moving
+       The outline is a tree-like structure of bookmarks  that  allows  moving
        within the PDF file.  Not all PDF files have outlines.
 
-       Layers (a.k.a. optional content) allow parts of the PDF content  to  be
+       Layers  (a.k.a.  optional content) allow parts of the PDF content to be
        shown or hidden.  Not all PDF files have layers.
 
-       Attachments  are  other files embedded within the PDF file.  There is a
-       'save' button for each attached file.  Not all PDF files  have  attach-
+       Attachments are other files embedded within the PDF file.  There  is  a
+       'save'  button  for each attached file.  Not all PDF files have attach-
        ments.
 
    Text selection
-       Dragging  the  mouse  with  the left button held down will highlight an
-       arbitrary rectangle.  Selected text can  be  copied  to  the  clipboard
-       (with  the  edit/copy menu item).  On X11, selected text will be avail-
-       able in the X selection buffer.
+       In block selection mode, dragging the mouse with the left  button  held
+       down will highlight an arbitrary rectangle.  Shift-clicking will extend
+       the selection.
 
+       In linear selection mode, dragging with the left button will  highlight
+       text  in reading order.  Double-clicking or triple-clicking will select
+       a word or a line, respectively.  Shift-clicking will extend the  selec-
+       tion.
+
+       Selected  text  can be copied to the clipboard (with the edit/copy menu
+       item).  On X11, selected text will be available in the X selection buf-
+       fer.
+
    Links
-       When the mouse is over a hyperlink, the link target will be shown in  a
+       When  the mouse is over a hyperlink, the link target will be shown in a
        popup near the bottom of the window.
 
        Clicking on a hyperlink will jump to the link's destination.  A link to
-       another PDF document will make xpdf load  that  document.   A  'launch'
-       link  to  an executable program will display a dialog, and if you click
-       'ok', execute the program.  URL links are opened in a  system-dependent
+       another  PDF  document  will  make xpdf load that document.  A 'launch'
+       link to an executable program will display a dialog, and if  you  click
+       'ok',  execute the program.  URL links are opened in a system-dependent
        way.  (On UNIX, Qt uses the $BROWSER environment variable.)
 
    Mouse bindings
@@ -235,11 +250,11 @@
 
        Dragging the mouse with the middle button held down pans the window.
 
-       The  right  mouse  button  opens  a  popup  menu  (see  popupMenuCmd in
+       The right  mouse  button  opens  a  popup  menu  (see  popupMenuCmd  in
        xpdfrc(5)).
 
    Key bindings
-       This section lists the default key bindings.  Bindings can  be  changed
+       This  section  lists the default key bindings.  Bindings can be changed
        using the config file (see xpdfrc(5)).
 
        control-o
@@ -246,8 +261,8 @@
               Open a new PDF file via a file requester.
 
        control-r
-              Reload  the  current  PDF  file.  Note that Xpdf will reload the
-              file automatically (on a  page  change  or  redraw)  if  it  has
+              Reload the current PDF file.  Note that  Xpdf  will  reload  the
+              file  automatically  (on  a  page  change  or  redraw) if it has
               changed since it was last loaded.
 
        control-f
@@ -281,8 +296,8 @@
               Open a new window.
 
        control-w
-              Close  the  current tab.  Closes the window if this was the last
-              open tab.  Quits the application if this was the last open  win-
+              Close the current tab.  Closes the window if this was  the  last
+              open  tab.  Quits the application if this was the last open win-
               dow.
 
        control-l
@@ -317,11 +332,11 @@
               Go to the last page.
 
        <space> or <PageDown>
-              Scroll  down  on the current page; if already at bottom, move to
+              Scroll down on the current page; if already at bottom,  move  to
               next page.
 
        control-<PageDown> or control-<down-arrow>
-              Go to the next page.  If <ScrollLock> is active, this  maintains
+              Go  to the next page.  If <ScrollLock> is active, this maintains
               the relative position on the page.
 
        <PageUp>
@@ -329,7 +344,7 @@
               ous page.
 
        control-<PageUp> or control-<up-arrow>
-              Go to the previous page.  If <ScrollLock> is active, this  main-
+              Go  to the previous page.  If <ScrollLock> is active, this main-
               tains the relative position on the page.
 
        <esc>  Exit full-screen mode.
@@ -343,22 +358,28 @@
        w      Set the zoom factor to 'width' (fit page width to window).
 
 Full-screen mode
-       Xpdf  can  be  placed into full-screen mode via the -fullscreen command
-       line option,  the  'full  screen'  menu  item,  or  a  binding  to  the
+       Xpdf can be placed into full-screen mode via  the  -fullscreen  command
+       line  option,  the  'full  screen'  menu  item,  or  a  binding  to the
        fullScreenMode or toggleFullScreenMode command.
 
-       Entering  full-screen  mode  automatically switches to single-page view
+       Entering full-screen mode automatically switches  to  single-page  view
        mode and to the fit-page zoom factor.
 
-       Full-screen mode can be exited via the default <esc>  key  binding,  or
+       Full-screen  mode  can  be exited via the default <esc> key binding, or
        via a binding to the windowMode or toggleFullScreenModecommand.
 
 COMMANDS
        Xpdf's key and mouse bindings are user-configurable, using the bind and
-       unbind commands in the config file (see xpdfrc(5)).  The  bind  command
-       allows  you  to bind a key or mouse button to a sequence of one or more
+       unbind  commands  in the config file (see xpdfrc(5)).  The bind command
+       allows you to bind a key or mouse button to a sequence of one  or  more
        commands.
 
+       In  commands  that take arguments (inside parentheses), special charac-
+       ters (namely '(', ')', ',', and '\x01') can  be  escaped  by  preceding
+       them  with  a  '\x01'  character.  This is mostly useful in things like
+       scripts that need to be able to open arbitrary  PDF  files,  using  the
+       'openFile' command.
+
        The following commands are supported:
 
        about  Open the 'about' dialog.
@@ -372,11 +393,11 @@
               Check that file is open in the current tab, and open it if not.
 
        checkOpenFileAtDest(file,dest)
-              Check that file is open in the current tab, and open it if  not.
+              Check  that file is open in the current tab, and open it if not.
               In either case go to the specified named destination.
 
        checkOpenFileAtPage(file,page)
-              Check  that file is open in the current tab, and open it if not.
+              Check that file is open in the current tab, and open it if  not.
               In either case go to the specified page.
 
        closeSidebar
@@ -403,6 +424,10 @@
 
        copy   Copy selected text to the clipboard.
 
+       copyLinkTarget
+              Copy  the target of the link under the mouse cursor to the clip-
+              board.
+
        endPan End a pan operation.
 
        endSelection
@@ -571,6 +596,8 @@
 
        quit   Quit from xpdf.
 
+       raise  Bring the xpdf window to the front.
+
        reload Reload the current PDF file.
 
        rotateCCW
@@ -674,10 +701,19 @@
               Scroll up by n pixels, moving to the previous page if  appropri-
               ate.
 
+       selectLine
+              Set the selection to the line at the current mouse position.
+
+       selectWord
+              Set the selection to the word at the current mouse position.
+
        setSelection(pg,ulx,uly,lrx,lry)
               Set  the selection to the specified coordinates on the specified
               page.
 
+       showKeyBindings
+              Open the key bindings dialog.
+
        showToolbar
               Show the toolbar.
 
@@ -694,12 +730,16 @@
        singlePageMode
               Switch to single-page view mode.
 
+       startExtendedSelection
+              Extend the selection to the current mouse position, and continue
+              extending as the mouse moves.
+
        startPan
-              Start  a pan operation at the current mouse position, which will
+              Start a pan operation at the current mouse position, which  will
               scroll the document as the mouse moves.
 
        startSelection
-              Start a selection at the current mouse position, which  will  be
+              Start  a  selection at the current mouse position, which will be
               extended as the mouse moves.
 
        toggleContinuousMode
@@ -715,12 +755,12 @@
               Toggle the sidebar between open and closed.
 
        toggleSidebarMoveResizeWin
-              Toggle  the sidebar between open and closed, resizing the window
-              so that the document size doesn't change, and moving the  window
+              Toggle the sidebar between open and closed, resizing the  window
+              so  that the document size doesn't change, and moving the window
               so that the document stays in the same place on the screen.
 
        toggleSidebarResizeWin
-              Toggle  the sidebar between open and closed, resizing the window
+              Toggle the sidebar between open and closed, resizing the  window
               so that the document size doesn't change.
 
        toggleToolbar
@@ -727,7 +767,7 @@
               Toggle the toolbar between shown and hidden.
 
        viewPageLabels
-              Show page labels (if the PDF file has them),  rather  than  page
+              Show  page  labels  (if the PDF file has them), rather than page
               numbers.
 
        viewPageNumbers
@@ -754,19 +794,19 @@
               Zoom to the current selection.
 
 REMOTE SERVER MODE
-       Starting  xpdf  with  the  "-remote"  switch puts it into remote server
-       mode.  All remaining command line options are commands  (see  the  COM-
+       Starting xpdf with the "-remote" switch  puts  it  into  remote  server
+       mode.   All  remaining  command line options are commands (see the COM-
        MANDS section).  Subsequent invocations of "xpdf -remote" with the same
-       remote server name will send commands to the  already-running  instance
-       of  xpdf.  The "checkOpenFile" commands are useful here for things like
+       remote  server  name will send commands to the already-running instance
+       of xpdf.  The "checkOpenFile" commands are useful here for things  like
        changing pages.  For example:
 
                   # Start up xpdf, and open something.pdf.
                   xpdf -remote foo 'openFile(something.pdf)'
 
-                  # Switch to page 7 in the  already-open  something.pdf.   If
+                  #  Switch  to  page 7 in the already-open something.pdf.  If
               the user
-                  #  has closed xpdf in the meantime, this will restart it and
+                  # has closed xpdf in the meantime, this will restart it  and
               reopen
                   # the file.
                   xpdf -remote foo 'checkOpenFileAtPage(something.pdf, 7)'
@@ -785,7 +825,7 @@
        99     Other error.
 
 AUTHOR
-       The xpdf software and documentation are  copyright  1996-2019  Glyph  &
+       The  xpdf  software  and  documentation are copyright 1996-2021 Glyph &
        Cog, LLC.
 
 SEE ALSO
@@ -795,4 +835,4 @@
 
 
 
-                                  25 Sep 2019                          xpdf(1)
+                                  28 Jan 2021                          xpdf(1)

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.5
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.5	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.5	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,7 +1,7 @@
-.\" Copyright 2002-2019 Glyph & Cog, LLC
-.TH xpdfrc 5 "25 Sep 2019"
+.\" Copyright 2002-2021 Glyph & Cog, LLC
+.TH xpdfrc 5 "28 Jan 2021"
 .SH NAME
-xpdfrc \- configuration file for Xpdf tools (version 4.02)
+xpdfrc \- configuration file for Xpdf tools (version 4.03)
 .SH DESCRIPTION
 All of the Xpdf tools read a single configuration file.  If you have a
 .I .xpdfrc
@@ -17,9 +17,26 @@
 per line.  Blank lines and lines starting with a \'#' (comments) are
 ignored.
 .PP
-Arguments may be quoted, using "double-quote" characters, e.g., for
-file names that contain spaces.
+Arguments can be single-quoted or double-quoted, e.g., for file names
+that contain spaces ("aaa bbb", 'aaa bbb').  This quoting does not
+provide any escaping, so there's no way to include a double quote in a
+double-quoted argument or a single quote in a single-quoted argument.
 .PP
+
+Arguments can also be at-quoted: @"aaa bbb".  At-quoted strings allow
+use of the DATADIR variable, which is set to the 'data' subdirectory
+in the xpdf install directory.  The percent sign (%) is an escape
+character: a percent sign followed by any other character is replaced
+with that character.
+.PP
+.RS
+.nf
+@"abc %"def%" ghi" --> abc "def" ghi    
+@"${DATADIR}/foo"  --> ...install-dir.../data/foo
+@"%${DATADIR}/foo" --> ${DATADIR}/foo
+.fi
+.RE
+.PP
 The following sections list all of the configuration options, sorted
 into functional groups.  There is an examples section at the end.
 .SH INCLUDE FILES
@@ -549,6 +566,11 @@
 visible.  If set to "no", xpdf opens with the sidebar collapsed.  The
 default is "yes".
 .TP
+.BI initialSidebarWidth " width"
+Sets the initial sidebar width, in pixels.  This is only relevant if
+initialSidebarState is "yes".  The default value is zero, which tells
+xpdf to use an internal default size.
+.TP
 .BR initialSelectMode " block | linear"
 Sets the initial selection mode.  The default setting is "linear".
 .TP
@@ -567,6 +589,10 @@
 Set the matte color for full-screen mode.  The color can be #RRGGBB
 (hexadecimal) or a named color.
 .TP
+.BI selectionColor " color"
+Set the selection color.  The color can be #RRGGBB (hexadecimal) or a
+named color.
+.TP
 .BI reverseVideoInvertImages " yes | no"
 If set to "no", xpdf's reverse-video mode inverts text and vector
 graphic content, but not images.  If set to "yes", xpdf inverts images
@@ -641,11 +667,13 @@
     end
     pgup
     pgdn
-    left / right / up / down        (arrow keys)
-    f1 .. f35                       (function keys)
-    mousePress1 .. mousePress7      (mouse buttons)
-    mouseRelease1 .. mouseRelease7  (mouse buttons)
-    mouseClick1 .. mouseClick7      (mouse buttons)
+    left / right / up / down                (arrow keys)
+    f1 .. f35                               (function keys)
+    mousePress1 .. mousePress7              (mouse buttons)
+    mouseRelease1 .. mouseRelease7          (mouse buttons)
+    mouseClick1 .. mouseClick7              (mouse buttons)
+    mouseDoubleClick1 .. mouseDoubleClick7  (mouse buttons)
+    mouseTripleClick1 .. mouseTripleClick7  (mouse buttons)
 
 .fi
 .I Context
@@ -708,11 +736,6 @@
 If set to "no", form fields will not be drawn or printed.  The default
 value is "yes".
 .TP
-.BI enableXFA " yes | no"
-If set to "yes", an XFA form (if present) will be rendered in place of
-an AcroForm.  If "no", an XFA form will never be rendered.  This
-defaults to "yes".
-.TP
 .BI printCommands " yes | no"
 If set to "yes", drawing commands are printed as they're executed
 (useful for debugging).  This defaults to "no".
@@ -783,7 +806,7 @@
 This is the user's configuration file.  If it exists, it will be read
 in place of the system-wide file.
 .SH AUTHOR
-The Xpdf software and documentation are copyright 1996-2019 Glyph &
+The Xpdf software and documentation are copyright 1996-2021 Glyph &
 Cog, LLC.
 .SH "SEE ALSO"
 .BR xpdf (1),

Modified: trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.cat
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.cat	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/doc/xpdfrc.cat	2021-02-01 06:11:18 UTC (rev 57588)
@@ -3,7 +3,7 @@
 
 
 NAME
-       xpdfrc - configuration file for Xpdf tools (version 4.02)
+       xpdfrc - configuration file for Xpdf tools (version 4.03)
 
 DESCRIPTION
        All  of the Xpdf tools read a single configuration file.  If you have a
@@ -17,9 +17,21 @@
        line.   Blank  lines  and  lines  starting  with  a  '#' (comments) are
        ignored.
 
-       Arguments may be quoted, using  "double-quote"  characters,  e.g.,  for
-       file names that contain spaces.
+       Arguments can be single-quoted or double-quoted, e.g., for  file  names
+       that contain spaces ("aaa bbb", 'aaa bbb').  This quoting does not pro-
+       vide any escaping, so there's no way to include a  double  quote  in  a
+       double-quoted argument or a single quote in a single-quoted argument.
 
+       Arguments  can  also be at-quoted: @"aaa bbb".  At-quoted strings allow
+       use of the DATADIR variable, which is set to the 'data' subdirectory in
+       the  xpdf install directory.  The percent sign (%) is an escape charac-
+       ter: a percent sign followed by any other character  is  replaced  with
+       that character.
+
+              @"abc %"def%" ghi" --> abc "def" ghi
+              @"${DATADIR}/foo"  --> ...install-dir.../data/foo
+              @"%${DATADIR}/foo" --> ${DATADIR}/foo
+
        The  following  sections  list all of the configuration options, sorted
        into functional groups.  There is an examples section at the end.
 
@@ -507,6 +519,11 @@
               etc.)  visible.  If set to "no", xpdf  opens  with  the  sidebar
               collapsed.  The default is "yes".
 
+       initialSidebarWidth width
+              Sets  the  initial sidebar width, in pixels.  This is only rele-
+              vant if initialSidebarState is  "yes".   The  default  value  is
+              zero, which tells xpdf to use an internal default size.
+
        initialSelectMode block | linear
               Sets  the  initial selection mode.  The default setting is "lin-
               ear".
@@ -526,15 +543,19 @@
               Set  the  matte  color  for  full-screen mode.  The color can be
               #RRGGBB (hexadecimal) or a named color.
 
+       selectionColor color
+              Set the selection color.  The color can be #RRGGBB (hexadecimal)
+              or a named color.
+
        reverseVideoInvertImages yes | no
-              If set to "no", xpdf's reverse-video mode inverts text and  vec-
-              tor  graphic  content,  but  not  images.  If set to "yes", xpdf
+              If  set to "no", xpdf's reverse-video mode inverts text and vec-
+              tor graphic content, but not images.   If  set  to  "yes",  xpdf
               inverts images as well.  The default is "no".
 
        popupMenuCmd title command ...
-              Add a command to the popup menu.  Title is the text to  be  dis-
-              played  in  the  menu.  Command is an Xpdf command (see the COM-
-              MANDS section of the xpdf(1) man page  for  details).   Multiple
+              Add  a  command to the popup menu.  Title is the text to be dis-
+              played in the menu.  Command is an Xpdf command  (see  the  COM-
+              MANDS  section  of  the xpdf(1) man page for details).  Multiple
               commands are separated by whitespace.
 
        maxTileWidth pixels
@@ -542,11 +563,11 @@
               ing pages.  This defaults to 1500.
 
        maxTileHeight pixels
-              Set the maximum height of tiles to be used by xpdf when  raster-
+              Set  the maximum height of tiles to be used by xpdf when raster-
               izing pages.  This defaults to 1500.
 
        tileCacheSize tiles
-              Set  the  maximum number of tiles to be cached by xpdf when ras-
+              Set the maximum number of tiles to be cached by xpdf  when  ras-
               terizing pages.  This defaults to 10.
 
        workerThreads numThreads
@@ -554,16 +575,16 @@
               izing pages.  This defaults to 1.
 
        launchCommand command
-              Sets  the  command  executed  when  you click on a "launch"-type
-              link.  The intent is for the  command  to  be  a  program/script
-              which  determines the file type and runs the appropriate viewer.
-              The command line will consist of the file to be  launched,  fol-
-              lowed  by  any  parameters  specified with the link.  Do not use
-              "%s" in "command".  By default, this is  unset,  and  Xpdf  will
+              Sets the command executed when  you  click  on  a  "launch"-type
+              link.   The  intent  is  for  the command to be a program/script
+              which determines the file type and runs the appropriate  viewer.
+              The  command  line will consist of the file to be launched, fol-
+              lowed by any parameters specified with the  link.   Do  not  use
+              "%s"  in  "command".   By  default, this is unset, and Xpdf will
               simply try to execute the file (after prompting the user).
 
        movieCommand command
-              Sets  the command executed when you click on a movie annotation.
+              Sets the command executed when you click on a movie  annotation.
               The string "%s" will be replaced with the movie file name.  This
               has no default value.
 
@@ -571,7 +592,7 @@
               Sets the default printer used in the viewer's print dialog.
 
        bind modifiers-key context command ...
-              Add  a  key  or  mouse button binding.  Modifiers can be zero or
+              Add a key or mouse button binding.  Modifiers  can  be  zero  or
               more of:
 
                   shift-
@@ -592,11 +613,13 @@
                   end
                   pgup
                   pgdn
-                  left / right / up / down        (arrow keys)
-                  f1 .. f35                       (function keys)
-                  mousePress1 .. mousePress7      (mouse buttons)
-                  mouseRelease1 .. mouseRelease7  (mouse buttons)
-                  mouseClick1 .. mouseClick7      (mouse buttons)
+                  left / right / up / down                (arrow keys)
+                  f1 .. f35                               (function keys)
+                  mousePress1 .. mousePress7              (mouse buttons)
+                  mouseRelease1 .. mouseRelease7          (mouse buttons)
+                  mouseClick1 .. mouseClick7              (mouse buttons)
+                  mouseDoubleClick1 .. mouseDoubleClick7  (mouse buttons)
+                  mouseTripleClick1 .. mouseTripleClick7  (mouse buttons)
 
               Context is either "any" or a comma-separated combination of:
 
@@ -605,14 +628,14 @@
                   overLink / offLink        (mouse over link or not)
                   scrLockOn / scrLockOff    (scroll lock on/off)
 
-              The context string can include only one  of  each  pair  in  the
+              The  context  string  can  include  only one of each pair in the
               above list.
 
-              Command  is  an  Xpdf  command  (see the COMMANDS section of the
-              xpdf(1) man page for details).  Multiple commands are  separated
+              Command is an Xpdf command (see  the  COMMANDS  section  of  the
+              xpdf(1)  man page for details).  Multiple commands are separated
               by whitespace.
 
-              The  bind  command replaces any existing binding, but only if it
+              The bind command replaces any existing binding, but only  if  it
               was defined for the exact same modifiers, key, and context.  All
               tokens (modifiers, key, context, commands) are case-sensitive.
 
@@ -630,10 +653,10 @@
               See the xpdf(1) man page for more examples.
 
        unbind modifiers-key context
-              Removes  a  key binding established with the bind command.  This
-              is most useful to remove default key bindings before  establish-
-              ing  new  ones  (e.g.,  if  the default key binding is given for
-              "any" context, and you want to create new key bindings for  mul-
+              Removes a key binding established with the bind  command.   This
+              is  most useful to remove default key bindings before establish-
+              ing new ones (e.g., if the default  key  binding  is  given  for
+              "any"  context, and you want to create new key bindings for mul-
               tiple contexts).
 
        tabStateFile path
@@ -642,24 +665,19 @@
 
 MISCELLANEOUS SETTINGS
        drawAnnotations yes | no
-              If set to "no", annotations will not be drawn or  printed.   The
+              If  set  to "no", annotations will not be drawn or printed.  The
               default value is "yes".
 
        drawFormFields yes | no
-              If  set  to "no", form fields will not be drawn or printed.  The
+              If set to "no", form fields will not be drawn or  printed.   The
               default value is "yes".
 
-       enableXFA yes | no
-              If set to "yes", an XFA form (if present) will  be  rendered  in
-              place  of  an AcroForm.  If "no", an XFA form will never be ren-
-              dered.  This defaults to "yes".
-
        printCommands yes | no
-              If set to "yes", drawing commands are printed  as  they're  exe-
+              If  set  to  "yes", drawing commands are printed as they're exe-
               cuted (useful for debugging).  This defaults to "no".
 
        errQuiet yes | no
-              If  set to "yes", this suppresses all error and warning messages
+              If set to "yes", this suppresses all error and warning  messages
               from all of the Xpdf tools.  This defaults to "no".
 
 EXAMPLES
@@ -716,7 +734,7 @@
 
 FILES
        /usr/local/etc/xpdfrc
-              This is the default location for the  system-wide  configuration
+              This  is  the default location for the system-wide configuration
               file.  Depending on build options, it may be placed elsewhere.
 
        $HOME/.xpdfrc
@@ -724,14 +742,14 @@
               read in place of the system-wide file.
 
 AUTHOR
-       The Xpdf software and documentation are  copyright  1996-2019  Glyph  &
+       The  Xpdf  software  and  documentation are copyright 1996-2021 Glyph &
        Cog, LLC.
 
 SEE ALSO
-       xpdf(1),   pdftops(1),  pdftotext(1),  pdftohtml(1),  pdfinfo(1),  pdf-
+       xpdf(1),  pdftops(1),  pdftotext(1),  pdftohtml(1),  pdfinfo(1),   pdf-
        fonts(1), pdfdetach(1), pdftoppm(1), pdftopng(1), pdfimages(1)
        http://www.xpdfreader.com/
 
 
 
-                                  25 Sep 2019                        xpdfrc(5)
+                                  28 Jan 2021                        xpdfrc(5)

Modified: trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiIdentifier.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiIdentifier.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiIdentifier.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -561,7 +561,7 @@
 
 static FoFiIdentifierType identifyCFF(Reader *reader, int start) {
   Guint offset0, offset1;
-  int hdrSize, offSize0, offSize1, pos, endPos, b0, n, i;
+  int hdrSize, offSize0, offSize1, pos, endPos, b0, n;
 
   //----- read the header
   if (reader->getByte(start) != 0x01 ||
@@ -622,20 +622,19 @@
   //----- parse the top dict, look for ROS as first entry
   // for a CID font, the top dict starts with:
   //     <int> <int> <int> ROS
-  for (i = 0; i < 3; ++i) {
-    b0 = reader->getByte(pos++);
+  while (pos >= 0 && pos < endPos) {
+    b0 = reader->getByte(pos);
     if (b0 == 0x1c) {
-      pos += 2;
+      pos += 3;
     } else if (b0 == 0x1d) {
-      pos += 4;
+      pos += 5;
     } else if (b0 >= 0xf7 && b0 <= 0xfe) {
+      pos += 2;
+    } else if (b0 >= 0x20 && b0 <= 0xf6) {
       pos += 1;
-    } else if (b0 < 0x20 || b0 > 0xf6) {
-      return fofiIdCFF8Bit;
+    } else {
+      break;
     }
-    if (pos >= endPos || pos < 0) {
-      return fofiIdCFF8Bit;
-    }
   }
   if (pos + 1 < endPos &&
       reader->getByte(pos) == 12 &&

Modified: trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiTrueType.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiTrueType.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiTrueType.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1730,7 +1730,7 @@
     if (encoding) {
       name = encoding[i];
     } else {
-      sprintf(buf2, "c%02x", i);
+      snprintf(buf2, sizeof(buf2), "c%02x", i);
       name = buf2;
     }
     if (name && strcmp(name, ".notdef")) {
@@ -2143,7 +2143,7 @@
 
 void FoFiTrueType::parse(int fontNum, GBool allowHeadlessCFF) {
   Guint topTag;
-  int offset, pos, ver, i, j;
+  int offset, pos, ver, i, j, k;
 
   parsedOk = gTrue;
 
@@ -2206,7 +2206,7 @@
   // check for the head table; allow for a head-less OpenType CFF font
   headlessCFF = gFalse;
   if (seekTable("head") < 0) {
-    if (openTypeCFF && allowHeadlessCFF) {
+    if (openTypeCFF && allowHeadlessCFF && seekTable("CFF ") >= 0) {
       headlessCFF = gTrue;
       nGlyphs = 0;
       bbox[0] = bbox[1] = bbox[2] = bbox[3] = 0;
@@ -2238,14 +2238,22 @@
       return;
     }
     cmaps = (TrueTypeCmap *)gmallocn(nCmaps, sizeof(TrueTypeCmap));
+    k = 0;
     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);
+      cmaps[k].platform = getU16BE(pos, &parsedOk);
+      cmaps[k].encoding = getU16BE(pos + 2, &parsedOk);
+      cmaps[k].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 (cmaps[k].offset >= tables[i].len) {
+	// skip any invalid subtables
+	continue;
+      }
+      cmaps[k].offset += tables[i].offset;
+      cmaps[k].fmt = getU16BE(cmaps[k].offset, &parsedOk);
+      cmaps[k].len = getU16BE(cmaps[k].offset + 2, &parsedOk);
+      ++k;
     }
+    nCmaps = k;
     if (!parsedOk) {
       return;
     }
@@ -2273,7 +2281,7 @@
   // NB: out-of-bounds entries are handled in writeTTF()
   if (!openTypeCFF) {
     i = seekTable("loca");
-    if (tables[i].len < 0) {
+    if (tables[i].len < (locaFmt ? 4 : 2)) {
       parsedOk = gFalse;
       return;
     }

Modified: trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -114,7 +114,7 @@
 		"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]);
+      snprintf(buf, sizeof(buf), "dup %d /%s put\n", i, newEncoding[i]);
       (*outputFunc)(outputStream, buf, (int)strlen(buf));
     }
   }

Modified: trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -26,6 +26,8 @@
 
 static char hexChars[17] = "0123456789ABCDEF";
 
+#define type1cSubrRecursionLimit 20
+
 //------------------------------------------------------------------------
 
 GBool Type1COp::isZero() {
@@ -39,28 +41,52 @@
 
 GBool Type1COp::isNegative() {
   switch (kind) {
-  case type1COpInteger:  return intgr < 0;
-  case type1COpFloat:    return flt < 0;
-  case type1COpRational: return (rat.num < 0) != (rat.den < 0);
-  default:               return gFalse;   // shouldn't happen
+  case type1COpInteger:
+    return intgr < 0;
+  case type1COpFloat:
+    return flt < 0;
+  case type1COpRational:
+    return (rat.num < 0) != (rat.den < 0);
+  default:
+    // shouldn't happen
+    return gFalse;
   }
 }
 
 int Type1COp::toInt() {
   switch (kind) {
-  case type1COpInteger:  return intgr;
-  case type1COpFloat:    return (int)flt;
-  case type1COpRational: return rat.num / rat.den;
-  default:               return 0;   // shouldn't happen
+  case type1COpInteger:
+    return intgr;
+  case type1COpFloat:
+    if (flt < -2e9 || flt > 2e9) {
+      return 0;
+    }
+    return (int)flt;
+  case type1COpRational:
+    if (rat.den == 0) {
+      return 0;
+    }
+    return rat.num / rat.den;
+  default:
+    // shouldn't happen
+    return 0;
   }
 }
 
 double Type1COp::toFloat() {
   switch (kind) {
-  case type1COpInteger:  return (double)intgr;
-  case type1COpFloat:    return flt;
-  case type1COpRational: return (double)rat.num / (double)rat.den;
-  default:               return 0.0;   // shouldn't happen
+  case type1COpInteger:
+    return (double)intgr;
+  case type1COpFloat:
+    return flt;
+  case type1COpRational:
+    if (rat.den == 0) {
+      return 0;
+    }
+    return (double)rat.num / (double)rat.den;
+  default:
+    // shouldn't happen
+    return 0.0;
   }
 }
 
@@ -607,7 +633,8 @@
 	  subrIdx.pos = -1;
 	}
 	cvtGlyph(val.pos, val.len, charStrings,
-		 &subrIdx, &privateDicts[fdSelect ? fdSelect[gid] : 0], gTrue);
+		 &subrIdx, &privateDicts[fdSelect ? fdSelect[gid] : 0],
+		 gTrue, 0);
       }
     }
   }
@@ -969,6 +996,10 @@
 	}
       }
     }
+    if (fd < 0 || fd >= nFDs) {
+      // this will only happen in a broken/damaged font
+      fd = 0;
+    }
 
     // font dictionary (unencrypted section)
     (*outputFunc)(outputStream, "16 dict begin\n", 14);
@@ -1269,7 +1300,7 @@
 
   // generate the charstring
   charBuf = new GString();
-  cvtGlyph(offset, nBytes, charBuf, subrIdx, pDict, gTrue);
+  cvtGlyph(offset, nBytes, charBuf, subrIdx, pDict, gTrue, 0);
 
   buf = GString::format("/{0:s} {1:d} RD ", glyphName, charBuf->getLength());
   eexecWrite(eb, buf->getCString());
@@ -1283,7 +1314,7 @@
 
 void FoFiType1C::cvtGlyph(int offset, int nBytes, GString *charBuf,
 			  Type1CIndex *subrIdx, Type1CPrivateDict *pDict,
-			  GBool top) {
+			  GBool top, int recursion) {
   Type1CIndexVal val;
   Type1COp zero, tmp;
   GBool ok, dInt;
@@ -1292,6 +1323,11 @@
   Guchar byte;
   int pos, subrBias, start, num, den, i, k;
 
+  if (recursion > type1cSubrRecursionLimit) {
+    //~ error(-1, "Recursive loop in Type1C glyph");
+    return;
+  }
+
   start = charBuf->getLength();
   if (top) {
     charBuf->append((char)73);
@@ -1504,7 +1540,8 @@
 	  ok = gTrue;
 	  getIndexVal(subrIdx, k, &val, &ok);
 	  if (ok) {
-	    cvtGlyph(val.pos, val.len, charBuf, subrIdx, pDict, gFalse);
+	    cvtGlyph(val.pos, val.len, charBuf, subrIdx, pDict, gFalse,
+		     recursion + 1);
 	  }
 	} else {
 	  //~ error(-1, "Too few args to Type 2 callsubr");
@@ -1739,7 +1776,8 @@
 	  ok = gTrue;
 	  getIndexVal(&gsubrIdx, k, &val, &ok);
 	  if (ok) {
-	    cvtGlyph(val.pos, val.len, charBuf, subrIdx, pDict, gFalse);
+	    cvtGlyph(val.pos, val.len, charBuf, subrIdx, pDict, gFalse,
+		     recursion + 1);
 	  }
 	} else {
 	  //~ error(-1, "Too few args to Type 2 callgsubr");
@@ -2945,11 +2983,10 @@
   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 {
+  for (i = 0; i < nGlyphs; ++i) {
+    fdSelect[i] = 0;
+  }
+  if (topDict.fdSelectOffset != 0) {
     pos = topDict.fdSelectOffset;
     fdSelectFmt = getU8(pos++, &parsedOk);
     if (!parsedOk) {
@@ -2992,9 +3029,6 @@
       }
     } else {
       //~ error(-1, "Unknown FDSelect table format in CID font");
-      for (i = 0; i < nGlyphs; ++i) {
-	fdSelect[i] = 0;
-      }
     }
   }
 }

Modified: trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/fofi/FoFiType1C.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -229,7 +229,7 @@
 		     Type1CPrivateDict *pDict);
   void cvtGlyph(int offset, int nBytes, GString *charBuf,
 		Type1CIndex *subrIdx, Type1CPrivateDict *pDict,
-		GBool top);
+		GBool top, int recursion);
   void cvtGlyphWidth(GBool useOp, GString *charBuf,
 		     Type1CPrivateDict *pDict);
   void cvtNum(Type1COp op, GString *charBuf);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/goo/CMakeLists.txt
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/goo/CMakeLists.txt	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/goo/CMakeLists.txt	2021-02-01 06:11:18 UTC (rev 57588)
@@ -20,6 +20,7 @@
   gmem.cc
   gmempp.cc
   parseargs.c
+  Trace.cc
 )
 
 add_library(goo

Modified: trunk/Build/source/libs/xpdf/xpdf-src/goo/FixedPoint.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/goo/FixedPoint.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/goo/FixedPoint.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -36,6 +36,7 @@
   FixedPoint(const FixedPoint &x) { val = x.val; }
   FixedPoint(double x) { val = (int)(x * (1 << fixptShift) + 0.5); }
   FixedPoint(int x) { val = x << fixptShift; }
+  FixedPoint(Guint x) { val = x << fixptShift; }
   FixedPoint(long x) { val = (int)x << fixptShift; }
 
   operator float()
@@ -52,31 +53,37 @@
   bool operator ==(FixedPoint x) const { return val == x.val; }
   bool operator ==(double x) const { return *this == (FixedPoint)x; }
   bool operator ==(int x) const { return *this == (FixedPoint)x; }
+  bool operator ==(Guint x) const { return *this == (FixedPoint)x; }
   bool operator ==(long x) const { return *this == (FixedPoint)x; }
 
   bool operator !=(FixedPoint x) const { return val != x.val; }
   bool operator !=(double x) const { return *this != (FixedPoint)x; }
   bool operator !=(int x) const { return *this != (FixedPoint)x; }
+  bool operator !=(Guint x) const { return *this != (FixedPoint)x; }
   bool operator !=(long x) const { return *this != (FixedPoint)x; }
 
   bool operator <(FixedPoint x) const { return val < x.val; }
   bool operator <(double x) const { return *this < (FixedPoint)x; }
   bool operator <(int x) const { return *this < (FixedPoint)x; }
+  bool operator <(Guint x) const { return *this < (FixedPoint)x; }
   bool operator <(long x) const { return *this < (FixedPoint)x; }
 
   bool operator <=(FixedPoint x) const { return val <= x.val; }
   bool operator <=(double x) const { return *this <= (FixedPoint)x; }
   bool operator <=(int x) const { return *this <= (FixedPoint)x; }
+  bool operator <=(Guint x) const { return *this <= (FixedPoint)x; }
   bool operator <=(long x) const { return *this <= (FixedPoint)x; }
 
   bool operator >(FixedPoint x) const { return val > x.val; }
   bool operator >(double x) const { return *this > (FixedPoint)x; }
   bool operator >(int x) const { return *this > (FixedPoint)x; }
+  bool operator >(Guint x) const { return *this > (FixedPoint)x; }
   bool operator >(long x) const { return *this > (FixedPoint)x; }
 
   bool operator >=(FixedPoint x) const { return val >= x.val; }
   bool operator >=(double x) const { return *this >= (FixedPoint)x; }
   bool operator >=(int x) const { return *this >= (FixedPoint)x; }
+  bool operator >=(Guint x) const { return *this >= (FixedPoint)x; }
   bool operator >=(long x) const { return *this >= (FixedPoint)x; }
 
   FixedPoint operator -() { return make(-val); }
@@ -84,41 +91,49 @@
   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 +(Guint 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 +=(Guint 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 -(Guint 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 -=(Guint 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 *(Guint 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 *=(Guint 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 /(Guint 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 /=(Guint x) { return *this /= (FixedPoint)x; }
   FixedPoint operator /=(long x) { return *this /= (FixedPoint)x; }
 
   static FixedPoint abs(FixedPoint x) { return make(::abs(x.val)); }

Added: trunk/Build/source/libs/xpdf/xpdf-src/goo/Trace.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/goo/Trace.cc	                        (rev 0)
+++ trunk/Build/source/libs/xpdf/xpdf-src/goo/Trace.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -0,0 +1,114 @@
+//========================================================================
+//
+// Trace.cc
+//
+// Nested tracing.
+//
+// Copyright 2020 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#if ENABLE_TRACING
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include "GString.h"
+#include "Trace.h"
+
+// NB: This module is NOT thread-safe.
+
+static bool traceInitialized = false;
+static FILE *traceOut = NULL;
+
+static void traceInit() {
+  if (traceInitialized) {
+    return;
+  }
+  //~ this could read an env var to set up an output file
+  GString *fileName = GString::format("/tmp/trace.{0:d}", (int)getpid());
+  traceOut = fopen(fileName->getCString(), "w");
+  delete fileName;
+  traceInitialized = true;
+}
+
+static void traceHeader(char flag, void *handle) {
+  timeval tv;
+  gettimeofday(&tv, NULL);
+  if (handle) {
+    fprintf(traceOut, "%c %ld %06ld %p ", flag, tv.tv_sec, tv.tv_usec, handle);
+  } else {
+    fprintf(traceOut, "%c %ld %06ld 0x0 ", flag, tv.tv_sec, tv.tv_usec);
+  }
+}
+
+void traceBegin(void *nestHandle, const char *fmt, ...) {
+  traceInit();
+  if (!traceOut) {
+    return;
+  }
+  traceHeader('B', nestHandle);
+  va_list args;
+  va_start(args, fmt);
+  vfprintf(traceOut, fmt, args);
+  va_end(args);
+  fprintf(traceOut, "\n");
+}
+
+void traceEnd(void *nestHandle, const char *fmt, ...) {
+  traceInit();
+  if (!traceOut) {
+    return;
+  }
+  traceHeader('E', nestHandle);
+  va_list args;
+  va_start(args, fmt);
+  vfprintf(traceOut, fmt, args);
+  va_end(args);
+  fprintf(traceOut, "\n");
+}
+
+void traceAlloc(void *resourceHandle, const char *fmt, ...) {
+  traceInit();
+  if (!traceOut) {
+    return;
+  }
+  traceHeader('A', resourceHandle);
+  va_list args;
+  va_start(args, fmt);
+  vfprintf(traceOut, fmt, args);
+  va_end(args);
+  fprintf(traceOut, "\n");
+}
+
+void traceFree(void *resourceHandle, const char *fmt, ...) {
+  traceInit();
+  if (!traceOut) {
+    return;
+  }
+  traceHeader('F', resourceHandle);
+  va_list args;
+  va_start(args, fmt);
+  vfprintf(traceOut, fmt, args);
+  va_end(args);
+  fprintf(traceOut, "\n");
+}
+
+void traceMessage(const char *fmt, ...) {
+  traceInit();
+  if (!traceOut) {
+    return;
+  }
+  traceHeader('M', NULL);
+  va_list args;
+  va_start(args, fmt);
+  vfprintf(traceOut, fmt, args);
+  va_end(args);
+  fprintf(traceOut, "\n");
+}
+
+#endif // ENABLE_TRACING

Added: trunk/Build/source/libs/xpdf/xpdf-src/goo/Trace.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/goo/Trace.h	                        (rev 0)
+++ trunk/Build/source/libs/xpdf/xpdf-src/goo/Trace.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -0,0 +1,43 @@
+//========================================================================
+//
+// Trace.h
+//
+// Nested tracing.
+//
+// Copyright 2020 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef TRACING_H
+#define TRACING_H
+
+#include <aconf.h>
+
+#if ENABLE_TRACING
+
+// Enter a nesting level.
+extern void traceBegin(void *nestHandle, const char *fmt, ...);
+
+// Exit a nesting level.
+extern void traceEnd(void *nestHandle, const char *fmt, ...);
+
+// Mark a resource as allocated.
+extern void traceAlloc(void *resourceHandle, const char *fmt, ...);
+
+// Mark a resource as freed.
+extern void traceFree(void *resourceHandle, const char *fmt, ...);
+
+// Misc message.
+extern void traceMessage(const char *fmt, ...);
+
+#else // ENABLE_TRACING
+
+static inline void traceBegin(void *nestHandle, const char *fmt, ...) {}
+static inline void traceEnd(void *nestHandle, const char *fmt, ...) {}
+static inline void traceAlloc(void *resourceHandle, const char *fmt, ...) {}
+static inline void traceFree(void *nestHandle, const char *fmt, ...) {}
+static inline void traceMessage(const char *fmt, ...) {}
+
+#endif // ENABLE_TRACING
+
+#endif // TRACING_H

Modified: trunk/Build/source/libs/xpdf/xpdf-src/goo/gfile.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/goo/gfile.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/goo/gfile.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -18,6 +18,8 @@
 #  include <windows.h>
 #  include <time.h>
 #  include <direct.h>
+#  include <shobjidl.h>
+#  include <shlguid.h>
 #else
 #  if !defined(ACORN)
 #    include <sys/types.h>
@@ -579,6 +581,32 @@
   }
   return s;
 }
+
+wchar_t *fileNameToUCS2(const char *path, wchar_t *out, size_t outSize) {
+  const char *p;
+  size_t i;
+
+  for (p = path, i = 0; *p && i < outSize - 1; ++i) {
+    if ((p[0] & 0xe0) == 0xc0 &&
+	p[1] && (p[1] & 0xc0) == 0x80) {
+      out[i] = (wchar_t)(((p[0] & 0x1f) << 6) |
+			  (p[1] & 0x3f));
+      p += 2;
+    } else if ((p[0] & 0xf0) == 0xe0 &&
+	       (p[1] & 0xc0) == 0x80 &&
+	       (p[2] & 0xc0) == 0x80) {
+      out[i] = (wchar_t)(((p[0] & 0x0f) << 12) |
+			 ((p[1] & 0x3f) << 6) |
+			  (p[2] & 0x3f));
+      p += 3;
+    } else {
+      out[i] = (wchar_t)(p[0] & 0xff);
+      p += 1;
+    }
+  }
+  out[i] = (wchar_t)0;
+  return out;
+}
 #endif
 
 FILE *openFile(const char *path, const char *mode) {
@@ -585,62 +613,17 @@
 #if defined(_WIN32)
   return fopen(path, mode);
 #if 0
-  OSVERSIONINFO version;
   wchar_t wPath[_MAX_PATH + 1];
-  char nPath[_MAX_PATH + 1];
   wchar_t wMode[8];
-  const char *p;
   int i;
 
-  // NB: _wfopen is only available in NT
-  version.dwOSVersionInfoSize = sizeof(version);
-  GetVersionEx(&version);
-  if (version.dwPlatformId == VER_PLATFORM_WIN32_NT) {
-    for (p = path, i = 0; *p && i < _MAX_PATH; ++i) {
-      if ((p[0] & 0xe0) == 0xc0 &&
-	  p[1] && (p[1] & 0xc0) == 0x80) {
-	wPath[i] = (wchar_t)(((p[0] & 0x1f) << 6) |
-			     (p[1] & 0x3f));
-	p += 2;
-      } else if ((p[0] & 0xf0) == 0xe0 &&
-		 p[1] && (p[1] & 0xc0) == 0x80 &&
-		 p[2] && (p[2] & 0xc0) == 0x80) {
-	wPath[i] = (wchar_t)(((p[0] & 0x0f) << 12) |
-			     ((p[1] & 0x3f) << 6) |
-			     (p[2] & 0x3f));
-	p += 3;
-      } else {
-	wPath[i] = (wchar_t)(p[0] & 0xff);
-	p += 1;
-      }
-    }
-    wPath[i] = (wchar_t)0;
-    for (i = 0; mode[i] && i < sizeof(wMode)/sizeof(wchar_t) - 1; ++i) {
-      wMode[i] = (wchar_t)(mode[i] & 0xff);
-    }
-    wMode[i] = (wchar_t)0;
-    return _wfopen(wPath, wMode);
-  } else {
-    for (p = path, i = 0; *p && i < _MAX_PATH; ++i) {
-      if ((p[0] & 0xe0) == 0xc0 &&
-	  p[1] && (p[1] & 0xc0) == 0x80) {
-	nPath[i] = (char)(((p[0] & 0x1f) << 6) |
-			  (p[1] & 0x3f));
-	p += 2;
-      } else if ((p[0] & 0xf0) == 0xe0 &&
-		 p[1] && (p[1] & 0xc0) == 0x80 &&
-		 p[2] && (p[2] & 0xc0) == 0x80) {
-	nPath[i] = (char)(((p[1] & 0x3f) << 6) |
-			  (p[2] & 0x3f));
-	p += 3;
-      } else {
-	nPath[i] = p[0];
-	p += 1;
-      }
-    }
-    nPath[i] = '\0';
-    return fopen(nPath, mode);
+  fileNameToUCS2(path, wPath, sizeof(wPath) / sizeof(wchar_t));
+  for (i = 0; mode[i] && i < sizeof(wMode)/sizeof(wchar_t) - 1; ++i) {
+    wMode[i] = (wchar_t)(mode[i] & 0xff);
   }
+  wMode[i] = (wchar_t)0;
+  readWindowsShortcut(wPath, _MAX_PATH + 1);
+  return _wfopen(wPath, wMode);
 #endif /* 0 */
 #elif defined(VMS)
   return fopen(path, mode, "ctx=stm");
@@ -649,6 +632,62 @@
 #endif
 }
 
+#if 0
+#ifdef _WIN32
+void readWindowsShortcut(wchar_t *wPath, size_t wPathSize) {
+  size_t n = wcslen(wPath);
+  if (n < 4 || wcscmp(wPath + n - 4, L".lnk")) {
+    return;
+  }
+  IShellLinkW *shellLink;
+  HRESULT hres;
+  hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
+			  IID_IShellLinkW, (LPVOID *)&shellLink);
+  bool needCoUninit = false;
+  if (hres == CO_E_NOTINITIALIZED) {
+    CoInitialize(NULL);
+    needCoUninit = true;
+    hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
+			    IID_IShellLinkW, (LPVOID *)&shellLink);
+  }
+  if (FAILED(hres)) {
+    return;
+  }
+  IPersistFile *persistFile;
+  hres = shellLink->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile);
+  if (FAILED(hres)) {
+    return;
+  }
+  hres = persistFile->Load(wPath, STGM_READ);
+  if (FAILED(hres)) {
+    fprintf(stderr, "IPersistFile.Load failed: 0x%08x\n", hres);
+    exit(1);
+  }
+  wchar_t target[_MAX_PATH + 1];
+  hres = shellLink->GetPath(target, _MAX_PATH + 1, NULL, 0);
+  if (FAILED(hres)) {
+    return;
+  }
+  shellLink->Release();
+  if (needCoUninit) {
+    CoUninitialize();
+  }
+  if (wcslen(target) > wPathSize - 1) {
+    return;
+  }
+  wcscpy(wPath, target);
+}
+#endif
+#endif /* 0 */
+
+int makeDir(const char *path, int mode) {
+#ifdef _WIN32
+  return _mkdir(path);
+#else
+  return mkdir(path, (mode_t)mode);
+#endif
+}
+
 char *getLine(char *buf, int size, FILE *f) {
   int c, i;
 

Modified: trunk/Build/source/libs/xpdf/xpdf-src/goo/gfile.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/goo/gfile.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/goo/gfile.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -80,13 +80,22 @@
 
 // Convert a file name from UCS-2 to UTF-8.
 extern GString *fileNameToUTF8(wchar_t *path);
+
+// Convert a file name from UTF-8 to UCS-2.  [out] has space for
+// [outSize] wchar_t elements (including the trailing zero).  Returns
+// [out].
+extern wchar_t *fileNameToUCS2(const char *path, wchar_t *out, size_t outSize);
 #endif
 
 // Open a file.  On Windows, this converts the path from UTF-8 to
-// UCS-2 and calls _wfopen (if available).  On other OSes, this simply
-// calls fopen.
+// UCS-2 and calls _wfopen().  On other OSes, this simply calls fopen().
 extern FILE *openFile(const char *path, const char *mode);
 
+// Create a directory.  On Windows, this converts the path from UTF-8
+// to UCS-2 and calls _wmkdir(), ignoring the mode argument.  On other
+// OSes, this simply calls mkdir().
+extern int makeDir(const char *path, int mode);
+
 // Just like fgets, but handles Unix, Mac, and/or DOS end-of-line
 // conventions.
 extern char *getLine(char *buf, int size, FILE *f);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/goo/gmem.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/goo/gmem.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/goo/gmem.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -32,10 +32,6 @@
 
 #endif // USE_EXCEPTIONS
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
 /*
  * Same as malloc, but prints error message and exits if malloc()
  * returns NULL.
@@ -96,8 +92,4 @@
  */
 extern char *copyString(const char *s);
 
-#ifdef __cplusplus
-}
 #endif
-
-#endif

Modified: trunk/Build/source/libs/xpdf/xpdf-src/splash/Splash.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/splash/Splash.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/splash/Splash.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -2,7 +2,7 @@
 //
 // Splash.cc
 //
-// Copyright 2003-2013 Glyph & Cog, LLC
+// Copyright 2003-2020 Glyph & Cog, LLC
 //
 //========================================================================
 
@@ -80,6 +80,12 @@
   Guchar aInput;
   SplashColor cSrcVal;
 
+  // source overprint mask
+  //~ this is a kludge - this pointer should be passed as an arg to the
+  //~   pipeRun function, but that would require passing in a lot of
+  //~   null pointers, since it's rarely used
+  Guint *srcOverprintMaskPtr;
+
   // special cases and result color
   GBool noTransparency;
   GBool shapeOnly;
@@ -163,7 +169,7 @@
 
 inline void Splash::pipeInit(SplashPipe *pipe, SplashPattern *pattern,
 			     Guchar aInput, GBool usesShape,
-			     GBool nonIsolatedGroup) {
+			     GBool nonIsolatedGroup, GBool usesSrcOverprint) {
   SplashColorMode mode;
 
   mode = bitmap->mode;
@@ -181,6 +187,9 @@
   // source alpha
   pipe->aInput = aInput;
 
+  // source overprint mask
+  pipe->srcOverprintMaskPtr = NULL;
+
   // special cases
   pipe->noTransparency = aInput == 255 &&
                          !state->softMask &&
@@ -212,7 +221,9 @@
 
   // select the 'run' function
   pipe->run = &Splash::pipeRun;
-  if (!pipe->pattern && pipe->noTransparency && !state->blendFunc) {
+  if (overprintMaskBitmap || usesSrcOverprint) {
+    // use Splash::pipeRun
+  } else if (!pipe->pattern && pipe->noTransparency && !state->blendFunc) {
     if (mode == splashModeMono1 && !bitmap->alpha) {
       pipe->run = &Splash::pipeRunSimpleMono1;
     } else if (mode == splashModeMono8 && bitmap->alpha) {
@@ -239,6 +250,9 @@
     } else if (mode == splashModeCMYK8 && bitmap->alpha) {
       pipe->run = &Splash::pipeRunShapeCMYK8;
 #endif
+    } else if (mode == splashModeMono8 && !bitmap->alpha) {
+      // this is used when drawing soft-masked images
+      pipe->run = &Splash::pipeRunShapeNoAlphaMono8;
     }
   } else if (!pipe->pattern && !pipe->noTransparency && !state->softMask &&
 	     usesShape &&
@@ -258,6 +272,26 @@
       pipe->run = &Splash::pipeRunAACMYK8;
 #endif
     }
+  } else if (!pipe->pattern &&
+             aInput == 255 &&
+             state->softMask &&
+             usesShape &&
+             !state->inNonIsolatedGroup &&
+             !state->inKnockoutGroup &&
+             !nonIsolatedGroup &&
+             state->overprintMask == 0xffffffff &&
+             !state->blendFunc) {
+    if (mode == splashModeMono8 && bitmap->alpha) {
+      pipe->run = &Splash::pipeRunSoftMaskMono8;
+    } else if (mode == splashModeRGB8 && bitmap->alpha) {
+      pipe->run = &Splash::pipeRunSoftMaskRGB8;
+    } else if (mode == splashModeBGR8 && bitmap->alpha) {
+      pipe->run = &Splash::pipeRunSoftMaskBGR8;
+#if SPLASH_CMYK
+    } else if (mode == splashModeCMYK8 && bitmap->alpha) {
+      pipe->run = &Splash::pipeRunSoftMaskCMYK8;
+#endif
+    }
   } else if (!pipe->pattern && !pipe->noTransparency && !state->softMask &&
 	     usesShape &&
 	     state->inNonIsolatedGroup && groupBackBitmap->alpha &&
@@ -292,6 +326,8 @@
   Guchar color0Mask;
   Guchar *alpha0Ptr;
   SplashColorPtr softMaskPtr;
+  Guint overprintMask;
+  Guint *overprintMaskPtr;
 #if SPLASH_CMYK
   Guchar aPrev;
   SplashColor cSrc2, cDest2;
@@ -313,6 +349,9 @@
       }
       cSrcPtr += cSrcStride;
       ++shapePtr2;
+      if (pipe->srcOverprintMaskPtr) {
+	++pipe->srcOverprintMaskPtr;
+      }
     }
   } else {
     shapeVal = 0xff;
@@ -326,6 +365,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   if (bitmap->mode == splashModeMono1) {
     destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)];
     destColorMask = (Guchar)(0x80 >> (x0 & 7));
@@ -367,6 +408,11 @@
   } else {
     alpha0Ptr = NULL;
   }
+  if (overprintMaskBitmap) {
+    overprintMaskPtr = overprintMaskBitmap + y * bitmap->width + x0;
+  } else {
+    overprintMaskPtr = NULL;
+  }
 
   for (x = x0; x <= x1; ++x) {
 
@@ -399,6 +445,12 @@
       }
       cSrcPtr += cSrcStride;
       shapePtr2 += shapeStride;
+      if (pipe->srcOverprintMaskPtr) {
+	++pipe->srcOverprintMaskPtr;
+      }
+      if (overprintMaskPtr) {
+	++overprintMaskPtr;
+      }
       continue;
     }
     lastX = x;
@@ -519,6 +571,15 @@
 
       //----- read source color; handle overprint
 
+      if (pipe->srcOverprintMaskPtr) {
+	overprintMask = *pipe->srcOverprintMaskPtr++;
+      } else {
+	overprintMask = state->overprintMask;
+      }
+      if (overprintMaskPtr) {
+	*overprintMaskPtr++ |= overprintMask;
+      }
+
       switch (bitmap->mode) {
       case splashModeMono1:
       case splashModeMono8:
@@ -545,22 +606,22 @@
 	    aPrev = aDest;
 	  }
 	}
-	if (state->overprintMask & 0x01) {
+	if (overprintMask & 0x01) {
 	  cSrc[0] = state->cmykTransferC[cSrcPtr[0]];
 	} else {
 	  cSrc[0] = div255(aPrev * cDest[0]);
 	}
-	if (state->overprintMask & 0x02) {
+	if (overprintMask & 0x02) {
 	  cSrc[1] = state->cmykTransferM[cSrcPtr[1]];
 	} else {
 	  cSrc[1] = div255(aPrev * cDest[1]);
 	}
-	if (state->overprintMask & 0x04) {
+	if (overprintMask & 0x04) {
 	  cSrc[2] = state->cmykTransferY[cSrcPtr[2]];
 	} else {
 	  cSrc[2] = div255(aPrev * cDest[2]);
 	}
-	if (state->overprintMask & 0x08) {
+	if (overprintMask & 0x08) {
 	  cSrc[3] = state->cmykTransferK[cSrcPtr[3]];
 	} else {
 	  cSrc[3] = div255(aPrev * cDest[3]);
@@ -862,6 +923,8 @@
   updateModX(x1);
   updateModY(y);
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)];
   destColorMask = (Guchar)(0x80 >> (x0 & 7));
 
@@ -905,6 +968,8 @@
   updateModX(x1);
   updateModY(y);
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -940,6 +1005,8 @@
   updateModX(x1);
   updateModY(y);
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -978,6 +1045,8 @@
   updateModX(x1);
   updateModY(y);
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -1017,6 +1086,8 @@
   updateModX(x1);
   updateModY(y);
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -1067,6 +1138,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)];
   destColorMask = (Guchar)(0x80 >> (x0 & 7));
 
@@ -1150,6 +1223,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -1243,6 +1318,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -1349,6 +1426,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -1456,6 +1535,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -1480,26 +1561,10 @@
     aDest = *destAlphaPtr;
 
     //----- overprint
-    if (state->overprintMask & 1) {
-      cSrc0 = state->cmykTransferC[cSrcPtr[0]];
-    } else {
-      cSrc0 = div255(aDest * cDest0);
-    }
-    if (state->overprintMask & 2) {
-      cSrc1 = state->cmykTransferM[cSrcPtr[1]];
-    } else {
-      cSrc1 = div255(aDest * cDest1);
-    }
-    if (state->overprintMask & 4) {
-      cSrc2 = state->cmykTransferY[cSrcPtr[2]];
-    } else {
-      cSrc2 = div255(aDest * cDest2);
-    }
-    if (state->overprintMask & 8) {
-      cSrc3 = state->cmykTransferK[cSrcPtr[3]];
-    } else {
-      cSrc3 = div255(aDest * cDest3);
-    }
+    cSrc0 = state->cmykTransferC[cSrcPtr[0]];
+    cSrc1 = state->cmykTransferM[cSrcPtr[1]];
+    cSrc2 = state->cmykTransferY[cSrcPtr[2]];
+    cSrc3 = state->cmykTransferK[cSrcPtr[3]];
 
     //----- source alpha
     aSrc = shape;
@@ -1552,6 +1617,80 @@
 
 
 // special case:
+// !pipe->pattern && pipe->shapeOnly && !state->blendFunc &&
+// bitmap->mode == splashModeMono8 && !bitmap->alpha
+void Splash::pipeRunShapeNoAlphaMono8(SplashPipe *pipe, int x0, int x1, int y,
+                                      Guchar *shapePtr,
+                                      SplashColorPtr cSrcPtr) {
+  Guchar shape, aSrc, cSrc0, cDest0, cResult0;
+  SplashColorPtr destColorPtr;
+  int cSrcStride, x, lastX;
+
+  if (cSrcPtr) {
+    cSrcStride = 1;
+  } else {
+    cSrcPtr = pipe->cSrcVal;
+    cSrcStride = 0;
+  }
+  for (; x0 <= x1; ++x0) {
+    if (*shapePtr) {
+      break;
+    }
+    cSrcPtr += cSrcStride;
+    ++shapePtr;
+  }
+  if (x0 > x1) {
+    return;
+  }
+  updateModX(x0);
+  updateModY(y);
+  lastX = x0;
+
+  useDestRow(y);
+
+  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];
+
+  for (x = x0; x <= x1; ++x) {
+
+    //----- shape
+    shape = *shapePtr;
+    if (!shape) {
+      ++destColorPtr;
+      cSrcPtr += cSrcStride;
+      ++shapePtr;
+      continue;
+    }
+    lastX = x;
+
+    //----- source color
+    cSrc0 = state->grayTransfer[cSrcPtr[0]];
+
+    //----- source alpha
+    aSrc = shape;
+
+    //----- special case for aSrc = 255
+    if (aSrc == 255) {
+      cResult0 = cSrc0;
+    } else {
+
+      //----- read destination pixel
+      cDest0 = *destColorPtr;
+
+      //----- result color
+      cResult0 = div255((255 - aSrc) * cDest0 + aSrc * cSrc0);
+    }
+
+    //----- write destination pixel
+    *destColorPtr++ = cResult0;
+
+    cSrcPtr += cSrcStride;
+    ++shapePtr;
+  }
+
+  updateModX(lastX);
+}
+
+// special case:
 // !pipe->pattern && !pipe->noTransparency && !state->softMask &&
 // pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc &&
 // !pipe->nonIsolatedGroup &&
@@ -1584,6 +1723,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)];
   destColorMask = (Guchar)(0x80 >> (x0 & 7));
 
@@ -1663,6 +1804,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -1746,6 +1889,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -1840,6 +1985,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -1935,6 +2082,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
 
@@ -2017,6 +2166,457 @@
 #endif
 
 
+// special case:
+// !pipe->pattern && aInput == 255 && state->softMask && usesShape &&
+// !state->inNonIsolatedGroup && !state->inKnockoutGroup &&
+// !nonIsolatedGroup && state->overprintMask == 0xffffffff &&
+// !state->blendFunc &&
+// bitmap->mode == splashModeMono8 && bitmap->alpha
+void Splash::pipeRunSoftMaskMono8(SplashPipe *pipe, int x0, int x1, int y,
+				  Guchar *shapePtr, SplashColorPtr cSrcPtr) {
+  Guchar shape, aSrc, aDest, alphaI, aResult;
+  Guchar cSrc0, cDest0, cResult0;
+  SplashColorPtr destColorPtr;
+  Guchar *destAlphaPtr;
+  SplashColorPtr softMaskPtr;
+  int cSrcStride, x, lastX;
+
+  if (cSrcPtr) {
+    cSrcStride = 1;
+  } else {
+    cSrcPtr = pipe->cSrcVal;
+    cSrcStride = 0;
+  }
+  for (; x0 <= x1; ++x0) {
+    if (*shapePtr) {
+      break;
+    }
+    cSrcPtr += cSrcStride;
+    ++shapePtr;
+  }
+  if (x0 > x1) {
+    return;
+  }
+  updateModX(x0);
+  updateModY(y);
+  lastX = x0;
+
+  useDestRow(y);
+
+  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];
+  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
+  softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0];
+
+  for (x = x0; x <= x1; ++x) {
+
+    //----- shape
+    shape = *shapePtr;
+    if (!shape) {
+      ++destColorPtr;
+      ++destAlphaPtr;
+      ++softMaskPtr;
+      cSrcPtr += cSrcStride;
+      ++shapePtr;
+      continue;
+    }
+    lastX = x;
+
+    //----- read source color
+    cSrc0 = state->grayTransfer[cSrcPtr[0]];
+
+    //----- source alpha
+    aSrc = div255(*softMaskPtr++ * shape);
+
+    //----- special case for aSrc = 255
+    if (aSrc == 255) {
+      aResult = 255;
+      cResult0 = cSrc0;
+    } else {
+
+      //----- read destination alpha
+      aDest = *destAlphaPtr;
+
+      //----- special case for aDest = 0
+      if (aDest == 0) {
+        aResult = aSrc;
+        cResult0 = cSrc0;
+      } else {
+
+        //----- read destination pixel
+        cDest0 = destColorPtr[0];
+
+        //----- result alpha and non-isolated group element correction
+        aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
+        alphaI = aResult;
+
+        //----- result color
+        cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
+      }
+    }
+
+    //----- write destination pixel
+    destColorPtr[0] = cResult0;
+    ++destColorPtr;
+    *destAlphaPtr++ = aResult;
+
+    cSrcPtr += cSrcStride;
+    ++shapePtr;
+  }
+
+  updateModX(lastX);
+}
+
+// special case:
+// !pipe->pattern && aInput == 255 && state->softMask && usesShape &&
+// !state->inNonIsolatedGroup && !state->inKnockoutGroup &&
+// !nonIsolatedGroup && state->overprintMask == 0xffffffff &&
+// !state->blendFunc &&
+// bitmap->mode == splashModeRGB8 && bitmap->alpha
+void Splash::pipeRunSoftMaskRGB8(SplashPipe *pipe, int x0, int x1, int y,
+                                 Guchar *shapePtr, SplashColorPtr cSrcPtr) {
+  Guchar shape, aSrc, aDest, alphaI, aResult;
+  Guchar cSrc0, cSrc1, cSrc2;
+  Guchar cDest0, cDest1, cDest2;
+  Guchar cResult0, cResult1, cResult2;
+  SplashColorPtr destColorPtr;
+  Guchar *destAlphaPtr;
+  SplashColorPtr softMaskPtr;
+  int cSrcStride, x, lastX;
+
+  if (cSrcPtr) {
+    cSrcStride = 3;
+  } else {
+    cSrcPtr = pipe->cSrcVal;
+    cSrcStride = 0;
+  }
+  for (; x0 <= x1; ++x0) {
+    if (*shapePtr) {
+      break;
+    }
+    cSrcPtr += cSrcStride;
+    ++shapePtr;
+  }
+  if (x0 > x1) {
+    return;
+  }
+  updateModX(x0);
+  updateModY(y);
+  lastX = x0;
+
+  useDestRow(y);
+
+  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3];
+  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
+  softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0];
+
+  for (x = x0; x <= x1; ++x) {
+
+    //----- shape
+    shape = *shapePtr;
+    if (!shape) {
+      destColorPtr += 3;
+      ++destAlphaPtr;
+      ++softMaskPtr;
+      cSrcPtr += cSrcStride;
+      ++shapePtr;
+      continue;
+    }
+    lastX = x;
+
+    //----- read source color
+    cSrc0 = state->rgbTransferR[cSrcPtr[0]];
+    cSrc1 = state->rgbTransferG[cSrcPtr[1]];
+    cSrc2 = state->rgbTransferB[cSrcPtr[2]];
+
+    //----- source alpha
+    aSrc = div255(*softMaskPtr++ * shape);
+
+    //----- special case for aSrc = 255
+    if (aSrc == 255) {
+      aResult = 255;
+      cResult0 = cSrc0;
+      cResult1 = cSrc1;
+      cResult2 = cSrc2;
+    } else {
+
+      //----- read destination alpha
+      aDest = *destAlphaPtr;
+
+      //----- special case for aDest = 0
+      if (aDest == 0) {
+        aResult = aSrc;
+        cResult0 = cSrc0;
+        cResult1 = cSrc1;
+        cResult2 = cSrc2;
+      } else {
+
+        //----- read destination pixel
+        cDest0 = destColorPtr[0];
+        cDest1 = destColorPtr[1];
+        cDest2 = destColorPtr[2];
+
+        //----- result alpha and non-isolated group element correction
+        aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
+        alphaI = aResult;
+
+        //----- result color
+        cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
+        cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
+        cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
+      }
+    }
+
+    //----- write destination pixel
+    destColorPtr[0] = cResult0;
+    destColorPtr[1] = cResult1;
+    destColorPtr[2] = cResult2;
+    destColorPtr += 3;
+    *destAlphaPtr++ = aResult;
+
+    cSrcPtr += cSrcStride;
+    ++shapePtr;
+  }
+
+  updateModX(lastX);
+}
+
+// special case:
+// !pipe->pattern && aInput == 255 && state->softMask && usesShape &&
+// !state->inNonIsolatedGroup && !state->inKnockoutGroup &&
+// !nonIsolatedGroup && state->overprintMask == 0xffffffff &&
+// !state->blendFunc &&
+// bitmap->mode == splashModeBGR8 && bitmap->alpha
+void Splash::pipeRunSoftMaskBGR8(SplashPipe *pipe, int x0, int x1, int y,
+                                 Guchar *shapePtr, SplashColorPtr cSrcPtr) {
+  Guchar shape, aSrc, aDest, alphaI, aResult;
+  Guchar cSrc0, cSrc1, cSrc2;
+  Guchar cDest0, cDest1, cDest2;
+  Guchar cResult0, cResult1, cResult2;
+  SplashColorPtr destColorPtr;
+  Guchar *destAlphaPtr;
+  SplashColorPtr softMaskPtr;
+  int cSrcStride, x, lastX;
+
+  if (cSrcPtr) {
+    cSrcStride = 3;
+  } else {
+    cSrcPtr = pipe->cSrcVal;
+    cSrcStride = 0;
+  }
+  for (; x0 <= x1; ++x0) {
+    if (*shapePtr) {
+      break;
+    }
+    cSrcPtr += cSrcStride;
+    ++shapePtr;
+  }
+  if (x0 > x1) {
+    return;
+  }
+  updateModX(x0);
+  updateModY(y);
+  lastX = x0;
+
+  useDestRow(y);
+
+  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3];
+  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
+  softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0];
+
+  for (x = x0; x <= x1; ++x) {
+
+    //----- shape
+    shape = *shapePtr;
+    if (!shape) {
+      destColorPtr += 3;
+      ++destAlphaPtr;
+      ++softMaskPtr;
+      cSrcPtr += cSrcStride;
+      ++shapePtr;
+      continue;
+    }
+    lastX = x;
+
+    //----- read source color
+    cSrc0 = state->rgbTransferR[cSrcPtr[0]];
+    cSrc1 = state->rgbTransferG[cSrcPtr[1]];
+    cSrc2 = state->rgbTransferB[cSrcPtr[2]];
+
+    //----- source alpha
+    aSrc = div255(*softMaskPtr++ * shape);
+
+    //----- special case for aSrc = 255
+    if (aSrc == 255) {
+      aResult = 255;
+      cResult0 = cSrc0;
+      cResult1 = cSrc1;
+      cResult2 = cSrc2;
+    } else {
+
+      //----- read destination alpha
+      aDest = *destAlphaPtr;
+
+      //----- special case for aDest = 0
+      if (aDest == 0) {
+        aResult = aSrc;
+        cResult0 = cSrc0;
+        cResult1 = cSrc1;
+        cResult2 = cSrc2;
+      } else {
+
+        //----- read destination pixel
+        cDest0 = destColorPtr[2];
+        cDest1 = destColorPtr[1];
+        cDest2 = destColorPtr[0];
+
+        //----- result alpha and non-isolated group element correction
+        aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
+        alphaI = aResult;
+
+        //----- result color
+        cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
+        cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
+        cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
+      }
+    }
+
+    //----- write destination pixel
+    destColorPtr[2] = cResult0;
+    destColorPtr[1] = cResult1;
+    destColorPtr[0] = cResult2;
+    destColorPtr += 3;
+    *destAlphaPtr++ = aResult;
+
+    cSrcPtr += cSrcStride;
+    ++shapePtr;
+  }
+
+  updateModX(lastX);
+}
+
+#if SPLASH_CMYK
+// special case:
+// !pipe->pattern && aInput == 255 && state->softMask && usesShape &&
+// !state->inNonIsolatedGroup && !state->inKnockoutGroup &&
+// !nonIsolatedGroup && state->overprintMask == 0xffffffff &&
+// !state->blendFunc &&
+// bitmap->mode == splashModeCMYK8 && bitmap->alpha
+void Splash::pipeRunSoftMaskCMYK8(SplashPipe *pipe, int x0, int x1, int y,
+				  Guchar *shapePtr, SplashColorPtr cSrcPtr) {
+  Guchar shape, aSrc, aDest, alphaI, aResult;
+  Guchar cSrc0, cSrc1, cSrc2, cSrc3;
+  Guchar cDest0, cDest1, cDest2, cDest3;
+  Guchar cResult0, cResult1, cResult2, cResult3;
+  SplashColorPtr destColorPtr;
+  Guchar *destAlphaPtr;
+  SplashColorPtr softMaskPtr;
+  int cSrcStride, x, lastX;
+
+  if (cSrcPtr) {
+    cSrcStride = 4;
+  } else {
+    cSrcPtr = pipe->cSrcVal;
+    cSrcStride = 0;
+  }
+  for (; x0 <= x1; ++x0) {
+    if (*shapePtr) {
+      break;
+    }
+    cSrcPtr += cSrcStride;
+    ++shapePtr;
+  }
+  if (x0 > x1) {
+    return;
+  }
+  updateModX(x0);
+  updateModY(y);
+  lastX = x0;
+
+  useDestRow(y);
+
+  destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 4];
+  destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
+  softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0];
+
+  for (x = x0; x <= x1; ++x) {
+
+    //----- shape
+    shape = *shapePtr;
+    if (!shape) {
+      destColorPtr += 4;
+      ++destAlphaPtr;
+      ++softMaskPtr;
+      cSrcPtr += cSrcStride;
+      ++shapePtr;
+      continue;
+    }
+    lastX = x;
+
+    //----- read destination pixel
+    cDest0 = destColorPtr[0];
+    cDest1 = destColorPtr[1];
+    cDest2 = destColorPtr[2];
+    cDest3 = destColorPtr[3];
+
+    //----- read destination alpha
+    aDest = *destAlphaPtr;
+
+    //----- overprint
+    cSrc0 = state->cmykTransferC[cSrcPtr[0]];
+    cSrc1 = state->cmykTransferM[cSrcPtr[1]];
+    cSrc2 = state->cmykTransferY[cSrcPtr[2]];
+    cSrc3 = state->cmykTransferK[cSrcPtr[3]];
+
+    //----- source alpha
+    aSrc = div255(*softMaskPtr++ * shape);
+
+    //----- special case for aSrc = 255
+    if (aSrc == 255) {
+      aResult = 255;
+      cResult0 = cSrc0;
+      cResult1 = cSrc1;
+      cResult2 = cSrc2;
+      cResult3 = cSrc3;
+    } else {
+
+      //----- special case for aDest = 0
+      if (aDest == 0) {
+        aResult = aSrc;
+        cResult0 = cSrc0;
+        cResult1 = cSrc1;
+        cResult2 = cSrc2;
+        cResult3 = cSrc3;
+      } else {
+
+        //----- result alpha and non-isolated group element correction
+        aResult = (Guchar)(aSrc + aDest - div255(aSrc * aDest));
+        alphaI = aResult;
+
+        //----- result color
+        cResult0 = (Guchar)(((alphaI - aSrc) * cDest0 + aSrc * cSrc0) / alphaI);
+        cResult1 = (Guchar)(((alphaI - aSrc) * cDest1 + aSrc * cSrc1) / alphaI);
+        cResult2 = (Guchar)(((alphaI - aSrc) * cDest2 + aSrc * cSrc2) / alphaI);
+        cResult3 = (Guchar)(((alphaI - aSrc) * cDest3 + aSrc * cSrc3) / alphaI);
+      }
+    }
+
+    //----- write destination pixel
+    destColorPtr[0] = cResult0;
+    destColorPtr[1] = cResult1;
+    destColorPtr[2] = cResult2;
+    destColorPtr[3] = cResult3;
+    destColorPtr += 4;
+    *destAlphaPtr++ = aResult;
+
+    cSrcPtr += cSrcStride;
+    ++shapePtr;
+  }
+
+  updateModX(lastX);
+}
+#endif
+
+
 void Splash::pipeRunNonIsoMono8(SplashPipe *pipe, int x0, int x1, int y,
 				Guchar *shapePtr, SplashColorPtr cSrcPtr) {
   Guchar shape, aSrc, aDest, alphaI, alpha0, aResult;
@@ -2046,6 +2646,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + x0];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
   alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y)
@@ -2130,6 +2732,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
   alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y)
@@ -2225,6 +2829,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 3];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
   alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y)
@@ -2321,6 +2927,8 @@
   updateModY(y);
   lastX = x0;
 
+  useDestRow(y);
+
   destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * 4];
   destAlphaPtr = &bitmap->alpha[y * bitmap->alphaRowSize + x0];
   alpha0Ptr = &groupBackBitmap->alpha[(groupBackY + y)
@@ -2409,6 +3017,84 @@
 #endif
 
 
+void Splash::useDestRow(int y) {
+  int y0, y1, yy;
+
+  if (groupDestInitMode == splashGroupDestPreInit) {
+    return;
+  }
+  if (groupDestInitYMin > groupDestInitYMax) {
+    y0 = y1 = y;
+    groupDestInitYMin = groupDestInitYMax = y;
+  } else if (y < groupDestInitYMin) {
+    y0 = y;
+    y1 = groupDestInitYMin - 1;
+    groupDestInitYMin = y;
+  } else if (y > groupDestInitYMax) {
+    y0 = groupDestInitYMax + 1;
+    y1 = y;
+    groupDestInitYMax = y;
+  } else {
+    return;
+  }
+  for (yy = y0; yy <= y1; ++yy) {
+    if (groupDestInitMode == splashGroupDestInitZero) {
+      // same as clear(color=0, alpha=0)
+      memset(bitmap->data + bitmap->rowSize * yy, 0,
+	     bitmap->rowSize < 0 ? -bitmap->rowSize : bitmap->rowSize);
+      if (bitmap->alpha) {
+	memset(bitmap->alpha + bitmap->alphaRowSize * yy, 0,
+	       bitmap->alphaRowSize);
+      }
+    } else { // (groupDestInitMode == splashGroupDestInitCopy)
+      // same as blitTransparent
+      copyGroupBackdropRow(yy);
+    }
+  }
+}
+
+void Splash::copyGroupBackdropRow(int y) {
+  SplashColorPtr p, q;
+  Guchar mask, srcMask;
+  int x;
+
+  if (groupBackBitmap->mode != bitmap->mode) {
+    return;
+  }
+
+  if (bitmap->mode == splashModeMono1) {
+    p = &bitmap->data[y * bitmap->rowSize];
+    mask = (Guchar)0x80;
+    q = &groupBackBitmap->data[(groupBackY + y) * groupBackBitmap->rowSize
+			       + (groupBackX >> 3)];
+    srcMask = (Guchar)(0x80 >> (groupBackX & 7));
+    for (x = 0; x < bitmap->width; ++x) {
+      if (*q & srcMask) {
+	*p |= mask;
+      } else {
+	*p &= (Guchar)~mask;
+      }
+      if (!(mask = (Guchar)(mask >> 1))) {
+	mask = 0x80;
+	++p;
+      }
+      if (!(srcMask = (Guchar)(srcMask >> 1))) {
+	srcMask = 0x80;
+	++q;
+      }
+    }
+  } else {
+    p = &bitmap->data[y * bitmap->rowSize];
+    q = &groupBackBitmap->data[(groupBackY + y) * groupBackBitmap->rowSize
+			       + bitmapComps * groupBackX];
+    memcpy(p, q, bitmapComps * bitmap->width);
+  }
+
+  if (bitmap->alpha) {
+    memset(&bitmap->alpha[y * bitmap->alphaRowSize], 0, bitmap->width);
+  }
+}
+
 //------------------------------------------------------------------------
 
 // Transform a point from user space to device space.
@@ -2428,7 +3114,13 @@
 
 SplashImageCache::SplashImageCache() {
   tag = NULL;
-  image = NULL;
+  width = 0;
+  height = 0;
+  mode = splashModeRGB8;
+  alpha = gFalse;
+  interpolate = gFalse;
+  colorData = NULL;
+  alphaData = NULL;
   refCount = 1;
 }
 
@@ -2436,9 +3128,39 @@
   if (tag) {
     delete tag;
   }
-  if (image) {
-    delete image;
+  gfree(colorData);
+  gfree(alphaData);
+}
+
+GBool SplashImageCache::match(GString *aTag, int aWidth, int aHeight,
+			      SplashColorMode aMode, GBool aAlpha,
+			      GBool aInterpolate) {
+  return aTag && tag && !aTag->cmp(tag) &&
+	 aWidth == width && aHeight == height &&
+	 aMode == mode && aAlpha == alpha &&
+	 aInterpolate == interpolate;
+}
+
+void SplashImageCache::reset(GString *aTag, int aWidth, int aHeight,
+			     SplashColorMode aMode, GBool aAlpha,
+			     GBool aInterpolate) {
+  if (tag) {
+    delete tag;
   }
+  if (aTag) {
+    tag = aTag->copy();
+  } else {
+    tag = NULL;
+  }
+  width = aWidth;
+  height = aHeight;
+  mode = aMode;
+  alpha = aAlpha;
+  interpolate = aInterpolate; 
+  gfree(colorData);
+  colorData = NULL;
+  gfree(alphaData);
+  alphaData = NULL;
 }
 
 void SplashImageCache::incRefCount() {
@@ -2453,6 +3175,1172 @@
 }
 
 //------------------------------------------------------------------------
+// ImageScaler
+//------------------------------------------------------------------------
+
+// Abstract base class.
+class ImageScaler {
+public:
+
+  ImageScaler() {}
+  virtual ~ImageScaler() {}
+
+  // Compute the next line of the scaled image.  This can be called up
+  // to [scaledHeight] times.
+  virtual void nextLine() = 0;
+
+  // Retrieve the color and alpha data generated by the most recent
+  // call to nextLine().
+  virtual Guchar *colorData() = 0;
+  virtual Guchar *alphaData() = 0;
+};
+
+//------------------------------------------------------------------------
+// BasicImageScaler
+//------------------------------------------------------------------------
+
+// The actual image scaler.
+class BasicImageScaler: public ImageScaler {
+public:
+
+  BasicImageScaler(SplashImageSource aSrc, void *aSrcData,
+		   int aSrcWidth, int aSrcHeight, int aNComps, GBool aHasAlpha,
+		   int aScaledWidth, int aScaledHeight, GBool aInterpolate);
+  virtual ~BasicImageScaler();
+  virtual void nextLine();
+  virtual Guchar *colorData() { return colorLine; }
+  virtual Guchar *alphaData() { return alphaLine; }
+
+protected:
+
+  void vertDownscaleHorizDownscale();
+  void vertDownscaleHorizUpscaleNoInterp();
+  void vertDownscaleHorizUpscaleInterp();
+  void vertUpscaleHorizDownscaleNoInterp();
+  void vertUpscaleHorizDownscaleInterp();
+  void vertUpscaleHorizUpscaleNoInterp();
+  void vertUpscaleHorizUpscaleInterp();
+
+  // source image data function
+  SplashImageSource src;
+  void *srcData;
+
+  // source image size
+  int srcWidth;
+  int srcHeight;
+
+  // scaled image size
+  int scaledWidth;
+  int scaledHeight;
+
+  // number of color and alpha components
+  int nComps;
+  GBool hasAlpha;
+
+  // params/state for vertical scaling
+  int yp, yq;
+  int yt, yn;
+  int ySrcCur, yScaledCur;
+  SplashCoord yInvScale;
+
+  // params for horizontal scaling
+  int xp, xq;
+  SplashCoord xInvScale;
+
+  // scaling function
+  void (BasicImageScaler::*scalingFunc)();
+
+  // temporary buffers for vertical scaling
+  Guchar *colorTmpBuf0;
+  Guchar *colorTmpBuf1;
+  Guchar *colorTmpBuf2;
+  Guchar *alphaTmpBuf0;
+  Guchar *alphaTmpBuf1;
+  Guchar *alphaTmpBuf2;
+  Guint *colorAccBuf;
+  Guint *alphaAccBuf;
+
+  // output of horizontal scaling
+  Guchar *colorLine;
+  Guchar *alphaLine;
+};
+
+BasicImageScaler::BasicImageScaler(SplashImageSource aSrc, void *aSrcData,
+				   int aSrcWidth, int aSrcHeight,
+				   int aNComps, GBool aHasAlpha,
+				   int aScaledWidth, int aScaledHeight,
+				   GBool aInterpolate) {
+  colorTmpBuf0 = NULL;
+  colorTmpBuf1 = NULL;
+  colorTmpBuf2 = NULL;
+  alphaTmpBuf0 = NULL;
+  alphaTmpBuf1 = NULL;
+  alphaTmpBuf2 = NULL;
+  colorAccBuf = NULL;
+  alphaAccBuf = NULL;
+  colorLine = NULL;
+  alphaLine = NULL;
+
+  src = aSrc;
+  srcData = aSrcData;
+  srcWidth = aSrcWidth;
+  srcHeight = aSrcHeight;
+  scaledWidth = aScaledWidth;
+  scaledHeight = aScaledHeight;
+  nComps = aNComps;
+  hasAlpha = aHasAlpha;
+
+  // select scaling function; allocate buffers
+  if (scaledHeight <= srcHeight) {
+    // vertical downscaling
+    yp = srcHeight / scaledHeight;
+    yq = srcHeight % scaledHeight;
+    yt = 0;
+    colorTmpBuf0 = (Guchar *)gmallocn(srcWidth, nComps);
+    colorAccBuf = (Guint *)gmallocn(srcWidth, nComps * (int)sizeof(Guint));
+    if (hasAlpha) {
+      alphaTmpBuf0 = (Guchar *)gmalloc(srcWidth);
+      alphaAccBuf = (Guint *)gmallocn(srcWidth, sizeof(Guint));
+    }
+    if (scaledWidth <= srcWidth) {
+      scalingFunc = &BasicImageScaler::vertDownscaleHorizDownscale;
+    } else {
+      if (aInterpolate) {
+	scalingFunc = &BasicImageScaler::vertDownscaleHorizUpscaleInterp;
+      } else {
+	scalingFunc = &BasicImageScaler::vertDownscaleHorizUpscaleNoInterp;
+      }
+    }
+  } else {
+    // vertical upscaling
+    yp = scaledHeight / srcHeight;
+    yq = scaledHeight % srcHeight;
+    yt = 0;
+    yn = 0;
+    if (aInterpolate) {
+      yInvScale = (SplashCoord)srcHeight / (SplashCoord)scaledHeight;
+      colorTmpBuf0 = (Guchar *)gmallocn(srcWidth, nComps);
+      colorTmpBuf1 = (Guchar *)gmallocn(srcWidth, nComps);
+      if (hasAlpha) {
+	alphaTmpBuf0 = (Guchar *)gmalloc(srcWidth);
+	alphaTmpBuf1 = (Guchar *)gmalloc(srcWidth);
+      }
+      ySrcCur = 0;
+      yScaledCur = 0;
+      if (scaledWidth <= srcWidth) {
+	scalingFunc = &BasicImageScaler::vertUpscaleHorizDownscaleInterp;
+      } else {
+	colorTmpBuf2 = (Guchar *)gmallocn(srcWidth, nComps);
+	if (hasAlpha) {
+	  alphaTmpBuf2 = (Guchar *)gmalloc(srcWidth);
+	}
+	scalingFunc = &BasicImageScaler::vertUpscaleHorizUpscaleInterp;
+      }
+    } else {
+      colorTmpBuf0 = (Guchar *)gmallocn(srcWidth, nComps);
+      if (hasAlpha) {
+	alphaTmpBuf0 = (Guchar *)gmalloc(srcWidth);
+      }
+      if (scaledWidth <= srcWidth) {
+	scalingFunc = &BasicImageScaler::vertUpscaleHorizDownscaleNoInterp;
+      } else {
+	scalingFunc = &BasicImageScaler::vertUpscaleHorizUpscaleNoInterp;
+      }
+    }
+  }
+  if (scaledWidth <= srcWidth) {
+    xp = srcWidth / scaledWidth;
+    xq = srcWidth % scaledWidth;
+  } else {
+    xp = scaledWidth / srcWidth;
+    xq = scaledWidth % srcWidth;
+    if (aInterpolate) {
+      xInvScale = (SplashCoord)srcWidth / (SplashCoord)scaledWidth;
+    }
+  }
+  colorLine = (Guchar *)gmallocn(scaledWidth, nComps);
+  if (hasAlpha) {
+    alphaLine = (Guchar *)gmalloc(scaledWidth);
+  }
+}
+
+BasicImageScaler::~BasicImageScaler() {
+  gfree(colorTmpBuf0);
+  gfree(colorTmpBuf1);
+  gfree(colorTmpBuf2);
+  gfree(alphaTmpBuf0);
+  gfree(alphaTmpBuf1);
+  gfree(alphaTmpBuf2);
+  gfree(colorAccBuf);
+  gfree(alphaAccBuf);
+  gfree(colorLine);
+  gfree(alphaLine);
+}
+
+void BasicImageScaler::nextLine() {
+  (this->*scalingFunc)();
+}
+
+void BasicImageScaler::vertDownscaleHorizDownscale() {
+  //--- vert downscale
+  int yStep = yp;
+  yt += yq;
+  if (yt >= scaledHeight) {
+    yt -= scaledHeight;
+    ++yStep;
+  }
+  memset(colorAccBuf, 0, (srcWidth * nComps) * sizeof(Guint));
+  if (hasAlpha) {
+    memset(alphaAccBuf, 0, srcWidth * sizeof(Guint));
+  }
+  int nRowComps = srcWidth * nComps;
+  for (int i = 0; i < yStep; ++i) {
+    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
+    for (int j = 0; j < nRowComps; ++j) {
+      colorAccBuf[j] += colorTmpBuf0[j];
+    }
+    if (hasAlpha) {
+      for (int j = 0; j < srcWidth; ++j) {
+	alphaAccBuf[j] += alphaTmpBuf0[j];
+      }
+    }
+  }
+
+  //--- horiz downscale
+  int colorAcc[splashMaxColorComps];
+  int xt = 0;
+  int unscaledColorIdx = 0;
+  int unscaledAlphaIdx = 0;
+  int scaledColorIdx = 0;
+
+  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
+    int xStep = xp;
+    xt += xq;
+    if (xt >= scaledWidth) {
+      xt -= scaledWidth;
+      ++xStep;
+    }
+
+    for (int j = 0; j < nComps; ++j) {
+      colorAcc[j] = 0;
+    }
+    for (int i = 0; i < xStep; ++i) {
+      for (int j = 0; j < nComps; ++j) {
+	colorAcc[j] += colorAccBuf[unscaledColorIdx + j];
+      }
+      unscaledColorIdx += nComps;
+    }
+    int nPixels = yStep * xStep;
+    for (int j = 0; j < nComps; ++j) {
+      colorLine[scaledColorIdx + j] = (Guchar)(colorAcc[j] / nPixels);
+    }
+    scaledColorIdx += nComps;
+
+    if (hasAlpha) {
+      int alphaAcc = 0;
+      for (int i = 0; i < xStep; ++i) {
+	alphaAcc += alphaAccBuf[unscaledAlphaIdx];
+	++unscaledAlphaIdx;
+      }
+      alphaLine[scaledIdx] = (Guchar)(alphaAcc / nPixels);
+    }
+  }
+}
+
+void BasicImageScaler::vertDownscaleHorizUpscaleNoInterp() {
+  //--- vert downscale
+  int yStep = yp;
+  yt += yq;
+  if (yt >= scaledHeight) {
+    yt -= scaledHeight;
+    ++yStep;
+  }
+  memset(colorAccBuf, 0, (srcWidth * nComps) * sizeof(Guint));
+  if (hasAlpha) {
+    memset(alphaAccBuf, 0, srcWidth * sizeof(Guint));
+  }
+  int nRowComps = srcWidth * nComps;
+  for (int i = 0; i < yStep; ++i) {
+    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
+    for (int j = 0; j < nRowComps; ++j) {
+      colorAccBuf[j] += colorTmpBuf0[j];
+    }
+    if (hasAlpha) {
+      for (int j = 0; j < srcWidth; ++j) {
+	alphaAccBuf[j] += alphaTmpBuf0[j];
+      }
+    }
+  }
+
+  //--- horiz upscale
+  Guchar colorBuf[splashMaxColorComps];
+  int xt = 0;
+  int scaledColorIdx = 0;
+  int srcColorIdx = 0;
+  int scaledAlphaIdx = 0;
+  for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
+    int xStep = xp;
+    xt += xq;
+    if (xt >= srcWidth) {
+      xt -= srcWidth;
+      ++xStep;
+    }
+    for (int j = 0; j < nComps; ++j) {
+      colorBuf[j] = (Guchar)(colorAccBuf[srcColorIdx + j] / yStep);
+    }
+    srcColorIdx += nComps;
+    for (int i = 0; i < xStep; ++i) {
+      for (int j = 0; j < nComps; ++j) {
+	colorLine[scaledColorIdx + j] = colorBuf[j];
+      }
+      scaledColorIdx += nComps;
+    }
+    if (hasAlpha) {
+      Guchar alphaBuf = (Guchar)(alphaAccBuf[srcIdx] / yStep);
+      for (int i = 0; i < xStep; ++i) {
+	alphaLine[scaledAlphaIdx] = alphaBuf;
+	++scaledAlphaIdx;
+      }
+    }
+  }
+}
+
+void BasicImageScaler::vertDownscaleHorizUpscaleInterp() {
+  //--- vert downscale
+  int yStep = yp;
+  yt += yq;
+  if (yt >= scaledHeight) {
+    yt -= scaledHeight;
+    ++yStep;
+  }
+  memset(colorAccBuf, 0, (srcWidth * nComps) * sizeof(Guint));
+  if (hasAlpha) {
+    memset(alphaAccBuf, 0, srcWidth * sizeof(Guint));
+  }
+  int nRowComps = srcWidth * nComps;
+  for (int i = 0; i < yStep; ++i) {
+    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
+    for (int j = 0; j < nRowComps; ++j) {
+      colorAccBuf[j] += colorTmpBuf0[j];
+    }
+    if (hasAlpha) {
+      for (int j = 0; j < srcWidth; ++j) {
+	alphaAccBuf[j] += alphaTmpBuf0[j];
+      }
+    }
+  }
+  for (int j = 0; j < srcWidth * nComps; ++j) {
+    colorAccBuf[j] /= yStep;
+  }
+  if (hasAlpha) {
+    for (int j = 0; j < srcWidth; ++j) {
+      alphaAccBuf[j] /= yStep;
+    }
+  }
+
+  //--- horiz upscale
+  int scaledColorIdx = 0;
+  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
+    SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale;
+    int x0 = splashFloor(xs - 0.5);
+    int x1 = x0 + 1;
+    SplashCoord s0 = (SplashCoord)x1 + 0.5 - xs;
+    SplashCoord s1 = (SplashCoord)1 - s0;
+    if (x0 < 0) {
+      x0 = 0;
+    }
+    if (x1 >= srcWidth) {
+      x1 = srcWidth - 1;
+    }
+    for (int j = 0; j < nComps; ++j) {
+      colorLine[scaledColorIdx + j] =
+	  (Guchar)(int)(s0 * colorAccBuf[x0 * nComps + j] +
+			s1 * colorAccBuf[x1 * nComps + j]);
+    }
+    scaledColorIdx += nComps;
+    if (hasAlpha) {
+      alphaLine[scaledIdx] = (Guchar)(int)(s0 * alphaAccBuf[x0] +
+					   s1 * alphaAccBuf[x1]);
+    }
+  }
+}
+
+void BasicImageScaler::vertUpscaleHorizDownscaleNoInterp() {
+  //--- vert upscale
+  if (yn == 0) {
+    yn = yp;
+    yt += yq;
+    if (yt >= srcHeight) {
+      yt -= srcHeight;
+      ++yn;
+    }
+    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
+  }
+  --yn;
+
+  //--- horiz downscale
+  int colorAcc[splashMaxColorComps];
+  int xt = 0;
+  int unscaledColorIdx = 0;
+  int unscaledAlphaIdx = 0;
+  int scaledColorIdx = 0;
+  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
+    int xStep = xp;
+    xt += xq;
+    if (xt >= scaledWidth) {
+      xt -= scaledWidth;
+      ++xStep;
+    }
+
+    for (int j = 0; j < nComps; ++j) {
+      colorAcc[j] = 0;
+    }
+    for (int i = 0; i < xStep; ++i) {
+      for (int j = 0; j < nComps; ++j) {
+	colorAcc[j] += colorTmpBuf0[unscaledColorIdx + j];
+      }
+      unscaledColorIdx += nComps;
+    }
+    for (int j = 0; j < nComps; ++j) {
+      colorLine[scaledColorIdx + j] = (Guchar)(colorAcc[j] / xStep);
+    }
+    scaledColorIdx += nComps;
+
+    if (hasAlpha) {
+      int alphaAcc = 0;
+      for (int i = 0; i < xStep; ++i) {
+	alphaAcc += alphaTmpBuf0[unscaledAlphaIdx];
+	++unscaledAlphaIdx;
+      }
+      alphaLine[scaledIdx] = (Guchar)(alphaAcc / xStep);
+    }
+  }
+}
+
+void BasicImageScaler::vertUpscaleHorizDownscaleInterp() {
+  //--- vert upscale
+  if (ySrcCur == 0) {
+    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
+    (*src)(srcData, colorTmpBuf1, alphaTmpBuf1);
+    ySrcCur = 1;
+  }
+  SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale;
+  int y0 = splashFloor(ys - 0.5);
+  int y1 = y0 + 1;
+  SplashCoord vs0 = (SplashCoord)y1 + 0.5 - ys;
+  SplashCoord vs1 = (SplashCoord)1 - vs0;
+  if (y1 > ySrcCur && ySrcCur < srcHeight - 1) {
+    Guchar *t = colorTmpBuf0;
+    colorTmpBuf0 = colorTmpBuf1;
+    colorTmpBuf1 = t;
+    if (hasAlpha) {
+      t = alphaTmpBuf0;
+      alphaTmpBuf0 = alphaTmpBuf1;
+      alphaTmpBuf1 = t;
+    }
+    (*src)(srcData, colorTmpBuf1, alphaTmpBuf1);
+    ++ySrcCur;
+  }
+  Guchar *color0 = colorTmpBuf0;
+  Guchar *color1 = colorTmpBuf1;
+  Guchar *alpha0 = alphaTmpBuf0;
+  Guchar *alpha1 = alphaTmpBuf1;
+  if (y0 < 0) {
+    y0 = 0;
+    color1 = color0;
+    alpha1 = alpha0;
+  }
+  if (y1 >= srcHeight) {
+    y1 = srcHeight - 1;
+    color0 = color1;
+    alpha0 = alpha1;
+  }
+  ++yScaledCur;
+
+  //--- horiz downscale
+  int colorAcc[splashMaxColorComps];
+  int xt = 0;
+  int unscaledColorIdx = 0;
+  int unscaledAlphaIdx = 0;
+  int scaledColorIdx = 0;
+  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
+    int xStep = xp;
+    xt += xq;
+    if (xt >= scaledWidth) {
+      xt -= scaledWidth;
+      ++xStep;
+    }
+
+    for (int j = 0; j < nComps; ++j) {
+      colorAcc[j] = 0;
+    }
+    for (int i = 0; i < xStep; ++i) {
+      for (int j = 0; j < nComps; ++j) {
+	colorAcc[j] += (int)(vs0 * (int)color0[unscaledColorIdx + j] +
+			     vs1 * (int)color1[unscaledColorIdx + j]);
+      }
+      unscaledColorIdx += nComps;
+    }
+    for (int j = 0; j < nComps; ++j) {
+      colorLine[scaledColorIdx + j] = (Guchar)(colorAcc[j] / xStep);
+    }
+    scaledColorIdx += nComps;
+
+    if (hasAlpha) {
+      int alphaAcc = 0;
+      for (int i = 0; i < xStep; ++i) {
+	alphaAcc += (int)(vs0 * (int)alpha0[unscaledAlphaIdx] +
+			  vs1 * (int)alpha1[unscaledAlphaIdx]);
+	++unscaledAlphaIdx;
+      }
+      alphaLine[scaledIdx] = (Guchar)(alphaAcc / xStep);
+    }
+  }
+}
+
+void BasicImageScaler::vertUpscaleHorizUpscaleNoInterp() {
+  //--- vert upscale
+  if (yn == 0) {
+    yn = yp;
+    yt += yq;
+    if (yt >= srcHeight) {
+      yt -= srcHeight;
+      ++yn;
+    }
+    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
+  }
+  --yn;
+
+  //--- horiz upscale
+  int xt = 0;
+  int scaledColorIdx = 0;
+  int srcColorIdx = 0;
+  int scaledAlphaIdx = 0;
+  for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
+    int xStep = xp;
+    xt += xq;
+    if (xt >= srcWidth) {
+      xt -= srcWidth;
+      ++xStep;
+    }
+    for (int i = 0; i < xStep; ++i) {
+      for (int j = 0; j < nComps; ++j) {
+	colorLine[scaledColorIdx + j] = colorTmpBuf0[srcColorIdx + j];
+      }
+      scaledColorIdx += nComps;
+    }
+    srcColorIdx += nComps;
+    if (hasAlpha) {
+      Guchar alphaBuf = alphaTmpBuf0[srcIdx];
+      for (int i = 0; i < xStep; ++i) {
+	alphaLine[scaledAlphaIdx] = alphaBuf;
+	++scaledAlphaIdx;
+      }
+    }
+  }
+}
+
+void BasicImageScaler::vertUpscaleHorizUpscaleInterp() {
+  //--- vert upscale
+  if (ySrcCur == 0) {
+    (*src)(srcData, colorTmpBuf0, alphaTmpBuf0);
+    (*src)(srcData, colorTmpBuf1, alphaTmpBuf1);
+    ySrcCur = 1;
+  }
+  SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale;
+  int y0 = splashFloor(ys - 0.5);
+  int y1 = y0 + 1;
+  SplashCoord vs0 = (SplashCoord)y1 + 0.5 - ys;
+  SplashCoord vs1 = (SplashCoord)1 - vs0;
+  if (y1 > ySrcCur && ySrcCur < srcHeight - 1) {
+    Guchar *t = colorTmpBuf0;
+    colorTmpBuf0 = colorTmpBuf1;
+    colorTmpBuf1 = t;
+    if (hasAlpha) {
+      t = alphaTmpBuf0;
+      alphaTmpBuf0 = alphaTmpBuf1;
+      alphaTmpBuf1 = t;
+    }
+    (*src)(srcData, colorTmpBuf1, alphaTmpBuf1);
+    ++ySrcCur;
+  }
+  Guchar *color0 = colorTmpBuf0;
+  Guchar *color1 = colorTmpBuf1;
+  Guchar *alpha0 = alphaTmpBuf0;
+  Guchar *alpha1 = alphaTmpBuf1;
+  if (y0 < 0) {
+    y0 = 0;
+    color1 = color0;
+    alpha1 = alpha0;
+  }
+  if (y1 >= srcHeight) {
+    y1 = srcHeight - 1;
+    color0 = color1;
+    alpha0 = alpha1;
+  }
+  ++yScaledCur;
+  for (int srcIdx = 0; srcIdx < srcWidth * nComps; ++srcIdx) {
+    colorTmpBuf2[srcIdx] = (Guchar)(int)(vs0 * (int)color0[srcIdx] +
+					 vs1 * (int)color1[srcIdx]);
+  }
+  if (hasAlpha) {
+    for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
+      alphaTmpBuf2[srcIdx] = (Guchar)(int)(vs0 * (int)alpha0[srcIdx] +
+					   vs1 * (int)alpha1[srcIdx]);
+    }
+  }
+
+  //--- horiz upscale
+  int scaledColorIdx = 0;
+  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
+    SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale;
+    int x0 = splashFloor(xs - 0.5);
+    int x1 = x0 + 1;
+    SplashCoord hs0 = (SplashCoord)x1 + 0.5 - xs;
+    SplashCoord hs1 = (SplashCoord)1 - hs0;
+    if (x0 < 0) {
+      x0 = 0;
+    }
+    if (x1 >= srcWidth) {
+      x1 = srcWidth - 1;
+    }
+    for (int j = 0; j < nComps; ++j) {
+      colorLine[scaledColorIdx + j] =
+	  (Guchar)(int)(hs0 * (int)colorTmpBuf2[x0 * nComps + j] +
+			hs1 * (int)colorTmpBuf2[x1 * nComps + j]);
+    }
+    scaledColorIdx += nComps;
+    if (hasAlpha) {
+      alphaLine[scaledIdx] = (Guchar)(int)(hs0 * (int)alphaTmpBuf2[x0] +
+					   hs1 * (int)alphaTmpBuf2[x1]);
+    }
+  }
+}
+
+//------------------------------------------------------------------------
+// SavingImageScaler
+//------------------------------------------------------------------------
+
+// Wrapper around BasicImageScaler that saves the scaled image for use
+// by ReplayImageScaler.
+class SavingImageScaler: public BasicImageScaler {
+public:
+
+  SavingImageScaler(SplashImageSource aSrc, void *aSrcData,
+		    int aSrcWidth, int aSrcHeight, int aNComps, GBool aHasAlpha,
+		    int aScaledWidth, int aScaledHeight, GBool aInterpolate,
+		    Guchar *aColorCache, Guchar *aAlphaCache);
+  virtual void nextLine();
+
+private:
+
+  Guchar *colorPtr;
+  Guchar *alphaPtr;
+};
+
+SavingImageScaler::SavingImageScaler(SplashImageSource aSrc, void *aSrcData,
+				     int aSrcWidth, int aSrcHeight,
+				     int aNComps, GBool aHasAlpha,
+				     int aScaledWidth, int aScaledHeight,
+				     GBool aInterpolate,
+				     Guchar *aColorCache, Guchar *aAlphaCache):
+  BasicImageScaler(aSrc, aSrcData, aSrcWidth, aSrcHeight, aNComps, aHasAlpha,
+		   aScaledWidth, aScaledHeight, aInterpolate)
+{
+  colorPtr = aColorCache;
+  alphaPtr = aAlphaCache;
+}
+
+void SavingImageScaler::nextLine() {
+  BasicImageScaler::nextLine();
+  memcpy(colorPtr, colorData(), scaledWidth * nComps);
+  colorPtr += scaledWidth * nComps;
+  if (hasAlpha) {
+    memcpy(alphaPtr, alphaData(), scaledWidth);
+    alphaPtr += scaledWidth;
+  }
+}
+
+//------------------------------------------------------------------------
+// ReplayImageScaler
+//------------------------------------------------------------------------
+
+// "Replay" a scaled image saved by SavingImageScaler.
+class ReplayImageScaler: public ImageScaler {
+public:
+
+  ReplayImageScaler(int aNComps, GBool aHasAlpha,
+		    int aScaledWidth,
+		    Guchar *aColorCache, Guchar *aAlphaCache);
+  virtual void nextLine();
+  virtual Guchar *colorData() { return colorLine; }
+  virtual Guchar *alphaData() { return alphaLine; }
+
+private:
+
+  int nComps;
+  GBool hasAlpha;
+  int scaledWidth;
+  Guchar *colorPtr;
+  Guchar *alphaPtr;
+  Guchar *colorLine;
+  Guchar *alphaLine;
+};
+
+ReplayImageScaler::ReplayImageScaler(int aNComps, GBool aHasAlpha,
+				     int aScaledWidth,
+				     Guchar *aColorCache, Guchar *aAlphaCache) {
+  nComps = aNComps;
+  hasAlpha = aHasAlpha;
+  scaledWidth = aScaledWidth;
+  colorPtr = aColorCache;
+  alphaPtr = aAlphaCache;
+  colorLine = NULL;
+  alphaLine = NULL;
+}
+
+void ReplayImageScaler::nextLine() {
+  colorLine = colorPtr;
+  alphaLine = alphaPtr;
+  colorPtr += scaledWidth * nComps;
+  if (hasAlpha) {
+    alphaPtr += scaledWidth;
+  }
+}
+
+//------------------------------------------------------------------------
+// ImageMaskScaler
+//------------------------------------------------------------------------
+
+class ImageMaskScaler {
+public:
+
+  // Set up a MaskScaler to scale from [srcWidth]x[srcHeight] to
+  // [scaledWidth]x[scaledHeight].
+  ImageMaskScaler(SplashImageMaskSource aSrc, void *aSrcData,
+		  int aSrcWidth, int aSrcHeight,
+		  int aScaledWidth, int aScaledHeight, GBool aInterpolate);
+
+  ~ImageMaskScaler();
+
+  // Compute the next line of the scaled image mask.  This can be
+  // called up to [scaledHeight] times.
+  void nextLine();
+
+  // Retrieve the data generated by the most recent call to
+  // nextLine().
+  Guchar *data() { return line; }
+
+private:
+
+  void vertDownscaleHorizDownscale();
+  void vertDownscaleHorizUpscaleNoInterp();
+  void vertDownscaleHorizUpscaleInterp();
+  void vertUpscaleHorizDownscaleNoInterp();
+  void vertUpscaleHorizDownscaleInterp();
+  void vertUpscaleHorizUpscaleNoInterp();
+  void vertUpscaleHorizUpscaleInterp();
+
+  // source image data function
+  SplashImageMaskSource src;
+  void *srcData;
+
+  // source image size
+  int srcWidth;
+  int srcHeight;
+
+  // scaled image size
+  int scaledWidth;
+  int scaledHeight;
+
+  // params/state for vertical scaling
+  int yp, yq;
+  int yt, yn;
+  int ySrcCur, yScaledCur;
+  SplashCoord yInvScale;
+
+  // params for horizontal scaling
+  int xp, xq;
+  SplashCoord xInvScale;
+
+  // vertical and horizontal scaling functions
+  void (ImageMaskScaler::*scalingFunc)();
+
+  // temporary buffers for vertical scaling
+  Guchar *tmpBuf0;
+  Guchar *tmpBuf1;
+  Guchar *tmpBuf2;
+  Guint *accBuf;
+
+  // output of horizontal scaling
+  Guchar *line;
+};
+
+ImageMaskScaler::ImageMaskScaler(SplashImageMaskSource aSrc, void *aSrcData,
+				 int aSrcWidth, int aSrcHeight,
+				 int aScaledWidth, int aScaledHeight,
+				 GBool aInterpolate) {
+  tmpBuf0 = NULL;
+  tmpBuf1 = NULL;
+  tmpBuf2 = NULL;
+  accBuf = NULL;
+  line = NULL;
+
+  src = aSrc;
+  srcData = aSrcData;
+  srcWidth = aSrcWidth;
+  srcHeight = aSrcHeight;
+  scaledWidth = aScaledWidth;
+  scaledHeight = aScaledHeight;
+
+  // select scaling function; allocate buffers
+  if (scaledHeight <= srcHeight) {
+    // vertical downscaling
+    yp = srcHeight / scaledHeight;
+    yq = srcHeight % scaledHeight;
+    yt = 0;
+    tmpBuf0 = (Guchar *)gmalloc(srcWidth);
+    accBuf = (Guint *)gmallocn(srcWidth, sizeof(Guint));
+    if (scaledWidth <= srcWidth) {
+      scalingFunc = &ImageMaskScaler::vertDownscaleHorizDownscale;
+    } else {
+      if (aInterpolate) {
+	scalingFunc = &ImageMaskScaler::vertDownscaleHorizUpscaleInterp;
+      } else {
+	scalingFunc = &ImageMaskScaler::vertDownscaleHorizUpscaleNoInterp;
+      }
+    }
+  } else {
+    // vertical upscaling
+    yp = scaledHeight / srcHeight;
+    yq = scaledHeight % srcHeight;
+    yt = 0;
+    yn = 0;
+    if (aInterpolate) {
+      yInvScale = (SplashCoord)srcHeight / (SplashCoord)scaledHeight;
+      tmpBuf0 = (Guchar *)gmalloc(srcWidth);
+      tmpBuf1 = (Guchar *)gmalloc(srcWidth);
+      ySrcCur = 0;
+      yScaledCur = 0;
+      if (scaledWidth <= srcWidth) {
+	scalingFunc = &ImageMaskScaler::vertUpscaleHorizDownscaleInterp;
+      } else {
+	tmpBuf2 = (Guchar *)gmalloc(srcWidth);
+	scalingFunc = &ImageMaskScaler::vertUpscaleHorizUpscaleInterp;
+      }
+    } else {
+      tmpBuf0 = (Guchar *)gmalloc(srcWidth);
+      if (scaledWidth <= srcWidth) {
+	scalingFunc = &ImageMaskScaler::vertUpscaleHorizDownscaleNoInterp;
+      } else {
+	scalingFunc = &ImageMaskScaler::vertUpscaleHorizUpscaleNoInterp;
+      }
+    }
+  }
+  if (scaledWidth <= srcWidth) {
+    xp = srcWidth / scaledWidth;
+    xq = srcWidth % scaledWidth;
+  } else {
+    xp = scaledWidth / srcWidth;
+    xq = scaledWidth % srcWidth;
+    if (aInterpolate) {
+      xInvScale = (SplashCoord)srcWidth / (SplashCoord)scaledWidth;
+    }
+  }
+  line = (Guchar *)gmalloc(scaledWidth);
+}
+
+ImageMaskScaler::~ImageMaskScaler() {
+  gfree(tmpBuf0);
+  gfree(tmpBuf1);
+  gfree(tmpBuf2);
+  gfree(accBuf);
+  gfree(line);
+}
+
+void ImageMaskScaler::nextLine() {
+  (this->*scalingFunc)();
+}
+
+void ImageMaskScaler::vertDownscaleHorizDownscale() {
+  //--- vert downscale
+  int yStep = yp;
+  yt += yq;
+  if (yt >= scaledHeight) {
+    yt -= scaledHeight;
+    ++yStep;
+  }
+  memset(accBuf, 0, srcWidth * sizeof(Guint));
+  for (int i = 0; i < yStep; ++i) {
+    (*src)(srcData, tmpBuf0);
+    for (int j = 0; j < srcWidth; ++j) {
+      accBuf[j] += tmpBuf0[j];
+    }
+  }
+
+  //--- horiz downscale
+  int acc;
+  int xt = 0;
+  int unscaledIdx = 0;
+  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
+    int xStep = xp;
+    xt += xq;
+    if (xt >= scaledWidth) {
+      xt -= scaledWidth;
+      ++xStep;
+    }
+
+    acc = 0;
+    for (int i = 0; i < xStep; ++i) {
+      acc += accBuf[unscaledIdx];
+      ++unscaledIdx;
+    }
+    line[scaledIdx] = (Guchar)((255 * acc) / (xStep * yStep));
+  }
+}
+
+void ImageMaskScaler::vertDownscaleHorizUpscaleNoInterp() {
+  //--- vert downscale
+  int yStep = yp;
+  yt += yq;
+  if (yt >= scaledHeight) {
+    yt -= scaledHeight;
+    ++yStep;
+  }
+  memset(accBuf, 0, srcWidth * sizeof(Guint));
+  for (int i = 0; i < yStep; ++i) {
+    (*src)(srcData, tmpBuf0);
+    for (int j = 0; j < srcWidth; ++j) {
+      accBuf[j] += tmpBuf0[j];
+    }
+  }
+
+  //--- horiz upscale
+  int xt = 0;
+  int scaledIdx = 0;
+  for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
+    int xStep = xp;
+    xt += xq;
+    if (xt >= srcWidth) {
+      xt -= srcWidth;
+      ++xStep;
+    }
+    Guchar buf = (Guchar)((255 * accBuf[srcIdx]) / yStep);
+    for (int i = 0; i < xStep; ++i) {
+      line[scaledIdx] = buf;
+      ++scaledIdx;
+    }
+  }
+}
+
+void ImageMaskScaler::vertDownscaleHorizUpscaleInterp() {
+  //--- vert downscale
+  int yStep = yp;
+  yt += yq;
+  if (yt >= scaledHeight) {
+    yt -= scaledHeight;
+    ++yStep;
+  }
+  memset(accBuf, 0, srcWidth * sizeof(Guint));
+  for (int i = 0; i < yStep; ++i) {
+    (*src)(srcData, tmpBuf0);
+    for (int j = 0; j < srcWidth; ++j) {
+      accBuf[j] += tmpBuf0[j];
+    }
+  }
+  for (int j = 0; j < srcWidth; ++j) {
+    accBuf[j] = (255 * accBuf[j]) / yStep;
+  }
+
+  //--- horiz upscale
+  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
+    SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale;
+    int x0 = splashFloor(xs - 0.5);
+    int x1 = x0 + 1;
+    SplashCoord s0 = (SplashCoord)x1 + 0.5 - xs;
+    SplashCoord s1 = (SplashCoord)1 - s0;
+    if (x0 < 0) {
+      x0 = 0;
+    }
+    if (x1 >= srcWidth) {
+      x1 = srcWidth - 1;
+    }
+    line[scaledIdx] = (Guchar)(int)(s0 * accBuf[x0] + s1 * accBuf[x1]);
+  }
+}
+
+void ImageMaskScaler::vertUpscaleHorizDownscaleNoInterp() {
+  //--- vert upscale
+  if (yn == 0) {
+    yn = yp;
+    yt += yq;
+    if (yt >= srcHeight) {
+      yt -= srcHeight;
+      ++yn;
+    }
+    (*src)(srcData, tmpBuf0);
+  }
+  --yn;
+
+  //--- horiz downscale
+  int acc;
+  int xt = 0;
+  int unscaledIdx = 0;
+  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
+    int xStep = xp;
+    xt += xq;
+    if (xt >= scaledWidth) {
+      xt -= scaledWidth;
+      ++xStep;
+    }
+
+    acc = 0;
+    for (int i = 0; i < xStep; ++i) {
+      acc += tmpBuf0[unscaledIdx];
+      ++unscaledIdx;
+    }
+    line[scaledIdx] = (Guchar)((255 * acc) / xStep);
+  }
+}
+
+void ImageMaskScaler::vertUpscaleHorizDownscaleInterp() {
+  //--- vert upscale
+  if (ySrcCur == 0) {
+    (*src)(srcData, tmpBuf0);
+    (*src)(srcData, tmpBuf1);
+    ySrcCur = 1;
+  }
+  SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale;
+  int y0 = splashFloor(ys - 0.5);
+  int y1 = y0 + 1;
+  SplashCoord vs0 = (SplashCoord)y1 + 0.5 - ys;
+  SplashCoord vs1 = (SplashCoord)1 - vs0;
+  if (y1 > ySrcCur && ySrcCur < srcHeight - 1) {
+    Guchar *t = tmpBuf0;
+    tmpBuf0 = tmpBuf1;
+    tmpBuf1 = t;
+    (*src)(srcData, tmpBuf1);
+    ++ySrcCur;
+  }
+  Guchar *mask0 = tmpBuf0;
+  Guchar *mask1 = tmpBuf1;
+  if (y0 < 0) {
+    y0 = 0;
+    mask1 = mask0;
+  }
+  if (y1 >= srcHeight) {
+    y1 = srcHeight - 1;
+    mask0 = mask1;
+  }
+  ++yScaledCur;
+
+  //--- horiz downscale
+  int acc;
+  int xt = 0;
+  int unscaledIdx = 0;
+  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
+    int xStep = xp;
+    xt += xq;
+    if (xt >= scaledWidth) {
+      xt -= scaledWidth;
+      ++xStep;
+    }
+
+    acc = 0;
+    for (int i = 0; i < xStep; ++i) {
+      acc += (int)(vs0 * (int)mask0[unscaledIdx] +
+		   vs1 * (int)mask1[unscaledIdx]);
+      ++unscaledIdx;
+    }
+    line[scaledIdx] = (Guchar)((255 * acc) / xStep);
+  }
+}
+
+void ImageMaskScaler::vertUpscaleHorizUpscaleNoInterp() {
+  //--- vert upscale
+  if (yn == 0) {
+    yn = yp;
+    yt += yq;
+    if (yt >= srcHeight) {
+      yt -= srcHeight;
+      ++yn;
+    }
+    (*src)(srcData, tmpBuf0);
+  }
+  --yn;
+
+  //--- horiz upscale
+  int xt = 0;
+  int scaledIdx = 0;
+  for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
+    int xStep = xp;
+    xt += xq;
+    if (xt >= srcWidth) {
+      xt -= srcWidth;
+      ++xStep;
+    }
+    Guchar buf = (Guchar)(255 * tmpBuf0[srcIdx]);
+    for (int i = 0; i < xStep; ++i) {
+      line[scaledIdx] = buf;
+      ++scaledIdx;
+    }
+  }
+}
+
+void ImageMaskScaler::vertUpscaleHorizUpscaleInterp() {
+  //--- vert upscale
+  if (ySrcCur == 0) {
+    (*src)(srcData, tmpBuf0);
+    (*src)(srcData, tmpBuf1);
+    ySrcCur = 1;
+  }
+  SplashCoord ys = ((SplashCoord)yScaledCur + 0.5) * yInvScale;
+  int y0 = splashFloor(ys - 0.5);
+  int y1 = y0 + 1;
+  SplashCoord vs0 = (SplashCoord)255 * ((SplashCoord)y1 + 0.5 - ys);
+  SplashCoord vs1 = (SplashCoord)255 - vs0;
+  if (y1 > ySrcCur && ySrcCur < srcHeight - 1) {
+    Guchar *t = tmpBuf0;
+    tmpBuf0 = tmpBuf1;
+    tmpBuf1 = t;
+    (*src)(srcData, tmpBuf1);
+    ++ySrcCur;
+  }
+  Guchar *mask0 = tmpBuf0;
+  Guchar *mask1 = tmpBuf1;
+  if (y0 < 0) {
+    y0 = 0;
+    mask1 = mask0;
+  }
+  if (y1 >= srcHeight) {
+    y1 = srcHeight - 1;
+    mask0 = mask1;
+  }
+  ++yScaledCur;
+  for (int srcIdx = 0; srcIdx < srcWidth; ++srcIdx) {
+    tmpBuf2[srcIdx] = (Guchar)(int)(vs0 * (int)mask0[srcIdx] +
+				    vs1 * (int)mask1[srcIdx]);
+  }
+
+  //--- horiz upscale
+  for (int scaledIdx = 0; scaledIdx < scaledWidth; ++scaledIdx) {
+    SplashCoord xs = ((SplashCoord)scaledIdx + 0.5) * xInvScale;
+    int x0 = splashFloor(xs - 0.5);
+    int x1 = x0 + 1;
+    SplashCoord hs0 = (SplashCoord)x1 + 0.5 - xs;
+    SplashCoord hs1 = (SplashCoord)1 - hs0;
+    if (x0 < 0) {
+      x0 = 0;
+    }
+    if (x1 >= srcWidth) {
+      x1 = srcWidth - 1;
+    }
+    line[scaledIdx] = (Guchar)(int)(hs0 * (int)tmpBuf2[x0] +
+				    hs1 * (int)tmpBuf2[x1]);
+  }
+}
+
+//------------------------------------------------------------------------
 // Splash
 //------------------------------------------------------------------------
 
@@ -2472,6 +4360,8 @@
     scanBuf2 = NULL;
   }
   groupBackBitmap = NULL;
+  groupDestInitMode = splashGroupDestPreInit;
+  overprintMaskBitmap = NULL;
   minLineWidth = 0;
   clearModRegion();
   debugMode = gFalse;
@@ -2499,6 +4389,8 @@
     scanBuf2 = NULL;
   }
   groupBackBitmap = NULL;
+  groupDestInitMode = splashGroupDestPreInit;
+  overprintMaskBitmap = NULL;
   minLineWidth = 0;
   clearModRegion();
   debugMode = gFalse;
@@ -2699,14 +4591,23 @@
 
 void Splash::setInTransparencyGroup(SplashBitmap *groupBackBitmapA,
 				    int groupBackXA, int groupBackYA,
+				    SplashGroupDestInitMode groupDestInitModeA,
 				    GBool nonIsolated, GBool knockout) {
   groupBackBitmap = groupBackBitmapA;
   groupBackX = groupBackXA;
   groupBackY = groupBackYA;
+  groupDestInitMode = groupDestInitModeA;
+  groupDestInitYMin = 1;
+  groupDestInitYMax = 0;
   state->inNonIsolatedGroup = nonIsolated;
   state->inKnockoutGroup = knockout;
 }
 
+void Splash::forceDeferredInit(int y, int h) {
+  useDestRow(y);
+  useDestRow(y + h - 1);
+}
+
 void Splash::setTransfer(Guchar *red, Guchar *green, Guchar *blue,
 			 Guchar *gray) {
   state->setTransfer(red, green, blue, gray);
@@ -3839,6 +5740,10 @@
   }
 }
 
+struct SplashDrawImageMaskRowData {
+  SplashPipe pipe;
+};
+
 // The glyphMode flag is not currently used, but may be useful if the
 // stroke adjustment behavior is changed.
 SplashError Splash::fillImageMask(GString *imageTag,
@@ -3845,12 +5750,6 @@
 				  SplashImageMaskSource src, void *srcData,
 				  int w, int h, SplashCoord *mat,
 				  GBool glyphMode, GBool interpolate) {
-  SplashBitmap *scaledMask;
-  SplashClipResult clipRes;
-  GBool minorAxisZero;
-  SplashCoord wSize, hSize, t0, t1;
-  int x0, y0, x1, y1, scaledWidth, scaledHeight;
-
   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],
@@ -3857,1113 +5756,468 @@
 	   (double)mat[3], (double)mat[4], (double)mat[5]);
   }
 
-  // check for singular matrix
+  //--- check for singular matrix
   if (!splashCheckDet(mat[0], mat[1], mat[2], mat[3], 0.000001)) {
     return splashErrSingularMatrix;
   }
 
-  minorAxisZero = splashAbs(mat[1]) <= 0.0001 && splashAbs(mat[2]) <= 0.0001;
-
-  // rough estimate of size of scaled mask
-  t0 = splashAbs(mat[0]);
-  t1 = splashAbs(mat[1]);
-  wSize = t0 > t1 ? t0 : t1;
-  t0 = splashAbs(mat[2]);
-  t1 = splashAbs(mat[3]);
-  hSize = t0 > t1 ? t0 : t1;
-
-  // stream-mode upscaling -- this is slower, so we only use it if the
-  // upscaled mask is large (in which case clipping should remove many
-  // pixels)
-#if USE_FIXEDPOINT
-  if ((wSize > 2 * w && hSize > 2 * h && (int)wSize > 1000000 / (int)hSize) ||
-      (wSize >     w && hSize >     h && (int)wSize > 10000000 / (int)hSize) ||
-      ((wSize > w || hSize > h) && (int)wSize > 25000000 / (int)hSize)) {
-#else
-  if ((wSize > 2 * w && hSize > 2 * h && wSize * hSize > 1000000) ||
-      (wSize >     w && hSize >     h && wSize * hSize > 10000000) ||
-      ((wSize > w || hSize > h) && wSize * hSize > 25000000)) {
-    upscaleMask(src, srcData, w, h, mat, glyphMode, interpolate);
-#endif
-
-  // scaling only
-  } else if (mat[0] > 0 && minorAxisZero && mat[3] > 0) {
-    getImageBounds(mat[4], mat[0] + mat[4], &x0, &x1);
-    getImageBounds(mat[5], mat[3] + mat[5], &y0, &y1);
-    clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1,
-				    state->strokeAdjust);
-    opClipRes = clipRes;
-    if (clipRes != splashClipAllOutside) {
-      scaledWidth = x1 - x0;
-      scaledHeight = y1 - y0;
-      scaledMask = scaleMask(imageTag, src, srcData, w, h,
-			     scaledWidth, scaledHeight, interpolate);
-      if (imageCache->vertFlip) {
-	vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1);
-	imageCache->vertFlip = gFalse;
+  //--- compute image bbox, check clipping
+  GBool flipsOnly = splashAbs(mat[1]) <= 0.0001 && splashAbs(mat[2]) <= 0.0001;
+  GBool horizFlip = gFalse;
+  GBool vertFlip = gFalse;
+  int xMin, yMin, xMax, yMax;
+  if (flipsOnly) {
+    horizFlip = mat[0] < 0;
+    vertFlip = mat[3] < 0;
+    if (vertFlip) {
+      if (horizFlip) {    // bottom-up, mirrored
+	getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax);
+	getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax);
+      } else {            // bottom-up
+	getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax);
+	getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax);
       }
-      if (imageCache->horizFlip) {
-	horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1);
-	imageCache->horizFlip = gFalse;
+    } else {
+      if (horizFlip) {    // top-down, mirrored
+	getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax);
+	getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax);
+      } else {            // top-down
+	getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax);
+	getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax);
       }
-      blitMask(scaledMask, x0, y0, clipRes);
     }
-    
-  // scaling plus vertical flip
-  } else if (mat[0] > 0 && minorAxisZero && mat[3] < 0) {
-    getImageBounds(mat[4], mat[0] + mat[4], &x0, &x1);
-    getImageBounds(mat[3] + mat[5], mat[5], &y0, &y1);
-    clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1,
-				    state->strokeAdjust);
-    opClipRes = clipRes;
-    if (clipRes != splashClipAllOutside) {
-      scaledWidth = x1 - x0;
-      scaledHeight = y1 - y0;
-      scaledMask = scaleMask(imageTag, src, srcData, w, h,
-			     scaledWidth, scaledHeight, interpolate);
-      if (!imageCache->vertFlip) {
-	vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1);
-	imageCache->vertFlip = gTrue;
-      }
-      if (imageCache->horizFlip) {
-	horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1);
-	imageCache->horizFlip = gFalse;
-      }
-      blitMask(scaledMask, x0, y0, clipRes);
+  } else {
+    int xx = splashRound(mat[4]);		// (0,0)
+    int yy = splashRound(mat[5]);
+    xMin = xMax = xx;
+    yMin = yMax = yy;
+    xx = splashRound(mat[0] + mat[4]);		// (1,0)
+    yy = splashRound(mat[1] + mat[5]);
+    if (xx < xMin) {
+      xMin = xx;
+    } else if (xx > xMax) {
+      xMax = xx;
     }
-
-  // scaling plus horizontal flip
-  } else if (mat[0] < 0 && minorAxisZero && mat[3] > 0) {
-    getImageBounds(mat[0] + mat[4], mat[4], &x0, &x1);
-    getImageBounds(mat[5], mat[3] + mat[5], &y0, &y1);
-    clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1,
-				    state->strokeAdjust);
-    opClipRes = clipRes;
-    if (clipRes != splashClipAllOutside) {
-      scaledWidth = x1 - x0;
-      scaledHeight = y1 - y0;
-      scaledMask = scaleMask(imageTag, src, srcData, w, h,
-			     scaledWidth, scaledHeight, interpolate);
-      if (imageCache->vertFlip) {
-	vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1);
-	imageCache->vertFlip = gFalse;
-      }
-      if (!imageCache->horizFlip) {
-	horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1);
-	imageCache->horizFlip = gTrue;
-      }
-      blitMask(scaledMask, x0, y0, clipRes);
+    if (yy < yMin) {
+      yMin = yy;
+    } else if (yy > yMax) {
+      yMax = yy;
     }
-
-  // scaling plus horizontal and vertical flips
-  } else if (mat[0] < 0 && minorAxisZero && mat[3] < 0) {
-    getImageBounds(mat[0] + mat[4], mat[4], &x0, &x1);
-    getImageBounds(mat[3] + mat[5], mat[5], &y0, &y1);
-    clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1,
-				    state->strokeAdjust);
-    opClipRes = clipRes;
-    if (clipRes != splashClipAllOutside) {
-      scaledWidth = x1 - x0;
-      scaledHeight = y1 - y0;
-      scaledMask = scaleMask(imageTag, src, srcData, w, h,
-			     scaledWidth, scaledHeight, interpolate);
-      if (!imageCache->vertFlip) {
-	vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1);
-	imageCache->vertFlip = gTrue;
-      }
-      if (!imageCache->horizFlip) {
-	horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1);
-	imageCache->horizFlip = gTrue;
-      }
-      blitMask(scaledMask, x0, y0, clipRes);
+    xx = splashRound(mat[2] + mat[4]);		// (0,1)
+    yy = splashRound(mat[3] + mat[5]);
+    if (xx < xMin) {
+      xMin = xx;
+    } else if (xx > xMax) {
+      xMax = xx;
     }
-
-  // all other cases
-  } else {
-    arbitraryTransformMask(imageTag, src, srcData, w, h,
-			   mat, glyphMode, interpolate);
-  }
-
-  return splashOk;
-}
-
-// The glyphMode flag is not currently used, but may be useful if the
-// stroke adjustment behavior is changed.
-void Splash::upscaleMask(SplashImageMaskSource src, void *srcData,
-			 int srcWidth, int srcHeight,
-			 SplashCoord *mat, GBool glyphMode,
-			 GBool interpolate) {
-  SplashClipResult clipRes;
-  SplashPipe pipe;
-  Guchar *unscaledImage, *p;
-  SplashCoord xMin, yMin, xMax, yMax, t;
-  SplashCoord mi0, mi1, mi2, mi3, mi4, mi5, det;
-  SplashCoord ix, iy, sx, sy, pix0, pix1;
-  int xMinI, yMinI, xMaxI, yMaxI, x, y, x0, y0, x1, y1, tt;
-
-  // compute the bbox of the target quadrilateral
-  xMin = xMax = mat[4];
-  t = mat[2] + mat[4];
-  if (t < xMin) {
-    xMin = t;
-  } else if (t > xMax) {
-    xMax = t;
-  }
-  t = mat[0] + mat[2] + mat[4];
-  if (t < xMin) {
-    xMin = t;
-  } else if (t > xMax) {
-    xMax = t;
-  }
-  t = mat[0] + mat[4];
-  if (t < xMin) {
-    xMin = t;
-  } else if (t > xMax) {
-    xMax = t;
-  }
-  getImageBounds(xMin, xMax, &xMinI, &xMaxI);
-  yMin = yMax = mat[5];
-  t = mat[3] + mat[5];
-  if (t < yMin) {
-    yMin = t;
-  } else if (t > yMax) {
-    yMax = t;
-  }
-  t = mat[1] + mat[3] + mat[5];
-  if (t < yMin) {
-    yMin = t;
-  } else if (t > yMax) {
-    yMax = t;
-  }
-  t = mat[1] + mat[5];
-  if (t < yMin) {
-    yMin = t;
-  } else if (t > yMax) {
-    yMax = t;
-  }
-  getImageBounds(yMin, yMax, &yMinI, &yMaxI);
-
-  // clipping
-  clipRes = state->clip->testRect(xMinI, yMinI, xMaxI - 1, yMaxI - 1,
-				  state->strokeAdjust);
-  opClipRes = clipRes;
-  if (clipRes == splashClipAllOutside) {
-    return;
-  }
-  if (clipRes != splashClipAllInside) {
-    if ((tt = state->clip->getXMinI(state->strokeAdjust)) > xMinI) {
-      xMinI = tt;
+    if (yy < yMin) {
+      yMin = yy;
+    } else if (yy > yMax) {
+      yMax = yy;
     }
-    if ((tt = state->clip->getXMaxI(state->strokeAdjust) + 1) < xMaxI) {
-      xMaxI = tt;
+    xx = splashRound(mat[0] + mat[2] + mat[4]);	// (1,1)
+    yy = splashRound(mat[1] + mat[3] + mat[5]);
+    if (xx < xMin) {
+      xMin = xx;
+    } else if (xx > xMax) {
+      xMax = xx;
     }
-    if ((tt = state->clip->getYMinI(state->strokeAdjust)) > yMinI) {
-      yMinI = tt;
+    if (yy < yMin) {
+      yMin = yy;
+    } else if (yy > yMax) {
+      yMax = yy;
     }
-    if ((tt = state->clip->getYMaxI(state->strokeAdjust) + 1) < yMaxI) {
-      yMaxI = tt;
+    if (xMax <= xMin) {
+      xMax = xMin + 1;
     }
+    if (yMax <= yMin) {
+      yMax = yMin + 1;
+    }
   }
+  SplashClipResult clipRes =
+      state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1,
+			    state->strokeAdjust);
+  // If the scaled mask is much wider and/or taller than the clip
+  // region, we use the "arbitrary" scaling path, to avoid a
+  // potentially very slow loop in the flips-only path (which scans
+  // the full width and height of the scaled mask, regardless of the
+  // clip region).
+  int clipW = state->clip->getXMaxI(state->strokeAdjust)
+	      - state->clip->getXMinI(state->strokeAdjust);
+  int clipH = state->clip->getYMaxI(state->strokeAdjust)
+	      - state->clip->getYMinI(state->strokeAdjust);
+  GBool veryLarge = ((xMax - xMin) / 8 > clipW && xMax - xMin > 1000) ||
+                    ((yMax - yMin) / 8 > clipH && yMax - yMin > 1000);
 
-  // invert the matrix
-  det = mat[0] * mat[3] - mat[1] * mat[2];
-  if (splashAbs(det) < 1e-6) {
-    // this should be caught by the singular matrix check in fillImageMask
-    return;
-  }
-  det = (SplashCoord)1 / det;
-  mi0 = det * mat[3] * srcWidth;
-  mi1 = -det * mat[1] * srcHeight;
-  mi2 = -det * mat[2] * srcWidth;
-  mi3 = det * mat[0] * srcHeight;
-  mi4 = det * (mat[2] * mat[5] - mat[3] * mat[4]) * srcWidth;
-  mi5 = -det * (mat[0] * mat[5] - mat[1] * mat[4]) * srcHeight;
+  //--- set up the SplashDrawImageMaskRowData object and the pipes
+  SplashDrawImageMaskRowData dd;
+  pipeInit(&dd.pipe, state->fillPattern,
+	   (Guchar)splashRound(state->fillAlpha * 255),
+	   gTrue, gFalse);
 
-  // grab the image
-  unscaledImage = (Guchar *)gmallocn(srcWidth, srcHeight);
-  for (y = 0, p = unscaledImage; y < srcHeight; ++y, p += srcWidth) {
-    (*src)(srcData, p);
-    for (x = 0; x < srcWidth; ++x) {
-      p[x] = (Guchar)(p[x] * 255);
+  //--- choose the drawRow function
+  SplashDrawImageMaskRowFunc drawRowFunc;
+  if (clipRes == splashClipAllInside) {
+    drawRowFunc = &Splash::drawImageMaskRowNoClip;
+  } else {
+    if (vectorAntialias) {
+      drawRowFunc = &Splash::drawImageMaskRowClipAA;
+    } else {
+      drawRowFunc = &Splash::drawImageMaskRowClipNoAA;
     }
   }
 
-  // draw it
-  pipeInit(&pipe, state->fillPattern,
-	   (Guchar)splashRound(state->fillAlpha * 255),
-	   gTrue, gFalse);
-  for (y = yMinI; y < yMaxI; ++y) {
-    for (x = xMinI; x < xMaxI; ++x) {
-      ix = ((SplashCoord)x + 0.5) * mi0 + ((SplashCoord)y + 0.5) * mi2 + mi4;
-      iy = ((SplashCoord)x + 0.5) * mi1 + ((SplashCoord)y + 0.5) * mi3 + mi5;
-      if (interpolate) {
-	if (ix >= 0 && ix < srcWidth && iy >= 0 && iy < srcHeight) {
-	  x0 = splashFloor(ix - 0.5);
-	  x1 = x0 + 1;
-	  sx = (ix - 0.5) - x0;
-	  y0 = splashFloor(iy - 0.5);
-	  y1 = y0 + 1;
-	  sy = (iy - 0.5) - y0;
-	  if (x0 < 0) {
-	    x0 = 0;
+  //--- horizontal/vertical flips only
+  if (flipsOnly && !veryLarge) {
+    if (clipRes != splashClipAllOutside) {
+      int scaledWidth = xMax - xMin;
+      int scaledHeight = yMax - yMin;
+      ImageMaskScaler scaler(src, srcData, w, h,
+			     scaledWidth, scaledHeight, interpolate);
+      Guchar *tmpLine = NULL;
+      if (horizFlip) {
+	tmpLine = (Guchar *)gmalloc(scaledWidth);
+      }
+      if (vertFlip) {
+	if (horizFlip) {    // bottom-up, mirrored
+	  for (int y = 0; y < scaledHeight; ++y) {
+	    scaler.nextLine();
+	    mirrorImageMaskRow(scaler.data(), tmpLine, scaledWidth);
+	    (this->*drawRowFunc)(&dd, tmpLine,
+				 xMin, yMax - 1 - y, scaledWidth);
 	  }
-	  if (x1 >= srcWidth) {
-	    x1 = srcWidth - 1;
+	} else {            // bottom-up
+	  for (int y = 0; y < scaledHeight; ++y) {
+	    scaler.nextLine();
+	    (this->*drawRowFunc)(&dd, scaler.data(),
+				 xMin, yMax - 1 - y, scaledWidth);
 	  }
-	  if (y0 < 0) {
-	    y0 = 0;
+	}
+      } else {
+	if (horizFlip) {    // top-down, mirrored
+	  for (int y = 0; y < scaledHeight; ++y) {
+	    scaler.nextLine();
+	    mirrorImageMaskRow(scaler.data(), tmpLine, scaledWidth);
+	    (this->*drawRowFunc)(&dd, tmpLine,
+				 xMin, yMin + y, scaledWidth);
 	  }
-	  if (y1 >= srcHeight) {
-	    y1 = srcHeight - 1;
+	} else {            // top-down
+	  for (int y = 0; y < scaledHeight; ++y) {
+	    scaler.nextLine();
+	    (this->*drawRowFunc)(&dd, scaler.data(),
+				 xMin, yMin + y, scaledWidth);
 	  }
-	  pix0 = ((SplashCoord)1 - sx)
-	           * (SplashCoord)unscaledImage[y0 * srcWidth + x0]
-	         + sx * (SplashCoord)unscaledImage[y0 * srcWidth + x1];
-	  pix1 = ((SplashCoord)1 - sx)
-	           * (SplashCoord)unscaledImage[y1 * srcWidth + x0]
-	         + sx * (SplashCoord)unscaledImage[y1 * srcWidth + x1];
-	  scanBuf[x] = (Guchar)splashRound(((SplashCoord)1 - sy) * pix0
-					   + sy * pix1);
-	} else {
-	  scanBuf[x] = 0;
 	}
-      } else {
-	x0 = splashFloor(ix);
-	y0 = splashFloor(iy);
-	if (x0 >= 0 && x0 < srcWidth && y0 >= 0 && y0 < srcHeight) {
-	  scanBuf[x] = unscaledImage[y0 * srcWidth + x0];
-	} else {
-	  scanBuf[x] = 0;
-	}
       }
+      gfree(tmpLine);
     }
-    if (clipRes != splashClipAllInside) {
-      if (vectorAntialias) {
-	state->clip->clipSpan(scanBuf, y, xMinI, xMaxI - 1,
-			      state->strokeAdjust);
-      } else {
-	state->clip->clipSpanBinary(scanBuf, y, xMinI, xMaxI - 1,
-				    state->strokeAdjust);
-      }
-    }
-    (this->*pipe.run)(&pipe, xMinI, xMaxI - 1, y, scanBuf + xMinI, NULL);
-  }
 
-  gfree(unscaledImage);
-}
-
-// The glyphMode flag is not currently used, but may be useful if the
-// stroke adjustment behavior is changed.
-void Splash::arbitraryTransformMask(GString *imageTag,
-				    SplashImageMaskSource src, void *srcData,
-				    int srcWidth, int srcHeight,
-				    SplashCoord *mat, GBool glyphMode,
-				    GBool interpolate) {
-  SplashBitmap *scaledMask;
-  SplashClipResult clipRes;
-  SplashPipe pipe;
-  int scaledWidth, scaledHeight, t0, t1;
-  SplashCoord r00, r01, r10, r11, det, ir00, ir01, ir10, ir11;
-  SplashCoord vx[4], vy[4];
-  int xMin, yMin, xMax, yMax;
-  ImageSection section[3];
-  int nSections;
-  int bw, y, xa, xb, x, i, xx, yy;
-
-  // compute the four vertices of the target quadrilateral
-  vx[0] = mat[4];                    vy[0] = mat[5];
-  vx[1] = mat[2] + mat[4];           vy[1] = mat[3] + mat[5];
-  vx[2] = mat[0] + mat[2] + mat[4];  vy[2] = mat[1] + mat[3] + mat[5];
-  vx[3] = mat[0] + mat[4];           vy[3] = mat[1] + mat[5];
-
-  // clipping
-  xMin = splashRound(vx[0]);
-  xMax = splashRound(vx[0]);
-  yMin = splashRound(vy[0]);
-  yMax = splashRound(vy[0]);
-  for (i = 1; i < 4; ++i) {
-    t0 = splashRound(vx[i]);
-    if (t0 < xMin) {
-      xMin = t0;
-    } else if (t0 > xMax) {
-      xMax = t0;
+  //--- arbitrary transform
+  } else {
+    // estimate of size of scaled image
+    int scaledWidth = splashRound(splashSqrt(mat[0] * mat[0]
+					     + mat[1] * mat[1]));
+    int scaledHeight = splashRound(splashSqrt(mat[2] * mat[2]
+					      + mat[3] * mat[3]));
+    if (scaledWidth < 1) {
+      scaledWidth = 1;
     }
-    t1 = splashRound(vy[i]);
-    if (t1 < yMin) {
-      yMin = t1;
-    } else if (t1 > yMax) {
-      yMax = t1;
+    if (scaledHeight < 1) {
+      scaledHeight = 1;
     }
-  }
-  clipRes = state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1,
-				  state->strokeAdjust);
-  opClipRes = clipRes;
-  if (clipRes == splashClipAllOutside) {
-    return;
-  }
+    GBool downscaling = gTrue;
+    if (veryLarge || (scaledWidth >= w && scaledHeight >= h)) {
+      downscaling = gFalse;
+      scaledWidth = w;
+      scaledHeight = h;
+    }
 
-  // compute the scale factors
-  if (mat[0] >= 0) {
-    t0 = splashRound(mat[0] + mat[4]) - splashRound(mat[4]);
-  } else {
-    t0 = splashRound(mat[4]) - splashRound(mat[0] + mat[4]);
-  }
-  if (mat[1] >= 0) {
-    t1 = splashRound(mat[1] + mat[5]) - splashRound(mat[5]);
-  } else {
-    t1 = splashRound(mat[5]) - splashRound(mat[1] + mat[5]);
-  }
-  scaledWidth = t0 > t1 ? t0 : t1;
-  if (mat[2] >= 0) {
-    t0 = splashRound(mat[2] + mat[4]) - splashRound(mat[4]);
-  } else {
-    t0 = splashRound(mat[4]) - splashRound(mat[2] + mat[4]);
-  }
-  if (mat[3] >= 0) {
-    t1 = splashRound(mat[3] + mat[5]) - splashRound(mat[5]);
-  } else {
-    t1 = splashRound(mat[5]) - splashRound(mat[3] + mat[5]);
-  }
-  scaledHeight = t0 > t1 ? t0 : t1;
-  if (scaledWidth == 0) {
-    scaledWidth = 1;
-  }
-  if (scaledHeight == 0) {
-    scaledHeight = 1;
-  }
+    // compute mapping from device space to scaled image space
+    SplashCoord mat1[6];
+    mat1[0] = mat[0] / scaledWidth;
+    mat1[1] = mat[1] / scaledWidth;
+    mat1[2] = mat[2] / scaledHeight;
+    mat1[3] = mat[3] / scaledHeight;
+    mat1[4] = mat[4];
+    mat1[5] = mat[5];
+    SplashCoord det = mat1[0] * mat1[3] - mat1[1] * mat1[2];
+    if (splashAbs(det) < 1e-6) {
+      // this should be caught by the singular matrix check in drawImage
+      return splashErrSingularMatrix;
+    }
+    SplashCoord invMat[6];
+    invMat[0] = mat1[3] / det;
+    invMat[1] = -mat1[1] / det;
+    invMat[2] = -mat1[2] / det;
+    invMat[3] = mat1[0] / det;
+    // the extra "+ 0.5 * (...)" terms are here because the
+    // drawImageArbitrary(No)Interp functions multiply by pixel
+    // centers, (x + 0.5, y + 0.5)
+    invMat[4] = (mat1[2] * mat1[5] - mat1[3] * mat1[4]) / det
+                + (invMat[0] + invMat[2]) * 0.5;
+    invMat[5] = (mat1[1] * mat1[4] - mat1[0] * mat1[5]) / det
+                + (invMat[1] + invMat[3]) * 0.5;
 
-  // compute the inverse transform (after scaling) matrix
-  r00 = mat[0] / scaledWidth;
-  r01 = mat[1] / scaledWidth;
-  r10 = mat[2] / scaledHeight;
-  r11 = mat[3] / scaledHeight;
-  det = r00 * r11 - r01 * r10;
-  if (splashAbs(det) < 1e-6) {
-    // this should be caught by the singular matrix check in fillImageMask
-    return;
-  }
-  ir00 = r11 / det;
-  ir01 = -r01 / det;
-  ir10 = -r10 / det;
-  ir11 = r00 / det;
-
-  // scale the input image
-  scaledMask = scaleMask(imageTag, src, srcData, srcWidth, srcHeight,
-			 scaledWidth, scaledHeight, interpolate);
-  if (imageCache->vertFlip) {
-    vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1);
-    imageCache->vertFlip = gFalse;
-  }
-  if (imageCache->horizFlip) {
-    horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1);
-    imageCache->horizFlip = gFalse;
-  }
-
-  // construct the three sections
-  i = 0;
-  if (vy[1] < vy[i]) {
-    i = 1;
-  }
-  if (vy[2] < vy[i]) {
-    i = 2;
-  }
-  if (vy[3] < vy[i]) {
-    i = 3;
-  }
-  // NB: if using fixed point, 0.000001 will be truncated to zero,
-  // so these two comparisons must be <=, not <
-  if (splashAbs(vy[i] - vy[(i-1) & 3]) <= 0.000001 &&
-      vy[(i-1) & 3] < vy[(i+1) & 3]) {
-    i = (i-1) & 3;
-  }
-  if (splashAbs(vy[i] - vy[(i+1) & 3]) <= 0.000001) {
-    section[0].y0 = splashRound(vy[i]);
-    section[0].y1 = splashRound(vy[(i+2) & 3]) - 1;
-    if (vx[i] < vx[(i+1) & 3]) {
-      section[0].ia0 = i;
-      section[0].ia1 = (i+3) & 3;
-      section[0].ib0 = (i+1) & 3;
-      section[0].ib1 = (i+2) & 3;
-    } else {
-      section[0].ia0 = (i+1) & 3;
-      section[0].ia1 = (i+2) & 3;
-      section[0].ib0 = i;
-      section[0].ib1 = (i+3) & 3;
-    }
-    nSections = 1;
-  } else {
-    section[0].y0 = splashRound(vy[i]);
-    section[2].y1 = splashRound(vy[(i+2) & 3]) - 1;
-    section[0].ia0 = section[0].ib0 = i;
-    section[2].ia1 = section[2].ib1 = (i+2) & 3;
-    if (vx[(i+1) & 3] < vx[(i+3) & 3]) {
-      section[0].ia1 = section[2].ia0 = (i+1) & 3;
-      section[0].ib1 = section[2].ib0 = (i+3) & 3;
-    } else {
-      section[0].ia1 = section[2].ia0 = (i+3) & 3;
-      section[0].ib1 = section[2].ib0 = (i+1) & 3;
-    }
-    if (vy[(i+1) & 3] < vy[(i+3) & 3]) {
-      section[1].y0 = splashRound(vy[(i+1) & 3]);
-      section[2].y0 = splashRound(vy[(i+3) & 3]);
-      if (vx[(i+1) & 3] < vx[(i+3) & 3]) {
-	section[1].ia0 = (i+1) & 3;
-	section[1].ia1 = (i+2) & 3;
-	section[1].ib0 = i;
-	section[1].ib1 = (i+3) & 3;
-      } else {
-	section[1].ia0 = i;
-	section[1].ia1 = (i+3) & 3;
-	section[1].ib0 = (i+1) & 3;
-	section[1].ib1 = (i+2) & 3;
+    // if downscaling: store the downscaled image mask
+    // if upscaling: store the unscaled image mask
+    Guchar *scaledMask = (Guchar *)gmallocn(scaledHeight, scaledWidth);
+    if (downscaling) {
+      ImageMaskScaler scaler(src, srcData, w, h,
+			     scaledWidth, scaledHeight, interpolate);
+      Guchar *ptr = scaledMask;
+      for (int y = 0; y < scaledHeight; ++y) {
+	scaler.nextLine();
+	memcpy(ptr, scaler.data(), scaledWidth);
+	ptr += scaledWidth;
       }
     } else {
-      section[1].y0 = splashRound(vy[(i+3) & 3]);
-      section[2].y0 = splashRound(vy[(i+1) & 3]);
-      if (vx[(i+1) & 3] < vx[(i+3) & 3]) {
-	section[1].ia0 = i;
-	section[1].ia1 = (i+1) & 3;
-	section[1].ib0 = (i+3) & 3;
-	section[1].ib1 = (i+2) & 3;
-      } else {
-	section[1].ia0 = (i+3) & 3;
-	section[1].ia1 = (i+2) & 3;
-	section[1].ib0 = i;
-	section[1].ib1 = (i+1) & 3;
+      Guchar *ptr = scaledMask;
+      for (int y = 0; y < scaledHeight; ++y) {
+	(*src)(srcData, ptr);
+	for (int x = 0; x < scaledWidth; ++x) {
+	  *ptr = (Guchar)(*ptr * 255);
+	  ++ptr;
+	}
       }
     }
-    section[0].y1 = section[1].y0 - 1;
-    section[1].y1 = section[2].y0 - 1;
-    nSections = 3;
-  }
-  for (i = 0; i < nSections; ++i) {
-    section[i].xa0 = vx[section[i].ia0];
-    section[i].ya0 = vy[section[i].ia0];
-    section[i].xa1 = vx[section[i].ia1];
-    section[i].ya1 = vy[section[i].ia1];
-    section[i].xb0 = vx[section[i].ib0];
-    section[i].yb0 = vy[section[i].ib0];
-    section[i].xb1 = vx[section[i].ib1];
-    section[i].yb1 = vy[section[i].ib1];
-    section[i].dxdya = (section[i].xa1 - section[i].xa0) /
-                       (section[i].ya1 - section[i].ya0);
-    section[i].dxdyb = (section[i].xb1 - section[i].xb0) /
-                       (section[i].yb1 - section[i].yb0);
-  }
 
-  // initialize the pixel pipe
-  pipeInit(&pipe, state->fillPattern,
-	   (Guchar)splashRound(state->fillAlpha * 255),
-	   gTrue, gFalse);
+    // draw it
+    if (interpolate) {
+      drawImageMaskArbitraryInterp(scaledMask,
+				   &dd, drawRowFunc, invMat,
+				   scaledWidth, scaledHeight,
+				   xMin, yMin, xMax, yMax);
+    } else {
+      drawImageMaskArbitraryNoInterp(scaledMask,
+				     &dd, drawRowFunc, invMat,
+				     scaledWidth, scaledHeight,
+				     xMin, yMin, xMax, yMax);
+    }
 
-  // make sure narrow images cover at least one pixel
-  if (nSections == 1) {
-    if (section[0].y0 == section[0].y1) {
-      ++section[0].y1;
-      clipRes = opClipRes = splashClipPartial;
-    }
-  } else {
-    if (section[0].y0 == section[2].y1) {
-      ++section[1].y1;
-      clipRes = opClipRes = splashClipPartial;
-    }
+    // free the downscaled/unscaled image
+    gfree(scaledMask);
   }
 
-  // scan all pixels inside the target region
-  bw = bitmap->width;
-  for (i = 0; i < nSections; ++i) {
-    for (y = section[i].y0; y <= section[i].y1; ++y) {
-      xa = splashRound(section[i].xa0 +
-		         ((SplashCoord)y + 0.5 - section[i].ya0) *
-		           section[i].dxdya);
-      xb = splashRound(section[i].xb0 +
-		         ((SplashCoord)y + 0.5 - section[i].yb0) *
-		           section[i].dxdyb);
-      if (xa > xb) {
-	continue;
-      }
-      // make sure narrow images cover at least one pixel
-      if (xa == xb) {
-	++xb;
-      }
-      // check the scanBuf bounds
-      if (xa >= bw || xb < 0) {
-	continue;
-      }
-      if (xa < 0) {
-	xa = 0;
-      }
-      if (xb > bw) {
-	xb = bw;
-      }
-      // get the scan line
-      for (x = xa; x < xb; ++x) {
-	// map (x+0.5, y+0.5) back to the scaled image
-	xx = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir00 +
-			 ((SplashCoord)y + 0.5 - mat[5]) * ir10);
-	yy = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir01 +
-			 ((SplashCoord)y + 0.5 - mat[5]) * ir11);
-	// xx should always be within bounds, but floating point
-	// inaccuracy can cause problems
-	if (xx < 0) {
-	  xx = 0;
-	} else if (xx >= scaledWidth) {
-	  xx = scaledWidth - 1;
-	}
-	if (yy < 0) {
-	  yy = 0;
-	} else if (yy >= scaledHeight) {
-	  yy = scaledHeight - 1;
-	}
-	scanBuf[x] = scaledMask->data[yy * scaledWidth + xx];
-      }
-      // clip the scan line
-      if (clipRes != splashClipAllInside) {
-	if (vectorAntialias) {
-	  state->clip->clipSpan(scanBuf, y, xa, xb - 1, state->strokeAdjust);
-	} else {
-	  state->clip->clipSpanBinary(scanBuf, y, xa, xb - 1,
-				      state->strokeAdjust);
-	}
-      }
-      // draw the scan line
-      (this->*pipe.run)(&pipe, xa, xb - 1, y, scanBuf + xa, NULL);
-    }
-  }
+  return splashOk;
 }
 
-// Scale an image mask into a SplashBitmap.
-SplashBitmap *Splash::scaleMask(GString *imageTag,
-				SplashImageMaskSource src, void *srcData,
-				int srcWidth, int srcHeight,
-				int scaledWidth, int scaledHeight,
-				GBool interpolate) {
-  if (imageCache->tag && imageTag &&
-      !imageCache->tag->cmp(imageTag) &&
-      imageCache->isMask &&
-      imageCache->width == scaledWidth &&
-      imageCache->height == scaledHeight &&
-      imageCache->interpolate == interpolate) {
-    return imageCache->image;
+void Splash::drawImageMaskArbitraryNoInterp(
+				   Guchar *scaledMask,
+				   SplashDrawImageMaskRowData *dd,
+				   SplashDrawImageMaskRowFunc drawRowFunc,
+				   SplashCoord *invMat,
+				   int scaledWidth, int scaledHeight,
+				   int xMin, int yMin, int xMax, int yMax) {
+  int tt = state->clip->getXMinI(state->strokeAdjust);
+  if (tt > xMin) {
+    xMin = tt;
   }
-  if (imageCache->tag) {
-    delete imageCache->tag;
+  tt = state->clip->getXMaxI(state->strokeAdjust) + 1;
+  if (tt < xMax) {
+    xMax = tt;
   }
-  if (imageCache->image) {
-    delete imageCache->image;
+  tt = state->clip->getYMinI(state->strokeAdjust);
+  if (tt > yMin) {
+    yMin = tt;
   }
-  imageCache->tag = imageTag ? imageTag->copy() : (GString *)NULL;
-  imageCache->isMask = gTrue;
-  imageCache->width = scaledWidth;
-  imageCache->height = scaledHeight;
-  imageCache->interpolate = interpolate;
-  imageCache->vertFlip = gFalse;
-  imageCache->horizFlip = gFalse;
-  imageCache->image = new SplashBitmap(scaledWidth, scaledHeight,
-				       1, splashModeMono8, gFalse,
-				       gTrue, NULL);
-  if (scaledHeight < srcHeight) {
-    if (scaledWidth < srcWidth) {
-      scaleMaskYdXd(src, srcData, srcWidth, srcHeight,
-		    scaledWidth, scaledHeight, imageCache->image);
-    } else {
-      scaleMaskYdXu(src, srcData, srcWidth, srcHeight,
-		    scaledWidth, scaledHeight, imageCache->image);
-    }
-  } else {
-    if (scaledWidth < srcWidth) {
-      scaleMaskYuXd(src, srcData, srcWidth, srcHeight,
-		    scaledWidth, scaledHeight, imageCache->image);
-    } else {
-      if (interpolate) {
-	scaleMaskYuXuI(src, srcData, srcWidth, srcHeight,
-		       scaledWidth, scaledHeight, imageCache->image);
-      } else {
-	scaleMaskYuXu(src, srcData, srcWidth, srcHeight,
-		      scaledWidth, scaledHeight, imageCache->image);
-      }
-    }
+  tt = state->clip->getYMaxI(state->strokeAdjust) + 1;
+  if (tt < yMax) {
+    yMax = tt;
   }
-  return imageCache->image;
-}
+  if (xMax <= xMin || yMax <= yMin) {
+    return;
+  }
 
-void Splash::scaleMaskYdXd(SplashImageMaskSource src, void *srcData,
-			   int srcWidth, int srcHeight,
-			   int scaledWidth, int scaledHeight,
-			   SplashBitmap *dest) {
-  Guchar *lineBuf;
-  Guint *pixBuf;
-  Guint pix;
-  Guchar *destPtr;
-  int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, d, d0, d1;
-  int i, j;
+  Guchar *buf = (Guchar *)gmalloc(xMax - xMin);
 
-  // Bresenham parameters for y scale
-  yp = srcHeight / scaledHeight;
-  yq = srcHeight % scaledHeight;
-
-  // Bresenham parameters for x scale
-  xp = srcWidth / scaledWidth;
-  xq = srcWidth % scaledWidth;
-
-  // allocate buffers
-  lineBuf = (Guchar *)gmalloc(srcWidth);
-  pixBuf = (Guint *)gmallocn(srcWidth, sizeof(int));
-
-  // init y scale Bresenham
-  yt = 0;
-
-  destPtr = dest->data;
-  for (y = 0; y < scaledHeight; ++y) {
-
-    // y scale Bresenham
-    if ((yt += yq) >= scaledHeight) {
-      yt -= scaledHeight;
-      yStep = yp + 1;
-    } else {
-      yStep = yp;
-    }
-
-    // read rows from image
-    memset(pixBuf, 0, srcWidth * sizeof(int));
-    for (i = 0; i < yStep; ++i) {
-      (*src)(srcData, lineBuf);
-      for (j = 0; j < srcWidth; ++j) {
-	pixBuf[j] += lineBuf[j];
+  for (int y = yMin; y < yMax; ++y) {
+    int rowMin = xMax;
+    int rowMax = 0;
+    for (int x = xMin; x < xMax; ++x) {
+      // note: invMat includes a "+0.5" factor so that this is really
+      // a multiply by (x+0.5, y+0.5)
+      int xx = splashFloor((SplashCoord)x * invMat[0]
+			   + (SplashCoord)y * invMat[2] + invMat[4]);
+      int yy = splashFloor((SplashCoord)x * invMat[1]
+			   + (SplashCoord)y * invMat[3] + invMat[5]);
+      if (xx >= 0 && xx < scaledWidth &&
+	  yy >= 0 && yy < scaledHeight) {
+	Guchar *p = scaledMask + (yy * scaledWidth + xx);
+	Guchar *q = buf + (x - xMin);
+	*q = *p;
+	if (x < rowMin) {
+	  rowMin = x;
+	}
+	rowMax = x + 1;
       }
     }
-
-    // init x scale Bresenham
-    xt = 0;
-    d0 = (255 << 23) / (yStep * xp);
-    d1 = (255 << 23) / (yStep * (xp + 1));
-
-    xx = 0;
-    for (x = 0; x < scaledWidth; ++x) {
-
-      // x scale Bresenham
-      if ((xt += xq) >= scaledWidth) {
-	xt -= scaledWidth;
-	xStep = xp + 1;
-	d = d1;
-      } else {
-	xStep = xp;
-	d = d0;
-      }
-
-      // compute the final pixel
-      pix = 0;
-      for (i = 0; i < xStep; ++i) {
-	pix += pixBuf[xx++];
-      }
-      // (255 * pix) / xStep * yStep
-      pix = (pix * d + (1 << 22)) >> 23;
-
-      // store the pixel
-      *destPtr++ = (Guchar)pix;
+    if (rowMin < rowMax) {
+      (this->*drawRowFunc)(dd, buf + (rowMin - xMin),
+			   rowMin, y, rowMax - rowMin);
     }
   }
 
-  gfree(pixBuf);
-  gfree(lineBuf);
+  gfree(buf);
 }
 
-void Splash::scaleMaskYdXu(SplashImageMaskSource src, void *srcData,
-			   int srcWidth, int srcHeight,
-			   int scaledWidth, int scaledHeight,
-			   SplashBitmap *dest) {
-  Guchar *lineBuf;
-  Guint *pixBuf;
-  Guint pix;
-  Guchar *destPtr;
-  int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, d;
-  int i, j;
+void Splash::drawImageMaskArbitraryInterp(
+				   Guchar *scaledMask,
+				   SplashDrawImageMaskRowData *dd,
+				   SplashDrawImageMaskRowFunc drawRowFunc,
+				   SplashCoord *invMat,
+				   int scaledWidth, int scaledHeight,
+				   int xMin, int yMin, int xMax, int yMax) {
+  int tt = state->clip->getXMinI(state->strokeAdjust);
+  if (tt > xMin) {
+    xMin = tt;
+  }
+  tt = state->clip->getXMaxI(state->strokeAdjust) + 1;
+  if (tt < xMax) {
+    xMax = tt;
+  }
+  tt = state->clip->getYMinI(state->strokeAdjust);
+  if (tt > yMin) {
+    yMin = tt;
+  }
+  tt = state->clip->getYMaxI(state->strokeAdjust) + 1;
+  if (tt < yMax) {
+    yMax = tt;
+  }
+  if (xMax <= xMin || yMax <= yMin) {
+    return;
+  }
 
-  // Bresenham parameters for y scale
-  yp = srcHeight / scaledHeight;
-  yq = srcHeight % scaledHeight;
+  Guchar *buf = (Guchar *)gmalloc(xMax - xMin);
 
-  // Bresenham parameters for x scale
-  xp = scaledWidth / srcWidth;
-  xq = scaledWidth % srcWidth;
-
-  // allocate buffers
-  lineBuf = (Guchar *)gmalloc(srcWidth);
-  pixBuf = (Guint *)gmallocn(srcWidth, sizeof(int));
-
-  // init y scale Bresenham
-  yt = 0;
-
-  destPtr = dest->data;
-  for (y = 0; y < scaledHeight; ++y) {
-
-    // y scale Bresenham
-    if ((yt += yq) >= scaledHeight) {
-      yt -= scaledHeight;
-      yStep = yp + 1;
-    } else {
-      yStep = yp;
-    }
-
-    // read rows from image
-    memset(pixBuf, 0, srcWidth * sizeof(int));
-    for (i = 0; i < yStep; ++i) {
-      (*src)(srcData, lineBuf);
-      for (j = 0; j < srcWidth; ++j) {
-	pixBuf[j] += lineBuf[j];
+  for (int y = yMin; y < yMax; ++y) {
+    int rowMin = xMax;
+    int rowMax = 0;
+    for (int x = xMin; x < xMax; ++x) {
+      // note: invMat includes a "+0.5" factor so that this is really
+      // a multiply by (x+0.5, y+0.5)
+      SplashCoord xs = (SplashCoord)x * invMat[0]
+	               + (SplashCoord)y * invMat[2] + invMat[4];
+      SplashCoord ys = (SplashCoord)x * invMat[1]
+	               + (SplashCoord)y * invMat[3] + invMat[5];
+      int x0 = splashFloor(xs - 0.5);
+      int x1 = x0 + 1;
+      int y0 = splashFloor(ys - 0.5);
+      int y1 = y0 + 1;
+      if (x1 >= 0 && x0 < scaledWidth && y1 >= 0 && y0 < scaledHeight) {
+	SplashCoord sx0 = (SplashCoord)x1 + 0.5 - xs;
+	SplashCoord sx1 = (SplashCoord)1 - sx0;
+	SplashCoord sy0 = (SplashCoord)y1 + 0.5 - ys;
+	SplashCoord sy1 = (SplashCoord)1 - sy0;
+	if (x0 < 0) {
+	  x0 = 0;
+	}
+	if (x1 >= scaledWidth) {
+	  x1 = scaledWidth - 1;
+	}
+	if (y0 < 0) {
+	  y0 = 0;
+	}
+	if (y1 >= scaledHeight) {
+	  y1 = scaledHeight - 1;
+	}
+	Guchar *p00 = scaledMask + (y0 * scaledWidth + x0);
+	Guchar *p10 = scaledMask + (y0 * scaledWidth + x1);
+	Guchar *p01 = scaledMask + (y1 * scaledWidth + x0);
+	Guchar *p11 = scaledMask + (y1 * scaledWidth + x1);
+	Guchar *q = buf + (x - xMin);
+	*q = (Guchar)(int)(sx0 * (sy0 * (int)*p00 + sy1 * (int)*p01) +
+			   sx1 * (sy0 * (int)*p10 + sy1 * (int)*p11));
+	if (x < rowMin) {
+	  rowMin = x;
+	}
+	rowMax = x + 1;
       }
     }
-
-    // init x scale Bresenham
-    xt = 0;
-    d = (255 << 23) / yStep;
-
-    for (x = 0; x < srcWidth; ++x) {
-
-      // x scale Bresenham
-      if ((xt += xq) >= srcWidth) {
-	xt -= srcWidth;
-	xStep = xp + 1;
-      } else {
-	xStep = xp;
-      }
-
-      // compute the final pixel
-      pix = pixBuf[x];
-      // (255 * pix) / yStep
-      pix = (pix * d + (1 << 22)) >> 23;
-
-      // store the pixel
-      for (i = 0; i < xStep; ++i) {
-	*destPtr++ = (Guchar)pix;
-      }
+    if (rowMin < rowMax) {
+      (this->*drawRowFunc)(dd, buf + (rowMin - xMin),
+			   rowMin, y, rowMax - rowMin);
     }
   }
 
-  gfree(pixBuf);
-  gfree(lineBuf);
+  gfree(buf);
 }
 
-void Splash::scaleMaskYuXd(SplashImageMaskSource src, void *srcData,
-			   int srcWidth, int srcHeight,
-			   int scaledWidth, int scaledHeight,
-			   SplashBitmap *dest) {
-  Guchar *lineBuf;
-  Guint pix;
-  Guchar *destPtr0, *destPtr;
-  int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, d, d0, d1;
-  int i;
+void Splash::mirrorImageMaskRow(Guchar *maskIn, Guchar *maskOut, int width) {
+  Guchar *p, *q;
 
-  // Bresenham parameters for y scale
-  yp = scaledHeight / srcHeight;
-  yq = scaledHeight % srcHeight;
-
-  // Bresenham parameters for x scale
-  xp = srcWidth / scaledWidth;
-  xq = srcWidth % scaledWidth;
-
-  // allocate buffers
-  lineBuf = (Guchar *)gmalloc(srcWidth);
-
-  // init y scale Bresenham
-  yt = 0;
-
-  destPtr0 = dest->data;
-  for (y = 0; y < srcHeight; ++y) {
-
-    // y scale Bresenham
-    if ((yt += yq) >= srcHeight) {
-      yt -= srcHeight;
-      yStep = yp + 1;
-    } else {
-      yStep = yp;
-    }
-
-    // read row from image
-    (*src)(srcData, lineBuf);
-
-    // init x scale Bresenham
-    xt = 0;
-    d0 = (255 << 23) / xp;
-    d1 = (255 << 23) / (xp + 1);
-
-    xx = 0;
-    for (x = 0; x < scaledWidth; ++x) {
-
-      // x scale Bresenham
-      if ((xt += xq) >= scaledWidth) {
-	xt -= scaledWidth;
-	xStep = xp + 1;
-	d = d1;
-      } else {
-	xStep = xp;
-	d = d0;
-      }
-
-      // compute the final pixel
-      pix = 0;
-      for (i = 0; i < xStep; ++i) {
-	pix += lineBuf[xx++];
-      }
-      // (255 * pix) / xStep
-      pix = (pix * d + (1 << 22)) >> 23;
-
-      // store the pixel
-      for (i = 0; i < yStep; ++i) {
-	destPtr = destPtr0 + i * scaledWidth + x;
-	*destPtr = (Guchar)pix;
-      }
-    }
-
-    destPtr0 += yStep * scaledWidth;
+  p = maskIn;
+  q = maskOut + (width - 1);
+  for (int i = 0; i < width; ++i) {
+    *q = *p;
+    ++p;
+    --q;
   }
+}
 
-  gfree(lineBuf);
+void Splash::drawImageMaskRowNoClip(SplashDrawImageMaskRowData *data,
+				    Guchar *maskData,
+				    int x, int y, int width) {
+  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, maskData, NULL);
 }
 
-void Splash::scaleMaskYuXu(SplashImageMaskSource src, void *srcData,
-			   int srcWidth, int srcHeight,
-			   int scaledWidth, int scaledHeight,
-			   SplashBitmap *dest) {
-  Guchar *lineBuf;
-  Guchar pix;
-  Guchar *srcPtr, *destPtr;
-  int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep;
-  int i;
-
-  // Bresenham parameters for y scale
-  yp = scaledHeight / srcHeight;
-  yq = scaledHeight % srcHeight;
-
-  // Bresenham parameters for x scale
-  xp = scaledWidth / srcWidth;
-  xq = scaledWidth % srcWidth;
-
-  // allocate buffers
-  lineBuf = (Guchar *)gmalloc(srcWidth);
-
-  // init y scale Bresenham
-  yt = 0;
-
-  destPtr = dest->data;
-  for (y = 0; y < srcHeight; ++y) {
-
-    // y scale Bresenham
-    if ((yt += yq) >= srcHeight) {
-      yt -= srcHeight;
-      yStep = yp + 1;
-    } else {
-      yStep = yp;
-    }
-
-    // read row from image
-    (*src)(srcData, lineBuf);
-
-    // init x scale Bresenham
-    xt = 0;
-
-    // generate one row
-    srcPtr = lineBuf;
-    for (x = 0; x < srcWidth; ++x) {
-
-      // x scale Bresenham
-      if ((xt += xq) >= srcWidth) {
-	xt -= srcWidth;
-	xStep = xp + 1;
-      } else {
-	xStep = xp;
-      }
-
-      // compute the final pixel
-      pix = *srcPtr ? 255 : 0;
-      ++srcPtr;
-
-      // duplicate the pixel horizontally
-      for (i = 0; i < xStep; ++i) {
-	*destPtr++ = pix;
-      }
-    }
-
-    // duplicate the row vertically
-    for (i = 1 ; i < yStep; ++i) {
-      memcpy(destPtr, destPtr - scaledWidth, scaledWidth);
-      destPtr += scaledWidth;
-    }
+void Splash::drawImageMaskRowClipNoAA(SplashDrawImageMaskRowData *data,
+				      Guchar *maskData,
+				      int x, int y, int width) {
+  if (y < 0 || y >= bitmap->height) {
+    return;
   }
-
-  gfree(lineBuf);
+  if (x < 0) {
+    maskData -= x;
+    width += x;
+    x = 0;
+  }
+  if (x + width > bitmap->width) {
+    width = bitmap->width - x;
+  }
+  if (width <= 0) {
+    return;
+  }
+  memcpy(scanBuf + x, maskData, width);
+  state->clip->clipSpanBinary(scanBuf, y, x, x + width - 1,
+			      state->strokeAdjust);
+  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
+			  scanBuf + x, NULL);
 }
 
-void Splash::scaleMaskYuXuI(SplashImageMaskSource src, void *srcData,
-			    int srcWidth, int srcHeight,
-			    int scaledWidth, int scaledHeight,
-			    SplashBitmap *dest) {
-  Guchar *lineBuf0, *lineBuf1, *tBuf;
-  Guchar pix;
-  SplashCoord yr, xr, ys, xs, ySrc, xSrc;
-  int ySrc0, ySrc1, yBuf, xSrc0, xSrc1, y, x;
-  Guchar *destPtr;
-
-  // ratios
-  yr = (SplashCoord)srcHeight / (SplashCoord)scaledHeight;
-  xr = (SplashCoord)srcWidth / (SplashCoord)scaledWidth;
-
-  // allocate buffers
-  lineBuf0 = (Guchar *)gmalloc(scaledWidth);
-  lineBuf1 = (Guchar *)gmalloc(scaledWidth);
-
-  // read first two rows
-  (*src)(srcData, lineBuf0);
-  if (srcHeight > 1) {
-    (*src)(srcData, lineBuf1);
-    yBuf = 1;
-  } else {
-    memcpy(lineBuf1, lineBuf0, srcWidth);
-    yBuf = 0;
+void Splash::drawImageMaskRowClipAA(SplashDrawImageMaskRowData *data,
+				    Guchar *maskData,
+				    int x, int y, int width) {
+  if (y < 0 || y >= bitmap->height) {
+    return;
   }
-
-  // interpolate first two rows
-  for (x = scaledWidth - 1; x >= 0; --x) {
-    xSrc = xr * x;
-    xSrc0 = splashFloor(xSrc + xr * 0.5 - 0.5);
-    xSrc1 = xSrc0 + 1;
-    xs = ((SplashCoord)xSrc1 + 0.5) - (xSrc + xr * 0.5);
-    if (xSrc0 < 0) {
-      xSrc0 = 0;
-    }
-    if (xSrc1 >= srcWidth) {
-      xSrc1 = srcWidth - 1;
-    }
-    lineBuf0[x] = (Guchar)(int)
-          ((xs * (int)lineBuf0[xSrc0] +
-	    ((SplashCoord)1 - xs) * (int)lineBuf0[xSrc1]) * 255);
-    lineBuf1[x] = (Guchar)(int)
-          ((xs * (int)lineBuf1[xSrc0] +
-	    ((SplashCoord)1 - xs) * (int)lineBuf1[xSrc1]) * 255);
+  if (x < 0) {
+    maskData -= x;
+    width += x;
+    x = 0;
   }
-
-  destPtr = dest->data;
-  for (y = 0; y < scaledHeight; ++y) {
-
-    // compute vertical interpolation parameters
-    ySrc = yr * y;
-    ySrc0 = splashFloor(ySrc + yr * 0.5 - 0.5);
-    ySrc1 = ySrc0 + 1;
-    ys = ((SplashCoord)ySrc1 + 0.5) - (ySrc + yr * 0.5);
-    if (ySrc0 < 0) {
-      ySrc0 = 0;
-      ys = 1;
-    }
-    if (ySrc1 >= srcHeight) {
-      ySrc1 = srcHeight - 1;
-      ys = 0;
-    }
-
-    // read another row (if necessary)
-    if (ySrc1 > yBuf) {
-      tBuf = lineBuf0;
-      lineBuf0 = lineBuf1;
-      lineBuf1 = tBuf;
-      (*src)(srcData, lineBuf1);
-
-      // interpolate the row
-      for (x = scaledWidth - 1; x >= 0; --x) {
-	xSrc = xr * x;
-	xSrc0 = splashFloor(xSrc + xr * 0.5 - 0.5);
-	xSrc1 = xSrc0 + 1;
-	xs = ((SplashCoord)xSrc1 + 0.5) - (xSrc + xr * 0.5);
-	if (xSrc0 < 0) {
-	  xSrc0 = 0;
-	}
-	if (xSrc1 >= srcWidth) {
-	  xSrc1 = srcWidth - 1;
-	}
-	lineBuf1[x] = (Guchar)(int)
-	              ((xs * (int)lineBuf1[xSrc0] +
-			((SplashCoord)1 - xs) * (int)lineBuf1[xSrc1]) * 255);
-      }
-
-      ++yBuf;
-    }
-
-    // do the vertical interpolation
-    for (x = 0; x < scaledWidth; ++x) {
-
-      pix = (Guchar)(int)(ys * (int)lineBuf0[x] +
-			  ((SplashCoord)1 - ys) * (int)lineBuf1[x]);
-
-      // store the pixel
-      *destPtr++ = pix;
-    }
+  if (x + width > bitmap->width) {
+    width = bitmap->width - x;
   }
-
-  gfree(lineBuf1);
-  gfree(lineBuf0);
+  if (width <= 0) {
+    return;
+  }
+  memcpy(scanBuf + x, maskData, width);
+  state->clip->clipSpan(scanBuf, y, x, x + width - 1, state->strokeAdjust);
+  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
+			  scanBuf + x, NULL);
 }
 
-void Splash::blitMask(SplashBitmap *src, int xDest, int yDest,
-		      SplashClipResult clipRes) {
+struct SplashDrawImageRowData {
+  int nComps;
+  GBool srcAlpha;
   SplashPipe pipe;
-  int w, h, x0, x1, y0, y1, y, t;
+};
 
-  w = src->width;
-  h = src->height;
-  pipeInit(&pipe, state->fillPattern,
-	   (Guchar)splashRound(state->fillAlpha * 255),
-	   gTrue, gFalse);
-  if (clipRes == splashClipAllInside) {
-    for (y = 0; y < h; ++y) {
-      (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
-			src->data + y * (size_t)w, NULL);
-    }
-  } else {
-    x0 = xDest;
-    if ((t = state->clip->getXMinI(state->strokeAdjust)) > x0) {
-      x0 = t;
-    }
-    x1 = xDest + w;
-    if ((t = state->clip->getXMaxI(state->strokeAdjust) + 1) < x1) {
-      x1 = t;
-    }
-    y0 = yDest;
-    if ((t = state->clip->getYMinI(state->strokeAdjust)) > y0) {
-      y0 = t;
-    }
-    y1 = yDest + h;
-    if ((t = state->clip->getYMaxI(state->strokeAdjust) + 1) < y1) {
-      y1 = t;
-    }
-    if (x0 < x1 && y0 < y1) {
-      for (y = y0; y < y1; ++y) {
-	memcpy(scanBuf + x0,
-	       src->data + (y - yDest) * (size_t)w + (x0 - xDest),
-	       x1 - x0);
-	if (vectorAntialias) {
-	  state->clip->clipSpan(scanBuf, y, x0, x1 - 1,
-				state->strokeAdjust);
-	} else {
-	  state->clip->clipSpanBinary(scanBuf, y, x0, x1 - 1,
-				      state->strokeAdjust);
-	}
-	(this->*pipe.run)(&pipe, x0, x1 - 1, y, scanBuf + x0, NULL);
-      }
-    }
-  }
-}
-
 SplashError Splash::drawImage(GString *imageTag,
 			      SplashImageSource src, void *srcData,
 			      SplashColorMode srcMode, GBool srcAlpha,
 			      int w, int h, SplashCoord *mat,
 			      GBool interpolate) {
-  GBool ok;
-  SplashBitmap *scaledImg;
-  SplashClipResult clipRes;
-  GBool minorAxisZero;
-  SplashCoord wSize, hSize, t0, t1;
-  int x0, y0, x1, y1, scaledWidth, scaledHeight;
-  int nComps;
-
   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],
@@ -4970,9 +6224,9 @@
 	   (double)mat[3], (double)mat[4], (double)mat[5]);
   }
 
-  // check color modes
-  ok = gFalse; // make gcc happy
-  nComps = 0; // make gcc happy
+  //--- check color modes
+  GBool ok = gFalse;
+  int nComps = 0;
   switch (bitmap->mode) {
   case splashModeMono1:
   case splashModeMono8:
@@ -4998,1665 +6252,727 @@
     return splashErrModeMismatch;
   }
 
-  // check for singular matrix
+  //--- check for singular matrix
   if (!splashCheckDet(mat[0], mat[1], mat[2], mat[3], 0.000001)) {
     return splashErrSingularMatrix;
   }
 
-  minorAxisZero = splashAbs(mat[1]) <= 0.0001 && splashAbs(mat[2]) <= 0.0001;
-
-  // rough estimate of size of scaled image
-  t0 = splashAbs(mat[0]);
-  t1 = splashAbs(mat[1]);
-  wSize = t0 > t1 ? t0 : t1;
-  t0 = splashAbs(mat[2]);
-  t1 = splashAbs(mat[3]);
-  hSize = t0 > t1 ? t0 : t1;
-
-  // stream-mode upscaling -- this is slower, so we only use it if the
-  // upscaled image is large (in which case clipping should remove
-  // many pixels)
-#if USE_FIXEDPOINT
-  if ((wSize > 2 * w && hSize > 2 * h && (int)wSize > 1000000 / (int)hSize) ||
-      (wSize >     w && hSize >     h && (int)wSize > 10000000 / (int)hSize) ||
-      ((wSize > w || hSize > h) && (int)wSize > 25000000 / (int)hSize)) {
-#else
-  if ((wSize > 2 * w && hSize > 2 * h && wSize * hSize > 1000000) ||
-      (wSize >     w && hSize >     h && wSize * hSize > 10000000) ||
-      ((wSize > w || hSize > h) && wSize * hSize > 25000000)) {
-#endif
-    upscaleImage(src, srcData, srcMode, nComps, srcAlpha,
-		 w, h, mat, interpolate);
-
-  // scaling only
-  } else if (mat[0] > 0 && minorAxisZero && mat[3] > 0) {
-    getImageBounds(mat[4], mat[0] + mat[4], &x0, &x1);
-    getImageBounds(mat[5], mat[3] + mat[5], &y0, &y1);
-    clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1,
-				    state->strokeAdjust);
-    opClipRes = clipRes;
-    if (clipRes != splashClipAllOutside) {
-      scaledWidth = x1 - x0;
-      scaledHeight = y1 - y0;
-      scaledImg = scaleImage(imageTag,
-			     src, srcData, srcMode, nComps, srcAlpha, w, h,
-			     scaledWidth, scaledHeight, interpolate);
-      if (imageCache->vertFlip) {
-	vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps);
-	imageCache->vertFlip = gFalse;
+  //--- compute image bbox, check clipping
+  GBool flipsOnly = splashAbs(mat[1]) <= 0.0001 && splashAbs(mat[2]) <= 0.0001;
+  GBool horizFlip = gFalse;
+  GBool vertFlip = gFalse;
+  int xMin, yMin, xMax, yMax;
+  if (flipsOnly) {
+    horizFlip = mat[0] < 0;
+    vertFlip = mat[3] < 0;
+    if (vertFlip) {
+      if (horizFlip) {    // bottom-up, mirrored
+	getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax);
+	getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax);
+      } else {            // bottom-up
+	getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax);
+	getImageBounds(mat[3] + mat[5], mat[5], &yMin, &yMax);
       }
-      if (imageCache->horizFlip) {
-	horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps);
-	imageCache->horizFlip = gFalse;
+    } else {
+      if (horizFlip) {    // top-down, mirrored
+	getImageBounds(mat[0] + mat[4], mat[4], &xMin, &xMax);
+	getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax);
+      } else {            // top-down
+	getImageBounds(mat[4], mat[0] + mat[4], &xMin, &xMax);
+	getImageBounds(mat[5], mat[3] + mat[5], &yMin, &yMax);
       }
-      blitImage(scaledImg, srcAlpha, x0, y0, clipRes);
     }
-    
-  // scaling plus vertical flip
-  } else if (mat[0] > 0 && minorAxisZero && mat[3] < 0) {
-    getImageBounds(mat[4], mat[0] + mat[4], &x0, &x1);
-    getImageBounds(mat[3] + mat[5], mat[5], &y0, &y1);
-    clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1,
-				    state->strokeAdjust);
-    opClipRes = clipRes;
-    if (clipRes != splashClipAllOutside) {
-      scaledWidth = x1 - x0;
-      scaledHeight = y1 - y0;
-      scaledImg = scaleImage(imageTag,
-			     src, srcData, srcMode, nComps, srcAlpha, w, h,
-			     scaledWidth, scaledHeight, interpolate);
-      if (!imageCache->vertFlip) {
-	vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps);
-	imageCache->vertFlip = gTrue;
-      }
-      if (imageCache->horizFlip) {
-	horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps);
-	imageCache->horizFlip = gFalse;
-      }
-      blitImage(scaledImg, srcAlpha, x0, y0, clipRes);
+  } else {
+    int xx = splashRound(mat[4]);		// (0,0)
+    int yy = splashRound(mat[5]);
+    xMin = xMax = xx;
+    yMin = yMax = yy;
+    xx = splashRound(mat[0] + mat[4]);		// (1,0)
+    yy = splashRound(mat[1] + mat[5]);
+    if (xx < xMin) {
+      xMin = xx;
+    } else if (xx > xMax) {
+      xMax = xx;
     }
-
-  // scaling plus horizontal flip
-  } else if (mat[0] < 0 && minorAxisZero && mat[3] > 0) {
-    getImageBounds(mat[0] + mat[4], mat[4], &x0, &x1);
-    getImageBounds(mat[5], mat[3] + mat[5], &y0, &y1);
-    clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1,
-				    state->strokeAdjust);
-    opClipRes = clipRes;
-    if (clipRes != splashClipAllOutside) {
-      scaledWidth = x1 - x0;
-      scaledHeight = y1 - y0;
-      scaledImg = scaleImage(imageTag,
-			     src, srcData, srcMode, nComps, srcAlpha, w, h,
-			     scaledWidth, scaledHeight, interpolate);
-      if (imageCache->vertFlip) {
-	vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps);
-	imageCache->vertFlip = gFalse;
-      }
-      if (!imageCache->horizFlip) {
-	horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps);
-	imageCache->horizFlip = gTrue;
-      }
-      blitImage(scaledImg, srcAlpha, x0, y0, clipRes);
+    if (yy < yMin) {
+      yMin = yy;
+    } else if (yy > yMax) {
+      yMax = yy;
     }
-    
-  // scaling plus horizontal and vertical flips
-  } else if (mat[0] < 0 && minorAxisZero && mat[3] < 0) {
-    getImageBounds(mat[0] + mat[4], mat[4], &x0, &x1);
-    getImageBounds(mat[3] + mat[5], mat[5], &y0, &y1);
-    clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1,
-				    state->strokeAdjust);
-    opClipRes = clipRes;
-    if (clipRes != splashClipAllOutside) {
-      scaledWidth = x1 - x0;
-      scaledHeight = y1 - y0;
-      scaledImg = scaleImage(imageTag,
-			     src, srcData, srcMode, nComps, srcAlpha, w, h,
-			     scaledWidth, scaledHeight, interpolate);
-      if (!imageCache->vertFlip) {
-	vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps);
-	imageCache->vertFlip = gTrue;
-      }
-      if (!imageCache->horizFlip) {
-	horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps);
-	imageCache->horizFlip = gTrue;
-      }
-      blitImage(scaledImg, srcAlpha, x0, y0, clipRes);
+    xx = splashRound(mat[2] + mat[4]);		// (0,1)
+    yy = splashRound(mat[3] + mat[5]);
+    if (xx < xMin) {
+      xMin = xx;
+    } else if (xx > xMax) {
+      xMax = xx;
     }
-
-  // all other cases
-  } else {
-    arbitraryTransformImage(imageTag, src, srcData, srcMode, nComps, srcAlpha,
-			    w, h, mat, interpolate);
-  }
-
-  return splashOk;
-}
-
-void Splash::upscaleImage(SplashImageSource src, void *srcData,
-			  SplashColorMode srcMode, int nComps,
-			  GBool srcAlpha, int srcWidth, int srcHeight,
-			  SplashCoord *mat, GBool interpolate) {
-  SplashClipResult clipRes;
-  SplashPipe pipe;
-  SplashColorPtr unscaledImage, pixelBuf, p, q, q00, q01, q10, q11;
-  Guchar *unscaledAlpha, *alphaPtr;
-  SplashCoord xMin, yMin, xMax, yMax, t;
-  SplashCoord mi0, mi1, mi2, mi3, mi4, mi5, det;
-  SplashCoord ix, iy, sx, sy, pix0, pix1;
-  SplashBitmapRowSize rowSize;
-  int xMinI, yMinI, xMaxI, yMaxI, x, y, x0, y0, x1, y1, tt, i;
-
-  // compute the bbox of the target quadrilateral
-  xMin = xMax = mat[4];
-  t = mat[2] + mat[4];
-  if (t < xMin) {
-    xMin = t;
-  } else if (t > xMax) {
-    xMax = t;
-  }
-  t = mat[0] + mat[2] + mat[4];
-  if (t < xMin) {
-    xMin = t;
-  } else if (t > xMax) {
-    xMax = t;
-  }
-  t = mat[0] + mat[4];
-  if (t < xMin) {
-    xMin = t;
-  } else if (t > xMax) {
-    xMax = t;
-  }
-  getImageBounds(xMin, xMax, &xMinI, &xMaxI);
-  yMin = yMax = mat[5];
-  t = mat[3] + mat[5];
-  if (t < yMin) {
-    yMin = t;
-  } else if (t > yMax) {
-    yMax = t;
-  }
-  t = mat[1] + mat[3] + mat[5];
-  if (t < yMin) {
-    yMin = t;
-  } else if (t > yMax) {
-    yMax = t;
-  }
-  t = mat[1] + mat[5];
-  if (t < yMin) {
-    yMin = t;
-  } else if (t > yMax) {
-    yMax = t;
-  }
-  getImageBounds(yMin, yMax, &yMinI, &yMaxI);
-
-  // clipping
-  clipRes = state->clip->testRect(xMinI, yMinI, xMaxI - 1, yMaxI - 1,
-				  state->strokeAdjust);
-  opClipRes = clipRes;
-  if (clipRes == splashClipAllOutside) {
-    return;
-  }
-  if (clipRes != splashClipAllInside) {
-    if ((tt = state->clip->getXMinI(state->strokeAdjust)) > xMinI) {
-      xMinI = tt;
+    if (yy < yMin) {
+      yMin = yy;
+    } else if (yy > yMax) {
+      yMax = yy;
     }
-    if ((tt = state->clip->getXMaxI(state->strokeAdjust) + 1) < xMaxI) {
-      xMaxI = tt;
+    xx = splashRound(mat[0] + mat[2] + mat[4]);	// (1,1)
+    yy = splashRound(mat[1] + mat[3] + mat[5]);
+    if (xx < xMin) {
+      xMin = xx;
+    } else if (xx > xMax) {
+      xMax = xx;
     }
-    if ((tt = state->clip->getYMinI(state->strokeAdjust)) > yMinI) {
-      yMinI = tt;
+    if (yy < yMin) {
+      yMin = yy;
+    } else if (yy > yMax) {
+      yMax = yy;
     }
-    if ((tt = state->clip->getYMaxI(state->strokeAdjust) + 1) < yMaxI) {
-      yMaxI = tt;
+    if (xMax <= xMin) {
+      xMax = xMin + 1;
     }
-  }
-
-  // invert the matrix
-  det = mat[0] * mat[3] - mat[1] * mat[2];
-  if (splashAbs(det) < 1e-6) {
-    // this should be caught by the singular matrix check in fillImageMask
-    return;
-  }
-  det = (SplashCoord)1 / det;
-  mi0 = det * mat[3] * srcWidth;
-  mi1 = -det * mat[1] * srcHeight;
-  mi2 = -det * mat[2] * srcWidth;
-  mi3 = det * mat[0] * srcHeight;
-  mi4 = det * (mat[2] * mat[5] - mat[3] * mat[4]) * srcWidth;
-  mi5 = -det * (mat[0] * mat[5] - mat[1] * mat[4]) * srcHeight;
-
-  // grab the image
-  if (srcWidth > INT_MAX / nComps) {
-    rowSize = -1;
-  } else {
-    rowSize = srcWidth * nComps;
-  }
-  unscaledImage = (SplashColorPtr)gmallocn64(srcHeight, rowSize);
-  if (srcAlpha) {
-    unscaledAlpha = (Guchar *)gmallocn(srcHeight, srcWidth);
-    for (y = 0, p = unscaledImage, alphaPtr = unscaledAlpha;
-	 y < srcHeight;
-	 ++y, p += rowSize, alphaPtr += srcWidth) {
-      (*src)(srcData, p, alphaPtr);
+    if (yMax <= yMin) {
+      yMax = yMin + 1;
     }
-  } else {
-    unscaledAlpha = NULL;
-    for (y = 0, p = unscaledImage; y < srcHeight; ++y, p += rowSize) {
-      (*src)(srcData, p, NULL);
-    }
   }
+  SplashClipResult clipRes =
+      state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1,
+			    state->strokeAdjust);
+  // If the scaled image is much wider and/or taller than the clip
+  // region, we use the arbitrary transform path, to avoid a
+  // potentially very slow loop in the flips-only path (which scans
+  // the full width and height of the scaled image, regardless of the
+  // clip region).
+  int clipW = state->clip->getXMaxI(state->strokeAdjust)
+	      - state->clip->getXMinI(state->strokeAdjust);
+  int clipH = state->clip->getYMaxI(state->strokeAdjust)
+	      - state->clip->getYMinI(state->strokeAdjust);
+  GBool veryLarge = ((xMax - xMin) / 8 > clipW && xMax - xMin > 1000) ||
+                    ((yMax - yMin) / 8 > clipH && yMax - yMin > 1000);
 
-  // draw it
-  pixelBuf = (SplashColorPtr)gmallocn(xMaxI - xMinI, nComps);
-  pipeInit(&pipe, NULL,
+  //--- set up the SplashDrawImageRowData object and the pipes
+  SplashDrawImageRowData dd;
+  dd.nComps = nComps;
+  dd.srcAlpha = srcAlpha;
+  pipeInit(&dd.pipe, NULL,
 	   (Guchar)splashRound(state->fillAlpha * 255),
-	   gTrue, gFalse);
-  for (y = yMinI; y < yMaxI; ++y) {
-    p = pixelBuf;
-    for (x = xMinI; x < xMaxI; ++x) {
-      ix = ((SplashCoord)x + 0.5) * mi0 + ((SplashCoord)y + 0.5) * mi2 + mi4;
-      iy = ((SplashCoord)x + 0.5) * mi1 + ((SplashCoord)y + 0.5) * mi3 + mi5;
-      if (interpolate) {
-	if (ix >= 0 && ix < srcWidth && iy >= 0 && iy < srcHeight) {
-	  x0 = splashFloor(ix - 0.5);
-	  x1 = x0 + 1;
-	  sx = (ix - 0.5) - x0;
-	  y0 = splashFloor(iy - 0.5);
-	  y1 = y0 + 1;
-	  sy = (iy - 0.5) - y0;
-	  if (x0 < 0) {
-	    x0 = 0;
-	  }
-	  if (x1 >= srcWidth) {
-	    x1 = srcWidth - 1;
-	  }
-	  if (y0 < 0) {
-	    y0 = 0;
-	  }
-	  if (y1 >= srcHeight) {
-	    y1 = srcHeight - 1;
-	  }
-	  q00 = &unscaledImage[y0 * rowSize + (SplashBitmapRowSize)x0 * nComps];
-	  q01 = &unscaledImage[y0 * rowSize + (SplashBitmapRowSize)x1 * nComps];
-	  q10 = &unscaledImage[y1 * rowSize + (SplashBitmapRowSize)x0 * nComps];
-	  q11 = &unscaledImage[y1 * rowSize + (SplashBitmapRowSize)x1 * nComps];
-	  for (i = 0; i < nComps; ++i) {
-	    pix0 = ((SplashCoord)1 - sx) * (int)*q00++ + sx * (int)*q01++;
-	    pix1 = ((SplashCoord)1 - sx) * (int)*q10++ + sx * (int)*q11++;
-	    *p++ = (Guchar)splashRound(((SplashCoord)1 - sy) * pix0
-				       + sy * pix1);
-	  }
-	  if (srcAlpha) {
-	    pix0 = ((SplashCoord)1 - sx)
-	             * (SplashCoord)unscaledAlpha[y0 * srcWidth + x0]
-	           + sx * (SplashCoord)unscaledAlpha[y0 * srcWidth + x1];
-	    pix1 = ((SplashCoord)1 - sx)
-	             * (SplashCoord)unscaledAlpha[y1 * srcWidth + x0]
-	           + sx * (SplashCoord)unscaledAlpha[y1 * srcWidth + x1];
-	    scanBuf[x] = (Guchar)splashRound(((SplashCoord)1 - sy) * pix0
-					     + sy * pix1);
-	  } else {
-	    scanBuf[x] = 0xff;
-	  }
-	} else {
-	  for (i = 0; i < nComps; ++i) {
-	    *p++ = 0;
-	  }
-	  scanBuf[x] = 0;
-	}
-      } else {
-	x0 = splashFloor(ix);
-	y0 = splashFloor(iy);
-	if (x0 >= 0 && x0 < srcWidth && y0 >= 0 && y0 < srcHeight) {
-	  q = &unscaledImage[y0 * rowSize + (SplashBitmapRowSize)x0 * nComps];
-	  for (i = 0; i < nComps; ++i) {
-	    *p++ = *q++;
-	  }
-	  if (srcAlpha) {
-	    scanBuf[x] = unscaledAlpha[y0 * srcWidth + x0];
-	  } else {
-	    scanBuf[x] = 0xff;
-	  }
-	} else {
-	  for (i = 0; i < nComps; ++i) {
-	    *p++ = 0;
-	  }
-	  scanBuf[x] = 0;
-	}
-      }
-    }
-    if (clipRes != splashClipAllInside) {
-      if (vectorAntialias) {
-	state->clip->clipSpan(scanBuf, y, xMinI, xMaxI - 1,
-			      state->strokeAdjust);
-      } else {
-	state->clip->clipSpanBinary(scanBuf, y, xMinI, xMaxI - 1,
-				    state->strokeAdjust);
-      }
-    }
-    (this->*pipe.run)(&pipe, xMinI, xMaxI - 1, y, scanBuf + xMinI, pixelBuf);
-  }
+	   clipRes != splashClipAllInside || srcAlpha,
+	   gFalse);
 
-  gfree(pixelBuf);
-  gfree(unscaledImage);
-  gfree(unscaledAlpha);
-}
-
-void Splash::arbitraryTransformImage(GString *imageTag,
-				     SplashImageSource src, void *srcData,
-				     SplashColorMode srcMode, int nComps,
-				     GBool srcAlpha,
-				     int srcWidth, int srcHeight,
-				     SplashCoord *mat, GBool interpolate) {
-  SplashBitmap *scaledImg;
-  SplashClipResult clipRes;
-  SplashPipe pipe;
-  SplashColorPtr pixelBuf;
-  int scaledWidth, scaledHeight, t0, t1;
-  SplashCoord r00, r01, r10, r11, det, ir00, ir01, ir10, ir11;
-  SplashCoord vx[4], vy[4];
-  int xMin, yMin, xMax, yMax;
-  ImageSection section[3];
-  int nSections;
-  int y, xa, xb, x, i, xx, yy;
-
-  // compute the four vertices of the target quadrilateral
-  vx[0] = mat[4];                    vy[0] = mat[5];
-  vx[1] = mat[2] + mat[4];           vy[1] = mat[3] + mat[5];
-  vx[2] = mat[0] + mat[2] + mat[4];  vy[2] = mat[1] + mat[3] + mat[5];
-  vx[3] = mat[0] + mat[4];           vy[3] = mat[1] + mat[5];
-
-  // clipping
-  xMin = splashRound(vx[0]);
-  xMax = splashRound(vx[0]);
-  yMin = splashRound(vy[0]);
-  yMax = splashRound(vy[0]);
-  for (i = 1; i < 4; ++i) {
-    t0 = splashRound(vx[i]);
-    if (t0 < xMin) {
-      xMin = t0;
-    } else if (t0 > xMax) {
-      xMax = t0;
-    }
-    t1 = splashRound(vy[i]);
-    if (t1 < yMin) {
-      yMin = t1;
-    } else if (t1 > yMax) {
-      yMax = t1;
-    }
-  }
-  clipRes = state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1,
-				  state->strokeAdjust);
-  opClipRes = clipRes;
-  if (clipRes == splashClipAllOutside) {
-    return;
-  }
-
-  // compute the scale factors
-  if (mat[0] >= 0) {
-    t0 = splashRound(mat[0] + mat[4]) - splashRound(mat[4]);
-  } else {
-    t0 = splashRound(mat[4]) - splashRound(mat[0] + mat[4]);
-  }
-  if (mat[1] >= 0) {
-    t1 = splashRound(mat[1] + mat[5]) - splashRound(mat[5]);
-  } else {
-    t1 = splashRound(mat[5]) - splashRound(mat[1] + mat[5]);
-  }
-  scaledWidth = t0 > t1 ? t0 : t1;
-  if (mat[2] >= 0) {
-    t0 = splashRound(mat[2] + mat[4]) - splashRound(mat[4]);
-  } else {
-    t0 = splashRound(mat[4]) - splashRound(mat[2] + mat[4]);
-  }
-  if (mat[3] >= 0) {
-    t1 = splashRound(mat[3] + mat[5]) - splashRound(mat[5]);
-  } else {
-    t1 = splashRound(mat[5]) - splashRound(mat[3] + mat[5]);
-  }
-  scaledHeight = t0 > t1 ? t0 : t1;
-  if (scaledWidth == 0) {
-    scaledWidth = 1;
-  }
-  if (scaledHeight == 0) {
-    scaledHeight = 1;
-  }
-
-  // compute the inverse transform (after scaling) matrix
-  r00 = mat[0] / scaledWidth;
-  r01 = mat[1] / scaledWidth;
-  r10 = mat[2] / scaledHeight;
-  r11 = mat[3] / scaledHeight;
-  det = r00 * r11 - r01 * r10;
-  if (splashAbs(det) < 1e-6) {
-    // this should be caught by the singular matrix check in drawImage
-    return;
-  }
-  ir00 = r11 / det;
-  ir01 = -r01 / det;
-  ir10 = -r10 / det;
-  ir11 = r00 / det;
-
-  // scale the input image
-  scaledImg = scaleImage(imageTag, src, srcData, srcMode, nComps, srcAlpha,
-			 srcWidth, srcHeight, scaledWidth, scaledHeight,
-			 interpolate);
-  if (imageCache->vertFlip) {
-    vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps);
-    imageCache->vertFlip = gFalse;
-  }
-  if (imageCache->horizFlip) {
-    horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps);
-    imageCache->horizFlip = gFalse;
-  }
-
-  // construct the three sections
-  i = 0;
-  if (vy[1] < vy[i]) {
-    i = 1;
-  }
-  if (vy[2] < vy[i]) {
-    i = 2;
-  }
-  if (vy[3] < vy[i]) {
-    i = 3;
-  }
-  // NB: if using fixed point, 0.000001 will be truncated to zero,
-  // so these two comparisons must be <=, not <
-  if (splashAbs(vy[i] - vy[(i-1) & 3]) <= 0.000001 &&
-      vy[(i-1) & 3] < vy[(i+1) & 3]) {
-    i = (i-1) & 3;
-  }
-  if (splashAbs(vy[i] - vy[(i+1) & 3]) <= 0.000001) {
-    section[0].y0 = splashRound(vy[i]);
-    section[0].y1 = splashRound(vy[(i+2) & 3]) - 1;
-    if (vx[i] < vx[(i+1) & 3]) {
-      section[0].ia0 = i;
-      section[0].ia1 = (i+3) & 3;
-      section[0].ib0 = (i+1) & 3;
-      section[0].ib1 = (i+2) & 3;
+  //--- choose the drawRow function
+  SplashDrawImageRowFunc drawRowFunc;
+  if (clipRes == splashClipAllInside) {
+    if (srcAlpha) {
+      drawRowFunc = &Splash::drawImageRowNoClipAlpha;
     } else {
-      section[0].ia0 = (i+1) & 3;
-      section[0].ia1 = (i+2) & 3;
-      section[0].ib0 = i;
-      section[0].ib1 = (i+3) & 3;
+      drawRowFunc = &Splash::drawImageRowNoClipNoAlpha;
     }
-    nSections = 1;
   } else {
-    section[0].y0 = splashRound(vy[i]);
-    section[2].y1 = splashRound(vy[(i+2) & 3]) - 1;
-    section[0].ia0 = section[0].ib0 = i;
-    section[2].ia1 = section[2].ib1 = (i+2) & 3;
-    if (vx[(i+1) & 3] < vx[(i+3) & 3]) {
-      section[0].ia1 = section[2].ia0 = (i+1) & 3;
-      section[0].ib1 = section[2].ib0 = (i+3) & 3;
-    } else {
-      section[0].ia1 = section[2].ia0 = (i+3) & 3;
-      section[0].ib1 = section[2].ib0 = (i+1) & 3;
-    }
-    if (vy[(i+1) & 3] < vy[(i+3) & 3]) {
-      section[1].y0 = splashRound(vy[(i+1) & 3]);
-      section[2].y0 = splashRound(vy[(i+3) & 3]);
-      if (vx[(i+1) & 3] < vx[(i+3) & 3]) {
-	section[1].ia0 = (i+1) & 3;
-	section[1].ia1 = (i+2) & 3;
-	section[1].ib0 = i;
-	section[1].ib1 = (i+3) & 3;
+    if (srcAlpha) {
+      if (vectorAntialias) {
+	drawRowFunc = &Splash::drawImageRowClipAlphaAA;
       } else {
-	section[1].ia0 = i;
-	section[1].ia1 = (i+3) & 3;
-	section[1].ib0 = (i+1) & 3;
-	section[1].ib1 = (i+2) & 3;
+	drawRowFunc = &Splash::drawImageRowClipAlphaNoAA;
       }
     } else {
-      section[1].y0 = splashRound(vy[(i+3) & 3]);
-      section[2].y0 = splashRound(vy[(i+1) & 3]);
-      if (vx[(i+1) & 3] < vx[(i+3) & 3]) {
-	section[1].ia0 = i;
-	section[1].ia1 = (i+1) & 3;
-	section[1].ib0 = (i+3) & 3;
-	section[1].ib1 = (i+2) & 3;
+      if (vectorAntialias) {
+	drawRowFunc = &Splash::drawImageRowClipNoAlphaAA;
       } else {
-	section[1].ia0 = (i+3) & 3;
-	section[1].ia1 = (i+2) & 3;
-	section[1].ib0 = i;
-	section[1].ib1 = (i+1) & 3;
+	drawRowFunc = &Splash::drawImageRowClipNoAlphaNoAA;
       }
     }
-    section[0].y1 = section[1].y0 - 1;
-    section[1].y1 = section[2].y0 - 1;
-    nSections = 3;
   }
-  for (i = 0; i < nSections; ++i) {
-    section[i].xa0 = vx[section[i].ia0];
-    section[i].ya0 = vy[section[i].ia0];
-    section[i].xa1 = vx[section[i].ia1];
-    section[i].ya1 = vy[section[i].ia1];
-    section[i].xb0 = vx[section[i].ib0];
-    section[i].yb0 = vy[section[i].ib0];
-    section[i].xb1 = vx[section[i].ib1];
-    section[i].yb1 = vy[section[i].ib1];
-    section[i].dxdya = (section[i].xa1 - section[i].xa0) /
-                       (section[i].ya1 - section[i].ya0);
-    section[i].dxdyb = (section[i].xb1 - section[i].xb0) /
-                       (section[i].yb1 - section[i].yb0);
-  }
 
-  // initialize the pixel pipe
-  pipeInit(&pipe, NULL,
-	   (Guchar)splashRound(state->fillAlpha * 255),
-	   gTrue, gFalse);
-
-  // make sure narrow images cover at least one pixel
-  if (nSections == 1) {
-    if (section[0].y0 == section[0].y1) {
-      ++section[0].y1;
-      clipRes = opClipRes = splashClipPartial;
-    }
-  } else {
-    if (section[0].y0 == section[2].y1) {
-      ++section[1].y1;
-      clipRes = opClipRes = splashClipPartial;
-    }
-  }
-
-  pixelBuf = (SplashColorPtr)gmallocn(xMax - xMin + 1, bitmapComps);
-
-  // scan all pixels inside the target region
-  for (i = 0; i < nSections; ++i) {
-    for (y = section[i].y0; y <= section[i].y1; ++y) {
-      xa = splashRound(section[i].xa0 +
-		         ((SplashCoord)y + 0.5 - section[i].ya0) *
-		           section[i].dxdya);
-      xb = splashRound(section[i].xb0 +
-		         ((SplashCoord)y + 0.5 - section[i].yb0) *
-		           section[i].dxdyb);
-      if (xa > xb) {
-	continue;
-      }
-      // make sure narrow images cover at least one pixel
-      if (xa == xb) {
-	++xb;
-      }
-      // check the scanBuf bounds
-      if (xa >= bitmap->width || xb < 0) {
-	continue;
-      }
-      if (xa < 0) {
-	xa = 0;
-      }
-      if (xb > bitmap->width) {
-	xb = bitmap->width;
-      }
-      // clip the scan line
-      memset(scanBuf + xa, 0xff, xb - xa);
-      if (clipRes != splashClipAllInside) {
-	if (vectorAntialias) {
-	  state->clip->clipSpan(scanBuf, y, xa, xb - 1,
-				state->strokeAdjust);
-	} else {
-	  state->clip->clipSpanBinary(scanBuf, y, xa, xb - 1,
-				      state->strokeAdjust);
+  //--- horizontal/vertical flips only
+  if (flipsOnly && !veryLarge) {
+    if (clipRes != splashClipAllOutside) {
+      int scaledWidth = xMax - xMin;
+      int scaledHeight = yMax - yMin;
+      ImageScaler *scaler = getImageScaler(imageTag, src, srcData,
+					   w, h, nComps,
+					   scaledWidth, scaledHeight,
+					   srcMode, srcAlpha, interpolate);
+      Guchar *tmpLine = NULL;
+      Guchar *tmpAlphaLine = NULL;
+      if (horizFlip) {
+	tmpLine = (Guchar *)gmallocn(scaledWidth, nComps);
+	if (srcAlpha) {
+	  tmpAlphaLine = (Guchar *)gmalloc(scaledWidth);
 	}
       }
-      // draw the scan line
-      for (x = xa; x < xb; ++x) {
-	// map (x+0.5, y+0.5) back to the scaled image
-	xx = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir00 +
-			 ((SplashCoord)y + 0.5 - mat[5]) * ir10);
-	yy = splashFloor(((SplashCoord)x + 0.5 - mat[4]) * ir01 +
-			 ((SplashCoord)y + 0.5 - mat[5]) * ir11);
-	// xx should always be within bounds, but floating point
-	// inaccuracy can cause problems
-	if (xx < 0) {
-	  xx = 0;
-	} else if (xx >= scaledWidth) {
-	  xx = scaledWidth - 1;
+      if (vertFlip) {
+	if (horizFlip) {    // bottom-up, mirrored
+	  for (int y = 0; y < scaledHeight; ++y) {
+	    scaler->nextLine();
+	    mirrorImageRow(scaler->colorData(), scaler->alphaData(),
+			   tmpLine, tmpAlphaLine,
+			   scaledWidth, nComps, srcAlpha);
+	    (this->*drawRowFunc)(&dd, tmpLine, tmpAlphaLine,
+				 xMin, yMax - 1 - y, scaledWidth);
+	  }
+	} else {            // bottom-up
+	  for (int y = 0; y < scaledHeight; ++y) {
+	    scaler->nextLine();
+	    (this->*drawRowFunc)(&dd, scaler->colorData(), scaler->alphaData(),
+				 xMin, yMax - 1 - y, scaledWidth);
+	  }
 	}
-	if (yy < 0) {
-	  yy = 0;
-	} else if (yy >= scaledHeight) {
-	  yy = scaledHeight - 1;
+      } else {
+	if (horizFlip) {    // top-down, mirrored
+	  for (int y = 0; y < scaledHeight; ++y) {
+	    scaler->nextLine();
+	    mirrorImageRow(scaler->colorData(), scaler->alphaData(),
+			   tmpLine, tmpAlphaLine,
+			   scaledWidth, nComps, srcAlpha);
+	    (this->*drawRowFunc)(&dd, tmpLine, tmpAlphaLine,
+				 xMin, yMin + y, scaledWidth);
+	  }
+	} else {            // top-down
+	  for (int y = 0; y < scaledHeight; ++y) {
+	    scaler->nextLine();
+	    (this->*drawRowFunc)(&dd, scaler->colorData(), scaler->alphaData(),
+				 xMin, yMin + y, scaledWidth);
+	  }
 	}
-	// get the color
-	scaledImg->getPixel(xx, yy, pixelBuf + (x - xa) * bitmapComps);
-	// apply alpha
-	if (srcAlpha) {
-	  scanBuf[x] = div255(scanBuf[x] *
-			      scaledImg->alpha[yy * scaledWidth + xx]);
-	}
       }
-      (this->*pipe.run)(&pipe, xa, xb - 1, y, scanBuf + xa, pixelBuf);
+      gfree(tmpLine);
+      gfree(tmpAlphaLine);
+      delete scaler;
     }
-  }
 
-  gfree(pixelBuf);
-}
-
-// Scale an image into a SplashBitmap.
-SplashBitmap *Splash::scaleImage(GString *imageTag,
-				 SplashImageSource src, void *srcData,
-				 SplashColorMode srcMode, int nComps,
-				 GBool srcAlpha, int srcWidth, int srcHeight,
-				 int scaledWidth, int scaledHeight,
-				 GBool interpolate) {
-  if (imageCache->tag && imageTag &&
-      !imageCache->tag->cmp(imageTag) &&
-      !imageCache->isMask &&
-      imageCache->width == scaledWidth &&
-      imageCache->height == scaledHeight &&
-      imageCache->mode == srcMode &&
-      imageCache->alpha == srcAlpha &&
-      imageCache->interpolate == interpolate) {
-    return imageCache->image;
-  }
-  if (imageCache->tag) {
-    delete imageCache->tag;
-  }
-  if (imageCache->image) {
-    delete imageCache->image;
-  }
-  imageCache->tag = imageTag ? imageTag->copy() : (GString *)NULL;
-  imageCache->isMask = gFalse;
-  imageCache->width = scaledWidth;
-  imageCache->height = scaledHeight;
-  imageCache->mode = srcMode;
-  imageCache->alpha = srcAlpha;
-  imageCache->interpolate = interpolate;
-  imageCache->vertFlip = gFalse;
-  imageCache->horizFlip = gFalse;
-  imageCache->image = new SplashBitmap(scaledWidth, scaledHeight, 1,
-				       srcMode, srcAlpha, gTrue, NULL);
-  if (scaledHeight < srcHeight) {
-    if (scaledWidth < srcWidth) {
-      scaleImageYdXd(src, srcData, srcMode, nComps, srcAlpha,
-		     srcWidth, srcHeight, scaledWidth, scaledHeight,
-		     imageCache->image);
-    } else {
-      scaleImageYdXu(src, srcData, srcMode, nComps, srcAlpha,
-		     srcWidth, srcHeight, scaledWidth, scaledHeight,
-		     imageCache->image);
-    }
+  //--- arbitrary transform
   } else {
-    if (scaledWidth < srcWidth) {
-      scaleImageYuXd(src, srcData, srcMode, nComps, srcAlpha,
-		     srcWidth, srcHeight, scaledWidth, scaledHeight,
-		     imageCache->image);
-    } else {
-      if (interpolate) {
-	scaleImageYuXuI(src, srcData, srcMode, nComps, srcAlpha,
-			srcWidth, srcHeight, scaledWidth, scaledHeight,
-			imageCache->image);
-      } else {
-	scaleImageYuXu(src, srcData, srcMode, nComps, srcAlpha,
-		       srcWidth, srcHeight, scaledWidth, scaledHeight,
-		       imageCache->image);
-      }
+    // estimate of size of scaled image
+    int scaledWidth = splashRound(splashSqrt(mat[0] * mat[0]
+					     + mat[1] * mat[1]));
+    int scaledHeight = splashRound(splashSqrt(mat[2] * mat[2]
+					      + mat[3] * mat[3]));
+    if (scaledWidth < 1) {
+      scaledWidth = 1;
     }
-  }
-  return imageCache->image;
-}
+    if (scaledHeight < 1) {
+      scaledHeight = 1;
+    }
+    if (veryLarge || (scaledWidth >= w && scaledHeight >= h)) {
+      scaledWidth = w;
+      scaledHeight = h;
+    }
 
-void Splash::scaleImageYdXd(SplashImageSource src, void *srcData,
-			    SplashColorMode srcMode, int nComps,
-			    GBool srcAlpha, int srcWidth, int srcHeight,
-			    int scaledWidth, int scaledHeight,
-			    SplashBitmap *dest) {
-  Guchar *lineBuf, *alphaLineBuf;
-  Guint *pixBuf, *alphaPixBuf;
-  Guint pix0, pix1, pix2;
-#if SPLASH_CMYK
-  Guint pix3;
-#endif
-  Guint alpha;
-  Guchar *destPtr, *destAlphaPtr;
-  int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, xxa, d, d0, d1;
-  int i, j;
+    // compute mapping from device space to scaled image space
+    SplashCoord mat1[6];
+    mat1[0] = mat[0] / scaledWidth;
+    mat1[1] = mat[1] / scaledWidth;
+    mat1[2] = mat[2] / scaledHeight;
+    mat1[3] = mat[3] / scaledHeight;
+    mat1[4] = mat[4];
+    mat1[5] = mat[5];
+    SplashCoord det = mat1[0] * mat1[3] - mat1[1] * mat1[2];
+    if (splashAbs(det) < 1e-6) {
+      // this should be caught by the singular matrix check in drawImage
+      return splashErrSingularMatrix;
+    }
+    SplashCoord invMat[6];
+    invMat[0] = mat1[3] / det;
+    invMat[1] = -mat1[1] / det;
+    invMat[2] = -mat1[2] / det;
+    invMat[3] = mat1[0] / det;
+    // the extra "+ 0.5 * (...)" terms are here because the
+    // drawImageArbitrary(No)Interp functions multiply by pixel
+    // centers, (x + 0.5, y + 0.5)
+    invMat[4] = (mat1[2] * mat1[5] - mat1[3] * mat1[4]) / det
+                + (invMat[0] + invMat[2]) * 0.5;
+    invMat[5] = (mat1[1] * mat1[4] - mat1[0] * mat1[5]) / det
+                + (invMat[1] + invMat[3]) * 0.5;
 
-  // Bresenham parameters for y scale
-  yp = srcHeight / scaledHeight;
-  yq = srcHeight % scaledHeight;
+    Guchar *scaledColor, *scaledAlpha;
+    GBool freeScaledImage;
+    getScaledImage(imageTag, src, srcData, w, h, nComps,
+		   scaledWidth, scaledHeight, srcMode, srcAlpha, interpolate,
+		   &scaledColor, &scaledAlpha, &freeScaledImage);
 
-  // Bresenham parameters for x scale
-  xp = srcWidth / scaledWidth;
-  xq = srcWidth % scaledWidth;
-
-  // allocate buffers
-  lineBuf = (Guchar *)gmallocn(srcWidth, nComps);
-  pixBuf = (Guint *)gmallocn(srcWidth, (int)(nComps * sizeof(int)));
-  if (srcAlpha) {
-    alphaLineBuf = (Guchar *)gmalloc(srcWidth);
-    alphaPixBuf = (Guint *)gmallocn(srcWidth, sizeof(int));
-  } else {
-    alphaLineBuf = NULL;
-    alphaPixBuf = NULL;
-  }
-
-  // init y scale Bresenham
-  yt = 0;
-
-  destPtr = dest->data;
-  destAlphaPtr = dest->alpha;
-  for (y = 0; y < scaledHeight; ++y) {
-
-    // y scale Bresenham
-    if ((yt += yq) >= scaledHeight) {
-      yt -= scaledHeight;
-      yStep = yp + 1;
+    // draw it
+    if (interpolate) {
+      drawImageArbitraryInterp(scaledColor, scaledAlpha,
+			       &dd, drawRowFunc, invMat,
+			       scaledWidth, scaledHeight,
+			       xMin, yMin, xMax, yMax,
+			       nComps, srcAlpha);
     } else {
-      yStep = yp;
+      drawImageArbitraryNoInterp(scaledColor, scaledAlpha,
+				 &dd, drawRowFunc, invMat,
+				 scaledWidth, scaledHeight,
+				 xMin, yMin, xMax, yMax,
+				 nComps, srcAlpha);
     }
 
-    // read rows from image
-    memset(pixBuf, 0, srcWidth * nComps * sizeof(int));
-    if (srcAlpha) {
-      memset(alphaPixBuf, 0, srcWidth * sizeof(int));
+    // free the downscaled/unscaled image
+    if (freeScaledImage) {
+      gfree(scaledColor);
+      gfree(scaledAlpha);
     }
-    for (i = 0; i < yStep; ++i) {
-      (*src)(srcData, lineBuf, alphaLineBuf);
-      for (j = 0; j < srcWidth * nComps; ++j) {
-	pixBuf[j] += lineBuf[j];
-      }
-      if (srcAlpha) {
-	for (j = 0; j < srcWidth; ++j) {
-	  alphaPixBuf[j] += alphaLineBuf[j];
-	}
-      }
-    }
+  }
 
-    // init x scale Bresenham
-    xt = 0;
-    d0 = (1 << 23) / (yStep * xp);
-    d1 = (1 << 23) / (yStep * (xp + 1));
+  return splashOk;
+}
 
-    xx = xxa = 0;
-    for (x = 0; x < scaledWidth; ++x) {
+ImageScaler *Splash::getImageScaler(GString *imageTag,
+				    SplashImageSource src, void *srcData,
+				    int w, int h, int nComps,
+				    int scaledWidth, int scaledHeight,
+				    SplashColorMode srcMode,
+				    GBool srcAlpha, GBool interpolate) {
+  // Notes:
+  //
+  // * If the scaled image width or height is greater than 2000, we
+  //   don't cache it.
+  //
+  // * Caching is done on the third consecutive use (second
+  //   consecutive reuse) of an image; this avoids overhead on the
+  //   common case of single-use images.
 
-      // x scale Bresenham
-      if ((xt += xq) >= scaledWidth) {
-	xt -= scaledWidth;
-	xStep = xp + 1;
-	d = d1;
+  if (scaledWidth < 2000 && scaledHeight < 2000 &&
+      imageCache->match(imageTag, scaledWidth, scaledHeight,
+			srcMode, srcAlpha, interpolate)) {
+    if (imageCache->colorData) {
+      return new ReplayImageScaler(nComps, srcAlpha, scaledWidth,
+				   imageCache->colorData,
+				   imageCache->alphaData);
+    } else {
+      int lineSize;
+      if (scaledWidth < INT_MAX / nComps) {
+	lineSize = scaledWidth * nComps;
       } else {
-	xStep = xp;
-	d = d0;
+	lineSize = -1;
       }
-
-      switch (srcMode) {
-
-      case splashModeMono8:
-
-	// compute the final pixel
-	pix0 = 0;
-	for (i = 0; i < xStep; ++i) {
-	  pix0 += pixBuf[xx++];
-	}
-	// pix / xStep * yStep
-	pix0 = (pix0 * d + (1 << 22)) >> 23;
-
-	// store the pixel
-	*destPtr++ = (Guchar)pix0;
-	break;
-
-      case splashModeRGB8:
-
-	// compute the final pixel
-	pix0 = pix1 = pix2 = 0;
-	for (i = 0; i < xStep; ++i) {
-	  pix0 += pixBuf[xx];
-	  pix1 += pixBuf[xx+1];
-	  pix2 += pixBuf[xx+2];
-	  xx += 3;
-	}
-	// pix / xStep * yStep
-	pix0 = (pix0 * d + (1 << 22)) >> 23;
-	pix1 = (pix1 * d + (1 << 22)) >> 23;
-	pix2 = (pix2 * d + (1 << 22)) >> 23;
-
-	// store the pixel
-	*destPtr++ = (Guchar)pix0;
-	*destPtr++ = (Guchar)pix1;
-	*destPtr++ = (Guchar)pix2;
-	break;
-
-#if SPLASH_CMYK
-      case splashModeCMYK8:
-
-	// compute the final pixel
-	pix0 = pix1 = pix2 = pix3 = 0;
-	for (i = 0; i < xStep; ++i) {
-	  pix0 += pixBuf[xx];
-	  pix1 += pixBuf[xx+1];
-	  pix2 += pixBuf[xx+2];
-	  pix3 += pixBuf[xx+3];
-	  xx += 4;
-	}
-	// pix / xStep * yStep
-	pix0 = (pix0 * d + (1 << 22)) >> 23;
-	pix1 = (pix1 * d + (1 << 22)) >> 23;
-	pix2 = (pix2 * d + (1 << 22)) >> 23;
-	pix3 = (pix3 * d + (1 << 22)) >> 23;
-
-	// store the pixel
-	*destPtr++ = (Guchar)pix0;
-	*destPtr++ = (Guchar)pix1;
-	*destPtr++ = (Guchar)pix2;
-	*destPtr++ = (Guchar)pix3;
-	break;
-#endif
-
-
-      case splashModeMono1: // mono1 is not allowed
-      case splashModeBGR8: // bgr8 is not allowed
-      default:
-	break;
-      }
-
-      // process alpha
+      imageCache->colorData = (Guchar *)gmallocn(scaledHeight, lineSize);
       if (srcAlpha) {
-	alpha = 0;
-	for (i = 0; i < xStep; ++i, ++xxa) {
-	  alpha += alphaPixBuf[xxa];
-	}
-	// alpha / xStep * yStep
-	alpha = (alpha * d + (1 << 22)) >> 23;
-	*destAlphaPtr++ = (Guchar)alpha;
+	imageCache->alphaData = (Guchar *)gmallocn(scaledHeight, scaledWidth);
       }
+      return new SavingImageScaler(src, srcData,
+				   w, h, nComps, srcAlpha,
+				   scaledWidth, scaledHeight,
+				   interpolate,
+				   imageCache->colorData,
+				   imageCache->alphaData);
     }
+  } else {
+    imageCache->reset(imageTag, scaledWidth, scaledHeight,
+		      srcMode, srcAlpha, interpolate);
+    return new BasicImageScaler(src, srcData,
+				w, h, nComps, srcAlpha,
+				scaledWidth, scaledHeight,
+				interpolate);
   }
-
-  gfree(alphaPixBuf);
-  gfree(alphaLineBuf);
-  gfree(pixBuf);
-  gfree(lineBuf);
 }
 
-void Splash::scaleImageYdXu(SplashImageSource src, void *srcData,
-			    SplashColorMode srcMode, int nComps,
-			    GBool srcAlpha, int srcWidth, int srcHeight,
+void Splash::getScaledImage(GString *imageTag,
+			    SplashImageSource src, void *srcData,
+			    int w, int h, int nComps,
 			    int scaledWidth, int scaledHeight,
-			    SplashBitmap *dest) {
-  Guchar *lineBuf, *alphaLineBuf;
-  Guint *pixBuf, *alphaPixBuf;
-  Guint pix[splashMaxColorComps];
-  Guint alpha;
-  Guchar *destPtr, *destAlphaPtr;
-  int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, d;
-  int i, j;
+			    SplashColorMode srcMode,
+			    GBool srcAlpha, GBool interpolate,
+			    Guchar **scaledColor, Guchar **scaledAlpha,
+			    GBool *freeScaledImage) {
+  // Notes:
+  //
+  // * If the scaled image width or height is greater than 2000, we
+  //   don't cache it.
+  //
+  // * This buffers the whole image anyway, so there's no reason to
+  //   skip caching on the first reuse.
 
-  // Bresenham parameters for y scale
-  yp = srcHeight / scaledHeight;
-  yq = srcHeight % scaledHeight;
-
-  // Bresenham parameters for x scale
-  xp = scaledWidth / srcWidth;
-  xq = scaledWidth % srcWidth;
-
-  // allocate buffers
-  lineBuf = (Guchar *)gmallocn(srcWidth, nComps);
-  pixBuf = (Guint *)gmallocn(srcWidth, (int)(nComps * sizeof(int)));
-  if (srcAlpha) {
-    alphaLineBuf = (Guchar *)gmalloc(srcWidth);
-    alphaPixBuf = (Guint *)gmallocn(srcWidth, sizeof(int));
-  } else {
-    alphaLineBuf = NULL;
-    alphaPixBuf = NULL;
-  }
-
-  // make gcc happy
-  pix[0] = pix[1] = pix[2] = 0;
-#if SPLASH_CMYK
-  pix[3] = 0;
-#endif
-
-  // init y scale Bresenham
-  yt = 0;
-
-  destPtr = dest->data;
-  destAlphaPtr = dest->alpha;
-  for (y = 0; y < scaledHeight; ++y) {
-
-    // y scale Bresenham
-    if ((yt += yq) >= scaledHeight) {
-      yt -= scaledHeight;
-      yStep = yp + 1;
+  if (scaledWidth >= 2000 || scaledHeight >= 2000) {
+    int lineSize;
+    if (scaledWidth < INT_MAX / nComps) {
+      lineSize = scaledWidth * nComps;
     } else {
-      yStep = yp;
+      lineSize = -1;
     }
-
-    // read rows from image
-    memset(pixBuf, 0, srcWidth * nComps * sizeof(int));
+    *scaledColor = (Guchar *)gmallocn(scaledHeight, lineSize);
     if (srcAlpha) {
-      memset(alphaPixBuf, 0, srcWidth * sizeof(int));
+      *scaledAlpha = (Guchar *)gmallocn(scaledHeight, scaledWidth);
+    } else {
+      *scaledAlpha = NULL;
     }
-    for (i = 0; i < yStep; ++i) {
-      (*src)(srcData, lineBuf, alphaLineBuf);
-      for (j = 0; j < srcWidth * nComps; ++j) {
-	pixBuf[j] += lineBuf[j];
+    *freeScaledImage = gTrue;
+    if (scaledWidth == w && scaledHeight == h) {
+      Guchar *colorPtr = *scaledColor;
+      Guchar *alphaPtr = *scaledAlpha;
+      for (int y = 0; y < scaledHeight; ++y) {
+	(*src)(srcData, colorPtr, alphaPtr);
+	colorPtr += scaledWidth * nComps;
+	if (srcAlpha) {
+	  alphaPtr += scaledWidth;
+	}
       }
-      if (srcAlpha) {
-	for (j = 0; j < srcWidth; ++j) {
-	  alphaPixBuf[j] += alphaLineBuf[j];
+    } else {
+      BasicImageScaler scaler(src, srcData, w, h, nComps, srcAlpha,
+			      scaledWidth, scaledHeight, interpolate);
+      Guchar *colorPtr = *scaledColor;
+      Guchar *alphaPtr = *scaledAlpha;
+      for (int y = 0; y < scaledHeight; ++y) {
+	scaler.nextLine();
+	memcpy(colorPtr, scaler.colorData(), scaledWidth * nComps);
+	colorPtr += scaledWidth * nComps;
+	if (srcAlpha) {
+	  memcpy(alphaPtr, scaler.alphaData(), scaledWidth);
+	  alphaPtr += scaledWidth;
 	}
       }
     }
-
-    // init x scale Bresenham
-    xt = 0;
-    d = (1 << 23) / yStep;
-
-    for (x = 0; x < srcWidth; ++x) {
-
-      // x scale Bresenham
-      if ((xt += xq) >= srcWidth) {
-	xt -= srcWidth;
-	xStep = xp + 1;
+  } else {
+    if (!imageCache->match(imageTag, scaledWidth, scaledHeight,
+			  srcMode, srcAlpha, interpolate) ||
+	!imageCache->colorData) {
+      imageCache->reset(imageTag, scaledWidth, scaledHeight,
+			srcMode, srcAlpha, interpolate);
+      int lineSize;
+      if (scaledWidth < INT_MAX / nComps) {
+	lineSize = scaledWidth * nComps;
       } else {
-	xStep = xp;
+	lineSize = -1;
       }
-
-      // compute the final pixel
-      for (i = 0; i < nComps; ++i) {
-	// pixBuf[] / yStep
-	pix[i] = (pixBuf[x * nComps + i] * d + (1 << 22)) >> 23;
+      imageCache->colorData = (Guchar *)gmallocn(scaledHeight, lineSize);
+      if (srcAlpha) {
+	imageCache->alphaData = (Guchar *)gmallocn(scaledHeight, scaledWidth);
       }
-
-      // store the pixel
-      switch (srcMode) {
-      case splashModeMono8:
-	for (i = 0; i < xStep; ++i) {
-	  *destPtr++ = (Guchar)pix[0];
+      if (scaledWidth == w && scaledHeight == h) {
+	Guchar *colorPtr = imageCache->colorData;
+	Guchar *alphaPtr = imageCache->alphaData;
+	for (int y = 0; y < scaledHeight; ++y) {
+	  (*src)(srcData, colorPtr, alphaPtr);
+	  colorPtr += scaledWidth * nComps;
+	  if (srcAlpha) {
+	    alphaPtr += scaledWidth;
+	  }
 	}
-	break;
-      case splashModeRGB8:
-	for (i = 0; i < xStep; ++i) {
-	  *destPtr++ = (Guchar)pix[0];
-	  *destPtr++ = (Guchar)pix[1];
-	  *destPtr++ = (Guchar)pix[2];
+      } else {
+	SavingImageScaler scaler(src, srcData, w, h, nComps, srcAlpha,
+				 scaledWidth, scaledHeight, interpolate,
+				 imageCache->colorData, imageCache->alphaData);
+	Guchar *colorPtr = imageCache->colorData;
+	Guchar *alphaPtr = imageCache->alphaData;
+	for (int y = 0; y < scaledHeight; ++y) {
+	  scaler.nextLine();
+	  memcpy(colorPtr, scaler.colorData(), scaledWidth * nComps);
+	  colorPtr += scaledWidth * nComps;
+	  if (srcAlpha) {
+	    memcpy(alphaPtr, scaler.alphaData(), scaledWidth);
+	    alphaPtr += scaledWidth;
+	  }
 	}
-	break;
-#if SPLASH_CMYK
-      case splashModeCMYK8:
-	for (i = 0; i < xStep; ++i) {
-	  *destPtr++ = (Guchar)pix[0];
-	  *destPtr++ = (Guchar)pix[1];
-	  *destPtr++ = (Guchar)pix[2];
-	  *destPtr++ = (Guchar)pix[3];
-	}
-	break;
-#endif
-      case splashModeMono1: // mono1 is not allowed
-      case splashModeBGR8: // BGR8 is not allowed
-      default:
-	break;
       }
-
-      // process alpha
-      if (srcAlpha) {
-	// alphaPixBuf[] / yStep
-	alpha = (alphaPixBuf[x] * d + (1 << 22)) >> 23;
-	for (i = 0; i < xStep; ++i) {
-	  *destAlphaPtr++ = (Guchar)alpha;
-	}
-      }
     }
+    *scaledColor = imageCache->colorData;
+    *scaledAlpha = imageCache->alphaData;
+    *freeScaledImage = gFalse;
   }
-
-  gfree(alphaPixBuf);
-  gfree(alphaLineBuf);
-  gfree(pixBuf);
-  gfree(lineBuf);
 }
 
-void Splash::scaleImageYuXd(SplashImageSource src, void *srcData,
-			    SplashColorMode srcMode, int nComps,
-			    GBool srcAlpha, int srcWidth, int srcHeight,
-			    int scaledWidth, int scaledHeight,
-			    SplashBitmap *dest) {
-  Guchar *lineBuf, *alphaLineBuf;
-  Guint pix[splashMaxColorComps];
-  Guint alpha;
-  Guchar *destPtr0, *destPtr, *destAlphaPtr0, *destAlphaPtr;
-  int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep, xx, xxa, d, d0, d1;
-  int i, j;
+void Splash::drawImageArbitraryNoInterp(Guchar *scaledColor,
+					Guchar *scaledAlpha,
+					SplashDrawImageRowData *dd,
+					SplashDrawImageRowFunc drawRowFunc,
+					SplashCoord *invMat,
+					int scaledWidth, int scaledHeight,
+					int xMin, int yMin, int xMax, int yMax,
+					int nComps, GBool srcAlpha) {
+  int tt = state->clip->getXMinI(state->strokeAdjust);
+  if (tt > xMin) {
+    xMin = tt;
+  }
+  tt = state->clip->getXMaxI(state->strokeAdjust) + 1;
+  if (tt < xMax) {
+    xMax = tt;
+  }
+  tt = state->clip->getYMinI(state->strokeAdjust);
+  if (tt > yMin) {
+    yMin = tt;
+  }
+  tt = state->clip->getYMaxI(state->strokeAdjust) + 1;
+  if (tt < yMax) {
+    yMax = tt;
+  }
+  if (xMax <= xMin || yMax <= yMin) {
+    return;
+  }
 
-  // Bresenham parameters for y scale
-  yp = scaledHeight / srcHeight;
-  yq = scaledHeight % srcHeight;
-
-  // Bresenham parameters for x scale
-  xp = srcWidth / scaledWidth;
-  xq = srcWidth % scaledWidth;
-
-  // allocate buffers
-  lineBuf = (Guchar *)gmallocn(srcWidth, nComps);
+  Guchar *colorBuf = (Guchar *)gmallocn(xMax - xMin, nComps);
+  Guchar *alphaBuf = NULL;
   if (srcAlpha) {
-    alphaLineBuf = (Guchar *)gmalloc(srcWidth);
-  } else {
-    alphaLineBuf = NULL;
+    alphaBuf = (Guchar *)gmalloc(xMax - xMin);
   }
 
-  // make gcc happy
-  pix[0] = pix[1] = pix[2] = 0;
-#if SPLASH_CMYK
-  pix[3] = 0;
-#endif
-
-  // init y scale Bresenham
-  yt = 0;
-
-  destPtr0 = dest->data;
-  destAlphaPtr0 = dest->alpha;
-  for (y = 0; y < srcHeight; ++y) {
-
-    // y scale Bresenham
-    if ((yt += yq) >= srcHeight) {
-      yt -= srcHeight;
-      yStep = yp + 1;
-    } else {
-      yStep = yp;
-    }
-
-    // read row from image
-    (*src)(srcData, lineBuf, alphaLineBuf);
-
-    // init x scale Bresenham
-    xt = 0;
-    d0 = (1 << 23) / xp;
-    d1 = (1 << 23) / (xp + 1);
-
-    xx = xxa = 0;
-    for (x = 0; x < scaledWidth; ++x) {
-
-      // x scale Bresenham
-      if ((xt += xq) >= scaledWidth) {
-	xt -= scaledWidth;
-	xStep = xp + 1;
-	d = d1;
-      } else {
-	xStep = xp;
-	d = d0;
-      }
-
-      // compute the final pixel
-      for (i = 0; i < nComps; ++i) {
-	pix[i] = 0;
-      }
-      for (i = 0; i < xStep; ++i) {
-	for (j = 0; j < nComps; ++j, ++xx) {
-	  pix[j] += lineBuf[xx];
+  for (int y = yMin; y < yMax; ++y) {
+    int rowMin = xMax;
+    int rowMax = 0;
+    for (int x = xMin; x < xMax; ++x) {
+      // note: invMat includes a "+0.5" factor so that this is really
+      // a multiply by (x+0.5, y+0.5)
+      int xx = splashFloor((SplashCoord)x * invMat[0]
+			   + (SplashCoord)y * invMat[2] + invMat[4]);
+      int yy = splashFloor((SplashCoord)x * invMat[1]
+			   + (SplashCoord)y * invMat[3] + invMat[5]);
+      if (xx >= 0 && xx < scaledWidth &&
+	  yy >= 0 && yy < scaledHeight) {
+	Guchar *p = scaledColor + (yy * scaledWidth + xx) * nComps;
+	Guchar *q = colorBuf + (x - xMin) * nComps;
+	for (int i = 0; i < nComps; ++i) {
+	  *q++ = *p++;
 	}
-      }
-      for (i = 0; i < nComps; ++i) {
-	// pix[] / xStep
-	pix[i] = (pix[i] * d + (1 << 22)) >> 23;
-      }
-
-      // store the pixel
-      switch (srcMode) {
-      case splashModeMono8:
-	for (i = 0; i < yStep; ++i) {
-	  destPtr = destPtr0 + (i * scaledWidth + x) * nComps;
-	  *destPtr++ = (Guchar)pix[0];
+	if (srcAlpha) {
+	  alphaBuf[x - xMin] = scaledAlpha[yy * scaledWidth + xx];
 	}
-	break;
-      case splashModeRGB8:
-	for (i = 0; i < yStep; ++i) {
-	  destPtr = destPtr0 + (i * scaledWidth + x) * nComps;
-	  *destPtr++ = (Guchar)pix[0];
-	  *destPtr++ = (Guchar)pix[1];
-	  *destPtr++ = (Guchar)pix[2];
+	if (x < rowMin) {
+	  rowMin = x;
 	}
-	break;
-#if SPLASH_CMYK
-      case splashModeCMYK8:
-	for (i = 0; i < yStep; ++i) {
-	  destPtr = destPtr0 + (i * scaledWidth + x) * nComps;
-	  *destPtr++ = (Guchar)pix[0];
-	  *destPtr++ = (Guchar)pix[1];
-	  *destPtr++ = (Guchar)pix[2];
-	  *destPtr++ = (Guchar)pix[3];
-	}
-	break;
-#endif
-      case splashModeMono1: // mono1 is not allowed
-      case splashModeBGR8: // BGR8 is not allowed
-      default:
-	break;
+	rowMax = x + 1;
       }
-
-      // process alpha
-      if (srcAlpha) {
-	alpha = 0;
-	for (i = 0; i < xStep; ++i, ++xxa) {
-	  alpha += alphaLineBuf[xxa];
-	}
-	// alpha / xStep
-	alpha = (alpha * d + (1 << 22)) >> 23;
-	for (i = 0; i < yStep; ++i) {
-	  destAlphaPtr = destAlphaPtr0 + i * scaledWidth + x;
-	  *destAlphaPtr = (Guchar)alpha;
-	}
-      }
     }
-
-    destPtr0 += yStep * scaledWidth * nComps;
-    if (srcAlpha) {
-      destAlphaPtr0 += yStep * scaledWidth;
+    if (rowMin < rowMax) {
+      (this->*drawRowFunc)(dd,
+			   colorBuf + (rowMin - xMin) * nComps,
+			   alphaBuf + (rowMin - xMin),
+			   rowMin, y, rowMax - rowMin);
     }
   }
 
-  gfree(alphaLineBuf);
-  gfree(lineBuf);
+  gfree(colorBuf);
+  gfree(alphaBuf);
 }
 
-void Splash::scaleImageYuXu(SplashImageSource src, void *srcData,
-			    SplashColorMode srcMode, int nComps,
-			    GBool srcAlpha, int srcWidth, int srcHeight,
-			    int scaledWidth, int scaledHeight,
-			    SplashBitmap *dest) {
-  Guchar *lineBuf, *alphaLineBuf;
-  Guchar pix0, pix1, pix2;
-#if SPLASH_CMYK
-  Guchar pix3;
-#endif
-  Guchar alpha;
-  Guchar *srcPtr, *srcAlphaPtr;
-  Guchar *destPtr, *destAlphaPtr;
-  int yp, yq, xp, xq, yt, y, yStep, xt, x, xStep;
-  int i;
+void Splash::drawImageArbitraryInterp(Guchar *scaledColor, Guchar *scaledAlpha,
+				      SplashDrawImageRowData *dd,
+				      SplashDrawImageRowFunc drawRowFunc,
+				      SplashCoord *invMat,
+				      int scaledWidth, int scaledHeight,
+				      int xMin, int yMin, int xMax, int yMax,
+				      int nComps, GBool srcAlpha) {
+  int tt = state->clip->getXMinI(state->strokeAdjust);
+  if (tt > xMin) {
+    xMin = tt;
+  }
+  tt = state->clip->getXMaxI(state->strokeAdjust) + 1;
+  if (tt < xMax) {
+    xMax = tt;
+  }
+  tt = state->clip->getYMinI(state->strokeAdjust);
+  if (tt > yMin) {
+    yMin = tt;
+  }
+  tt = state->clip->getYMaxI(state->strokeAdjust) + 1;
+  if (tt < yMax) {
+    yMax = tt;
+  }
+  if (xMax <= xMin || yMax <= yMin) {
+    return;
+  }
 
-  // Bresenham parameters for y scale
-  yp = scaledHeight / srcHeight;
-  yq = scaledHeight % srcHeight;
-
-  // Bresenham parameters for x scale
-  xp = scaledWidth / srcWidth;
-  xq = scaledWidth % srcWidth;
-
-  // allocate buffers
-  lineBuf = (Guchar *)gmallocn(srcWidth, nComps);
+  Guchar *colorBuf = (Guchar *)gmallocn(xMax - xMin, nComps);
+  Guchar *alphaBuf = NULL;
   if (srcAlpha) {
-    alphaLineBuf = (Guchar *)gmalloc(srcWidth);
-  } else {
-    alphaLineBuf = NULL;
+    alphaBuf = (Guchar *)gmalloc(xMax - xMin);
   }
 
-  // init y scale Bresenham
-  yt = 0;
-
-  destPtr = dest->data;
-  destAlphaPtr = dest->alpha;
-  for (y = 0; y < srcHeight; ++y) {
-
-    // y scale Bresenham
-    if ((yt += yq) >= srcHeight) {
-      yt -= srcHeight;
-      yStep = yp + 1;
-    } else {
-      yStep = yp;
-    }
-
-    // read row from image
-    (*src)(srcData, lineBuf, alphaLineBuf);
-
-    // init x scale Bresenham
-    xt = 0;
-
-    // generate one row
-    srcPtr = lineBuf;
-    srcAlphaPtr = alphaLineBuf;
-    for (x = 0; x < srcWidth; ++x) {
-
-      // x scale Bresenham
-      if ((xt += xq) >= srcWidth) {
-	xt -= srcWidth;
-	xStep = xp + 1;
-      } else {
-	xStep = xp;
-      }
-
-      // duplicate the pixel horizontally
-      switch (srcMode) {
-      case splashModeMono8:
-	pix0 = *srcPtr++;
-	for (i = 0; i < xStep; ++i) {
-	  *destPtr++ = pix0;
+  for (int y = yMin; y < yMax; ++y) {
+    int rowMin = xMax;
+    int rowMax = 0;
+    for (int x = xMin; x < xMax; ++x) {
+      // note: invMat includes a "+0.5" factor so that this is really
+      // a multiply by (x+0.5, y+0.5)
+      SplashCoord xs = (SplashCoord)x * invMat[0]
+	               + (SplashCoord)y * invMat[2] + invMat[4];
+      SplashCoord ys = (SplashCoord)x * invMat[1]
+	               + (SplashCoord)y * invMat[3] + invMat[5];
+      int x0 = splashFloor(xs - 0.5);
+      int x1 = x0 + 1;
+      int y0 = splashFloor(ys - 0.5);
+      int y1 = y0 + 1;
+      if (x1 >= 0 && x0 < scaledWidth && y1 >= 0 && y0 < scaledHeight) {
+	SplashCoord sx0 = (SplashCoord)x1 + 0.5 - xs;
+	SplashCoord sx1 = (SplashCoord)1 - sx0;
+	SplashCoord sy0 = (SplashCoord)y1 + 0.5 - ys;
+	SplashCoord sy1 = (SplashCoord)1 - sy0;
+	if (x0 < 0) {
+	  x0 = 0;
 	}
-	break;
-      case splashModeRGB8:
-	pix0 = *srcPtr++;
-	pix1 = *srcPtr++;
-	pix2 = *srcPtr++;
-	for (i = 0; i < xStep; ++i) {
-	  *destPtr++ = pix0;
-	  *destPtr++ = pix1;
-	  *destPtr++ = pix2;
+	if (x1 >= scaledWidth) {
+	  x1 = scaledWidth - 1;
 	}
-	break;
-#if SPLASH_CMYK
-      case splashModeCMYK8:
-	pix0 = *srcPtr++;
-	pix1 = *srcPtr++;
-	pix2 = *srcPtr++;
-	pix3 = *srcPtr++;
-	for (i = 0; i < xStep; ++i) {
-	  *destPtr++ = pix0;
-	  *destPtr++ = pix1;
-	  *destPtr++ = pix2;
-	  *destPtr++ = pix3;
+	if (y0 < 0) {
+	  y0 = 0;
 	}
-	break;
-#endif
-      case splashModeMono1: // mono1 is not allowed
-      case splashModeBGR8: // BGR8 is not allowed
-      default:
-	break;
-      }
-
-      // duplicate the alpha value horizontally
-      if (srcAlpha) {
-	alpha = *srcAlphaPtr++;
-	for (i = 0; i < xStep; ++i) {
-	  *destAlphaPtr++ = alpha;
+	if (y1 >= scaledHeight) {
+	  y1 = scaledHeight - 1;
 	}
+	Guchar *p00 = scaledColor + (y0 * scaledWidth + x0) * nComps;
+	Guchar *p10 = scaledColor + (y0 * scaledWidth + x1) * nComps;
+	Guchar *p01 = scaledColor + (y1 * scaledWidth + x0) * nComps;
+	Guchar *p11 = scaledColor + (y1 * scaledWidth + x1) * nComps;
+	Guchar *q = colorBuf + (x - xMin) * nComps;
+	for (int i = 0; i < nComps; ++i) {
+	  *q++ = (Guchar)(int)(sx0 * (sy0 * (int)*p00++ + sy1 * (int)*p01++) +
+			       sx1 * (sy0 * (int)*p10++ + sy1 * (int)*p11++));
+	}
+	if (srcAlpha) {
+	  p00 = scaledAlpha + (y0 * scaledWidth + x0);
+	  p10 = scaledAlpha + (y0 * scaledWidth + x1);
+	  p01 = scaledAlpha + (y1 * scaledWidth + x0);
+	  p11 = scaledAlpha + (y1 * scaledWidth + x1);
+	  q = alphaBuf + (x - xMin);
+	  *q = (Guchar)(int)(sx0 * (sy0 * (int)*p00 + sy1 * (int)*p01) +
+			     sx1 * (sy0 * (int)*p10 + sy1 * (int)*p11));
+	}
+	if (x < rowMin) {
+	  rowMin = x;
+	}
+	rowMax = x + 1;
       }
     }
-
-    // duplicate the row vertically
-    for (i = 1; i < yStep; ++i) {
-      memcpy(destPtr, destPtr - scaledWidth * nComps,
-	     scaledWidth * nComps);
-      destPtr += scaledWidth * nComps;
+    if (rowMin < rowMax) {
+      (this->*drawRowFunc)(dd,
+			   colorBuf + (rowMin - xMin) * nComps,
+			   alphaBuf + (rowMin - xMin),
+			   rowMin, y, rowMax - rowMin);
     }
-    if (srcAlpha) {
-      for (i = 1; i < yStep; ++i) {
-	memcpy(destAlphaPtr, destAlphaPtr - scaledWidth, scaledWidth);
-	destAlphaPtr += scaledWidth;
-      }
-    }
   }
 
-  gfree(alphaLineBuf);
-  gfree(lineBuf);
+  gfree(colorBuf);
+  gfree(alphaBuf);
 }
 
-void Splash::scaleImageYuXuI(SplashImageSource src, void *srcData,
-			     SplashColorMode srcMode, int nComps,
-			     GBool srcAlpha, int srcWidth, int srcHeight,
-			     int scaledWidth, int scaledHeight,
-			     SplashBitmap *dest) {
-  Guchar *lineBuf0, *lineBuf1, *alphaLineBuf0, *alphaLineBuf1, *tBuf;
-  Guchar pix[splashMaxColorComps];
-  SplashCoord yr, xr, ys, xs, ySrc, xSrc;
-  int ySrc0, ySrc1, yBuf, xSrc0, xSrc1, y, x, i;
-  Guchar *destPtr, *destAlphaPtr;
+void Splash::mirrorImageRow(Guchar *colorIn, Guchar *alphaIn,
+			    Guchar *colorOut, Guchar *alphaOut,
+			    int width, int nComps, GBool srcAlpha) {
+  Guchar *p, *q;
 
-  // ratios
-  yr = (SplashCoord)srcHeight / (SplashCoord)scaledHeight;
-  xr = (SplashCoord)srcWidth / (SplashCoord)scaledWidth;
-
-  // allocate buffers
-  lineBuf0 = (Guchar *)gmallocn(scaledWidth, nComps);
-  lineBuf1 = (Guchar *)gmallocn(scaledWidth, nComps);
-  if (srcAlpha) {
-    alphaLineBuf0 = (Guchar *)gmalloc(scaledWidth);
-    alphaLineBuf1 = (Guchar *)gmalloc(scaledWidth);
-  } else {
-    alphaLineBuf0 = NULL;
-    alphaLineBuf1 = NULL;
-  }
-
-  // read first two rows
-  (*src)(srcData, lineBuf0, alphaLineBuf0);
-  if (srcHeight > 1) {
-    (*src)(srcData, lineBuf1, alphaLineBuf1);
-    yBuf = 1;
-  } else {
-    memcpy(lineBuf1, lineBuf0, srcWidth * nComps);
-    if (srcAlpha) {
-      memcpy(alphaLineBuf1, alphaLineBuf0, srcWidth);
+  p = colorIn;
+  q = colorOut + (width - 1) * nComps;
+  for (int i = 0; i < width; ++i) {
+    for (int j = 0; j < nComps; ++j) {
+      q[j] = p[j];
     }
-    yBuf = 0;
+    p += nComps;
+    q -= nComps;
   }
 
-  // interpolate first two rows
-  for (x = scaledWidth - 1; x >= 0; --x) {
-    xSrc = xr * x;
-    xSrc0 = splashFloor(xSrc + xr * 0.5 - 0.5);
-    xSrc1 = xSrc0 + 1;
-    xs = ((SplashCoord)xSrc1 + 0.5) - (xSrc + xr * 0.5);
-    if (xSrc0 < 0) {
-      xSrc0 = 0;
+  if (srcAlpha) {
+    p = alphaIn;
+    q = alphaOut + (width - 1);
+    for (int i = 0; i < width; ++i) {
+      *q = *p;
+      ++p;
+      --q;
     }
-    if (xSrc1 >= srcWidth) {
-      xSrc1 = srcWidth - 1;
-    }
-    for (i = 0; i < nComps; ++i) {
-      lineBuf0[x*nComps+i] = (Guchar)(int)
-	    (xs * (int)lineBuf0[xSrc0*nComps+i] +
-	     ((SplashCoord)1 - xs) * (int)lineBuf0[xSrc1*nComps+i]);
-      lineBuf1[x*nComps+i] = (Guchar)(int)
-	    (xs * (int)lineBuf1[xSrc0*nComps+i] +
-	     ((SplashCoord)1 - xs) * (int)lineBuf1[xSrc1*nComps+i]);
-    }
-    if (srcAlpha) {
-      alphaLineBuf0[x] = (Guchar)(int)
-	    (xs * (int)alphaLineBuf0[xSrc0] +
-	     ((SplashCoord)1 - xs) * (int)alphaLineBuf0[xSrc1]);
-      alphaLineBuf1[x] = (Guchar)(int)
-	    (xs * (int)alphaLineBuf1[xSrc0] +
-	     ((SplashCoord)1 - xs) * (int)alphaLineBuf1[xSrc1]);
-    }
   }
+}
 
-  // make gcc happy
-  pix[0] = pix[1] = pix[2] = 0;
-#if SPLASH_CMYK
-  pix[3] = 0;
-#endif
+void Splash::drawImageRowNoClipNoAlpha(SplashDrawImageRowData *data,
+				       Guchar *colorData, Guchar *alphaData,
+				       int x, int y, int width) {
+  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y, NULL, colorData);
+}
 
-  destPtr = dest->data;
-  destAlphaPtr = dest->alpha;
-  for (y = 0; y < scaledHeight; ++y) {
-
-    // compute vertical interpolation parameters
-    ySrc = yr * y;
-    ySrc0 = splashFloor(ySrc + yr * 0.5 - 0.5);
-    ySrc1 = ySrc0 + 1;
-    ys = ((SplashCoord)ySrc1 + 0.5) - (ySrc + yr * 0.5);
-    if (ySrc0 < 0) {
-      ySrc0 = 0;
-      ys = 1;
-    }
-    if (ySrc1 >= srcHeight) {
-      ySrc1 = srcHeight - 1;
-      ys = 0;
-    }
-
-    // read another row (if necessary)
-    if (ySrc1 > yBuf) {
-      tBuf = lineBuf0;
-      lineBuf0 = lineBuf1;
-      lineBuf1 = tBuf;
-      tBuf = alphaLineBuf0;
-      alphaLineBuf0 = alphaLineBuf1;
-      alphaLineBuf1 = tBuf;
-      (*src)(srcData, lineBuf1, alphaLineBuf1);
-
-      // interpolate the row
-      for (x = scaledWidth - 1; x >= 0; --x) {
-	xSrc = xr * x;
-	xSrc0 = splashFloor(xSrc + xr * 0.5 - 0.5);
-	xSrc1 = xSrc0 + 1;
-	xs = ((SplashCoord)xSrc1 + 0.5) - (xSrc + xr * 0.5);
-	if (xSrc0 < 0) {
-	  xSrc0 = 0;
-	}
-	if (xSrc1 >= srcWidth) {
-	  xSrc1 = srcWidth - 1;
-	}
-	for (i = 0; i < nComps; ++i) {
-	  lineBuf1[x*nComps+i] = (Guchar)(int)
-	        (xs * (int)lineBuf1[xSrc0*nComps+i] +
-		 ((SplashCoord)1 - xs) * (int)lineBuf1[xSrc1*nComps+i]);
-	}
-	if (srcAlpha) {
-	  alphaLineBuf1[x] = (Guchar)(int)
-	        (xs * (int)alphaLineBuf1[xSrc0] +
-		 ((SplashCoord)1 - xs) * (int)alphaLineBuf1[xSrc1]);
-	}
-      }
-
-      ++yBuf;
-    }
-
-    // do the vertical interpolation
-    for (x = 0; x < scaledWidth; ++x) {
-
-      for (i = 0; i < nComps; ++i) {
-	pix[i] = (Guchar)(int)
-	         (ys * (int)lineBuf0[x*nComps+i] +
-		  ((SplashCoord)1 - ys) * (int)lineBuf1[x*nComps+i]);
-      }
-
-      // store the pixel
-      switch (srcMode) {
-      case splashModeMono8:
-	*destPtr++ = pix[0];
-	break;
-      case splashModeRGB8:
-	*destPtr++ = pix[0];
-	*destPtr++ = pix[1];
-	*destPtr++ = pix[2];
-	break;
-#if SPLASH_CMYK
-      case splashModeCMYK8:
-	*destPtr++ = pix[0];
-	*destPtr++ = pix[1];
-	*destPtr++ = pix[2];
-	*destPtr++ = pix[3];
-	break;
-#endif
-      case splashModeMono1: // mono1 is not allowed
-      case splashModeBGR8: // BGR8 is not allowed
-      default:
-	break;
-      }
-
-      // process alpha
-      if (srcAlpha) {
-	*destAlphaPtr++ = (Guchar)(int)
-	                  (ys * (int)alphaLineBuf0[x] +
-			   ((SplashCoord)1 - ys) * (int)alphaLineBuf1[x]);
-      }
-    }
-  }
-
-  gfree(alphaLineBuf1);
-  gfree(alphaLineBuf0);
-  gfree(lineBuf1);
-  gfree(lineBuf0);
+void Splash::drawImageRowNoClipAlpha(SplashDrawImageRowData *data,
+				     Guchar *colorData, Guchar *alphaData,
+				     int x, int y, int width) {
+  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
+			  alphaData, colorData);
 }
 
-void Splash::vertFlipImage(SplashBitmap *img, int width, int height,
-			   int nComps) {
-  Guchar *lineBuf;
-  Guchar *p0, *p1;
-  int w;
-
-  w = width * nComps;
-  lineBuf = (Guchar *)gmalloc(w);
-  for (p0 = img->data, p1 = img->data + (height - 1) * (size_t)w;
-       p0 < p1;
-       p0 += w, p1 -= w) {
-    memcpy(lineBuf, p0, w);
-    memcpy(p0, p1, w);
-    memcpy(p1, lineBuf, w);
+void Splash::drawImageRowClipNoAlphaNoAA(SplashDrawImageRowData *data,
+					 Guchar *colorData,
+					 Guchar *alphaData,
+					 int x, int y, int width) {
+  if (y < 0 || y >= bitmap->height) {
+    return;
   }
-  if (img->alpha) {
-    for (p0 = img->alpha, p1 = img->alpha + (height - 1) * (size_t)width;
-	 p0 < p1;
-	 p0 += width, p1 -= width) {
-      memcpy(lineBuf, p0, width);
-      memcpy(p0, p1, width);
-      memcpy(p1, lineBuf, width);
-    }
+  if (x < 0) {
+    colorData -= x * data->nComps;
+    width += x;
+    x = 0;
   }
-  gfree(lineBuf);
-}
-
-void Splash::horizFlipImage(SplashBitmap *img, int width, int height,
-			    int nComps) {
-  Guchar *lineBuf;
-  SplashColorPtr p0, p1, p2;
-  int w, x, y, i;
-
-  w = width * nComps;
-  lineBuf = (Guchar *)gmalloc(w);
-  for (y = 0, p0 = img->data; y < height; ++y, p0 += img->rowSize) {
-    memcpy(lineBuf, p0, w);
-    p1 = p0;
-    p2 = lineBuf + (w - nComps);
-    for (x = 0; x < width; ++x) {
-      for (i = 0; i < nComps; ++i) {
-	p1[i] = p2[i];
-      }
-      p1 += nComps;
-      p2 -= nComps;
-    }
+  if (x + width > bitmap->width) {
+    width = bitmap->width - x;
   }
-  if (img->alpha) {
-    for (y = 0, p0 = img->alpha; y < height; ++y, p0 += width) {
-      memcpy(lineBuf, p0, width);
-      p1 = p0;
-      p2 = lineBuf + (width - 1);
-      for (x = 0; x < width; ++x) {
-	*p1++ = *p2--;
-      }
-    }
+  if (width <= 0) {
+    return;
   }
-  gfree(lineBuf);
+  memset(scanBuf + x, 0xff, width);
+  state->clip->clipSpanBinary(scanBuf, y, x, x + width - 1,
+			      state->strokeAdjust);
+  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
+			  scanBuf + x, colorData);
 }
 
-void Splash::blitImage(SplashBitmap *src, GBool srcAlpha, int xDest, int yDest,
-		       SplashClipResult clipRes) {
-  SplashPipe pipe;
-  int w, h, x0, y0, x1, y1, y;
-
-  // split the image into clipped and unclipped regions
-  w = src->width;
-  h = src->height;
-  if (clipRes == splashClipAllInside) {
-    x0 = 0;
-    y0 = 0;
-    x1 = w;
-    y1 = h;
-  } else {
-    if (state->clip->getNumPaths()) {
-      x0 = x1 = w;
-      y0 = y1 = h;
-    } else {
-      if ((x0 = splashCeil(state->clip->getXMin()) - xDest) < 0) {
-	x0 = 0;
-      }
-      if ((y0 = splashCeil(state->clip->getYMin()) - yDest) < 0) {
-	y0 = 0;
-      }
-      if ((x1 = splashFloor(state->clip->getXMax()) - xDest) > w) {
-	x1 = w;
-      }
-      if (x1 < x0) {
-	x1 = x0;
-      }
-      if ((y1 = splashFloor(state->clip->getYMax()) - yDest) > h) {
-	y1 = h; 
-     }
-      if (y1 < y0) {
-	y1 = y0;
-      }
-    }
+void Splash::drawImageRowClipNoAlphaAA(SplashDrawImageRowData *data,
+				       Guchar *colorData,
+				       Guchar *alphaData,
+				       int x, int y, int width) {
+  if (y < 0 || y >= bitmap->height) {
+    return;
   }
-
-  // draw the unclipped region
-  if (x0 < w && y0 < h && x0 < x1 && y0 < y1) {
-    pipeInit(&pipe, NULL,
-	     (Guchar)splashRound(state->fillAlpha * 255),
-	     srcAlpha, gFalse);
-    if (srcAlpha) {
-      for (y = y0; y < y1; ++y) {
-	(this->*pipe.run)(&pipe, xDest + x0, xDest + x1 - 1, yDest + y,
-			  src->alpha + y * src->alphaRowSize + x0,
-			  src->data + y * src->rowSize + x0 * bitmapComps);
-      }
-    } else {
-      for (y = y0; y < y1; ++y) {
-	(this->*pipe.run)(&pipe, xDest + x0, xDest + x1 - 1, yDest + y,
-			  NULL,
-			  src->data + y * src->getRowSize() +
-			    x0 * bitmapComps);
-      }
-    }
+  if (x < 0) {
+    colorData -= x * data->nComps;
+    width += x;
+    x = 0;
   }
+  if (x + width > bitmap->width) {
+    width = bitmap->width - x;
+  }
+  if (width <= 0) {
+    return;
+  }
+  memset(scanBuf + x, 0xff, width);
+  state->clip->clipSpan(scanBuf, y, x, x + width - 1, state->strokeAdjust);
+  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
+			  scanBuf + x, colorData);
+}
 
-  // draw the clipped regions
-  if (y0 > 0) {
-    blitImageClipped(src, srcAlpha, 0, 0, xDest, yDest, w, y0);
+void Splash::drawImageRowClipAlphaNoAA(SplashDrawImageRowData *data,
+				       Guchar *colorData,
+				       Guchar *alphaData,
+				       int x, int y, int width) {
+  if (y < 0 || y >= bitmap->height) {
+    return;
   }
-  if (y1 < h) {
-    blitImageClipped(src, srcAlpha, 0, y1, xDest, yDest + y1, w, h - y1);
+  if (x < 0) {
+    colorData -= x * data->nComps;
+    alphaData -= x;
+    width += x;
+    x = 0;
   }
-  if (x0 > 0 && y0 < y1) {
-    blitImageClipped(src, srcAlpha, 0, y0, xDest, yDest + y0, x0, y1 - y0);
+  if (x + width > bitmap->width) {
+    width = bitmap->width - x;
   }
-  if (x1 < w && y0 < y1) {
-    blitImageClipped(src, srcAlpha, x1, y0, xDest + x1, yDest + y0,
-		     w - x1, y1 - y0);
+  if (width <= 0) {
+    return;
   }
+  memcpy(scanBuf + x, alphaData, width);
+  state->clip->clipSpanBinary(scanBuf, y, x, x + width - 1,
+			      state->strokeAdjust);
+  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
+			  scanBuf + x, colorData);
 }
 
-void Splash::blitImageClipped(SplashBitmap *src, GBool srcAlpha,
-			      int xSrc, int ySrc, int xDest, int yDest,
-			      int w, int h) {
-  SplashPipe pipe;
-  int y;
-
-  if (xDest < 0) {
-    xSrc -= xDest;
-    w += xDest;
-    xDest = 0;
+void Splash::drawImageRowClipAlphaAA(SplashDrawImageRowData *data,
+				     Guchar *colorData,
+				     Guchar *alphaData,
+				     int x, int y, int width) {
+  if (y < 0 || y >= bitmap->height) {
+    return;
   }
-  if (xDest + w > bitmap->width) {
-    w = bitmap->width - xDest;
+  if (x < 0) {
+    colorData -= x * data->nComps;
+    alphaData -= x;
+    width += x;
+    x = 0;
   }
-  if (yDest < 0) {
-    ySrc -= yDest;
-    h += yDest;
-    yDest = 0;
+  if (x + width > bitmap->width) {
+    width = bitmap->width - x;
   }
-  if (yDest + h > bitmap->height) {
-    h = bitmap->height - yDest;
-  }
-  if (w <= 0 || h <= 0) {
+  if (width <= 0) {
     return;
   }
-
-  pipeInit(&pipe, NULL,
-	   (Guchar)splashRound(state->fillAlpha * 255),
-	   gTrue, gFalse);
-  if (srcAlpha) {
-    for (y = 0; y < h; ++y) {
-      memcpy(scanBuf + xDest,
-	     src->alpha + (ySrc + y) * src->alphaRowSize + xSrc,
-	     w);
-      if (vectorAntialias) {
-	state->clip->clipSpan(scanBuf, yDest + y, xDest, xDest + w - 1,
-			      state->strokeAdjust);
-      } else {
-	state->clip->clipSpanBinary(scanBuf, yDest + y, xDest, xDest + w - 1,
-				    state->strokeAdjust);
-      }
-      (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
-			scanBuf + xDest,
-			src->data + (ySrc + y) * src->rowSize +
-			  xSrc * bitmapComps);
-    }
-  } else {
-    for (y = 0; y < h; ++y) {
-      memset(scanBuf + xDest, 0xff, w);
-      if (vectorAntialias) {
-	state->clip->clipSpan(scanBuf, yDest + y, xDest, xDest + w - 1,
-			      state->strokeAdjust);
-      } else {
-	state->clip->clipSpanBinary(scanBuf, yDest + y, xDest, xDest + w - 1,
-				    state->strokeAdjust);
-      }
-      (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
-			scanBuf + xDest,
-			src->data + (ySrc + y) * src->rowSize +
-			  xSrc * bitmapComps);
-    }
-  }
+  memcpy(scanBuf + x, alphaData, width);
+  state->clip->clipSpan(scanBuf, y, x, x + width - 1, state->strokeAdjust);
+  (this->*data->pipe.run)(&data->pipe, x, x + width - 1, y,
+			  scanBuf + x, colorData);
 }
 
 SplashError Splash::composite(SplashBitmap *src, int xSrc, int ySrc,
@@ -6950,6 +7266,101 @@
   return splashOk;
 }
 
+SplashError Splash::compositeWithOverprint(SplashBitmap *src,
+					   Guint *srcOverprintMaskBitmap,
+					   int xSrc, int ySrc,
+					   int xDest, int yDest, int w, int h,
+					   GBool noClip, GBool nonIsolated) {
+  SplashPipe pipe;
+  int x0, x1, y0, y1, y, t;
+
+  if (!(src->mode == bitmap->mode ||
+	(src->mode == splashModeMono8 && bitmap->mode == splashModeMono1) ||
+	(src->mode == splashModeRGB8 && bitmap->mode == splashModeBGR8))) {
+    return splashErrModeMismatch;
+  }
+
+  pipeInit(&pipe, NULL,
+	   (Guchar)splashRound(state->fillAlpha * 255),
+	   !noClip || src->alpha != NULL, nonIsolated, gTrue);
+
+  if (noClip) {
+    if (src->alpha) {
+      for (y = 0; y < h; ++y) {
+	pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap + y * w + xSrc;
+	// this uses shape instead of alpha, which isn't technically
+	// correct, but works out the same
+	(this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
+			  src->alpha +
+			    (ySrc + y) * src->alphaRowSize + xSrc,
+			  src->data + (ySrc + y) * src->rowSize +
+			    xSrc * bitmapComps);
+      }
+    } else {
+      for (y = 0; y < h; ++y) {
+	pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap + y * w + xSrc;
+	(this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y,
+			  NULL,
+			  src->data + (ySrc + y) * src->rowSize +
+			    xSrc * bitmapComps);
+      }
+    }
+  } else {
+    x0 = xDest;
+    if ((t = state->clip->getXMinI(state->strokeAdjust)) > x0) {
+      x0 = t;
+    }
+    x1 = xDest + w;
+    if ((t = state->clip->getXMaxI(state->strokeAdjust) + 1) < x1) {
+      x1 = t;
+    }
+    y0 = yDest;
+    if ((t = state->clip->getYMinI(state->strokeAdjust)) > y0) {
+      y0 = t;
+    }
+    y1 = yDest + h;
+    if ((t = state->clip->getYMaxI(state->strokeAdjust) + 1) < y1) {
+      y1 = t;
+    }
+    if (x0 < x1 && y0 < y1) {
+      if (src->alpha) {
+	for (y = y0; y < y1; ++y) {
+	  memcpy(scanBuf + x0,
+		 src->alpha + (ySrc + y - yDest) * src->alphaRowSize + 
+		   (xSrc + x0 - xDest),
+		 x1 - x0);
+	  state->clip->clipSpan(scanBuf, y, x0, x1 - 1, state->strokeAdjust);
+	  pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap
+	                             + (ySrc + y - yDest) * w
+	                             + (xSrc + x0 - xDest);
+	  // this uses shape instead of alpha, which isn't technically
+	  // correct, but works out the same
+	  (this->*pipe.run)(&pipe, x0, x1 - 1, y,
+			    scanBuf + x0,
+			    src->data +
+			      (ySrc + y - yDest) * src->rowSize +
+			      (xSrc + x0 - xDest) * bitmapComps);
+	}
+      } else {
+	for (y = y0; y < y1; ++y) {
+	  memset(scanBuf + x0, 0xff, x1 - x0);
+	  state->clip->clipSpan(scanBuf, y, x0, x1 - 1, state->strokeAdjust);
+	  pipe.srcOverprintMaskPtr = srcOverprintMaskBitmap
+	                             + (ySrc + y - yDest) * w
+	                             + (xSrc + x0 - xDest);
+	  (this->*pipe.run)(&pipe, x0, x1 - 1, y,
+			    scanBuf + x0,
+			    src->data +
+			      (ySrc + y - yDest) * src->rowSize +
+			      (xSrc + x0 - xDest) * bitmapComps);
+	}
+      }
+    }
+  }
+
+  return splashOk;
+}
+
 void Splash::compositeBackground(SplashColorPtr color) {
   SplashColorPtr p;
   Guchar *q;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/splash/Splash.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/splash/Splash.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/splash/Splash.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -29,6 +29,17 @@
 class SplashXPath;
 class SplashFont;
 struct SplashPipe;
+struct SplashDrawImageMaskRowData;
+class ImageScaler;
+typedef void (Splash::*SplashDrawImageMaskRowFunc)(
+				      SplashDrawImageMaskRowData *data,
+				      Guchar *maskData,
+				      int x, int y, int width);
+struct SplashDrawImageRowData;
+typedef void (Splash::*SplashDrawImageRowFunc)(SplashDrawImageRowData *data,
+					       Guchar *colorData,
+					       Guchar *alphaData,
+					       int x, int y, int width);
 
 //------------------------------------------------------------------------
 
@@ -66,6 +77,15 @@
 };
 
 //------------------------------------------------------------------------
+
+// Transparency group destination bitmap initialization control.
+enum SplashGroupDestInitMode {
+  splashGroupDestPreInit,	// dest is already initialized
+  splashGroupDestInitZero,	// initialize to zero (isolated group)
+  splashGroupDestInitCopy	// copy backdrop (non-isolated group)
+};
+
+//------------------------------------------------------------------------
 // SplashImageCache
 //------------------------------------------------------------------------
 
@@ -76,19 +96,23 @@
 
   SplashImageCache();
   ~SplashImageCache();
+  GBool match(GString *aTag, int aWidth, int aHeight,
+	      SplashColorMode aMode, GBool aAlpha,
+	      GBool aInterpolate);
+  void reset(GString *aTag, int aWidth, int aHeight,
+	     SplashColorMode aMode, GBool aAlpha,
+	     GBool aInterpolate);
   void incRefCount();
   void decRefCount();
 
   GString *tag;
-  GBool isMask;
   int width;
   int height;
   SplashColorMode mode;
   GBool alpha;
   GBool interpolate;
-  GBool vertFlip;
-  GBool horizFlip;
-  SplashBitmap *image;
+  Guchar *colorData;
+  Guchar *alphaData;
 
   int refCount;
 };
@@ -161,7 +185,9 @@
   void setSoftMask(SplashBitmap *softMask);
   void setInTransparencyGroup(SplashBitmap *groupBackBitmapA,
 			      int groupBackXA, int groupBackYA,
+			      SplashGroupDestInitMode groupDestInitModeA,
 			      GBool nonIsolated, GBool knockout);
+  void forceDeferredInit(int y, int h);
   void setTransfer(Guchar *red, Guchar *green, Guchar *blue, Guchar *gray);
   void setOverprintMask(Guint overprintMask);
   void setEnablePathSimplification(GBool en);
@@ -234,6 +260,15 @@
 			int xDest, int yDest, int w, int h,
 			GBool noClip, GBool nonIsolated);
 
+  // Composite a rectangular region from <src> onto this Splash
+  // object, using <srcOverprintMaskBitmap> as the overprint mask per
+  // pixel.  This is only supported for CMYK and DeviceN bitmaps.
+  SplashError compositeWithOverprint(SplashBitmap *src,
+				     Guint *srcOverprintMaskBitmap,
+				     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);
@@ -269,6 +304,10 @@
   // Return the associated bitmap.
   SplashBitmap *getBitmap() { return bitmap; }
 
+  // Enable writing the per-pixel overprint mask to a separate bitmap.
+  void setOverprintMaskBitmap(Guint *overprintMaskBitmapA)
+    { overprintMaskBitmap = overprintMaskBitmapA; }
+
   // Set the minimum line width.
   void setMinLineWidth(SplashCoord w) { minLineWidth = w; }
 
@@ -298,7 +337,7 @@
 
   void pipeInit(SplashPipe *pipe, SplashPattern *pattern,
 		Guchar aInput, GBool usesShape,
-		GBool nonIsolatedGroup);
+		GBool nonIsolatedGroup, GBool usesSrcOverprint = gFalse);
   void pipeRun(SplashPipe *pipe, int x0, int x1, int y,
 	       Guchar *shapePtr, SplashColorPtr cSrcPtr);
   void pipeRunSimpleMono1(SplashPipe *pipe, int x0, int x1, int y,
@@ -325,6 +364,8 @@
   void pipeRunShapeCMYK8(SplashPipe *pipe, int x0, int x1, int y,
 			 Guchar *shapePtr, SplashColorPtr cSrcPtr);
 #endif
+  void pipeRunShapeNoAlphaMono8(SplashPipe *pipe, int x0, int x1, int y,
+                                Guchar *shapePtr, SplashColorPtr cSrcPtr);
   void pipeRunAAMono1(SplashPipe *pipe, int x0, int x1, int y,
 		      Guchar *shapePtr, SplashColorPtr cSrcPtr);
   void pipeRunAAMono8(SplashPipe *pipe, int x0, int x1, int y,
@@ -337,6 +378,16 @@
   void pipeRunAACMYK8(SplashPipe *pipe, int x0, int x1, int y,
 		      Guchar *shapePtr, SplashColorPtr cSrcPtr);
 #endif
+  void pipeRunSoftMaskMono8(SplashPipe *pipe, int x0, int x1, int y,
+			    Guchar *shapePtr, SplashColorPtr cSrcPtr);
+  void pipeRunSoftMaskRGB8(SplashPipe *pipe, int x0, int x1, int y,
+                           Guchar *shapePtr, SplashColorPtr cSrcPtr);
+  void pipeRunSoftMaskBGR8(SplashPipe *pipe, int x0, int x1, int y,
+                           Guchar *shapePtr, SplashColorPtr cSrcPtr);
+#if SPLASH_CMYK
+  void pipeRunSoftMaskCMYK8(SplashPipe *pipe, int x0, int x1, int y,
+			    Guchar *shapePtr, SplashColorPtr cSrcPtr);
+#endif
   void pipeRunNonIsoMono8(SplashPipe *pipe, int x0, int x1, int y,
 			  Guchar *shapePtr, SplashColorPtr cSrcPtr);
   void pipeRunNonIsoRGB8(SplashPipe *pipe, int x0, int x1, int y,
@@ -347,6 +398,8 @@
   void pipeRunNonIsoCMYK8(SplashPipe *pipe, int x0, int x1, int y,
 			  Guchar *shapePtr, SplashColorPtr cSrcPtr);
 #endif
+  void useDestRow(int y);
+  void copyGroupBackdropRow(int y);
   void transform(SplashCoord *matrix, SplashCoord xi, SplashCoord yi,
 		 SplashCoord *xo, SplashCoord *yo);
   void updateModX(int x);
@@ -371,92 +424,81 @@
   SplashError fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph);
   void getImageBounds(SplashCoord xyMin, SplashCoord xyMax,
 		      int *xyMinI, int *xyMaxI);
-  void upscaleMask(SplashImageMaskSource src, void *srcData,
-		   int srcWidth, int srcHeight,
-		   SplashCoord *mat, GBool glyphMode,
-		   GBool interpolate);
-  void arbitraryTransformMask(GString *imageTag,
-			      SplashImageMaskSource src, void *srcData,
-			      int srcWidth, int srcHeight,
-			      SplashCoord *mat, GBool glyphMode,
-			      GBool interpolate);
-  SplashBitmap *scaleMask(GString *imageTag,
-			  SplashImageMaskSource src, void *srcData,
-			  int srcWidth, int srcHeight,
-			  int scaledWidth, int scaledHeight,
-			  GBool interpolate);
-  void scaleMaskYdXd(SplashImageMaskSource src, void *srcData,
-		     int srcWidth, int srcHeight,
-		     int scaledWidth, int scaledHeight,
-		     SplashBitmap *dest);
-  void scaleMaskYdXu(SplashImageMaskSource src, void *srcData,
-		     int srcWidth, int srcHeight,
-		     int scaledWidth, int scaledHeight,
-		     SplashBitmap *dest);
-  void scaleMaskYuXd(SplashImageMaskSource src, void *srcData,
-		     int srcWidth, int srcHeight,
-		     int scaledWidth, int scaledHeight,
-		     SplashBitmap *dest);
-  void scaleMaskYuXu(SplashImageMaskSource src, void *srcData,
-		     int srcWidth, int srcHeight,
-		     int scaledWidth, int scaledHeight,
-		     SplashBitmap *dest);
-  void scaleMaskYuXuI(SplashImageMaskSource src, void *srcData,
-		      int srcWidth, int srcHeight,
+  void drawImageMaskArbitraryNoInterp(Guchar *scaledMask,
+				      SplashDrawImageMaskRowData *dd,
+				      SplashDrawImageMaskRowFunc drawRowFunc,
+				      SplashCoord *invMat,
+				      int scaledWidth, int scaledHeight,
+				      int xMin, int yMin, int xMax, int yMax);
+  void drawImageMaskArbitraryInterp(Guchar *scaledMask,
+				    SplashDrawImageMaskRowData *dd,
+				    SplashDrawImageMaskRowFunc drawRowFunc,
+				    SplashCoord *invMat,
+				    int scaledWidth, int scaledHeight,
+				    int xMin, int yMin, int xMax, int yMax);
+  void mirrorImageMaskRow(Guchar *maskIn, Guchar *maskOut, int width);
+  void drawImageMaskRowNoClip(SplashDrawImageMaskRowData *data,
+			      Guchar *maskData,
+			      int x, int y, int width);
+  void drawImageMaskRowClipNoAA(SplashDrawImageMaskRowData *data,
+				Guchar *maskData,
+				int x, int y, int width);
+  void drawImageMaskRowClipAA(SplashDrawImageMaskRowData *data,
+			      Guchar *maskData,
+			      int x, int y, int width);
+  ImageScaler *getImageScaler(GString *imageTag,
+			      SplashImageSource src, void *srcData,
+			      int w, int h, int nComps,
+			      int scaledWidth, int scaledHeight,
+			      SplashColorMode srcMode,
+			      GBool srcAlpha, GBool interpolate);
+  void getScaledImage(GString *imageTag,
+		      SplashImageSource src, void *srcData,
+		      int w, int h, int nComps,
 		      int scaledWidth, int scaledHeight,
-		      SplashBitmap *dest);
-  void blitMask(SplashBitmap *src, int xDest, int yDest,
-		SplashClipResult clipRes);
-  void upscaleImage(SplashImageSource src, void *srcData,
-		    SplashColorMode srcMode, int nComps,
-		    GBool srcAlpha, int srcWidth, int srcHeight,
-		    SplashCoord *mat, GBool interpolate);
-  void arbitraryTransformImage(GString *imageTag,
-			       SplashImageSource src, void *srcData,
-			       SplashColorMode srcMode, int nComps,
-			       GBool srcAlpha,
-			       int srcWidth, int srcHeight,
-			       SplashCoord *mat, GBool interpolate);
-  SplashBitmap *scaleImage(GString *imageTag,
-			   SplashImageSource src, void *srcData,
-			   SplashColorMode srcMode, int nComps,
-			   GBool srcAlpha, int srcWidth, int srcHeight,
-			   int scaledWidth, int scaledHeight,
-			   GBool interpolate);
-  void scaleImageYdXd(SplashImageSource src, void *srcData,
-		      SplashColorMode srcMode, int nComps,
-		      GBool srcAlpha, int srcWidth, int srcHeight,
-		      int scaledWidth, int scaledHeight,
-		      SplashBitmap *dest);
-  void scaleImageYdXu(SplashImageSource src, void *srcData,
-		      SplashColorMode srcMode, int nComps,
-		      GBool srcAlpha, int srcWidth, int srcHeight,
-		      int scaledWidth, int scaledHeight,
-		      SplashBitmap *dest);
-  void scaleImageYuXd(SplashImageSource src, void *srcData,
-		      SplashColorMode srcMode, int nComps,
-		      GBool srcAlpha, int srcWidth, int srcHeight,
-		      int scaledWidth, int scaledHeight,
-		      SplashBitmap *dest);
-  void scaleImageYuXu(SplashImageSource src, void *srcData,
-		      SplashColorMode srcMode, int nComps,
-		      GBool srcAlpha, int srcWidth, int srcHeight,
-		      int scaledWidth, int scaledHeight,
-		      SplashBitmap *dest);
-  void scaleImageYuXuI(SplashImageSource src, void *srcData,
-		       SplashColorMode srcMode, int nComps,
-		       GBool srcAlpha, int srcWidth, int srcHeight,
-		       int scaledWidth, int scaledHeight,
-		       SplashBitmap *dest);
-  void vertFlipImage(SplashBitmap *img, int width, int height,
-		     int nComps);
-  void horizFlipImage(SplashBitmap *img, int width, int height,
-		      int nComps);
-  void blitImage(SplashBitmap *src, GBool srcAlpha, int xDest, int yDest,
-		 SplashClipResult clipRes);
-  void blitImageClipped(SplashBitmap *src, GBool srcAlpha,
-			int xSrc, int ySrc, int xDest, int yDest,
-			int w, int h);
+		      SplashColorMode srcMode,
+		      GBool srcAlpha, GBool interpolate,
+		      Guchar **scaledColor, Guchar **scaledAlpha,
+		      GBool *freeScaledImage);
+  void drawImageArbitraryNoInterp(Guchar *scaledColor, Guchar *scaledAlpha,
+				  SplashDrawImageRowData *dd,
+				  SplashDrawImageRowFunc drawRowFunc,
+				  SplashCoord *invMat,
+				  int scaledWidth, int scaledHeight,
+				  int xMin, int yMin, int xMax, int yMax,
+				  int nComps, GBool srcAlpha);
+  void drawImageArbitraryInterp(Guchar *scaledColor, Guchar *scaledAlpha,
+				SplashDrawImageRowData *dd,
+				SplashDrawImageRowFunc drawRowFunc,
+				SplashCoord *invMat,
+				int scaledWidth, int scaledHeight,
+				int xMin, int yMin, int xMax, int yMax,
+				int nComps, GBool srcAlpha);
+  void mirrorImageRow(Guchar *colorIn, Guchar *alphaIn,
+		      Guchar *colorOut, Guchar *alphaOut,
+		      int width, int nComps, GBool srcAlpha);
+  void drawImageRowNoClipNoAlpha(SplashDrawImageRowData *data,
+				 Guchar *colorData, Guchar *alphaData,
+				 int x, int y, int width);
+  void drawImageRowNoClipAlpha(SplashDrawImageRowData *data,
+			       Guchar *colorData, Guchar *alphaData,
+			       int x, int y, int width);
+  void drawImageRowClipNoAlphaNoAA(SplashDrawImageRowData *data,
+				   Guchar *colorData,
+				   Guchar *alphaData,
+				   int x, int y, int width);
+  void drawImageRowClipNoAlphaAA(SplashDrawImageRowData *data,
+				 Guchar *colorData,
+				 Guchar *alphaData,
+				 int x, int y, int width);
+  void drawImageRowClipAlphaNoAA(SplashDrawImageRowData *data,
+				 Guchar *colorData,
+				 Guchar *alphaData,
+				 int x, int y, int width);
+  void drawImageRowClipAlphaAA(SplashDrawImageRowData *data,
+			       Guchar *colorData,
+			       Guchar *alphaData,
+			       int x, int y, int width);
   void dumpPath(SplashPath *path);
   void dumpXPath(SplashXPath *path);
 
@@ -474,6 +516,9 @@
   SplashBitmap			// for transparency groups, this is the bitmap
     *groupBackBitmap;		//   containing the alpha0/color0 values
   int groupBackX, groupBackY;	// offset within groupBackBitmap
+  SplashGroupDestInitMode groupDestInitMode;
+  int groupDestInitYMin, groupDestInitYMax;
+  Guint *overprintMaskBitmap;
   SplashCoord minLineWidth;
   int modXMin, modYMin, modXMax, modYMax;
   SplashClipResult opClipRes;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -16,6 +16,8 @@
 #include <limits.h>
 #include "gmem.h"
 #include "gmempp.h"
+#include "gfile.h"
+#include "Trace.h"
 #include "SplashErrorCodes.h"
 #include "SplashBitmap.h"
 
@@ -64,6 +66,11 @@
   rowSize += rowPad - 1;
   rowSize -= rowSize % rowPad;
 
+  traceAlloc(this, "alloc bitmap: %d x %d x %d %s -> %lld bytes",
+	     width, height, splashColorModeNComps[mode],
+	     alphaA ? "with alpha" : "without alpha",
+	     height * rowSize + (alphaA ? height * width : 0));
+
   parent = parentA;
   oldData = NULL;
   oldAlpha = NULL;
@@ -75,8 +82,14 @@
       parent->oldHeight == height) {
     data = parent->oldData;
     parent->oldData = NULL;
+    traceMessage("reusing bitmap memory");
   } else {
     data = (SplashColorPtr)gmallocn64(height, rowSize);
+    traceMessage("not reusing bitmap memory"
+		 " (parent=%p parent->oldData=%p same-size=%d)",
+		 parent, parent ? parent->oldData : NULL,
+		 parent ? (parent->oldRowSize == rowSize &&
+			   parent->oldHeight == height) : 0);
   }
   if (!topDown) {
     data += (height - 1) * rowSize;
@@ -99,11 +112,12 @@
 }
 
 SplashBitmap::~SplashBitmap() {
+  traceFree(this, "free bitmap");
   if (data && rowSize < 0) {
     rowSize = -rowSize;
     data -= (height - 1) * rowSize;
   }
-  if (parent && rowSize > 10000000 / height) {
+  if (parent && rowSize > 4000000 / height) {
     gfree(parent->oldData);
     gfree(parent->oldAlpha);
     parent->oldData = data;
@@ -123,7 +137,7 @@
   FILE *f;
   SplashError err;
 
-  if (!(f = fopen(fileName, "wb"))) {
+  if (!(f = openFile(fileName, "wb"))) {
     return splashErrOpenFile;
   }
   err = writePNMFile(f);
@@ -211,7 +225,7 @@
   if (!alpha) {
     return splashErrModeMismatch;
   }
-  if (!(f = fopen(fileName, "wb"))) {
+  if (!(f = openFile(fileName, "wb"))) {
     return splashErrOpenFile;
   }
   fprintf(f, "P5\n%d %d\n255\n", width, height);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashBitmap.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -17,6 +17,10 @@
 
 #include <stdio.h>
 #include <limits.h>
+// older compilers won't define SIZE_MAX in stdint.h without this
+#ifndef __STDC_LIMIT_MACROS
+#  define __STDC_LIMIT_MACROS 1
+#endif
 #include <stdint.h>
 #include "SplashTypes.h"
 

Modified: trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFont.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFont.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFont.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -71,7 +71,11 @@
     // if the glyphs are too large, don't cache them -- setting the
     // cache bitmap size to something tiny will cause getGlyph() to
     // fall back to the uncached case
-    glyphW = glyphH = 1;
+    glyphW = glyphH = 0;
+    glyphSize = 0;
+    cacheSets = 0;
+    cacheAssoc = 0;
+    return;
   }
   if (aa) {
     glyphSize = glyphW * glyphH;
@@ -117,29 +121,33 @@
   }
 
   // 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) <
+  if (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+k].mru;
+	  }
 	}
+	cacheTags[i+j].mru = 0x80000000;
+	bitmap->aa = aa;
+	bitmap->data = cache + (i+j) * glyphSize;
+	bitmap->freeData = gFalse;
+	return gTrue;
       }
-      cacheTags[i+j].mru = 0x80000000;
-      bitmap->aa = aa;
-      bitmap->data = cache + (i+j) * glyphSize;
-      bitmap->freeData = gFalse;
-      return gTrue;
     }
+  } else {
+    i = 0; // make gcc happy
   }
 
   // generate the glyph bitmap
@@ -149,7 +157,7 @@
 
   // if the glyph doesn't fit in the bounding box, return a temporary
   // uncached bitmap
-  if (bitmap2.w > glyphW || bitmap2.h > glyphH) {
+  if (!cache || bitmap2.w > glyphW || bitmap2.h > glyphH) {
     *bitmap = bitmap2;
     return gTrue;
   }

Modified: trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -20,6 +20,7 @@
 #include "gmem.h"
 #include "gmempp.h"
 #include "GString.h"
+#include "GList.h"
 #include "SplashMath.h"
 #include "SplashFTFontEngine.h"
 #include "SplashFontFile.h"
@@ -48,6 +49,7 @@
   for (i = 0; i < splashFontCacheSize; ++i) {
     fontCache[i] = NULL;
   }
+  badFontFiles = new GList();
 
 #if HAVE_FREETYPE_H
   if (enableFreeType) {
@@ -66,6 +68,7 @@
       delete fontCache[i];
     }
   }
+  deleteGList(badFontFiles, SplashFontFileID);
 
 #if HAVE_FREETYPE_H
   if (ftEngine) {
@@ -89,6 +92,15 @@
   return NULL;
 }
 
+GBool SplashFontEngine::checkForBadFontFile(SplashFontFileID *id) {
+  for (int i = 0; i < badFontFiles->getLength(); ++i) {
+    if (((SplashFontFileID *)badFontFiles->get(i))->matches(id)) {
+      return gTrue;
+    }
+  }
+  return gFalse;
+}
+
 SplashFontFile *SplashFontEngine::loadType1Font(SplashFontFileID *idA,
 #if LOAD_FONTS_FROM_MEM
 						GString *fontBuf,
@@ -122,6 +134,10 @@
   }
 #endif
 
+  if (!fontFile) {
+    badFontFiles->append(idA);
+  }
+
   return fontFile;
 }
 
@@ -162,6 +178,10 @@
   }
 #endif
 
+  if (!fontFile) {
+    badFontFiles->append(idA);
+  }
+
   return fontFile;
 }
 
@@ -202,6 +222,10 @@
   }
 #endif
 
+  if (!fontFile) {
+    badFontFiles->append(idA);
+  }
+
   return fontFile;
 }
 
@@ -243,6 +267,10 @@
   }
 #endif
 
+  if (!fontFile) {
+    badFontFiles->append(idA);
+  }
+
   return fontFile;
 }
 
@@ -284,6 +312,10 @@
   }
 #endif
 
+  if (!fontFile) {
+    badFontFiles->append(idA);
+  }
+
   return fontFile;
 }
 
@@ -327,6 +359,10 @@
   }
 #endif
 
+  if (!fontFile) {
+    badFontFiles->append(idA);
+  }
+
   return fontFile;
 }
 

Modified: trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashFontEngine.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -17,6 +17,7 @@
 
 #include "gtypes.h"
 class GString;
+class GList;
 
 class SplashFTFontEngine;
 class SplashDTFontEngine;
@@ -54,6 +55,10 @@
   // matching entry in the cache.
   SplashFontFile *getFontFile(SplashFontFileID *id);
 
+  // Returns true if [id] refers to a bad font file, i.e., if one of
+  // the loadXXXFont functions has returned NULL for that ID.
+  GBool checkForBadFontFile(SplashFontFileID *id);
+
   // Load fonts - these create new SplashFontFile objects.
   SplashFontFile *loadType1Font(SplashFontFileID *idA,
 #if LOAD_FONTS_FROM_MEM
@@ -114,6 +119,7 @@
 private:
 
   SplashFont *fontCache[splashFontCacheSize];
+  GList *badFontFiles;		// [SplashFontFileID]
 
 #if HAVE_FREETYPE_H
   SplashFTFontEngine *ftEngine;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashXPath.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashXPath.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/splash/SplashXPath.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -593,7 +593,8 @@
       seg->dydx = 0;
     }
 #else
-    if (seg->y0 == seg->y1 || seg->x0 == seg->x1) {
+    if (splashAbs(seg->y1 - seg->y0) < 1e-200 ||
+	splashAbs(seg->x1 - seg->x0) < 1e-200) {
       seg->dxdy = 0;
       seg->dydx = 0;
     } else {

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -27,6 +27,9 @@
 #include "OptionalContent.h"
 #include "Annot.h"
 #include "Lexer.h"
+#include "XFAScanner.h"
+#include "UTF8.h"
+#include "PDF417Barcode.h"
 #include "AcroForm.h"
 
 //------------------------------------------------------------------------
@@ -55,6 +58,11 @@
 #define acroFormQuadCenter 1
 #define acroFormQuadRight  2
 
+#define acroFormVAlignTop               0
+#define acroFormVAlignMiddle            1
+#define acroFormVAlignMiddleNoDescender 2
+#define acroFormVAlignBottom            3
+
 #define annotFlagHidden    0x0002
 #define annotFlagPrint     0x0004
 #define annotFlagNoView    0x0020
@@ -63,8 +71,258 @@
 // = (4 * (sqrt(2) - 1) / 3) * r
 #define bezierCircle 0.55228475
 
+// limit recursive field-parent lookups to avoid infinite loops
+#define maxFieldObjectDepth 50
+
 //------------------------------------------------------------------------
 
+// 5 bars + 5 spaces -- each can be wide (1) or narrow (0)
+// (there are always exactly 3 wide elements;
+// the last space is always narrow)
+static Guchar code3Of9Data[128][10] = {
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x00
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x10
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 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, 1, 1, 0, 0, 0, 1, 0, 0, 0 }, // ' ' = 0x20
+  { 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, 1, 0, 1, 0, 1, 0, 0, 0, 0 }, // '$' = 0x24
+  { 0, 0, 0, 1, 0, 1, 0, 1, 0, 0 }, // '%' = 0x25
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 1, 0, 0, 1, 0, 1, 0, 0, 0 }, // '*' = 0x2a
+  { 0, 1, 0, 0, 0, 1, 0, 1, 0, 0 }, // '+' = 0x2b
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0 }, // '-' = 0x2d
+  { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0 }, // '.' = 0x2e
+  { 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 }, // '/' = 0x2f
+  { 0, 0, 0, 1, 1, 0, 1, 0, 0, 0 }, // '0' = 0x30
+  { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0 }, // '1'
+  { 0, 0, 1, 1, 0, 0, 0, 0, 1, 0 }, // '2'
+  { 1, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, // '3'
+  { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0 }, // '4'
+  { 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // '5'
+  { 0, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, // '6'
+  { 0, 0, 0, 1, 0, 0, 1, 0, 1, 0 }, // '7'
+  { 1, 0, 0, 1, 0, 0, 1, 0, 0, 0 }, // '8'
+  { 0, 0, 1, 1, 0, 0, 1, 0, 0, 0 }, // '9'
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x40
+  { 1, 0, 0, 0, 0, 1, 0, 0, 1, 0 }, // 'A' = 0x41
+  { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 }, // 'B'
+  { 1, 0, 1, 0, 0, 1, 0, 0, 0, 0 }, // 'C'
+  { 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 }, // 'D'
+  { 1, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // 'E'
+  { 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // 'F'
+  { 0, 0, 0, 0, 0, 1, 1, 0, 1, 0 }, // 'G'
+  { 1, 0, 0, 0, 0, 1, 1, 0, 0, 0 }, // 'H'
+  { 0, 0, 1, 0, 0, 1, 1, 0, 0, 0 }, // 'I'
+  { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, // 'J'
+  { 1, 0, 0, 0, 0, 0, 0, 1, 1, 0 }, // 'K'
+  { 0, 0, 1, 0, 0, 0, 0, 1, 1, 0 }, // 'L'
+  { 1, 0, 1, 0, 0, 0, 0, 1, 0, 0 }, // 'M'
+  { 0, 0, 0, 0, 1, 0, 0, 1, 1, 0 }, // 'N'
+  { 1, 0, 0, 0, 1, 0, 0, 1, 0, 0 }, // 'O'
+  { 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 }, // 'P' = 0x50
+  { 0, 0, 0, 0, 0, 0, 1, 1, 1, 0 }, // 'Q'
+  { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0 }, // 'R'
+  { 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, // 'S'
+  { 0, 0, 0, 0, 1, 0, 1, 1, 0, 0 }, // 'T'
+  { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0 }, // 'U'
+  { 0, 1, 1, 0, 0, 0, 0, 0, 1, 0 }, // 'V'
+  { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, // 'W'
+  { 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 }, // 'X'
+  { 1, 1, 0, 0, 1, 0, 0, 0, 0, 0 }, // 'Y'
+  { 0, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, // 'Z'
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x60
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x70
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
+};
+
+// 3 bars + 3 spaces -- each can be 1, 2, 3, or 4 units wide
+static Guchar code128Data[107][6] = {
+  { 2, 1, 2, 2, 2, 2 },
+  { 2, 2, 2, 1, 2, 2 },
+  { 2, 2, 2, 2, 2, 1 },
+  { 1, 2, 1, 2, 2, 3 },
+  { 1, 2, 1, 3, 2, 2 },
+  { 1, 3, 1, 2, 2, 2 },
+  { 1, 2, 2, 2, 1, 3 },
+  { 1, 2, 2, 3, 1, 2 },
+  { 1, 3, 2, 2, 1, 2 },
+  { 2, 2, 1, 2, 1, 3 },
+  { 2, 2, 1, 3, 1, 2 },
+  { 2, 3, 1, 2, 1, 2 },
+  { 1, 1, 2, 2, 3, 2 },
+  { 1, 2, 2, 1, 3, 2 },
+  { 1, 2, 2, 2, 3, 1 },
+  { 1, 1, 3, 2, 2, 2 },
+  { 1, 2, 3, 1, 2, 2 },
+  { 1, 2, 3, 2, 2, 1 },
+  { 2, 2, 3, 2, 1, 1 },
+  { 2, 2, 1, 1, 3, 2 },
+  { 2, 2, 1, 2, 3, 1 },
+  { 2, 1, 3, 2, 1, 2 },
+  { 2, 2, 3, 1, 1, 2 },
+  { 3, 1, 2, 1, 3, 1 },
+  { 3, 1, 1, 2, 2, 2 },
+  { 3, 2, 1, 1, 2, 2 },
+  { 3, 2, 1, 2, 2, 1 },
+  { 3, 1, 2, 2, 1, 2 },
+  { 3, 2, 2, 1, 1, 2 },
+  { 3, 2, 2, 2, 1, 1 },
+  { 2, 1, 2, 1, 2, 3 },
+  { 2, 1, 2, 3, 2, 1 },
+  { 2, 3, 2, 1, 2, 1 },
+  { 1, 1, 1, 3, 2, 3 },
+  { 1, 3, 1, 1, 2, 3 },
+  { 1, 3, 1, 3, 2, 1 },
+  { 1, 1, 2, 3, 1, 3 },
+  { 1, 3, 2, 1, 1, 3 },
+  { 1, 3, 2, 3, 1, 1 },
+  { 2, 1, 1, 3, 1, 3 },
+  { 2, 3, 1, 1, 1, 3 },
+  { 2, 3, 1, 3, 1, 1 },
+  { 1, 1, 2, 1, 3, 3 },
+  { 1, 1, 2, 3, 3, 1 },
+  { 1, 3, 2, 1, 3, 1 },
+  { 1, 1, 3, 1, 2, 3 },
+  { 1, 1, 3, 3, 2, 1 },
+  { 1, 3, 3, 1, 2, 1 },
+  { 3, 1, 3, 1, 2, 1 },
+  { 2, 1, 1, 3, 3, 1 },
+  { 2, 3, 1, 1, 3, 1 },
+  { 2, 1, 3, 1, 1, 3 },
+  { 2, 1, 3, 3, 1, 1 },
+  { 2, 1, 3, 1, 3, 1 },
+  { 3, 1, 1, 1, 2, 3 },
+  { 3, 1, 1, 3, 2, 1 },
+  { 3, 3, 1, 1, 2, 1 },
+  { 3, 1, 2, 1, 1, 3 },
+  { 3, 1, 2, 3, 1, 1 },
+  { 3, 3, 2, 1, 1, 1 },
+  { 3, 1, 4, 1, 1, 1 },
+  { 2, 2, 1, 4, 1, 1 },
+  { 4, 3, 1, 1, 1, 1 },
+  { 1, 1, 1, 2, 2, 4 },
+  { 1, 1, 1, 4, 2, 2 },
+  { 1, 2, 1, 1, 2, 4 },
+  { 1, 2, 1, 4, 2, 1 },
+  { 1, 4, 1, 1, 2, 2 },
+  { 1, 4, 1, 2, 2, 1 },
+  { 1, 1, 2, 2, 1, 4 },
+  { 1, 1, 2, 4, 1, 2 },
+  { 1, 2, 2, 1, 1, 4 },
+  { 1, 2, 2, 4, 1, 1 },
+  { 1, 4, 2, 1, 1, 2 },
+  { 1, 4, 2, 2, 1, 1 },
+  { 2, 4, 1, 2, 1, 1 },
+  { 2, 2, 1, 1, 1, 4 },
+  { 4, 1, 3, 1, 1, 1 },
+  { 2, 4, 1, 1, 1, 2 },
+  { 1, 3, 4, 1, 1, 1 },
+  { 1, 1, 1, 2, 4, 2 },
+  { 1, 2, 1, 1, 4, 2 },
+  { 1, 2, 1, 2, 4, 1 },
+  { 1, 1, 4, 2, 1, 2 },
+  { 1, 2, 4, 1, 1, 2 },
+  { 1, 2, 4, 2, 1, 1 },
+  { 4, 1, 1, 2, 1, 2 },
+  { 4, 2, 1, 1, 1, 2 },
+  { 4, 2, 1, 2, 1, 1 },
+  { 2, 1, 2, 1, 4, 1 },
+  { 2, 1, 4, 1, 2, 1 },
+  { 4, 1, 2, 1, 2, 1 },
+  { 1, 1, 1, 1, 4, 3 },
+  { 1, 1, 1, 3, 4, 1 },
+  { 1, 3, 1, 1, 4, 1 },
+  { 1, 1, 4, 1, 1, 3 },
+  { 1, 1, 4, 3, 1, 1 },
+  { 4, 1, 1, 1, 1, 3 },
+  { 4, 1, 1, 3, 1, 1 },
+  { 1, 1, 3, 1, 4, 1 },
+  { 1, 1, 4, 1, 3, 1 },
+  { 3, 1, 1, 1, 4, 1 },
+  { 4, 1, 1, 1, 3, 1 },
+  { 2, 1, 1, 4, 1, 2 }, // start code A
+  { 2, 1, 1, 2, 1, 4 }, // start code B
+  { 2, 1, 1, 2, 3, 2 }, // start code C
+  { 2, 3, 3, 1, 1, 1 }  // stop code (without final bar)
+};
+
+//------------------------------------------------------------------------
+
 // map an annotation ref to a page number
 class AcroFormAnnotPage {
 public:
@@ -83,7 +341,7 @@
   Object acroFormObj2;
   AcroForm *acroForm;
   AcroFormField *field;
-  Object fieldsObj, annotsObj, annotRef, annotObj, obj1, obj2;
+  Object xfaObj, fieldsObj, annotsObj, annotRef, annotObj, obj1, obj2;
   int pageNum, i, j;
 
   // this is the normal case: acroFormObj is a dictionary, as expected
@@ -90,6 +348,14 @@
   if (acroFormObjA->isDict()) {
     acroForm = new AcroForm(docA, acroFormObjA);
 
+    if (!acroFormObjA->dictLookup("XFA", &xfaObj)->isNull()) {
+      acroForm->xfaScanner = XFAScanner::load(&xfaObj);
+      if (!catalog->getNeedsRendering()) {
+	acroForm->isStaticXFA = gTrue;
+      }
+    }
+    xfaObj.free();
+
     if (acroFormObjA->dictLookup("NeedAppearances", &obj1)->isBool()) {
       acroForm->needAppearances = obj1.getBool();
     }
@@ -183,11 +449,14 @@
   return acroForm;
 }
 
-AcroForm::AcroForm(PDFDoc *docA, Object *acroFormObjA): Form(docA) {
+AcroForm::AcroForm(PDFDoc *docA, Object *acroFormObjA) {
+  doc = docA;
   acroFormObjA->copy(&acroFormObj);
   needAppearances = gFalse;
   annotPages = new GList();
   fields = new GList();
+  xfaScanner = NULL;
+  isStaticXFA = gFalse;
 }
 
 AcroForm::~AcroForm() {
@@ -194,8 +463,13 @@
   acroFormObj.free();
   deleteGList(annotPages, AcroFormAnnotPage);
   deleteGList(fields, AcroFormField);
+  delete xfaScanner;
 }
 
+const char *AcroForm::getType() {
+  return isStaticXFA ? "static XFA" : "AcroForm";
+}
+
 void AcroForm::buildAnnotPageList(Catalog *catalog) {
   Object annotsObj, annotObj;
   int pageNum, i;
@@ -296,10 +570,44 @@
   return fields->getLength();
 }
 
-FormField *AcroForm::getField(int idx) {
+AcroFormField *AcroForm::getField(int idx) {
   return (AcroFormField *)fields->get(idx);
 }
 
+AcroFormField *AcroForm::findField(int pg, double x, double y) {
+  AcroFormField *field;
+  double llx, lly, urx, ury;
+  int i;
+
+  for (i = 0; i < fields->getLength(); ++i) {
+    field = (AcroFormField *)fields->get(i);
+    if (field->getPageNum() == pg) {
+      field->getBBox(&llx, &lly, &urx, &ury);
+      if (llx <= x && x <= urx && lly <= y && y <= ury) {
+	return field;
+      }
+    }
+  }
+  return NULL;
+}
+
+int AcroForm::findFieldIdx(int pg, double x, double y) {
+  AcroFormField *field;
+  double llx, lly, urx, ury;
+  int i;
+
+  for (i = 0; i < fields->getLength(); ++i) {
+    field = (AcroFormField *)fields->get(i);
+    if (field->getPageNum() == pg) {
+      field->getBBox(&llx, &lly, &urx, &ury);
+      if (llx <= x && x <= urx && lly <= y && y <= ury) {
+	return i;
+      }
+    }
+  }
+  return -1;
+}
+
 //------------------------------------------------------------------------
 // AcroFormField
 //------------------------------------------------------------------------
@@ -307,11 +615,14 @@
 AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) {
   GString *typeStr;
   TextString *nameA;
+  GString *xfaName;
   Guint flagsA;
   GBool haveFlags, typeFromParentA;
   Object fieldObjA, parentObj, parentObj2, obj1, obj2;
   AcroFormFieldType typeA;
+  XFAField *xfaFieldA;
   AcroFormField *field;
+  int depth, i0, i1;
 
   fieldRefA->fetch(acroFormA->doc->getXRef(), &fieldObjA);
 
@@ -345,7 +656,8 @@
   //----- get info from parent non-terminal fields
 
   fieldObjA.dictLookup("Parent", &parentObj);
-  while (parentObj.isDict()) {
+  depth = 0;
+  while (parentObj.isDict() && depth < maxFieldObjectDepth) {
 
     if (parentObj.dictLookup("T", &obj1)->isString()) {
       if (nameA->getLength()) {
@@ -373,6 +685,8 @@
     parentObj.dictLookup("Parent", &parentObj2);
     parentObj.free();
     parentObj = parentObj2;
+
+    ++depth;
   }
   parentObj.free();
 
@@ -381,6 +695,33 @@
     goto err1;
   }
 
+  //----- get static XFA info
+
+  xfaFieldA = NULL;
+  if (acroFormA->xfaScanner) {
+    // convert field name to UTF-8, and remove segments that start
+    // with '#' -- to match the XFA field name
+    xfaName = nameA->toUTF8();
+    i0 = 0;
+    while (i0 < xfaName->getLength()) {
+      i1 = i0;
+      while (i1 < xfaName->getLength()) {
+	if (xfaName->getChar(i1) == '.') {
+	  ++i1;
+	  break;
+	}
+	++i1;
+      }
+      if (xfaName->getChar(i0) == '#') {
+	xfaName->del(i0, i1 - i0);
+      } else {
+	i0 = i1;
+      }
+    }
+    xfaFieldA = acroFormA->xfaScanner->findField(xfaName);
+    delete xfaName;
+  }
+
   //----- check for a radio button
 
   // this is a kludge: if we see a Btn-type field with kids, and the
@@ -400,7 +741,9 @@
       typeA = acroFormFieldCheckbox;
     }
   } else if (!typeStr->cmp("Tx")) {
-    if (flagsA & acroFormFlagFileSelect) {
+    if (xfaFieldA && xfaFieldA->getBarcodeInfo()) {
+      typeA = acroFormFieldBarcode;
+    } else if (flagsA & acroFormFlagFileSelect) {
       typeA = acroFormFieldFileSelect;
     } else if (flagsA & acroFormFlagMultiline) {
       typeA = acroFormFieldMultilineText;
@@ -422,7 +765,7 @@
   delete typeStr;
 
   field = new AcroFormField(acroFormA, fieldRefA, &fieldObjA,
-			    typeA, nameA, flagsA, typeFromParentA);
+			    typeA, nameA, flagsA, typeFromParentA, xfaFieldA);
   fieldObjA.free();
   return field;
 
@@ -436,7 +779,8 @@
 AcroFormField::AcroFormField(AcroForm *acroFormA,
 			     Object *fieldRefA, Object *fieldObjA,
 			     AcroFormFieldType typeA, TextString *nameA,
-			     Guint flagsA, GBool typeFromParentA) {
+			     Guint flagsA, GBool typeFromParentA,
+			     XFAField *xfaFieldA) {
   acroForm = acroFormA;
   fieldRefA->copy(&fieldRef);
   fieldObjA->copy(&fieldObj);
@@ -444,6 +788,7 @@
   name = nameA;
   flags = flagsA;
   typeFromParent = typeFromParentA;
+  xfaField = xfaFieldA;
 }
 
 AcroFormField::~AcroFormField() {
@@ -478,6 +823,7 @@
   case acroFormFieldFileSelect:    return "FileSelect";
   case acroFormFieldMultilineText: return "MultilineText";
   case acroFormFieldText:          return "Text";
+  case acroFormFieldBarcode:       return "Barcode";
   case acroFormFieldComboBox:      return "ComboBox";
   case acroFormFieldListBox:       return "ListBox";
   case acroFormFieldSignature:     return "Signature";
@@ -508,36 +854,47 @@
   u = NULL;
   *length = 0;
 
-  fieldLookup("V", &obj1);
-  if (obj1.isName()) {
-    s = obj1.getName();
-    n = (int)strlen(s);
-    u = (Unicode *)gmallocn(n, sizeof(Unicode));
-    for (i = 0; i < n; ++i) {
-      u[i] = s[i] & 0xff;
+  // if this field has a counterpart in the XFA form, take the value
+  // from the XFA field (NB: an XFA field with no value overrides the
+  // AcroForm value)
+  if (xfaField) {
+    if (xfaField->getValue()) {
+      u = utf8ToUnicode(xfaField->getValue(), length);
     }
-    *length = n;
-  } else if (obj1.isString()) {
-    ts = new TextString(obj1.getString());
-    n = ts->getLength();
-    u = (Unicode *)gmallocn(n, sizeof(Unicode));
-    memcpy(u, ts->getUnicode(), n * sizeof(Unicode));
-    *length = n;
-    delete ts;
-  } else if (obj1.isDict()) {
-    obj1.dictLookup("Contents", &obj2);
-    if (obj2.isString()) {
-      gs = obj2.getString();
-      n = gs->getLength();
+
+  // no XFA form - take the AcroForm value
+  } else {
+    fieldLookup("V", &obj1);
+    if (obj1.isName()) {
+      s = obj1.getName();
+      n = (int)strlen(s);
       u = (Unicode *)gmallocn(n, sizeof(Unicode));
       for (i = 0; i < n; ++i) {
-	u[i] = gs->getChar(i) & 0xff;
+        u[i] = s[i] & 0xff;
       }
       *length = n;
+    } else if (obj1.isString()) {
+      ts = new TextString(obj1.getString());
+      n = ts->getLength();
+      u = (Unicode *)gmallocn(n, sizeof(Unicode));
+      memcpy(u, ts->getUnicode(), n * sizeof(Unicode));
+      *length = n;
+      delete ts;
+    } else if (obj1.isDict()) {
+      obj1.dictLookup("Contents", &obj2);
+      if (obj2.isString()) {
+        gs = obj2.getString();
+        n = gs->getLength();
+        u = (Unicode *)gmallocn(n, sizeof(Unicode));
+        for (i = 0; i < n; ++i) {
+  	u[i] = gs->getChar(i) & 0xff;
+        }
+        *length = n;
+      }
+      obj2.free();
     }
-    obj2.free();
+    obj1.free();
   }
-  obj1.free();
 
   return u;
 }
@@ -818,6 +1175,8 @@
   render = gFalse;
   if (acroForm->needAppearances) {
     render = gTrue;
+  } else if (xfaField && xfaField->getValue()) {
+    render = gTrue;
   } else {
     if (!annotObj->dictLookup("AP", &obj1)->isDict()) {
       render = gTrue;
@@ -889,9 +1248,17 @@
   double borderWidth;
   double *borderDash;
   GString *appearanceState;
-  int borderDashLength, rot, quadding, comb, nOptions, topIdx, i, j;
+  int borderDashLength, rot, quadding, vAlign, comb, nOptions, topIdx, i;
 
   appearBuf = new GString();
+#if 0 //~debug
+  appearBuf->appendf("1 1 0 rg 0 0 {0:.4f} {1:.4f} re f\n",
+		     xMax - xMin, yMax - yMin);
+#endif
+#if 0 //~debug
+  appearBuf->appendf("1 1 0 RG 0 0 {0:.4f} {1:.4f} re s\n",
+		     xMax - xMin, yMax - yMin);
+#endif
 
   // get the appearance characteristics (MK) dictionary
   if (annot->lookup("MK", &mkObj)->isDict()) {
@@ -1141,6 +1508,9 @@
   asObj.free();
   apObj.free();
 
+  int valueLength;
+  Unicode *value = getValue(&valueLength);
+
   // draw the field contents
   if (ftObj.isName("Btn")) {
     caption = NULL;
@@ -1153,12 +1523,13 @@
     // radio button
     if (flags & acroFormFlagRadio) {
       //~ Acrobat doesn't draw a caption if there is no AP dict (?)
-      if (fieldLookup("V", &obj1)
-	    ->isName(appearanceState->getCString())) {
+      if (value && unicodeStringEqual(value, valueLength,
+				      appearanceState->getCString())) {
 	if (caption) {
-	  drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter,
-		   gFalse, gTrue, rot, xMin, yMin, xMax, yMax, borderWidth,
-		   appearBuf);
+	  drawText(caption, da, fontDict, gFalse, 0,
+		   acroFormQuadCenter, acroFormVAlignMiddleNoDescender,
+		   gFalse, gTrue, rot, 0, 0, xMax - xMin, yMax - yMin,
+		   borderWidth, gFalse, appearBuf);
 	} else {
 	  if (mkDict) {
 	    if (mkDict->lookup("BC", &obj2)->isArray() &&
@@ -1173,55 +1544,112 @@
 	  }
 	}
       }
-      obj1.free();
     // pushbutton
     } else if (flags & acroFormFlagPushbutton) {
       if (caption) {
-	drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter,
-		 gFalse, gFalse, rot, xMin, yMin, xMax, yMax, borderWidth,
-		 appearBuf);
+	drawText(caption, da, fontDict, gFalse, 0,
+		 acroFormQuadCenter, acroFormVAlignMiddle,
+		 gFalse, gFalse, rot, 0, 0, xMax - xMin, yMax - yMin,
+		 borderWidth, gFalse, appearBuf);
       }
     // checkbox
     } else {
-      fieldLookup("V", &obj1);
-      if (obj1.isName() && !(obj1.isName("Off") ||
-			     obj1.isName("No") ||
-			     obj1.isName(""))) {
+      if (value && !(unicodeStringEqual(value, valueLength, "Off") ||
+		     unicodeStringEqual(value, valueLength, "No") ||
+		     unicodeStringEqual(value, valueLength, "0") ||
+		     valueLength == 0)) {
 	if (!caption) {
 	  caption = new GString("3"); // ZapfDingbats checkmark
 	}
-	drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter,
-		 gFalse, gTrue, rot, xMin, yMin, xMax, yMax, borderWidth,
-		 appearBuf);
+	drawText(caption, da, fontDict, gFalse, 0,
+		 acroFormQuadCenter, acroFormVAlignMiddleNoDescender,
+		 gFalse, gTrue, rot, 0, 0, xMax - xMin, yMax - yMin,
+		 borderWidth, gFalse, appearBuf);
       }
-      obj1.free();
     }
     if (caption) {
       delete caption;
     }
   } else if (ftObj.isName("Tx")) {
-    //~ value strings can be Unicode
-    fieldLookup("V", &obj1);
-    if (obj1.isString()) {
-      if (fieldLookup("Q", &obj2)->isInt()) {
-	quadding = obj2.getInt();
+    XFAFieldBarcodeInfo *barcodeInfo = xfaField ? xfaField->getBarcodeInfo()
+                                                : (XFAFieldBarcodeInfo *)NULL;
+    if (value) {
+      //~ value strings can be Unicode
+      GString *valueLatin1 = unicodeToLatin1(value, valueLength);
+      if (barcodeInfo) {
+	drawBarcode(valueLatin1, da, fontDict, rot, xMin, yMin, xMax, yMax,
+		    barcodeInfo, appearBuf);
       } else {
-	quadding = acroFormQuadLeft;
-      }
-      obj2.free();
-      comb = 0;
-      if (flags & acroFormFlagComb) {
-	if (fieldLookup("MaxLen", &obj2)->isInt()) {
-	  comb = obj2.getInt();
+	if (fieldLookup("Q", &obj2)->isInt()) {
+	  quadding = obj2.getInt();
+	} else {
+	  quadding = acroFormQuadLeft;
 	}
 	obj2.free();
+	vAlign = (flags & acroFormFlagMultiline) ? acroFormVAlignTop
+	                                         : acroFormVAlignMiddle;
+	XFAFieldLayoutInfo *layoutInfo = xfaField ? xfaField->getLayoutInfo()
+	                                          : (XFAFieldLayoutInfo *)NULL;
+	if (layoutInfo) {
+	  switch (layoutInfo->hAlign) {
+	  case xfaFieldLayoutHAlignLeft:
+	  default:
+	    quadding = acroFormQuadLeft;
+	    break;
+	  case xfaFieldLayoutHAlignCenter:
+	    quadding = acroFormQuadCenter;
+	    break;
+	  case xfaFieldLayoutHAlignRight:
+	    quadding = acroFormQuadRight;
+	    break;
+	  }
+	  switch (layoutInfo->vAlign) {
+	  case xfaFieldLayoutVAlignTop:
+	  default:
+	    vAlign = acroFormVAlignTop;
+	    break;
+	  case xfaFieldLayoutVAlignMiddle:
+	    vAlign = acroFormVAlignMiddle;
+	    break;
+	  case xfaFieldLayoutVAlignBottom:
+	    vAlign = acroFormVAlignBottom;
+	    break;
+	  }
+	}
+	comb = 0;
+	if (flags & acroFormFlagComb) {
+	  if (fieldLookup("MaxLen", &obj2)->isInt()) {
+	    comb = obj2.getInt();
+	  }
+	  obj2.free();
+	}
+	XFAFieldPictureInfo *pictureInfo =
+	    xfaField ? xfaField->getPictureInfo()
+	             : (XFAFieldPictureInfo *)NULL;
+	GString *value2 = valueLatin1;
+	if (pictureInfo) {
+	  switch (pictureInfo->subtype) {
+	  case xfaFieldPictureDateTime:
+	    value2 = pictureFormatDateTime(valueLatin1, pictureInfo->format);
+	    break;
+	  case xfaFieldPictureNumeric:
+	    value2 = pictureFormatNumber(valueLatin1, pictureInfo->format);
+	    break;
+	  case xfaFieldPictureText:
+	    value2 = pictureFormatText(valueLatin1, pictureInfo->format);
+	    break;
+	  }
+	}
+	drawText(value2, da, fontDict,
+		 flags & acroFormFlagMultiline, comb, quadding, vAlign,
+		 gTrue, gFalse, rot, 0, 0, xMax - xMin, yMax - yMin,
+		 borderWidth, gFalse, appearBuf);
+	if (value2 != valueLatin1) {
+	  delete value2;
+	}
       }
-      drawText(obj1.getString(), da, fontDict,
-	       flags & acroFormFlagMultiline, comb, quadding,
-	       gTrue, gFalse, rot, xMin, yMin, xMax, yMax, borderWidth,
-	       appearBuf);
+      delete valueLatin1;
     }
-    obj1.free();
   } else if (ftObj.isName("Ch")) {
     //~ value/option strings can be Unicode
     if (fieldLookup("Q", &obj1)->isInt()) {
@@ -1230,10 +1658,39 @@
       quadding = acroFormQuadLeft;
     }
     obj1.free();
+    vAlign = acroFormVAlignMiddle;
+    XFAFieldLayoutInfo *layoutInfo = xfaField ? xfaField->getLayoutInfo()
+                                              : (XFAFieldLayoutInfo *)NULL;
+    if (layoutInfo) {
+      switch (layoutInfo->hAlign) {
+      case xfaFieldLayoutHAlignLeft:
+      default:
+	quadding = acroFormQuadLeft;
+	break;
+      case xfaFieldLayoutHAlignCenter:
+	quadding = acroFormQuadCenter;
+	break;
+      case xfaFieldLayoutHAlignRight:
+	quadding = acroFormQuadRight;
+	break;
+      }
+      switch (layoutInfo->vAlign) {
+      case xfaFieldLayoutVAlignTop:
+      default:
+	vAlign = acroFormVAlignTop;
+	break;
+      case xfaFieldLayoutVAlignMiddle:
+	vAlign = acroFormVAlignMiddle;
+	break;
+      case xfaFieldLayoutVAlignBottom:
+	vAlign = acroFormVAlignBottom;
+	break;
+      }
+    }
     // combo box
     if (flags & acroFormFlagCombo) {
-      if (fieldLookup("V", &obj1)->isString()) {
-	val = obj1.getString()->copy();
+      if (value) {
+	val = unicodeToLatin1(value, valueLength);
 	if (fieldObj.dictLookup("Opt", &obj2)->isArray()) {
 	  for (i = 0, done = false; i < obj2.arrayGetLength() && !done; ++i) {
 	    obj2.arrayGet(i, &obj3);
@@ -1254,12 +1711,12 @@
 	}
 	obj2.free();
 	drawText(val, da, fontDict,
-		 gFalse, 0, quadding, gTrue, gFalse, rot,
-		 xMin, yMin, xMax, yMax, borderWidth, appearBuf);
+		 gFalse, 0, quadding, vAlign, gTrue, gFalse, rot,
+		 0, 0, xMax - xMin, yMax - yMin, borderWidth,
+		 gFalse, appearBuf);
 	delete val;
 	//~ Acrobat draws a popup icon on the right side
       }
-      obj1.free();
     // list box
     } else {
       if (fieldObj.dictLookup("Opt", &obj1)->isArray()) {
@@ -1285,27 +1742,15 @@
 	// get the selected option(s)
 	selection = (GBool *)gmallocn(nOptions, sizeof(GBool));
 	//~ need to use the I field in addition to the V field
-	fieldLookup("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();
-	    }
-	  }
+	  selection[i] = unicodeStringEqual(value, valueLength, text[i]);
 	}
-	obj2.free();
 	// get the top index
 	if (fieldObj.dictLookup("TI", &obj2)->isInt()) {
 	  topIdx = obj2.getInt();
+	  if (topIdx < 0 || topIdx >= nOptions) {
+	    topIdx = 0;
+	  }
 	} else {
 	  topIdx = 0;
 	}
@@ -1334,14 +1779,16 @@
       delete da;
     }
     da = new GString("/Helv 10 Tf 1 0 0 rg");
-    drawText(caption, da, fontDict,
-	     gFalse, 0, acroFormQuadLeft, gFalse, gFalse, rot,
-	     xMin, yMin, xMax, yMax, borderWidth, appearBuf);
+    drawText(caption, da, fontDict, gFalse, 0,
+	     acroFormQuadLeft, acroFormVAlignMiddle, gFalse, gFalse, rot,
+	     0, 0, xMax - xMin, yMax - yMin, borderWidth, gFalse, appearBuf);
     delete caption;
   } else {
     error(errSyntaxError, -1, "Unknown field type");
   }
 
+  gfree(value);
+
   delete appearanceState;
   if (da) {
     delete da;
@@ -1457,17 +1904,20 @@
 
 // Draw the variable text or caption for a field.
 void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict,
-			     GBool multiline, int comb, int quadding,
+			     GBool multiline, int comb,
+			     int quadding, int vAlign,
 			     GBool txField, GBool forceZapfDingbats, int rot,
-			     double xMin, double yMin, double xMax, double yMax,
-			     double border, GString *appearBuf) {
+			     double x, double y, double width, double height,
+			     double border, GBool whiteBackground,
+			     GString *appearBuf) {
   GString *text2;
   GList *daToks;
   GString *tok;
   GfxFont *font;
   double dx, dy;
-  double fontSize, fontSize2, x, xPrev, y, w, w2, wMax;
-  int tfPos, tmPos, i, j, k, c;
+  double fontSize, fontSize2, topBorder, xx, xPrev, yy, w, wMax;
+  double offset, offset2, charWidth, ascent, descent;
+  int tfPos, tmPos, nLines, 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
@@ -1501,7 +1951,7 @@
   if (da) {
     daToks = tokenize(da);
     for (i = 2; i < daToks->getLength(); ++i) {
-      if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) {
+      if (!((GString *)daToks->get(i))->cmp("Tf")) {
 	tfPos = i - 2;
       } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) {
 	tmPos = i - 6;
@@ -1558,23 +2008,21 @@
   }
   appearBuf->append("q\n");
   if (rot == 90) {
-    appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", xMax - xMin);
-    dx = yMax - yMin;
-    dy = xMax - xMin;
+    appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", width);
+    dx = height;
+    dy = width;
   } else if (rot == 180) {
-    appearBuf->appendf("-1 0 0 -1 {0:.4f} {1:.4f} cm\n",
-		       xMax - xMin, yMax - yMin);
-    dx = xMax - yMax;
-    dy = yMax - yMin;
+    appearBuf->appendf("-1 0 0 -1 {0:.4f} {1:.4f} cm\n", width, height);
+    dx = width;
+    dy = height;
   } else if (rot == 270) {
-    appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin);
-    dx = yMax - yMin;
-    dy = xMax - xMin;
+    appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", height);
+    dx = height;
+    dy = width;
   } else { // assume rot == 0
-    dx = xMax - xMin;
-    dy = yMax - yMin;
+    dx = width;
+    dy = height;
   }
-  appearBuf->append("BT\n");
 
   // multi-line text
   if (multiline) {
@@ -1582,22 +2030,29 @@
 
     wMax = dx - 2 * border - 4;
 
+#if 1 //~tmp
+    // this is a kludge that appears to match Adobe's behavior
+    if (height > 15) {
+      topBorder = 5;
+    } else {
+      topBorder = 2;
+    }
+#else
+    topBorder = 5;
+#endif
+
     // compute font autosize
     if (fontSize == 0) {
       for (fontSize = 10; fontSize > 1; --fontSize) {
-	y = dy - 3;
-	w2 = 0;
+	yy = dy - topBorder;
 	i = 0;
 	while (i < text2->getLength()) {
 	  getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k);
-	  if (w > w2) {
-	    w2 = w;
-	  }
 	  i = k;
-	  y -= fontSize;
+	  yy -= fontSize;
 	}
 	// approximate the descender for the last line
-	if (y >= 0.33 * fontSize && w <= wMax) {
+	if (yy >= 0.25 * fontSize && w <= wMax) {
 	  break;
 	}
       }
@@ -1609,22 +2064,54 @@
     }
 
     // starting y coordinate
-    // (note: each line of text starts with a Td operator that moves
-    // down a line)
-    if (dy > fontSize + 6) {
-      y = dy - 3;
+    nLines = 0;
+    i = 0;
+    while (i < text2->getLength()) {
+      getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k);
+      i = k;
+      ++nLines;
+    }
+    if (font) {
+      ascent = font->getDeclaredAscent() * fontSize;
+      descent = font->getDescent() * fontSize;
     } else {
-      y = 0.5 * dy - 0.4 * fontSize + fontSize;
+      ascent = 0.75 * fontSize;
+      descent = -0.25 * fontSize;
     }
+    switch (vAlign) {
+    case acroFormVAlignTop:
+    default:
+      yy = dy - ascent - topBorder;
+      break;
+    case acroFormVAlignMiddle:
+      yy = 0.5 * (dy - nLines * fontSize) + (nLines - 1) * fontSize - descent;
+      break;
+    case acroFormVAlignMiddleNoDescender:
+      yy = 0.5 * (dy - nLines * fontSize) + (nLines - 1) * fontSize;
+      break;
+    case acroFormVAlignBottom:
+      yy = (nLines - 1) * fontSize - descent;
+      break;
+    }
+    // if the field is shorter than a line of text, Acrobat positions
+    // the text relative to the bottom edge
+    if (dy < fontSize + topBorder) {
+      yy = 2 - descent;
+    }
+    // each line of text starts with a Td operator that moves down a
+    // line -- so move up a line here
+    yy += fontSize;
 
+    appearBuf->append("BT\n");
+
     // set the font matrix
     if (tmPos >= 0) {
       tok = (GString *)daToks->get(tmPos + 4);
       tok->clear();
-      tok->append('0');
+      tok->appendf("{0:.4f}", x);
       tok = (GString *)daToks->get(tmPos + 5);
       tok->clear();
-      tok->appendf("{0:.4f}", y);
+      tok->appendf("{0:.4f}", y + yy);
     }
 
     // write the DA string
@@ -1636,7 +2123,7 @@
 
     // write the font matrix (if not part of the DA string)
     if (tmPos < 0) {
-      appearBuf->appendf("1 0 0 1 0 {0:.4f} Tm\n", y);
+      appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y + yy);
     }
 
     // write a series of lines of text
@@ -1650,18 +2137,18 @@
       switch (quadding) {
       case acroFormQuadLeft:
       default:
-	x = border + 2;
+	xx = border + 2;
 	break;
       case acroFormQuadCenter:
-	x = (dx - w) / 2;
+	xx = (dx - w) / 2;
 	break;
       case acroFormQuadRight:
-	x = dx - border - 2 - w;
+	xx = dx - border - 2 - w;
 	break;
       }
 
       // draw the line
-      appearBuf->appendf("{0:.4f} {1:.4f} Td\n", x - xPrev, -fontSize);
+      appearBuf->appendf("{0:.4f} {1:.4f} Td\n", xx - xPrev, -fontSize);
       appearBuf->append('(');
       for (; i < j; ++i) {
 	c = text2->getChar(i) & 0xff;
@@ -1678,9 +2165,11 @@
 
       // next line
       i = k;
-      xPrev = x;
+      xPrev = xx;
     }
 
+    appearBuf->append("ET\n");
+
   // single-line text
   } else {
     //~ replace newlines with spaces? - what does Acrobat do?
@@ -1689,7 +2178,7 @@
     if (comb > 0) {
 
       // compute comb spacing
-      w = (dx - 2 * border) / comb;
+      w = dx / comb;
 
       // compute font autosize
       if (fontSize == 0) {
@@ -1712,25 +2201,48 @@
       switch (quadding) {
       case acroFormQuadLeft:
       default:
-	x = border + 2;
+	xx = 0;
 	break;
       case acroFormQuadCenter:
-	x = border + 2 + 0.5 * (comb - text2->getLength()) * w;
+	xx = ((comb - text2->getLength()) / 2) * w;
 	break;
       case acroFormQuadRight:
-	x = border + 2 + (comb - text2->getLength()) * w;
+	xx = (comb - text2->getLength()) * w;
 	break;
       }
-      y = 0.5 * dy - 0.4 * fontSize;
+      if (font) {
+	ascent = font->getDeclaredAscent() * fontSize;
+	descent = font->getDescent() * fontSize;
+      } else {
+	ascent = 0.75 * fontSize;
+	descent = -0.25 * fontSize;
+      }
+      switch (vAlign) {
+      case acroFormVAlignTop:
+      default:
+	yy = dy - ascent;
+	break;
+      case acroFormVAlignMiddle:
+	yy = 0.5 * (dy - ascent - descent);
+	break;
+      case acroFormVAlignMiddleNoDescender:
+	yy = 0.5 * (dy - ascent);
+	break;
+      case acroFormVAlignBottom:
+	yy = -descent;
+	break;
+      }
 
+      appearBuf->append("BT\n");
+
       // set the font matrix
       if (tmPos >= 0) {
 	tok = (GString *)daToks->get(tmPos + 4);
 	tok->clear();
-	tok->appendf("{0:.4f}", x);
+	tok->appendf("{0:.4f}", x + xx);
 	tok = (GString *)daToks->get(tmPos + 5);
 	tok->clear();
-	tok->appendf("{0:.4f}", y);
+	tok->appendf("{0:.4f}", y + yy);
       }
 
       // write the DA string
@@ -1742,29 +2254,35 @@
 
       // write the font matrix (if not part of the DA string)
       if (tmPos < 0) {
-	appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y);
+	appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x + xx, y + yy);
       }
 
       // write the text string
-      //~ this should center (instead of left-justify) each character within
-      //~     its comb cell
+      offset = 0;
       for (i = 0; i < text2->getLength(); ++i) {
-	if (i > 0) {
-	  appearBuf->appendf("{0:.4f} 0 Td\n", w);
-	}
-	appearBuf->append('(');
 	c = text2->getChar(i) & 0xff;
-	if (c == '(' || c == ')' || c == '\\') {
-	  appearBuf->append('\\');
-	  appearBuf->append((char)c);
-	} else if (c < 0x20 || c >= 0x80) {
-	  appearBuf->appendf("{0:.4f} 0 Td\n", w);
+	if (c >= 0x20 && c < 0x80) {
+	  if (font && !font->isCIDFont()) {
+	    charWidth = ((Gfx8BitFont *)font)->getWidth((Guchar)c) * fontSize;
+	  } else {
+	    // otherwise, make a crude estimate
+	    charWidth = 0.5 * fontSize;
+	  }
+	  offset2 = 0.5 * (w - charWidth);
+	  appearBuf->appendf("{0:.4f} 0 Td\n", offset + offset2);
+	  if (c == '(' || c == ')' || c == '\\') {
+	    appearBuf->appendf("(\\{0:c}) Tj\n", c);
+	  } else {
+	    appearBuf->appendf("({0:c}) Tj\n", c);
+	  }
+	  offset = w - offset2;
 	} else {
-	  appearBuf->append((char)c);
+	  offset += w;
 	}
-	appearBuf->append(") Tj\n");
       }
 
+      appearBuf->append("ET\n");
+
     // regular (non-comb) formatting
     } else {
 
@@ -1802,25 +2320,54 @@
       switch (quadding) {
       case acroFormQuadLeft:
       default:
-	x = border + 2;
+	xx = border + 2;
 	break;
       case acroFormQuadCenter:
-	x = (dx - w) / 2;
+	xx = (dx - w) / 2;
 	break;
       case acroFormQuadRight:
-	x = dx - border - 2 - w;
+	xx = dx - border - 2 - w;
 	break;
       }
-      y = 0.5 * dy - 0.4 * fontSize;
+      if (font) {
+	ascent = font->getDeclaredAscent() * fontSize;
+	descent = font->getDescent() * fontSize;
+      } else {
+	ascent = 0.75 * fontSize;
+	descent = -0.25 * fontSize;
+      }
+      switch (vAlign) {
+      case acroFormVAlignTop:
+      default:
+	yy = dy - ascent;
+	break;
+      case acroFormVAlignMiddle:
+	yy = 0.5 * (dy - ascent - descent);
+	break;
+      case acroFormVAlignMiddleNoDescender:
+	yy = 0.5 * (dy - ascent);
+	break;
+      case acroFormVAlignBottom:
+	yy = -descent;
+	break;
+      }
 
+      if (whiteBackground) {
+	appearBuf->appendf("q 1 g {0:.4f} {1:.4f} {2:.4f} {3:.4f} re f Q\n",
+			   xx - 0.25 * fontSize, yy - 0.35 * fontSize,
+			   w + 0.5 * fontSize, 1.2 * fontSize);
+      }
+
+      appearBuf->append("BT\n");
+
       // set the font matrix
       if (tmPos >= 0) {
 	tok = (GString *)daToks->get(tmPos + 4);
 	tok->clear();
-	tok->appendf("{0:.4f}", x);
+	tok->appendf("{0:.4f}", x + xx);
 	tok = (GString *)daToks->get(tmPos + 5);
 	tok->clear();
-	tok->appendf("{0:.4f}", y);
+	tok->appendf("{0:.4f}", y + yy);
       }
 
       // write the DA string
@@ -1832,7 +2379,7 @@
 
       // write the font matrix (if not part of the DA string)
       if (tmPos < 0) {
-	appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y);
+	appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x + xx, y + yy);
       }
 
       // write the text string
@@ -1850,10 +2397,11 @@
       }
       appearBuf->append(") Tj\n");
     }
+
+    appearBuf->append("ET\n");
   }
 
   // cleanup
-  appearBuf->append("ET\n");
   appearBuf->append("Q\n");
   if (txField) {
     appearBuf->append("EMC\n");
@@ -2197,6 +2745,203 @@
   appearBuf->append("S\n");
 }
 
+void AcroFormField::drawBarcode(GString *value, GString *da,
+				GfxFontDict *fontDict, int rot,
+				double xMin, double yMin,
+				double xMax, double yMax,
+				XFAFieldBarcodeInfo *barcodeInfo,
+				GString *appearBuf) {
+  //--- handle rotation
+  double w, h;
+  appearBuf->append("q\n");
+  switch (rot) {
+  case 0:
+  default:
+    w = xMax - xMin;
+    h = yMax - yMin;
+    break;
+  case 90:
+    appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", xMax - xMin);
+    w = yMax - yMin;
+    h = xMax - xMin;
+    break;
+  case 180:
+    appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin);
+    w = yMax - yMin;
+    h = xMax - xMin;
+    break;
+  case 270:
+    appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin);
+    w = yMax - yMin;
+    h = xMax - xMin;
+    break;
+  }
+
+  //--- get the font size
+  double fontSize = 0.2 * h;
+  if (da) {
+    GList *daToks = tokenize(da);
+    for (int i = 2; i < daToks->getLength(); ++i) {
+      if (!((GString *)daToks->get(i))->cmp("Tf")) {
+	fontSize = atof(((GString *)daToks->get(i - 1))->getCString());
+	break;
+      }
+    }
+    deleteGList(daToks, GString);
+  }
+
+  //--- compute the embedded text type position
+  GBool doText = gTrue;
+  double yText = 0;
+  int vAlign = acroFormVAlignTop;
+  double yBarcode = 0;
+  double hBarcode = 0;
+  GBool whiteBackground = gFalse;
+  //~ this uses an estimate of the font baseline position
+  if (barcodeInfo->textLocation &&
+      !barcodeInfo->textLocation->cmp("above")) {
+    yText = h;
+    vAlign = acroFormVAlignTop;
+    yBarcode = 0;
+    hBarcode = h - fontSize;
+  } else if (barcodeInfo->textLocation &&
+	     !barcodeInfo->textLocation->cmp("belowEmbedded")) {
+    yText = 0;
+    vAlign = acroFormVAlignBottom;
+    yBarcode = 0;
+    hBarcode = h;
+    whiteBackground = gTrue;
+  } else if (barcodeInfo->textLocation &&
+	     !barcodeInfo->textLocation->cmp("aboveEmbedded")) {
+    yText = h;
+    vAlign = acroFormVAlignTop;
+    yBarcode = 0;
+    hBarcode = h;
+    whiteBackground = gTrue;
+  } else if (barcodeInfo->textLocation &&
+	     !barcodeInfo->textLocation->cmp("none")) {
+    doText = gFalse;
+  } else { // default is "below"
+    yText = 0;
+    vAlign = acroFormVAlignBottom;
+    yBarcode = fontSize;
+    hBarcode = h - fontSize;
+  }
+  double wText = w;
+
+  //--- remove extraneous start/stop chars
+  GString *value2 = value->copy();
+  if (!barcodeInfo->barcodeType->cmp("code3Of9")) {
+    if (value2->getLength() >= 1 && value2->getChar(0) == '*') {
+      value2->del(0);
+    }
+    if (value2->getLength() >= 1 &&
+	value2->getChar(value2->getLength() - 1) == '*') {
+      value2->del(value2->getLength() - 1);
+    }
+  }
+
+  //--- draw the bar code
+  if (!barcodeInfo->barcodeType->cmp("code3Of9")) {
+    if (!barcodeInfo->dataLength) {
+      error(errSyntaxError, -1,
+	    "Missing 'dataLength' attribute in barcode field");
+      goto err;
+    }
+    appearBuf->append("0 g\n");
+    double wNarrow = w / ((7 + 3 * barcodeInfo->wideNarrowRatio)
+			  * (barcodeInfo->dataLength + 2));
+    double xx = 0;
+    for (int i = -1; i <= value2->getLength(); ++i) {
+      int c;
+      if (i < 0 || i >= value2->getLength()) {
+	c = '*';
+      } else {
+	c = value2->getChar(i) & 0x7f;
+      }
+      for (int j = 0; j < 10; j += 2) {
+	appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n",
+			   xx, yBarcode,
+			   (code3Of9Data[c][j] ? barcodeInfo->wideNarrowRatio
+			                       : 1) * wNarrow,
+			   hBarcode);
+	xx += ((code3Of9Data[c][j] ? barcodeInfo->wideNarrowRatio : 1) +
+	       (code3Of9Data[c][j+1] ? barcodeInfo->wideNarrowRatio : 1))
+	      * wNarrow;
+      }
+    }
+    // center the text on the drawn barcode (not the max length barcode)
+    wText = (value2->getLength() + 2) * (7 + 3 * barcodeInfo->wideNarrowRatio)
+            * wNarrow;
+  } else if (!barcodeInfo->barcodeType->cmp("code128B")) {
+    if (!barcodeInfo->dataLength) {
+      error(errSyntaxError, -1,
+	    "Missing 'dataLength' attribute in barcode field");
+      goto err;
+    }
+    appearBuf->append("0 g\n");
+    double wNarrow = w / (11 * (barcodeInfo->dataLength + 3) + 2);
+    double xx = 0;
+    int checksum = 0;
+    for (int i = -1; i <= value2->getLength() + 1; ++i) {
+      int c;
+      if (i == -1) {
+	// start code B
+	c = 104;
+	checksum += c;
+      } else if (i == value2->getLength()) {
+	// checksum
+	c = checksum % 103;
+      } else if (i == value2->getLength() + 1) {
+	// stop code
+	c = 106;
+      } else {
+	c = value2->getChar(i) & 0xff;
+	if (c >= 32 && c <= 127) {
+	  c -= 32;
+	} else {
+	  c = 0;
+	}	  
+	checksum += (i + 1) * c;
+      }
+      for (int j = 0; j < 6; j += 2) {
+	appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n",
+			   xx, yBarcode,
+			   code128Data[c][j] * wNarrow, hBarcode);
+	xx += (code128Data[c][j] + code128Data[c][j+1]) * wNarrow;
+      }
+    }
+    // final bar of the stop code
+    appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n",
+		       xx, yBarcode, 2 * wNarrow, hBarcode);
+    // center the text on the drawn barcode (not the max length barcode)
+    wText = (11 * (value2->getLength() + 3) + 2) * wNarrow;
+  } else if (!barcodeInfo->barcodeType->cmp("pdf417")) {
+    drawPDF417Barcode(w, h, barcodeInfo->moduleWidth,
+		      barcodeInfo->moduleHeight,
+		      barcodeInfo->errorCorrectionLevel,
+		      value2, appearBuf);
+    doText = gFalse;
+  } else {
+    error(errSyntaxError, -1,
+	  "Unimplemented barcode type '{0:t}' in barcode field",
+	  barcodeInfo->barcodeType);
+  }
+  //~ add other barcode types here
+
+  //--- draw the embedded text
+  if (doText) {
+    drawText(value2, da, fontDict, gFalse, 0, acroFormQuadCenter,
+	     vAlign, gFalse, gFalse, 0, 0, yText, wText, yText + fontSize,
+	     0, whiteBackground, appearBuf);
+  }
+
+  appearBuf->append("Q\n");
+
+ err:
+  delete value2;
+}
+
 GList *AcroFormField::tokenize(GString *s) {
   GList *toks;
   int i, j;
@@ -2373,19 +3118,769 @@
 }
 
 Object *AcroFormField::fieldLookup(Dict *dict, const char *key, Object *obj) {
-  Object parent;
+  Object parent, parent2;
+  int depth;
 
   if (!dict->lookup(key, obj)->isNull()) {
     return obj;
   }
   obj->free();
-  if (dict->lookup("Parent", &parent)->isDict()) {
-    fieldLookup(parent.getDict(), key, obj);
-  } else {
-    // some fields don't specify a parent, so we check the AcroForm
-    // dictionary just in case
-    acroForm->acroFormObj.dictLookup(key, obj);
+
+  dict->lookup("Parent", &parent)->isDict();
+  depth = 0;
+  while (parent.isDict() && depth < maxFieldObjectDepth) {
+    if (!parent.dictLookup(key, obj)->isNull()) {
+      parent.free();
+      return obj;
+    }
+    obj->free();
+    parent.dictLookup("Parent", &parent2);
+    parent.free();
+    parent = parent2;
+    ++depth;
   }
   parent.free();
+
+  // some fields don't specify a parent, so we check the AcroForm
+  // dictionary just in case
+  acroForm->acroFormObj.dictLookup(key, obj);
   return obj;
 }
+
+Unicode *AcroFormField::utf8ToUnicode(GString *s, int *unicodeLength) {
+  int n = 0;
+  int i = 0;
+  Unicode u;
+  while (getUTF8(s, &i, &u)) {
+    ++n;
+  }
+  Unicode *uVec = (Unicode *)gmallocn(n, sizeof(Unicode));
+  n = 0;
+  i = 0;
+  while (getUTF8(s, &i, &uVec[n])) {
+    ++n;
+  }
+  *unicodeLength = n;
+  return uVec;
+}
+
+GString *AcroFormField::unicodeToLatin1(Unicode *u, int unicodeLength) {
+  GString *s = new GString();
+  for (int i = 0; i < unicodeLength; ++i) {
+    if (u[i] <= 0xff) {
+      s->append((char)u[i]);
+    }
+  }
+  return s;
+}
+
+GBool AcroFormField::unicodeStringEqual(Unicode *u, int unicodeLength,
+					GString *s) {
+  if (s->getLength() != unicodeLength) {
+    return gFalse;
+  }
+  for (int i = 0; i < unicodeLength; ++i) {
+    if ((s->getChar(i) & 0xff) != u[i]) {
+      return gFalse;
+    }
+  }
+  return gTrue;
+}
+
+GBool AcroFormField::unicodeStringEqual(Unicode *u, int unicodeLength,
+					const char *s) {
+  for (int i = 0; i < unicodeLength; ++i) {
+    if (!s[i] || (s[i] & 0xff) != u[i]) {
+      return gFalse;
+    }
+  }
+  return gTrue;
+}
+
+//------------------------------------------------------------------------
+// 'picture' formatting
+//------------------------------------------------------------------------
+
+class PictureNode {
+public:
+  virtual ~PictureNode() {}
+  virtual GBool isLiteral() { return gFalse; }
+  virtual GBool isSign() { return gFalse; }
+  virtual GBool isDigit() { return gFalse; }
+  virtual GBool isDecPt() { return gFalse; }
+  virtual GBool isSeparator() { return gFalse; }
+  virtual GBool isYear() { return gFalse; }
+  virtual GBool isMonth() { return gFalse; }
+  virtual GBool isDay() { return gFalse; }
+  virtual GBool isHour() { return gFalse; }
+  virtual GBool isMinute() { return gFalse; }
+  virtual GBool isSecond() { return gFalse; }
+  virtual GBool isChar() { return gFalse; }
+};
+
+class PictureLiteral: public PictureNode {
+public:
+  PictureLiteral(GString *sA) { s = sA; }
+  virtual ~PictureLiteral() { delete s; }
+  virtual GBool isLiteral() { return gTrue; }
+  GString *s;
+};
+
+class PictureSign: public PictureNode {
+public:
+  PictureSign(char cA) { c = cA; }
+  virtual GBool isSign() { return gTrue; }
+  char c;
+};
+
+class PictureDigit: public PictureNode {
+public:
+  PictureDigit(char cA) { c = cA; pos = 0; }
+  virtual GBool isDigit() { return gTrue; }
+  char c;
+  int pos;
+};
+
+class PictureDecPt: public PictureNode {
+public:
+  PictureDecPt() { }
+  virtual GBool isDecPt() { return gTrue; }
+};
+
+class PictureSeparator: public PictureNode {
+public:
+  PictureSeparator() { }
+  virtual GBool isSeparator() { return gTrue; }
+};
+
+class PictureYear: public PictureNode {
+public:
+  PictureYear(int nDigitsA) { nDigits = nDigitsA; }
+  virtual GBool isYear() { return gTrue; }
+  int nDigits;
+};
+
+class PictureMonth: public PictureNode {
+public:
+  PictureMonth(int nDigitsA) { nDigits = nDigitsA; }
+  virtual GBool isMonth() { return gTrue; }
+  int nDigits;
+};
+
+class PictureDay: public PictureNode {
+public:
+  PictureDay(int nDigitsA) { nDigits = nDigitsA; }
+  virtual GBool isDay() { return gTrue; }
+  int nDigits;
+};
+
+class PictureHour: public PictureNode {
+public:
+  PictureHour(GBool is24HourA, int nDigitsA)
+    { is24Hour = is24HourA; nDigits = nDigitsA; }
+  virtual GBool isHour() { return gTrue; }
+  GBool is24Hour;
+  int nDigits;
+};
+
+class PictureMinute: public PictureNode {
+public:
+  PictureMinute(int nDigitsA) { nDigits = nDigitsA; }
+  virtual GBool isMinute() { return gTrue; }
+  int nDigits;
+};
+
+class PictureSecond: public PictureNode {
+public:
+  PictureSecond(int nDigitsA) { nDigits = nDigitsA; }
+  virtual GBool isSecond() { return gTrue; }
+  int nDigits;
+};
+
+class PictureChar: public PictureNode {
+public:
+  PictureChar() {}
+  virtual GBool isChar() { return gTrue; }
+};
+
+GString *AcroFormField::pictureFormatDateTime(GString *value,
+					      GString *picture) {
+  GList *pic;
+  PictureNode *node;
+  GString *ret, *s;
+  char c;
+  int year, month, day, hour, min, sec;
+  int len, picStart, picEnd, u, n, i, j;
+
+  len = value->getLength();
+  if (len == 0) {
+    return value->copy();
+  }
+
+  //--- parse the value
+
+  // expected format is yyyy(-mm(-dd)?)?Thh(:mm(:ss)?)?
+  // where:
+  // - the '-'s and ':'s are optional
+  // - the 'T' is literal
+  // - we're ignoring optional time zone info at the end
+  // (if the value is not in this canonical format, we just punt and
+  // return the value string)
+  //~ another option would be to parse the value following the
+  //~   <ui><picture> element
+  year = month = day = hour = min = sec = 0;
+  i = 0;
+  if (!(i + 4 <= len && isValidInt(value, i, 4))) {
+    return value->copy();
+  }
+  year = convertInt(value, i, 4);
+  i += 4;
+  if (i < len && value->getChar(i) == '-') {
+    ++i;
+  }
+  if (i + 2 <= len && isValidInt(value, i, 2)) {
+    month = convertInt(value, i, 2);
+    i += 2;
+    if (i < len && value->getChar(i) == '-') {
+      ++i;
+    }
+    if (i + 2 <= len && isValidInt(value, i, 2)) {
+      day = convertInt(value, i, 2);
+      i += 2;
+    }
+  }
+  if (i < len) {
+    if (value->getChar(i) != 'T') {
+      return value->copy();
+    }
+    ++i;
+    if (!(i + 2 <= len && isValidInt(value, i, 2))) {
+      return value->copy();
+    }
+    hour = convertInt(value, i, 2);
+    i += 2;
+    if (i < len && value->getChar(i) == ':') {
+      ++i;
+    }
+    if (i + 2 <= len && isValidInt(value, i, 2)) {
+      min = convertInt(value, i, 2);
+      i += 2;
+      if (i < len && value->getChar(i) == ':') {
+	++i;
+      }
+      if (i + 2 <= len && isValidInt(value, i, 2)) {
+	sec = convertInt(value, i, 2);
+	i += 2;
+      }
+    }
+  }
+  if (i < len) {
+    return value->copy();
+  }
+
+  //--- skip the category and locale in the picture
+
+  picStart = 0;
+  picEnd = picture->getLength();
+  for (i = 0; i < picture->getLength(); ++i) {
+    c = picture->getChar(i);
+    if (c == '{') {
+      picStart = i + 1;
+      for (picEnd = picStart;
+	   picEnd < picture->getLength() && picture->getChar(picEnd) != '}';
+	   ++picEnd) ;
+      break;
+    } else if (!((c >= 'a' && c <= 'z') ||
+		 (c >= 'A' && c <= 'Z') ||
+		 c == '(' ||
+		 c == ')')) {
+      break;
+    }
+  }
+
+  //--- parse the picture
+
+  pic = new GList();
+  i = picStart;
+  while (i < picEnd) {
+    c = picture->getChar(i);
+    ++i;
+    if (c == '\'') {
+      s = new GString();
+      while (i < picEnd) {
+	c = picture->getChar(i);
+	if (c == '\'') {
+	  ++i;
+	  if (i < picEnd && picture->getChar(i) == '\'') {
+	    s->append('\'');
+	    ++i;
+	  } else {
+	    break;
+	  }
+	} else if (c == '\\') {
+	  ++i;
+	  if (i == picEnd) {
+	    break;
+	  }
+	  c = picture->getChar(i);
+	  ++i;
+	  if (c == 'u' && i+4 <= picEnd) {
+	    u = 0;
+	    for (j = 0; j < 4; ++j, ++i) {
+	      c = picture->getChar(i);
+	      u <<= 4;
+	      if (c >= '0' && c <= '9') {
+		u += c - '0';
+	      } else if (c >= 'a' && c <= 'f') {
+		u += c - 'a' + 10;
+	      } else if (c >= 'A' && c <= 'F') {
+		u += c - 'A' + 10;
+	      }
+	    }
+	    //~ this should convert to UTF-8 (?)
+	    if (u <= 0xff) {
+	      s->append((char)u);
+	    }
+	  } else {
+	    s->append(c);
+	  }
+	} else {
+	  s->append(c);
+	}
+      }
+      pic->append(new PictureLiteral(s));
+    } else if (c == ',' || c == '-' || c == ':' ||
+	       c == '/' || c == '.' || c == ' ') {
+      s = new GString();
+      s->append(c);
+      pic->append(new PictureLiteral(s));
+    } else if (c == 'Y') {
+      for (n = 1; n < 4 && i < picEnd && picture->getChar(i) == 'Y'; ++n, ++i) ;
+      pic->append(new PictureYear(n));
+    } else if (c == 'M') {
+      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'M'; ++n, ++i) ;
+      pic->append(new PictureMonth(n));
+    } else if (c == 'D') {
+      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'D'; ++n, ++i) ;
+      pic->append(new PictureDay(n));
+    } else if (c == 'h') {
+      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'h'; ++n, ++i) ;
+      pic->append(new PictureHour(gFalse, n));
+    } else if (c == 'H') {
+      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'H'; ++n, ++i) ;
+      pic->append(new PictureHour(gTrue, n));
+    } else if (c == 'M') {
+      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'M'; ++n, ++i) ;
+      pic->append(new PictureMinute(n));
+    } else if (c == 'S') {
+      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'S'; ++n, ++i) ;
+      pic->append(new PictureSecond(n));
+    }
+  }
+
+  //--- generate formatted text
+
+  ret = new GString();
+  for (i = 0; i < pic->getLength(); ++i) {
+    node = (PictureNode *)pic->get(i);
+    if (node->isLiteral()) {
+      ret->append(((PictureLiteral *)node)->s);
+    } else if (node->isYear()) {
+      if (((PictureYear *)node)->nDigits == 2) {
+	if (year >= 1930 && year < 2030) {
+	  ret->appendf("{0:02d}", year % 100);
+	} else {
+	  ret->append("??");
+	}
+      } else {
+	ret->appendf("{0:04d}", year);
+      }
+    } else if (node->isMonth()) {
+      if (((PictureMonth *)node)->nDigits == 1) {
+	ret->appendf("{0:d}", month);
+      } else {
+	ret->appendf("{0:02d}", month);
+      }
+    } else if (node->isDay()) {
+      if (((PictureDay *)node)->nDigits == 1) {
+	ret->appendf("{0:d}", day);
+      } else {
+	ret->appendf("{0:02d}", day);
+      }
+    } else if (node->isHour()) {
+      if (((PictureHour *)node)->is24Hour) {
+	n = hour;
+      } else {
+	n = hour % 12;
+	if (n == 0) {
+	  n = 12;
+	}
+      }
+      if (((PictureHour *)node)->nDigits == 1) {
+	ret->appendf("{0:d}", n);
+      } else {
+	ret->appendf("{0:02d}", n);
+      }
+    } else if (node->isMinute()) {
+      if (((PictureMinute *)node)->nDigits == 1) {
+	ret->appendf("{0:d}", min);
+      } else {
+	ret->appendf("{0:02d}", min);
+      }
+    } else if (node->isSecond()) {
+      if (((PictureSecond *)node)->nDigits == 1) {
+	ret->appendf("{0:d}", sec);
+      } else {
+	ret->appendf("{0:02d}", sec);
+      }
+    }
+  }
+  deleteGList(pic, PictureNode);
+
+  return ret;
+}
+
+GString *AcroFormField::pictureFormatNumber(GString *value, GString *picture) {
+  GList *pic;
+  PictureNode *node;
+  GString *ret, *s;
+  GBool neg, haveDigits;
+  char c;
+  int start, decPt, trailingZero, len;
+  int picStart, picEnd, u, pos, i, j;
+
+  len = value->getLength();
+  if (len == 0) {
+    return value->copy();
+  }
+
+  //--- parse the value
+
+  // -nnnn.nnnn0000
+  //  ^   ^    ^   ^
+  //  |   |    |   +-- len
+  //  |   |    +------ trailingZero
+  //  |   +----------- decPt
+  //  +--------------- start
+  start = 0;
+  neg = gFalse;
+  if (value->getChar(start) == '-') {
+    neg = gTrue;
+    ++start;
+  } else if (value->getChar(start) == '+') {
+    ++start;
+  }
+  for (decPt = start; decPt < len && value->getChar(decPt) != '.'; ++decPt) ;
+  for (trailingZero = len;
+       trailingZero > decPt && value->getChar(trailingZero - 1) == '0';
+       --trailingZero) ;
+
+  //--- skip the category and locale in the picture
+
+  picStart = 0;
+  picEnd = picture->getLength();
+  for (i = 0; i < picture->getLength(); ++i) {
+    c = picture->getChar(i);
+    if (c == '{') {
+      picStart = i + 1;
+      for (picEnd = picStart;
+	   picEnd < picture->getLength() && picture->getChar(picEnd) != '}';
+	   ++picEnd) ;
+      break;
+    } else if (!((c >= 'a' && c <= 'z') ||
+		 (c >= 'A' && c <= 'Z') ||
+		 c == '(' ||
+		 c == ')')) {
+      break;
+    }
+  }
+
+  //--- parse the picture
+
+  pic = new GList();
+  i = picStart;
+  while (i < picEnd) {
+    c = picture->getChar(i);
+    ++i;
+    if (c == '\'') {
+      s = new GString();
+      while (i < picEnd) {
+	c = picture->getChar(i);
+	if (c == '\'') {
+	  ++i;
+	  if (i < picEnd && picture->getChar(i) == '\'') {
+	    s->append('\'');
+	    ++i;
+	  } else {
+	    break;
+	  }
+	} else if (c == '\\') {
+	  ++i;
+	  if (i == picEnd) {
+	    break;
+	  }
+	  c = picture->getChar(i);
+	  ++i;
+	  if (c == 'u' && i+4 <= picEnd) {
+	    u = 0;
+	    for (j = 0; j < 4; ++j, ++i) {
+	      c = picture->getChar(i);
+	      u <<= 4;
+	      if (c >= '0' && c <= '9') {
+		u += c - '0';
+	      } else if (c >= 'a' && c <= 'F') {
+		u += c - 'a' + 10;
+	      } else if (c >= 'A' && c <= 'F') {
+		u += c - 'A' + 10;
+	      }
+	    }
+	    //~ this should convert to UTF-8 (?)
+	    if (u <= 0xff) {
+	      s->append((char)u);
+	    }
+	  } else {
+	    s->append(c);
+	  }
+	} else {
+	  s->append(c);
+	  ++i;
+	}
+      }
+      pic->append(new PictureLiteral(s));
+    } else if (c == '-' || c == ':' || c == '/' || c == ' ') {
+      s = new GString();
+      s->append(c);
+      pic->append(new PictureLiteral(s));
+    } else if (c == 's' || c == 'S') {
+      pic->append(new PictureSign(c));
+    } else if (c == 'Z' || c == 'z' || c == '8' || c == '9') {
+      pic->append(new PictureDigit(c));
+    } else if (c == '.') {
+      pic->append(new PictureDecPt());
+    } else if (c == ',') {
+      pic->append(new PictureSeparator());
+    }
+  }
+  for (i = 0; i < pic->getLength(); ++i) {
+    node = (PictureNode *)pic->get(i);
+    if (node->isDecPt()) {
+      break;
+    }
+  }
+  pos = 0;
+  for (j = i - 1; j >= 0; --j) {
+    node = (PictureNode *)pic->get(j);
+    if (node->isDigit()) {
+      ((PictureDigit *)node)->pos = pos;
+      ++pos;
+    }
+  }
+  pos = -1;
+  for (j = i + 1; j < pic->getLength(); ++j) {
+    node = (PictureNode *)pic->get(j);
+    if (node->isDigit()) {
+      ((PictureDigit *)node)->pos = pos;
+      --pos;
+    }
+  }
+
+  //--- generate formatted text
+
+  ret = new GString();
+  haveDigits = gFalse;
+  for (i = 0; i < pic->getLength(); ++i) {
+    node = (PictureNode *)pic->get(i);
+    if (node->isLiteral()) {
+      ret->append(((PictureLiteral *)node)->s);
+    } else if (node->isSign()) {
+      if (((PictureSign *)node)->c == 'S') {
+	ret->append(neg ? '-' : ' ');
+      } else {
+	if (neg) {
+	  ret->append('-');
+	}
+      }
+    } else if (node->isDigit()) {
+      pos = ((PictureDigit *)node)->pos;
+      c = ((PictureDigit *)node)->c;
+      if (pos >= 0 && pos < decPt - start) {
+	ret->append(value->getChar(decPt - 1 - pos));
+	haveDigits = gTrue;
+      } else if (pos < 0 && -pos <= trailingZero - decPt - 1) {
+	ret->append(value->getChar(decPt - pos));
+	haveDigits = gTrue;
+      } else if (c == '8' &&
+		 pos < 0 &&
+		 -pos <= len - decPt - 1) {
+	ret->append('0');
+	haveDigits = gTrue;
+      } else if (c == '9') {
+	ret->append('0');
+	haveDigits = gTrue;
+      } else if (c == 'Z' && pos >= 0) {
+	ret->append(' ');
+      }
+    } else if (node->isDecPt()) {
+      if (!(i+1 < pic->getLength() &&
+	    ((PictureNode *)pic->get(i+1))->isDigit() &&
+	    ((PictureDigit *)pic->get(i+1))->c == 'z') ||
+	  trailingZero > decPt + 1) {
+	ret->append('.');
+      }
+    } else if (node->isSeparator()) {
+      if (haveDigits) {
+	ret->append(',');
+      }
+    }
+  }
+  deleteGList(pic, PictureNode);
+
+  return ret;
+}
+
+GString *AcroFormField::pictureFormatText(GString *value, GString *picture) {
+  GList *pic;
+  PictureNode *node;
+  GString *ret, *s;
+  char c;
+  int len, picStart, picEnd, u, i, j;
+
+  len = value->getLength();
+  if (len == 0) {
+    return value->copy();
+  }
+
+  //--- skip the category and locale in the picture
+
+  picStart = 0;
+  picEnd = picture->getLength();
+  for (i = 0; i < picture->getLength(); ++i) {
+    c = picture->getChar(i);
+    if (c == '{') {
+      picStart = i + 1;
+      for (picEnd = picStart;
+	   picEnd < picture->getLength() && picture->getChar(picEnd) != '}';
+	   ++picEnd) ;
+      break;
+    } else if (!((c >= 'a' && c <= 'z') ||
+		 (c >= 'A' && c <= 'Z') ||
+		 c == '(' ||
+		 c == ')')) {
+      break;
+    }
+  }
+
+  //--- parse the picture
+
+  pic = new GList();
+  i = picStart;
+  while (i < picEnd) {
+    c = picture->getChar(i);
+    ++i;
+    if (c == '\'') {
+      s = new GString();
+      while (i < picEnd) {
+	c = picture->getChar(i);
+	if (c == '\'') {
+	  ++i;
+	  if (i < picEnd && picture->getChar(i) == '\'') {
+	    s->append('\'');
+	    ++i;
+	  } else {
+	    break;
+	  }
+	} else if (c == '\\') {
+	  ++i;
+	  if (i == picEnd) {
+	    break;
+	  }
+	  c = picture->getChar(i);
+	  ++i;
+	  if (c == 'u' && i+4 <= picEnd) {
+	    u = 0;
+	    for (j = 0; j < 4; ++j, ++i) {
+	      c = picture->getChar(i);
+	      u <<= 4;
+	      if (c >= '0' && c <= '9') {
+		u += c - '0';
+	      } else if (c >= 'a' && c <= 'F') {
+		u += c - 'a' + 10;
+	      } else if (c >= 'A' && c <= 'F') {
+		u += c - 'A' + 10;
+	      }
+	    }
+	    //~ this should convert to UTF-8 (?)
+	    if (u <= 0xff) {
+	      s->append((char)u);
+	    }
+	  } else {
+	    s->append(c);
+	  }
+	} else {
+	  s->append(c);
+	  ++i;
+	}
+      }
+      pic->append(new PictureLiteral(s));
+    } else if (c == ',' || c == '-' || c == ':' ||
+	       c == '/' || c == '.' || c == ' ') {
+      s = new GString();
+      s->append(c);
+      pic->append(new PictureLiteral(s));
+    } else if (c == 'A' || c == 'X' || c == 'O' || c == '0' || c == '9') {
+      pic->append(new PictureChar());
+    }
+  }
+
+  //--- generate formatted text
+
+  ret = new GString();
+  j = 0;
+  for (i = 0; i < pic->getLength(); ++i) {
+    node = (PictureNode *)pic->get(i);
+    if (node->isLiteral()) {
+      ret->append(((PictureLiteral *)node)->s);
+    } else if (node->isChar()) {
+      // if there are more chars in the picture than in the value,
+      // Adobe renders the value as-is, without picture formatting
+      if (j >= value->getLength()) {
+	delete ret;
+	ret = value->copy();
+	break;
+      }
+      ret->append(value->getChar(j));
+      ++j;
+    }
+  }
+  deleteGList(pic, PictureNode);
+
+  return ret;
+}
+
+GBool AcroFormField::isValidInt(GString *s, int start, int len) {
+  int i;
+
+  for (i = 0; i < len; ++i) {
+    if (!(start + i < s->getLength() &&
+	  s->getChar(start + i) >= '0' &&
+	  s->getChar(start + i) <= '9')) {
+      return gFalse;
+    }
+  }
+  return gTrue;
+}
+
+int AcroFormField::convertInt(GString *s, int start, int len) {
+  char c;
+  int x, i;
+
+  x = 0;
+  for (i = 0; i < len && start + i < s->getLength(); ++i) {
+    c = s->getChar(start + i);
+    if (c < '0' || c > '9') {
+      break;
+    }
+    x = x * 10 + (c - '0');
+  }
+  return x;
+}

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/AcroForm.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -15,27 +15,32 @@
 #pragma interface
 #endif
 
-#include "Form.h"
-
 class TextString;
+class Gfx;
 class GfxFont;
 class GfxFontDict;
+class AcroFormField;
+class XFAScanner;
+class XFAField;
+class XFAFieldBarcodeInfo;
 
 //------------------------------------------------------------------------
 
-class AcroForm: public Form {
+class AcroForm {
 public:
 
   static AcroForm *load(PDFDoc *docA, Catalog *catalog, Object *acroFormObjA);
 
-  virtual ~AcroForm();
+  ~AcroForm();
 
-  virtual const char *getType() { return "AcroForm"; }
+  const char *getType();
 
-  virtual void draw(int pageNum, Gfx *gfx, GBool printing);
+  void draw(int pageNum, Gfx *gfx, GBool printing);
 
-  virtual int getNumFields();
-  virtual FormField *getField(int idx);
+  int getNumFields();
+  AcroFormField *getField(int idx);
+  AcroFormField *findField(int pg, double x, double y);
+  int findFieldIdx(int pg, double x, double y);
 
 private:
 
@@ -44,10 +49,13 @@
   int lookupAnnotPage(Object *annotRef);
   void scanField(Object *fieldRef);
 
+  PDFDoc *doc;
   Object acroFormObj;
   GBool needAppearances;
   GList *annotPages;		// [AcroFormAnnotPage]
   GList *fields;		// [AcroFormField]
+  XFAScanner *xfaScanner;
+  GBool isStaticXFA;
 
   friend class AcroFormField;
 };
@@ -61,28 +69,29 @@
   acroFormFieldFileSelect,
   acroFormFieldMultilineText,
   acroFormFieldText,
+  acroFormFieldBarcode,
   acroFormFieldComboBox,
   acroFormFieldListBox,
   acroFormFieldSignature
 };
 
-class AcroFormField: public FormField {
+class AcroFormField {
 public:
 
   static AcroFormField *load(AcroForm *acroFormA, Object *fieldRefA);
 
-  virtual ~AcroFormField();
+  ~AcroFormField();
 
-  virtual int getPageNum();
-  virtual const char *getType();
-  virtual Unicode *getName(int *length);
-  virtual Unicode *getValue(int *length);
-  virtual void getBBox(double *llx, double *lly, double *urx, double *ury);
-  virtual void getFont(Ref *fontID, double *fontSize);
-  virtual void getColor(double *red, double *green, double *blue);
+  int getPageNum();
+  const char *getType();
+  Unicode *getName(int *length);
+  Unicode *getValue(int *length);
+  void getBBox(double *llx, double *lly, double *urx, double *ury);
+  void getFont(Ref *fontID, double *fontSize);
+  void getColor(double *red, double *green, double *blue);
   int getMaxLen();
 
-  virtual Object *getResources(Object *res);
+  Object *getResources(Object *res);
 
   AcroFormFieldType getAcroFormFieldType() { return type; }
   Object *getFieldRef(Object *ref);
@@ -94,7 +103,7 @@
 
   AcroFormField(AcroForm *acroFormA, Object *fieldRefA, Object *fieldObjA,
 		AcroFormFieldType typeA, TextString *nameA,
-		Guint flagsA, GBool typeFromParentA);
+		Guint flagsA, GBool typeFromParentA, XFAField *xfaFieldA);
   Ref findFontName(char *fontTag);
   void draw(int pageNum, Gfx *gfx, GBool printing);
   void drawAnnot(int pageNum, Gfx *gfx, GBool printing,
@@ -107,10 +116,10 @@
 			 double xMax, double yMax);
   void setColor(Array *a, GBool fill, int adjust, GString *appearBuf);
   void drawText(GString *text, GString *da, GfxFontDict *fontDict,
-		GBool multiline, int comb, int quadding,
+		GBool multiline, int comb, int quadding, int vAlign,
 		GBool txField, GBool forceZapfDingbats, int rot,
-		double xMin, double yMin, double xMax, double yMax,
-		double border, GString *appearBuf);
+		double x, double y, double width, double height,
+		double border, GBool whiteBackground, GString *appearBuf);
   void drawListBox(GString **text, GBool *selection,
 		   int nOptions, int topIdx,
 		   GString *da, GfxFontDict *fontDict,
@@ -126,6 +135,9 @@
 			 GString *appearBuf);
   void drawCircleBottomRight(double cx, double cy, double r,
 			     GString *appearBuf);
+  void drawBarcode(GString *value, GString *da, GfxFontDict *fontDict, int rot,
+		   double xMin, double yMin, double xMax, double yMax,
+		   XFAFieldBarcodeInfo *barcodeInfo, GString *appearBuf);
   GList *tokenize(GString *s);
   Object *getAnnotObj(Object *annotObj);
   Object *getAnnotResources(Dict *annot, Object *res);
@@ -132,6 +144,15 @@
   void buildDefaultResourceDict(Object *dr);
   Object *fieldLookup(const char *key, Object *obj);
   Object *fieldLookup(Dict *dict, const char *key, Object *obj);
+  Unicode *utf8ToUnicode(GString *s, int *unicodeLength);
+  GString *unicodeToLatin1(Unicode *u, int unicodeLength);
+  GBool unicodeStringEqual(Unicode *u, int unicodeLength, GString *s);
+  GBool unicodeStringEqual(Unicode *u, int unicodeLength, const char *s);
+  GString *pictureFormatDateTime(GString *value, GString *picture);
+  GString *pictureFormatNumber(GString *value, GString *picture);
+  GString *pictureFormatText(GString *value, GString *picture);
+  GBool isValidInt(GString *s, int start, int len);
+  int convertInt(GString *s, int start, int len);
 
   AcroForm *acroForm;
   Object fieldRef;
@@ -140,6 +161,7 @@
   TextString *name;
   Guint flags;
   GBool typeFromParent;
+  XFAField *xfaField;
 
   friend class AcroForm;
 };

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Annot.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Annot.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Annot.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -25,7 +25,7 @@
 #include "Lexer.h"
 #include "PDFDoc.h"
 #include "OptionalContent.h"
-#include "Form.h"
+#include "AcroForm.h"
 #include "BuiltinFontTables.h"
 #include "FontEncodingTables.h"
 #include "Annot.h"

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CMakeLists.txt
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CMakeLists.txt	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CMakeLists.txt	2021-02-01 06:11:18 UTC (rev 57588)
@@ -38,7 +38,6 @@
   Dict.cc
   Error.cc
   FontEncodingTables.cc
-  Form.cc
   Function.cc
   Gfx.cc
   GfxFont.cc
@@ -67,7 +66,7 @@
   UnicodeRemapping.cc
   UnicodeTypeTable.cc
   UTF8.cc
-  XFAForm.cc
+  XFAScanner.cc
   XRef.cc
   Zoox.cc
 )
@@ -88,6 +87,7 @@
     PDFCore.cc
     PreScanOutputDev.cc
     PSOutputDev.cc
+    ShadingImage.cc
     SplashOutputDev.cc
     TextOutputDev.cc
     TileCache.cc
@@ -104,6 +104,7 @@
     $<TARGET_OBJECTS:xpdf_objs>
     PreScanOutputDev.cc
     PSOutputDev.cc
+    ShadingImage.cc
     SplashOutputDev.cc
     pdftops.cc
   )
@@ -151,6 +152,7 @@
   add_executable(pdftohtml
     $<TARGET_OBJECTS:xpdf_objs>
     HTMLGen.cc
+    ShadingImage.cc
     SplashOutputDev.cc
     TextOutputDev.cc
     WebFont.cc
@@ -215,6 +217,7 @@
 if (HAVE_SPLASH)
   add_executable(pdftoppm
     $<TARGET_OBJECTS:xpdf_objs>
+    ShadingImage.cc
     SplashOutputDev.cc
     pdftoppm.cc
   )
@@ -234,6 +237,7 @@
 if (HAVE_SPLASH AND PNG_FOUND)
   add_executable(pdftopng
     $<TARGET_OBJECTS:xpdf_objs>
+    ShadingImage.cc
     SplashOutputDev.cc
     pdftopng.cc
   )

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -28,7 +28,7 @@
 #include "Page.h"
 #include "Error.h"
 #include "Link.h"
-#include "Form.h"
+#include "AcroForm.h"
 #include "TextString.h"
 #include "Catalog.h"
 
@@ -159,6 +159,7 @@
   baseURI = NULL;
   form = NULL;
   embeddedFiles = NULL;
+  pageLabels = NULL;
 #if MULTITHREADED
   gInitMutex(&pageMutex);
 #endif
@@ -222,7 +223,7 @@
   catDict.dictLookup("AcroForm", &acroForm);
 
   // get the NeedsRendering flag
-  // NB: Form::load() uses this value
+  // NB: AcroForm::load() uses this value
   needsRendering = catDict.dictLookup("NeedsRendering", &obj)->isBool() &&
                    obj.getBool();
   obj.free();
@@ -230,7 +231,7 @@
   // create the Form
   // (if acroForm is a null object, this will still create an AcroForm
   // if there are unattached Widget-type annots)
-  form = Form::load(doc, this, &acroForm);
+  form = AcroForm::load(doc, this, &acroForm);
 
   // get the OCProperties dictionary
   catDict.dictLookup("OCProperties", &ocProperties);
@@ -241,7 +242,6 @@
   // get the ViewerPreferences object
   catDict.dictLookupNF("ViewerPreferences", &viewerPrefs);
 
-  pageLabels = NULL;
   if (catDict.dictLookup("PageLabels", &obj)->isDict()) {
     readPageLabelTree(&obj);
   }
@@ -1190,7 +1190,7 @@
 	return gFalse;
       }
     }
-    *n = (len - prefixLength - 1) * 26 + (u[i] - (Unicode)style) + 1;
+    *n = (len - prefixLength - 1) * 26 + (u[prefixLength] - (Unicode)style) + 1;
     return gTrue;
   }
   return gFalse;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Catalog.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -30,7 +30,7 @@
 class LinkDest;
 class PageTreeNode;
 class PageLabelNode;
-class Form;
+class AcroForm;
 class TextString;
 
 //------------------------------------------------------------------------
@@ -88,7 +88,7 @@
 
   Object *getAcroForm() { return &acroForm; }
 
-  Form *getForm() { return form; }
+  AcroForm *getForm() { return form; }
 
   GBool getNeedsRendering() { return needsRendering; }
 
@@ -137,7 +137,7 @@
   Object outline;		// outline dictionary
   Object acroForm;		// AcroForm dictionary
   GBool needsRendering;		// NeedsRendering flag
-  Form *form;			// parsed form
+  AcroForm *form;		// parsed form
   Object ocProperties;		// OCProperties dictionary
   GList *embeddedFiles;		// embedded file list [EmbeddedFile]
   GList *pageLabels;		// page labels [PageLabelNode]

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -470,8 +470,13 @@
 void CharCodeToUnicode::addMapping(CharCode code, char *uStr, int n,
 				   int offset) {
   CharCode oldLen, i;
+#if 1 //~utf16
+  Unicode u[maxUnicodeString];
+  int uLen, j;
+#else
   Unicode u;
   int j;
+#endif
 
   if (code > 0xffffff) {
     // This is an arbitrary limit to avoid integer overflow issues.
@@ -478,6 +483,11 @@
     // (I've seen CMaps with mappings for <ffffffff>.)
     return;
   }
+#if 1 //~utf16
+  if ((uLen = parseUTF16String(uStr, n, u)) == 0) {
+    return;
+  }
+#endif
   if (code >= mapLen) {
     oldLen = mapLen;
     mapLen = mapLen ? 2 * mapLen : 256;
@@ -489,6 +499,25 @@
       map[i] = 0;
     }
   }
+#if 1 //~utf16
+  if (uLen == 1) {
+    map[code] = u[0] + offset;
+  } else {
+    if (sMapLen >= sMapSize) {
+      sMapSize = sMapSize + 16;
+      sMap = (CharCodeToUnicodeString *)
+	       greallocn(sMap, sMapSize, sizeof(CharCodeToUnicodeString));
+    }
+    map[code] = 0;
+    sMap[sMapLen].c = code;
+    for (j = 0; j < uLen; ++j) {
+      sMap[sMapLen].u[j] = u[j];
+    }
+    sMap[sMapLen].u[uLen - 1] += offset;
+    sMap[sMapLen].len = uLen;
+    ++sMapLen;
+  }
+#else //~utf16
   if (n <= 4) {
     if (!parseHex(uStr, n, &u)) {
       error(errSyntaxWarning, -1, "Illegal entry in ToUnicode CMap");
@@ -515,8 +544,38 @@
     sMap[sMapLen].u[sMap[sMapLen].len - 1] += offset;
     ++sMapLen;
   }
+#endif //~utf16
 }
 
+// Convert a UTF-16BE hex string into a sequence of up to
+// maxUnicodeString Unicode chars.
+int CharCodeToUnicode::parseUTF16String(char *uStr, int n, Unicode *uOut) {
+  int i = 0;
+  int uLen = 0;
+  while (i < n) {
+    Unicode u;
+    int j = n;
+    if (j - i > 4) {
+      j = i + 4;
+    }
+    if (!parseHex(uStr + i, j - i, &u)) {
+      error(errSyntaxWarning, -1, "Illegal entry in ToUnicode CMap");
+      return 0;
+    }
+    // look for a UTF-16 pair
+    if (uLen > 0 && uOut[uLen-1] >= 0xd800 && uOut[uLen-1] <= 0xdbff &&
+	u >= 0xdc00 && u <= 0xdfff) {
+      uOut[uLen-1] = 0x10000 + ((uOut[uLen-1] & 0x03ff) << 10) + (u & 0x03ff);
+    } else {
+      if (uLen < maxUnicodeString) {
+	uOut[uLen++] = u;
+      }
+    }
+    i = j;
+  }
+  return uLen;
+}
+
 void CharCodeToUnicode::addMappingInt(CharCode code, Unicode u) {
   CharCode oldLen, i;
 

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/CharCodeToUnicode.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -80,6 +80,7 @@
 
   GBool parseCMap1(int (*getCharFunc)(void *), void *data, int nBits);
   void addMapping(CharCode code, char *uStr, int n, int offset);
+  int parseUTF16String(char *uStr, int n, Unicode *uOut);
   void addMappingInt(CharCode code, Unicode u);
   CharCodeToUnicode();
   CharCodeToUnicode(GString *tagA);

Deleted: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Form.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Form.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Form.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,99 +0,0 @@
-//========================================================================
-//
-// Form.cc
-//
-// Copyright 2012 Glyph & Cog, LLC
-//
-//========================================================================
-
-#include <aconf.h>
-
-#ifdef USE_GCC_PRAGMAS
-#pragma implementation
-#endif
-
-#include "gmempp.h"
-#include "GlobalParams.h"
-#include "Error.h"
-#include "Object.h"
-#include "PDFDoc.h"
-#include "AcroForm.h"
-#include "XFAForm.h"
-#include "Form.h"
-
-//------------------------------------------------------------------------
-// Form
-//------------------------------------------------------------------------
-
-Form *Form::load(PDFDoc *docA, Catalog *catalog, Object *acroFormObj) {
-  Form *form;
-  Object xfaObj;
-
-  if (acroFormObj->isDict()) {
-    //~ temporary: create an XFAForm only for XFAF, not for dynamic XFA
-    acroFormObj->dictLookup("XFA", &xfaObj);
-    if (globalParams->getEnableXFA() &&
-	!xfaObj.isNull() &&
-	!catalog->getNeedsRendering()) {
-      form = XFAForm::load(docA, catalog, acroFormObj, &xfaObj);
-      xfaObj.free();
-      return form;
-    }
-    xfaObj.free();
-  }
-
-  // if acroFormObj is a null object, this will still create an
-  // AcroForm if there are unattached Widget-type annots
-  return AcroForm::load(docA, catalog, acroFormObj);
-}
-
-Form::Form(PDFDoc *docA) {
-  doc = docA;
-}
-
-Form::~Form() {
-}
-
-FormField *Form::findField(int pg, double x, double y) {
-  FormField *field;
-  double llx, lly, urx, ury;
-  int i;
-
-  for (i = 0; i < getNumFields(); ++i) {
-    field = getField(i);
-    if (field->getPageNum() == pg) {
-      field->getBBox(&llx, &lly, &urx, &ury);
-      if (llx <= x && x <= urx && lly <= y && y <= ury) {
-	return field;
-      }
-    }
-  }
-  return NULL;
-}
-
-int Form::findFieldIdx(int pg, double x, double y) {
-  FormField *field;
-  double llx, lly, urx, ury;
-  int i;
-
-  for (i = 0; i < getNumFields(); ++i) {
-    field = getField(i);
-    if (field->getPageNum() == pg) {
-      field->getBBox(&llx, &lly, &urx, &ury);
-      if (llx <= x && x <= urx && lly <= y && y <= ury) {
-	return i;
-      }
-    }
-  }
-  return -1;
-}
-
-//------------------------------------------------------------------------
-// FormField
-//------------------------------------------------------------------------
-
-FormField::FormField() {
-}
-
-FormField::~FormField() {
-}

Deleted: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Form.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Form.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Form.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,77 +0,0 @@
-//========================================================================
-//
-// Form.h
-//
-// Copyright 2012 Glyph & Cog, LLC
-//
-//========================================================================
-
-#ifndef FORM_H
-#define FORM_H
-
-#include <aconf.h>
-
-#ifdef USE_GCC_PRAGMAS
-#pragma interface
-#endif
-
-#include "gtypes.h"
-
-class Gfx;
-class FormField;
-
-//------------------------------------------------------------------------
-
-class Form {
-public:
-
-  static Form *load(PDFDoc *docA, Catalog *catalog, Object *acroFormObj);
-
-  virtual ~Form();
-
-  virtual const char *getType() = 0;
-
-  virtual void draw(int pageNum, Gfx *gfx, GBool printing) = 0;
-
-  virtual int getNumFields() = 0;
-  virtual FormField *getField(int idx) = 0;
-
-  FormField *findField(int pg, double x, double y);
-  int findFieldIdx(int pg, double x, double y);
-
-protected:
-
-  Form(PDFDoc *docA);
-
-  PDFDoc *doc;
-};
-
-//------------------------------------------------------------------------
-
-class FormField {
-public:
-
-  FormField();
-  virtual ~FormField();
-
-  virtual int getPageNum() = 0;
-  virtual const char *getType() = 0;
-
-  // Return the field name.  This never returns NULL.
-  virtual Unicode *getName(int *length) = 0;
-
-  // Return the field value.  This returns NULL if the field does not
-  // have a value.
-  virtual Unicode *getValue(int *length) = 0;
-
-  virtual void getBBox(double *llx, double *lly, double *urx, double *ury) = 0;
-  virtual void getFont(Ref *fontID, double *fontSize) = 0;
-  virtual void getColor(double *red, double *green, double *blue) = 0;
-
-  // Return the resource dictionaries used to draw this field.  The
-  // returned object must be either a dictionary or an array of
-  // dictonaries.
-  virtual Object *getResources(Object *res) = 0;
-};
-
-#endif

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -21,6 +21,7 @@
 #include "gmempp.h"
 #include "GString.h"
 #include "GList.h"
+#include "Trace.h"
 #include "GlobalParams.h"
 #include "CharTypes.h"
 #include "Object.h"
@@ -146,7 +147,15 @@
           &Gfx::opSetStrokeRGBColor},
   {"S",   0, {tchkNone},
           &Gfx::opStroke},
-  {"SC",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
+  {"SC",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum},
           &Gfx::opSetStrokeColor},
   {"SCN", -33, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
 	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
@@ -240,7 +249,15 @@
           &Gfx::opSetRenderingIntent},
   {"s",   0, {tchkNone},
           &Gfx::opCloseStroke},
-  {"sc",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum},
+  {"sc",  -4, {tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum,   tchkNum,    tchkNum,    tchkNum,
+	       tchkNum},
           &Gfx::opSetFillColor},
   {"scn", -33, {tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
 	        tchkSCN,   tchkSCN,    tchkSCN,    tchkSCN,
@@ -277,6 +294,7 @@
   Ref r;
 
   if (resDict) {
+    valid = gTrue;
 
     // build font dictionary
     fonts = NULL;
@@ -312,6 +330,7 @@
     resDict->lookup("Properties", &propsDict);
 
   } else {
+    valid = gFalse;
     fonts = NULL;
     xObjDict.initNull();
     colorSpaceDict.initNull();
@@ -394,7 +413,8 @@
   return gFalse;
 }
 
-void GfxResources::lookupColorSpace(const char *name, Object *obj) {
+void GfxResources::lookupColorSpace(const char *name, Object *obj,
+				    GBool inherit) {
   GfxResources *resPtr;
 
   //~ should also test for G, RGB, and CMYK - but only in inline images (?)
@@ -411,6 +431,9 @@
       }
       obj->free();
     }
+    if (!inherit && valid) {
+      break;
+    }
   }
   obj->initNull();
 }
@@ -692,7 +715,7 @@
   aborted = gFalse;
   errCount = 0;
   numArgs = 0;
-  parser->getObj(&obj);
+  getContentObj(&obj);
   while (!obj.isEOF()) {
 
     // check for an abort
@@ -748,7 +771,7 @@
     }
 
     // grab the next object
-    parser->getObj(&obj);
+    getContentObj(&obj);
   }
   obj.free();
 
@@ -772,6 +795,15 @@
   }
 }
 
+void Gfx::getContentObj(Object *obj) {
+  parser->getObj(obj);
+  if (obj->isRef()) {
+    error(errSyntaxError, getPos(), "Indirect reference in content stream");
+    obj->free();
+    obj->initError();
+  }
+}
+
 // Returns true if successful, false on error.
 GBool Gfx::execOp(Object *cmd, Object args[], int numArgs) {
   Operator *op;
@@ -807,10 +839,9 @@
     }
   } else {
     if (numArgs > -op->numArgs) {
-      error(errSyntaxError, getPos(),
+      error(errSyntaxWarning, getPos(),
 	    "Too many ({0:d}) args to '{1:s}' operator",
 	    numArgs, name);
-      return gFalse;
     }
   }
   for (i = 0; i < numArgs; ++i) {
@@ -951,14 +982,11 @@
 }
 
 void Gfx::opSetExtGState(Object args[], int numArgs) {
-  Object obj1, obj2, obj3, objRef3, obj4, obj5;
+  Object obj1, obj2, obj3, objRef3, obj4, obj5, backdropColorObj;
   Object args2[2];
   GfxBlendMode mode;
   GBool haveFillOP;
   Function *funcs[4];
-  GfxColor backdropColor;
-  GBool haveBackdropColor;
-  GfxColorSpace *blendingColorSpace;
   GBool alpha, knockout;
   double opac;
   int i;
@@ -1167,48 +1195,20 @@
 	}
       }
       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();
+      obj2.dictLookup("BC", &backdropColorObj);
       if (obj2.dictLookup("G", &obj3)->isStream()) {
 	if (obj3.streamGetDict()->lookup("Group", &obj4)->isDict()) {
-	  blendingColorSpace = NULL;
 	  knockout = gFalse;
-	  if (!obj4.dictLookup("CS", &obj5)->isNull()) {
-	    blendingColorSpace = GfxColorSpace::parse(&obj5
-						      );
-	  }
-	  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;
-	      }
-	    }
-	  }
 	  obj2.dictLookupNF("G", &objRef3);
 	  // it doesn't make sense for softmasks to be non-isolated,
 	  // because they're blended with a backdrop color, rather
 	  // than the original backdrop
-	  doSoftMask(&obj3, &objRef3, alpha, blendingColorSpace,
-		     gTrue, knockout, funcs[0], &backdropColor);
+	  doSoftMask(&obj3, &objRef3, alpha, gTrue, knockout, funcs[0],
+		     &backdropColorObj);
 	  objRef3.free();
 	  if (funcs[0]) {
 	    delete funcs[0];
@@ -1223,6 +1223,7 @@
 	      "Invalid soft mask in ExtGState - missing group");
       }
       obj3.free();
+      backdropColorObj.free();
     } else if (!obj2.isNull()) {
       error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState");
     }
@@ -1233,9 +1234,8 @@
 }
 
 void Gfx::doSoftMask(Object *str, Object *strRef, GBool alpha,
-		     GfxColorSpace *blendingColorSpace,
 		     GBool isolated, GBool knockout,
-		     Function *transferFunc, GfxColor *backdropColor) {
+		     Function *transferFunc, Object *backdropColorObj) {
   Dict *dict, *resDict;
   double m[6], bbox[4];
   Object obj1, obj2;
@@ -1291,14 +1291,10 @@
 
   // draw it
   ++formDepth;
-  drawForm(strRef, resDict, m, bbox, gTrue, gTrue,
-	   blendingColorSpace, isolated, knockout,
-	   alpha, transferFunc, backdropColor);
+  drawForm(strRef, resDict, m, bbox, gTrue, gTrue, isolated, knockout,
+	   alpha, transferFunc, backdropColorObj);
   --formDepth;
 
-  if (blendingColorSpace) {
-    delete blendingColorSpace;
-  }
   obj1.free();
 }
 
@@ -1502,6 +1498,10 @@
   }
 }
 
+// Technically, per the PDF spec, the 'sc' operator can only be used
+// with device, CIE, and Indexed color spaces.  Some buggy PDF
+// generators use it for DeviceN, so we also allow that (same as
+// 'scn', minus the optional pattern name argument).
 void Gfx::opSetFillColor(Object args[], int numArgs) {
   GfxColor color;
   int i;
@@ -1524,6 +1524,10 @@
   out->updateFillColor(state);
 }
 
+// Technically, per the PDF spec, the 'SC' operator can only be used
+// with device, CIE, and Indexed color spaces.  Some buggy PDF
+// generators use it for DeviceN, so we also allow that (same as
+// 'SCN', minus the optional pattern name argument).
 void Gfx::opSetStrokeColor(Object args[], int numArgs) {
   GfxColor color;
   int i;
@@ -1998,6 +2002,7 @@
   state->lineTo(0, 1);
   state->closePath();
   doPatternFill(gTrue);
+  state->clearPath();
 
   restoreState();
 }
@@ -2318,35 +2323,9 @@
     state->clearPath();
   }
 
-#if 1 //~tmp: turn off anti-aliasing temporarily
-  out->setInShading(gTrue);
-#endif
+  // perform the fill
+  doShFill(shading);
 
-  // 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
-  out->setInShading(gFalse);
-#endif
-
   // restore graphics state
   restoreStateStack(savedState);
 }
@@ -2395,10 +2374,20 @@
   state->setFillColorSpace(shading->getColorSpace()->copy());
   out->updateFillColorSpace(state);
 
-#if 1 //~tmp: turn off anti-aliasing temporarily
-  out->setInShading(gTrue);
-#endif
+  // perform the fill
+  doShFill(shading);
 
+  // restore graphics state
+  restoreStateStack(savedState);
+
+  delete shading;
+}
+
+void Gfx::doShFill(GfxShading *shading) {
+  if (out->shadedFill(state, shading)) {
+    return;
+  }
+
   // do shading type-specific operations
   switch (shading->getType()) {
   case 1:
@@ -2419,15 +2408,6 @@
     doPatchMeshShFill((GfxPatchMeshShading *)shading);
     break;
   }
-
-#if 1 //~tmp: turn off anti-aliasing temporarily
-  out->setInShading(gFalse);
-#endif
-
-  // restore graphics state
-  restoreStateStack(savedState);
-
-  delete shading;
 }
 
 void Gfx::doFunctionShFill(GfxFunctionShading *shading) {
@@ -2434,11 +2414,6 @@
   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]);
@@ -2567,11 +2542,6 @@
   GfxColor colors[axialSplits];
   int abortCheckCounter, nComps, i, j, k;
 
-  if (out->useShadedFills() &&
-      out->axialShadedFill(state, shading)) {
-    return;
-  }
-
   // get the clip region bbox
   state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax);
 
@@ -2872,11 +2842,6 @@
   double theta, alpha, angle, t;
   int abortCheckCounter, ia, ib, k, n;
 
-  if (out->useShadedFills() &&
-      out->radialShadedFill(state, shading)) {
-    return;
-  }
-
   // get the shading info
   shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
   t0 = shading->getDomain0();
@@ -3515,8 +3480,8 @@
     }
     for (i = 0; i < shading->getNComps(); ++i) {
       patch00.color[0][0][i] = patch->color[0][0][i];
-      patch00.color[0][1][i] = 0.5  * (patch->color[0][0][i] +
-				       patch->color[0][1][i]);
+      patch00.color[0][1][i] = 0.5 * (patch->color[0][0][i] +
+				      patch->color[0][1][i]);
       patch01.color[0][0][i] = patch00.color[0][1][i];
       patch01.color[0][1][i] = patch->color[0][1][i];
       patch01.color[1][1][i] = 0.5 * (patch->color[0][1][i] +
@@ -3905,6 +3870,19 @@
       //~ the CTM concat values here are wrong (but never used)
       out->updateCTM(state, 1, 0, 0, 1, 0, 0);
       state->transformDelta(dx, dy, &ddx, &ddy);
+#if 0
+      // The PDF spec says: "the graphics state shall be inherited
+      // from the environment of the text-showing operator that caused
+      // the [Type 3] glyph description to be invoked".  However,
+      // Acrobat apparently copies the fill color to the stroke color.
+      // It looks like Ghostscript does the same.  (This is only
+      // relevant for uncached Type 3 glyphs.)  Uncomment this block
+      // to make Xpdf mimic Adobe (in violation of the PDF spec).
+      state->setStrokeColorSpace(state->getFillColorSpace()->copy());
+      out->updateStrokeColorSpace(state);
+      state->setStrokeColor(state->getFillColor());
+      out->updateStrokeColor(state);
+#endif
       if (!out->beginType3Char(state, curX + riseX, curY + riseY, ddx, ddy,
 			       code, u, uLen)) {
 	((Gfx8BitFont *)font)->getCharProcNF(code, &charProcRef);
@@ -4149,7 +4127,7 @@
   GBool mask, invert;
   GfxColorSpace *colorSpace, *maskColorSpace;
   GfxImageColorMap *colorMap, *maskColorMap;
-  Object maskObj, smaskObj;
+  Object maskObj, smaskObj, maskRef;
   GBool haveColorKeyMask, haveExplicitMask, haveSoftMask, haveMatte;
   int maskColors[2*gfxColorMaxComps];
   int maskWidth, maskHeight;
@@ -4591,15 +4569,20 @@
     // draw it
     } else {
       if (haveSoftMask) {
+	dict->lookupNF("Mask", &maskRef);
 	out->drawSoftMaskedImage(state, ref, str, width, height, colorMap,
-				 maskStr, maskWidth, maskHeight, maskColorMap,
+				 &maskRef, maskStr, maskWidth, maskHeight,
+				 maskColorMap,
 				 haveMatte ? matte : (double *)NULL,
 				 interpolate);
+	maskRef.free();
 	delete maskColorMap;
       } else if (haveExplicitMask) {
+	dict->lookupNF("Mask", &maskRef);
 	out->drawMaskedImage(state, ref, str, width, height, colorMap,
-			     maskStr, maskWidth, maskHeight, maskInvert,
-			     interpolate);
+			     &maskRef, maskStr, maskWidth, maskHeight,
+			     maskInvert, interpolate);
+	maskRef.free();
       } else {
 	out->drawImage(state, ref, str, width, height, colorMap,
 		       haveColorKeyMask ? maskColors : (int *)NULL, inlineImg,
@@ -4642,7 +4625,6 @@
 void Gfx::doForm(Object *strRef, Object *str) {
   Dict *dict;
   GBool transpGroup, isolated, knockout;
-  GfxColorSpace *blendingColorSpace;
   Object matrixObj, bboxObj;
   double m[6], bbox[4];
   Object resObj;
@@ -4705,15 +4687,9 @@
 
   // 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();
       }
@@ -4729,13 +4705,9 @@
 
   // draw it
   ++formDepth;
-  drawForm(strRef, resDict, m, bbox,
-	   transpGroup, gFalse, blendingColorSpace, isolated, knockout);
+  drawForm(strRef, resDict, m, bbox, transpGroup, gFalse, isolated, knockout);
   --formDepth;
 
-  if (blendingColorSpace) {
-    delete blendingColorSpace;
-  }
   resObj.free();
 }
 
@@ -4742,12 +4714,14 @@
 void Gfx::drawForm(Object *strRef, Dict *resDict,
 		   double *matrix, double *bbox,
 		   GBool transpGroup, GBool softMask,
-		   GfxColorSpace *blendingColorSpace,
 		   GBool isolated, GBool knockout,
 		   GBool alpha, Function *transferFunc,
-		   GfxColor *backdropColor) {
+		   Object *backdropColorObj) {
   Parser *oldParser;
   GfxState *savedState;
+  GfxColorSpace *blendingColorSpace;
+  GfxColor backdropColor;
+  Object strObj, groupAttrsObj, csObj, obj1;
   double oldBaseMatrix[6];
   int i;
 
@@ -4779,7 +4753,23 @@
   out->clip(state);
   state->clearPath();
 
+  blendingColorSpace = NULL;
   if (softMask || transpGroup) {
+    // get the blending color space
+    // NB: this must be done AFTER pushing the resource dictionary,
+    //     so that any Default*** color spaces are available
+    strRef->fetch(xref, &strObj);
+    if (strObj.streamGetDict()->lookup("Group", &groupAttrsObj)->isDict()) {
+      if (!groupAttrsObj.dictLookup("CS", &csObj)->isNull()) {
+	blendingColorSpace = GfxColorSpace::parse(&csObj
+						  );
+      }
+      csObj.free();
+    }
+    groupAttrsObj.free();
+    strObj.free();
+
+    traceBegin(oldBaseMatrix, softMask ? "begin soft mask" : "begin t-group");
     if (state->getBlendMode() != gfxBlendNormal) {
       state->setBlendMode(gfxBlendNormal);
       out->updateBlendMode(state);
@@ -4831,11 +4821,34 @@
   popResources();
 
   if (softMask) {
-    out->setSoftMask(state, bbox, alpha, transferFunc, backdropColor);
+    for (i = 0; i < gfxColorMaxComps; ++i) {
+      backdropColor.c[i] = 0;
+    }
+    if (backdropColorObj->isArray()) {
+      for (i = 0;
+	   i < backdropColorObj->arrayGetLength() && i < gfxColorMaxComps;
+	   ++i) {
+	backdropColorObj->arrayGet(i, &obj1);
+	if (obj1.isNum()) {
+	  backdropColor.c[i] = dblToCol(obj1.getNum());
+	}
+	obj1.free();
+      }
+    } else if (blendingColorSpace) {
+      blendingColorSpace->getDefaultColor(&backdropColor);
+    }
+    //~ else: need to get the parent or default color space (?)
+    out->setSoftMask(state, bbox, alpha, transferFunc, &backdropColor);
+    traceEnd(oldBaseMatrix, "end soft mask");
   } else if (transpGroup) {
     out->paintTransparencyGroup(state, bbox);
+    traceEnd(oldBaseMatrix, "end t-group");
   }
 
+  if (blendingColorSpace) {
+    delete blendingColorSpace;
+  }
+
   return;
 }
 
@@ -4911,7 +4924,7 @@
 
   // build dictionary
   dict.initDict(xref);
-  parser->getObj(&obj);
+  getContentObj(&obj);
   while (!obj.isCmd("ID") && !obj.isEOF()) {
     if (!obj.isName()) {
       error(errSyntaxError, getPos(),
@@ -4920,14 +4933,19 @@
     } else {
       key = copyString(obj.getName());
       obj.free();
-      parser->getObj(&obj);
-      if (obj.isEOF() || obj.isError()) {
+      getContentObj(&obj);
+      if (obj.isEOF()) {
 	gfree(key);
 	break;
       }
-      dict.dictAdd(key, &obj);
+      if (obj.isError()) {
+	gfree(key);
+	obj.free();
+      } else {
+	dict.dictAdd(key, &obj);
+      }
     }
-    parser->getObj(&obj);
+    getContentObj(&obj);
   }
   if (obj.isEOF()) {
     error(errSyntaxError, getPos(), "End of file in inline image");
@@ -5108,7 +5126,7 @@
 
     // get the form bounding box
     dict->lookup("BBox", &bboxObj);
-    if (!bboxObj.isArray()) {
+    if (!bboxObj.isArray() || bboxObj.arrayGetLength() != 4) {
       error(errSyntaxError, getPos(), "Bad form bounding box");
       bboxObj.free();
       str.free();
@@ -5116,7 +5134,11 @@
     }
     for (i = 0; i < 4; ++i) {
       bboxObj.arrayGet(i, &obj1);
-      bbox[i] = obj1.getNum();
+      if (obj1.isNum()) {
+	bbox[i] = obj1.getNum();
+      } else {
+	bbox[i] = 0;
+      }
       obj1.free();
     }
     bboxObj.free();

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Gfx.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -76,7 +76,7 @@
   GfxFont *lookupFontByRef(Ref ref);
   GBool lookupXObject(const char *name, Object *obj);
   GBool lookupXObjectNF(const char *name, Object *obj);
-  void lookupColorSpace(const char *name, Object *obj);
+  void lookupColorSpace(const char *name, Object *obj, GBool inherit = gTrue);
   GfxPattern *lookupPattern(const char *name
 			    );
   GfxShading *lookupShading(const char *name
@@ -88,6 +88,7 @@
 
 private:
 
+  GBool valid;
   GfxFontDict *fonts;
   Object xObjDict;
   Object colorSpaceDict;
@@ -164,10 +165,9 @@
 
   void drawForm(Object *strRef, 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);
+		Object *backdropColorObj = NULL);
 
   // Take all of the content stream stack entries from <oldGfx>.  This
   // is useful when creating a new Gfx object to handle a pattern,
@@ -214,6 +214,7 @@
 
   GBool checkForContentStreamLoop(Object *ref);
   void go(GBool topLevel);
+  void getContentObj(Object *obj);
   GBool execOp(Object *cmd, Object args[], int numArgs);
   Operator *findOp(char *name);
   GBool checkArg(Object *arg, TchkType type);
@@ -231,9 +232,8 @@
   void opSetLineWidth(Object args[], int numArgs);
   void opSetExtGState(Object args[], int numArgs);
   void doSoftMask(Object *str, Object *strRef, GBool alpha,
-		  GfxColorSpace *blendingColorSpace,
 		  GBool isolated, GBool knockout,
-		  Function *transferFunc, GfxColor *backdropColor);
+		  Function *transferFunc, Object *backdropColorObj);
   void opSetRenderingIntent(Object args[], int numArgs);
   GfxRenderingIntent parseRenderingIntent(const char *name);
 
@@ -280,6 +280,7 @@
   void doShadingPatternFill(GfxShadingPattern *sPat,
 			    GBool stroke, GBool eoFill, GBool text);
   void opShFill(Object args[], int numArgs);
+  void doShFill(GfxShading *shading);
   void doFunctionShFill(GfxFunctionShading *shading);
   void doFunctionShFill1(GfxFunctionShading *shading,
 			 double x0, double y0,

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -467,6 +467,9 @@
       } else {
 	t2 = 0;
       }
+      if (t != 0 && t < 1.9) {
+	declaredAscent = t;
+      }
       // if both Ascent and CapHeight are set, use the smaller one
       // (because the most common problem is that Ascent is too large)
       if (t2 != 0 && (t == 0 || t2 < t)) {
@@ -851,19 +854,22 @@
   }
   str = obj2.getStream();
 
-  size = 0;
-  buf = NULL;
+  size = 4096;
+  buf = (char *)gmalloc(size);
+  *len = 0;
   str->reset();
   do {
-    if (size > INT_MAX - 4096) {
-      error(errSyntaxError, -1, "Embedded font file is too large");
-      break;
+    if (*len > size - 4096) {
+      if (size > INT_MAX / 2) {
+	error(errSyntaxError, -1, "Embedded font file is too large");
+	break;
+      }
+      size *= 2;
+      buf = (char *)grealloc(buf, size);
     }
-    buf = (char *)grealloc(buf, size + 4096);
-    n = str->getBlock(buf + size, 4096);
-    size += n;
+    n = str->getBlock(buf + *len, 4096);
+    *len += n;
   } while (n == 4096);
-  *len = size;
   str->close();
 
   obj2.free();
@@ -947,6 +953,7 @@
     missingWidth = builtinFont->missingWidth;
     ascent = 0.001 * builtinFont->ascent;
     descent = 0.001 * builtinFont->descent;
+    declaredAscent = ascent;
     fontBBox[0] = 0.001 * builtinFont->bbox[0];
     fontBBox[1] = 0.001 * builtinFont->bbox[1];
     fontBBox[2] = 0.001 * builtinFont->bbox[2];
@@ -955,6 +962,7 @@
     missingWidth = 0;
     ascent = 0.75;
     descent = -0.25;
+    declaredAscent = ascent;
     fontBBox[0] = fontBBox[1] = fontBBox[2] = fontBBox[3] = 0;
   }
 
@@ -966,6 +974,7 @@
   if (builtinFont) {
     ascent = 0.001 * builtinFont->ascent;
     descent = 0.001 * builtinFont->descent;
+    declaredAscent = ascent;
     fontBBox[0] = 0.001 * builtinFont->bbox[0];
     fontBBox[1] = 0.001 * builtinFont->bbox[1];
     fontBBox[2] = 0.001 * builtinFont->bbox[2];
@@ -1459,13 +1468,20 @@
   }
 
   // reverse map the char names through MacRomanEncoding, then map the
-  // char codes through the cmap
+  // char codes through the cmap; fall back on Unicode if that doesn't
+  // work
   if (useMacRoman) {
     for (i = 0; i < 256; ++i) {
       if ((charName = enc[i])) {
 	if ((code = globalParams->getMacRomanCharCode(charName))) {
 	  map[i] = ff->mapCodeToGID(cmap, code);
+	} else if (unicodeCmap >= 0 &&
+		   (u = globalParams->mapNameToUnicode(charName))) {
+	  map[i] = ff->mapCodeToGID(unicodeCmap, u);
 	}
+      } else if (unicodeCmap >= 0 &&
+		 (n = ctu->mapToUnicode((CharCode)i, &u, 1))) {
+	map[i] = ff->mapCodeToGID(cmap, u);
       } else {
 	map[i] = -1;
       }
@@ -1631,6 +1647,7 @@
   missingWidth = 0;
   ascent = 0.95;
   descent = -0.35;
+  declaredAscent = ascent;
   fontBBox[0] = fontBBox[1] = fontBBox[2] = fontBBox[3] = 0;
   collection = NULL;
   cMap = NULL;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxFont.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -185,6 +185,7 @@
   // Return the ascent and descent values.
   double getAscent() { return ascent; }
   double getDescent() { return descent; }
+  double getDeclaredAscent() { return declaredAscent; }
 
   // Return the writing mode (0=horizontal, 1=vertical).
   virtual int getWMode() { return 0; }
@@ -234,6 +235,7 @@
   double missingWidth;		// "default" width
   double ascent;		// max height above baseline
   double descent;		// max depth below baseline
+  double declaredAscent;	// ascent value, before munging
   GBool hasToUnicode;		// true if the font has a ToUnicode map
   GBool ok;
 };

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -101,6 +101,7 @@
 
 GfxColorSpace::GfxColorSpace() {
   overprintMask = 0x0f;
+  defaultColorSpace = gFalse;
 }
 
 GfxColorSpace::~GfxColorSpace() {
@@ -2683,6 +2684,11 @@
 	  "Missing or invalid BitsPerCoordinate in shading dictionary");
     goto err2;
   }
+  if (coordBits <= 0 || coordBits > 32) {
+    error(errSyntaxError, -1,
+	  "Invalid BitsPerCoordinate in shading dictionary");
+    goto err2;
+  }
   obj1.free();
   if (dict->lookup("BitsPerComponent", &obj1)->isInt()) {
     compBits = obj1.getInt();
@@ -2691,6 +2697,11 @@
 	  "Missing or invalid BitsPerComponent in shading dictionary");
     goto err2;
   }
+  if (compBits <= 0 || compBits > 16) {
+    error(errSyntaxError, -1,
+	  "Invalid BitsPerComponent in shading dictionary");
+    goto err2;
+  }
   obj1.free();
   flagBits = vertsPerRow = 0; // make gcc happy
   if (typeA == 4) {
@@ -2701,6 +2712,10 @@
 	    "Missing or invalid BitsPerFlag in shading dictionary");
       goto err2;
     }
+    if (flagBits < 2 || flagBits > 8) {
+      error(errSyntaxError, -1, "Invalid BitsPerFlag in shading dictionary");
+      goto err2;
+    }
     obj1.free();
   } else {
     if (dict->lookup("VerticesPerRow", &obj1)->isInt()) {
@@ -2711,6 +2726,11 @@
       goto err2;
     }
     obj1.free();
+    if (vertsPerRow < 2) {
+      error(errSyntaxError, -1,
+	    "Invalid VerticesPerRow in shading dictionary");
+      goto err2;
+    }
   }
   if (dict->lookup("Decode", &obj1)->isArray() &&
       obj1.arrayGetLength() >= 6) {
@@ -2911,6 +2931,34 @@
   }
 }
 
+void GfxGouraudTriangleShading::getBBox(double *xMin, double *yMin,
+					double *xMax, double *yMax) {
+  double xxMin = 0;
+  double yyMin = 0;
+  double xxMax = 0;
+  double yyMax = 0;
+  if (nVertices > 0) {
+    xxMin = xxMax = vertices[0].x;
+    yyMin = yyMax = vertices[0].y;
+  }
+  for (int i = 1; i < nVertices; ++i) {
+    if (vertices[i].x < xxMin) {
+      xxMin = vertices[i].x;
+    } else if (vertices[i].x > xxMax) {
+      xxMax = vertices[i].x;
+    }
+    if (vertices[i].y < yyMin) {
+      yyMin = vertices[i].y;
+    } else if (vertices[i].y > yyMax) {
+      yyMax = vertices[i].y;
+    }
+  }
+  *xMin = xxMin;
+  *yMin = yyMin;
+  *xMax = xxMax;
+  *yMax = yyMax;
+}
+
 void GfxGouraudTriangleShading::getColor(double *in, GfxColor *out) {
   double c[gfxColorMaxComps];
   int i;
@@ -2996,6 +3044,10 @@
   Object obj1, obj2;
   int i, j;
 
+  nPatchesA = 0;
+  patchesA = NULL;
+  patchesSize = 0;
+
   if (dict->lookup("BitsPerCoordinate", &obj1)->isInt()) {
     coordBits = obj1.getInt();
   } else {
@@ -3003,6 +3055,11 @@
 	  "Missing or invalid BitsPerCoordinate in shading dictionary");
     goto err2;
   }
+  if (coordBits <= 0 || coordBits > 32) {
+    error(errSyntaxError, -1,
+	  "Invalid BitsPerCoordinate in shading dictionary");
+    goto err2;
+  }
   obj1.free();
   if (dict->lookup("BitsPerComponent", &obj1)->isInt()) {
     compBits = obj1.getInt();
@@ -3011,6 +3068,11 @@
 	  "Missing or invalid BitsPerComponent in shading dictionary");
     goto err2;
   }
+  if (compBits <= 0 || compBits > 16) {
+    error(errSyntaxError, -1,
+	  "Invalid BitsPerComponent in shading dictionary");
+    goto err2;
+  }
   obj1.free();
   if (dict->lookup("BitsPerFlag", &obj1)->isInt()) {
     flagBits = obj1.getInt();
@@ -3019,6 +3081,10 @@
 	  "Missing or invalid BitsPerFlag in shading dictionary");
     goto err2;
   }
+  if (flagBits < 2 || flagBits > 8) {
+    error(errSyntaxError, -1, "Invalid BitsPerFlag in shading dictionary");
+    goto err2;
+  }
   obj1.free();
   if (dict->lookup("Decode", &obj1)->isArray() &&
       obj1.arrayGetLength() >= 6) {
@@ -3076,9 +3142,6 @@
   }
   obj1.free();
 
-  nPatchesA = 0;
-  patchesA = NULL;
-  patchesSize = 0;
   bitBuf = new GfxShadingBitBuf(str);
   while (1) {
     if (!bitBuf->getBits(flagBits, &flag)) {
@@ -3506,6 +3569,9 @@
  err2:
   obj1.free();
  err1:
+  if (patchesA) {
+    gfree(patchesA);
+  }
   return NULL;
 }
 
@@ -3513,6 +3579,38 @@
   return new GfxPatchMeshShading(this);
 }
 
+void GfxPatchMeshShading::getBBox(double *xMin, double *yMin,
+				  double *xMax, double *yMax) {
+  double xxMin = 0;
+  double yyMin = 0;
+  double xxMax = 0;
+  double yyMax = 0;
+  if (nPatches > 0) {
+    xxMin = patches[0].x[0][0];
+    yyMin = patches[0].y[0][0];
+  }
+  for (int i = 0; i < nPatches; ++i) {
+    for (int j = 0; j < 4; ++j) {
+      for (int k = 0; k < 4; ++k) {
+	if (patches[i].x[j][k] < xxMin) {
+	  xxMin = patches[i].x[j][k];
+	} else if (patches[i].x[j][k] > xxMax) {
+	  xxMax = patches[i].x[j][k];
+	}
+	if (patches[i].y[j][k] < yyMin) {
+	  yyMin = patches[i].y[j][k];
+	} else if (patches[i].y[j][k] > yyMax) {
+	  yyMax = patches[i].y[j][k];
+	}
+      }
+    }
+  }
+  *xMin = xxMin;
+  *yMin = yyMin;
+  *xMax = xxMax;
+  *yMax = yyMax;
+}
+
 void GfxPatchMeshShading::getColor(double *in, GfxColor *out) {
   double c[gfxColorMaxComps];
   int i;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GfxState.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -207,6 +207,11 @@
   // Return the color space's overprint mask.
   Guint getOverprintMask() { return overprintMask; }
 
+  // Return true if this color space object is the result of
+  // substituting a DefaultGray/RGB/CMYK color space for
+  // DeviceGray/RGB/CMYK.
+  GBool isDefaultColorSpace() { return defaultColorSpace; }
+
   // Return the number of color space modes
   static int getNumColorSpaceModes();
 
@@ -216,6 +221,7 @@
 protected:
 
   Guint overprintMask;
+  GBool defaultColorSpace;
 };
 
 //------------------------------------------------------------------------
@@ -877,6 +883,7 @@
   void getTriangle(int i, double *x0, double *y0, double *color0,
 		   double *x1, double *y1, double *color1,
 		   double *x2, double *y2, double *color2);
+  void getBBox(double *xMin, double *yMin, double *xMax, double *yMax);
   void getColor(double *in, GfxColor *out);
 
 private:
@@ -916,6 +923,7 @@
   int getNComps() { return nComps; }
   int getNPatches() { return nPatches; }
   GfxPatch *getPatch(int i) { return &patches[i]; }
+  void getBBox(double *xMin, double *yMin, double *xMax, double *yMax);
   void getColor(double *in, GfxColor *out);
 
 private:

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -178,24 +178,21 @@
 public:
 
   GString *name;
-  GBool bold;
-  GBool italic;
   GString *path;
   SysFontType type;
   int fontNum;			// for TrueType collections
 
-  SysFontInfo(GString *nameA, GBool boldA, GBool italicA,
-	      GString *pathA, SysFontType typeA, int fontNumA);
+  SysFontInfo(GString *nameA, GString *pathA, SysFontType typeA, int fontNumA);
   ~SysFontInfo();
-  GBool match(SysFontInfo *fi);
-  GBool match(GString *nameA, GBool boldA, GBool italicA);
+  GString *mungeName1(GString *in);
+  GString *mungeName2(GString *in);
+  void mungeName3(GString *name, GBool *bold, GBool *italic);
+  int match(GString *nameA);
 };
 
-SysFontInfo::SysFontInfo(GString *nameA, GBool boldA, GBool italicA,
-			 GString *pathA, SysFontType typeA, int fontNumA) {
+SysFontInfo::SysFontInfo(GString *nameA, GString *pathA,
+			 SysFontType typeA, int fontNumA) {
   name = nameA;
-  bold = boldA;
-  italic = italicA;
   path = pathA;
   type = typeA;
   fontNum = fontNumA;
@@ -206,16 +203,152 @@
   delete path;
 }
 
-GBool SysFontInfo::match(SysFontInfo *fi) {
-  return !strcasecmp(name->getCString(), fi->name->getCString()) &&
-         bold == fi->bold && italic == fi->italic;
+// Remove space/comma/dash/underscore chars.
+// Uppercase the name.
+GString *SysFontInfo::mungeName1(GString *in) {
+  GString *out = new GString();
+  for (char *p = in->getCString(); *p; ++p) {
+    if (*p == ' ' || *p == ',' || *p == '-' || *p == '_') {
+      // skip
+    } else if (*p >= 'a' && *p <= 'z') {
+      out->append((char)(*p & 0xdf));
+    } else {
+      out->append(*p);
+    }
+  }
+  return out;
 }
 
-GBool SysFontInfo::match(GString *nameA, GBool boldA, GBool italicA) {
-  return !strcasecmp(name->getCString(), nameA->getCString()) &&
-         bold == boldA && italic == italicA;
+// Remove trailing "Identity-H"/"Identity-V" from the name.
+// Split the name into tokens at space/comma/dash/underscore.
+// Remove trailing "MT" or "BT" from tokens.
+// Remove trailing "PS" and "WGL4" from tokens.
+// Uppercase each token.
+// Concatenate tokens (dropping the space/comma/dash chars).
+GString *SysFontInfo::mungeName2(GString *in) {
+  GString *out = new GString();
+  char *p0 = in->getCString();
+  while (*p0) {
+    if (!strcmp(p0, "Identity-H") || !strcmp(p0, "Identity-V")) {
+      break;
+    }
+    char *p1;
+    for (p1 = p0 + 1;
+	 *p1 && *p1 != ' ' && *p1 != ',' && *p1 != '-' && *p1 != '_';
+	 ++p1) ;
+    char *p2 = p1;
+    if (p2 - p0 >= 2 && (p2[-2] == 'B' || p2[-2] == 'M') && p2[-1] == 'T') {
+      p2 -= 2;
+    }
+    if (p2 - p0 >= 2 && p2[-2] == 'P' && p2[-1] == 'S') {
+      p2 -= 2;
+    }
+    if (p2 - p0 >= 4 &&
+	p2[-4] == 'W' && p2[-3] == 'G' && p2[-2] == 'L' && p2[-1] == '4') {
+      p2 -= 4;
+    }
+    for (; p0 < p2; ++p0) {
+      if (*p0 >= 'a' && *p0 <= 'z') {
+	out->append((char)(*p0 & 0xdf));
+      } else {
+	out->append(*p0);
+      }
+    }
+    for (p0 = p1; *p0 == ' ' || *p0 == ',' || *p0 == '-' || *p0 == '_'; ++p0) ;
+  }
+  return out;
 }
 
+// Remove trailing bold/italic/regular/roman tags from the name.
+// (Note: the names have already been uppercased by mungeName1/2.)
+void SysFontInfo::mungeName3(GString *name, GBool *bold, GBool *italic) {
+  *bold = gFalse;
+  *italic = gFalse;
+  int n = name->getLength();
+  while (1) {
+    if (n >= 4 && !strcmp(name->getCString() + n - 4, "BOLD")) {
+      name->del(n - 4, 4);
+      n -= 4;
+      *bold = gTrue;
+    } else if (n >= 6 && !strcmp(name->getCString() + n - 6, "ITALIC")) {
+      name->del(n - 6, 6);
+      n -= 6;
+      *italic = gTrue;
+    } else if (n >= 7 && !strcmp(name->getCString() + n - 7, "REGULAR")) {
+      name->del(n - 7, 7);
+      n -= 7;
+    } else if (n >= 5 && !strcmp(name->getCString() + n - 5, "ROMAN")) {
+      name->del(n - 5, 5);
+      n -= 5;
+    } else {
+      break;
+    }
+  }
+}
+
+// Returns a score indicating how well this font matches [nameA].  A
+// higher score is better.  Zero indicates a non-match.
+int SysFontInfo::match(GString *nameA) {
+  // fast fail: check if the first two letters match
+  if (strncasecmp(name->getCString(), nameA->getCString(), 2)) {
+    return 0;
+  }
+
+  GString *pdfName1 = mungeName1(nameA);
+  GString *sysName1 = mungeName1(name);
+  if (!pdfName1->cmp(sysName1)) {
+    delete pdfName1;
+    delete sysName1;
+    return 8;
+  }
+
+  GString *pdfName2 = mungeName2(nameA);
+  GString *sysName2 = mungeName2(name);
+  if (!pdfName2->cmp(sysName2)) {
+    delete pdfName1;
+    delete sysName1;
+    delete pdfName2;
+    delete sysName2;
+    return 7;
+  }
+
+  GBool pdfBold1, pdfItalic1, sysBold1, sysItalic1;
+  mungeName3(pdfName1, &pdfBold1, &pdfItalic1);
+  mungeName3(sysName1, &sysBold1, &sysItalic1);
+  int eq1 = !pdfName1->cmp(sysName1);
+
+  GBool pdfBold2, pdfItalic2, sysBold2, sysItalic2;
+  mungeName3(pdfName2, &pdfBold2, &pdfItalic2);
+  mungeName3(sysName2, &sysBold2, &sysItalic2);
+  int eq2 =!pdfName2->cmp(sysName2);
+
+  delete pdfName1;
+  delete sysName1;
+  delete pdfName2;
+  delete sysName2;
+
+  if (eq1 && pdfBold1 == sysBold1 && pdfItalic1 == sysItalic1) {
+    return 6;
+  }
+  if (eq2 && pdfBold2 == sysBold2 && pdfItalic2 == sysItalic2) {
+    return 5;
+  }
+  if (eq1 && pdfItalic1 == sysItalic1) {
+    return 4;
+  }
+  if (eq2 && pdfItalic2 == sysItalic2) {
+    return 3;
+  }
+  if (eq1) {
+    return 2;
+  }
+  if (eq2) {
+    return 1;
+  }
+
+  return 0;
+}
+
 //------------------------------------------------------------------------
 // SysFontList
 //------------------------------------------------------------------------
@@ -254,102 +387,17 @@
 }
 
 SysFontInfo *SysFontList::find(GString *name) {
-  GString *name2;
-  GBool bold, italic;
-  SysFontInfo *fi;
-  char c;
-  int n, i;
-
-  name2 = name->copy();
-
-  // remove space, comma, dash chars
-  i = 0;
-  while (i < name2->getLength()) {
-    c = name2->getChar(i);
-    if (c == ' ' || c == ',' || c == '-') {
-      name2->del(i);
-    } else {
-      ++i;
+  SysFontInfo *match = NULL;
+  int score = 0;
+  for (int i = 0; i < fonts->getLength(); ++i) {
+    SysFontInfo *fi = (SysFontInfo *)fonts->get(i);
+    int s = fi->match(name);
+    if (s > score) {
+      match = fi;
+      score = s;
     }
   }
-  n = name2->getLength();
-
-  // font names like "Arial-BoldMT,Bold" are occasionally used,
-  // so run this loop twice
-  bold = italic = gFalse;
-  for (i = 0; i < 2; ++i) {
-
-    // remove trailing "MT" (Foo-MT, Foo-BoldMT, etc.)
-    if (n > 2 && !strcmp(name2->getCString() + n - 2, "MT")) {
-      name2->del(n - 2, 2);
-      n -= 2;
-    }
-
-    // look for "Regular"
-    if (n > 7 && !strcmp(name2->getCString() + n - 7, "Regular")) {
-      name2->del(n - 7, 7);
-      n -= 7;
-    }
-
-    // look for "Italic"
-    if (n > 6 && !strcmp(name2->getCString() + n - 6, "Italic")) {
-      name2->del(n - 6, 6);
-      italic = gTrue;
-      n -= 6;
-    }
-
-    // look for "Bold"
-    if (n > 4 && !strcmp(name2->getCString() + n - 4, "Bold")) {
-      name2->del(n - 4, 4);
-      bold = gTrue;
-      n -= 4;
-    }
-  }
-
-  // remove trailing "PS"
-  if (n > 2 && !strcmp(name2->getCString() + n - 2, "PS")) {
-    name2->del(n - 2, 2);
-    n -= 2;
-  }
-
-  // remove trailing "IdentityH"
-  if (n > 9 && !strcmp(name2->getCString() + n - 9, "IdentityH")) {
-    name2->del(n - 9, 9);
-    n -= 9;
-  }
-
-  // search for the font
-  fi = NULL;
-  for (i = 0; i < fonts->getLength(); ++i) {
-    fi = (SysFontInfo *)fonts->get(i);
-    if (fi->match(name2, bold, italic)) {
-      break;
-    }
-    fi = NULL;
-  }
-  if (!fi && bold) {
-    // try ignoring the bold flag
-    for (i = 0; i < fonts->getLength(); ++i) {
-      fi = (SysFontInfo *)fonts->get(i);
-      if (fi->match(name2, gFalse, italic)) {
-	break;
-      }
-      fi = NULL;
-    }
-  }
-  if (!fi && (bold || italic)) {
-    // try ignoring the bold and italic flags
-    for (i = 0; i < fonts->getLength(); ++i) {
-      fi = (SysFontInfo *)fonts->get(i);
-      if (fi->match(name2, gFalse, gFalse)) {
-	break;
-      }
-      fi = NULL;
-    }
-  }
-
-  delete name2;
-  return fi;
+  return match;
 }
 
 #ifdef _WIN32
@@ -421,50 +469,15 @@
 
 SysFontInfo *SysFontList::makeWindowsFont(char *name, int fontNum,
 					  char *path) {
-  int n;
-  GBool bold, italic;
-  GString *s;
-  char c;
-  int i;
-  SysFontType type;
+  int n = (int)strlen(name);
 
-  n = (int)strlen(name);
-  bold = italic = gFalse;
-
-  // remove trailing ' (TrueType)'
-  if (n > 11 && !strncmp(name + n - 11, " (TrueType)", 11)) {
+  // remove trailing ' (TrueType)' or ' (OpenType)'
+  if (n > 11 && (!strncmp(name + n - 11, " (TrueType)", 11) ||
+		 !strncmp(name + n - 11, " (OpenType)", 11))) {
     n -= 11;
   }
 
-  // remove trailing ' Italic'
-  if (n > 7 && !strncmp(name + n - 7, " Italic", 7)) {
-    n -= 7;
-    italic = gTrue;
-  }
-
-  // remove trailing ' Bold'
-  if (n > 5 && !strncmp(name + n - 5, " Bold", 5)) {
-    n -= 5;
-    bold = gTrue;
-  }
-
-  // remove trailing ' Regular'
-  if (n > 8 && !strncmp(name + n - 8, " Regular", 8)) {
-    n -= 8;
-  }
-
-  //----- normalize the font name
-  s = new GString(name, n);
-  i = 0;
-  while (i < s->getLength()) {
-    c = s->getChar(i);
-    if (c == ' ' || c == ',' || c == '-') {
-      s->del(i);
-    } else {
-      ++i;
-    }
-  }
-
+  SysFontType type;
   if (!strcasecmp(path + strlen(path) - 4, ".ttc")) {
     type = sysFontTTC;
   } else if (!strcasecmp(path + strlen(path) - 4, ".otf")) {
@@ -472,31 +485,32 @@
   } else {
     type = sysFontTTF;
   }
-  return new SysFontInfo(s, bold, italic, new GString(path), type, fontNum);
+
+  return new SysFontInfo(new GString(name, n), new GString(path),
+			 type, fontNum);
 }
-#endif
+#endif // _WIN32
 
 #if HAVE_FONTCONFIG
 void SysFontList::scanFontconfigFonts() {
+  FcConfig *cfg;
   FcPattern *pattern;
   FcObjectSet *objSet;
   FcFontSet *fontSet;
-  char *family, *file, *styleLang, *style;
-  GString *family2;
+  char *name, *file;
   SysFontType type;
-  GBool bold, italic;
-  char c;
-  int fontNum, i, j, n;
+  int fontNum, i, n;
 
-  FcInit();
+  if (!(cfg = FcInitLoadConfigAndFonts())) {
+    return;
+  }
 
   pattern = FcPatternBuild(NULL,
 			   FC_OUTLINE, FcTypeBool, FcTrue,
 			   FC_SCALABLE, FcTypeBool, FcTrue,
 			   NULL);
-  objSet = FcObjectSetBuild(FC_FAMILY, FC_STYLE, FC_STYLELANG,
-			    FC_FILE, FC_INDEX, NULL);
-  fontSet = FcFontList(NULL, pattern, objSet);
+  objSet = FcObjectSetBuild(FC_FULLNAME, FC_FILE, FC_INDEX, NULL);
+  fontSet = FcFontList(cfg, pattern, objSet);
   FcPatternDestroy(pattern);
   FcObjectSetDestroy(objSet);
 
@@ -528,54 +542,21 @@
 	fontNum = 0;
       }
 
-      //--- font family
-      if (FcPatternGetString(fontSet->fonts[i], FC_FAMILY, 0,
-			     (FcChar8 **)&family)
+      //--- font name
+      if (FcPatternGetString(fontSet->fonts[i], FC_FULLNAME, 0,
+			     (FcChar8 **)&name)
 	  != FcResultMatch) {
 	continue;
       }
 
-      //----- normalize the font name
-      family2 = new GString(family);
-      j = 0;
-      while (j < family2->getLength()) {
-	c = family2->getChar(j);
-	if (c == ' ' || c == ',' || c == '-') {
-	  family2->del(j);
-	} else {
-	  ++j;
-	}
-      }
-
-      //--- font style
-      style = NULL;
-      for (j = 0;
-	   FcPatternGetString(fontSet->fonts[i], FC_STYLELANG, j,
-			      (FcChar8 **)&styleLang)
-	     == FcResultMatch;
-	   ++j) {
-	if (!strcmp(styleLang, "en")) {
-	  if (FcPatternGetString(fontSet->fonts[i], FC_STYLE, j,
-				 (FcChar8 **)&style)
-	      != FcResultMatch) {
-	    style = NULL;
-	  }
-	  break;
-	}
-	++j;
-      }
-      bold = style && strstr(style, "Bold") != NULL;
-      italic = style && (strstr(style, "Italic") != NULL ||
-			 strstr(style, "Oblique") != NULL);
-
-      fonts->append(new SysFontInfo(family2, bold, italic,
-				    new GString(file), type, fontNum));
+      fonts->append(new SysFontInfo(new GString(name), new GString(file),
+				    type, fontNum));
     }
 
     FcFontSetDestroy(fontSet);
   }
 
-  FcFini();
+  FcConfigDestroy(cfg);
 }
 #endif // HAVE_FONTCONFIG
 
@@ -663,6 +644,8 @@
 #else
   baseDir = appendToPath(getHomeDir(), ".xpdf");
 #endif
+  configFileVars = new GHash(gTrue);
+  setDataDirVar();
   nameToUnicode = new NameToCharCode();
   cidToUnicodes = new GHash(gTrue);
   unicodeToUnicodes = new GHash(gTrue);
@@ -736,6 +719,7 @@
   initialDisplayMode = new GString("continuous");
   initialToolbarState = gTrue;
   initialSidebarState = gTrue;
+  initialSidebarWidth = 0;
   initialSelectMode = new GString("linear");
   maxTileWidth = 1500;
   maxTileHeight = 1500;
@@ -761,6 +745,7 @@
   paperColor = new GString("#ffffff");
   matteColor = new GString("#808080");
   fullScreenMatteColor = new GString("#000000");
+  selectionColor = new GString("#8080ff");
   reverseVideoInvertImages = gFalse;
   launchCommand = NULL;
   movieCommand = NULL;
@@ -769,7 +754,6 @@
   mapUnknownCharNames = gFalse;
   mapExtTrueTypeFontsViaUnicode = gTrue;
   droppedFonts = new GHash(gTrue);
-  enableXFA = gTrue;
   createDefaultKeyBindings();
   popupMenuCmds = new GList();
   tabStateFile = appendToPath(getHomeDir(), ".xpdf.tab-state");
@@ -847,6 +831,31 @@
   }
 }
 
+void GlobalParams::setDataDirVar() {
+  GString *dir;
+
+#if defined(XPDFRC_DATADIR)
+  dir = new GString(XPDFRC_DATADIR);
+#elif defined(_WIN32)
+  wchar_t buf[512];
+  DWORD n = GetModuleFileNameW(NULL, buf, sizeof(buf) / sizeof(wchar_t));
+  if (n <= 0 || n >= sizeof(buf)) {
+    // error or path too long for buffer - just use the current dir
+    buf[0] = L'\0';
+  }
+  GString *path = fileNameToUTF8(buf);
+  dir = grabPath(path->getCString());
+  delete path;
+  appendToPath(dir, "data");
+#else
+  //~ may be useful to allow the options of using the install dir
+  //~   and/or the user's home dir (?)
+  dir = new GString("./data");
+#endif
+
+  configFileVars->add(new GString("DATADIR"), dir);
+}
+
 void GlobalParams::createDefaultKeyBindings() {
   keyBindings = new GList();
 
@@ -853,8 +862,20 @@
   //----- mouse buttons
   keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress1, xpdfKeyModNone,
 				     xpdfKeyContextAny, "startSelection"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress1, xpdfKeyModShift,
+				     xpdfKeyContextAny,
+				     "startExtendedSelection"));
   keyBindings->append(new KeyBinding(xpdfKeyCodeMouseRelease1, xpdfKeyModNone,
 				     xpdfKeyContextAny, "endSelection"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMouseRelease1, xpdfKeyModShift,
+				     xpdfKeyContextAny,
+				     "endSelection"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMouseDoubleClick1,
+				     xpdfKeyModNone, xpdfKeyContextAny,
+				     "selectWord"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMouseTripleClick1,
+				     xpdfKeyModNone, xpdfKeyContextAny,
+				     "selectLine"));
   keyBindings->append(new KeyBinding(xpdfKeyCodeMouseClick1, xpdfKeyModNone,
 				     xpdfKeyContextAny, "followLinkNoSel"));
   keyBindings->append(new KeyBinding(xpdfKeyCodeMouseClick2, xpdfKeyModNone,
@@ -876,6 +897,10 @@
 				     xpdfKeyContextAny, "scrollLeft(16)"));
   keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress7, xpdfKeyModNone,
 				     xpdfKeyContextAny, "scrollRight(16)"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress4, xpdfKeyModCtrl,
+				     xpdfKeyContextAny, "zoomIn"));
+  keyBindings->append(new KeyBinding(xpdfKeyCodeMousePress5, xpdfKeyModCtrl,
+				     xpdfKeyContextAny, "zoomOut"));
 
   //----- control keys
   keyBindings->append(new KeyBinding('o', xpdfKeyModCtrl,
@@ -1002,26 +1027,10 @@
 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, (int)(p2 - p1)));
-    p1 = *p2 ? p2 + 1 : p2;
-  }
+  tokens = parseLineTokens(buf, fileName, line);
 
   // parse the line
   if (tokens->getLength() > 0 &&
@@ -1153,6 +1162,9 @@
     } else if (!cmd->cmp("initialSidebarState")) {
       parseYesNo("initialSidebarState", &initialSidebarState,
 		 tokens, fileName, line);
+    } else if (!cmd->cmp("initialSidebarWidth")) {
+      parseInteger("initialSidebarWidth", &initialSidebarWidth,
+		   tokens, fileName, line);
     } else if (!cmd->cmp("initialSelectMode")) {
       parseString("initialSelectMode", &initialSelectMode,
 		  tokens, fileName, line);
@@ -1217,6 +1229,8 @@
     } else if (!cmd->cmp("fullScreenMatteColor")) {
       parseString("fullScreenMatteColor", &fullScreenMatteColor,
 		  tokens, fileName, line);
+    } else if (!cmd->cmp("selectionColor")) {
+      parseString("selectionColor", &selectionColor, tokens, fileName, line);
     } else if (!cmd->cmp("reverseVideoInvertImages")) {
       parseYesNo("reverseVideoInvertImages", &reverseVideoInvertImages,
 		 tokens, fileName, line);
@@ -1238,8 +1252,6 @@
 		 tokens, fileName, line);
     } else if (!cmd->cmp("dropFont")) {
       parseDropFont(tokens, fileName, line);
-    } else if (!cmd->cmp("enableXFA")) {
-      parseYesNo("enableXFA", &enableXFA, tokens, fileName, line);
     } else if (!cmd->cmp("bind")) {
       parseBind(tokens, fileName, line);
     } else if (!cmd->cmp("unbind")) {
@@ -1269,6 +1281,8 @@
       } else if (!cmd->cmp("fontpath") || !cmd->cmp("fontmap")) {
 	error(errConfig, -1,
 	      "The config file format has changed since Xpdf 0.9x");
+      } else if (!cmd->cmp("enableXFA")) {
+	error(errConfig, -1, "The enableXFA option is no longer used");
       }
     }
   }
@@ -1276,6 +1290,71 @@
   deleteGList(tokens, GString);
 }
 
+// Split a line into a sequence of tokens.  Tokens are separated by
+// whitespace.  Each token is one of:
+//   - unquoted string, which can contain any char other than
+//     whitespace, and which cannot start with a single quote, double
+//     quote, or at-double-quote (xxxx)
+//   - single-quoted string, which can contain any char other than the
+//     single quote ('xxxx')
+//   - double-quoted string, which can contain any char other than the
+//     double quote ("xxxx")
+//   - at-double-quoted string, which can contain variables and escape
+//     chars (@"xxxx")
+//     - variables look like ${name}
+//     - special chars (${}") can be escaped with %, e.g.,
+//       @"foo%"bar", @"foo%$bar", @"foo%%bar"
+GList *GlobalParams::parseLineTokens(char *buf, GString *fileName, int line) {
+  GList *tokens = new GList();
+  char *p1 = buf;
+  while (*p1) {
+    for (; *p1 && isspace(*p1); ++p1) ;
+    if (!*p1) {
+      break;
+    }
+    if (*p1 == '"' || *p1 == '\'') {
+      char *p2;
+      for (p2 = p1 + 1; *p2 && *p2 != *p1; ++p2) ;
+      ++p1;
+      tokens->append(new GString(p1, (int)(p2 - p1)));
+      p1 = *p2 ? p2 + 1 : p2;
+    } else if (*p1 == '@' && p1[1] == '"') {
+      GString *token = new GString();
+      char *p2 = p1 + 2;
+      while (*p2 && *p2 != '"') {
+	if (*p2 == '%' && p2[1]) {
+	  token->append(p2[1]);
+	  p2 += 2;
+	} else if (*p2 == '$' && p2[1] == '{') {
+	  p2 += 2;
+	  char *p3;
+	  for (p3 = p2; *p3 && *p3 != '}'; ++p3) ;
+	  GString *varName = new GString(p2, (int)(p3 - p2));
+	  GString *varValue = (GString *)configFileVars->lookup(varName);
+	  if (varValue) {
+	    token->append(varValue);
+	  } else {
+	    error(errConfig, -1, "Unknown config file variable '%t'", varName);
+	  }
+	  delete varName;
+	  p2 = *p3 ? p3 + 1 : p3;
+	} else {
+	  token->append(*p2);
+	  ++p2;
+	}
+      }
+      tokens->append(token);
+      p1 = *p2 ? p2 + 1 : p2;
+    } else {
+      char *p2;
+      for (p2 = p1 + 1; *p2 && !isspace(*p2); ++p2) ;
+      tokens->append(new GString(p1, (int)(p2 - p1)));
+      p1 = p2;
+    }
+  }
+  return tokens;
+}
+
 void GlobalParams::parseNameToUnicode(GList *tokens, GString *fileName,
 				      int line) {
   GString *name;
@@ -1738,6 +1817,8 @@
     *code = xpdfKeyCodeEnter;
   } else if (!strcmp(p0, "backspace")) {
     *code = xpdfKeyCodeBackspace;
+  } else if (!strcmp(p0, "esc")) {
+    *code = xpdfKeyCodeEsc;
   } else if (!strcmp(p0, "insert")) {
     *code = xpdfKeyCodeInsert;
   } else if (!strcmp(p0, "delete")) {
@@ -1758,8 +1839,6 @@
     *code = xpdfKeyCodeUp;
   } else if (!strcmp(p0, "down")) {
     *code = xpdfKeyCodeDown;
-  } else if (!strcmp(p0, "esc")) {
-    *code = xpdfKeyCodeEsc;
   } else if (p0[0] == 'f' && p0[1] >= '1' && p0[1] <= '9' && !p0[2]) {
     *code = xpdfKeyCodeF1 + (p0[1] - '1');
   } else if (p0[0] == 'f' &&
@@ -1782,6 +1861,16 @@
 	     (!p0[11] || (p0[11] >= '0' && p0[11] <= '9' && !p0[12])) &&
 	     (btn = atoi(p0 + 10)) >= 1 && btn <= 32) {
     *code = xpdfKeyCodeMouseClick1 + btn - 1;
+  } else if (!strncmp(p0, "mouseDoubleClick", 16) &&
+	     p0[16] >= '0' && p0[16] <= '9' &&
+	     (!p0[17] || (p0[17] >= '0' && p0[17] <= '9' && !p0[18])) &&
+	     (btn = atoi(p0 + 16)) >= 1 && btn <= 32) {
+    *code = xpdfKeyCodeMouseDoubleClick1 + btn - 1;
+  } else if (!strncmp(p0, "mouseTripleClick", 16) &&
+	     p0[16] >= '0' && p0[16] <= '9' &&
+	     (!p0[17] || (p0[17] >= '0' && p0[17] <= '9' && !p0[18])) &&
+	     (btn = atoi(p0 + 16)) >= 1 && btn <= 32) {
+    *code = xpdfKeyCodeMouseTripleClick1 + btn - 1;
   } else if (*p0 >= 0x20 && *p0 <= 0x7e && !p0[1]) {
     *code = (int)*p0;
   } else {
@@ -1981,6 +2070,7 @@
   delete macRomanReverseMap;
 
   delete baseDir;
+  deleteGHash(configFileVars, GString);
   delete nameToUnicode;
   deleteGHash(cidToUnicodes, GString);
   deleteGHash(unicodeToUnicodes, GString);
@@ -2009,6 +2099,9 @@
   if (fullScreenMatteColor) {
     delete fullScreenMatteColor;
   }
+  if (selectionColor) {
+    delete selectionColor;
+  }
   if (launchCommand) {
     delete launchCommand;
   }
@@ -2784,6 +2877,26 @@
   return s;
 }
 
+GList *GlobalParams::getAvailableTextEncodings() {
+  GList *list;       // [GString]
+  GHashIter *iter;
+  GString *key;
+  void *val;
+
+  list = new GList();
+  lockGlobalParams;
+  residentUnicodeMaps->startIter(&iter);
+  while (residentUnicodeMaps->getNext(&iter, &key, &val)) {
+    list->append(key->copy());
+  }
+  unicodeMaps->startIter(&iter);
+  while (unicodeMaps->getNext(&iter, &key, &val)) {
+    list->append(key->copy());
+  }
+  unlockGlobalParams;
+  return list;
+}
+
 EndOfLineKind GlobalParams::getTextEOL() {
   EndOfLineKind eol;
 
@@ -2856,6 +2969,15 @@
   return state;
 }
 
+int GlobalParams::getInitialSidebarWidth() {
+  int w;
+
+  lockGlobalParams;
+  w = initialSidebarWidth;
+  unlockGlobalParams;
+  return w;
+}
+
 GString *GlobalParams::getInitialSelectMode() {
   GString *s;
 
@@ -3075,6 +3197,15 @@
   return s;
 }
 
+GString *GlobalParams::getSelectionColor() {
+  GString *s;
+
+  lockGlobalParams;
+  s = selectionColor->copy();
+  unlockGlobalParams;
+  return s;
+}
+
 GBool GlobalParams::getReverseVideoInvertImages() {
   GBool invert;
 
@@ -3129,15 +3260,6 @@
   return isDropped;
 }
 
-GBool GlobalParams::getEnableXFA() {
-  GBool enable;
-
-  lockGlobalParams;
-  enable = enableXFA;
-  unlockGlobalParams;
-  return enable;
-}
-
 GList *GlobalParams::getKeyBinding(int code, int mods, int context) {
   KeyBinding *binding;
   GList *cmds;
@@ -3164,6 +3286,10 @@
   return cmds;
 }
 
+GList *GlobalParams::getAllKeyBindings() {
+  return keyBindings;
+}
+
 int GlobalParams::getNumPopupMenuCmds() {
   int n;
 
@@ -3214,7 +3340,7 @@
   return debugLogFile;
 }
 
-void GlobalParams::debugLogPrintf(char *fmt, ...) {
+void GlobalParams::debugLogPrintf(const char *fmt, ...) {
   GString *path;
   FILE *f;
   GBool needClose;
@@ -3620,12 +3746,6 @@
   unlockGlobalParams;
 }
 
-void GlobalParams::setEnableXFA(GBool enable) {
-  lockGlobalParams;
-  enableXFA = enable;
-  unlockGlobalParams;
-}
-
 void GlobalParams::setTabStateFile(char *tabStateFileA) {
   lockGlobalParams;
   delete tabStateFile;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/GlobalParams.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -134,50 +134,68 @@
   ~KeyBinding();
 };
 
-#define xpdfKeyCodeTab            0x1000
-#define xpdfKeyCodeReturn         0x1001
-#define xpdfKeyCodeEnter          0x1002
-#define xpdfKeyCodeBackspace      0x1003
-#define xpdfKeyCodeEsc            0x1004
-#define xpdfKeyCodeInsert         0x1005
-#define xpdfKeyCodeDelete         0x1006
-#define xpdfKeyCodeHome           0x1007
-#define xpdfKeyCodeEnd            0x1008
-#define xpdfKeyCodePgUp           0x1009
-#define xpdfKeyCodePgDn           0x100a
-#define xpdfKeyCodeLeft           0x100b
-#define xpdfKeyCodeRight          0x100c
-#define xpdfKeyCodeUp             0x100d
-#define xpdfKeyCodeDown           0x100e
-#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 xpdfKeyCodeTab                0x1000
+#define xpdfKeyCodeReturn             0x1001
+#define xpdfKeyCodeEnter              0x1002
+#define xpdfKeyCodeBackspace          0x1003
+#define xpdfKeyCodeEsc                0x1004
+#define xpdfKeyCodeInsert             0x1005
+#define xpdfKeyCodeDelete             0x1006
+#define xpdfKeyCodeHome               0x1007
+#define xpdfKeyCodeEnd                0x1008
+#define xpdfKeyCodePgUp               0x1009
+#define xpdfKeyCodePgDn               0x100a
+#define xpdfKeyCodeLeft               0x100b
+#define xpdfKeyCodeRight              0x100c
+#define xpdfKeyCodeUp                 0x100d
+#define xpdfKeyCodeDown               0x100e
+#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 xpdfKeyCodeMousePress32   0x2020
-#define xpdfKeyCodeMouseRelease1  0x2101
-#define xpdfKeyCodeMouseRelease2  0x2102
-#define xpdfKeyCodeMouseRelease3  0x2103
-#define xpdfKeyCodeMouseRelease4  0x2104
-#define xpdfKeyCodeMouseRelease5  0x2105
-#define xpdfKeyCodeMouseRelease6  0x2106
-#define xpdfKeyCodeMouseRelease7  0x2107
+#define xpdfKeyCodeMousePress32       0x2020
+#define xpdfKeyCodeMouseRelease1      0x2101
+#define xpdfKeyCodeMouseRelease2      0x2102
+#define xpdfKeyCodeMouseRelease3      0x2103
+#define xpdfKeyCodeMouseRelease4      0x2104
+#define xpdfKeyCodeMouseRelease5      0x2105
+#define xpdfKeyCodeMouseRelease6      0x2106
+#define xpdfKeyCodeMouseRelease7      0x2107
 // ...
-#define xpdfKeyCodeMouseRelease32 0x2120
-#define xpdfKeyCodeMouseClick1    0x2201
-#define xpdfKeyCodeMouseClick2    0x2202
-#define xpdfKeyCodeMouseClick3    0x2203
-#define xpdfKeyCodeMouseClick4    0x2204
-#define xpdfKeyCodeMouseClick5    0x2205
-#define xpdfKeyCodeMouseClick6    0x2206
-#define xpdfKeyCodeMouseClick7    0x2207
+#define xpdfKeyCodeMouseRelease32     0x2120
+#define xpdfKeyCodeMouseClick1        0x2201
+#define xpdfKeyCodeMouseClick2        0x2202
+#define xpdfKeyCodeMouseClick3        0x2203
+#define xpdfKeyCodeMouseClick4        0x2204
+#define xpdfKeyCodeMouseClick5        0x2205
+#define xpdfKeyCodeMouseClick6        0x2206
+#define xpdfKeyCodeMouseClick7        0x2207
 // ...
-#define xpdfKeyCodeMouseClick32   0x2220
+#define xpdfKeyCodeMouseClick32       0x2220
+#define xpdfKeyCodeMouseDoubleClick1  0x2301
+#define xpdfKeyCodeMouseDoubleClick2  0x2302
+#define xpdfKeyCodeMouseDoubleClick3  0x2303
+#define xpdfKeyCodeMouseDoubleClick4  0x2304
+#define xpdfKeyCodeMouseDoubleClick5  0x2305
+#define xpdfKeyCodeMouseDoubleClick6  0x2306
+#define xpdfKeyCodeMouseDoubleClick7  0x2307
+// ...
+#define xpdfKeyCodeMouseDoubleClick32 0x2320
+#define xpdfKeyCodeMouseTripleClick1  0x2401
+#define xpdfKeyCodeMouseTripleClick2  0x2402
+#define xpdfKeyCodeMouseTripleClick3  0x2403
+#define xpdfKeyCodeMouseTripleClick4  0x2404
+#define xpdfKeyCodeMouseTripleClick5  0x2405
+#define xpdfKeyCodeMouseTripleClick6  0x2406
+#define xpdfKeyCodeMouseTripleClick7  0x2407
+// ...
+#define xpdfKeyCodeMouseTripleClick32 0x2420
 #define xpdfKeyModNone            0
 #define xpdfKeyModShift           (1 << 0)
 #define xpdfKeyModCtrl            (1 << 1)
@@ -279,6 +297,7 @@
   GBool getPSAlwaysRasterize();
   GBool getPSNeverRasterize();
   GString *getTextEncodingName();
+  GList *getAvailableTextEncodings();
   EndOfLineKind getTextEOL();
   GBool getTextPageBreaks();
   GBool getTextKeepTinyChars();
@@ -287,6 +306,7 @@
   GString *getInitialDisplayMode();
   GBool getInitialToolbarState();
   GBool getInitialSidebarState();
+  int getInitialSidebarWidth();
   GString *getInitialSelectMode();
   int getMaxTileWidth();
   int getMaxTileHeight();
@@ -312,6 +332,7 @@
   GString *getPaperColor();
   GString *getMatteColor();
   GString *getFullScreenMatteColor();
+  GString *getSelectionColor();
   GBool getReverseVideoInvertImages();
   GString *getLaunchCommand() { return launchCommand; }
   GString *getMovieCommand() { return movieCommand; }
@@ -320,8 +341,8 @@
   GBool getMapUnknownCharNames();
   GBool getMapExtTrueTypeFontsViaUnicode();
   GBool isDroppedFont(const char *fontName);
-  GBool getEnableXFA();
   GList *getKeyBinding(int code, int mods, int context);
+  GList *getAllKeyBindings();
   int getNumPopupMenuCmds();
   PopupMenuCmd *getPopupMenuCmd(int idx);
   GString *getTabStateFile();
@@ -328,7 +349,7 @@
   GBool getPrintCommands();
   GBool getErrQuiet();
   GString *getDebugLogFile();
-  void debugLogPrintf(char *fmt, ...);
+  void debugLogPrintf(const char *fmt, ...);
 
   CharCodeToUnicode *getCIDToUnicode(GString *collection);
   CharCodeToUnicode *getUnicodeToUnicode(GString *fontName);
@@ -378,7 +399,6 @@
   void setMapNumericCharNames(GBool map);
   void setMapUnknownCharNames(GBool map);
   void setMapExtTrueTypeFontsViaUnicode(GBool map);
-  void setEnableXFA(GBool enable);
   void setTabStateFile(char *tabStateFileA);
   void setPrintCommands(GBool printCommandsA);
   void setErrQuiet(GBool errQuietA);
@@ -392,8 +412,10 @@
 
 private:
 
+  void setDataDirVar();
   void createDefaultKeyBindings();
   void parseFile(GString *fileName, FILE *f);
+  GList *parseLineTokens(char *buf, GString *fileName, int line);
   void parseNameToUnicode(GList *tokens, GString *fileName, int line);
   void parseCIDToUnicode(GList *tokens, GString *fileName, int line);
   void parseUnicodeToUnicode(GList *tokens, GString *fileName, int line);
@@ -438,9 +460,14 @@
   NameToCharCode *		// mapping from char name to
     macRomanReverseMap;		//   MacRomanEncoding index
 
+  //----- meta settings
+
+  GString *baseDir;		// base directory - for plugins, etc.
+  GHash *configFileVars;	// variables for use in the config file
+				//   [GString]
+
   //----- 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
@@ -522,6 +549,7 @@
 				//   or closed (false)
   GBool initialSidebarState;	// initial sidebar state - open (true)
 				//   or closed (false)
+  int initialSidebarWidth;	// initial sidebar width
   GString *initialSelectMode;	// initial selection mode (block or linear)
   int maxTileWidth;		// maximum rasterization tile width
   int maxTileHeight;		// maximum rasterization tile height
@@ -548,6 +576,7 @@
   GString *paperColor;		// paper (page background) color
   GString *matteColor;		// matte (background outside of page) color
   GString *fullScreenMatteColor; // matte color in full-screen mode
+  GString *selectionColor;	// selection color
   GBool reverseVideoInvertImages; // invert images in reverse video mode
   GString *launchCommand;	// command executed for 'launch' links
   GString *movieCommand;	// command executed for movie annotations
@@ -558,7 +587,6 @@
   GBool mapExtTrueTypeFontsViaUnicode;  // map char codes to GID via Unicode
 				        //   for external TrueType fonts?
   GHash *droppedFonts;		// dropped fonts [int]
-  GBool enableXFA;		// enable XFA form rendering
   GList *keyBindings;		// key & mouse button bindings [KeyBinding]
   GList *popupMenuCmds;		// popup menu commands [PopupMenuCmd]
   GString *tabStateFile;	// path for the tab state save file

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/HTMLGen.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/HTMLGen.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/HTMLGen.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -214,6 +214,7 @@
   // set up the TextOutputDev
   textOutControl.mode = textOutReadingOrder;
   textOutControl.html = gTrue;
+  textOutControl.splitRotatedWords = gTrue;
   textOut = new TextOutputDev(NULL, &textOutControl, gFalse);
   if (!textOut->isOk()) {
     ok = gFalse;
@@ -492,7 +493,7 @@
        (spanDir >= 0) ? wordIdx <= lastWordIdx : wordIdx >= lastWordIdx;
        wordIdx += spanDir) {
     word1 = (TextWord *)words->get(wordIdx);
-    invisible = allTextInvisible || word1->isInvisible();
+    invisible = allTextInvisible || word1->isInvisible() || word1->isRotated();
     if (!drawInvisibleText && invisible) {
       continue;
     }
@@ -513,6 +514,7 @@
 	word1->getFontInfo() != word0->getFontInfo() ||
 	word1->getFontSize() != word0->getFontSize() ||
 	word1->isInvisible() != word0->isInvisible() ||
+	word1->isRotated() != word0->isRotated() ||
 	vertAlign1 != vertAlign0 ||
 	r1 != r0 || g1 != g0 || b1 != b0) {
       if (word0) {
@@ -717,6 +719,7 @@
       getFontDetails(font, &family, &weight, &style, &scale);
       fontSpec = GString::format("font-family:ff{0:d},{1:s}; font-weight:{2:s}; font-style:{3:s};",
 				 nextFontFaceIdx, family, weight, style);
+      ++nextFontFaceIdx;
       fontDefn = new HTMLGenFontDefn(id, fontFace, fontSpec, 1.0);
     }
     delete fontPath;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -29,7 +29,6 @@
 ImageOutputDev::ImageOutputDev(char *fileRootA, GBool dumpJPEGA,
 			       GBool dumpRawA, GBool listA) {
   fileRoot = copyString(fileRootA);
-  fileName = (char *)gmalloc((int)strlen(fileRoot) + 30);
   dumpJPEG = dumpJPEGA;
   dumpRaw = dumpRawA;
   list = listA;
@@ -39,7 +38,6 @@
 }
 
 ImageOutputDev::~ImageOutputDev() {
-  gfree(fileName);
   gfree(fileRoot);
 }
 
@@ -60,6 +58,7 @@
 void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str,
 				   int width, int height, GBool invert,
 				   GBool inlineImg, GBool interpolate) {
+  GString *fileName;
   FILE *f;
   char buf[4096];
   int size, n, i;
@@ -68,11 +67,12 @@
   if (dumpRaw && !inlineImg) {
 
     // open the image file
-    sprintf(fileName, "%s-%04d.%s",
-	    fileRoot, imgNum, getRawFileExtension(str));
+    fileName = GString::format("{0:s}-{1:04d}.{2:s}",
+			       fileRoot, imgNum, getRawFileExtension(str));
     ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+    if (!(f = openFile(fileName->getCString(), "wb"))) {
+      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
+      delete fileName;
       return;
     }
 
@@ -92,10 +92,11 @@
   } else if (dumpJPEG && str->getKind() == strDCT && !inlineImg) {
 
     // open the image file
-    sprintf(fileName, "%s-%04d.jpg", fileRoot, imgNum);
+    fileName = GString::format("{0:s}-{1:04d}.jpg", fileRoot, imgNum);
     ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+    if (!(f = openFile(fileName->getCString(), "wb"))) {
+      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
+      delete fileName;
       return;
     }
 
@@ -115,10 +116,11 @@
   } else {
 
     // open the image file and write the PBM header
-    sprintf(fileName, "%s-%04d.pbm", fileRoot, imgNum);
+    fileName = GString::format("{0:s}-{1:04d}.pbm", fileRoot, imgNum);
     ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+    if (!(f = openFile(fileName->getCString(), "wb"))) {
+      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
+      delete fileName;
       return;
     }
     fprintf(f, "P4\n");
@@ -144,8 +146,10 @@
   }
 
   if (list) {
-    writeImageInfo(width, height, state, NULL);
+    writeImageInfo(fileName, width, height, state, NULL);
   }
+
+  delete fileName;
 }
 
 void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str,
@@ -154,6 +158,7 @@
 			       int *maskColors, GBool inlineImg,
 			       GBool interpolate) {
   GfxColorSpaceMode csMode;
+  GString *fileName;
   FILE *f;
   ImageStream *imgStr;
   Guchar *p;
@@ -173,11 +178,12 @@
   if (dumpRaw && !inlineImg) {
 
     // open the image file
-    sprintf(fileName, "%s-%04d.%s",
-	    fileRoot, imgNum, getRawFileExtension(str));
+    fileName = GString::format("{0:s}-{1:04d}.{2:s}",
+			       fileRoot, imgNum, getRawFileExtension(str));
     ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+    if (!(f = openFile(fileName->getCString(), "wb"))) {
+      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
+      delete fileName;
       return;
     }
 
@@ -200,10 +206,11 @@
 	     !inlineImg) {
 
     // open the image file
-    sprintf(fileName, "%s-%04d.jpg", fileRoot, imgNum);
+    fileName = GString::format("{0:s}-{1:04d}.jpg", fileRoot, imgNum);
     ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+    if (!(f = openFile(fileName->getCString(), "wb"))) {
+      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
+      delete fileName;
       return;
     }
 
@@ -224,10 +231,11 @@
 	     colorMap->getBits() == 1) {
 
     // open the image file and write the PBM header
-    sprintf(fileName, "%s-%04d.pbm", fileRoot, imgNum);
+    fileName = GString::format("{0:s}-{1:04d}.pbm", fileRoot, imgNum);
     ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+    if (!(f = openFile(fileName->getCString(), "wb"))) {
+      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
+      delete fileName;
       return;
     }
     fprintf(f, "P4\n");
@@ -259,10 +267,11 @@
 	     (csMode == csDeviceGray || csMode == csCalGray)) {
 
     // open the image file and write the PGM header
-    sprintf(fileName, "%s-%04d.pgm", fileRoot, imgNum);
+    fileName = GString::format("{0:s}-{1:04d}.pgm", fileRoot, imgNum);
     ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+    if (!(f = openFile(fileName->getCString(), "wb"))) {
+      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
+      delete fileName;
       return;
     }
     fprintf(f, "P5\n");
@@ -300,10 +309,11 @@
   } else {
 
     // open the image file and write the PPM header
-    sprintf(fileName, "%s-%04d.ppm", fileRoot, imgNum);
+    fileName = GString::format("{0:s}-{1:04d}.ppm", fileRoot, imgNum);
     ++imgNum;
-    if (!(f = fopen(fileName, "wb"))) {
-      error(errIO, -1, "Couldn't open image file '{0:s}'", fileName);
+    if (!(f = openFile(fileName->getCString(), "wb"))) {
+      error(errIO, -1, "Couldn't open image file '{0:t}'", fileName);
+      delete fileName;
       return;
     }
     fprintf(f, "P6\n");
@@ -343,19 +353,21 @@
   }
 
   if (list) {
-    writeImageInfo(width, height, state, colorMap);
+    writeImageInfo(fileName, width, height, state, colorMap);
   }
+
+  delete fileName;
 }
 
 void ImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
 				     int width, int height,
 				     GfxImageColorMap *colorMap,
-				     Stream *maskStr,
+				     Object *maskRef, Stream *maskStr,
 				     int maskWidth, int maskHeight,
 				     GBool maskInvert, GBool interpolate) {
   drawImage(state, ref, str, width, height, colorMap,
 	    NULL, gFalse, interpolate);
-  drawImageMask(state, ref, maskStr, maskWidth, maskHeight, maskInvert,
+  drawImageMask(state, maskRef, maskStr, maskWidth, maskHeight, maskInvert,
 		gFalse, interpolate);
 }
 
@@ -363,13 +375,13 @@
 					 Stream *str,
 					 int width, int height,
 					 GfxImageColorMap *colorMap,
-					 Stream *maskStr,
+					 Object *maskRef, Stream *maskStr,
 					 int maskWidth, int maskHeight,
 					 GfxImageColorMap *maskColorMap,
 					 double *matte, GBool interpolate) {
   drawImage(state, ref, str, width, height, colorMap,
 	    NULL, gFalse, interpolate);
-  drawImage(state, ref, maskStr, maskWidth, maskHeight, maskColorMap,
+  drawImage(state, maskRef, maskStr, maskWidth, maskHeight, maskColorMap,
 	    NULL, gFalse, interpolate);
 }
 
@@ -401,7 +413,8 @@
   }
 }
 
-void ImageOutputDev::writeImageInfo(int width, int height, GfxState *state,
+void ImageOutputDev::writeImageInfo(GString *fileName,
+				    int width, int height, GfxState *state,
 				    GfxImageColorMap *colorMap) {
   const char *mode;
   double hdpi, vdpi, x0, y0, x1, y1;
@@ -433,7 +446,7 @@
   }
 
   printf("%s: page=%d width=%d height=%d hdpi=%.2f vdpi=%.2f %s%s bpc=%d\n",
-	 fileName, curPageNum, width, height, hdpi, vdpi,
+	 fileName->getCString(), curPageNum, width, height, hdpi, vdpi,
 	 mode ? "colorspace=" : "mask",
 	 mode ? mode : "",
 	 bpc);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ImageOutputDev.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -85,12 +85,13 @@
   virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
 			       int width, int height,
 			       GfxImageColorMap *colorMap,
-			       Stream *maskStr, int maskWidth, int maskHeight,
+			       Object *maskRef, Stream *maskStr,
+			       int maskWidth, int maskHeight,
 			       GBool maskInvert, GBool interpolate);
   virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
 				   int width, int height,
 				   GfxImageColorMap *colorMap,
-				   Stream *maskStr,
+				   Object *maskRef, Stream *maskStr,
 				   int maskWidth, int maskHeight,
 				   GfxImageColorMap *maskColorMap,
 				   double *matte, GBool interpolate);
@@ -99,11 +100,11 @@
 
   Stream *getRawStream(Stream *str);
   const char *getRawFileExtension(Stream *str);
-  void writeImageInfo(int width, int height, GfxState *state,
+  void writeImageInfo(GString *fileName,
+		      int width, int height, GfxState *state,
 		      GfxImageColorMap *colorMap);
 
   char *fileRoot;		// root of output file names
-  char *fileName;		// buffer for output file names
   GBool dumpJPEG;		// set to dump native JPEG files
   GBool dumpRaw;		// set to dump raw PDF-native image files
   GBool list;			// set to write image info to stdout

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1304,7 +1304,6 @@
   Guint *refSegs;
   int c1, c2, c3;
   Guint i;
-  GBool done;
 
   done = gFalse;
   while (!done && readULong(&segNum)) {
@@ -1723,17 +1722,18 @@
 	    "Bad delta-height value in JBIG2 symbol dictionary");
       goto syntaxError;
     }
-    // sanity check to avoid extremely long run-times with damaged streams
-    if (dh > 1000000) {
-      error(errSyntaxError, getPos(),
-	    "Bogus delta-height value in JBIG2 symbol dictionary");
-      goto syntaxError;
-    }
     symHeight += dh;
     symWidth = 0;
     totalWidth = 0;
     j = i;
 
+    // sanity check to avoid extremely long run-times with damaged streams
+    if (symHeight > 100000) {
+      error(errSyntaxError, getPos(),
+	    "Bogus symbol height value in JBIG2 symbol dictionary");
+      goto syntaxError;
+    }
+
     // read the symbols in this height class
     while (1) {
 
@@ -1760,6 +1760,13 @@
 	goto syntaxError;
       }
 
+      // sanity check to avoid extremely long run-times with damaged streams
+      if (symWidth > 100000) {
+	error(errSyntaxError, getPos(),
+	      "Bogus symbol width value in JBIG2 symbol dictionary");
+	goto syntaxError;
+      }
+
       // using a collective bitmap, so don't read a bitmap here
       if (huff && !refAgg) {
 	symWidths[i] = symWidth;
@@ -1977,6 +1984,15 @@
     error(errSyntaxError, getPos(), "Bad size in JBIG2 text region segment");
     return;
   }
+  // sanity check: if the w/h/x/y values are way out of range, it likely
+  // indicates a damaged JBIG2 stream
+  if (w / 10 > pageW || h / 10 > pageH ||
+      x / 10 > pageW || y / 10 > pageH) {
+    error(errSyntaxError, getPos(),
+	  "Bad size or position in JBIG2 text region segment");
+    done = gTrue;
+    return;
+  }
   extCombOp = segInfoFlags & 7;
 
   // rest of the text region header
@@ -2557,6 +2573,15 @@
       !readUByte(&segInfoFlags)) {
     goto eofError;
   }
+  // sanity check: if the w/h/x/y values are way out of range, it likely
+  // indicates a damaged JBIG2 stream
+  if (w / 10 > pageW || h / 10 > pageH ||
+      x / 10 > pageW || y / 10 > pageH) {
+    error(errSyntaxError, getPos(),
+	  "Bad size or position in JBIG2 halftone region segment");
+    done = gTrue;
+    return;
+  }
   extCombOp = segInfoFlags & 7;
 
   // rest of the halftone region header
@@ -2720,6 +2745,15 @@
 	  "Bad bitmap size in JBIG2 generic region segment");
     return;
   }
+  // sanity check: if the w/h/x/y values are way out of range, it likely
+  // indicates a damaged JBIG2 stream
+  if (w / 10 > pageW || h / 10 > pageH ||
+      x / 10 > pageW || y / 10 > pageH) {
+    error(errSyntaxError, getPos(),
+	  "Bad size or position in JBIG2 generic region segment");
+    done = gTrue;
+    return;
+  }
   extCombOp = segInfoFlags & 7;
 
   // rest of the generic region segment header
@@ -3623,6 +3657,15 @@
 	  "Bad size in JBIG2 generic refinement region segment");
     return;
   }
+  // sanity check: if the w/h/x/y values are way out of range, it likely
+  // indicates a damaged JBIG2 stream
+  if (w / 10 > pageW || h / 10 > pageH ||
+      x / 10 > pageW || y / 10 > pageH) {
+    error(errSyntaxError, getPos(),
+	  "Bad size or position in JBIG2 generic refinement region segment");
+    done = gTrue;
+    return;
+  }
   extCombOp = segInfoFlags & 7;
 
   // rest of the generic refinement region segment header

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JBIG2Stream.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -128,6 +128,7 @@
   Guchar *dataPtr;
   Guchar *dataEnd;
   Guint byteCounter;
+  GBool done;
 
   JArithmeticDecoder *arithDecoder;
   JArithmeticDecoderStats *genericRegionStats;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -20,7 +20,6 @@
 #include "JPXStream.h"
 
 //~ to do:
-//  - precincts
 //  - ROI
 //  - progression order changes
 //  - packed packet headers
@@ -28,7 +27,6 @@
 //  - make sure all needed JP2/JPX subboxes are parsed (readBoxes)
 //  - can we assume that QCC segments must come after the QCD segment?
 //  - handle tilePartToEOC in readTilePartData
-//  - progression orders 2, 3, and 4
 //  - in coefficient decoding (readCodeBlockData):
 //    - selective arithmetic coding bypass
 //      (this also affects reading the cb->dataLen array)
@@ -325,7 +323,7 @@
 	    for (r = 0; r <= tileComp->nDecompLevels; ++r) {
 	      resLevel = &tileComp->resLevels[r];
 	      if (resLevel->precincts) {
-		for (pre = 0; pre < 1; ++pre) {
+		for (pre = 0; pre < resLevel->nPrecincts; ++pre) {
 		  precinct = &resLevel->precincts[pre];
 		  if (precinct->subbands) {
 		    for (sb = 0; sb < (Guint)(r == 0 ? 1 : 3); ++sb) {
@@ -350,13 +348,13 @@
 		    gfree(precinct->subbands);
 		  }
 		}
-		gfree(img.tiles[i].tileComps[comp].resLevels[r].precincts);
+		gfree(resLevel->precincts);
 	      }
 	    }
-	    gfree(img.tiles[i].tileComps[comp].resLevels);
+	    gfree(tileComp->resLevels);
 	  }
 	}
-	gfree(img.tiles[i].tileComps);
+	gfree(tile->tileComps);
       }
     }
     gfree(img.tiles);
@@ -1036,13 +1034,6 @@
 	error(errSyntaxError, getPos(), "Error in JPX COD marker segment");
 	return jpxDecodeFatalError;
       }
-#if 1 //~ progression orders 2-4 are unimplemented
-      if (progOrder >= 2) {
-	error(errUnimplemented, -1,
-	      "JPX progression order {0:d} is unimplemented",
-	      progOrder);
-      }
-#endif
       codeBlockW += 2;
       codeBlockH += 2;
       for (i = 0; i < img.nXTiles * img.nYTiles; ++i) {
@@ -1445,7 +1436,8 @@
   ok = gTrue;
   while (1) {
     if (!readTilePart()) {
-      return jpxDecodeFatalError;
+      ok = gFalse;
+      break;
     }
     if (!readMarkerHdr(&segType, &segLen)) {
       error(errSyntaxError, getPos(), "Error in JPX codestream");
@@ -1497,8 +1489,11 @@
   Guint style, progOrder, nLayers, multiComp, nDecompLevels;
   Guint codeBlockW, codeBlockH, codeBlockStyle, transform;
   Guint precinctSize, qStyle;
-  Guint n, nSBs, nx, ny, sbx0, sby0, comp, segLen;
-  Guint i, j, k, cbX, cbY, r, pre, sb, cbi, cbj;
+  Guint px0, py0, px1, py1;
+  Guint preCol0, preCol1, preRow0, preRow1, preCol, preRow;
+  Guint cbCol0, cbCol1, cbRow0, cbRow1, cbX, cbY;
+  Guint n, nSBs, nx, ny, comp, segLen;
+  Guint i, j, k, r, pre, sb, cbi, cbj;
   int segType, level;
 
   // process the SOT marker segment
@@ -1534,6 +1529,10 @@
     switch (segType) {
     case 0x52:			// COD - coding style default
       cover(34);
+      if (tilePartIdx != 0) {
+	error(errSyntaxError, getPos(), "Extraneous JPX COD marker segment");
+	return gFalse;
+      }
       if (!readUByte(&style) ||
 	  !readUByte(&progOrder) ||
 	  !readUWord(&nLayers) ||
@@ -1553,13 +1552,6 @@
 	error(errSyntaxError, getPos(), "Error in JPX COD marker segment");
 	return gFalse;
       }
-#if 1 //~ progression orders 2-4 are unimplemented
-      if (img.tiles[tileIdx].progOrder >= 2) {
-	error(errUnimplemented, -1,
-	      "JPX progression order {0:d} is unimplemented",
-	      img.tiles[tileIdx].progOrder);
-      }
-#endif
       codeBlockW += 2;
       codeBlockH += 2;
       img.tiles[tileIdx].progOrder = progOrder;
@@ -1607,6 +1599,10 @@
       break;
     case 0x53:			// COC - coding style component
       cover(35);
+      if (tilePartIdx != 0) {
+	error(errSyntaxError, getPos(), "Extraneous JPX COC marker segment");
+	return gFalse;
+      }
       if ((img.nComps > 256 && !readUWord(&comp)) ||
 	  (img.nComps <= 256 && !readUByte(&comp)) ||
 	  comp >= img.nComps ||
@@ -1623,7 +1619,7 @@
 	  nDecompLevels > 31 ||
 	  codeBlockW > 8 ||
 	  codeBlockH > 8) {
-	error(errSyntaxError, getPos(), "Error in JPX COD marker segment");
+	error(errSyntaxError, getPos(), "Error in JPX COC marker segment");
 	return gFalse;
       }
       img.tiles[tileIdx].tileComps[comp].style =
@@ -1659,6 +1655,10 @@
       break;
     case 0x5c:			// QCD - quantization default
       cover(36);
+      if (tilePartIdx != 0) {
+	error(errSyntaxError, getPos(), "Extraneous JPX QCD marker segment");
+	return gFalse;
+      }
       if (!readUByte(&img.tiles[tileIdx].tileComps[0].quantStyle)) {
 	error(errSyntaxError, getPos(), "Error in JPX QCD marker segment");
 	return gFalse;
@@ -1726,6 +1726,10 @@
       break;
     case 0x5d:			// QCC - quantization component
       cover(37);
+      if (tilePartIdx != 0) {
+	error(errSyntaxError, getPos(), "Extraneous JPX QCC marker segment");
+	return gFalse;
+      }
       if ((img.nComps > 256 && !readUWord(&comp)) ||
 	  (img.nComps <= 256 && !readUByte(&comp)) ||
 	  comp >= img.nComps ||
@@ -1786,6 +1790,10 @@
       break;
     case 0x5e:			// RGN - region of interest
       cover(38);
+      if (tilePartIdx != 0) {
+	error(errSyntaxError, getPos(), "Extraneous JPX RGN marker segment");
+	return gFalse;
+      }
 #if 1 //~ ROI is unimplemented
       error(errUnimplemented, -1, "Got a JPX RGN segment");
       if (segLen > 2 &&
@@ -1889,7 +1897,6 @@
   //----- initialize the tile, precincts, and code-blocks
   if (tilePartIdx == 0) {
     tile = &img.tiles[tileIdx];
-    tile->init = gTrue;
     i = tileIdx / img.nXTiles;
     j = tileIdx % img.nXTiles;
     if ((tile->x0 = img.xTileOffset + j * img.xTileSize) < img.xOffset) {
@@ -1919,8 +1926,6 @@
       tileComp->y0 = jpxCeilDiv(tile->y0, tileComp->vSep);
       tileComp->x1 = jpxCeilDiv(tile->x1, tileComp->hSep);
       tileComp->y1 = jpxCeilDiv(tile->y1, tileComp->vSep);
-      tileComp->cbW = 1 << tileComp->codeBlockW;
-      tileComp->cbH = 1 << tileComp->codeBlockH;
       tileComp->w = jpxCeilDivPow2(tileComp->x1, reduction)
 	            - jpxCeilDivPow2(tileComp->x0, reduction);
       tileComp->h = jpxCeilDivPow2(tileComp->y1, reduction)
@@ -1939,12 +1944,26 @@
       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);
+	resLevel->x0 = jpxCeilDivPow2(tileComp->x0,
+				      tileComp->nDecompLevels - r);
+	resLevel->y0 = jpxCeilDivPow2(tileComp->y0,
+				      tileComp->nDecompLevels - r);
+	resLevel->x1 = jpxCeilDivPow2(tileComp->x1,
+				      tileComp->nDecompLevels - r);
+	resLevel->y1 = jpxCeilDivPow2(tileComp->y1,
+				      tileComp->nDecompLevels - r);
+	resLevel->codeBlockW = r == 0 ? resLevel->precinctWidth
+	                              : resLevel->precinctWidth - 1;
+	if (resLevel->codeBlockW > tileComp->codeBlockW) {
+	  resLevel->codeBlockW = tileComp->codeBlockW;
+	}
+	resLevel->cbW = 1 << resLevel->codeBlockW;
+	resLevel->codeBlockH = r == 0 ? resLevel->precinctHeight
+	                              : resLevel->precinctHeight - 1;
+	if (resLevel->codeBlockH > tileComp->codeBlockH) {
+	  resLevel->codeBlockH = tileComp->codeBlockH;
+	}
+	resLevel->cbH = 1 << resLevel->codeBlockH;
 	// the JPEG 2000 spec says that packets for empty res levels
 	// should all be present in the codestream (B.6, B.9, B.10),
 	// but it appears that encoders drop packets if the res level
@@ -1952,6 +1971,7 @@
 	resLevel->empty = resLevel->x0 == resLevel->x1 ||
 	                  resLevel->y0 == resLevel->y1;
 	if (r == 0) {
+	  nSBs = 1;
 	  resLevel->bx0[0] = resLevel->x0;
 	  resLevel->by0[0] = resLevel->y0;
 	  resLevel->bx1[0] = resLevel->x1;
@@ -1960,18 +1980,19 @@
 	                    (resLevel->bx0[0] == resLevel->bx1[0] ||
 			     resLevel->by0[0] == resLevel->by1[0]);
 	} 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);
+	  nSBs = 3;
+	  resLevel->bx0[0] = jpxCeilDivPow2(resLevel->x0 - 1, 1);
+	  resLevel->by0[0] = jpxCeilDivPow2(resLevel->y0, 1);
+	  resLevel->bx1[0] = jpxCeilDivPow2(resLevel->x1 - 1, 1);
+	  resLevel->by1[0] = jpxCeilDivPow2(resLevel->y1, 1);
+	  resLevel->bx0[1] = jpxCeilDivPow2(resLevel->x0, 1);
+	  resLevel->by0[1] = jpxCeilDivPow2(resLevel->y0 - 1, 1);
+	  resLevel->bx1[1] = jpxCeilDivPow2(resLevel->x1, 1);
+	  resLevel->by1[1] = jpxCeilDivPow2(resLevel->y1 - 1, 1);
+	  resLevel->bx0[2] = jpxCeilDivPow2(resLevel->x0 - 1, 1);
+	  resLevel->by0[2] = jpxCeilDivPow2(resLevel->y0 - 1, 1);
+	  resLevel->bx1[2] = jpxCeilDivPow2(resLevel->x1 - 1, 1);
+	  resLevel->by1[2] = jpxCeilDivPow2(resLevel->y1 - 1, 1);
 	  resLevel->empty = resLevel->empty &&
 	                    (resLevel->bx0[0] == resLevel->bx1[0] ||
 			     resLevel->by0[0] == resLevel->by1[0]) &&
@@ -1980,135 +2001,155 @@
 	                    (resLevel->bx0[2] == resLevel->bx1[2] ||
 			     resLevel->by0[2] == resLevel->by1[2]);
 	}
-	resLevel->precincts = (JPXPrecinct *)gmallocn(1, sizeof(JPXPrecinct));
-	for (pre = 0; pre < 1; ++pre) {
+	preCol0 = jpxFloorDivPow2(resLevel->x0, resLevel->precinctWidth);
+	preCol1 = jpxCeilDivPow2(resLevel->x1, resLevel->precinctWidth);
+	preRow0 = jpxFloorDivPow2(resLevel->y0, resLevel->precinctHeight);
+	preRow1 = jpxCeilDivPow2(resLevel->y1, resLevel->precinctHeight);
+	resLevel->nPrecincts = (preCol1 - preCol0) * (preRow1 - preRow0);
+	resLevel->precincts = (JPXPrecinct *)gmallocn(resLevel->nPrecincts,
+						      sizeof(JPXPrecinct));
+	for (pre = 0; pre < resLevel->nPrecincts; ++pre) {
 	  resLevel->precincts[pre].subbands = NULL;
 	}
-	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) {
-	    precinct->subbands[sb].inclusion = NULL;
-	    precinct->subbands[sb].zeroBitPlane = NULL;
-	    precinct->subbands[sb].cbs = NULL;
-	  }
-	  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;
+	precinct = resLevel->precincts;
+	for (preRow = preRow0; preRow < preRow1; ++preRow) {
+	  for (preCol = preCol0; preCol < preCol1; ++preCol) {
+	    precinct->subbands =
+	        (JPXSubband *)gmallocn(nSBs, sizeof(JPXSubband));
+	    for (sb = 0; sb < nSBs; ++sb) {
+	      precinct->subbands[sb].inclusion = NULL;
+	      precinct->subbands[sb].zeroBitPlane = NULL;
+	      precinct->subbands[sb].cbs = NULL;
 	    }
-	    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));
-	    for (k = 0; k < subband->nXCBs * subband->nYCBs; ++k) {
-	      subband->cbs[k].dataLen = NULL;
-	      subband->cbs[k].touched = NULL;
-	      subband->cbs[k].arithDecoder = NULL;
-	      subband->cbs[k].stats = NULL;
-	    }
-	    sbx0 = jpxFloorDivPow2(subband->x0, tileComp->codeBlockW);
-	    sby0 = jpxFloorDivPow2(subband->y0, tileComp->codeBlockH);
-	    if (r == 0) { // (NL)LL
-	      sbCoeffs = tileComp->data;
-	    } else if (sb == 0) { // (NL-r+1)HL
-	      sbCoeffs = tileComp->data
-		         + resLevel->bx1[1] - resLevel->bx0[1];
-	    } else if (sb == 1) { // (NL-r+1)LH
-	      sbCoeffs = tileComp->data
-		         + (resLevel->by1[0] - resLevel->by0[0]) * tileComp->w;
-	    } else { // (NL-r+1)HH
-	      sbCoeffs = tileComp->data
-		         + (resLevel->by1[0] - resLevel->by0[0]) * tileComp->w
-		         + resLevel->bx1[1] - resLevel->bx0[1];
-	    }
-	    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->dataLenSize = 1;
-		cb->dataLen = (Guint *)gmalloc(sizeof(Guint));
-		if (r <= tileComp->nDecompLevels - reduction) {
-		  cb->coeffs = sbCoeffs
-		               + (cb->y0 - subband->y0) * tileComp->w
-		               + (cb->x0 - subband->x0);
-		  cb->touched = (char *)gmalloc(1 << (tileComp->codeBlockW
-						      + tileComp->codeBlockH));
-		  cb->len = 0;
-		  for (cbj = 0; cbj < cb->y1 - cb->y0; ++cbj) {
-		    for (cbi = 0; cbi < cb->x1 - cb->x0; ++cbi) {
-		      cb->coeffs[cbj * tileComp->w + cbi] = 0;
+	    for (sb = 0; sb < nSBs; ++sb) {
+	      subband = &precinct->subbands[sb];
+	      if (r == 0) {
+		px0 = preCol << resLevel->precinctWidth;
+		px1 = (preCol + 1) << resLevel->precinctWidth;
+		py0 = preRow << resLevel->precinctHeight;
+		py1 = (preRow + 1) << resLevel->precinctHeight;
+	      } else {
+		px0 = preCol << (resLevel->precinctWidth - 1);
+		px1 = (preCol + 1) << (resLevel->precinctWidth - 1);
+		py0 = preRow << (resLevel->precinctHeight - 1);
+		py1 = (preRow + 1) << (resLevel->precinctHeight - 1);
+	      }
+	      if (px0 < resLevel->bx0[sb]) {
+		px0 = resLevel->bx0[sb];
+	      }
+	      if (px1 > resLevel->bx1[sb]) {
+		px1 = resLevel->bx1[sb];
+	      }
+	      if (py0 < resLevel->by0[sb]) {
+		py0 = resLevel->by0[sb];
+	      }
+	      if (py1 > resLevel->by1[sb]) {
+		py1 = resLevel->by1[sb];
+	      }
+	      if (r == 0) { // (NL)LL
+		sbCoeffs = tileComp->data;
+	      } else if (sb == 0) { // (NL-r+1)HL
+		sbCoeffs = tileComp->data
+                           + resLevel->bx1[1] - resLevel->bx0[1];
+	      } else if (sb == 1) { // (NL-r+1)LH
+		sbCoeffs = tileComp->data
+		           + (resLevel->by1[0] - resLevel->by0[0]) * tileComp->w;
+	      } else { // (NL-r+1)HH
+		sbCoeffs = tileComp->data
+		           + (resLevel->by1[0] - resLevel->by0[0]) * tileComp->w
+		           + (resLevel->bx1[1] - resLevel->bx0[1]);
+	      }
+	      cbCol0 = jpxFloorDivPow2(px0, resLevel->codeBlockW);
+	      cbCol1 = jpxCeilDivPow2(px1, resLevel->codeBlockW);
+	      cbRow0 = jpxFloorDivPow2(py0, resLevel->codeBlockH);
+	      cbRow1 = jpxCeilDivPow2(py1, resLevel->codeBlockH);
+	      subband->nXCBs = cbCol1 - cbCol0;
+	      subband->nYCBs = cbRow1 - cbRow0;
+	      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));
+	      for (k = 0; k < subband->nXCBs * subband->nYCBs; ++k) {
+		subband->cbs[k].dataLen = NULL;
+		subband->cbs[k].touched = NULL;
+		subband->cbs[k].arithDecoder = NULL;
+		subband->cbs[k].stats = NULL;
+	      }
+	      cb = subband->cbs;
+	      for (cbY = cbRow0; cbY < cbRow1; ++cbY) {
+		for (cbX = cbCol0; cbX < cbCol1; ++cbX) {
+		  cb->x0 = cbX << resLevel->codeBlockW;
+		  cb->x1 = cb->x0 + resLevel->cbW;
+		  if (cb->x0 < px0) {
+		    cb->x0 = px0;
+		  }
+		  if (cb->x1 > px1) {
+		    cb->x1 = px1;
+		  }
+		  cb->y0 = cbY << resLevel->codeBlockH;
+		  cb->y1 = cb->y0 + resLevel->cbH;
+		  if (cb->y0 < py0) {
+		    cb->y0 = py0;
+		  }
+		  if (cb->y1 > py1) {
+		    cb->y1 = py1;
+		  }
+		  cb->seen = gFalse;
+		  cb->lBlock = 3;
+		  cb->nextPass = jpxPassCleanup;
+		  cb->nZeroBitPlanes = 0;
+		  cb->dataLenSize = 1;
+		  cb->dataLen = (Guint *)gmalloc(sizeof(Guint));
+		  if (r <= tileComp->nDecompLevels - reduction) {
+		    cb->coeffs = sbCoeffs
+		                 + (cb->y0 - resLevel->by0[sb]) * tileComp->w
+		                 + (cb->x0 - resLevel->bx0[sb]);
+		    cb->touched = (char *)gmalloc(1 << (resLevel->codeBlockW
+							+ resLevel->codeBlockH));
+		    cb->len = 0;
+		    for (cbj = 0; cbj < cb->y1 - cb->y0; ++cbj) {
+		      for (cbi = 0; cbi < cb->x1 - cb->x0; ++cbi) {
+			cb->coeffs[cbj * tileComp->w + cbi] = 0;
+		      }
 		    }
+		    memset(cb->touched, 0,
+			   ((size_t)1 << (resLevel->codeBlockW
+					  + resLevel->codeBlockH)));
+		  } else {
+		    cb->coeffs = NULL;
+		    cb->touched = NULL;
+		    cb->len = 0;
 		  }
-		  memset(cb->touched, 0,
-			 ((size_t)1 << (tileComp->codeBlockW
-					+ tileComp->codeBlockH)));
-		} else {
-		  cb->coeffs = NULL;
-		  cb->touched = NULL;
-		  cb->len = 0;
+		  ++cb;
 		}
-		++cb;
 	      }
 	    }
+	    ++precinct;
 	  }
 	}
       }
     }
+    tile->init = gTrue;
   }
 
   return readTilePartData(tileIdx, tilePartLen, tilePartToEOC);
@@ -2384,13 +2425,17 @@
     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;
-	    tile->done = gTrue;
+      resLevel = &tile->tileComps[tile->comp].resLevels[tile->res];
+      if (++tile->precinct == resLevel->nPrecincts) {
+	tile->precinct = 0;
+	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;
+	      tile->done = gTrue;
+	    }
 	  }
 	}
       }
@@ -2397,34 +2442,42 @@
       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;
-	    tile->done = gTrue;
+      resLevel = &tile->tileComps[tile->comp].resLevels[tile->res];
+      if (++tile->precinct == resLevel->nPrecincts) {
+	tile->precinct = 0;
+	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;
+	      tile->done = gTrue;
+	    }
 	  }
 	}
       }
       break;
     case 2: // resolution level, precinct, component, layer
-      //~ this isn't correct -- see B.12.1.3
       cover(60);
+      //~ this is incorrect if there are subsampled components (?)
       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;
-	    tile->done = gTrue;
+	  resLevel = &tile->tileComps[tile->comp].resLevels[tile->res];
+	  if (++tile->precinct == resLevel->nPrecincts) {
+	    tile->precinct = 0;
+	    if (++tile->res == tile->maxNDecompLevels + 1) {
+	      tile->res = 0;
+	      tile->done = gTrue;
+	    }
 	  }
 	}
       }
       break;
     case 3: // precinct, component, resolution level, layer
-      //~ this isn't correct -- see B.12.1.4
       cover(61);
+      //~ this is incorrect if there are subsampled components (?)
       if (++tile->layer == tile->nLayers) {
 	tile->layer = 0;
 	if (++tile->res == tile->maxNDecompLevels + 1) {
@@ -2431,21 +2484,28 @@
 	  tile->res = 0;
 	  if (++tile->comp == img.nComps) {
 	    tile->comp = 0;
-	    tile->done = gTrue;
+	    resLevel = &tile->tileComps[tile->comp].resLevels[tile->res];
+	    if (++tile->precinct == resLevel->nPrecincts) {
+	      tile->precinct = 0;
+	      tile->done = gTrue;
+	    }
 	  }
 	}
       }
       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;
-	    tile->done = gTrue;
+	  resLevel = &tile->tileComps[tile->comp].resLevels[tile->res];
+	  if (++tile->precinct == resLevel->nPrecincts) {
+	    tile->precinct = 0;
+	    if (++tile->comp == img.nComps) {
+	      tile->comp = 0;
+	      tile->done = gTrue;
+	    }
 	  }
 	}
       }
@@ -2515,13 +2575,13 @@
       for (y0 = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched;
 	   y0 < cb->y1;
 	   y0 += 4, coeff0 += 4 * tileComp->w,
-	     touched0 += 4 << tileComp->codeBlockW) {
+	     touched0 += 4 << resLevel->codeBlockW) {
 	for (x = cb->x0, coeff1 = coeff0, touched1 = touched0;
 	     x < cb->x1;
 	     ++x, ++coeff1, ++touched1) {
 	  for (y1 = 0, coeff = coeff1, touched = touched1;
 	       y1 < 4 && y0+y1 < cb->y1;
-	       ++y1, coeff += tileComp->w, touched += tileComp->cbW) {
+	       ++y1, coeff += tileComp->w, touched += resLevel->cbW) {
 	    if (!*coeff) {
 	      horiz = vert = diag = 0;
 	      horizSign = vertSign = 2;
@@ -2590,13 +2650,13 @@
       for (y0 = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched;
 	   y0 < cb->y1;
 	   y0 += 4, coeff0 += 4 * tileComp->w,
-	     touched0 += 4 << tileComp->codeBlockW) {
+	     touched0 += 4 << resLevel->codeBlockW) {
 	for (x = cb->x0, coeff1 = coeff0, touched1 = touched0;
 	     x < cb->x1;
 	     ++x, ++coeff1, ++touched1) {
 	  for (y1 = 0, coeff = coeff1, touched = touched1;
 	       y1 < 4 && y0+y1 < cb->y1;
-	       ++y1, coeff += tileComp->w, touched += tileComp->cbW) {
+	       ++y1, coeff += tileComp->w, touched += resLevel->cbW) {
 	    if (*coeff && !*touched) {
 	      if (*coeff == 1 || *coeff == -1) {
 		all = 0;
@@ -2651,7 +2711,7 @@
       for (y0 = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched;
 	   y0 < cb->y1;
 	   y0 += 4, coeff0 += 4 * tileComp->w,
-	     touched0 += 4 << tileComp->codeBlockW) {
+	     touched0 += 4 << resLevel->codeBlockW) {
 	for (x = cb->x0, coeff1 = coeff0, touched1 = touched0;
 	     x < cb->x1;
 	     ++x, ++coeff1, ++touched1) {
@@ -2658,9 +2718,9 @@
 	  y1 = 0;
 	  if (y0 + 3 < cb->y1 &&
 	      !(*touched1) &&
-	      !(touched1[tileComp->cbW]) &&
-	      !(touched1[2 * tileComp->cbW]) &&
-	      !(touched1[3 * tileComp->cbW]) &&
+	      !(touched1[resLevel->cbW]) &&
+	      !(touched1[2 * resLevel->cbW]) &&
+	      !(touched1[3 * resLevel->cbW]) &&
 	      (x == cb->x0 || y0 == cb->y0 ||
 	       !coeff1[-(int)tileComp->w - 1]) &&
 	      (y0 == cb->y0 ||
@@ -2702,9 +2762,9 @@
 	    }
 	  }
 	  for (coeff = &coeff1[y1 * tileComp->w],
-		 touched = &touched1[y1 << tileComp->codeBlockW];
+		 touched = &touched1[y1 << resLevel->codeBlockW];
 	       y1 < 4 && y0 + y1 < cb->y1;
-	       ++y1, coeff += tileComp->w, touched += tileComp->cbW) {
+	       ++y1, coeff += tileComp->w, touched += resLevel->cbW) {
 	    if (!*touched) {
 	      horiz = vert = diag = 0;
 	      horizSign = vertSign = 2;
@@ -2814,7 +2874,7 @@
   int shift2;
   double mu;
   int val;
-  Guint r, cbX, cbY, x, y;
+  Guint r, pre, cbX, cbY, x, y;
 
   cover(68);
 
@@ -2821,8 +2881,6 @@
   //----- (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;
@@ -2843,44 +2901,48 @@
   }
 
   // do fixed point adjustment and dequantization on (NL)LL
-  cb = subband->cbs;
-  for (cbY = 0; cbY < subband->nYCBs; ++cbY) {
-    for (cbX = 0; cbX < subband->nXCBs; ++cbX) {
-      for (y = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched;
-	   y < cb->y1;
-	   ++y, coeff0 += tileComp->w, touched0 += tileComp->cbW) {
-	for (x = cb->x0, coeff = coeff0, touched = touched0;
-	     x < cb->x1;
-	     ++x, ++coeff, ++touched) {
-	  val = *coeff;
-	  if (val != 0) {
-	    shift2 = shift - (cb->nZeroBitPlanes + cb->len + *touched);
-	    if (shift2 > 0) {
-	      cover(94);
-	      if (val < 0) {
-		val = (val << shift2) - (1 << (shift2 - 1));
+  for (pre = 0; pre < resLevel->nPrecincts; ++pre) {
+    precinct = &resLevel->precincts[pre];
+    subband = &precinct->subbands[0];
+    cb = subband->cbs;
+    for (cbY = 0; cbY < subband->nYCBs; ++cbY) {
+      for (cbX = 0; cbX < subband->nXCBs; ++cbX) {
+	for (y = cb->y0, coeff0 = cb->coeffs, touched0 = cb->touched;
+	     y < cb->y1;
+	     ++y, coeff0 += tileComp->w, touched0 += resLevel->cbW) {
+	  for (x = cb->x0, coeff = coeff0, touched = touched0;
+	       x < cb->x1;
+	       ++x, ++coeff, ++touched) {
+	    val = *coeff;
+	    if (val != 0) {
+	      shift2 = shift - (cb->nZeroBitPlanes + cb->len + *touched);
+	      if (shift2 > 0) {
+		cover(94);
+		if (val < 0) {
+		  val = (val << shift2) - (1 << (shift2 - 1));
+		} else {
+		  val = (val << shift2) + (1 << (shift2 - 1));
+		}
 	      } else {
-		val = (val << shift2) + (1 << (shift2 - 1));
+		cover(95);
+		val >>= -shift2;
 	      }
-	    } else {
-	      cover(95);
-	      val >>= -shift2;
-	    }
-	    if (qStyle == 0) {
-	      cover(96);
-	      if (tileComp->transform == 0) {
-		cover(97);
-		val &= -1 << (fracBits - tileComp->prec);
+	      if (qStyle == 0) {
+		cover(96);
+		if (tileComp->transform == 0) {
+		  cover(97);
+		  val &= -1 << (fracBits - tileComp->prec);
+		}
+	      } else {
+		cover(98);
+		val = (int)((double)val * mu);
 	      }
-	    } else {
-	      cover(98);
-	      val = (int)((double)val * mu);
 	    }
+	    *coeff = val;
 	  }
-	  *coeff = val;
 	}
+	++cb;
       }
-      ++cb;
     }
   }
 
@@ -2914,11 +2976,10 @@
   int val;
   int *dataPtr, *bufPtr;
   Guint nx1, nx2, ny1, ny2, offset;
-  Guint x, y, sb, cbX, cbY;
+  Guint x, y, sb, pre, cbX, cbY;
 
   qStyle = tileComp->quantStyle & 0x1f;
   guard = (tileComp->quantStyle >> 5) & 7;
-  precinct = &resLevel->precincts[0];
 
   //----- compute subband bounds
 
@@ -2931,10 +2992,10 @@
   //   | LH | HH | <- ny1
   //   +----+----+
   //               <- ny2
-  nx1 = precinct->subbands[1].x1 - precinct->subbands[1].x0;
-  nx2 = nx1 + precinct->subbands[0].x1 - precinct->subbands[0].x0;
-  ny1 = precinct->subbands[0].y1 - precinct->subbands[0].y0;
-  ny2 = ny1 + precinct->subbands[1].y1 - precinct->subbands[1].y0;
+  nx1 = resLevel->bx1[1] - resLevel->bx0[1];
+  nx2 = nx1 + resLevel->bx1[0] - resLevel->bx0[0];
+  ny1 = resLevel->by1[0] - resLevel->by0[0];
+  ny2 = ny1 + resLevel->by1[1] - resLevel->by0[1];
   if (nx2 == 0 || ny2 == 0) {
     return;
   }
@@ -2965,44 +3026,48 @@
     }
 
     // fixed point adjustment and dequantization
-    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, touched0 = cb->touched;
-	     y < cb->y1;
-	     ++y, coeff0 += tileComp->w, touched0 += tileComp->cbW) {
-	  for (x = cb->x0, coeff = coeff0, touched = touched0;
-	       x < cb->x1;
-	       ++x, ++coeff, ++touched) {
-	    val = *coeff;
-	    if (val != 0) {
-	      shift2 = shift - (cb->nZeroBitPlanes + cb->len + *touched);
-	      if (shift2 > 0) {
-		cover(74);
-		if (val < 0) {
-		  val = (val << shift2) - (1 << (shift2 - 1));
+
+    for (pre = 0; pre < resLevel->nPrecincts; ++pre) {
+      precinct = &resLevel->precincts[pre];
+      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, touched0 = cb->touched;
+	       y < cb->y1;
+	       ++y, coeff0 += tileComp->w, touched0 += resLevel->cbW) {
+	    for (x = cb->x0, coeff = coeff0, touched = touched0;
+		 x < cb->x1;
+		 ++x, ++coeff, ++touched) {
+	      val = *coeff;
+	      if (val != 0) {
+		shift2 = shift - (cb->nZeroBitPlanes + cb->len + *touched);
+		if (shift2 > 0) {
+		  cover(74);
+		  if (val < 0) {
+		    val = (val << shift2) - (1 << (shift2 - 1));
+		  } else {
+		    val = (val << shift2) + (1 << (shift2 - 1));
+		  }
 		} else {
-		  val = (val << shift2) + (1 << (shift2 - 1));
+		  cover(75);
+		  val >>= -shift2;
 		}
-	      } else {
-		cover(75);
-		val >>= -shift2;
-	      }
-	      if (qStyle == 0) {
-		cover(76);
-		if (tileComp->transform == 0) {
-		  val &= -1 << (fracBits - tileComp->prec);
+		if (qStyle == 0) {
+		  cover(76);
+		  if (tileComp->transform == 0) {
+		    val &= -1 << (fracBits - tileComp->prec);
+		  }
+		} else {
+		  cover(77);
+		  val = (int)((double)val * mu);
 		}
-	      } else {
-		cover(77);
-		val = (int)((double)val * mu);
 	      }
+	      *coeff = val;
 	    }
-	    *coeff = val;
 	  }
+	  ++cb;
 	}
-	++cb;
       }
     }
   }
@@ -3010,13 +3075,9 @@
   //----- inverse transform
 
   // horizontal (row) transforms
-  if (r == tileComp->nDecompLevels) {
-    offset = 3 + (tileComp->x0 & 1);
-  } else {
-    offset = 3 + (tileComp->resLevels[r+1].x0 & 1);
-  }
+  offset = 3 + (resLevel->x0 & 1);
   for (y = 0, dataPtr = tileComp->data; y < ny2; ++y, dataPtr += tileComp->w) {
-    if (precinct->subbands[0].x0 == precinct->subbands[1].x0) {
+    if (resLevel->bx0[0] == resLevel->bx0[1]) {
       // fetch LL/LH
       for (x = 0, bufPtr = tileComp->buf + offset;
 	   x < nx1;
@@ -3050,13 +3111,9 @@
   }
 
   // vertical (column) transforms
-  if (r == tileComp->nDecompLevels) {
-    offset = 3 + (tileComp->y0 & 1);
-  } else {
-    offset = 3 + (tileComp->resLevels[r+1].y0 & 1);
-  }
+  offset = 3 + (resLevel->y0 & 1);
   for (x = 0, dataPtr = tileComp->data; x < nx2; ++x, ++dataPtr) {
-    if (precinct->subbands[1].y0 == precinct->subbands[0].y0) {
+    if (resLevel->by0[0] == resLevel->by0[1]) {
       // fetch LL/HL
       for (y = 0, bufPtr = tileComp->buf + offset;
 	   y < ny1;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/JPXStream.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -135,7 +135,6 @@
 
 struct JPXSubband {
   //----- computed
-  Guint x0, y0, x1, y1;		// bounds
   Guint nXCBs, nYCBs;		// number of code-blocks in the x and y
 				//   directions
 
@@ -152,9 +151,6 @@
 //------------------------------------------------------------------------
 
 struct JPXPrecinct {
-  //----- computed
-  Guint x0, y0, x1, y1;		// bounds of the precinct
-
   //----- children
   JPXSubband *subbands;		// the subbands
 };
@@ -165,12 +161,18 @@
   //----- from the COD and COC segments (main and tile)
   Guint precinctWidth;		// log2(precinct width)
   Guint precinctHeight;		// log2(precinct height)
+  Guint nPrecincts;
 
   //----- computed
-  Guint x0, y0, x1, y1;		// bounds of the tile-comp (for this res level)
+  Guint x0, y0, x1, y1;		// bounds of this tile-comp at this res level
   Guint bx0[3], by0[3],		// subband bounds
         bx1[3], by1[3];
-  GBool empty;			// true if all subbands are zero width or height
+  Guint codeBlockW;		// log2(code-block width)
+  Guint codeBlockH;		// log2(code-block height)
+  Guint cbW;			// code-block width
+  Guint cbH;			// code-block height
+  GBool empty;			// true if all subbands and precincts are
+				//   zero width or height
 
   //---- children
   JPXPrecinct *precincts;	// the precincts
@@ -201,8 +203,6 @@
   //----- computed
   Guint x0, y0, x1, y1;		// bounds of the tile-comp, in ref coords
   Guint w, h;			// data size = {x1 - x0, y1 - y0} >> reduction
-  Guint cbW;			// code-block width
-  Guint cbH;			// code-block height
 
   //----- image data
   int *data;			// the decoded image data

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Lexer.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Lexer.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Lexer.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -237,11 +237,6 @@
       switch (c = getChar()) {
 
       case EOF:
-#if 0
-      // This breaks some PDF files, e.g., ones from Photoshop.
-      case '\r':
-      case '\n':
-#endif
 	error(errSyntaxError, getPos(), "Unterminated string");
 	done = gTrue;
 	break;
@@ -259,6 +254,16 @@
 	}
 	break;
 
+      case '\r':
+	// The PDF spec says that any literal end-of-line sequence
+	// (LF, CR, CR+LF) is translated to a single LF char.
+	c = lookChar();
+	if (c == '\n') {
+	  getChar();
+	}
+	c2 = '\n';
+	break;
+
       case '\\':
 	switch (c = getChar()) {
 	case 'n':

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Object.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Object.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Object.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -45,11 +45,12 @@
 #ifdef DEBUG_MEM
 #if MULTITHREADED
 GAtomicCounter Object::numAlloc[numObjTypes] =
+  {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
 #else
 long Object::numAlloc[numObjTypes] =
-#endif
   {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
 #endif
+#endif // DEBUG_MEM
 
 Object *Object::initArray(XRef *xref) {
   initObj(objArray);
@@ -229,13 +230,15 @@
   long t;
 
   t = 0;
-  for (i = 0; i < numObjTypes; ++i)
+  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)
+      if (numAlloc[i] > 0) {
 	fprintf(f, "  %-20s: %6ld\n", objTypeNames[i], numAlloc[i]);
+      }
     }
   }
 #endif

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OptionalContent.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OptionalContent.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OptionalContent.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -172,6 +172,7 @@
   int policy;
   Ref ref;
   Object obj2, obj3, obj4, obj5;
+  GBool gotOCG;
   int i;
 
   if (obj->isNull()) {
@@ -224,34 +225,40 @@
 	obj2.free();
 	return gFalse;
       }
+      gotOCG = gFalse;
       for (i = 0; i < obj4.arrayGetLength(); ++i) {
 	obj4.arrayGetNF(i, &obj5);
 	if (obj5.isRef()) {
 	  ref = obj5.getRef();
-	  if (!(ocg = findOCG(&ref))) {
-	    obj5.free();
-	    obj4.free();
-	    obj3.free();
-	    obj2.free();
-	    return gFalse;
+	  if ((ocg = findOCG(&ref))) {
+	    gotOCG = gTrue;
+	    switch (policy) {
+	    case ocPolicyAllOn:
+	      *visible = *visible && ocg->getState();
+	      break;
+	    case ocPolicyAnyOn:
+	      *visible = *visible || ocg->getState();
+	      break;
+	    case ocPolicyAnyOff:
+	      *visible = *visible || !ocg->getState();
+	      break;
+	    case ocPolicyAllOff:
+	      *visible = *visible && !ocg->getState();
+	      break;
+	    }
 	  }
-	  switch (policy) {
-	  case ocPolicyAllOn:
-	    *visible = *visible && ocg->getState();
-	    break;
-	  case ocPolicyAnyOn:
-	    *visible = *visible || ocg->getState();
-	    break;
-	  case ocPolicyAnyOff:
-	    *visible = *visible || !ocg->getState();
-	    break;
-	  case ocPolicyAllOff:
-	    *visible = *visible && !ocg->getState();
-	    break;
-	  }
 	}
 	obj5.free();
       }
+      if (!gotOCG) {
+	// if the "OCGs" array is "empty or contains references only
+	// to null or deleted objects", this OCMD doesn't have any
+	// effect
+	obj4.free();
+	obj3.free();
+	obj2.free();
+	return gFalse;
+      }
       obj4.free();
     }
     obj3.free();

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -113,7 +113,7 @@
 void OutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
 				int width, int height,
 				GfxImageColorMap *colorMap,
-				Stream *maskStr,
+				Object *maskRef, Stream *maskStr,
 				int maskWidth, int maskHeight,
 				GBool maskInvert, GBool interpolate) {
   drawImage(state, ref, str, width, height, colorMap, NULL, gFalse,
@@ -123,7 +123,7 @@
 void OutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
 				    int width, int height,
 				    GfxImageColorMap *colorMap,
-				    Stream *maskStr,
+				    Object *maskRef, Stream *maskStr,
 				    int maskWidth, int maskHeight,
 				    GfxImageColorMap *maskColorMap,
 				    double *matte, GBool interpolate) {

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/OutputDev.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -24,9 +24,7 @@
 struct GfxColor;
 class GfxColorSpace;
 class GfxImageColorMap;
-class GfxFunctionShading;
-class GfxAxialShading;
-class GfxRadialShading;
+class GfxShading;
 class Stream;
 class Links;
 class Link;
@@ -61,11 +59,6 @@
   // 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; }
@@ -166,13 +159,8 @@
 				 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 shadedFill(GfxState *state, GfxShading *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) {}
@@ -212,12 +200,13 @@
   virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
 			       int width, int height,
 			       GfxImageColorMap *colorMap,
-			       Stream *maskStr, int maskWidth, int maskHeight,
+			       Object *maskRef, Stream *maskStr,
+			       int maskWidth, int maskHeight,
 			       GBool maskInvert, GBool interpolate);
   virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
 				   int width, int height,
 				   GfxImageColorMap *colorMap,
-				   Stream *maskStr,
+				   Object *maskRef, Stream *maskStr,
 				   int maskWidth, int maskHeight,
 				   GfxImageColorMap *maskColorMap,
 				   double *matte, GBool interpolate);
@@ -253,10 +242,6 @@
   //----- links
   virtual void processLink(Link *link) {}
 
-#if 1 //~tmp: turn off anti-aliasing temporarily
-  virtual void setInShading(GBool sh) {}
-#endif
-
 private:
 
   double defCTM[6];		// default coordinate transform matrix

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -26,7 +26,7 @@
 #include "PDFDoc.h"
 #include "Link.h"
 #include "Annot.h"
-#include "Form.h"
+#include "AcroForm.h"
 #include "OptionalContent.h"
 #include "TileMap.h"
 #include "TileCache.h"
@@ -260,9 +260,15 @@
   if (page <= 0 || page > doc->getNumPages()) {
     return;
   }
-  if (scrollToTop ||
-      state->getDisplayMode() == displayContinuous ||
-      state->getDisplayMode() == displaySideBySideContinuous) {
+  if (!scrollToTop &&
+      (state->getDisplayMode() == displayContinuous ||
+       state->getDisplayMode() == displaySideBySideContinuous)) {
+    scrollY = tileMap->getPageTopY(page)
+              + (state->getScrollY()
+		 - tileMap->getPageTopY(tileMap->getFirstPage()));
+  } else if (scrollToTop ||
+	     state->getDisplayMode() == displayContinuous ||
+	     state->getDisplayMode() == displaySideBySideContinuous) {
     scrollY = tileMap->getPageTopY(page);
   } else if (scrollToBottom) {
     scrollY = tileMap->getPageBottomY(page);
@@ -679,19 +685,23 @@
 	state->getDisplayMode() == displaySideBySideContinuous) {
       next = state->getDisplayMode() == displaySideBySideContinuous ? 2 : 1;
       topPage = tileMap->getFirstPage();
-      topPageY = tileMap->getPageTopY(topPage);
-      sy = state->getScrollY();
-      dy = sy - topPageY;
-      // note: dy can be negative here if the inter-page gap is at the
-      // top of the window
-      if (-16 < dy && dy < 16) {
-	state->setScrollPosition(state->getScrollPage(), x, topPageY);
-      } else if (topPage + next <= doc->getNumPages()) {
-	topPage += next;
+      // NB: topPage can be out of bounds here, because the scroll
+      // position isn't adjusted until finishUpdate is called, below
+      if (topPage > 0 && topPage <= doc->getNumPages()) {
 	topPageY = tileMap->getPageTopY(topPage);
+	sy = state->getScrollY();
 	dy = sy - topPageY;
-	if (-16 < dy && dy < 0) {
+	// note: dy can be negative here if the inter-page gap is at the
+	// top of the window
+	if (-16 < dy && dy < 16) {
 	  state->setScrollPosition(state->getScrollPage(), x, topPageY);
+	} else if (topPage + next <= doc->getNumPages()) {
+	  topPage += next;
+	  topPageY = tileMap->getPageTopY(topPage);
+	  dy = sy - topPageY;
+	  if (-16 < dy && dy < 0) {
+	    state->setScrollPosition(state->getScrollPage(), x, topPageY);
+	  }
 	}
       }
     }
@@ -1100,7 +1110,7 @@
 
   // build the list of rectangles
   //~ this doesn't handle RtL, vertical, or rotated text
-  loadText(selectPage);
+  loadText(page);
   rects = new GList();
   if (begin.colIdx == end.colIdx &&
       begin.parIdx == end.parIdx &&
@@ -1108,9 +1118,9 @@
     // same line
     text->convertPosToPointUpper(&begin, &x0, &y0);
     text->convertPosToPointLower(&end, &x1, &y1);
-    cvtDevToUser(selectPage, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0);
-    cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1);
-    rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1));
+    cvtDevToUser(page, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0);
+    cvtDevToUser(page, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1);
+    rects->append(new SelectRect(page, ux0, uy0, ux1, uy1));
   } else if (begin.colIdx == end.colIdx) {
     // same column
     text->convertPosToPointUpper(&begin, &x0, &y0);
@@ -1117,42 +1127,42 @@
     text->convertPosToPointRightEdge(&begin, &x1, &y1);
     text->convertPosToPointLeftEdge(&end, &x2, &y2);
     text->convertPosToPointLower(&end, &x3, &y3);
-    cvtDevToUser(selectPage, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0);
-    cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1);
-    rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1));
-    cvtDevToUser(selectPage, (int)(x2 + 0.5), (int)(y1 + 0.5), &ux0, &uy0);
-    cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y2 + 0.5), &ux1, &uy1);
-    rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1));
-    cvtDevToUser(selectPage, (int)(x2 + 0.5), (int)(y2 + 0.5), &ux0, &uy0);
-    cvtDevToUser(selectPage, (int)(x3 + 0.5), (int)(y3 + 0.5), &ux1, &uy1);
-    rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1));
+    cvtDevToUser(page, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0);
+    cvtDevToUser(page, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1);
+    rects->append(new SelectRect(page, ux0, uy0, ux1, uy1));
+    cvtDevToUser(page, (int)(x2 + 0.5), (int)(y1 + 0.5), &ux0, &uy0);
+    cvtDevToUser(page, (int)(x1 + 0.5), (int)(y2 + 0.5), &ux1, &uy1);
+    rects->append(new SelectRect(page, ux0, uy0, ux1, uy1));
+    cvtDevToUser(page, (int)(x2 + 0.5), (int)(y2 + 0.5), &ux0, &uy0);
+    cvtDevToUser(page, (int)(x3 + 0.5), (int)(y3 + 0.5), &ux1, &uy1);
+    rects->append(new SelectRect(page, ux0, uy0, ux1, uy1));
   } else {
     // different columns
     text->convertPosToPointUpper(&begin, &x0, &y0);
     text->convertPosToPointRightEdge(&begin, &x1, &y1);
     text->getColumnLowerLeft(begin.colIdx, &x2, &y2);
-    cvtDevToUser(selectPage, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0);
-    cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1);
-    rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1));
-    cvtDevToUser(selectPage, (int)(x2 + 0.5), (int)(y1 + 0.5), &ux0, &uy0);
-    cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y2 + 0.5), &ux1, &uy1);
-    rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1));
+    cvtDevToUser(page, (int)(x0 + 0.5), (int)(y0 + 0.5), &ux0, &uy0);
+    cvtDevToUser(page, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux1, &uy1);
+    rects->append(new SelectRect(page, ux0, uy0, ux1, uy1));
+    cvtDevToUser(page, (int)(x2 + 0.5), (int)(y1 + 0.5), &ux0, &uy0);
+    cvtDevToUser(page, (int)(x1 + 0.5), (int)(y2 + 0.5), &ux1, &uy1);
+    rects->append(new SelectRect(page, ux0, uy0, ux1, uy1));
     for (colIdx = begin.colIdx + 1; colIdx < end.colIdx; ++colIdx) {
       text->getColumnLowerLeft(colIdx, &x0, &y0);
       text->getColumnUpperRight(colIdx, &x1, &y1);
-      cvtDevToUser(selectPage, (int)(x0 + 0.5), (int)(y1 + 0.5), &ux0, &uy0);
-      cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y0 + 0.5), &ux1, &uy1);
-      rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1));
+      cvtDevToUser(page, (int)(x0 + 0.5), (int)(y1 + 0.5), &ux0, &uy0);
+      cvtDevToUser(page, (int)(x1 + 0.5), (int)(y0 + 0.5), &ux1, &uy1);
+      rects->append(new SelectRect(page, ux0, uy0, ux1, uy1));
     }
     text->getColumnUpperRight(end.colIdx, &x0, &y0);
     text->convertPosToPointLeftEdge(&end, &x1, &y1);
     text->convertPosToPointLower(&end, &x2, &y2);
-    cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y0 + 0.5), &ux0, &uy0);
-    cvtDevToUser(selectPage, (int)(x0 + 0.5), (int)(y1 + 0.5), &ux1, &uy1);
-    rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1));
-    cvtDevToUser(selectPage, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux0, &uy0);
-    cvtDevToUser(selectPage, (int)(x2 + 0.5), (int)(y2 + 0.5), &ux1, &uy1);
-    rects->append(new SelectRect(selectPage, ux0, uy0, ux1, uy1));
+    cvtDevToUser(page, (int)(x1 + 0.5), (int)(y0 + 0.5), &ux0, &uy0);
+    cvtDevToUser(page, (int)(x0 + 0.5), (int)(y1 + 0.5), &ux1, &uy1);
+    rects->append(new SelectRect(page, ux0, uy0, ux1, uy1));
+    cvtDevToUser(page, (int)(x1 + 0.5), (int)(y1 + 0.5), &ux0, &uy0);
+    cvtDevToUser(page, (int)(x2 + 0.5), (int)(y2 + 0.5), &ux1, &uy1);
+    rects->append(new SelectRect(page, ux0, uy0, ux1, uy1));
   }
 
   // get window coord bboxes for old selection and new selection;
@@ -1264,10 +1274,33 @@
 }
 
 void PDFCore::finishSelectionDrag() {
-  selectPage = 0;
-  selectStartX = selectStartY = 0;
+  // nothing
 }
 
+void PDFCore::selectWord(int pg, int x, int y) {
+  TextPosition endPos;
+
+  loadText(pg);
+  if (text->findWordPoints(x, y, &selectStartPos, &endPos)) {
+    selectPage = pg;
+    setLinearSelection(pg, &selectStartPos, &endPos);
+  } else {
+    selectPage = 0;
+  }
+}
+
+void PDFCore::selectLine(int pg, int x, int y) {
+  TextPosition endPos;
+
+  loadText(pg);
+  if (text->findLinePoints(x, y, &selectStartPos, &endPos)) {
+    selectPage = pg;
+    setLinearSelection(pg, &selectStartPos, &endPos);
+  } else {
+    selectPage = 0;
+  }
+}
+
 GBool PDFCore::getSelection(int *pg, double *ulx, double *uly,
 			    double *lrx, double *lry) {
   SelectRect *rect;
@@ -1550,7 +1583,46 @@
   return gTrue;
 }
 
+GList *PDFCore::findAll(Unicode *u, int len, GBool caseSensitive,
+			GBool wholeWord, int firstPage, int lastPage) {
+  GList *results = new GList();
 
+  TextOutputDev *textOut = new TextOutputDev(NULL, &textOutCtrl, gFalse);
+  if (!textOut->isOk()) {
+    delete textOut;
+    return results;
+  }
+
+  for (int pg = firstPage; pg <= lastPage; ++pg) {
+    doc->displayPage(textOut, pg, 72, 72, 0, gFalse, gTrue, gFalse);
+    GBool first = gTrue;
+    while (1) {
+      double xMin, yMin, xMax, yMax;
+      if (!textOut->findText(u, len, first, gTrue, !first, gFalse,
+			    caseSensitive, gFalse, wholeWord,
+			    &xMin, &yMin, &xMax, &yMax)) {
+	break;
+      }
+      double uxMin, uyMin, uxMax, uyMax, t;
+      textOut->cvtDevToUser(xMin, yMin, &uxMin, &uyMin);
+      textOut->cvtDevToUser(xMax, yMax, &uxMax, &uyMax);
+      if (uxMin > uxMax) {
+	t = uxMin;  uxMin = uxMax;  uxMax = t;
+      }
+      if (uyMin > uyMax) {
+	t = uyMin;  uyMin = uyMax;  uyMax = t;
+      }
+      results->append(new FindResult(pg, uxMin, uyMin, uxMax, uyMax));
+      first = gFalse;
+    }
+  }
+
+  delete textOut;
+
+  return results;
+}
+
+
 GBool PDFCore::cvtWindowToUser(int xw, int yw,
 			       int *pg, double *xu, double *yu) {
   return tileMap->cvtWindowToUser(xw, yw, pg, xu, yu);
@@ -1683,7 +1755,7 @@
   return annots->getAnnot(idx);
 }
 
-FormField *PDFCore::findFormField(int pg, double x, double y) {
+AcroFormField *PDFCore::findFormField(int pg, double x, double y) {
   if (!doc->getCatalog()->getForm()) {
     return NULL;
   }
@@ -1697,7 +1769,7 @@
   return doc->getCatalog()->getForm()->findFieldIdx(pg, x, y);
 }
 
-FormField *PDFCore::getFormField(int idx) {
+AcroFormField *PDFCore::getFormField(int idx) {
   if (!doc->getCatalog()->getForm()) {
     return NULL;
   }

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFCore.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -32,7 +32,7 @@
 class LinkAction;
 class Annot;
 class Annots;
-class FormField;
+class AcroFormField;
 class TextPage;
 class HighlightFile;
 class OptionalContentGroup;
@@ -66,6 +66,17 @@
 };
 
 //------------------------------------------------------------------------
+// FindResult
+//------------------------------------------------------------------------
+
+struct FindResult {
+  FindResult(int pageA, double xMinA, double yMinA, double xMaxA, double yMaxA)
+    : page(pageA), xMin(xMinA), yMin(yMinA), xMax(xMaxA), yMax(yMaxA) {}
+  int page;
+  double xMin, yMin, xMax, yMax;
+};
+
+//------------------------------------------------------------------------
 // PDFCore
 //------------------------------------------------------------------------
 
@@ -176,6 +187,8 @@
   void startSelectionDrag(int pg, int x, int y);
   void moveSelectionDrag(int pg, int x, int y);
   void finishSelectionDrag();
+  void selectWord(int pg, int x, int y);
+  void selectLine(int pg, int x, int y);
 
   // Retrieve the current selection.  This function uses user
   // coordinates.  Returns false if there is no selection.
@@ -198,6 +211,8 @@
   virtual GBool findU(Unicode *u, int len, GBool caseSensitive,
 		      GBool next, GBool backward, GBool wholeWord,
 		      GBool onePageOnly);
+  GList *findAll(Unicode *u, int len, GBool caseSensitive,
+		 GBool wholeWord, int firstPage, int lastPage);
 
 
   //----- coordinate conversion
@@ -242,9 +257,9 @@
   Annot *findAnnot(int pg, double x, double y);
   int findAnnotIdx(int pg, double x, double y);
   Annot *getAnnot(int idx);
-  FormField *findFormField(int pg, double x, double y);
+  AcroFormField *findFormField(int pg, double x, double y);
   int findFormFieldIdx(int pg, double x, double y);
-  FormField *getFormField(int idx);
+  AcroFormField *getFormField(int idx);
   GBool overText(int pg, double x, double y);
   void forceRedraw();
   void setTileDoneCbk(void (*cbk)(void *data), void *data);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -21,6 +21,7 @@
 #endif
 #include "gmempp.h"
 #include "GString.h"
+#include "gfile.h"
 #include "config.h"
 #include "GlobalParams.h"
 #include "Page.h"
@@ -113,6 +114,7 @@
   ok = setup(ownerPassword, userPassword);
 }
 
+#if 0
 #ifdef _WIN32
 PDFDoc::PDFDoc(wchar_t *fileNameA, int fileNameLen, GString *ownerPassword,
 	       GString *userPassword, PDFCore *coreA) {
@@ -122,14 +124,21 @@
 
   init(coreA);
 
+  // handle a Windows shortcut
+  wchar_t wPath[MAX_PATH + 1];
+  int n = fileNameLen < MAX_PATH ? fileNameLen : MAX_PATH;
+  memcpy(wPath, fileNameA, n * sizeof(wchar_t));
+  wPath[n] = L'\0';
+  readWindowsShortcut(wPath, MAX_PATH + 1);
+  int wPathLen = (int)wcslen(wPath);
+
   // save both Unicode and 8-bit copies of the file name
   fileName = new GString();
-  fileNameU = (wchar_t *)gmallocn(fileNameLen + 1, sizeof(wchar_t));
-  for (i = 0; i < fileNameLen; ++i) {
+  fileNameU = (wchar_t *)gmallocn(wPathLen + 1, sizeof(wchar_t));
+  memcpy(fileNameU, wPath, (wPathLen + 1) * sizeof(wchar_t));
+  for (i = 0; i < wPathLen; ++i) {
     fileName->append((char)fileNameA[i]);
-    fileNameU[i] = fileNameA[i];
   }
-  fileNameU[fileNameLen] = L'\0';
 
   // try to open file
   // NB: _wfopen is only available in NT
@@ -153,6 +162,7 @@
   ok = setup(ownerPassword, userPassword);
 }
 #endif
+#endif /* 0 */
 
 PDFDoc::PDFDoc(char *fileNameA, GString *ownerPassword,
 	       GString *userPassword, PDFCore *coreA) {
@@ -165,7 +175,7 @@
 /*
 #ifdef _WIN32
   Unicode u;
-  int n, i, j;
+  int i, j;
 #endif
 */
 
@@ -175,17 +185,19 @@
 
 #if defined(_WIN32)
 #if 0
-  n = 0;
+  wchar_t wPath[MAX_PATH + 1];
   i = 0;
-  while (getUTF8(fileName, &i, &u)) {
-    ++n;
+  j = 0;
+  while (j < MAX_PATH && getUTF8(fileName, &i, &u)) {
+    wPath[j++] = (wchar_t)u;
   }
-  fileNameU = (wchar_t *)gmallocn(n + 1, sizeof(wchar_t));
-  i = j = 0;
-  while (j < n && getUTF8(fileName, &i, &u)) {
-    fileNameU[j++] = (wchar_t)u;
-  }
-  fileNameU[n] = L'\0';
+  wPath[j] = L'\0';
+  readWindowsShortcut(wPath, MAX_PATH + 1);
+  int wPathLen = (int)wcslen(wPath);
+
+  fileNameU = (wchar_t *)gmallocn(wPathLen + 1, sizeof(wchar_t));
+  memcpy(fileNameU, wPath, (wPathLen + 1) * sizeof(wchar_t));
+
   // NB: _wfopen is only available in NT
   version.dwOSVersionInfoSize = sizeof(version);
   GetVersionEx(&version);
@@ -579,6 +591,18 @@
   return ret;
 }
 
+GBool PDFDoc::saveEmbeddedFileU(int idx, const char *path) {
+  FILE *f;
+  GBool ret;
+
+  if (!(f = openFile(path, "wb"))) {
+    return gFalse;
+  }
+  ret = saveEmbeddedFile2(idx, f);
+  fclose(f);
+  return ret;
+}
+
 #ifdef _WIN32
 GBool PDFDoc::saveEmbeddedFile(int idx, const wchar_t *path, int pathLen) {
   FILE *f;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PDFDoc.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -185,6 +185,7 @@
   int getEmbeddedFileNameLength(int idx)
     { return catalog->getEmbeddedFileNameLength(idx); }
   GBool saveEmbeddedFile(int idx, const char *path);
+  GBool saveEmbeddedFileU(int idx, const char *path);
 #ifdef _WIN32
   GBool saveEmbeddedFile(int idx, const wchar_t *path, int pathLen);
 #endif

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -40,7 +40,7 @@
 #include "XRef.h"
 #include "PreScanOutputDev.h"
 #include "CharCodeToUnicode.h"
-#include "Form.h"
+#include "AcroForm.h"
 #include "TextString.h"
 #if HAVE_SPLASH
 #  include "Splash.h"
@@ -1287,7 +1287,8 @@
 			 GBool manualCtrlA,
 			 PSOutCustomCodeCbk customCodeCbkA,
 			 void *customCodeCbkDataA,
-			 GBool honorUserUnitA) {
+			 GBool honorUserUnitA,
+			 GBool fileNameIsUTF8) {
   FILE *f;
   PSFileType fileTypeA;
 
@@ -1333,7 +1334,12 @@
 #endif
   } else {
     fileTypeA = psFile;
-    if (!(f = fopen(fileName, "w"))) {
+    if (fileNameIsUTF8) {
+      f = openFile(fileName, "w");
+    } else {
+      f = fopen(fileName, "w");
+    }
+    if (!f) {
       error(errIO, -1, "Couldn't open PostScript file '{0:s}'", fileName);
       ok = gFalse;
       return;
@@ -1737,7 +1743,7 @@
   Page *page;
   Dict *resDict;
   Annots *annots;
-  Form *form;
+  AcroForm *form;
   Object obj1, obj2, obj3;
   GString *s;
   GBool needDefaultFont;
@@ -1994,6 +2000,7 @@
   }
   patDict.free();
 
+
   //----- recursively scan SMask transparency groups in ExtGState dicts
   resDict->lookup("ExtGState", &gsDict);
   if (gsDict.isDict()) {
@@ -2211,13 +2218,15 @@
     if (fontLoc->locType == gfxFontLocResident &&
 	fontLoc->fontType >= fontCIDType0) {
       subst = gTrue;
-      if ((uMap = globalParams->getUnicodeMap(fontLoc->encoding))) {
-	fi->ff->encoding = fontLoc->encoding->copy();
-	uMap->decRefCnt();
-      } else {
-	error(errSyntaxError, -1,
-	      "Couldn't find Unicode map for 16-bit font encoding '{0:t}'",
-	      fontLoc->encoding);
+      if (!fi->ff->encoding) {
+	if ((uMap = globalParams->getUnicodeMap(fontLoc->encoding))) {
+	  fi->ff->encoding = fontLoc->encoding->copy();
+	  uMap->decRefCnt();
+	} else {
+	  error(errSyntaxError, -1,
+		"Couldn't find Unicode map for 16-bit font encoding '{0:t}'",
+		fontLoc->encoding);
+	}
       }
     }
 
@@ -2244,7 +2253,7 @@
 	if (font->getType() == fontTrueType &&
 	    !subst &&
 	    !((Gfx8BitFont *)font)->getHasEncoding()) {
-	  sprintf(buf, "c%02x", i+j);
+	  snprintf(buf, sizeof(buf), "c%02x", i+j);
 	  charName = buf;
 	} else {
 	  charName = ((Gfx8BitFont *)font)->getCharName(i+j);
@@ -2392,6 +2401,7 @@
   // open the font file
   if (!(fontFile = fopen(fileName->getCString(), "rb"))) {
     error(errIO, -1, "Couldn't open external font file");
+    delete psName;
     return NULL;
   }
 
@@ -3623,7 +3633,7 @@
 	 p[0] == 0x80 &&
 	 (p[1] == 0x01 || p[1] == 0x02)) {
     len = p[2] + (p[3] << 8) + (p[4] << 16) + (p[5] << 24);
-    if (len > remain - 6) {
+    if (len < 0 || len > remain - 6) {
       break;
     }
     if (p[1] == 0x01) {
@@ -4102,6 +4112,7 @@
   resObj.free();
 }
 
+
 GBool PSOutputDev::checkPageSlice(Page *page, double hDPI, double vDPI,
 				  int rotateA, GBool useMediaBox, GBool crop,
 				  int sliceX, int sliceY,
@@ -4441,9 +4452,17 @@
       landscape = gFalse;
     } else {
       rotate = (360 - state->getRotate()) % 360;
+      double scaledWidth = width * userUnit;
+      double scaledHeight = height * userUnit;
+      if (xScale0 > 0 && yScale0 > 0) {
+	scaledWidth *= xScale0;
+	scaledHeight *= yScale0;
+      }
       if (rotate == 0 || rotate == 180) {
-	if ((width < height && imgWidth > imgHeight && height > imgHeight) ||
-	    (width > height && imgWidth < imgHeight && width > imgWidth)) {
+	if ((scaledWidth < scaledHeight && imgWidth > imgHeight &&
+	     scaledHeight > imgHeight) ||
+	    (scaledWidth > scaledHeight && imgWidth < imgHeight &&
+	     scaledWidth > imgWidth)) {
 	  rotate += 90;
 	  landscape = gTrue;
 	} else {
@@ -4450,8 +4469,10 @@
 	  landscape = gFalse;
 	}
       } else { // rotate == 90 || rotate == 270
-	if ((height < width && imgWidth > imgHeight && width > imgHeight) ||
-	    (height > width && imgWidth < imgHeight && height > imgWidth)) {
+	if ((scaledHeight < scaledWidth && imgWidth > imgHeight &&
+	     scaledWidth > imgHeight) ||
+	    (scaledHeight > scaledWidth && imgWidth < imgHeight &&
+	     scaledHeight > imgWidth)) {
 	  rotate = 270 - rotate;
 	  landscape = gTrue;
 	} else {
@@ -5252,6 +5273,29 @@
   noStateChanges = gFalse;
 }
 
+GBool PSOutputDev::shadedFill(GfxState *state, GfxShading *shading) {
+  if (level != psLevel2 &&
+      level != psLevel2Sep &&
+      level != psLevel3 &&
+      level != psLevel3Sep) {
+    return gFalse;
+  }
+
+  switch (shading->getType()) {
+  case 1:
+    functionShadedFill(state, (GfxFunctionShading *)shading);
+    return gTrue;
+  case 2:
+    axialShadedFill(state, (GfxAxialShading *)shading);
+    return gTrue;
+  case 3:
+    radialShadedFill(state, (GfxRadialShading *)shading);
+    return gTrue;
+  default:
+    return gFalse;
+  }
+}
+
 GBool PSOutputDev::functionShadedFill(GfxState *state,
 				      GfxFunctionShading *shading) {
   double x0, y0, x1, y1;
@@ -5957,7 +6001,7 @@
 void PSOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str,
 				  int width, int height,
 				  GfxImageColorMap *colorMap,
-				  Stream *maskStr,
+				  Object *maskRef, Stream *maskStr,
 				  int maskWidth, int maskHeight,
 				  GBool maskInvert, GBool interpolate) {
   int len;
@@ -8301,7 +8345,7 @@
       writePSChar((char)*p);
       line += 2;
     } else if (*p < 0x20 || *p >= 0x80) {
-      sprintf(buf, "\\%03o", *p);
+      snprintf(buf, sizeof(buf), "\\%03o", *p);
       writePS(buf);
       line += 4;
     } else {
@@ -8351,7 +8395,7 @@
 	c == '(' || c == ')' || c == '<' || c == '>' ||
 	c == '[' || c == ']' || c == '{' || c == '}' ||
 	c == '/' || c == '%') {
-      sprintf(buf, "#%02x", c & 0xff);
+      snprintf(buf, sizeof(buf), "#%02x", c & 0xff);
       name2->append(buf);
     } else {
       name2->append(c);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PSOutputDev.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -38,6 +38,9 @@
 class GfxIndexedColorSpace;
 class GfxSeparationColorSpace;
 class GfxDeviceNColorSpace;
+class GfxFunctionShading;
+class GfxAxialShading;
+class GfxRadialShading;
 class PDFRectangle;
 class PSOutCustomColor;
 class PSOutputDev;
@@ -82,7 +85,8 @@
 	      GBool manualCtrlA = gFalse,
 	      PSOutCustomCodeCbk customCodeCbkA = NULL,
 	      void *customCodeCbkDataA = NULL,
-	      GBool honorUserUnitA = gFalse);
+	      GBool honorUserUnitA = gFalse,
+	      GBool fileNameIsUTF8 = gFalse);
 
   // Open a PSOutputDev that will write to a generic stream.
   PSOutputDev(PSOutputFunc outputFuncA, void *outputStreamA,
@@ -118,13 +122,6 @@
   // 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 || level == psLevel2Sep ||
-	     level == psLevel3 || level == psLevel3Sep; }
-
   // Does this device use drawForm()?  If this returns false,
   // form-type XObjects will be interpreted (i.e., unrolled).
   virtual GBool useDrawForm() { return preload; }
@@ -214,10 +211,7 @@
 				 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);
+  virtual GBool shadedFill(GfxState *state, GfxShading *shading);
 
   //----- path clipping
   virtual void clip(GfxState *state);
@@ -238,7 +232,8 @@
   virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
 			       int width, int height,
 			       GfxImageColorMap *colorMap,
-			       Stream *maskStr, int maskWidth, int maskHeight,
+			       Object *maskRef, Stream *maskStr,
+			       int maskWidth, int maskHeight,
 			       GBool maskInvert, GBool interpolate);
 
 #if OPI_SUPPORT
@@ -342,6 +337,10 @@
 			   double *mat, double *bbox,
 			   int x0, int y0, int x1, int y1,
 			   double xStep, double yStep);
+  GBool functionShadedFill(GfxState *state,
+  			   GfxFunctionShading *shading);
+  GBool axialShadedFill(GfxState *state, GfxAxialShading *shading);
+  GBool radialShadedFill(GfxState *state, GfxRadialShading *shading);
   void doPath(GfxPath *path);
   void doImageL1(Object *ref, GfxState *state,
 		 GfxImageColorMap *colorMap,

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Page.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Page.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Page.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -14,6 +14,7 @@
 
 #include <stddef.h>
 #include "gmempp.h"
+#include "Trace.h"
 #include "GlobalParams.h"
 #include "Object.h"
 #include "Array.h"
@@ -26,7 +27,7 @@
 #include "Gfx.h"
 #include "GfxState.h"
 #include "Annot.h"
-#include "Form.h"
+#include "AcroForm.h"
 #endif
 #include "Error.h"
 #include "Catalog.h"
@@ -335,7 +336,7 @@
   Gfx *gfx;
   Object obj;
   Annots *annotList;
-  Form *form;
+  AcroForm *form;
   int i;
 
   if (!out->checkPageSlice(this, hDPI, vDPI, rotate, useMediaBox, crop,
@@ -344,6 +345,8 @@
     return;
   }
 
+  traceBegin(this, "begin page");
+
   rotate += getRotate();
   if (rotate >= 360) {
     rotate -= 360;
@@ -404,7 +407,9 @@
   }
 
   delete gfx;
-#endif
+#endif // PDF_PARSER_ONLY
+
+  traceEnd(this, "end page");
 }
 
 void Page::makeBox(double hDPI, double vDPI, int rotate,

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Page.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Page.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Page.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -127,6 +127,7 @@
 
   // Get page parameters.
   int getNum() { return num; }
+  PageAttrs *getAttrs() { return attrs; }
   PDFRectangle *getMediaBox() { return attrs->getMediaBox(); }
   PDFRectangle *getCropBox() { return attrs->getCropBox(); }
   GBool isCropped() { return attrs->isCropped(); }

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -76,8 +76,7 @@
   }
 }
 
-GBool PreScanOutputDev::functionShadedFill(GfxState *state,
-					   GfxFunctionShading *shading) {
+GBool PreScanOutputDev::shadedFill(GfxState *state, GfxShading *shading) {
   if (shading->getColorSpace()->getMode() != csDeviceGray &&
       shading->getColorSpace()->getMode() != csCalGray) {
     gray = gFalse;
@@ -90,34 +89,6 @@
   return gTrue;
 }
 
-GBool PreScanOutputDev::axialShadedFill(GfxState *state,
-					GfxAxialShading *shading) {
-  if (shading->getColorSpace()->getMode() != csDeviceGray &&
-      shading->getColorSpace()->getMode() != csCalGray) {
-    gray = gFalse;
-  }
-  mono = gFalse;
-  if (state->getFillOpacity() != 1 ||
-      state->getBlendMode() != gfxBlendNormal) {
-    transparency = gTrue;
-  }
-  return gTrue;
-}
-
-GBool PreScanOutputDev::radialShadedFill(GfxState *state,
-					 GfxRadialShading *shading) {
-  if (shading->getColorSpace()->getMode() != csDeviceGray &&
-      shading->getColorSpace()->getMode() != csCalGray) {
-    gray = gFalse;
-  }
-  mono = gFalse;
-  if (state->getFillOpacity() != 1 ||
-      state->getBlendMode() != gfxBlendNormal) {
-    transparency = gTrue;
-  }
-  return gTrue;
-}
-
 void PreScanOutputDev::clip(GfxState *state) {
   //~ check for a rectangle "near" the edge of the page;
   //~   else set gdi to false
@@ -229,7 +200,7 @@
 				       Stream *str,
 				       int width, int height,
 				       GfxImageColorMap *colorMap,
-				       Stream *maskStr,
+				       Object *maskRef, Stream *maskStr,
 				       int maskWidth, int maskHeight,
 				       GBool maskInvert, GBool interpolate) {
   GfxColorSpace *colorSpace;
@@ -258,7 +229,7 @@
 					   Stream *str,
 					   int width, int height,
 					   GfxImageColorMap *colorMap,
-					   Stream *maskStr,
+					   Object *maskRef, Stream *maskStr,
 					   int maskWidth, int maskHeight,
 					   GfxImageColorMap *maskColorMap,
 					   double *matte, GBool interpolate) {

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/PreScanOutputDev.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -46,11 +46,6 @@
   // 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 gTrue; }
-
   // Does this device use beginType3Char/endType3Char?  Otherwise,
   // text in Type 3 fonts will be drawn with drawChar/drawString.
   virtual GBool interpretType3Chars() { return gTrue; }
@@ -72,10 +67,7 @@
 				 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);
+  virtual GBool shadedFill(GfxState *state, GfxShading *shading);
 
   //----- path clipping
   virtual void clip(GfxState *state);
@@ -99,12 +91,13 @@
   virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
 			       int width, int height,
 			       GfxImageColorMap *colorMap,
-			       Stream *maskStr, int maskWidth, int maskHeight,
+			       Object *maskRef, Stream *maskStr,
+			       int maskWidth, int maskHeight,
 			       GBool maskInvert, GBool interpolate);
   virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
 				   int width, int height,
 				   GfxImageColorMap *colorMap,
-				   Stream *maskStr,
+				   Object *maskRef, Stream *maskStr,
 				   int maskWidth, int maskHeight,
 				   GfxImageColorMap *maskColorMap,
 				   double *matte, GBool interpolate);

Added: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.cc	                        (rev 0)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -0,0 +1,1327 @@
+//========================================================================
+//
+// ShadingImage.cc
+//
+// Copyright 2020 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <math.h>
+#include "Trace.h"
+#include "GfxState.h"
+#include "SplashBitmap.h"
+#include "SplashPattern.h"
+#include "SplashPath.h"
+#include "Splash.h"
+#include "ShadingImage.h"
+
+// Max recursive depth for a patch mesh shading fill.
+#define patchMaxDepth 10
+
+// Max delta allowed in any color component for a patch mesh shading
+// fill.
+#define patchColorDelta (dblToCol(1 / 256.0))
+
+SplashBitmap *ShadingImage::generateBitmap(GfxState *state,
+					   GfxShading *shading,
+					   SplashColorMode mode,
+					   GBool reverseVideo,
+					   Splash *parentSplash,
+					   SplashBitmap *parentBitmap,
+					   int *xOut, int *yOut) {
+  switch (shading->getType()) {
+  case 1:
+    return generateFunctionBitmap(state, (GfxFunctionShading *)shading,
+				  mode, reverseVideo,
+				  parentSplash, parentBitmap, xOut, yOut);
+    break;
+  case 2:
+    return generateAxialBitmap(state, (GfxAxialShading *)shading,
+			       mode, reverseVideo,
+			       parentSplash, parentBitmap, xOut, yOut);
+    break;
+  case 3:
+    return generateRadialBitmap(state, (GfxRadialShading *)shading,
+				mode, reverseVideo,
+				parentSplash, parentBitmap, xOut, yOut);
+    break;
+  case 4:
+  case 5:
+    return generateGouraudTriangleBitmap(state,
+					 (GfxGouraudTriangleShading *)shading,
+					 mode, reverseVideo,
+					 parentSplash, parentBitmap,
+					 xOut, yOut);
+    break;
+  case 6:
+  case 7:
+    return generatePatchMeshBitmap(state, (GfxPatchMeshShading *)shading,
+				   mode, reverseVideo,
+				   parentSplash, parentBitmap, xOut, yOut);
+    break;
+  default:
+    return NULL;
+  }
+}
+
+SplashBitmap *ShadingImage::generateFunctionBitmap(GfxState *state,
+						   GfxFunctionShading *shading,
+						   SplashColorMode mode,
+						   GBool reverseVideo,
+						   Splash *parentSplash,
+						   SplashBitmap *parentBitmap,
+						   int *xOut, int *yOut) {
+  // get the shading parameters
+  double x0, y0, x1, y1;
+  shading->getDomain(&x0, &y0, &x1, &y1);
+  double *patternMat = shading->getMatrix();
+
+  // get the clip bbox
+  double fxMin, fyMin, fxMax, fyMax;
+  state->getClipBBox(&fxMin, &fyMin, &fxMax, &fyMax);
+  if (fxMin > fxMax || fyMin > fyMax) {
+    return NULL;
+  }
+
+  // convert to integer coords
+  int xMin = (int)floor(fxMin);
+  int yMin = (int)floor(fyMin);
+  int xMax = (int)floor(fxMax) + 1;
+  int yMax = (int)floor(fyMax) + 1;
+  int bitmapWidth = xMax - xMin;
+  int bitmapHeight = yMax - yMin;
+
+  // allocate the bitmap
+  traceMessage("function shading fill bitmap");
+  SplashBitmap *bitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, mode,
+					  gTrue, gTrue, parentBitmap);
+  int nComps = splashColorModeNComps[mode];
+
+  // compute the domain -> device space transform = mat * CTM
+  double *ctm = state->getCTM();
+  double mat[6];
+  mat[0] = patternMat[0] * ctm[0] + patternMat[1] * ctm[2];
+  mat[1] = patternMat[0] * ctm[1] + patternMat[1] * ctm[3];
+  mat[2] = patternMat[2] * ctm[0] + patternMat[3] * ctm[2];
+  mat[3] = patternMat[2] * ctm[1] + patternMat[3] * ctm[3];
+  mat[4] = patternMat[4] * ctm[0] + patternMat[5] * ctm[2] + ctm[4];
+  mat[5] = patternMat[4] * ctm[1] + patternMat[5] * ctm[3] + ctm[5];
+
+  // compute the device space -> domain transform
+  double det = mat[0] * mat[3] - mat[1] * mat[2];
+  if (fabs(det) < 0.000001) {
+    return NULL;
+  }
+  det = 1 / det;
+  double iMat[6];
+  iMat[0] = mat[3] * det;
+  iMat[1] = -mat[1] * det;
+  iMat[2] = -mat[2] * det;
+  iMat[3] = mat[0] * det;
+  iMat[4] = (mat[2] * mat[5] - mat[3] * mat[4]) * det;
+  iMat[5] = (mat[1] * mat[4] - mat[0] * mat[5]) * det;
+
+  // fill the bitmap
+  SplashColorPtr dataPtr = bitmap->getDataPtr();
+  Guchar *alphaPtr = bitmap->getAlphaPtr();
+  for (int y = 0; y < bitmapHeight; ++y) {
+    for (int x = 0; x < bitmapWidth; ++x) {
+
+      // convert coords to the pattern domain
+      double tx = xMin + x + 0.5;
+      double ty = yMin + y + 0.5;
+      double xx = tx * iMat[0] + ty * iMat[2] + iMat[4];
+      double yy = tx * iMat[1] + ty * iMat[3] + iMat[5];
+
+      // get the color
+      if (xx >= x0 && xx <= x1 && yy >= y0 && yy <= y1) {
+	GfxColor color;
+	shading->getColor(xx, yy, &color);
+	SplashColor sColor;
+	computeShadingColor(state, mode, reverseVideo, &color, sColor);
+	for (int i = 0; i < nComps; ++i) {
+	  *dataPtr++ = sColor[i];
+	}
+	*alphaPtr++ = 0xff;
+      } else {
+	dataPtr += nComps;
+	*alphaPtr++ = 0;
+      }
+    }
+  }
+
+  *xOut = xMin;
+  *yOut = yMin;
+  return bitmap;
+}
+
+SplashBitmap *ShadingImage::generateAxialBitmap(GfxState *state,
+						GfxAxialShading *shading,
+						SplashColorMode mode,
+						GBool reverseVideo,
+						Splash *parentSplash,
+						SplashBitmap *parentBitmap,
+						int *xOut, int *yOut) {
+  // get the shading parameters
+  double x0, y0, x1, y1;
+  shading->getCoords(&x0, &y0, &x1, &y1);
+  double t0 = shading->getDomain0();
+  double t1 = shading->getDomain1();
+  GBool ext0 = shading->getExtend0();
+  GBool ext1 = shading->getExtend1();
+  double dx = x1 - x0;
+  double dy = y1 - y0;
+  double d = dx * dx + dy * dy;
+  GBool dZero = fabs(d) < 0.0001;
+  if (!dZero) {
+    d = 1 / d;
+  }
+  if (dZero && !ext0 && !ext1) {
+    return NULL;
+  }
+
+  // get the clip bbox
+  double fxMin, fyMin, fxMax, fyMax;
+  state->getClipBBox(&fxMin, &fyMin, &fxMax, &fyMax);
+  if (fxMin > fxMax || fyMin > fyMax) {
+    return NULL;
+  }
+
+  // convert to integer coords
+  int xMin = (int)floor(fxMin);
+  int yMin = (int)floor(fyMin);
+  int xMax = (int)floor(fxMax) + 1;
+  int yMax = (int)floor(fyMax) + 1;
+  int bitmapWidth = xMax - xMin;
+  int bitmapHeight = yMax - yMin;
+
+  // compute the inverse CTM
+  double *ctm = state->getCTM();
+  double det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
+  if (fabs(det) < 0.000001) {
+    return NULL;
+  }
+  det = 1 / det;
+  double ictm[6];
+  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;
+
+  // convert axis endpoints to device space
+  double xx0, yy0, xx1, yy1;
+  state->transform(x0, y0, &xx0, &yy0);
+  state->transform(x1, y1, &xx1, &yy1);
+
+  // allocate the bitmap
+  traceMessage("axial shading fill bitmap");
+  SplashBitmap *bitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, mode,
+					  gTrue, gTrue, parentBitmap);
+  int nComps = splashColorModeNComps[mode];
+
+  // special case: zero-length axis
+  if (dZero) {
+    GfxColor color;
+    if (ext0) {
+      shading->getColor(t0, &color);
+    } else {
+      shading->getColor(t1, &color);
+    }
+    SplashColor sColor;
+    computeShadingColor(state, mode, reverseVideo, &color, sColor);
+    SplashColorPtr dataPtr = bitmap->getDataPtr();
+    for (int y = 0; y < bitmapHeight; ++y) {
+      for (int x = 0; x < bitmapWidth; ++x) {
+	for (int i = 0; i < nComps; ++i) {
+	  *dataPtr++ = sColor[i];
+	}
+      }
+    }
+    memset(bitmap->getAlphaPtr(), 0xff, (size_t)bitmapWidth * bitmapHeight);
+
+  // special case: horizontal axis (in device space)
+  } else if (fabs(yy0 - yy1) < 0.01) {
+    for (int x = 0; x < bitmapWidth; ++x) {
+      SplashColorPtr dataPtr = bitmap->getDataPtr() + x * nComps;
+      Guchar *alphaPtr = bitmap->getAlphaPtr() + x;
+      double tx = xMin + x + 0.5;
+      double ty = yMin + 0.5;
+      double xx = tx * ictm[0] + ty * ictm[2] + ictm[4];
+      double yy = tx * ictm[1] + ty * ictm[3] + ictm[5];
+      double s = ((xx - x0) * dx + (yy - y0) * dy) * d;
+      GBool go = gFalse;
+      if (s < 0) {
+	go = ext0;
+      } else if (s > 1) {
+	go = ext1;
+      } else {
+	go = gTrue;
+      }
+      if (go) {
+	GfxColor color;
+	if (s <= 0) {
+	  shading->getColor(t0, &color);
+	} else if (s >= 1) {
+	  shading->getColor(t1, &color);
+	} else {
+	  double t = t0 + s * (t1 - t0);
+	  shading->getColor(t, &color);
+	}
+	SplashColor sColor;
+	computeShadingColor(state, mode, reverseVideo, &color, sColor);
+	for (int y = 0; y < bitmapHeight; ++y) {
+	  for (int i = 0; i < nComps; ++i) {
+	    dataPtr[i] = sColor[i];
+	  }
+	  dataPtr += bitmap->getRowSize();
+	  *alphaPtr = 0xff;
+	  alphaPtr += bitmapWidth;
+	}
+      } else {
+	for (int y = 0; y < bitmapHeight; ++y) {
+	  *alphaPtr = 0;
+	  alphaPtr += bitmapWidth;
+	}
+      }
+    }
+
+  // special case: vertical axis (in device space)
+  } else if (fabs(xx0 - xx1) < 0.01) {
+    for (int y = 0; y < bitmapHeight; ++y) {
+      SplashColorPtr dataPtr = bitmap->getDataPtr() + y * bitmap->getRowSize();
+      Guchar *alphaPtr = bitmap->getAlphaPtr() + y * bitmapWidth;
+      double tx = xMin + 0.5;
+      double ty = yMin + y + 0.5;
+      double xx = tx * ictm[0] + ty * ictm[2] + ictm[4];
+      double yy = tx * ictm[1] + ty * ictm[3] + ictm[5];
+      double s = ((xx - x0) * dx + (yy - y0) * dy) * d;
+      GBool go = gFalse;
+      if (s < 0) {
+	go = ext0;
+      } else if (s > 1) {
+	go = ext1;
+      } else {
+	go = gTrue;
+      }
+      if (go) {
+	GfxColor color;
+	if (s <= 0) {
+	  shading->getColor(t0, &color);
+	} else if (s >= 1) {
+	  shading->getColor(t1, &color);
+	} else {
+	  double t = t0 + s * (t1 - t0);
+	  shading->getColor(t, &color);
+	}
+	SplashColor sColor;
+	computeShadingColor(state, mode, reverseVideo, &color, sColor);
+	for (int x = 0; x < bitmapWidth; ++x) {
+	  for (int i = 0; i < nComps; ++i) {
+	    dataPtr[i] = sColor[i];
+	  }
+	  dataPtr += nComps;
+	}
+	memset(alphaPtr, 0xff, bitmapWidth);
+      } else {
+	memset(alphaPtr, 0, bitmapWidth);
+      }
+    }
+
+  // general case
+  } else {
+    // pre-compute colors along the axis
+    int nColors = (int)(1.5 * sqrt((xx1 - xx0) * (xx1 - xx0)
+				   + (yy1 - yy0) * (yy1 - yy0)));
+    if (nColors < 16) {
+      nColors = 16;
+    } else if (nColors > 1024) {
+      nColors = 1024;
+    }
+    SplashColorPtr sColors = (SplashColorPtr)gmallocn(nColors, nComps);
+    SplashColorPtr sColor = sColors;
+    for (int i = 0; i < nColors; ++i) {
+      double s = (double)i / (double)(nColors - 1);
+      double t = t0 + s * (t1 - t0);
+      GfxColor color;
+      shading->getColor(t, &color);
+      computeShadingColor(state, mode, reverseVideo, &color, sColor);
+      sColor += nComps;
+    }
+
+    SplashColorPtr dataPtr = bitmap->getDataPtr();
+    Guchar *alphaPtr = bitmap->getAlphaPtr();
+    for (int y = 0; y < bitmapHeight; ++y) {
+      for (int x = 0; x < bitmapWidth; ++x) {
+
+	// convert coords to user space
+	double tx = xMin + x + 0.5;
+	double ty = yMin + y + 0.5;
+	double xx = tx * ictm[0] + ty * ictm[2] + ictm[4];
+	double yy = tx * ictm[1] + ty * ictm[3] + ictm[5];
+
+	// compute the position along the axis
+	double s = ((xx - x0) * dx + (yy - y0) * dy) * d;
+	GBool go = gFalse;
+	if (s < 0) {
+	  go = ext0;
+	} else if (s > 1) {
+	  go = ext1;
+	} else {
+	  go = gTrue;
+	}
+	if (go) {
+	  if (s <= 0) {
+	    sColor = sColors;
+	  } else if (s >= 1) {
+	    sColor = sColors + (nColors - 1) * nComps;
+	  } else {
+	    int i = (int)((nColors - 1) * s + 0.5);
+	    sColor = sColors + i * nComps;
+	  }
+	  for (int i = 0; i < nComps; ++i) {
+	    *dataPtr++ = sColor[i];
+	  }
+	  *alphaPtr++ = 0xff;
+	} else {
+	  dataPtr += nComps;
+	  *alphaPtr++ = 0;
+	}
+      }
+    }
+    gfree(sColors);
+  }
+
+  *xOut = xMin;
+  *yOut = yMin;
+  return bitmap;
+}
+
+SplashBitmap *ShadingImage::generateRadialBitmap(GfxState *state,
+						 GfxRadialShading *shading,
+						 SplashColorMode mode,
+						 GBool reverseVideo,
+						 Splash *parentSplash,
+						 SplashBitmap *parentBitmap,
+						 int *xOut, int *yOut) {
+  // get the shading parameters
+  double x0, y0, r0, x1, y1, r1;
+  shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
+  double t0 = shading->getDomain0();
+  double t1 = shading->getDomain1();
+  GBool ext0 = shading->getExtend0();
+  GBool ext1 = shading->getExtend1();
+  double h = sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
+  GBool enclosed = fabs(r1 - r0) >= h;
+
+  // get the clip bbox
+  double fxMin, fyMin, fxMax, fyMax;
+  state->getClipBBox(&fxMin, &fyMin, &fxMax, &fyMax);
+  if (fxMin > fxMax || fyMin > fyMax) {
+    return NULL;
+  }
+
+  // intersect with shading region (in user space): if the extend
+  // flags are false (or just the larger extend flag is false, in the
+  // "enclosed" case), we can use the bbox for the two circles
+  if ((!ext0 && !ext1) ||
+      (enclosed && !(r0 > r1 ? ext0 : ext1))) {
+    double uxMin = (x0 - r0) < (x1 - r1) ? (x0 - r0) : (x1 - r1);
+    double uxMax = (x0 + r0) > (x1 + r1) ? (x0 + r0) : (x1 + r1);
+    double uyMin = (y0 - r0) < (y1 - r1) ? (y0 - r0) : (y1 - r1);
+    double uyMax = (y0 + r0) > (y1 + r1) ? (y0 + r0) : (y1 + r1);
+    double dxMin, dyMin, dxMax, dyMax;
+    transformBBox(state, uxMin, uyMin, uxMax, uyMax,
+		  &dxMin, &dyMin, &dxMax, &dyMax);
+    if (dxMin > fxMin) {
+      fxMin = dxMin;
+    }
+    if (dxMax < dxMax) {
+      fxMax = dxMax;
+    }
+    if (dyMin > fyMin) {
+      fyMin = dyMin;
+    }
+    if (dyMax < fyMax) {
+      fyMax = dyMax;
+    }
+    if (fxMin > fxMax || fyMin > fyMax) {
+      return NULL;
+    }
+  }
+
+  // convert to integer coords
+  int xMin = (int)floor(fxMin);
+  int yMin = (int)floor(fyMin);
+  int xMax = (int)floor(fxMax) + 1;
+  int yMax = (int)floor(fyMax) + 1;
+  int bitmapWidth = xMax - xMin;
+  int bitmapHeight = yMax - yMin;
+
+  // compute the inverse CTM
+  double *ctm = state->getCTM();
+  double det = ctm[0] * ctm[3] - ctm[1] * ctm[2];
+  if (fabs(det) < 0.000001) {
+    return NULL;
+  }
+  det = 1 / det;
+  double ictm[6];
+  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;
+
+  // allocate the bitmap
+  traceMessage("radial shading fill bitmap");
+  SplashBitmap *bitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, mode,
+					  gTrue, gTrue, parentBitmap);
+  int nComps = splashColorModeNComps[mode];
+
+  // pre-compute colors along the axis
+  int nColors = (int)sqrt((double)(bitmapWidth * bitmapWidth
+				   + bitmapHeight * bitmapHeight));
+  if (nColors < 16) {
+    nColors = 16;
+  } else if (nColors > 1024) {
+    nColors = 1024;
+  }
+  SplashColorPtr sColors = (SplashColorPtr)gmallocn(nColors, nComps);
+  SplashColorPtr sColor = sColors;
+  for (int i = 0; i < nColors; ++i) {
+    double s = (double)i / (double)(nColors - 1);
+    double t = t0 + s * (t1 - t0);
+    GfxColor color;
+    shading->getColor(t, &color);
+    computeShadingColor(state, mode, reverseVideo, &color, sColor);
+    sColor += nComps;
+  }
+
+  // special case: in the "enclosed" + extended case, we can fill the
+  // bitmap with the outer color and just render inside the larger
+  // circle
+  int bxMin, byMin, bxMax, byMax;
+  if (enclosed &&
+      ((r0 > r1 && ext0) || (r1 > r0 && ext1))) {
+    double uxMin, uyMin, uxMax, uyMax;
+    if (r0 > r1) {
+      sColor = sColors;
+      uxMin = x0 - r0;
+      uxMax = x0 + r0;
+      uyMin = y0 - r0;
+      uyMax = y0 + r0;
+    } else {
+      sColor = sColors + (nColors - 1) * nComps;
+      uxMin = x1 - r1;
+      uxMax = x1 + r1;
+      uyMin = y1 - r1;
+      uyMax = y1 + r1;
+    }
+
+    // convert bbox of larger circle to device space
+    double dxMin, dyMin, dxMax, dyMax;
+    transformBBox(state, uxMin, uyMin, uxMax, uyMax,
+		  &dxMin, &dyMin, &dxMax, &dyMax);
+    bxMin = (int)floor(dxMin - xMin);
+    if (bxMin < 0) {
+      bxMin = 0;
+    }
+    byMin = (int)floor(dyMin - yMin);
+    if (byMin < 0) {
+      byMin = 0;
+    }
+    bxMax = (int)floor(dxMax - xMin) + 1;
+    if (bxMax > bitmapWidth) {
+      bxMax = bitmapWidth;
+    }
+    byMax = (int)floor(dyMax - yMin) + 1;
+    if (byMax > bitmapHeight) {
+      byMax = bitmapHeight;
+    }
+
+    // fill bitmap (except for the rectangle containing the larger circle)
+    SplashColorPtr dataPtr = bitmap->getDataPtr();
+    Guchar *alphaPtr = bitmap->getAlphaPtr();
+    for (int y = 0; y < bitmapHeight; ++y) {
+      for (int x = 0; x < bitmapWidth; ++x) {
+	if (y >= byMin && y < byMax && x >= bxMin && x < bxMax) {
+	  dataPtr += nComps;
+	  ++alphaPtr;
+	} else {
+	  for (int i = 0; i < nComps; ++i) {
+	    *dataPtr++ = sColor[i];
+	  }
+	  *alphaPtr++ = 0xff;
+	}
+      }
+    }
+
+  } else {
+    bxMin = 0;
+    byMin = 0;
+    bxMax = bitmapWidth;
+    byMax = bitmapHeight;
+  }
+
+  // render the shading into the bitmap
+  double dx = x1 - x0;
+  double dy = y1 - y0;
+  double dr = r1 - r0;
+  double r0dr = r0 * dr;
+  double r02 = r0 * r0;
+  double a = dx * dx + dy * dy - dr * dr;
+  GBool aIsZero;
+  double a2;
+  if (fabs(a) < 0.00001) {
+    aIsZero = gTrue;
+    a2 = 0;
+  } else {
+    aIsZero = gFalse;
+    a2 = 1 / (2 * a);
+  }
+  for (int y = byMin; y < byMax; ++y) {
+    SplashColorPtr dataPtr = bitmap->getDataPtr()
+                             + y * bitmap->getRowSize() + bxMin * nComps;
+    Guchar *alphaPtr = bitmap->getAlphaPtr()
+                       + y * bitmap->getAlphaRowSize() + bxMin;
+    for (int x = bxMin; x < bxMax; ++x) {
+
+      // convert coords to user space
+      double tx = xMin + x + 0.5;
+      double ty = yMin + y + 0.5;
+      double xx = tx * ictm[0] + ty * ictm[2] + ictm[4];
+      double yy = tx * ictm[1] + ty * ictm[3] + ictm[5];
+
+      // compute the radius of the circle at x,y
+      double b = 2 * ((xx - x0) * dx + (yy - y0) * dy + r0dr);
+      double c = (xx - x0) * (xx - x0) + (yy - y0) * (yy - y0) - r02;
+      double s = 0;
+      GBool go = gFalse;
+      if (aIsZero) {
+	if (fabs(b) < 0.000001) {
+	  if (c <= 0) {
+	    if (ext0) {
+	      s = 0;
+	      go = gTrue;
+	    }
+	  } else {
+	    if (ext1) {
+	      s = 1;
+	      go = gTrue;
+	    }
+	  }
+	} else {
+	  double s0 = c / b;
+	  double rs0 = r0 + s0 * (r1 - r0);
+	  if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) { 
+	    s = s0;
+	    go = gTrue;
+	  }
+	}
+      } else {
+	double e = b*b - 4*a*c;
+	if (e >= 0) {
+	  double es = sqrt(e);
+	  double s0 = (b + es) * a2;
+	  double s1 = (b - es) * a2;
+	  double rs0 = r0 + s0 * (r1 - r0);
+	  double rs1 = r0 + s1 * (r1 - r0);
+	  if (s0 > s1) {
+	    if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) {
+	      s = s0;
+	      go = gTrue;
+	    } else if ((s1 >= 0 || ext0) && (s1 <= 1 || ext1) && rs1 >= 0) {
+	      s = s1;
+	      go = gTrue;
+	    }
+	  } else {
+	    if ((s1 >= 0 || ext0) && (s1 <= 1 || ext1) && rs1 >= 0) {
+	      s = s1;
+	      go = gTrue;
+	    } else if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) {
+	      s = s0;
+	      go = gTrue;
+	    }
+	  }
+	}
+      }
+      if (!go) {
+	dataPtr += nComps;
+	*alphaPtr++ = 0x00;
+	continue;
+      }
+      if (s <= 0) {
+	sColor = sColors;
+      } else if (s >= 1) {
+	sColor = sColors + (nColors - 1) * nComps;
+      } else {
+	int i = (int)((nColors - 1) * s + 0.5);
+	sColor = sColors + i * nComps;
+      }
+      for (int i = 0; i < nComps; ++i) {
+	*dataPtr++ = sColor[i];
+      }
+      *alphaPtr++ = 0xff;
+    }
+  }
+
+  gfree(sColors);
+
+  *xOut = xMin;
+  *yOut = yMin;
+  return bitmap;
+}
+
+SplashBitmap *ShadingImage::generateGouraudTriangleBitmap(
+					GfxState *state,
+					GfxGouraudTriangleShading *shading,
+					SplashColorMode mode,
+					GBool reverseVideo,
+					Splash *parentSplash,
+					SplashBitmap *parentBitmap,
+					int *xOut, int *yOut) {
+  // get the clip bbox
+  double fxMin, fyMin, fxMax, fyMax;
+  state->getClipBBox(&fxMin, &fyMin, &fxMax, &fyMax);
+  if (fxMin > fxMax || fyMin > fyMax) {
+    return NULL;
+  }
+
+  // get the shading bbox
+  double tx0, ty0, tx1, ty1, dx, dy, txMin, tyMin, txMax, tyMax;
+  shading->getBBox(&tx0, &ty0, &tx1, &ty1);
+  state->transform(tx0, ty0, &dx, &dy);
+  txMin = txMax = dx;
+  tyMin = tyMax = dy;
+  state->transform(tx0, ty1, &dx, &dy);
+  if (dx < txMin) {
+    txMin = dx;
+  } else if (dx > txMax) {
+    txMax = dx;
+  }
+  if (dy < tyMin) {
+    tyMin = dy;
+  } else if (dy > tyMax) {
+    tyMax = dy;
+  }
+  state->transform(tx1, ty0, &dx, &dy);
+  if (dx < txMin) {
+    txMin = dx;
+  } else if (dx > txMax) {
+    txMax = dx;
+  }
+  if (dy < tyMin) {
+    tyMin = dy;
+  } else if (dy > tyMax) {
+    tyMax = dy;
+  }
+  state->transform(tx1, ty1, &dx, &dy);
+  if (dx < txMin) {
+    txMin = dx;
+  } else if (dx > txMax) {
+    txMax = dx;
+  }
+  if (dy < tyMin) {
+    tyMin = dy;
+  } else if (dy > tyMax) {
+    tyMax = dy;
+  }
+  if (txMin > fxMin) {
+    fxMin = txMin;
+  }
+  if (txMax < fxMax) {
+    fxMax = txMax;
+  }
+  if (tyMin > fyMin) {
+    fyMin = tyMin;
+  }
+  if (tyMax < fyMax) {
+    fyMax = tyMax;
+  }
+  if (fxMin > fxMax || fyMin > fyMax) {
+    return NULL;
+  }
+
+  // convert to integer coords
+  int xMin = (int)floor(fxMin);
+  int yMin = (int)floor(fyMin);
+  int xMax = (int)floor(fxMax) + 1;
+  int yMax = (int)floor(fyMax) + 1;
+  int bitmapWidth = xMax - xMin;
+  int bitmapHeight = yMax - yMin;
+
+  // allocate the bitmap
+  traceMessage("Gouraud triangle shading fill bitmap");
+  SplashBitmap *bitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, mode,
+					  gTrue, gTrue, parentBitmap);
+
+  // clear the bitmap
+  memset(bitmap->getDataPtr(), 0, bitmap->getHeight() * bitmap->getRowSize());
+  memset(bitmap->getAlphaPtr(), 0, bitmap->getHeight() * bitmap->getWidth());
+
+  // draw the triangles
+  for (int i = 0; i < shading->getNTriangles(); ++i) {
+    double x0, y0, x1, y1, x2, y2;
+    double color0[gfxColorMaxComps];
+    double color1[gfxColorMaxComps];
+    double color2[gfxColorMaxComps];
+    shading->getTriangle(i, &x0, &y0, color0,
+			 &x1, &y1, color1,
+			 &x2, &y2, color2);
+    gouraudFillTriangle(state, bitmap, mode, reverseVideo,
+			xMin, yMin, xMax, yMax,
+			x0, y0, color0, x1, y1, color1, x2, y2, color2,
+			shading);
+  }
+
+  *xOut = xMin;
+  *yOut = yMin;
+  return bitmap;
+}
+
+void ShadingImage::gouraudFillTriangle(GfxState *state, SplashBitmap *bitmap,
+				       SplashColorMode mode,
+				       GBool reverseVideo,
+				       int xMin, int yMin, int xMax, int yMax,
+				       double x0, double y0, double *color0,
+				       double x1, double y1, double *color1,
+				       double x2, double y2, double *color2,
+				       GfxGouraudTriangleShading *shading) {
+  int nShadingComps = shading->getNComps();
+  int nBitmapComps = splashColorModeNComps[mode];
+
+  //--- transform the vertices to device space, sort by y
+  double dx0, dy0, dx1, dy1, dx2, dy2;
+  state->transform(x0, y0, &dx0, &dy0);
+  state->transform(x1, y1, &dx1, &dy1);
+  state->transform(x2, y2, &dx2, &dy2);
+  if (dy0 > dy1) {
+    double t = dx0;  dx0 = dx1;  dx1 = t;
+    t = dy0;  dy0 = dy1;  dy1 = t;
+    double *tc = color0;  color0 = color1;  color1 = tc;
+  }
+  if (dy1 > dy2) {
+    double t = dx1;  dx1 = dx2;  dx2 = t;
+    t = dy1;  dy1 = dy2;  dy2 = t;
+    double *tc = color1;  color1 = color2;  color2 = tc;
+  }
+  if (dy0 > dy1) {
+    double t = dx0;  dx0 = dx1;  dx1 = t;
+    t = dy0;  dy0 = dy1;  dy1 = t;
+    double *tc = color0;  color0 = color1;  color1 = tc;
+  }
+
+  //--- y loop
+  int syMin = (int)floor(dy0);
+  if (syMin < yMin) {
+    syMin = yMin;
+  }
+  int syMax = (int)floor(dy2) + 1;
+  if (syMax > yMax) {
+    syMax = yMax;
+  }
+  for (int sy = syMin; sy < syMax; ++sy) {
+
+    //--- vertical interpolation
+    double xx0, xx1;
+    double cc0[gfxColorMaxComps], cc1[gfxColorMaxComps];
+    if (sy <= dy0) {
+      xx0 = xx1 = dx0;
+      for (int i = 0; i < nShadingComps; ++i) {
+	cc0[i] = cc1[i] = color0[i];
+      }
+    } else if (sy >= dy2) {
+      xx0 = xx1 = dx2;
+      for (int i = 0; i < nShadingComps; ++i) {
+	cc0[i] = cc1[i] = color2[i];
+      }
+    } else {
+      if (sy <= dy1) {
+	double interp = (sy - dy0) / (dy1 - dy0);
+	xx0 = dx0 + interp * (dx1 - dx0);
+	for (int i = 0; i < nShadingComps; ++i) {
+	  cc0[i] = color0[i] + interp * (color1[i] - color0[i]);
+	}
+      } else {
+	double interp = (sy - dy1) / (dy2 - dy1);
+	xx0 = dx1 + interp * (dx2 - dx1);
+	for (int i = 0; i < nShadingComps; ++i) {
+	  cc0[i] = color1[i] + interp * (color2[i] - color1[i]);
+	}
+      }
+      double interp = (sy - dy0) / (dy2 - dy0);
+      xx1 = dx0 + interp * (dx2 - dx0);
+      for (int i = 0; i < nShadingComps; ++i) {
+	cc1[i] = color0[i] + interp * (color2[i] - color0[i]);
+      }
+    }
+
+    //--- x loop
+    if (xx0 > xx1) {
+      double t = xx0;  xx0 = xx1;  xx1 = t;
+      for (int i = 0; i < nShadingComps; ++i) {
+	t = cc0[i];  cc0[i] = cc1[i];  cc1[i] = t;
+      }
+    }
+    int sxMin = (int)floor(xx0);
+    if (sxMin < xMin) {
+      sxMin = xMin;
+    }
+    int sxMax = (int)floor(xx1) + 1;
+    if (sxMax > xMax) {
+      sxMax = xMax;
+    }
+    SplashColorPtr dataPtr = bitmap->getDataPtr()
+                             + (sy - yMin) * bitmap->getRowSize()
+                             + (sxMin - xMin) * nBitmapComps;
+    if (sxMin < sxMax) {
+      Guchar *alphaPtr = bitmap->getAlphaPtr()
+	                 + (sy - yMin) * bitmap->getWidth()
+	                 + (sxMin - xMin);
+      memset(alphaPtr, 0xff, sxMax - sxMin);
+    }
+    for (int sx = sxMin; sx < sxMax; ++sx) {
+
+      //--- horizontal interpolation
+      double cc[gfxColorMaxComps];
+      if (sx <= xx0) {
+	for (int i = 0; i < nShadingComps; ++i) {
+	  cc[i] = cc0[i];
+	}
+      } else if (sx >= xx1) {
+	for (int i = 0; i < nShadingComps; ++i) {
+	  cc[i] = cc1[i];
+	}
+      } else {
+	for (int i = 0; i < nShadingComps; ++i) {
+	  double interp = (sx - xx0) / (xx1 - xx0);
+	  cc[i] = cc0[i] + interp * (cc1[i] - cc0[i]);
+	}
+      }
+
+      //--- compute color and set pixel
+      GfxColor gColor;
+      shading->getColor(cc, &gColor);
+      SplashColor sColor;
+      computeShadingColor(state, mode, reverseVideo, &gColor, sColor);
+      for (int i = 0; i < nBitmapComps; ++i) {
+	dataPtr[i] = sColor[i];
+      }
+      dataPtr += nBitmapComps;
+    }
+  }
+}
+
+SplashBitmap *ShadingImage::generatePatchMeshBitmap(
+					GfxState *state,
+					GfxPatchMeshShading *shading,
+					SplashColorMode mode,
+					GBool reverseVideo,
+					Splash *parentSplash,
+					SplashBitmap *parentBitmap,
+					int *xOut, int *yOut) {
+  // get the clip bbox
+  double fxMin, fyMin, fxMax, fyMax;
+  state->getClipBBox(&fxMin, &fyMin, &fxMax, &fyMax);
+  if (fxMin > fxMax || fyMin > fyMax) {
+    return NULL;
+  }
+
+  // get the shading bbox
+  double tx0, ty0, tx1, ty1, dx, dy, txMin, tyMin, txMax, tyMax;
+  shading->getBBox(&tx0, &ty0, &tx1, &ty1);
+  state->transform(tx0, ty0, &dx, &dy);
+  txMin = txMax = dx;
+  tyMin = tyMax = dy;
+  state->transform(tx0, ty1, &dx, &dy);
+  if (dx < txMin) {
+    txMin = dx;
+  } else if (dx > txMax) {
+    txMax = dx;
+  }
+  if (dy < tyMin) {
+    tyMin = dy;
+  } else if (dy > tyMax) {
+    tyMax = dy;
+  }
+  state->transform(tx1, ty0, &dx, &dy);
+  if (dx < txMin) {
+    txMin = dx;
+  } else if (dx > txMax) {
+    txMax = dx;
+  }
+  if (dy < tyMin) {
+    tyMin = dy;
+  } else if (dy > tyMax) {
+    tyMax = dy;
+  }
+  state->transform(tx1, ty1, &dx, &dy);
+  if (dx < txMin) {
+    txMin = dx;
+  } else if (dx > txMax) {
+    txMax = dx;
+  }
+  if (dy < tyMin) {
+    tyMin = dy;
+  } else if (dy > tyMax) {
+    tyMax = dy;
+  }
+  if (txMin > fxMin) {
+    fxMin = txMin;
+  }
+  if (txMax < fxMax) {
+    fxMax = txMax;
+  }
+  if (tyMin > fyMin) {
+    fyMin = tyMin;
+  }
+  if (tyMax < fyMax) {
+    fyMax = tyMax;
+  }
+  if (fxMin > fxMax || fyMin > fyMax) {
+    return NULL;
+  }
+
+  // convert to integer coords
+  int xMin = (int)floor(fxMin);
+  int yMin = (int)floor(fyMin);
+  int xMax = (int)floor(fxMax) + 1;
+  int yMax = (int)floor(fyMax) + 1;
+  int bitmapWidth = xMax - xMin;
+  int bitmapHeight = yMax - yMin;
+
+  // allocate the bitmap
+  traceMessage("Gouraud triangle shading fill bitmap");
+  SplashBitmap *bitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1, mode,
+					  gTrue, gTrue, parentBitmap);
+
+  // allocate a Splash object
+  // vector antialiasing is disabled to avoid artifacts along triangle edges
+  Splash *splash = new Splash(bitmap, gFalse,
+			      parentSplash->getImageCache(),
+			      parentSplash->getScreen());
+  SplashColor zero;
+  for (int i = 0; i < splashColorModeNComps[mode]; ++i) {
+    zero[i] = 0;
+  }
+  splash->clear(zero, 0x00);
+
+  // draw the patches
+  int start;
+  if (shading->getNPatches() > 128) {
+    start = 3;
+  } else if (shading->getNPatches() > 64) {
+    start = 2;
+  } else if (shading->getNPatches() > 16) {
+    start = 1;
+  } else {
+    start = 0;
+  }
+  for (int i = 0; i < shading->getNPatches(); ++i) {
+    fillPatch(state, splash, mode, reverseVideo,
+	      xMin, yMin, shading->getPatch(i), shading, start);
+  }
+
+  delete splash;
+
+  *xOut = xMin;
+  *yOut = yMin;
+  return bitmap;
+}
+
+void ShadingImage::fillPatch(GfxState *state, Splash *splash,
+			     SplashColorMode mode, GBool reverseVideo,
+			     int xMin, int yMin,
+			     GfxPatch *patch,
+			     GfxPatchMeshShading *shading,
+			     int depth) {
+  GfxColor c00;
+  shading->getColor(patch->color[0][0], &c00);
+  GBool stop = gFalse;
+
+  // stop subdivision at max depth
+  if (depth == patchMaxDepth) {
+    stop = gTrue;
+  }
+
+  // stop subdivision if colors are close enough
+  if (!stop) {
+    int nComps = shading->getColorSpace()->getNComps();
+    GfxColor c01, c10, c11;
+    shading->getColor(patch->color[0][1], &c01);
+    shading->getColor(patch->color[1][0], &c10);
+    shading->getColor(patch->color[1][1], &c11);
+    int i;
+    for (i = 0; i < nComps; ++i) {
+      if (abs(c00.c[i] - c01.c[i]) > patchColorDelta ||
+	  abs(c01.c[i] - c11.c[i]) > patchColorDelta ||
+	  abs(c11.c[i] - c10.c[i]) > patchColorDelta ||
+	  abs(c10.c[i] - c00.c[i]) > patchColorDelta) {
+	break;
+      }
+    }
+    if (i == nComps) {
+      stop = gTrue;
+    }
+  }
+
+  // stop subdivision if patch is small enough
+  if (!stop) {
+    double xxMin = 0;
+    double yyMin = 0;
+    double xxMax = 0;
+    double yyMax = 0;
+    for (int j = 0; j < 4; ++j) {
+      for (int i = 0; i < 4; ++i) {
+	double xx, yy;
+	state->transformDelta(patch->x[i][j], patch->y[i][j], &xx, &yy);
+	if (i == 0 && j == 0) {
+	  xxMin = xxMax = xx;
+	  yyMin = yyMax = yy;
+	} else {
+	  if (xx < xxMin) {
+	    xxMin = xx;
+	  } else if (xx > xxMax) {
+	    xxMax = xx;
+	  }
+	  if (yy < yyMin) {
+	    yyMin = yy;
+	  } else if (yy > yyMax) {
+	    yyMax = yy;
+	  }
+	}
+      }
+    }
+    if (xxMax - xxMin < 1 && yyMax - yyMin < 1) {
+      stop = gTrue;
+    }
+  }
+
+  // draw the patch
+  if (stop) {
+    SplashColor sColor;
+    computeShadingColor(state, mode, reverseVideo, &c00, sColor);
+    splash->setFillPattern(new SplashSolidColor(sColor));
+    SplashPath *path = new SplashPath();
+    double xx0, yy0, xx1, yy1, xx2, yy2, xx3, yy3;
+    state->transform(patch->x[0][0], patch->y[0][0], &xx0, &yy0);
+    path->moveTo(xx0 - xMin, yy0 - yMin);
+    state->transform(patch->x[0][1], patch->y[0][1], &xx1, &yy1);
+    state->transform(patch->x[0][2], patch->y[0][2], &xx2, &yy2);
+    state->transform(patch->x[0][3], patch->y[0][3], &xx3, &yy3);
+    path->curveTo(xx1 - xMin, yy1 - yMin, xx2 - xMin, yy2 - yMin,
+		  xx3 - xMin, yy3 - yMin);
+    state->transform(patch->x[1][3], patch->y[1][3], &xx1, &yy1);
+    state->transform(patch->x[2][3], patch->y[2][3], &xx2, &yy2);
+    state->transform(patch->x[3][3], patch->y[3][3], &xx3, &yy3);
+    path->curveTo(xx1 - xMin, yy1 - yMin, xx2 - xMin, yy2 - yMin,
+		  xx3 - xMin, yy3 - yMin);
+    state->transform(patch->x[3][2], patch->y[3][2], &xx1, &yy1);
+    state->transform(patch->x[3][1], patch->y[3][1], &xx2, &yy2);
+    state->transform(patch->x[3][0], patch->y[3][0], &xx3, &yy3);
+    path->curveTo(xx1 - xMin, yy1 - yMin, xx2 - xMin, yy2 - yMin,
+		  xx3 - xMin, yy3 - yMin);
+    state->transform(patch->x[2][0], patch->y[2][0], &xx1, &yy1);
+    state->transform(patch->x[1][0], patch->y[1][0], &xx2, &yy2);
+    path->curveTo(xx1 - xMin, yy1 - yMin, xx2 - xMin, yy2 - yMin,
+		  xx0 - xMin, yy0 - yMin);
+    path->close();
+    splash->fill(path, gFalse);
+    delete path;
+
+  // subdivide the patch
+  } else {
+    double xx[4][8], yy[4][8];
+    for (int 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]);
+      double xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
+      double 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];
+    }
+    GfxPatch patch00, patch01, patch10, patch11;
+    for (int 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]);
+      double xxm = 0.5 * (xx[1][i] + xx[2][i]);
+      double 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 (int 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]);
+      double xxm = 0.5 * (xx[1][i] + xx[2][i]);
+      double 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];
+    }
+    for (int i = 0; i < shading->getNComps(); ++i) {
+      patch00.color[0][0][i] = patch->color[0][0][i];
+      patch00.color[0][1][i] = 0.5 * (patch->color[0][0][i] +
+				      patch->color[0][1][i]);
+      patch01.color[0][0][i] = patch00.color[0][1][i];
+      patch01.color[0][1][i] = patch->color[0][1][i];
+      patch01.color[1][1][i] = 0.5 * (patch->color[0][1][i] +
+				      patch->color[1][1][i]);
+      patch11.color[0][1][i] = patch01.color[1][1][i];
+      patch11.color[1][1][i] = patch->color[1][1][i];
+      patch11.color[1][0][i] = 0.5 * (patch->color[1][1][i] +
+				      patch->color[1][0][i]);
+      patch10.color[1][1][i] = patch11.color[1][0][i];
+      patch10.color[1][0][i] = patch->color[1][0][i];
+      patch10.color[0][0][i] = 0.5 * (patch->color[1][0][i] +
+				      patch->color[0][0][i]);
+      patch00.color[1][0][i] = patch10.color[0][0][i];
+      patch00.color[1][1][i] = 0.5 * (patch00.color[1][0][i] +
+				      patch01.color[1][1][i]);
+      patch01.color[1][0][i] = patch00.color[1][1][i];
+      patch11.color[0][0][i] = patch00.color[1][1][i];
+      patch10.color[0][1][i] = patch00.color[1][1][i];
+    }
+    fillPatch(state, splash, mode, reverseVideo, xMin, yMin, &patch00,
+	      shading, depth + 1);
+    fillPatch(state, splash, mode, reverseVideo, xMin, yMin, &patch10,
+	      shading, depth + 1);
+    fillPatch(state, splash, mode, reverseVideo, xMin, yMin, &patch01,
+	      shading, depth + 1);
+    fillPatch(state, splash, mode, reverseVideo, xMin, yMin, &patch11,
+	      shading, depth + 1);
+  }
+}
+
+void ShadingImage::computeShadingColor(GfxState *state,
+				       SplashColorMode mode,
+				       GBool reverseVideo,
+				       GfxColor *color,
+				       SplashColorPtr sColor) {
+  GfxGray gray;
+  GfxRGB rgb;
+#if SPLASH_CMYK
+  GfxCMYK cmyk;
+#endif
+
+  state->setFillColor(color);
+  switch (mode) {
+  case splashModeMono8:
+    state->getFillGray(&gray);
+    if (reverseVideo) {
+      gray = gfxColorComp1 - gray;
+    }
+    sColor[0] = colToByte(gray);
+    break;
+  case splashModeRGB8:
+    state->getFillRGB(&rgb);
+    if (reverseVideo) {
+      rgb.r = gfxColorComp1 - rgb.r;
+      rgb.g = gfxColorComp1 - rgb.g;
+      rgb.b = gfxColorComp1 - rgb.b;
+    }
+    sColor[0] = colToByte(rgb.r);
+    sColor[1] = colToByte(rgb.g);
+    sColor[2] = colToByte(rgb.b);
+    break;
+#if SPLASH_CMYK
+  case splashModeCMYK8:
+    state->getFillCMYK(&cmyk);
+    sColor[0] = colToByte(cmyk.c);
+    sColor[1] = colToByte(cmyk.m);
+    sColor[2] = colToByte(cmyk.y);
+    sColor[3] = colToByte(cmyk.k);
+    break;
+#endif
+  case splashModeMono1:
+  case splashModeBGR8:
+    // mode cannot be Mono1 or BGR8
+    break;
+  }
+}
+
+// Transform a user space bbox to a device space bbox.
+void ShadingImage::transformBBox(GfxState *state,
+				 double uxMin, double uyMin,
+				 double uxMax, double uyMax,
+				 double *dxMin, double *dyMin,
+				 double *dxMax, double *dyMax) {
+  double tx, ty;
+  state->transform(uxMin, uyMin, &tx, &ty);
+  *dxMin = *dxMax = tx;
+  *dyMin = *dyMax = ty;
+  state->transform(uxMin, uyMax, &tx, &ty);
+  if (tx < *dxMin) {
+    *dxMin = tx;
+  } else if (tx > *dxMax) {
+    *dxMax = tx;
+  }
+  if (ty < *dyMin) {
+    *dyMin = ty;
+  } else if (ty > *dyMax) {
+    *dyMax = ty;
+  }
+  state->transform(uxMax, uyMin, &tx, &ty);
+  if (tx < *dxMin) {
+    *dxMin = tx;
+  } else if (tx > *dxMax) {
+    *dxMax = tx;
+  }
+  if (ty < *dyMin) {
+    *dyMin = ty;
+  } else if (ty > *dyMax) {
+    *dyMax = ty;
+  }
+  state->transform(uxMax, uyMax, &tx, &ty);
+  if (tx < *dxMin) {
+    *dxMin = tx;
+  } else if (tx > *dxMax) {
+    *dxMax = tx;
+  }
+  if (ty < *dyMin) {
+    *dyMin = ty;
+  } else if (ty > *dyMax) {
+    *dyMax = ty;
+  }
+}
+

Added: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.h	                        (rev 0)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/ShadingImage.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -0,0 +1,106 @@
+//========================================================================
+//
+// ShadingImage.h
+//
+// Convert shading patterns to bitmaps.
+//
+// Copyright 2020 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef SHADINGIMAGE_H
+#define SHADINGIMAGE_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+#include "SplashTypes.h"
+
+class GfxState;
+class GfxShading;
+class SplashBitmap;
+class Splash;
+
+class ShadingImage {
+public:
+
+  // Generate a [mode] bitmap for [shading], using the clipping and
+  // CTM info from [state].  Returns the bitmap and sets [xOut],[yOut]
+  // to the upper-left corner of the region in device space.  Returns
+  // NULL if the clip region is empty.
+  static SplashBitmap *generateBitmap(GfxState *state, GfxShading *shading,
+				      SplashColorMode mode,
+				      GBool reverseVideo,
+				      Splash *parentSplash,
+				      SplashBitmap *parentBitmap,
+				      int *xOut, int *yOut);
+
+
+private:
+
+  static SplashBitmap *generateFunctionBitmap(GfxState *state,
+					      GfxFunctionShading *shading,
+					      SplashColorMode mode,
+					      GBool reverseVideo,
+					      Splash *parentSplash,
+					      SplashBitmap *parentBitmap,
+					      int *xOut, int *yOut);
+  static SplashBitmap *generateAxialBitmap(GfxState *state,
+					   GfxAxialShading *shading,
+					   SplashColorMode mode,
+					   GBool reverseVideo,
+					   Splash *parentSplash,
+					   SplashBitmap *parentBitmap,
+					   int *xOut, int *yOut);
+  static SplashBitmap *generateRadialBitmap(GfxState *state,
+					    GfxRadialShading *shading,
+					    SplashColorMode mode,
+					    GBool reverseVideo,
+					    Splash *parentSplash,
+					    SplashBitmap *parentBitmap,
+					    int *xOut, int *yOut);
+  static SplashBitmap *generateGouraudTriangleBitmap(
+					GfxState *state,
+					GfxGouraudTriangleShading *shading,
+					SplashColorMode mode,
+					GBool reverseVideo,
+					Splash *parentSplash,
+					SplashBitmap *parentBitmap,
+					int *xOut, int *yOut);
+  static void gouraudFillTriangle(GfxState *state, SplashBitmap *bitmap,
+				  SplashColorMode mode,
+				  GBool reverseVideo,
+				  int xMin, int yMin, int xMax, int yMax,
+				  double x0, double y0, double *color0,
+				  double x1, double y1, double *color1,
+				  double x2, double y2, double *color2,
+				  GfxGouraudTriangleShading *shading);
+  static SplashBitmap *generatePatchMeshBitmap(GfxState *state,
+					       GfxPatchMeshShading *shading,
+					       SplashColorMode mode,
+					       GBool reverseVideo,
+					       Splash *parentSplash,
+					       SplashBitmap *parentBitmap,
+					       int *xOut, int *yOut);
+  static void fillPatch(GfxState *state, Splash *splash,
+			SplashColorMode mode, GBool reverseVideo,
+			int xMin, int yMin,
+			GfxPatch *patch,
+			GfxPatchMeshShading *shading,
+			int depth);
+  static void computeShadingColor(GfxState *state,
+				  SplashColorMode mode,
+				  GBool reverseVideo,
+				  GfxColor *color,
+				  SplashColorPtr sColor);
+  static void transformBBox(GfxState *state,
+			    double uxMin, double uyMin,
+			    double uxMax, double uyMax,
+			    double *dxMin, double *dyMin,
+			    double *dxMax, double *dyMax);
+};
+
+#endif

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -17,11 +17,13 @@
 #include <limits.h>
 #include "gmempp.h"
 #include "gfile.h"
+#include "Trace.h"
 #include "GlobalParams.h"
 #include "Error.h"
 #include "Object.h"
 #include "Gfx.h"
 #include "GfxFont.h"
+#include "ShadingImage.h"
 #include "Link.h"
 #include "CharCodeToUnicode.h"
 #include "FontEncodingTables.h"
@@ -52,6 +54,18 @@
 
 //------------------------------------------------------------------------
 
+// max tile size (used in tilingPatternFill())
+// - Adobe uses a resolution-independent threshold here, of 6M sq pts
+// - xpdf uses a resolution-dependent max, but with different values
+//   on 32-bit and 64-bit systems
+#if SplashBitmapRowSizeMax == INT_MAX
+#  define maxTileSize 200000000
+#else
+#  define maxTileSize 2000000000
+#endif
+
+//------------------------------------------------------------------------
+
 // Type 3 font cache size parameters
 #define type3FontCacheAssoc   8
 #define type3FontCacheMaxSets 8
@@ -67,6 +81,11 @@
 
 //------------------------------------------------------------------------
 
+// 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);
+}
+
 // Clip x to lie in [0, 255].
 static inline Guchar clip255(int x) {
   return x < 0 ? 0 : x > 255 ? 255 : (Guchar)x;
@@ -499,7 +518,7 @@
   int cacheAssoc;		// cache associativity (glyphs per set)
   Guchar *cacheData;		// glyph pixmap cache
   T3FontCacheTag *cacheTags;	// cache tags, i.e., char codes
-  GBool inUse;			// set while this T3 font is in active use
+  int refCount;			// active reference count for this T3 font
 };
 
 T3FontCache::T3FontCache(Ref *fontIDA, double m11A, double m12A,
@@ -541,7 +560,7 @@
   for (i = 0; i < cacheSets * cacheAssoc; ++i) {
     cacheTags[i].mru = (Gushort)(i & (cacheAssoc - 1));
   }
-  inUse = gFalse;
+  refCount = 0;
 }
 
 T3FontCache::~T3FontCache() {
@@ -565,6 +584,7 @@
   SplashBitmap *origBitmap;
   Splash *origSplash;
   double origCTM4, origCTM5;
+  SplashStrokeAdjustMode savedStrokeAdjust;
 
   T3GlyphStack *next;		// next object on stack
 };
@@ -711,6 +731,9 @@
   if (bitmap) {
     delete bitmap;
   }
+  if (textClipPath) {
+    delete textClipPath;
+  }
 }
 
 void SplashOutputDev::startDoc(XRef *xrefA) {
@@ -763,6 +786,7 @@
       delete bitmap;
       bitmap = NULL;
     }
+    traceMessage("page bitmap");
     bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode,
 			      colorMode != splashModeMono1, bitmapTopDown,
 			      NULL);
@@ -1194,18 +1218,21 @@
     goto err1;
   }
 
-  // sanity-check the font size - skip anything larger than 20 inches
-  // (this avoids problems allocating memory for the font cache)
+  // sanity-check the font size: skip anything larger than 10k x 10k,
+  // to avoid problems allocating a bitmap (note that code in
+  // SplashFont disables caching at a smaller size than this)
   state->textTransformDelta(state->getFontSize(), state->getFontSize(),
 			    &fsx, &fsy);
   state->transformDelta(fsx, fsy, &fsx, &fsy);
-  if (fabs(fsx) > 20 * state->getHDPI() ||
-      fabs(fsy) > 20 * state->getVDPI()) {
+  if (fabs(fsx) > 20000 || fabs(fsy) > 20000) {
     goto err1;
   }
 
   // check the font file cache
   id = new SplashOutFontFileID(gfxFont->getID());
+  if (fontEngine->checkForBadFontFile(id)) {
+    goto err2;
+  }
   if ((fontFile = fontEngine->getFontFile(id))) {
     delete id;
 
@@ -1308,7 +1335,7 @@
 	      gfxFont->getName() ? gfxFont->getName()->getCString()
 	                         : "(unnamed)");
 	delete fontLoc;
-	goto err2;
+	goto err1;
       }
       break;
     case fontType1C:
@@ -1337,7 +1364,7 @@
 	      gfxFont->getName() ? gfxFont->getName()->getCString()
 	                         : "(unnamed)");
 	delete fontLoc;
-	goto err2;
+	goto err1;
       }
       break;
     case fontType1COT:
@@ -1370,7 +1397,7 @@
 	      gfxFont->getName() ? gfxFont->getName()->getCString()
 	                         : "(unnamed)");
 	delete fontLoc;
-	goto err2;
+	goto err1;
       }
       break;
     case fontTrueType:
@@ -1415,7 +1442,7 @@
 	      gfxFont->getName() ? gfxFont->getName()->getCString()
 	                         : "(unnamed)");
 	delete fontLoc;
-	goto err2;
+	goto err1;
       }
       break;
     case fontCIDType0:
@@ -1443,7 +1470,7 @@
 	      gfxFont->getName() ? gfxFont->getName()->getCString()
 	                         : "(unnamed)");
 	delete fontLoc;
-	goto err2;
+	goto err1;
       }
       break;
     case fontCIDType0COT:
@@ -1513,7 +1540,7 @@
 	      gfxFont->getName() ? gfxFont->getName()->getCString()
 	                         : "(unnamed)");
 	delete fontLoc;
-	goto err2;
+	goto err1;
       }
       break;
     case fontCIDType2:
@@ -1588,7 +1615,7 @@
 	      gfxFont->getName() ? gfxFont->getName()->getCString()
 	                         : "(unnamed)");
 	delete fontLoc;
-	goto err2;
+	goto err1;
       }
       break;
     default:
@@ -1734,6 +1761,7 @@
   SplashBitmap *origBitmap, *tileBitmap;
   Splash *origSplash;
   SplashColor color;
+  Guint *overprintMaskBitmap;
   double *ctm;
   double ictm[6], tileMat[6], mat1[6], mat2[6];
   double tileXMin, tileYMin, tileXMax, tileYMax;
@@ -1818,7 +1846,7 @@
   if (tileXMax - tileXMin + 0.5 > (double)INT_MAX ||
       tileYMax - tileYMin + 0.5 > (double)INT_MAX ||
       tileW > INT_MAX / tileH ||
-      tileSize > 1000000) {
+      tileSize > maxTileSize) {
     mat1[0] = mat[0];
     mat1[1] = mat[1];
     mat1[2] = mat[2];
@@ -1969,6 +1997,7 @@
   // create a temporary bitmap
   origBitmap = bitmap;
   origSplash = splash;
+  traceMessage("tiling pattern bitmap");
   bitmap = tileBitmap = new SplashBitmap(tileW, tileH, bitmapRowPad,
 					 colorMode, gTrue, bitmapTopDown,
 					 origBitmap);
@@ -1978,6 +2007,20 @@
     color[i] = 0;
   }
   splash->clear(color);
+#if SPLASH_CMYK
+  // if we're doing overprint preview, we need to track the overprint
+  // mask at each pixel in the tile bitmap
+  if (globalParams->getOverprintPreview() &&
+      colorMode == splashModeCMYK8) {
+    overprintMaskBitmap = (Guint *)gmallocn(tileH, tileW * (int)sizeof(Guint));
+    memset(overprintMaskBitmap, 0, tileH * tileW * sizeof(Guint));
+    splash->setOverprintMaskBitmap(overprintMaskBitmap);
+  } else {
+    overprintMaskBitmap = NULL;
+  }
+#else // SPLASH_CMYK
+  overprintMaskBitmap = NULL;
+#endif // SPLASH_CMYK
   splash->setMinLineWidth(globalParams->getMinLineWidth());
   splash->setStrokeAdjust(
 		 mapStrokeAdjustMode[globalParams->getStrokeAdjust()]);
@@ -2005,434 +2048,48 @@
   splash->setOverprintMask(0xffffffff);
 
   // draw the tiles
-  for (iy = iyMin; iy < iyMax; ++iy) {
-    for (ix = ixMin; ix < ixMax; ++ix) {
-      x = (int)(adjXMin + ix * xStepX + iy * yStepX + 0.5);
-      y = (int)(adjYMin + ix * xStepY + iy * yStepY + 0.5);
-      splash->composite(tileBitmap, 0, 0, x, y, tileW, tileH,
-			gFalse, gFalse);
-    }
-  }
-
-  delete tileBitmap;
-}
-
-GBool SplashOutputDev::axialShadedFill(GfxState *state,
-				       GfxAxialShading *shading) {
-  double x0, y0, x1, y1, t0, t1;
-  GBool ext0, ext1;
-  double uxMin, uyMin, uxMax, uyMax, det;
-  double *ctm;
-  double ictm[6];
-  double xMin, yMin, xMax, yMax, tx, ty, xx, yy;
-  double xx0, yy0, xx1, yy1, dx, dy, d, s, t;
-  GBool dZero, go;
-  int ixMin, iyMin, ixMax, iyMax, bitmapWidth, bitmapHeight, nColors;
-  SplashClipResult clipRes;
-  SplashColorMode srcMode;
-  SplashBitmap *tBitmap;
-  int nComps;
-  int x, y, i;
-  SplashColorPtr dataPtr;
-  Guchar *alphaPtr;
-  GfxColor color;
-  SplashColorPtr sColors, sColor;
-  SplashColor sColor0;
-
-
-  // get the shading parameters
-  shading->getCoords(&x0, &y0, &x1, &y1);
-  t0 = shading->getDomain0();
-  t1 = shading->getDomain1();
-  ext0 = shading->getExtend0();
-  ext1 = shading->getExtend1();
-  dx = x1 - x0;
-  dy = y1 - y0;
-  d = dx * dx + dy * dy;
-  dZero = fabs(d) < 0.0001;
-  if (!dZero) {
-    d = 1 / d;
-  }
-  if (dZero && !ext0 && !ext1) {
-    return gTrue;
-  }
-
-  // get clip region (in user space)
-  state->getUserClipBBox(&uxMin, &uyMin, &uxMax, &uyMax);
-  if (uxMin > uxMax || uyMin > uyMax) {
-    return gTrue;
-  }
-
-  // convert the region to device space
-  ctm = state->getCTM();
-  tx = uxMin * ctm[0] + uyMin * ctm[2] + ctm[4];
-  ty = uxMin * ctm[1] + uyMin * ctm[3] + ctm[5];
-  xMin = xMax = tx;
-  yMin = yMax = ty;
-  tx = uxMin * ctm[0] + uyMax * ctm[2] + ctm[4];
-  ty = uxMin * ctm[1] + uyMax * ctm[3] + ctm[5];
-  if (tx < xMin) {
-    xMin = tx;
-  } else if (tx > xMax) {
-    xMax = tx;
-  }
-  if (ty < yMin) {
-    yMin = ty;
-  } else if (ty > yMax) {
-    yMax = ty;
-  }
-  tx = uxMax * ctm[0] + uyMin * ctm[2] + ctm[4];
-  ty = uxMax * ctm[1] + uyMin * ctm[3] + ctm[5];
-  if (tx < xMin) {
-    xMin = tx;
-  } else if (tx > xMax) {
-    xMax = tx;
-  }
-  if (ty < yMin) {
-    yMin = ty;
-  } else if (ty > yMax) {
-    yMax = ty;
-  }
-  tx = uxMax * ctm[0] + uyMax * ctm[2] + ctm[4];
-  ty = uxMax * ctm[1] + uyMax * ctm[3] + ctm[5];
-  if (tx < xMin) {
-    xMin = tx;
-  } else if (tx > xMax) {
-    xMax = tx;
-  }
-  if (ty < yMin) {
-    yMin = ty;
-  } else if (ty > yMax) {
-    yMax = ty;
-  }
-  ixMin = (int)floor(xMin);
-  iyMin = (int)floor(yMin);
-  ixMax = (int)floor(xMax) + 1;
-  iyMax = (int)floor(yMax) + 1;
-  clipRes = splash->limitRectToClipRect(&ixMin, &iyMin, &ixMax, &iyMax);
-  if (clipRes == splashClipAllOutside) {
-    return gTrue;
-  }
-
-  // allocate a bitmap
-  if (colorMode == splashModeMono1) {
-    srcMode = splashModeMono8;
-  } else if (colorMode == splashModeBGR8) {
-    srcMode = splashModeRGB8;
-  } else {
-    srcMode = colorMode;
-  }
-  bitmapWidth = ixMax - ixMin;
-  bitmapHeight = iyMax - iyMin;
-  tBitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1,
-			     srcMode, gTrue, gTrue, bitmap);
-  memset(tBitmap->getAlphaPtr(), 0, (size_t)bitmapWidth * bitmapHeight);
-  nComps = splashColorModeNComps[srcMode];
-
-  // compute the inverse 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;
-
-  // render the shading into the bitmap
-  xx0 = x0 * ctm[0] + y0 * ctm[2] + ctm[4];
-  yy0 = x0 * ctm[1] + y0 * ctm[3] + ctm[5];
-  xx1 = x1 * ctm[0] + y1 * ctm[2] + ctm[4];
-  yy1 = x1 * ctm[1] + y1 * ctm[3] + ctm[5];
-  sColors = NULL;
-
-  // special case: zero-length axis
-  if (dZero) {
-    if (ext0) {
-      shading->getColor(t0, &color);
-    } else {
-      shading->getColor(t1, &color);
-    }
-    computeShadingColor(state, srcMode, &color, sColor0);
-    dataPtr = tBitmap->getDataPtr();
-    alphaPtr = tBitmap->getAlphaPtr();
-    for (y = 0; y < bitmapHeight; ++y) {
-      for (x = 0; x < bitmapWidth; ++x) {
-	for (i = 0; i < nComps; ++i) {
-	  *dataPtr++ = sColor0[i];
-	}
-	*alphaPtr++ = 0xff;
+  if (tileW == 1 && tileH == 1 &&
+      fabs(xStepX * yStepY - xStepY * yStepX) < 0.9) {
+    // if the tile is 1x1 pixel, and the stepping completely fills the
+    // area, just composite the 1x1 image across the clip region
+    // (this avoids performance problems in cases where the step size
+    // is very small) (we compare to 0.9 instead of 1.0 to avoid fp
+    // jitter issues)
+    ixMin = (int)floor(clipXMin);
+    ixMax = (int)floor(clipXMax) + 1;
+    iyMin = (int)floor(clipYMin);
+    iyMax = (int)floor(clipYMax) + 1;
+    for (iy = iyMin; iy < iyMax; ++iy) {
+      for (ix = ixMin; ix < ixMax; ++ix) {
+	splash->composite(tileBitmap, 0, 0, ix, iy, tileW, tileH,
+			  gFalse, gFalse);
       }
     }
-
-  // special case: horizontal axis (in device space)
-  } else if (fabs(yy0 - yy1) < 0.01) {
-    for (x = 0; x < bitmapWidth; ++x) {
-      dataPtr = tBitmap->getDataPtr() + x * nComps;
-      alphaPtr = tBitmap->getAlphaPtr() + x;
-      tx = ixMin + x + 0.5;
-      ty = iyMin + 0.5;
-      xx = tx * ictm[0] + ty * ictm[2] + ictm[4];
-      yy = tx * ictm[1] + ty * ictm[3] + ictm[5];
-      s = ((xx - x0) * dx + (yy - y0) * dy) * d;
-      go = gFalse;
-      if (s < 0) {
-	go = ext0;
-      } else if (s > 1) {
-	go = ext1;
-      } else {
-	go = gTrue;
-      }
-      if (!go) {
-	continue;
-      }
-      if (s <= 0) {
-	shading->getColor(t0, &color);
-      } else if (s >= 1) {
-	shading->getColor(t1, &color);
-      } else {
-	t = t0 + s * (t1 - t0);
-	shading->getColor(t, &color);
-      }
-      computeShadingColor(state, srcMode, &color, sColor0);
-      for (y = 0; y < bitmapHeight; ++y) {
-	for (i = 0; i < nComps; ++i) {
-	  dataPtr[i] = sColor0[i];
-	}
-	*alphaPtr = 0xff;
-	dataPtr += tBitmap->getRowSize();
-	alphaPtr += bitmapWidth;
-      }
-    }
-
-  // special case: vertical axis (in device space)
-  } else if (fabs(xx0 - xx1) < 0.01) {
-    dataPtr = tBitmap->getDataPtr();
-    alphaPtr = tBitmap->getAlphaPtr();
-    for (y = 0; y < bitmapHeight; ++y) {
-      tx = ixMin + 0.5;
-      ty = iyMin + y + 0.5;
-      xx = tx * ictm[0] + ty * ictm[2] + ictm[4];
-      yy = tx * ictm[1] + ty * ictm[3] + ictm[5];
-      s = ((xx - x0) * dx + (yy - y0) * dy) * d;
-      go = gFalse;
-      if (s < 0) {
-	go = ext0;
-      } else if (s > 1) {
-	go = ext1;
-      } else {
-	go = gTrue;
-      }
-      if (!go) {
-	continue;
-      }
-      if (s <= 0) {
-	shading->getColor(t0, &color);
-      } else if (s >= 1) {
-	shading->getColor(t1, &color);
-      } else {
-	t = t0 + s * (t1 - t0);
-	shading->getColor(t, &color);
-      }
-      computeShadingColor(state, srcMode, &color, sColor0);
-      for (x = 0; x < bitmapWidth; ++x) {
-	for (i = 0; i < nComps; ++i) {
-	  *dataPtr++ = sColor0[i];
-	}
-	*alphaPtr++ = 0xff;
-      }
-    }
-
-  // general case
   } else {
-    // pre-compute colors along the axis
-    nColors = (int)(1.5 * sqrt((xx1 - xx0) * (xx1 - xx0)
-			       + (yy1 - yy0) * (yy1 - yy0)));
-    if (nColors < 16) {
-      nColors = 16;
-    } else if (nColors > 1024) {
-      nColors = 1024;
-    }
-    sColors = (SplashColorPtr)gmallocn(nColors, nComps);
-    sColor = sColors;
-    for (i = 0; i < nColors; ++i) {
-      s = (double)i / (double)(nColors - 1);
-      t = t0 + s * (t1 - t0);
-      shading->getColor(t, &color);
-      computeShadingColor(state, srcMode, &color, sColor);
-      sColor += nComps;
-    }
-
-    dataPtr = tBitmap->getDataPtr();
-    alphaPtr = tBitmap->getAlphaPtr();
-    for (y = 0; y < bitmapHeight; ++y) {
-      for (x = 0; x < bitmapWidth; ++x) {
-
-	// convert coords to user space
-	tx = ixMin + x + 0.5;
-	ty = iyMin + y + 0.5;
-	xx = tx * ictm[0] + ty * ictm[2] + ictm[4];
-	yy = tx * ictm[1] + ty * ictm[3] + ictm[5];
-
-	// compute the position along the axis
-	s = ((xx - x0) * dx + (yy - y0) * dy) * d;
-	go = gFalse;
-	if (s < 0) {
-	  go = ext0;
-	} else if (s > 1) {
-	  go = ext1;
+    for (iy = iyMin; iy < iyMax; ++iy) {
+      for (ix = ixMin; ix < ixMax; ++ix) {
+	x = (int)(adjXMin + ix * xStepX + iy * yStepX + 0.5);
+	y = (int)(adjYMin + ix * xStepY + iy * yStepY + 0.5);
+	if (overprintMaskBitmap) {
+	  splash->compositeWithOverprint(tileBitmap, overprintMaskBitmap,
+					 0, 0, x, y, tileW, tileH,
+					 gFalse, gFalse);
 	} else {
-	  go = gTrue;
+	  splash->composite(tileBitmap, 0, 0, x, y, tileW, tileH,
+			    gFalse, gFalse);
 	}
-	if (!go) {
-	  dataPtr += nComps;
-	  ++alphaPtr;
-	  continue;
-	}
-	if (s <= 0) {
-	  sColor = sColors;
-	} else if (s >= 1) {
-	  sColor = sColors + (nColors - 1) * nComps;
-	} else {
-	  i = (int)((nColors - 1) * s + 0.5);
-	  sColor = sColors + i * nComps;
-	}
-	for (i = 0; i < nComps; ++i) {
-	  *dataPtr++ = sColor[i];
-	}
-	*alphaPtr++ = 0xff;
       }
     }
   }
 
-  // composite the bitmap
-  setOverprintMask(state, state->getFillColorSpace(),
-		   state->getFillOverprint(), state->getOverprintMode(),
-		   NULL);
-  splash->composite(tBitmap, 0, 0, ixMin, iyMin, bitmapWidth, bitmapHeight,
-		    clipRes == splashClipAllInside, gFalse);
+  gfree(overprintMaskBitmap);
+  delete tileBitmap;
+}
 
-  gfree(sColors);
-  delete tBitmap;
+GBool SplashOutputDev::shadedFill(GfxState *state, GfxShading *shading) {
 
-  return gTrue;
-}
-
-GBool SplashOutputDev::radialShadedFill(GfxState *state,
-					GfxRadialShading *shading) {
-  double x0, y0, r0, x1, y1, r1, t0, t1, h;
-  GBool ext0, ext1, enclosed;
-  double uxMin, uyMin, uxMax, uyMax, det;
-  double *ctm;
-  double ictm[6];
-  double xMin, yMin, xMax, yMax, tx, ty, xx, yy;
-  double dx, dy, dr, r0dr, r02, a, a2, b, c, e, es, s, s0, s1, rs0, rs1, t;
-  GBool aIsZero, go;
-  int ixMin, iyMin, ixMax, iyMax, bitmapWidth, bitmapHeight, nColors;
-  int bxMin, byMin, bxMax, byMax;
-  SplashClipResult clipRes;
+  // generate the bitmap
   SplashColorMode srcMode;
-  SplashBitmap *tBitmap;
-  int nComps;
-  int x, y, i;
-  SplashColorPtr dataPtr;
-  Guchar *alphaPtr;
-  GfxColor color;
-  SplashColorPtr sColors, sColor;
-
-
-  // get the shading parameters
-  shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1);
-  t0 = shading->getDomain0();
-  t1 = shading->getDomain1();
-  ext0 = shading->getExtend0();
-  ext1 = shading->getExtend1();
-  h = sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
-  enclosed = fabs(r1 - r0) >= h;
-
-  // get clip region (in user space)
-  state->getUserClipBBox(&uxMin, &uyMin, &uxMax, &uyMax);
-  if (uxMin > uxMax || uyMin > uyMax) {
-    return gTrue;
-  }
-
-  // intersect with shading region (in user space): if the extend
-  // flags are false (or just the larger extend flag is false, in the
-  // "enclosed" case), we can use the bbox for the two circles
-  if ((!ext0 && !ext1) ||
-      (enclosed && !(r0 > r1 ? ext0 : ext1))) {
-    tx = (x0 - r0) < (x1 - r1) ? (x0 - r0) : (x1 - r1);
-    if (tx > uxMin) {
-      uxMin = tx;
-    }
-    tx = (x0 + r0) > (x1 + r1) ? (x0 + r0) : (x1 + r1);
-    if (tx < uxMax) {
-      uxMax = tx;
-    }
-    ty = (y0 - r0) < (y1 - r1) ? (y0 - r0) : (y1 - r1);
-    if (ty > uyMin) {
-      uyMin = ty;
-    }
-    ty = (y0 + r0) > (y1 + r1) ? (y0 + r0) : (y1 + r1);
-    if (ty < uyMax) {
-      uyMax = ty;
-    }
-  }
-  if (uxMin > uxMax || uyMin > uyMax) {
-    return gTrue;
-  }
-
-  // convert the region to device space
-  ctm = state->getCTM();
-  tx = uxMin * ctm[0] + uyMin * ctm[2] + ctm[4];
-  ty = uxMin * ctm[1] + uyMin * ctm[3] + ctm[5];
-  xMin = xMax = tx;
-  yMin = yMax = ty;
-  tx = uxMin * ctm[0] + uyMax * ctm[2] + ctm[4];
-  ty = uxMin * ctm[1] + uyMax * ctm[3] + ctm[5];
-  if (tx < xMin) {
-    xMin = tx;
-  } else if (tx > xMax) {
-    xMax = tx;
-  }
-  if (ty < yMin) {
-    yMin = ty;
-  } else if (ty > yMax) {
-    yMax = ty;
-  }
-  tx = uxMax * ctm[0] + uyMin * ctm[2] + ctm[4];
-  ty = uxMax * ctm[1] + uyMin * ctm[3] + ctm[5];
-  if (tx < xMin) {
-    xMin = tx;
-  } else if (tx > xMax) {
-    xMax = tx;
-  }
-  if (ty < yMin) {
-    yMin = ty;
-  } else if (ty > yMax) {
-    yMax = ty;
-  }
-  tx = uxMax * ctm[0] + uyMax * ctm[2] + ctm[4];
-  ty = uxMax * ctm[1] + uyMax * ctm[3] + ctm[5];
-  if (tx < xMin) {
-    xMin = tx;
-  } else if (tx > xMax) {
-    xMax = tx;
-  }
-  if (ty < yMin) {
-    yMin = ty;
-  } else if (ty > yMax) {
-    yMax = ty;
-  }
-  ixMin = (int)floor(xMin);
-  iyMin = (int)floor(yMin);
-  ixMax = (int)floor(xMax) + 1;
-  iyMax = (int)floor(yMax) + 1;
-  clipRes = splash->limitRectToClipRect(&ixMin, &iyMin, &ixMax, &iyMax);
-  if (clipRes == splashClipAllOutside) {
-    return gTrue;
-  }
-  
-  // allocate a bitmap
   if (colorMode == splashModeMono1) {
     srcMode = splashModeMono8;
   } else if (colorMode == splashModeBGR8) {
@@ -2440,287 +2097,36 @@
   } else {
     srcMode = colorMode;
   }
-  bitmapWidth = ixMax - ixMin;
-  bitmapHeight = iyMax - iyMin;
-  tBitmap = new SplashBitmap(bitmapWidth, bitmapHeight, 1,
-			     srcMode, gTrue, gTrue, bitmap);
-  memset(tBitmap->getAlphaPtr(), 0, tBitmap->getAlphaRowSize() * bitmapHeight);
-  nComps = splashColorModeNComps[srcMode];
-
-  // compute the inverse 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;
-
-  // pre-compute colors along the axis
-  nColors = (int)sqrt((double)(bitmapWidth * bitmapWidth
-			       + bitmapHeight * bitmapHeight));
-  if (nColors < 16) {
-    nColors = 16;
-  } else if (nColors > 1024) {
-    nColors = 1024;
+  int x, y;
+  SplashBitmap *tBitmap = ShadingImage::generateBitmap(state, shading, srcMode,
+						       reverseVideo,
+						       splash, bitmap, &x, &y);
+  if (!tBitmap) {
+    // clip region is empty - nothing to draw
+    return gTrue;
   }
-  sColors = (SplashColorPtr)gmallocn(nColors, nComps);
-  sColor = sColors;
-  for (i = 0; i < nColors; ++i) {
-    s = (double)i / (double)(nColors - 1);
-    t = t0 + s * (t1 - t0);
-    shading->getColor(t, &color);
-    computeShadingColor(state, srcMode, &color, sColor);
-    sColor += nComps;
-  }
 
-  // special case: in the "enclosed" + extended case, we can fill the
-  // bitmap with the outer color and just render inside the larger
-  // circle
-  if (enclosed &&
-      ((r0 > r1 && ext0) || (r1 > r0 && ext1))) {
-    if (r0 > r1) {
-      sColor = sColors;
-      uxMin = x0 - r0;
-      uxMax = x0 + r0;
-      uyMin = y0 - r0;
-      uyMax = y0 + r0;
-    } else {
-      sColor = sColors + (nColors - 1) * nComps;
-      uxMin = x1 - r1;
-      uxMax = x1 + r1;
-      uyMin = y1 - r1;
-      uyMax = y1 + r1;
-    }
-
-    // convert bbox of larger circle to device space
-    tx = uxMin * ctm[0] + uyMin * ctm[2] + ctm[4];
-    ty = uxMin * ctm[1] + uyMin * ctm[3] + ctm[5];
-    xMin = xMax = tx;
-    yMin = yMax = ty;
-    tx = uxMin * ctm[0] + uyMax * ctm[2] + ctm[4];
-    ty = uxMin * ctm[1] + uyMax * ctm[3] + ctm[5];
-    if (tx < xMin) {
-      xMin = tx;
-    } else if (tx > xMax) {
-      xMax = tx;
-    }
-    if (ty < yMin) {
-      yMin = ty;
-    } else if (ty > yMax) {
-      yMax = ty;
-    }
-    tx = uxMax * ctm[0] + uyMin * ctm[2] + ctm[4];
-    ty = uxMax * ctm[1] + uyMin * ctm[3] + ctm[5];
-    if (tx < xMin) {
-      xMin = tx;
-    } else if (tx > xMax) {
-      xMax = tx;
-    }
-    if (ty < yMin) {
-      yMin = ty;
-    } else if (ty > yMax) {
-      yMax = ty;
-    }
-    tx = uxMax * ctm[0] + uyMax * ctm[2] + ctm[4];
-    ty = uxMax * ctm[1] + uyMax * ctm[3] + ctm[5];
-    if (tx < xMin) {
-      xMin = tx;
-    } else if (tx > xMax) {
-      xMax = tx;
-    }
-    if (ty < yMin) {
-      yMin = ty;
-    } else if (ty > yMax) {
-      yMax = ty;
-    }
-    bxMin = (int)floor(xMin - ixMin);
-    if (bxMin < 0) {
-      bxMin = 0;
-    }
-    byMin = (int)floor(yMin - iyMin);
-    if (byMin < 0) {
-      byMin = 0;
-    }
-    bxMax = (int)floor(xMax - ixMin) + 1;
-    if (bxMax > bitmapWidth) {
-      bxMax = bitmapWidth;
-    }
-    byMax = (int)floor(yMax - iyMin) + 1;
-    if (byMax > bitmapHeight) {
-      byMax = bitmapHeight;
-    }
-
-    // fill bitmap (except for the rectangle containing the larger circle)
-    dataPtr = tBitmap->getDataPtr();
-    alphaPtr = tBitmap->getAlphaPtr();
-    for (y = 0; y < bitmapHeight; ++y) {
-      for (x = 0; x < bitmapWidth; ++x) {
-	if (y >= byMin && y < byMax && x >= bxMin && x < bxMax) {
-	  dataPtr += nComps;
-	  ++alphaPtr;
-	} else {
-	  for (i = 0; i < nComps; ++i) {
-	    *dataPtr++ = sColor[i];
-	  }
-	  *alphaPtr++ = 0xff;
-	}
-      }
-    }
-
-  } else {
-    bxMin = 0;
-    byMin = 0;
-    bxMax = bitmapWidth;
-    byMax = bitmapHeight;
+  // check clipping and composite the bitmap
+  int xMin = x;
+  int yMin = y;
+  int xMax = x + tBitmap->getWidth();
+  int yMax = y + tBitmap->getHeight();
+  SplashClipResult clipRes = splash->limitRectToClipRect(&xMin, &yMin,
+							 &xMax, &yMax);
+  if (clipRes != splashClipAllOutside) {
+    setOverprintMask(state, state->getFillColorSpace(),
+		     state->getFillOverprint(), state->getOverprintMode(),
+		     NULL);
+    splash->composite(tBitmap, xMin - x, yMin - y, xMin, yMin,
+		      xMax - xMin, yMax - yMin,
+		      clipRes == splashClipAllInside, gFalse);
   }
 
-  // render the shading into the bitmap
-  dx = x1 - x0;
-  dy = y1 - y0;
-  dr = r1 - r0;
-  r0dr = r0 * dr;
-  r02 = r0 * r0;
-  a = dx * dx + dy * dy - dr * dr;
-  if (fabs(a) < 0.00001) {
-    aIsZero = gTrue;
-    a2 = 0;
-  } else {
-    aIsZero = gFalse;
-    a2 = 1 / (2 * a);
-  }
-  for (y = byMin; y < byMax; ++y) {
-    dataPtr = tBitmap->getDataPtr()
-              + y * tBitmap->getRowSize() + bxMin * nComps;
-    alphaPtr = tBitmap->getAlphaPtr() + y * tBitmap->getAlphaRowSize() + bxMin;
-    for (x = bxMin; x < bxMax; ++x) {
-
-      // convert coords to user space
-      tx = ixMin + x + 0.5;
-      ty = iyMin + y + 0.5;
-      xx = tx * ictm[0] + ty * ictm[2] + ictm[4];
-      yy = tx * ictm[1] + ty * ictm[3] + ictm[5];
-
-      // compute the radius of the circle at x,y
-      b = 2 * ((xx - x0) * dx + (yy - y0) * dy + r0dr);
-      c = (xx - x0) * (xx - x0) + (yy - y0) * (yy - y0) - r02;
-      s = 0;
-      go = gFalse;
-      if (aIsZero) {
-	if (fabs(b) < 0.000001) {
-	  if (c <= 0) {
-	    if (ext0) {
-	      s = 0;
-	      go = gTrue;
-	    }
-	  } else {
-	    if (ext1) {
-	      s = 1;
-	      go = gTrue;
-	    }
-	  }
-	} else {
-	  s0 = c / b;
-	  rs0 = r0 + s0 * (r1 - r0);
-	  if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) { 
-	    s = s0;
-	    go = gTrue;
-	  }
-	}
-      } else {
-	e = b*b -  4*a*c;
-	if (e >= 0) {
-	  es = sqrt(e);
-	  s0 = (b + es) * a2;
-	  s1 = (b - es) * a2;
-	  rs0 = r0 + s0 * (r1 - r0);
-	  rs1 = r0 + s1 * (r1 - r0);
-	  if (s0 > s1) {
-	    if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) {
-	      s = s0;
-	      go = gTrue;
-	    } else if ((s1 >= 0 || ext0) && (s1 <= 1 || ext1) && rs1 >= 0) {
-	      s = s1;
-	      go = gTrue;
-	    }
-	  } else {
-	    if ((s1 >= 0 || ext0) && (s1 <= 1 || ext1) && rs1 >= 0) {
-	      s = s1;
-	      go = gTrue;
-	    } else if ((s0 >= 0 || ext0) && (s0 <= 1 || ext1) && rs0 >= 0) {
-	      s = s0;
-	      go = gTrue;
-	    }
-	  }
-	}
-      }
-      if (!go) {
-	dataPtr += nComps;
-	++alphaPtr;
-	continue;
-      }
-      if (s <= 0) {
-	sColor = sColors;
-      } else if (s >= 1) {
-	sColor = sColors + (nColors - 1) * nComps;
-      } else {
-	i = (int)((nColors - 1) * s + 0.5);
-	sColor = sColors + i * nComps;
-      }
-      for (i = 0; i < nComps; ++i) {
-	*dataPtr++ = sColor[i];
-      }
-      *alphaPtr++ = 0xff;
-    }
-  }
-
-  // composite the bitmap
-  setOverprintMask(state, state->getFillColorSpace(),
-		   state->getFillOverprint(), state->getOverprintMode(),
-		   NULL);
-  splash->composite(tBitmap, 0, 0, ixMin, iyMin, bitmapWidth, bitmapHeight,
-		    clipRes == splashClipAllInside, gFalse);
-
-  gfree(sColors);
   delete tBitmap;
 
   return gTrue;
 }
 
-void SplashOutputDev::computeShadingColor(GfxState *state,
-					  SplashColorMode mode,
-					  GfxColor *color,
-					  SplashColorPtr sColor) {
-  GfxGray gray;
-  GfxRGB rgb;
-#if SPLASH_CMYK
-  GfxCMYK cmyk;
-#endif
-
-  state->setFillColor(color);
-  switch (mode) {
-  case splashModeMono8:
-    state->getFillGray(&gray);
-    getColor(gray, sColor);
-    break;
-  case splashModeRGB8:
-    state->getFillRGB(&rgb);
-    getColor(&rgb, sColor);
-    break;
-#if SPLASH_CMYK
-  case splashModeCMYK8:
-    state->getFillCMYK(&cmyk);
-    getColor(&cmyk, sColor);
-    break;
-#endif
-  case splashModeMono1:
-  case splashModeBGR8:
-    // mode cannot be Mono1 or BGR8
-    break;
-  }
-}
-
-
 void SplashOutputDev::clip(GfxState *state) {
   SplashPath *path;
 
@@ -2799,6 +2205,7 @@
 
   if (skipHorizText || skipRotatedText) {
     state->getFontTransMat(&m[0], &m[1], &m[2], &m[3]);
+    // this matches the 'diagonal' test in TextPage::updateFont()
     horiz = m[0] > 0 && fabs(m[1]) < 0.001 &&
             fabs(m[2]) < 0.001 && m[3] < 0;
     if ((skipHorizText && horiz) || (skipRotatedText && !horiz)) {
@@ -2924,7 +2331,7 @@
   }
 
   if (!(gfxFont = state->getFont())) {
-    return gFalse;
+    return gTrue;
   }
   fontID = gfxFont->getID();
   ctm = state->getCTM();
@@ -2954,7 +2361,7 @@
 	}
       } else {
 	for (j = nT3Fonts - 1; j >= 0; --j) {
-	  if (!t3FontCache[j]->inUse) {
+	  if (t3FontCache[j]->refCount == 0) {
 	    break;
 	  }
 	}
@@ -3038,7 +2445,11 @@
     }
   }
 
-  t3Font->inUse = gTrue;
+  if (t3Font->refCount > 1000) {
+    error(errSyntaxError, -1, "Type 3 CharProcs nested too deeply");
+    return gTrue;
+  }
+  ++t3Font->refCount;
 
   // push a new Type 3 glyph record
   t3gs = new T3GlyphStack();
@@ -3050,6 +2461,10 @@
   t3GlyphStack->cacheData = NULL;
   t3GlyphStack->haveDx = gFalse;
   t3GlyphStack->doNotCache = gFalse;
+#if 1 //~t3-sa
+  t3GlyphStack->savedStrokeAdjust = splash->getStrokeAdjust();
+  splash->setStrokeAdjust(splashStrokeAdjustOff);
+#endif
 
   return gFalse;
 }
@@ -3074,9 +2489,12 @@
     drawType3Glyph(state, t3GlyphStack->cache,
 		   t3GlyphStack->cacheTag, t3GlyphStack->cacheData);
   }
+#if 1 //~t3-sa
+  splash->setStrokeAdjust(t3GlyphStack->savedStrokeAdjust);
+#endif
   t3gs = t3GlyphStack;
   t3GlyphStack = t3gs->next;
-  t3gs->cache->inUse = gFalse;
+  --t3gs->cache->refCount;
   delete t3gs;
 }
 
@@ -3186,6 +2604,7 @@
   // create the temporary bitmap
   if (colorMode == splashModeMono1) {
     colorMode = splashModeMono1;
+    traceMessage("T3 glyph bitmap");
     bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1,
 			      splashModeMono1, gFalse, gTrue, bitmap);
     splash = new Splash(bitmap, gFalse,
@@ -3196,6 +2615,7 @@
     color[0] = 0xff;
   } else {
     colorMode = splashModeMono8;
+    traceMessage("T3 glyph bitmap");
     bitmap = new SplashBitmap(t3Font->glyphW, t3Font->glyphH, 1,
 			      splashModeMono8, gFalse, gTrue, bitmap);
     splash = new Splash(bitmap, vectorAntialias,
@@ -3299,7 +2719,7 @@
   imgMaskData.height = height;
   imgMaskData.y = 0;
 
-  imgTag = makeImageTag(ref);
+  imgTag = makeImageTag(ref, gfxRenderingIntentRelativeColorimetric, NULL);
   splash->fillImageMask(imgTag,
 			&imageMaskSrc, &imgMaskData, width, height, mat,
 			t3GlyphStack != NULL, interpolate);
@@ -3344,6 +2764,7 @@
   imgMaskData.width = width;
   imgMaskData.height = height;
   imgMaskData.y = 0;
+  traceMessage("image mask soft mask bitmap");
   maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(),
 				1, splashModeMono8, gFalse, gTrue, bitmap);
   maskSplash = new Splash(maskBitmap, gTrue, splash->getImageCache());
@@ -3354,7 +2775,7 @@
   clearMaskRegion(state, maskSplash, 0, 0, 1, 1);
   maskColor[0] = 0xff;
   maskSplash->setFillPattern(new SplashSolidColor(maskColor));
-  imgTag = makeImageTag(ref);
+  imgTag = makeImageTag(ref, gfxRenderingIntentRelativeColorimetric, NULL);
   maskSplash->fillImageMask(imgTag, &imageMaskSrc, &imgMaskData,
 			    width, height, mat, gFalse, interpolate);
   delete imgTag;
@@ -3643,7 +3064,8 @@
     srcMode = colorMode;
   }
   src = maskColors ? &alphaImageSrc : &imageSrc;
-  imgTag = makeImageTag(ref);
+  imgTag = makeImageTag(ref, state->getRenderingIntent(),
+			colorMap->getColorSpace());
   splash->drawImage(imgTag,
 		    src, &imgData, srcMode, maskColors ? gTrue : gFalse,
 		    width, height, mat, interpolate);
@@ -3775,9 +3197,9 @@
 void SplashOutputDev::drawMaskedImage(GfxState *state, Object *ref,
 				      Stream *str, int width, int height,
 				      GfxImageColorMap *colorMap,
-				      Stream *maskStr, int maskWidth,
-				      int maskHeight, GBool maskInvert,
-				      GBool interpolate) {
+				      Object *maskRef, Stream *maskStr,
+				      int maskWidth, int maskHeight,
+				      GBool maskInvert, GBool interpolate) {
   GfxImageColorMap *maskColorMap;
   Object maskDecode, decodeLow, decodeHigh;
   double *ctm;
@@ -3817,7 +3239,7 @@
 					new GfxDeviceGrayColorSpace());
     maskDecode.free();
     drawSoftMaskedImage(state, ref, str, width, height, colorMap,
-			maskStr, maskWidth, maskHeight, maskColorMap,
+			maskRef, maskStr, maskWidth, maskHeight, maskColorMap,
 			NULL, interpolate);
     delete maskColorMap;
 
@@ -3837,6 +3259,7 @@
     imgMaskData.width = maskWidth;
     imgMaskData.height = maskHeight;
     imgMaskData.y = 0;
+    traceMessage("masked image bitmap");
     maskBitmap = new SplashBitmap(width, height, 1, splashModeMono1,
 				  gFalse, gTrue, bitmap);
     maskSplash = new Splash(maskBitmap, gFalse, splash->getImageCache());
@@ -3931,7 +3354,8 @@
     } else {
       srcMode = colorMode;
     }
-    imgTag = makeImageTag(ref);
+    imgTag = makeImageTag(ref, state->getRenderingIntent(),
+			  colorMap->getColorSpace());
     splash->drawImage(imgTag,
 		      &maskedImageSrc, &imgData, srcMode, gTrue,
 		      width, height, mat, interpolate);
@@ -4055,7 +3479,7 @@
 void SplashOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref,
 					  Stream *str, int width, int height,
 					  GfxImageColorMap *colorMap,
-					  Stream *maskStr,
+					  Object *maskRef, Stream *maskStr,
 					  int maskWidth, int maskHeight,
 					  GfxImageColorMap *maskColorMap,
 					  double *matte, GBool interpolate) {
@@ -4147,18 +3571,13 @@
 #endif
     }
     //~ could add the matteImgData.lookup special case
-    if (colorMap->getBits() <= 8) {
-      n = 1 << maskColorMap->getBits();
-    } else {
-      // GfxImageColorMap and ImageStream compress 16-bit samples to 8-bit
-      n = 1 << 8;
-    }
     matteImgData.colorMode = colorMode;
     matteImgData.invert = reverseVideo && reverseVideoInvertImages;
     matteImgData.width = width;
     matteImgData.height = height;
     matteImgData.y = 0;
-    imgTag = makeImageTag(ref);
+    imgTag = makeImageTag(ref, state->getRenderingIntent(),
+			  colorMap->getColorSpace());
     splash->drawImage(imgTag, &softMaskMatteImageSrc, &matteImgData,
 		      srcMode, gTrue, width, height, mat, interpolate);
     delete imgTag;
@@ -4186,7 +3605,12 @@
     imgMaskData.width = maskWidth;
     imgMaskData.height = maskHeight;
     imgMaskData.y = 0;
-    n = 1 << maskColorMap->getBits();
+    if (maskColorMap->getBits() <= 8) {
+      n = 1 << maskColorMap->getBits();
+    } else {
+      // GfxImageColorMap and ImageStream compress 16-bit samples to 8-bit
+      n = 1 << 8;
+    }
     imgMaskData.lookup = (SplashColorPtr)gmalloc(n);
     for (i = 0; i < n; ++i) {
       pix = (Guchar)i;
@@ -4193,6 +3617,7 @@
       maskColorMap->getGray(&pix, &gray, state->getRenderingIntent());
       imgMaskData.lookup[i] = colToByte(gray);
     }
+    traceMessage("soft masked image bitmap");
     maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(),
 				  1, splashModeMono8, gFalse, gTrue, bitmap);
     maskSplash = new Splash(maskBitmap, vectorAntialias,
@@ -4273,7 +3698,8 @@
       }
     }
 
-    imgTag = makeImageTag(ref);
+    imgTag = makeImageTag(ref, state->getRenderingIntent(),
+			  colorMap->getColorSpace());
     splash->drawImage(imgTag,
 		      &imageSrc, &imgData, srcMode, gFalse, width, height, mat,
 		      interpolate);
@@ -4288,11 +3714,14 @@
   }
 }
 
-GString *SplashOutputDev::makeImageTag(Object *ref) {
-  if (!ref || !ref->isRef()) {
+GString *SplashOutputDev::makeImageTag(Object *ref, GfxRenderingIntent ri,
+				       GfxColorSpace *colorSpace) {
+  if (!ref || !ref->isRef() ||
+      (colorSpace && colorSpace->isDefaultColorSpace())) {
     return NULL;
   }
-  return GString::format("{0:d}_{1:d}", ref->getRefNum(), ref->getRefGen());
+  return GString::format("{0:d}_{1:d}_{2:d}",
+			 ref->getRefNum(), ref->getRefGen(), (int)ri);
 }
 
 void SplashOutputDev::reduceImageResolution(Stream *str, double *ctm,
@@ -4471,7 +3900,8 @@
     ty = bh - 1;
   }
   w = (int)ceil(xMax) - tx + 1;
-  if (tx + w > bw) {
+  // NB bw and tx are both non-negative, so 'bw - tx' can't overflow
+  if (bw - tx < w) {
     w = bw - tx;
   }
   if (w < 1) {
@@ -4478,7 +3908,8 @@
     w = 1;
   }
   h = (int)ceil(yMax) - ty + 1;
-  if (ty + h > bh) {
+  // NB bh and ty are both non-negative, so 'bh - ty' can't overflow
+  if (bh - ty < h) {
     h = bh - ty;
   }
   if (h < 1) {
@@ -4524,6 +3955,7 @@
   }
 
   // create the temporary bitmap
+  traceMessage("t-group bitmap");
   bitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, gTrue,
 			    bitmapTopDown, transpGroup->origBitmap); 
   splash = new Splash(bitmap, vectorAntialias,
@@ -4535,32 +3967,70 @@
   splash->setEnablePathSimplification(
 		 globalParams->getEnablePathSimplification());
   copyState(transpGroup->origSplash, gTrue);
+  if (!isolated || knockout) {
+    // non-isolated and knockout groups nested in another group will
+    // read the parent group bitmap, so we need to force any deferred
+    // initialization on the parent
+    transpGroup->origSplash->forceDeferredInit(ty, h);
+  }
   if (isolated) {
-    for (i = 0; i < splashMaxColorComps; ++i) {
-      color[i] = 0;
+    // isolated group
+    backdropBitmap = transpGroup->origBitmap;
+    transpGroup->backdropBitmap = NULL;
+    if (forSoftMask) {
+      // setSoftMask uses the whole bitmap, not just the mod region,
+      // so we can't use the deferred initialization optimization
+      for (i = 0; i < splashMaxColorComps; ++i) {
+	color[i] = 0;
+      }
+      splash->clear(color, 0);
+      splash->setInTransparencyGroup(backdropBitmap, tx, ty,
+				     splashGroupDestPreInit,
+				     gFalse, knockout);
+    } else {
+      splash->setInTransparencyGroup(backdropBitmap, tx, ty,
+				     splashGroupDestInitZero,
+				     gFalse, knockout);
     }
-    splash->clear(color, 0);
-  } else {
-    splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h);
-  }
-  if (!isolated &&
-      transpGroup->origBitmap->getAlphaPtr() &&
-      transpGroup->origSplash->getInNonIsolatedGroup() &&
-      colorMode != splashModeMono1) {
-    // when drawing a non-isolated group into another non-isolated group,
+  } else if (transpGroup->origBitmap->getAlphaPtr() &&
+	     transpGroup->origSplash->getInNonIsolatedGroup() &&
+	     colorMode != splashModeMono1) {
+    // non-isolated group drawn in another non-isolated group:
     // compute a backdrop bitmap with corrected alpha values
+    traceMessage("t-group backdrop bitmap");
     backdropBitmap = new SplashBitmap(w, h, bitmapRowPad, colorMode, gTrue,
 				      bitmapTopDown, transpGroup->origBitmap);
     transpGroup->origSplash->blitCorrectedAlpha(backdropBitmap,
 						tx, ty, 0, 0, w, h);
     transpGroup->backdropBitmap = backdropBitmap;
-    splash->setInTransparencyGroup(backdropBitmap, 0, 0,
-				   !isolated, knockout);
+    if (forSoftMask) {
+      // setSoftMask uses the whole bitmap, not just the mod region,
+      // so we can't use the deferred initialization optimization
+      splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h);
+      splash->setInTransparencyGroup(backdropBitmap, 0, 0,
+				     splashGroupDestPreInit,
+				     gTrue, knockout);
+    } else {
+      splash->setInTransparencyGroup(backdropBitmap, 0, 0,
+				     splashGroupDestInitCopy,
+				     gTrue, knockout);
+    }
   } else {
+    // other non-isolated group
     backdropBitmap = transpGroup->origBitmap;
     transpGroup->backdropBitmap = NULL;
-    splash->setInTransparencyGroup(backdropBitmap, tx, ty,
-				   !isolated, knockout);
+    if (forSoftMask) {
+      // setSoftMask uses the whole bitmap, not just the mod region,
+      // so we can't use the deferred initialization optimization
+      splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h);
+      splash->setInTransparencyGroup(backdropBitmap, tx, ty,
+				     splashGroupDestPreInit,
+				     gTrue, knockout);
+    } else {
+      splash->setInTransparencyGroup(backdropBitmap, tx, ty,
+				     splashGroupDestInitCopy,
+				     gTrue, knockout);
+    }
   }
   splash->clearModRegion();
   transpGroup->tBitmap = bitmap;
@@ -4632,7 +4102,8 @@
   Splash *tSplash;
   SplashTransparencyGroup *transpGroup;
   SplashColor color;
-  SplashColorPtr p;
+  SplashColorPtr p, colorPtr, colorPtr2;
+  Guchar *alphaPtr;
   GfxGray gray;
   GfxRGB rgb;
 #if SPLASH_CMYK
@@ -4639,7 +4110,9 @@
   GfxCMYK cmyk;
 #endif
   double backdrop, backdrop2, lum, lum2;
-  int tx, ty, x, y;
+  Guchar lum8;
+  SplashBitmapRowSize rowSize;
+  int tw, th, tNComps, tx, ty, x, y;
 
   tx = transpGroupStack->tx;
   ty = transpGroupStack->ty;
@@ -4709,6 +4182,7 @@
     backdrop2 = backdrop;
   }
 
+  traceMessage("soft mask bitmap");
   softMask = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(),
 			      1, splashModeMono8, gFalse, gTrue, bitmap);
   memset(softMask->getDataPtr(), (int)(backdrop2 * 255.0 + 0.5),
@@ -4715,45 +4189,65 @@
 	 softMask->getRowSize() * softMask->getHeight());
   if (tx < softMask->getWidth() && ty < softMask->getHeight()) {
     p = softMask->getDataPtr() + ty * softMask->getRowSize() + tx;
-    for (y = 0; y < tBitmap->getHeight(); ++y) {
-      for (x = 0; x < tBitmap->getWidth(); ++x) {
-	if (alpha) {
-	  lum = tBitmap->getAlpha(x, y) / 255.0;
-	} else {
-	  tBitmap->getPixel(x, y, color);
+    tw = tBitmap->getWidth();
+    th = tBitmap->getHeight();
+    rowSize = softMask->getRowSize();
+    if (alpha) {
+      alphaPtr = tBitmap->getAlphaPtr();
+      for (y = 0; y < th; ++y) {
+	for (x = 0; x < tw; ++x) {
+	  lum = *alphaPtr++ / 255.0;
+	  if (transferFunc) {
+	    transferFunc->transform(&lum, &lum2);
+	  } else {
+	    lum2 = lum;
+	  }
+	  p[x] = (Guchar)(lum2 * 255.0 + 0.5);
+	}
+	p += rowSize;
+      }
+    } else {
+      colorPtr = tBitmap->getDataPtr();
+      tNComps = splashColorModeNComps[tBitmap->getMode()];
+      lum8 = 0;  // make gcc happy
+      for (y = 0; y < th; ++y) {
+	colorPtr2 = colorPtr;
+	for (x = 0; x < tw; ++x) {
 	  // convert to luminosity
 	  switch (tBitmap->getMode()) {
 	  case splashModeMono1:
+	    lum8 = 0;
+	    break;
 	  case splashModeMono8:
-	    lum = color[0] / 255.0;
+	    lum8 = colorPtr2[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];
+	    // [0.3, 0.59, 0.11] * 255 = [77, 150, 28]
+	    lum8 = div255(77 * colorPtr2[0] +
+			  150 * colorPtr2[1] +
+			  28 * colorPtr2[1]);
 	    break;
 #if SPLASH_CMYK
 	  case splashModeCMYK8:
-	    lum = (1 - color[3] / 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;
-	    }
+	    lum8 = clip255(255 - colorPtr2[3]
+			   - div255(77 * colorPtr2[0] +
+				    150 * colorPtr2[1] +
+				    28 * colorPtr2[2]));
 	    break;
 #endif
 	  }
+	  if (transferFunc) {
+	    lum = lum8 / 255.0;
+	    transferFunc->transform(&lum, &lum2);
+	    lum8 = (Guchar)(lum2 * 255.0 + 0.5);
+	  }
+	  p[x] = lum8;
+	  colorPtr2 += tNComps;
 	}
-	if (transferFunc) {
-	  transferFunc->transform(&lum, &lum2);
-	} else {
-	  lum2 = lum;
-	}
-	p[x] = (Guchar)(lum2 * 255.0 + 0.5);
+	p += rowSize;
+	colorPtr += tBitmap->getRowSize();
       }
-      p += softMask->getRowSize();
     }
   }
   splash->setSoftMask(softMask);
@@ -4993,6 +4487,10 @@
 }
 
 #if 1 //~tmp: turn off anti-aliasing temporarily
+// This was originally used with gradient shadings -- that's no longer
+// necessary, now that shadings are all converted to device space
+// images.  It's still used with knockout groups, however, because the
+// rasterizer doesn't properly separate opacity and shape.
 void SplashOutputDev::setInShading(GBool sh) {
   splash->setInShading(sh);
 }

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/SplashOutputDev.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -69,11 +69,6 @@
   // 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 gTrue; }
-
   // Does this device use beginType3Char/endType3Char?  Otherwise,
   // text in Type 3 fonts will be drawn with drawChar/drawString.
   virtual GBool interpretType3Chars() { return gTrue; }
@@ -123,8 +118,7 @@
 				 double *mat, double *bbox,
 				 int x0, int y0, int x1, int y1,
 				 double xStep, double yStep);
-  virtual GBool axialShadedFill(GfxState *state, GfxAxialShading *shading);
-  virtual GBool radialShadedFill(GfxState *state, GfxRadialShading *shading);
+  virtual GBool shadedFill(GfxState *state, GfxShading *shading);
 
   //----- path clipping
   virtual void clip(GfxState *state);
@@ -156,12 +150,13 @@
   virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str,
 			       int width, int height,
 			       GfxImageColorMap *colorMap,
-			       Stream *maskStr, int maskWidth, int maskHeight,
+			       Object *maskRef, Stream *maskStr,
+			       int maskWidth, int maskHeight,
 			       GBool maskInvert, GBool interpolate);
   virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str,
 				   int width, int height,
 				   GfxImageColorMap *colorMap,
-				   Stream *maskStr,
+				   Object *maskRef, Stream *maskStr,
 				   int maskWidth, int maskHeight,
 				   GfxImageColorMap *maskColorMap,
 				   double *matte, GBool interpolate);
@@ -240,10 +235,6 @@
   // Get the screen parameters.
   SplashScreenParams *getScreenParams() { return &screenParams; }
 
-#if 1 //~tmp: turn off anti-aliasing temporarily
-  virtual void setInShading(GBool sh);
-#endif
-
 private:
 
   void setupScreenParams(double hDPI, double vDPI);
@@ -260,10 +251,6 @@
   void setOverprintMask(GfxState *state, GfxColorSpace *colorSpace,
 			GBool overprintFlag, int overprintMode,
 			GfxColor *singleColor);
-  void computeShadingColor(GfxState *state,
-			   SplashColorMode mode,
-			   GfxColor *color,
-			   SplashColorPtr sColor);
   SplashPath *convertPath(GfxState *state, GfxPath *path,
 			  GBool dropEmptySubpaths);
   void doUpdateFont(GfxState *state);
@@ -279,7 +266,8 @@
   static GBool softMaskMatteImageSrc(void *data,
 				     SplashColorPtr colorLine,
 				     Guchar *alphaLine);
-  GString *makeImageTag(Object *ref);
+  GString *makeImageTag(Object *ref, GfxRenderingIntent ri,
+			GfxColorSpace *colorSpace);
   void reduceImageResolution(Stream *str, double *mat,
 			     int *width, int *height);
   void clearMaskRegion(GfxState *state,
@@ -287,6 +275,9 @@
 		       double xMin, double yMin,
 		       double xMax, double yMax);
   void copyState(Splash *oldSplash, GBool copyColors);
+#if 1 //~tmp: turn off anti-aliasing temporarily
+  void setInShading(GBool sh);
+#endif
 
   SplashColorMode colorMode;
   int bitmapRowPad;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1317,6 +1317,7 @@
   eof = gFalse;
   inputBits = 0;
   clearTable();
+  totalIn = totalOut = 0;
 }
 
 GBool LZWStream::processNextCode() {
@@ -1382,7 +1383,15 @@
       nextBits = 12;
   }
   prevCode = code;
+  totalOut += seqLength;
 
+  // check for a 'decompression bomb'
+  if (totalOut > 50000000 && totalIn < totalOut / 250) {
+    error(errSyntaxError, getPos(), "Decompression bomb in flate stream");
+    eof = gTrue;
+    return gFalse;
+  }
+
   // reset buffer
   seqIndex = 0;
 
@@ -1405,6 +1414,7 @@
       return EOF;
     inputBuf = (inputBuf << 8) | (c & 0xff);
     inputBits += 8;
+    ++totalIn;
   }
   code = (inputBuf >> (inputBits - nextBits)) & ((1 << nextBits) - 1);
   inputBits -= nextBits;
@@ -2268,7 +2278,6 @@
 GString *CCITTFaxStream::getPSFilter(int psLevel, const char *indent,
 				     GBool okToReadStream) {
   GString *s;
-  char s1[50];
 
   if (psLevel < 2) {
     return NULL;
@@ -2278,8 +2287,7 @@
   }
   s->append(indent)->append("<< ");
   if (encoding != 0) {
-    sprintf(s1, "/K %d ", encoding);
-    s->append(s1);
+    s->appendf("/K {0:d} ", encoding);
   }
   if (endOfLine) {
     s->append("/EndOfLine true ");
@@ -2287,11 +2295,9 @@
   if (byteAlign) {
     s->append("/EncodedByteAlign true ");
   }
-  sprintf(s1, "/Columns %d ", columns);
-  s->append(s1);
+  s->appendf("/Columns {0:d} ", columns);
   if (rows != 0) {
-    sprintf(s1, "/Rows %d ", rows);
-    s->append(s1);
+    s->appendf("/Rows {0:d} ", rows);
   }
   if (!endOfBlock) {
     s->append("/EndOfBlock false ");
@@ -4059,7 +4065,7 @@
       error(errSyntaxError, getPos(), "Bad DCT quantization table");
       return gFalse;
     }
-    if (index == numQuantTables) {
+    if (index >= numQuantTables) {
       numQuantTables = index + 1;
     }
     for (i = 0; i < 64; ++i) {
@@ -4951,6 +4957,8 @@
   endOfBlock = eof = gTrue;
   cmf = str->getChar();
   flg = str->getChar();
+  totalIn = 2;
+  totalOut = 0;
   if (cmf == EOF || flg == EOF)
     return;
   if ((cmf & 0x0f) != 0x08) {
@@ -5017,7 +5025,7 @@
 }
 
 int FlateStream::getBlock(char *blk, int size) {
-  int n;
+  int n, k;
 
   if (pred) {
     return pred->getBlock(blk, size);
@@ -5031,11 +5039,17 @@
       }
       readSome();
     }
-    while (remain && n < size) {
-      blk[n++] = buf[index];
-      index = (index + 1) & flateMask;
-      --remain;
+    k = remain;
+    if (size - n < k) {
+      k = size - n;
     }
+    if (flateWindow - index < k) {
+      k = flateWindow - index;
+    }
+    memcpy(blk + n, buf + index, k);
+    n += k;
+    index = (index + k) & flateMask;
+    remain -= k;
   }
   return n;
 }
@@ -5061,7 +5075,7 @@
 void FlateStream::readSome() {
   int code1, code2;
   int len, dist;
-  int i, j, k;
+  int src, dest, n1, n2, n3, i, j, k;
   int c;
 
   if (endOfBlock) {
@@ -5090,12 +5104,78 @@
       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;
+      dest = index;
+      src = (index - dist) & flateMask;
+      // the following is an optimized version of:
+      // for (k = 0; k < len; ++k) {
+      //   buf[dest] = buf[src];
+      //   dest = (dest + 1) & flateMask;
+      //   src = (src + 1) & flateMask;
+      // }
+      if (dest + len <= flateWindow) {
+	if (src + len <= flateWindow) {
+	  for (k = 0; k < len; ++k) {
+	    buf[dest + k] = buf[src + k];
+	  }
+	} else {
+	  n1 = flateWindow - src;
+	  n2 = len - n1;
+	  for (k = 0; k < n1; ++k) {
+	    buf[dest + k] = buf[src + k];
+	  }
+	  dest = dest + n1;
+	  src = 0;
+	  for (k = 0; k < n2; ++k) {
+	    buf[dest + k] = buf[src + k];
+	  }
+	}
+      } else {
+	if (src + len <= flateWindow) {
+	  n1 = flateWindow - dest;
+	  n2 = len - n1;
+	  for (k = 0; k < n1; ++k) {
+	    buf[dest + k] = buf[src + k];
+	  }
+	  dest = 0;
+	  src = src + n1;
+	  for (k = 0; k < n2; ++k) {
+	    buf[dest + k] = buf[src + k];
+	  }
+	} else if (src < dest) {
+	  n1 = flateWindow - dest;
+	  n2 = dest - src;
+	  n3 = len - n1 - n2;
+	  for (k = 0; k < n1; ++k) {
+	    buf[dest + k] = buf[src + k];
+	  }
+	  dest = 0;
+	  src = src + n1;
+	  for (k = 0; k < n2; ++k) {
+	    buf[dest + k] = buf[src + k];
+	  }
+	  dest = n2;
+	  src = 0;
+	  for (k = 0; k < n3; ++k) {
+	    buf[dest + k] = buf[src + k];
+	  }
+	} else {
+	  n1 = flateWindow - src;
+	  n2 = src - dest;
+	  n3 = len - n1 - n2;
+	  for (k = 0; k < n1; ++k) {
+	    buf[dest + k] = buf[src + k];
+	  }
+	  dest = dest + n1;
+	  src = 0;
+	  for (k = 0; k < n2; ++k) {
+	    buf[dest + k] = buf[src + k];
+	  }
+	  dest = 0;
+	  src = n2;
+	  for (k = 0; k < n3; ++k) {
+	    buf[dest + k] = buf[src + k];
+	  }
+	}
       }
       remain = len;
     }
@@ -5113,8 +5193,17 @@
     blockLen -= len;
     if (blockLen == 0)
       endOfBlock = gTrue;
+    totalIn += remain;
   }
+  totalOut += remain;
 
+  // check for a 'decompression bomb'
+  if (totalOut > 50000000 && totalIn < totalOut / 250) {
+    error(errSyntaxError, getPos(), "Decompression bomb in flate stream");
+    endOfBlock = eof = gTrue;
+    remain = 0;
+  }
+
   return;
 
 err:
@@ -5164,6 +5253,7 @@
 	    "Bad uncompressed block length in flate stream");
     codeBuf = 0;
     codeSize = 0;
+    totalIn += 4;
 
   // compressed block with fixed codes
   } else if (blockHdr == 1) {
@@ -5358,6 +5448,7 @@
     }
     codeBuf |= (c & 0xff) << codeSize;
     codeSize += 8;
+    ++totalIn;
   }
   code = &tab->codes[codeBuf & ((1 << tab->maxLen) - 1)];
   if (codeSize == 0 || codeSize < code->len || code->len == 0) {
@@ -5376,6 +5467,7 @@
       return EOF;
     codeBuf |= (c & 0xff) << codeSize;
     codeSize += 8;
+    ++totalIn;
   }
   c = codeBuf & ((1 << bits) - 1);
   codeBuf >>= bits;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Stream.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -501,6 +501,8 @@
   int seqLength;		// length of current sequence
   int seqIndex;			// index into current sequence
   GBool first;			// first code after a table clear
+  unsigned long long totalIn;	// total number of encoded bytes read so far
+  unsigned long long totalOut;	// total number of bytes decoded so far
 
   GBool processNextCode();
   void clearTable();
@@ -814,6 +816,8 @@
   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
+  unsigned long long totalIn;	// total number of encoded bytes read so far
+  unsigned long long totalOut;	// total number of bytes decoded so far
 
   static int			// code length code reordering
     codeLenCodeMap[flateMaxCodeLenCodes];

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -26,6 +26,7 @@
 #include "gmempp.h"
 #include "GString.h"
 #include "GList.h"
+#include "gfile.h"
 #include "config.h"
 #include "Error.h"
 #include "GlobalParams.h"
@@ -88,11 +89,13 @@
 // Inter-character spacing that varies by less than this multiple of
 // font size is assumed to be equivalent.
 #define uniformSpacing 0.07
+#define tableModeUniformSpacing 0.14
 
 // Typical word spacing, as a fraction of font size.  This will be
 // added to the minimum inter-character spacing, to account for wide
 // character spacing.
 #define wordSpacing 0.1
+#define tableModeWordSpacing 0.2
 
 // Minimum paragraph indent from left margin, as a fraction of font
 // size.
@@ -114,6 +117,9 @@
 // value multiplied by font size.
 #define simpleLayoutGapThreshold 0.7
 
+// Minimum overlap in simple2 mode.
+#define simple2MinOverlap 0.2
+
 // Table cells (TextColumns) are allowed to overlap by this much
 // in table layout mode (as a fraction of cell width or height).
 #define tableCellOverlapSlack 0.05
@@ -157,9 +163,28 @@
 // rectangles, in order to work around flakey ascent values in fonts.
 #define selectionAscent 0.8
 
+// Grid size used to bin sort characters for overlap detection.
+#define overlapGridWidth 20
+#define overlapGridHeight 20
+
+// Minimum character bbox overlap (horizontal and vertical) as a
+// fraction of character bbox width/height for a character to be
+// treated as overlapping.
+#define minCharOverlap 0.3
+
 #define maxUnicodeLen 16
 
 //------------------------------------------------------------------------
+
+static inline double dmin(double x, double y) {
+  return x < y ? x : y;
+}
+
+static inline double dmax(double x, double y) {
+  return x > y ? x : y;
+}
+
+//------------------------------------------------------------------------
 // TextChar
 //------------------------------------------------------------------------
 
@@ -168,31 +193,36 @@
 
   TextChar(Unicode cA, int charPosA, int charLenA,
 	   double xMinA, double yMinA, double xMaxA, double yMaxA,
-	   int rotA, GBool clippedA, GBool invisibleA,
+	   int rotA, GBool rotatedA, GBool clippedA, GBool invisibleA,
 	   TextFontInfo *fontA, double fontSizeA,
 	   double colorRA, double colorGA, double colorBA);
 
   static int cmpX(const void *p1, const void *p2);
   static int cmpY(const void *p1, const void *p2);
+  static int cmpCharPos(const void *p1, const void *p2);
 
   Unicode c;
   int charPos;
   int charLen;
   double xMin, yMin, xMax, yMax;
-  Guchar rot;
-  char clipped;
-  char invisible;
-  char spaceAfter;
   TextFontInfo *font;
   double fontSize;
   double colorR,
          colorG,
          colorB;
+
+  // group the byte-size fields to minimize object size
+  Guchar rot;
+  char rotated;
+  char clipped;
+  char invisible;
+  char spaceAfter;
+  char overlap;
 };
 
 TextChar::TextChar(Unicode cA, int charPosA, int charLenA,
 		   double xMinA, double yMinA, double xMaxA, double yMaxA,
-		   int rotA, GBool clippedA, GBool invisibleA,
+		   int rotA, GBool rotatedA, GBool clippedA, GBool invisibleA,
 		   TextFontInfo *fontA, double fontSizeA,
 		   double colorRA, double colorGA, double colorBA) {
   double t;
@@ -228,6 +258,7 @@
     yMax = 1e8;
   }
   rot = (Guchar)rotA;
+  rotated = (char)rotatedA;
   clipped = (char)clippedA;
   invisible = (char)invisibleA;
   spaceAfter = (char)gFalse;
@@ -236,6 +267,7 @@
   colorR = colorRA;
   colorG = colorGA;
   colorB = colorBA;
+  overlap = gFalse;
 }
 
 int TextChar::cmpX(const void *p1, const void *p2) {
@@ -264,6 +296,12 @@
   }
 }
 
+int TextChar::cmpCharPos(const void *p1, const void *p2) {
+  const TextChar *ch1 = *(const TextChar **)p1;
+  const TextChar *ch2 = *(const TextChar **)p2;
+  return ch1->charPos - ch2->charPos;
+}
+
 //------------------------------------------------------------------------
 // TextBlock
 //------------------------------------------------------------------------
@@ -408,6 +446,40 @@
 }
 
 //------------------------------------------------------------------------
+// TextCharLine
+//------------------------------------------------------------------------
+
+class TextCharLine {
+public:
+
+  TextCharLine(int rotA);
+  ~TextCharLine();
+  void add(TextChar *ch);
+
+  GList *chars;
+  double yMin, yMax;
+  int rot;
+  TextCharLine *next, *prev;
+};
+
+TextCharLine::TextCharLine(int rotA) {
+  chars = new GList();
+  yMin = yMax = 0;
+  rot = rotA;
+  next = prev = NULL;
+}
+
+TextCharLine::~TextCharLine() {
+  delete chars;
+}
+
+void TextCharLine::add(TextChar *ch) {
+  chars->append(ch);
+  yMin = ch->yMin;
+  yMax = ch->yMax;
+}
+
+//------------------------------------------------------------------------
 // TextGaps
 //------------------------------------------------------------------------
 
@@ -539,8 +611,12 @@
   html = gFalse;
   clipText = gFalse;
   discardDiagonalText = gFalse;
+  discardRotatedText = gFalse;
   discardInvisibleText = gFalse;
   discardClippedText = gFalse;
+  splitRotatedWords = gFalse;
+  overlapHandling = textOutIgnoreOverlaps;
+  separateLargeChars = gTrue;
   insertBOM = gFalse;
   marginLeft = 0;
   marginRight = 0;
@@ -602,13 +678,15 @@
 }
 
 GBool TextFontInfo::matches(GfxState *state) {
-  Ref *id;
+  Ref id;
 
-  if (!state->getFont()) {
-    return gFalse;
+  if (state->getFont()) {
+    id = *state->getFont()->getID();
+  } else {
+    id.num = -1;
+    id.gen = -1;
   }
-  id = state->getFont()->getID();
-  return id->num == fontID.num && id->gen == fontID.gen;
+  return id.num == fontID.num && id.gen == fontID.gen;
 }
 
 //------------------------------------------------------------------------
@@ -618,11 +696,12 @@
 // Build a TextWord object, using chars[start .. start+len-1].
 // (If rot >= 2, the chars list is in reverse order.)
 TextWord::TextWord(GList *chars, int start, int lenA,
-		   int rotA, int dirA, GBool spaceAfterA) {
+		   int rotA, GBool rotatedA, int dirA, GBool spaceAfterA) {
   TextChar *ch;
   int i;
 
-  rot = rotA;
+  rot = (char)rotA;
+  rotated = (char)rotatedA;
   len = lenA;
   text = (Unicode *)gmallocn(len, sizeof(Unicode));
   edge = (double *)gmallocn(len + 1, sizeof(double));
@@ -680,8 +759,8 @@
   ch = (TextChar *)chars->get(start);
   font = ch->font;
   fontSize = ch->fontSize;
-  dir = dirA;
-  spaceAfter = spaceAfterA;
+  dir = (char)dirA;
+  spaceAfter = (char)spaceAfterA;
   underlined = gFalse;
   link = NULL;
   colorR = ch->colorR;
@@ -1057,6 +1136,7 @@
   curFontSize = 0;
   curRot = 0;
   diagonal = gFalse;
+  rotated = gFalse;
   nTinyChars = 0;
   actualText = NULL;
   actualTextLen = 0;
@@ -1108,6 +1188,7 @@
   curFontSize = 0;
   curRot = 0;
   diagonal = gFalse;
+  rotated = gFalse;
   nTinyChars = 0;
   gfree(actualText);
   actualText = NULL;
@@ -1219,7 +1300,12 @@
     m[2] = m2[2];
     m[3] = m2[3];
   }
-  if (fabs(m[0]) >= fabs(m[1]))  {
+  if (curFontSize == 0) {
+    // special case - if the font size is zero, just assume plain
+    // horizontal text
+    curRot = 0;
+    diagonal = gFalse;
+  } else if (fabs(m[0]) >= fabs(m[1]))  {
     if (m[0] > 0) {
       curRot = 0;
     } else {
@@ -1234,6 +1320,9 @@
     }
     diagonal = fabs(m[0]) > diagonalThreshold * fabs(m[1]);
   }
+  // this matches the 'horiz' test in SplashOutputDev::drawChar()
+  rotated = !(m[0] > 0 && fabs(m[1]) < 0.001 &&
+	      fabs(m[2]) < 0.001 && m[3] < 0);
 }
 
 void TextPage::addChar(GfxState *state, double x, double y,
@@ -1260,8 +1349,9 @@
     return;
   }
 
-  // throw away diagonal chars
-  if (control.discardDiagonalText && diagonal) {
+  // throw away diagonal/rotated chars
+  if ((control.discardDiagonalText && diagonal) ||
+      (control.discardRotatedText && rotated)) {
     charPos += nBytes;
     return;
   }
@@ -1299,9 +1389,15 @@
   }
 
   // skip space, tab, and non-breaking space characters
-  if (uLen == 1 && (u[0] == (Unicode)0x20 ||
-		    u[0] == (Unicode)0x09 ||
-		    u[0] == (Unicode)0xa0)) {
+  // (ActualText spans can result in multiple space chars)
+  for (i = 0; i < uLen; ++i) {
+    if (u[i] != (Unicode)0x20 &&
+	u[i] != (Unicode)0x09 &&
+	u[i] != (Unicode)0xa0) {
+      break;
+    }
+  }
+  if (i == uLen && uLen >= 1) {
     charPos += nBytes;
     if (chars->getLength() > 0) {
       ((TextChar *)chars->get(chars->getLength() - 1))->spaceAfter =
@@ -1400,7 +1496,7 @@
       }
       chars->append(new TextChar(uBuf[j], charPos, nBytes,
 				 xMin, yMin, xMax, yMax,
-				 curRot, clipped,
+				 curRot, rotated, clipped,
 				 state->getRender() == 3 || alpha < 0.001,
 				 curFont, curFontSize,
 				 colToDbl(rgb.r), colToDbl(rgb.g),
@@ -1503,6 +1599,10 @@
     writeSimpleLayout(outputStream, outputFunc, uMap, space, spaceLen,
 		      eol, eolLen);
     break;
+  case textOutSimple2Layout:
+    writeSimple2Layout(outputStream, outputFunc, uMap, space, spaceLen,
+		       eol, eolLen);
+    break;
   case textOutLinePrinter:
     writeLinePrinter(outputStream, outputFunc, uMap, space, spaceLen,
 		     eol, eolLen);
@@ -1530,11 +1630,20 @@
   TextColumn *col;
   TextParagraph *par;
   TextLine *line;
+  GList *overlappingChars;
   GList *columns;
   GBool primaryLR;
   GString *s;
   int colIdx, parIdx, lineIdx, rot, n;
 
+#if 0 //~debug
+  dumpChars(chars);
+#endif
+  if (control.overlapHandling != textOutIgnoreOverlaps) {
+    overlappingChars = separateOverlappingText(chars);
+  } else {
+    overlappingChars = NULL;
+  }
   rot = rotateChars(chars);
   primaryLR = checkPrimaryLR(chars);
   tree = splitChars(chars);
@@ -1553,6 +1662,12 @@
     rotateUnderlinesAndLinks(rot);
     generateUnderlinesAndLinks(columns);
   }
+  if (overlappingChars) {
+    if (overlappingChars->getLength() > 0) {
+      columns->append(buildOverlappingTextColumn(overlappingChars));
+    }
+    deleteGList(overlappingChars, TextChar);
+  }
 #if 0 //~debug
   dumpColumns(columns);
 #endif
@@ -1585,24 +1700,44 @@
 
 GList *TextPage::makeColumns() {
   TextBlock *tree;
+  GList *overlappingChars;
   GList *columns;
   GBool primaryLR;
   int rot;
 
-  rot = rotateChars(chars);
-  primaryLR = checkPrimaryLR(chars);
-  if ((tree = splitChars(chars))) {
-    columns = buildColumns(tree, primaryLR);
-    delete tree;
+  if (control.mode == textOutSimple2Layout) {
+    primaryLR = checkPrimaryLR(chars);
+    rotateCharsToZero(chars);
+    columns = buildSimple2Columns(chars);
+    unrotateCharsFromZero(chars);
+    unrotateColumnsFromZero(columns);
   } else {
-    // no text
-    columns = new GList();
+    if (control.overlapHandling != textOutIgnoreOverlaps) {
+      overlappingChars = separateOverlappingText(chars);
+    } else {
+      overlappingChars = NULL;
+    }
+    rot = rotateChars(chars);
+    primaryLR = checkPrimaryLR(chars);
+    if ((tree = splitChars(chars))) {
+      columns = buildColumns(tree, primaryLR);
+      delete tree;
+    } else {
+      // no text
+      columns = new GList();
+    }
+    unrotateChars(chars, rot);
+    unrotateColumns(columns, rot);
+    if (control.html) {
+      generateUnderlinesAndLinks(columns);
+    }
+    if (overlappingChars) {
+      if (overlappingChars->getLength() > 0) {
+	columns->append(buildOverlappingTextColumn(overlappingChars));
+      }
+      deleteGList(overlappingChars, TextChar);
+    }
   }
-  unrotateChars(chars, rot);
-  unrotateColumns(columns, rot);
-  if (control.html) {
-    generateUnderlinesAndLinks(columns);
-  }
   return columns;
 }
 
@@ -1618,6 +1753,7 @@
   TextColumn *col;
   TextParagraph *par;
   TextLine *line;
+  GList *overlappingChars;
   GList *columns;
   GBool primaryLR;
   int ph, colIdx, parIdx, lineIdx, rot, y, i;
@@ -1628,6 +1764,11 @@
 #if 0 //~debug
   dumpUnderlines();
 #endif
+  if (control.overlapHandling != textOutIgnoreOverlaps) {
+    overlappingChars = separateOverlappingText(chars);
+  } else {
+    overlappingChars = NULL;
+  }
   rot = rotateChars(chars);
   primaryLR = checkPrimaryLR(chars);
   tree = splitChars(chars);
@@ -1700,6 +1841,29 @@
   gfree(outLen);
 
   deleteGList(columns, TextColumn);
+
+  if (overlappingChars) {
+    if (overlappingChars->getLength() > 0) {
+      TextColumn *col = buildOverlappingTextColumn(overlappingChars);
+      (*outputFunc)(outputStream, eol, eolLen);
+      for (parIdx = 0; parIdx < col->paragraphs->getLength(); ++parIdx) {
+	par = (TextParagraph *)col->paragraphs->get(parIdx);
+	for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) {
+	  line = (TextLine *)par->lines->get(lineIdx);
+	  GString *s = new GString();
+	  encodeFragment(line->text, line->len, uMap, primaryLR, s);
+	  s->append(eol, eolLen);
+	  (*outputFunc)(outputStream, s->getCString(), s->getLength());
+	  delete s;
+	}
+	if (parIdx < col->paragraphs->getLength() - 1) {
+	  (*outputFunc)(outputStream, eol, eolLen);
+	}
+      }
+      delete col;
+    }
+    deleteGList(overlappingChars, TextChar);
+  }
 }
 
 void TextPage::writeSimpleLayout(void *outputStream,
@@ -1762,6 +1926,46 @@
   deleteGList(superLines, TextSuperLine);
 }
 
+void TextPage::writeSimple2Layout(void *outputStream,
+				  TextOutputFunc outputFunc,
+				  UnicodeMap *uMap,
+				  char *space, int spaceLen,
+				  char *eol, int eolLen) {
+  GList *columns;
+  TextColumn *col;
+  TextParagraph *par;
+  TextLine *line;
+  GString *out;
+  GBool primaryLR;
+  int colIdx, parIdx, lineIdx;
+
+  primaryLR = checkPrimaryLR(chars);
+  rotateCharsToZero(chars);
+#if 0 //~debug
+  dumpChars(chars);
+#endif
+  columns = buildSimple2Columns(chars);
+  unrotateCharsFromZero(chars);
+  unrotateColumnsFromZero(columns);
+
+  for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) {
+    col = (TextColumn *)columns->get(colIdx);
+    for (parIdx = 0; parIdx < col->paragraphs->getLength(); ++parIdx) {
+      par = (TextParagraph *)col->paragraphs->get(parIdx);
+      for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) {
+	line = (TextLine *)par->lines->get(lineIdx);
+	out = new GString();
+	encodeFragment(line->text, line->len, uMap, primaryLR, out);
+	(*outputFunc)(outputStream, out->getCString(), out->getLength());
+	delete out;
+	(*outputFunc)(outputStream, eol, eolLen);
+      }
+    }
+  }
+
+  deleteGList(columns, TextColumn);
+}
+
 void TextPage::writeLinePrinter(void *outputStream,
 				TextOutputFunc outputFunc,
 				UnicodeMap *uMap,
@@ -1778,7 +1982,8 @@
 
   rot = rotateChars(chars);
   chars->sort(&TextChar::cmpX);
-  removeDuplicates(chars, 0);
+  // don't call removeDuplicates here, because it expects to be
+  // working on a secondary list that doesn't own the TextChar objects
   chars->sort(&TextChar::cmpY);
 
   // get character pitch
@@ -1796,7 +2001,7 @@
 	    ch->yMin + ascentAdjustFactor * (ch->yMax - ch->yMin) <
 	      ch2->yMax - descentAdjustFactor * (ch2->yMax - ch2->yMin)) {
 	  delta = fabs(ch2->xMin - ch->xMin);
-	  if (delta > 0 && delta < pitch) {
+	  if (delta > 0.01 && delta < pitch) {
 	    pitch = delta;
 	  }
 	}
@@ -2135,6 +2340,53 @@
   return rot;
 }
 
+// Rotate all chars to zero rotation.  This leaves the TextChar.rot
+// fields unchanged.
+void TextPage::rotateCharsToZero(GList *charsA) {
+  TextChar *ch;
+  double xMin, yMin, xMax, yMax;
+  int i;
+
+  for (i = 0; i < charsA->getLength(); ++i) {
+    ch = (TextChar *)charsA->get(i);
+    switch (ch->rot) {
+    case 0:
+    default:
+      break;
+    case 1:
+      xMin = ch->yMin;
+      xMax = ch->yMax;
+      yMin = pageWidth - ch->xMax;
+      yMax = pageWidth - ch->xMin;
+      ch->xMin = xMin;
+      ch->xMax = xMax;
+      ch->yMin = yMin;
+      ch->yMax = yMax;
+      break;
+    case 2:
+      xMin = pageWidth - ch->xMax;
+      xMax = pageWidth - ch->xMin;
+      yMin = pageHeight - ch->yMax;
+      yMax = pageHeight - ch->yMin;
+      ch->xMin = xMin;
+      ch->xMax = xMax;
+      ch->yMin = yMin;
+      ch->yMax = yMax;
+      break;
+    case 3:
+      xMin = pageHeight - ch->yMax;
+      xMax = pageHeight - ch->yMin;
+      yMin = ch->xMin;
+      yMax = ch->xMax;
+      ch->xMin = xMin;
+      ch->xMax = xMax;
+      ch->yMin = yMin;
+      ch->yMax = yMax;
+      break;
+    }
+  }
+}
+
 // Rotate the TextUnderlines and TextLinks to match the transform
 // performed by rotateChars().
 void TextPage::rotateUnderlinesAndLinks(int rot) {
@@ -2286,6 +2538,224 @@
   }
 }
 
+// Undo the coordinate transform performed by rotateCharsToZero().
+void TextPage::unrotateCharsFromZero(GList *charsA) {
+  TextChar *ch;
+  double xMin, yMin, xMax, yMax;
+  int i;
+
+  for (i = 0; i < charsA->getLength(); ++i) {
+    ch = (TextChar *)charsA->get(i);
+    switch (ch->rot) {
+    case 0:
+    default:
+      break;
+    case 1:
+      xMin = pageWidth - ch->yMax;
+      xMax = pageWidth - ch->yMin;
+      yMin = ch->xMin;
+      yMax = ch->xMax;
+      ch->xMin = xMin;
+      ch->xMax = xMax;
+      ch->yMin = yMin;
+      ch->yMax = yMax;
+      break;
+    case 2:
+      xMin = pageWidth - ch->xMax;
+      xMax = pageWidth - ch->xMin;
+      yMin = pageHeight - ch->yMax;
+      yMax = pageHeight - ch->yMin;
+      ch->xMin = xMin;
+      ch->xMax = xMax;
+      ch->yMin = yMin;
+      ch->yMax = yMax;
+      break;
+    case 3:
+      xMin = ch->yMin;
+      xMax = ch->yMax;
+      yMin = pageHeight - ch->xMax;
+      yMax = pageHeight - ch->xMin;
+      ch->xMin = xMin;
+      ch->xMax = xMax;
+      ch->yMin = yMin;
+      ch->yMax = yMax;
+      break;
+    }
+  }
+}
+
+// Undo the coordinate transform performed by rotateCharsToZero().
+void TextPage::unrotateColumnsFromZero(GList *columns) {
+  TextColumn *col;
+  TextParagraph *par;
+  TextLine *line;
+  TextWord *word;
+  double xMin, yMin, xMax, yMax;
+  int colIdx, parIdx, lineIdx, wordIdx, i;
+
+  for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) {
+    col = (TextColumn *)columns->get(colIdx);
+    switch (col->getRotation()) {
+    case 0:
+    default:
+      break;
+    case 1:
+      xMin = pageWidth - col->yMax;
+      xMax = pageWidth - col->yMin;
+      yMin = col->xMin;
+      yMax = col->xMax;
+      col->xMin = xMin;
+      col->xMax = xMax;
+      col->yMin = yMin;
+      col->yMax = yMax;
+      for (parIdx = 0;
+	   parIdx < col->paragraphs->getLength();
+	   ++parIdx) {
+	par = (TextParagraph *)col->paragraphs->get(parIdx);
+	xMin = pageWidth - par->yMax;
+	xMax = pageWidth - par->yMin;
+	yMin = par->xMin;
+	yMax = par->xMax;
+	par->xMin = xMin;
+	par->xMax = xMax;
+	par->yMin = yMin;
+	par->yMax = yMax;
+	for (lineIdx = 0;
+	     lineIdx < par->lines->getLength();
+	     ++lineIdx) {
+	  line = (TextLine *)par->lines->get(lineIdx);
+	  xMin = pageWidth - line->yMax;
+	  xMax = pageWidth - line->yMin;
+	  yMin = line->xMin;
+	  yMax = line->xMax;
+	  line->xMin = xMin;
+	  line->xMax = xMax;
+	  line->yMin = yMin;
+	  line->yMax = yMax;
+	  for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) {
+	    word = (TextWord *)line->words->get(wordIdx);
+	    xMin = pageWidth - word->yMax;
+	    xMax = pageWidth - word->yMin;
+	    yMin = word->xMin;
+	    yMax = word->xMax;
+	    word->xMin = xMin;
+	    word->xMax = xMax;
+	    word->yMin = yMin;
+	    word->yMax = yMax;
+	  }
+	}
+      }
+      break;
+    case 2:
+      xMin = pageWidth - col->xMax;
+      xMax = pageWidth - col->xMin;
+      yMin = pageHeight - col->yMax;
+      yMax = pageHeight - col->yMin;
+      col->xMin = xMin;
+      col->xMax = xMax;
+      col->yMin = yMin;
+      col->yMax = yMax;
+      for (parIdx = 0;
+	   parIdx < col->paragraphs->getLength();
+	   ++parIdx) {
+	par = (TextParagraph *)col->paragraphs->get(parIdx);
+	xMin = pageWidth - par->xMax;
+	xMax = pageWidth - par->xMin;
+	yMin = pageHeight - par->yMax;
+	yMax = pageHeight - par->yMin;
+	par->xMin = xMin;
+	par->xMax = xMax;
+	par->yMin = yMin;
+	par->yMax = yMax;
+	for (lineIdx = 0;
+	     lineIdx < par->lines->getLength();
+	     ++lineIdx) {
+	  line = (TextLine *)par->lines->get(lineIdx);
+	  xMin = pageWidth - line->xMax;
+	  xMax = pageWidth - line->xMin;
+	  yMin = pageHeight - line->yMax;
+	  yMax = pageHeight - line->yMin;
+	  line->xMin = xMin;
+	  line->xMax = xMax;
+	  line->yMin = yMin;
+	  line->yMax = yMax;
+	  for (i = 0; i <= line->len; ++i) {
+	    line->edge[i] = pageWidth - line->edge[i];
+	  }
+	  for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) {
+	    word = (TextWord *)line->words->get(wordIdx);
+	    xMin = pageWidth - word->xMax;
+	    xMax = pageWidth - word->xMin;
+	    yMin = pageHeight - word->yMax;
+	    yMax = pageHeight - word->yMin;
+	    word->xMin = xMin;
+	    word->xMax = xMax;
+	    word->yMin = yMin;
+	    word->yMax = yMax;
+	    for (i = 0; i <= word->len; ++i) {
+	      word->edge[i] = pageWidth - word->edge[i];
+	    }
+	  }
+	}
+      }
+      break;
+    case 3:
+      xMin = col->yMin;
+      xMax = col->yMax;
+      yMin = pageHeight - col->xMax;
+      yMax = pageHeight - col->xMin;
+      col->xMin = xMin;
+      col->xMax = xMax;
+      col->yMin = yMin;
+      col->yMax = yMax;
+      for (parIdx = 0;
+	   parIdx < col->paragraphs->getLength();
+	   ++parIdx) {
+	par = (TextParagraph *)col->paragraphs->get(parIdx);
+	xMin = par->yMin;
+	xMax = par->yMax;
+	yMin = pageHeight - par->xMax;
+	yMax = pageHeight - par->xMin;
+	par->xMin = xMin;
+	par->xMax = xMax;
+	par->yMin = yMin;
+	par->yMax = yMax;
+	for (lineIdx = 0;
+	     lineIdx < par->lines->getLength();
+	     ++lineIdx) {
+	  line = (TextLine *)par->lines->get(lineIdx);
+	  xMin = line->yMin;
+	  xMax = line->yMax;
+	  yMin = pageHeight - line->xMax;
+	  yMax = pageHeight - line->xMin;
+	  line->xMin = xMin;
+	  line->xMax = xMax;
+	  line->yMin = yMin;
+	  line->yMax = yMax;
+	  for (i = 0; i <= line->len; ++i) {
+	    line->edge[i] = pageHeight - line->edge[i];
+	  }
+	  for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) {
+	    word = (TextWord *)line->words->get(wordIdx);
+	    xMin = word->yMin;
+	    xMax = word->yMax;
+	    yMin = pageHeight - word->xMax;
+	    yMax = pageHeight - word->xMin;
+	    word->xMin = xMin;
+	    word->xMax = xMax;
+	    word->yMin = yMin;
+	    word->yMax = yMax;
+	    for (i = 0; i <= word->len; ++i) {
+	      word->edge[i] = pageHeight - word->edge[i];
+	    }
+	  }
+	}
+      }
+      break;
+    }
+  }
+}
+
 // Undo the coordinate transform performed by rotateChars().
 void TextPage::unrotateColumns(GList *columns, int rot) {
   TextColumn *col;
@@ -2601,7 +3071,8 @@
   int i, j;
 
   if (rot & 1) {
-    for (i = 0; i < charsA->getLength(); ++i) {
+    i = 0;
+    while (i < charsA->getLength()) {
       ch = (TextChar *)charsA->get(i);
       xDelta = dupMaxSecDelta * ch->fontSize;
       yDelta = dupMaxPriDelta * ch->fontSize;
@@ -2615,6 +3086,11 @@
 	    fabs(ch2->xMin - ch->xMin) < xDelta &&
 	    fabs(ch2->xMax - ch->xMax) < xDelta &&
 	    fabs(ch2->yMax - ch->yMax) < yDelta) {
+	  if (ch->invisible && !ch2->invisible) {
+	    charsA->del(i);
+	    --i;
+	    break;
+	  }
 	  if (ch2->spaceAfter) {
 	    ch->spaceAfter = (char)gTrue;
 	  }
@@ -2623,9 +3099,11 @@
 	  ++j;
 	}
       }
+      ++i;
     }
   } else {
-    for (i = 0; i < charsA->getLength(); ++i) {
+    i = 0;
+    while (i < charsA->getLength()) {
       ch = (TextChar *)charsA->get(i);
       xDelta = dupMaxPriDelta * ch->fontSize;
       yDelta = dupMaxSecDelta * ch->fontSize;
@@ -2639,15 +3117,257 @@
 	    fabs(ch2->xMax - ch->xMax) < xDelta &&
 	    fabs(ch2->yMin - ch->yMin) < yDelta &&
 	    fabs(ch2->yMax - ch->yMax) < yDelta) {
+	  if (ch->invisible && !ch2->invisible) {
+	    charsA->del(i);
+	    --i;
+	    break;
+	  }
+	  if (ch2->spaceAfter) {
+	    ch->spaceAfter = (char)gTrue;
+	  }
 	  charsA->del(j);
 	} else {
 	  ++j;
 	}
       }
+      ++i;
     }
   }
 }
 
+struct TextCharNode {
+  TextCharNode(TextChar *chA, TextCharNode *nextA): ch(chA), next(nextA) {}
+  TextChar *ch;
+  TextCharNode *next;
+};
+
+// Separate out any overlapping text.  If handling is
+// textOutAppendOverlaps, return a list of the overlapping chars; else
+// delete them and return NULL.
+GList *TextPage::separateOverlappingText(GList *charsA) {
+  // bin-sort the TextChars
+  TextCharNode *grid[overlapGridHeight][overlapGridWidth];
+  for (int y = 0; y < overlapGridHeight; ++y) {
+    for (int x = 0; x < overlapGridWidth; ++x) {
+      grid[y][x] = NULL;
+    }
+  }
+  for (int i = 0; i < charsA->getLength(); ++i) {
+    TextChar *ch = (TextChar *)charsA->get(i);
+    int y0 = (int)floor(overlapGridHeight * ch->yMin / pageHeight);
+    int y1 = (int)ceil(overlapGridHeight * ch->yMax / pageHeight);
+    int x0 = (int)floor(overlapGridWidth * ch->xMin / pageWidth);
+    int x1 = (int)ceil(overlapGridWidth * ch->yMin / pageWidth);
+    if (y0 < 0) {
+      y0 = 0;
+    }
+    if (y1 >= overlapGridHeight) {
+      y1 = overlapGridHeight - 1;
+    }
+    if (x0 < 0) {
+      x0 = 0;
+    }
+    if (x1 >= overlapGridWidth) {
+      x1 = overlapGridWidth - 1;
+    }
+    for (int y = y0; y <= y1; ++y) {
+      for (int x = x0; x <= x1; ++x) {
+	grid[y][x] = new TextCharNode(ch, grid[y][x]);
+      }
+    }
+  }
+
+  // look for overlaps in each cell
+  GBool foundOverlaps = gFalse;
+  for (int y = 0; y < overlapGridHeight; ++y) {
+    for (int x = 0; x < overlapGridWidth; ++x) {
+      for (TextCharNode *p0 = grid[y][x]; p0; p0 = p0->next) {
+	for (TextCharNode *p1 = p0->next; p1; p1 = p1->next) {
+	  if (p0->ch->colorR != p1->ch->colorR ||
+	      p0->ch->colorG != p1->ch->colorG ||
+	      p0->ch->colorB != p1->ch->colorB) {
+	    double ovx = (dmin(p0->ch->xMax, p1->ch->xMax)
+			  - dmax(p0->ch->xMin, p1->ch->xMin))
+	                 / dmin(p0->ch->xMax - p0->ch->xMin,
+			       p1->ch->xMax - p1->ch->xMin);
+	    double ovy = (dmin(p0->ch->yMax, p1->ch->yMax)
+			  - dmax(p0->ch->yMin, p1->ch->yMin))
+	                 / dmin(p0->ch->yMax - p0->ch->yMin,
+			       p1->ch->yMax - p1->ch->yMin);
+	    if (ovx > minCharOverlap && ovy > minCharOverlap) {
+	      // assume the lighter colored text is extraneous
+	      if (p0->ch->colorR + p0->ch->colorG + p0->ch->colorB
+		  < p1->ch->colorR + p1->ch->colorG + p1->ch->colorB) {
+		p1->ch->overlap = gTrue;
+	      } else {
+		p0->ch->overlap = gTrue;
+	      }
+	      foundOverlaps = gTrue;
+	    }
+	  }
+	}
+      }
+    }
+  }
+
+  // find overlapped strings
+  GList *overlapChars = NULL;
+  if (control.overlapHandling == textOutAppendOverlaps) {
+    overlapChars = new GList();
+  }
+  if (foundOverlaps) {
+    charsA->sort(&TextChar::cmpCharPos);
+    int i = 0;
+    while (i < charsA->getLength()) {
+      TextChar *ch0 = (TextChar *)charsA->get(i);
+      if (ch0->overlap) {
+	int j0, j1;
+	for (j0 = i - 1; j0 >= 0; --j0) {
+	  TextChar *ch1 = (TextChar *)charsA->get(j0);
+	  if (ch1->colorR != ch0->colorR ||
+	      ch1->colorG != ch0->colorG ||
+	      ch1->colorB != ch0->colorB ||
+	      ch1->rot != ch0->rot) {
+	    break;
+	  }
+	}
+	++j0;
+	for (j1 = i + 1; j1 < charsA->getLength(); ++j1) {
+	  TextChar *ch1 = (TextChar *)charsA->get(j1);
+	  if (ch1->colorR != ch0->colorR ||
+	      ch1->colorG != ch0->colorG ||
+	      ch1->colorB != ch0->colorB ||
+	      ch1->rot != ch0->rot) {
+	    break;
+	  }
+	}
+	--j1;
+	for (int j = j0; j <= j1; ++j) {
+	  if (overlapChars) {
+	    overlapChars->append(charsA->get(j0));
+	  } else {
+	    delete (TextChar *)charsA->get(j0);
+	  }
+	  charsA->del(j0);
+	}
+	i = j0;
+      } else {
+	++i;
+      }
+    }
+  }
+
+  // free memory
+  for (int y = 0; y < overlapGridHeight; ++y) {
+    for (int x = 0; x < overlapGridWidth; ++x) {
+      TextCharNode *p0 = grid[y][x];
+      while (p0) {
+	TextCharNode *p1 = p0->next;
+	delete p0;
+	p0 = p1;
+      }
+    }
+  }
+
+  return overlapChars;
+}
+
+// Construct a TextColumn from the list of separated overlapping
+// chars.
+TextColumn *TextPage::buildOverlappingTextColumn(GList *overlappingChars) {
+  GList *pars = new GList();
+  GList *lines = new GList();
+  GList *words = new GList();
+  int wordStart = 0;
+  double lineXMin = 0, lineYMin = 0, lineXMax = 0, lineYMax = 0;
+  double colXMin = 0, colYMin = 0, colXMax = 0, colYMax = 0;
+  for (int i = 0; i < overlappingChars->getLength(); ++i) {
+    TextChar *ch = (TextChar *)overlappingChars->get(i);
+    TextChar *chNext = NULL;
+    if (i + 1 < overlappingChars->getLength()) {
+      chNext = (TextChar *)overlappingChars->get(i + 1);
+    }
+    double sp = 0;
+    double dy = 0;
+    if (chNext) {
+      switch (ch->rot) {
+      case 0:
+      default:
+	sp = chNext->xMin - ch->xMax;
+	dy = chNext->yMin - ch->yMin;
+	break;
+      case 1:
+	sp = chNext->yMin - ch->yMax;
+	dy = chNext->xMax - ch->xMax;
+	break;
+      case 2:
+	sp = ch->xMin - chNext->xMax;
+	dy = ch->yMax - chNext->yMax;
+	break;
+      case 3:
+	sp = ch->yMin - chNext->yMax;
+	dy = ch->xMin - chNext->xMin;
+	break;
+      }
+    }
+    // the +1 here allows for a space character after ch
+    GBool parBreak = !chNext ||
+                     chNext->rot != ch->rot ||
+                     chNext->charPos > ch->charPos + ch->charLen + 1;
+    GBool lineBreak = parBreak ||
+                      sp < -rawModeCharOverlap * ch->fontSize ||
+                      fabs(dy) > rawModeLineDelta * ch->fontSize;
+    GBool wordBreak = lineBreak ||
+                      ch->spaceAfter ||
+                      sp > rawModeWordSpacing * ch->fontSize;
+    if (!wordBreak) {
+      continue;
+    }
+    TextWord *word = new TextWord(overlappingChars, wordStart,
+				  i - wordStart + 1, ch->rot, ch->rotated,
+				  getCharDirection(ch), !lineBreak);
+    words->append(word);
+    if (words->getLength() == 0) {
+      lineXMin = word->xMin;
+      lineYMin = word->yMin;
+      lineXMax = word->xMax;
+      lineYMax = word->yMax;
+    } else {
+      lineXMin = dmin(lineXMin, word->xMin);
+      lineYMin = dmin(lineYMin, word->yMin);
+      lineXMax = dmax(lineXMax, word->xMax);
+      lineYMax = dmax(lineYMax, word->yMax);
+    }
+    wordStart = i + 1;
+    if (!lineBreak) {
+      continue;
+    }
+    lines->append(new TextLine(words, lineXMin, lineYMin, lineXMax, lineYMax,
+			       ((TextWord *)words->get(0))->fontSize));
+    words = new GList();
+    if (!parBreak) {
+      continue;
+    }
+    TextParagraph *par = new TextParagraph(lines, gFalse);
+    pars->append(par);
+    if (pars->getLength() == 0) {
+      colXMin = par->xMin;
+      colYMin = par->yMin;
+      colXMax = par->xMax;
+      colYMax = par->yMax;
+    } else {
+      colXMin = dmin(colXMin, par->xMin);
+      colYMin = dmin(colYMin, par->yMin);
+      colXMax = dmax(colXMax, par->xMax);
+      colYMax = dmax(colYMax, par->yMax);
+    }
+    lines = new GList();
+  }
+  delete words;
+  delete lines;
+  return new TextColumn(pars, colXMin, colYMin, colXMax, colYMax);
+}
+
 // Split the characters into trees of TextBlocks, one tree for each
 // rotation.  Merge into a single tree (with the primary rotation).
 TextBlock *TextPage::splitChars(GList *charsA) {
@@ -2860,12 +3580,15 @@
   //    largeCharSize could otherwise have slightly different values
   //    here and where it's used below to do the large char partition
   //    (because it gets truncated from 80 to 64 bits when spilled)
-  largeCharSize = (int)(largeCharThreshold * avgFontSize * 256) / 256.0;
   nLargeChars = 0;
-  for (i = 0; i < charsA->getLength(); ++i) {
-    ch = (TextChar *)charsA->get(i);
-    if (ch->fontSize > largeCharSize) {
-      ++nLargeChars;
+  largeCharSize = 0;
+  if (control.separateLargeChars) {
+    largeCharSize = (int)(largeCharThreshold * avgFontSize * 256) / 256.0;
+    for (i = 0; i < charsA->getLength(); ++i) {
+      ch = (TextChar *)charsA->get(i);
+      if (ch->fontSize > largeCharSize) {
+	++nLargeChars;
+      }
     }
   }
 
@@ -3092,7 +3815,7 @@
   if (xMin * invSplitPrecision < 0.5 * INT_MIN ||
       xMax * invSplitPrecision > 0.5 * INT_MAX ||
       yMin * invSplitPrecision < 0.5 * INT_MIN ||
-      xMax * invSplitPrecision > 0.5 * INT_MAX) {
+      yMax * invSplitPrecision > 0.5 * INT_MAX) {
     return;
   }
   // add some slack to the array bounds to avoid floating point
@@ -3543,7 +4266,7 @@
   int i;
 
   lines = new GList();
-  buildLines(blk, lines);
+  buildLines(blk, lines, gFalse);
 
   spaceThresh = paragraphSpacingThreshold * getAverageLineSpacing(lines);
 
@@ -3712,12 +4435,13 @@
   return sp;
 }
 
-void TextPage::buildLines(TextBlock *blk, GList *lines) {
+void TextPage::buildLines(TextBlock *blk, GList *lines,
+			  GBool splitSuperLines) {
   TextLine *line;
   int i;
 
-  switch (blk->tag) {
-  case blkTagLine:
+  if (blk->tag == blkTagLine ||
+      (blk->tag == blkTagSuperLine && !splitSuperLines)) {
     line = buildLine(blk);
     if (blk->rot == 1 || blk->rot == 2) {
       lines->insert(0, line);
@@ -3724,32 +4448,219 @@
     } else {
       lines->append(line);
     }
-    break;
-  case blkTagSuperLine:
-  case blkTagColumn:
-  case blkTagMulticolumn: // multicolumn should never happen here
+  } else {
     for (i = 0; i < blk->children->getLength(); ++i) {
-      buildLines((TextBlock *)blk->children->get(i), lines);
+      buildLines((TextBlock *)blk->children->get(i), lines, splitSuperLines);
     }
-    break;
   }
 }
 
+GList *TextPage::buildSimple2Columns(GList *charsA) {
+  GList *columns, *paragraphs, *lines;
+  TextParagraph *paragraph;
+  int rot;
+
+  charsA->sort(&TextChar::cmpX);
+  columns = new GList();
+  for (rot = 0; rot < 4; ++rot) {
+    lines = buildSimple2Lines(charsA, rot);
+    if (lines->getLength() == 0) {
+      delete lines;
+      continue;
+    }
+    paragraph = new TextParagraph(lines, gFalse);
+    paragraphs = new GList();
+    paragraphs->append(paragraph);
+    columns->append(new TextColumn(paragraphs,
+				   paragraph->xMin, paragraph->yMin,
+				   paragraph->xMax, paragraph->yMax));
+  }
+  return columns;
+}
+
+GList *TextPage::buildSimple2Lines(GList *charsA, int rot) {
+  GList *openCharLines, *lines;
+  TextCharLine *firstCharLine, *lastCharLine, *charLine, *p;
+  TextChar *ch;
+  TextLine *line;
+  double bestOverlap, overlap, xMin, yMin, xMax, yMax;
+  int bestLine, i, j, k, m;
+
+  firstCharLine = lastCharLine = NULL;
+  openCharLines = new GList();
+  for (i = 0; i < charsA->getLength(); ++i) {
+    ch = (TextChar *)charsA->get(i);
+    if (ch->rot != rot) {
+      continue;
+    }
+
+    // find the first open line with line.yMax > ch.yMin
+    j = -1;
+    k = openCharLines->getLength();
+    while (j < k - 1) {
+      // invariants: openLines[j].yMax <= ch.yMin (or j = -1)
+      //             openLines[k].yMax >  ch.yMin (or k = nOpenLines)
+      //             j < k - 1
+      m = j + (k - j) / 2;
+      charLine = (TextCharLine *)openCharLines->get(m);
+      if (charLine->yMax <= ch->yMin) {
+	j = m;
+      } else {
+	k = m;
+      }
+    }
+
+    // check overlap for all overlapping lines
+    // i.e., all lines with line.yMin < ch.yMax and line.yMax > ch.yMin
+    bestLine = -1;
+    bestOverlap = 0;
+    for (; k < openCharLines->getLength(); ++k) {
+      charLine = (TextCharLine *)openCharLines->get(k);
+      if (charLine->yMin >= ch->yMax) {
+	break;
+      }
+      overlap = ((ch->yMax < charLine->yMax ? ch->yMax : charLine->yMax)
+		 - (ch->yMin > charLine->yMin ? ch->yMin : charLine->yMin))
+	        / (ch->yMax - ch->yMin);
+      if (overlap > bestOverlap) {
+	bestLine = k;
+	bestOverlap = overlap;
+      }
+    }
+
+    // found an overlapping line
+    if (bestLine >= 0 && bestOverlap > simple2MinOverlap) {
+      k = bestLine;
+      charLine = (TextCharLine *)openCharLines->get(k);
+
+    // else insert a new line immediately before line k
+    } else {
+      charLine = new TextCharLine(ch->rot);
+      if (k < openCharLines->getLength()) {
+	p = (TextCharLine *)openCharLines->get(k);
+	if (p->prev) {
+	  p->prev->next = charLine;
+	  charLine->prev = p->prev;
+	} else {
+	  firstCharLine = charLine;
+	}
+	p->prev = charLine;
+	charLine->next = p;
+      } else {
+	if (lastCharLine) {
+	  lastCharLine->next = charLine;
+	  charLine->prev = lastCharLine;
+	} else {
+	  firstCharLine = charLine;
+	}
+	lastCharLine = charLine;
+      }
+      openCharLines->insert(k, charLine);
+    }
+
+    // add the char to the line
+    charLine->add(ch);
+    charLine->yMin = ch->yMin;
+    charLine->yMax = ch->yMax;
+
+    // update open lines before k
+    j = k - 1;
+    while (j >= 0) {
+      charLine = (TextCharLine *)openCharLines->get(j);
+      if (charLine->yMax <= ch->yMin) {
+	break;
+      }
+      charLine->yMax = ch->yMin;
+      if (charLine->yMin < charLine->yMax) {
+	break;
+      }
+      openCharLines->del(j);
+      --j;
+    }
+
+    // update open lines after k
+    j = k + 1;
+    while (j < openCharLines->getLength()) {
+      charLine = (TextCharLine *)openCharLines->get(j);
+      if (charLine->yMin >= ch->yMax) {
+	break;
+      }
+      charLine->yMin = ch->yMax;
+      if (charLine->yMin < charLine->yMax) {
+	break;
+      }
+      openCharLines->del(j);
+    }
+  }
+
+  // build TextLine objects
+  lines = new GList();
+  for (charLine = firstCharLine; charLine; charLine = p) {
+    xMin = yMin = xMax = yMax = 0;
+    for (j = 0; j < charLine->chars->getLength(); ++j) {
+      ch = (TextChar *)charLine->chars->get(j);
+      if (j == 0) {
+	xMin = ch->xMin;
+	yMin = ch->yMin;
+	xMax = ch->xMax;
+	yMax = ch->yMax;
+      } else {
+	if (ch->xMin < xMin) {
+	  xMin = ch->xMin;
+	}
+	if (ch->yMin < yMin) {
+	  yMin = ch->yMin;
+	}
+	if (ch->xMax < xMax) {
+	  xMax = ch->xMax;
+	}
+	if (ch->yMax < yMax) {
+	  yMax = ch->yMax;
+	}
+      }
+    }
+    // the chars have been rotated to 0, without changing the
+    // TextChar.rot values, so we need to tell buildLine to use rot=0,
+    // and then set the word and line rotation correctly afterward
+    line = buildLine(charLine->chars, 0, xMin, yMin, xMax, yMax);
+    line->rot = charLine->rot;
+    for (i = 0; i < line->words->getLength(); ++i) {
+      ((TextWord *)line->words->get(i))->rot = (char)charLine->rot;
+    }
+    lines->append(line);
+    p = charLine->next;
+    delete charLine;
+  }
+
+  delete openCharLines;
+
+  return lines;
+}
+
 TextLine *TextPage::buildLine(TextBlock *blk) {
   GList *charsA;
+
+  charsA = new GList();
+  getLineChars(blk, charsA);
+  TextLine *line = buildLine(charsA, blk->rot,
+			     blk->xMin, blk->yMin, blk->xMax, blk->yMax);
+  delete charsA;
+  return line;
+}
+
+TextLine *TextPage::buildLine(GList *charsA, int rot,
+			      double xMin, double yMin,
+			      double xMax, double yMax) {
   GList *words;
   TextChar *ch, *ch2;
   TextWord *word;
   double wordSp, lineFontSize, sp;
   int dir, dir2;
-  GBool spaceAfter, spaceBefore;
+  GBool rotated, spaceAfter, spaceBefore;
   int i, j;
 
-  charsA = new GList();
-  getLineChars(blk, charsA);
+  wordSp = computeWordSpacingThreshold(charsA, rot);
 
-  wordSp = computeWordSpacingThreshold(charsA, blk->rot);
-
   words = new GList();
   lineFontSize = 0;
   spaceBefore = gFalse;
@@ -3758,10 +4669,11 @@
     sp = wordSp - 1;
     spaceAfter = gFalse;
     dir = getCharDirection((TextChar *)charsA->get(i));
+    rotated = ((TextChar *)charsA->get(i))->rotated;
     for (j = i+1; j < charsA->getLength(); ++j) {
       ch = (TextChar *)charsA->get(j-1);
       ch2 = (TextChar *)charsA->get(j);
-      sp = (blk->rot & 1) ? (ch2->yMin - ch->yMax) : (ch2->xMin - ch->xMax);
+      sp = (rot & 1) ? (ch2->yMin - ch->yMax) : (ch2->xMin - ch->xMax);
       if (sp > wordSp) {
 	spaceAfter = gTrue;
 	break;
@@ -3775,6 +4687,7 @@
       dir2 = getCharDirection(ch2);
       if (ch->font != ch2->font ||
 	  fabs(ch->fontSize - ch2->fontSize) > 0.01 ||
+	  (control.splitRotatedWords && ch2->rotated != rotated) ||
 	  (dir && dir2 && dir2 != dir) ||
 	  (control.mode == textOutRawOrder &&
 	   ch2->charPos != ch->charPos + ch->charLen)) {
@@ -3785,11 +4698,11 @@
       }
       sp = wordSp - 1;
     }
-    word = new TextWord(charsA, i, j - i, blk->rot, dir,
-			(blk->rot >= 2) ? spaceBefore : spaceAfter);
+    word = new TextWord(charsA, i, j - i, rot, rotated, dir,
+			(rot >= 2) ? spaceBefore : spaceAfter);
     spaceBefore = spaceAfter;
     i = j;
-    if (blk->rot >= 2) {
+    if (rot >= 2) {
       words->insert(0, word);
     } else {
       words->append(word);
@@ -3799,10 +4712,7 @@
     }
   }
 
-  delete charsA;
-
-  return new TextLine(words, blk->xMin, blk->yMin, blk->xMax, blk->yMax,
-		      lineFontSize);
+  return new TextLine(words, xMin, yMin, xMax, yMax, lineFontSize);
 }
 
 void TextPage::getLineChars(TextBlock *blk, GList *charsA) {
@@ -3822,10 +4732,19 @@
 // spaces.
 double TextPage::computeWordSpacingThreshold(GList *charsA, int rot) {
   TextChar *ch, *ch2;
+  double uniformSp, wordSp;
   double avgFontSize;
   double minAdjGap, maxAdjGap, minSpGap, maxSpGap, minGap, maxGap, gap, gap2;
   int i;
 
+  if (control.mode == textOutTableLayout) {
+    uniformSp = tableModeUniformSpacing;
+    wordSp = tableModeWordSpacing;
+  } else {
+    uniformSp = uniformSpacing;
+    wordSp = wordSpacing;
+  }
+
   avgFontSize = 0;
   minGap = maxGap = 0;
   minAdjGap = minSpGap = 1;
@@ -3876,13 +4795,13 @@
   // (3) otherwise assume it's a single word (technically it could be
   //     either "ABC" or "A B C", but it's essentially impossible to
   //     tell)
-  if (maxGap - minGap < uniformSpacing * avgFontSize) {
+  if (maxGap - minGap < uniformSp * avgFontSize) {
     if (minSpGap <= maxSpGap) {
       if (minAdjGap <= maxAdjGap &&
 	  minSpGap - maxAdjGap > 0.01) {
 	return 0.5 * (maxAdjGap + minSpGap);
       } else if (minAdjGap > maxAdjGap &&
-		 maxSpGap - minSpGap < uniformSpacing * avgFontSize) {
+		 maxSpGap - minSpGap < uniformSp * avgFontSize) {
 	return minSpGap - 1;
       }
     }
@@ -3890,7 +4809,7 @@
 
   // if there is some variation in spacing, but it's small, assume
   // there are some inter-word spaces
-  } else if (maxGap - minGap < wordSpacing * avgFontSize) {
+  } else if (maxGap - minGap < wordSp * avgFontSize) {
     return 0.5 * (minGap + maxGap);
 
   // if there is a large variation in spacing, use the SpGap/AdjGap
@@ -3901,12 +4820,12 @@
   } else {
     if (minAdjGap <= maxAdjGap &&
 	minSpGap <= maxSpGap &&
-	minSpGap - maxAdjGap > uniformSpacing * avgFontSize) {
-      gap = wordSpacing * avgFontSize;
+	minSpGap - maxAdjGap > uniformSp * avgFontSize) {
+      gap = wordSp * avgFontSize;
       gap2 = 0.5 * (minSpGap - minGap);
       return minGap + (gap < gap2 ? gap : gap2);
     } else {
-      return minGap + wordSpacing * avgFontSize;
+      return minGap + wordSp * avgFontSize;
     }
   }
 }
@@ -4069,16 +4988,10 @@
   GList *lines;
   int i;
 
-  if (blk->tag == blkTagLine) {
+  if (blk->tag == blkTagLine || blk->tag == blkTagSuperLine) {
     lines = new GList();
-    buildLines(blk, lines);
+    buildLines(blk, lines, gTrue);
     superLines->append(new TextSuperLine(lines));
-  } else if (blk->tag == blkTagSuperLine) {
-    lines = new GList();
-    for (i = 0; i < blk->children->getLength(); ++i) {
-      buildLines((TextBlock *)blk->children->get(i), lines);
-    }
-    superLines->append(new TextSuperLine(lines));
   } else {
     for (i = 0; i < blk->children->getLength(); ++i) {
       buildSuperLines((TextBlock *)blk->children->get(i), superLines);
@@ -4681,6 +5594,59 @@
   return gTrue;
 }
 
+GBool TextPage::findWordPoints(double x, double y,
+			       TextPosition *startPos, TextPosition *endPos) {
+  TextPosition pos;
+  TextColumn *col;
+  TextParagraph *par;
+  TextLine *line;
+  int startCharIdx, endCharIdx;
+
+  if (!findPointInside(x, y, &pos)) {
+    return gFalse;
+  }
+  col = (TextColumn *)findCols->get(pos.colIdx);
+  par = (TextParagraph *)col->getParagraphs()->get(pos.parIdx);
+  line = (TextLine *)par->getLines()->get(pos.lineIdx);
+
+  for (startCharIdx = pos.charIdx;
+       startCharIdx > 0 && line->text[startCharIdx - 1] != 0x20;
+       --startCharIdx) ;
+  *startPos = pos;
+  startPos->charIdx = startCharIdx;
+
+  for (endCharIdx = pos.charIdx;
+       endCharIdx < line->len && line->text[endCharIdx] != 0x20;
+       ++endCharIdx) ;
+  *endPos = pos;
+  endPos->charIdx = endCharIdx;
+
+  return gTrue;
+}
+
+GBool TextPage::findLinePoints(double x, double y,
+			       TextPosition *startPos, TextPosition *endPos) {
+  TextPosition pos;
+  TextColumn *col;
+  TextParagraph *par;
+  TextLine *line;
+
+  if (!findPointInside(x, y, &pos)) {
+    return gFalse;
+  }
+  col = (TextColumn *)findCols->get(pos.colIdx);
+  par = (TextParagraph *)col->getParagraphs()->get(pos.parIdx);
+  line = (TextLine *)par->getLines()->get(pos.lineIdx);
+
+  *startPos = pos;
+  startPos->charIdx = 0;
+
+  *endPos = pos;
+  endPos->charIdx = line->len;
+
+  return gTrue;
+}
+
 // Find the position in [col] corresponding to [x],[y].  The column,
 // [col], was found by findPointInside() or findPointNear().
 void TextPage::findPointInColumn(TextColumn *col, double x, double y,
@@ -4860,33 +5826,56 @@
   TextParagraph *par;
   TextLine *line;
   TextWord *word;
+  GList *overlappingChars;
   GList *words;
   GBool primaryLR;
   int rot, colIdx, parIdx, lineIdx, wordIdx;
 
 #if 0 //~debug
-  dumpCharList(charList);
+  dumpChars(charList);
 #endif
-  rot = rotateChars(charList);
-  primaryLR = checkPrimaryLR(charList);
-  tree = splitChars(charList);
+
+  if (control.mode == textOutSimple2Layout) {
+    rot = 0;
+    primaryLR = checkPrimaryLR(chars);
+    rotateCharsToZero(chars);
+    columns = buildSimple2Columns(chars);
+    unrotateCharsFromZero(chars);
+    unrotateColumnsFromZero(columns);
+
+  } else {
+    if (control.overlapHandling != textOutIgnoreOverlaps) {
+      overlappingChars = separateOverlappingText(chars);
+    } else {
+      overlappingChars = NULL;
+    }
+    rot = rotateChars(charList);
+    primaryLR = checkPrimaryLR(charList);
+    tree = splitChars(charList);
 #if 0 //~debug
-  dumpTree(tree);
+    dumpTree(tree);
 #endif
-  if (!tree) {
-    // no text
-    unrotateChars(charList, rot);
-    return new TextWordList(new GList(), gTrue);
-  }
-  columns = buildColumns(tree, primaryLR);
+    if (!tree) {
+      // no text
+      unrotateChars(charList, rot);
+      return new TextWordList(new GList(), gTrue);
+    }
+    columns = buildColumns(tree, primaryLR);
 #if 0 //~debug
-  dumpColumns(columns, gTrue);
+    dumpColumns(columns, gTrue);
 #endif
-  delete tree;
-  unrotateChars(charList, rot);
-  if (control.html) {
-    rotateUnderlinesAndLinks(rot);
-    generateUnderlinesAndLinks(columns);
+    delete tree;
+    unrotateChars(charList, rot);
+    if (control.html) {
+      rotateUnderlinesAndLinks(rot);
+      generateUnderlinesAndLinks(columns);
+    }
+    if (overlappingChars) {
+      if (overlappingChars->getLength() > 0) {
+	columns->append(buildOverlappingTextColumn(overlappingChars));
+      }
+      deleteGList(overlappingChars, TextChar);
+    }
   }
 
   words = new GList();
@@ -4897,8 +5886,12 @@
       for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) {
 	line = (TextLine *)par->lines->get(lineIdx);
 	for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) {
-	  word = (TextWord *)line->words->get(wordIdx);
-	  words->append(word->copy());
+	  word = ((TextWord *)line->words->get(wordIdx))->copy();
+	  if (wordIdx == line->words->getLength() - 1 &&
+	      !line->getHyphenated()) {
+	    word->spaceAfter = gTrue;
+	  }
+	  words->append(word);
 	}
       }
     }
@@ -4906,6 +5899,7 @@
 
   switch (control.mode) {
   case textOutReadingOrder:
+  case textOutSimple2Layout:
     // already in reading order
     break;
   case textOutPhysLayout:
@@ -4958,7 +5952,8 @@
 	 tree->type == blkLeaf ? "leaf" :
 	                 tree->type == blkHorizSplit ? "horiz" : "vert",
 	 tree->tag == blkTagMulticolumn ? "multicolumn" :
-	                tree->tag == blkTagColumn ? "column" : "line",
+	                tree->tag == blkTagColumn ? "column" :
+                        tree->tag == blkTagSuperLine ? "superline" : "line",
 	 tree->smallSplit,
 	 tree->rot, tree->xMin, tree->yMin, tree->xMax, tree->yMax);
   if (tree->type == blkLeaf) {
@@ -5001,18 +5996,18 @@
 	    word = (TextWord *)line->words->get(wordIdx);
 	    printf("      word: xMin=%g yMin=%g xMax=%g yMax=%g\n",
 		   word->xMin, word->yMin, word->xMax, word->yMax);
-	    printf("            ");
+	    printf("            '");
 	    for (i = 0; i < word->len; ++i) {
 	      printf("%c", word->text[i] & 0xff);
 	    }
-	    printf("\n");
+	    printf("'\n");
 	  }
 	} else {
-	  printf("          ");
+	  printf("          '");
 	  for (i = 0; i < line->len; ++i) {
 	    printf("%c", line->text[i] & 0xff);
 	  }
-	  printf("\n");
+	  printf("'\n");
 	}
       }
     }
@@ -5041,7 +6036,7 @@
 }
 
 TextOutputDev::TextOutputDev(char *fileName, TextOutputControl *controlA,
-			     GBool append) {
+			     GBool append, GBool fileNameIsUTF8) {
   text = NULL;
   control = *controlA;
   ok = gTrue;
@@ -5055,15 +6050,22 @@
       // keep DOS from munging the end-of-line characters
       setmode(fileno(stdout), O_BINARY);
 #endif
-    } else if ((outputStream = fopen(fileName, append ? "ab" : "wb"))) {
+    } else {
+      if (fileNameIsUTF8) {
+	outputStream = openFile(fileName, append ? "ab" : "wb");
+      } else {
+	outputStream = fopen(fileName, append ? "ab" : "wb");
+      }
+      if (!outputStream) {
+	error(errIO, -1, "Couldn't open text file '{0:s}'", fileName);
+	ok = gFalse;
+	return;
+      }
       needClose = gTrue;
-    } else {
-      error(errIO, -1, "Couldn't open text file '{0:s}'", fileName);
-      ok = gFalse;
-      return;
     }
     outputFunc = &outputToFile;
   } else {
+    outputFunc = NULL;
     outputStream = NULL;
   }
 

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextOutputDev.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -42,6 +42,7 @@
   textOutReadingOrder,		// format into reading order
   textOutPhysLayout,		// maintain original physical layout
   textOutSimpleLayout,		// simple one-column physical layout
+  textOutSimple2Layout,		// simple one-column physical layout
   textOutTableLayout,		// similar to PhysLayout, but optimized
 				//   for tables
   textOutLinePrinter,		// strict fixed-pitch/height layout
@@ -48,6 +49,12 @@
   textOutRawOrder		// keep text in content stream order
 };
 
+enum TextOutputOverlapHandling {
+  textOutIgnoreOverlaps,	// no special handling for overlaps
+  textOutAppendOverlaps,	// append overlapping text to main text
+  textOutDiscardOverlaps	// discard overlapping text
+};
+
 class TextOutputControl {
 public:
 
@@ -66,8 +73,17 @@
 				//   in after forming columns
   GBool discardDiagonalText;	// discard all text that's not close to
 				//   0/90/180/270 degrees
+  GBool discardRotatedText;	// discard all text that's not horizontal
+				//   (0 degrees)
   GBool discardInvisibleText;	// discard all invisible characters
   GBool discardClippedText;	// discard all clipped characters
+  GBool splitRotatedWords;	// do not combine horizontal and
+				//   non-horizontal chars in a single
+				//   word
+  TextOutputOverlapHandling	// how to handle overlapping text
+               overlapHandling;
+  GBool separateLargeChars;	// separate "large" characters from
+				//   "regular" characters
   GBool insertBOM;		// insert a Unicode BOM at the start of
 				//   the text output
   double marginLeft,		// characters outside the margins are
@@ -124,7 +140,7 @@
 public:
 
   TextWord(GList *chars, int start, int lenA,
-	   int rotA, int dirA, GBool spaceAfterA);
+	   int rotA, GBool rotatedA, int dirA, GBool spaceAfterA);
   ~TextWord();
   TextWord *copy() { return new TextWord(this); }
 
@@ -144,6 +160,7 @@
 		   double *xMaxA, double *yMaxA);
   double getFontSize() { return fontSize; }
   int getRotation() { return rot; }
+  GBool isRotated() { return (GBool)rotated; }
   int getCharPos() { return charPos[0]; }
   int getCharLen() { return charPos[len] - charPos[0]; }
   int getDirection() { return dir; }
@@ -158,8 +175,6 @@
   static int cmpYX(const void *p1, const void *p2);
   static int cmpCharPos(const void *p1, const void *p2);
 
-  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
   Unicode *text;		// the text
@@ -171,19 +186,22 @@
   int len;			// number of characters
   TextFontInfo *font;		// font information
   double fontSize;		// font size
-  int dir;			// character direction (+1 = left-to-right;
-				//   -1 = right-to-left; 0 = neither)
-  GBool spaceAfter;		// set if there is a space between this
-				//   word and the next word on the line
-
-  GBool underlined;
   TextLink *link;
-
   double colorR,		// word color
          colorG,
          colorB;
   GBool invisible;		// set for invisible text (render mode 3)
 
+  // group the byte-size fields to minimize object size
+  Guchar rot;			// rotation, multiple of 90 degrees
+				//   (0, 1, 2, or 3)
+  char rotated;			// set if this word is non-horizontal
+  char dir;			// character direction (+1 = left-to-right;
+				//   -1 = right-to-left; 0 = neither)
+  char spaceAfter;		// set if there is a space between this
+				//   word and the next word on the line
+  char underlined;
+
   friend class TextBlock;
   friend class TextLine;
   friend class TextPage;
@@ -209,6 +227,7 @@
   GList *getWords() { return words; }
   int getLength() { return len; }
   double getEdge(int idx) { return edge[idx]; }
+  GBool getHyphenated() { return hyphenated; }
 
 private:
 
@@ -407,6 +426,16 @@
   // are no columns.
   GBool findPointNear(double x, double y, TextPosition *pos);
 
+  // Find the start and end of a word inside a column.  Returns false
+  // if x,y fall outside all columns.
+  GBool findWordPoints(double x, double y,
+		       TextPosition *startPos, TextPosition *endPos);
+
+  // Find the start and end of a line inside a column.  Returns false
+  // if x,y fall outside all columns.
+  GBool findLinePoints(double x, double y,
+		       TextPosition *startPos, TextPosition *endPos);
+
   // Get the upper point of a TextPosition.
   void convertPosToPointUpper(TextPosition *pos, double *x, double *y);
 
@@ -478,6 +507,11 @@
 			 UnicodeMap *uMap,
 			 char *space, int spaceLen,
 			 char *eol, int eolLen);
+  void writeSimple2Layout(void *outputStream,
+			  TextOutputFunc outputFunc,
+			  UnicodeMap *uMap,
+			  char *space, int spaceLen,
+			  char *eol, int eolLen);
   void writeLinePrinter(void *outputStream,
 			TextOutputFunc outputFunc,
 			UnicodeMap *uMap,
@@ -493,12 +527,17 @@
 
   // analysis
   int rotateChars(GList *charsA);
+  void rotateCharsToZero(GList *charsA);
   void rotateUnderlinesAndLinks(int rot);
   void unrotateChars(GList *charsA, int rot);
+  void unrotateCharsFromZero(GList *charsA);
+  void unrotateColumnsFromZero(GList *columns);
   void unrotateColumns(GList *columns, int rot);
   void unrotateWords(GList *words, int rot);
   GBool checkPrimaryLR(GList *charsA);
   void removeDuplicates(GList *charsA, int rot);
+  GList *separateOverlappingText(GList *charsA);
+  TextColumn *buildOverlappingTextColumn(GList *overlappingChars);
   TextBlock *splitChars(GList *charsA);
   TextBlock *split(GList *charsA, int rot);
   GList *getChars(GList *charsA, double xMin, double yMin,
@@ -522,8 +561,12 @@
   double getLineIndent(TextLine *line, TextBlock *blk);
   double getAverageLineSpacing(GList *lines);
   double getLineSpacing(TextLine *line0, TextLine *line1);
-  void buildLines(TextBlock *blk, GList *lines);
+  void buildLines(TextBlock *blk, GList *lines, GBool splitSuperLines);
+  GList *buildSimple2Columns(GList *charsA);
+  GList *buildSimple2Lines(GList *charsA, int rot);
   TextLine *buildLine(TextBlock *blk);
+  TextLine *buildLine(GList *charsA, int rot,
+		      double xMin, double yMin, double xMax, double yMax);
   void getLineChars(TextBlock *blk, GList *charsA);
   double computeWordSpacingThreshold(GList *charsA, int rot);
   int getCharDirection(TextChar *ch);
@@ -563,6 +606,7 @@
   int curRot;			// current rotation
   GBool diagonal;		// set if rotation is not close to
 				//   0/90/180/270 degrees
+  GBool rotated;		// set if text is not horizontal (0 degrees)
   int nTinyChars;		// number of "tiny" chars seen so far
   Unicode *actualText;		// current "ActualText" span
   int actualTextLen;
@@ -605,7 +649,7 @@
   // is maintained.  If <rawOrder> is true, the text is kept in
   // content stream order.
   TextOutputDev(char *fileName, TextOutputControl *controlA,
-		GBool append);
+		GBool append, GBool fileNameIsUTF8 = gFalse);
 
   // Create a TextOutputDev which will write to a generic stream.  If
   // <physLayoutA> is true, the original physical layout of the text

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -201,3 +201,13 @@
   }
   return s;
 }
+
+GString *TextString::toUTF8() {
+  GString *s = new GString();
+  for (int i = 0; i < len; ++i) {
+    char buf[8];
+    int n = mapUTF8(u[i], buf, sizeof(buf));
+    s->append(buf, n);
+  }
+  return s;
+}

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/TextString.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -56,6 +56,9 @@
   // Create a PDF text string from a TextString.
   GString *toPDFTextString();
 
+  // Convert a TextString to UTF-8.
+  GString *toUTF8();
+
 private:
 
   void expand(int delta);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/WebFont.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/WebFont.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/WebFont.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -22,6 +22,7 @@
 
 WebFont::WebFont(GfxFont *gfxFontA, XRef *xref) {
   GfxFontType type;
+  Ref id;
 
   gfxFont = gfxFontA;
   fontBuf = NULL;
@@ -29,24 +30,26 @@
   ffType1C = NULL;
   isOpenType = gFalse;
 
-  type = gfxFont->getType();
-  if (type == fontTrueType ||
-      type == fontTrueTypeOT ||
-      type == fontCIDType2 ||
-      type == fontCIDType2OT) {
-    if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) {
-      ffTrueType = FoFiTrueType::make(fontBuf, fontLength, 0);
+  if (gfxFont->getEmbeddedFontID(&id)) {
+    type = gfxFont->getType();
+    if (type == fontTrueType ||
+	type == fontTrueTypeOT ||
+	type == fontCIDType2 ||
+	type == fontCIDType2OT) {
+      if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) {
+	ffTrueType = FoFiTrueType::make(fontBuf, fontLength, 0);
+      }
+    } else if (type == fontType1C ||
+	       type == fontCIDType0C) {
+      if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) {
+	ffType1C = FoFiType1C::make(fontBuf, fontLength);
+      }
+    } else if (type == fontType1COT ||
+	       type == fontCIDType0COT) {
+      if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) {
+	isOpenType = gTrue;
+      }
     }
-  } else if (type == fontType1C ||
-	     type == fontCIDType0C) {
-    if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) {
-      ffType1C = FoFiType1C::make(fontBuf, fontLength);
-    }
-  } else if (type == fontType1COT ||
-	     type == fontCIDType0COT) {
-    if ((fontBuf = gfxFont->readEmbFontFile(xref, &fontLength))) {
-      isOpenType = gTrue;
-    }
   }
 }
 

Deleted: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,3245 +0,0 @@
-//========================================================================
-//
-// XFAForm.cc
-//
-// Copyright 2012 Glyph & Cog, LLC
-//
-//========================================================================
-
-#include <aconf.h>
-
-#ifdef USE_GCC_PRAGMAS
-#pragma implementation
-#endif
-
-#include <stdlib.h>
-#include "gmem.h"
-#include "gmempp.h"
-#include "GString.h"
-#include "GList.h"
-#include "GHash.h"
-#include "Error.h"
-#include "Object.h"
-#include "PDFDoc.h"
-#include "Gfx.h"
-#include "GfxFont.h"
-#include "Zoox.h"
-#include "PDF417Barcode.h"
-#include "UTF8.h"
-#include "XFAForm.h"
-
-#ifdef _WIN32
-#  undef strcasecmp
-#  undef strncasecmp
-#  define strcasecmp _stricmp
-#  define strncasecmp _strnicmp
-#endif
-
-//------------------------------------------------------------------------
-
-// 5 bars + 5 spaces -- each can be wide (1) or narrow (0)
-// (there are always exactly 3 wide elements;
-// the last space is always narrow)
-static Guchar code3Of9Data[128][10] = {
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x00
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x10
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 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, 1, 1, 0, 0, 0, 1, 0, 0, 0 }, // ' ' = 0x20
-  { 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, 1, 0, 1, 0, 1, 0, 0, 0, 0 }, // '$' = 0x24
-  { 0, 0, 0, 1, 0, 1, 0, 1, 0, 0 }, // '%' = 0x25
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 1, 0, 0, 1, 0, 1, 0, 0, 0 }, // '*' = 0x2a
-  { 0, 1, 0, 0, 0, 1, 0, 1, 0, 0 }, // '+' = 0x2b
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0 }, // '-' = 0x2d
-  { 1, 1, 0, 0, 0, 0, 1, 0, 0, 0 }, // '.' = 0x2e
-  { 0, 1, 0, 1, 0, 0, 0, 1, 0, 0 }, // '/' = 0x2f
-  { 0, 0, 0, 1, 1, 0, 1, 0, 0, 0 }, // '0' = 0x30
-  { 1, 0, 0, 1, 0, 0, 0, 0, 1, 0 }, // '1'
-  { 0, 0, 1, 1, 0, 0, 0, 0, 1, 0 }, // '2'
-  { 1, 0, 1, 1, 0, 0, 0, 0, 0, 0 }, // '3'
-  { 0, 0, 0, 1, 1, 0, 0, 0, 1, 0 }, // '4'
-  { 1, 0, 0, 1, 1, 0, 0, 0, 0, 0 }, // '5'
-  { 0, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, // '6'
-  { 0, 0, 0, 1, 0, 0, 1, 0, 1, 0 }, // '7'
-  { 1, 0, 0, 1, 0, 0, 1, 0, 0, 0 }, // '8'
-  { 0, 0, 1, 1, 0, 0, 1, 0, 0, 0 }, // '9'
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x40
-  { 1, 0, 0, 0, 0, 1, 0, 0, 1, 0 }, // 'A' = 0x41
-  { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0 }, // 'B'
-  { 1, 0, 1, 0, 0, 1, 0, 0, 0, 0 }, // 'C'
-  { 0, 0, 0, 0, 1, 1, 0, 0, 1, 0 }, // 'D'
-  { 1, 0, 0, 0, 1, 1, 0, 0, 0, 0 }, // 'E'
-  { 0, 0, 1, 0, 1, 1, 0, 0, 0, 0 }, // 'F'
-  { 0, 0, 0, 0, 0, 1, 1, 0, 1, 0 }, // 'G'
-  { 1, 0, 0, 0, 0, 1, 1, 0, 0, 0 }, // 'H'
-  { 0, 0, 1, 0, 0, 1, 1, 0, 0, 0 }, // 'I'
-  { 0, 0, 0, 0, 1, 1, 1, 0, 0, 0 }, // 'J'
-  { 1, 0, 0, 0, 0, 0, 0, 1, 1, 0 }, // 'K'
-  { 0, 0, 1, 0, 0, 0, 0, 1, 1, 0 }, // 'L'
-  { 1, 0, 1, 0, 0, 0, 0, 1, 0, 0 }, // 'M'
-  { 0, 0, 0, 0, 1, 0, 0, 1, 1, 0 }, // 'N'
-  { 1, 0, 0, 0, 1, 0, 0, 1, 0, 0 }, // 'O'
-  { 0, 0, 1, 0, 1, 0, 0, 1, 0, 0 }, // 'P' = 0x50
-  { 0, 0, 0, 0, 0, 0, 1, 1, 1, 0 }, // 'Q'
-  { 1, 0, 0, 0, 0, 0, 1, 1, 0, 0 }, // 'R'
-  { 0, 0, 1, 0, 0, 0, 1, 1, 0, 0 }, // 'S'
-  { 0, 0, 0, 0, 1, 0, 1, 1, 0, 0 }, // 'T'
-  { 1, 1, 0, 0, 0, 0, 0, 0, 1, 0 }, // 'U'
-  { 0, 1, 1, 0, 0, 0, 0, 0, 1, 0 }, // 'V'
-  { 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, // 'W'
-  { 0, 1, 0, 0, 1, 0, 0, 0, 1, 0 }, // 'X'
-  { 1, 1, 0, 0, 1, 0, 0, 0, 0, 0 }, // 'Y'
-  { 0, 1, 1, 0, 1, 0, 0, 0, 0, 0 }, // 'Z'
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x60
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // 0x70
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-  { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
-};
-
-// 3 bars + 3 spaces -- each can be 1, 2, 3, or 4 units wide
-static Guchar code128Data[107][6] = {
-  { 2, 1, 2, 2, 2, 2 },
-  { 2, 2, 2, 1, 2, 2 },
-  { 2, 2, 2, 2, 2, 1 },
-  { 1, 2, 1, 2, 2, 3 },
-  { 1, 2, 1, 3, 2, 2 },
-  { 1, 3, 1, 2, 2, 2 },
-  { 1, 2, 2, 2, 1, 3 },
-  { 1, 2, 2, 3, 1, 2 },
-  { 1, 3, 2, 2, 1, 2 },
-  { 2, 2, 1, 2, 1, 3 },
-  { 2, 2, 1, 3, 1, 2 },
-  { 2, 3, 1, 2, 1, 2 },
-  { 1, 1, 2, 2, 3, 2 },
-  { 1, 2, 2, 1, 3, 2 },
-  { 1, 2, 2, 2, 3, 1 },
-  { 1, 1, 3, 2, 2, 2 },
-  { 1, 2, 3, 1, 2, 2 },
-  { 1, 2, 3, 2, 2, 1 },
-  { 2, 2, 3, 2, 1, 1 },
-  { 2, 2, 1, 1, 3, 2 },
-  { 2, 2, 1, 2, 3, 1 },
-  { 2, 1, 3, 2, 1, 2 },
-  { 2, 2, 3, 1, 1, 2 },
-  { 3, 1, 2, 1, 3, 1 },
-  { 3, 1, 1, 2, 2, 2 },
-  { 3, 2, 1, 1, 2, 2 },
-  { 3, 2, 1, 2, 2, 1 },
-  { 3, 1, 2, 2, 1, 2 },
-  { 3, 2, 2, 1, 1, 2 },
-  { 3, 2, 2, 2, 1, 1 },
-  { 2, 1, 2, 1, 2, 3 },
-  { 2, 1, 2, 3, 2, 1 },
-  { 2, 3, 2, 1, 2, 1 },
-  { 1, 1, 1, 3, 2, 3 },
-  { 1, 3, 1, 1, 2, 3 },
-  { 1, 3, 1, 3, 2, 1 },
-  { 1, 1, 2, 3, 1, 3 },
-  { 1, 3, 2, 1, 1, 3 },
-  { 1, 3, 2, 3, 1, 1 },
-  { 2, 1, 1, 3, 1, 3 },
-  { 2, 3, 1, 1, 1, 3 },
-  { 2, 3, 1, 3, 1, 1 },
-  { 1, 1, 2, 1, 3, 3 },
-  { 1, 1, 2, 3, 3, 1 },
-  { 1, 3, 2, 1, 3, 1 },
-  { 1, 1, 3, 1, 2, 3 },
-  { 1, 1, 3, 3, 2, 1 },
-  { 1, 3, 3, 1, 2, 1 },
-  { 3, 1, 3, 1, 2, 1 },
-  { 2, 1, 1, 3, 3, 1 },
-  { 2, 3, 1, 1, 3, 1 },
-  { 2, 1, 3, 1, 1, 3 },
-  { 2, 1, 3, 3, 1, 1 },
-  { 2, 1, 3, 1, 3, 1 },
-  { 3, 1, 1, 1, 2, 3 },
-  { 3, 1, 1, 3, 2, 1 },
-  { 3, 3, 1, 1, 2, 1 },
-  { 3, 1, 2, 1, 1, 3 },
-  { 3, 1, 2, 3, 1, 1 },
-  { 3, 3, 2, 1, 1, 1 },
-  { 3, 1, 4, 1, 1, 1 },
-  { 2, 2, 1, 4, 1, 1 },
-  { 4, 3, 1, 1, 1, 1 },
-  { 1, 1, 1, 2, 2, 4 },
-  { 1, 1, 1, 4, 2, 2 },
-  { 1, 2, 1, 1, 2, 4 },
-  { 1, 2, 1, 4, 2, 1 },
-  { 1, 4, 1, 1, 2, 2 },
-  { 1, 4, 1, 2, 2, 1 },
-  { 1, 1, 2, 2, 1, 4 },
-  { 1, 1, 2, 4, 1, 2 },
-  { 1, 2, 2, 1, 1, 4 },
-  { 1, 2, 2, 4, 1, 1 },
-  { 1, 4, 2, 1, 1, 2 },
-  { 1, 4, 2, 2, 1, 1 },
-  { 2, 4, 1, 2, 1, 1 },
-  { 2, 2, 1, 1, 1, 4 },
-  { 4, 1, 3, 1, 1, 1 },
-  { 2, 4, 1, 1, 1, 2 },
-  { 1, 3, 4, 1, 1, 1 },
-  { 1, 1, 1, 2, 4, 2 },
-  { 1, 2, 1, 1, 4, 2 },
-  { 1, 2, 1, 2, 4, 1 },
-  { 1, 1, 4, 2, 1, 2 },
-  { 1, 2, 4, 1, 1, 2 },
-  { 1, 2, 4, 2, 1, 1 },
-  { 4, 1, 1, 2, 1, 2 },
-  { 4, 2, 1, 1, 1, 2 },
-  { 4, 2, 1, 2, 1, 1 },
-  { 2, 1, 2, 1, 4, 1 },
-  { 2, 1, 4, 1, 2, 1 },
-  { 4, 1, 2, 1, 2, 1 },
-  { 1, 1, 1, 1, 4, 3 },
-  { 1, 1, 1, 3, 4, 1 },
-  { 1, 3, 1, 1, 4, 1 },
-  { 1, 1, 4, 1, 1, 3 },
-  { 1, 1, 4, 3, 1, 1 },
-  { 4, 1, 1, 1, 1, 3 },
-  { 4, 1, 1, 3, 1, 1 },
-  { 1, 1, 3, 1, 4, 1 },
-  { 1, 1, 4, 1, 3, 1 },
-  { 3, 1, 1, 1, 4, 1 },
-  { 4, 1, 1, 1, 3, 1 },
-  { 2, 1, 1, 4, 1, 2 }, // start code A
-  { 2, 1, 1, 2, 1, 4 }, // start code B
-  { 2, 1, 1, 2, 3, 2 }, // start code C
-  { 2, 3, 3, 1, 1, 1 }  // stop code (without final bar)
-};
-
-//------------------------------------------------------------------------
-
-class XFATableInfo {
-public:
-
-  XFATableInfo(ZxAttr *columnWidthsAttr);
-  ~XFATableInfo();
-  void computeRowHeight(ZxElement *rowSubform);
-
-  int nColumns;			// number of columns
-  double *columnRight;		// right edge (x coord) of each column
-  int rowIdx;			// current row index
-  int columnIdx;		// current column index
-  double rowTop;		// top edge (y coord) of current row
-  double rowHeight;		// height of current row (max cell height
-				//   so far)
-};
-
-XFATableInfo::XFATableInfo(ZxAttr *columnWidthsAttr) {
-  GString *s;
-  double w;
-  int i, columnRightSize;
-
-  nColumns = 0;
-  columnRight = NULL;
-  columnRightSize = 0;
-  if (columnWidthsAttr) {
-    s = columnWidthsAttr->getValue();
-    i = 0;
-    while (1) {
-      for (;
-	   i < s->getLength() &&
-	     (s->getChar(i) == ' ' || s->getChar(i) == '\t' ||
-	      s->getChar(i) == '\r' || s->getChar(i) == '\n');
-	   ++i) ;
-      if (i == s->getLength()) {
-	break;
-      }
-      w = XFAFormField::getMeasurement(s, i);
-      if (nColumns == columnRightSize) {
-	columnRightSize = columnRightSize ? 2 * columnRightSize : 8;
-	columnRight = (double *)greallocn(columnRight, columnRightSize,
-					  sizeof(double));
-      }
-      columnRight[nColumns] = (nColumns > 0 ? columnRight[nColumns - 1] : 0) + w;
-      ++nColumns;
-      for (++i;
-	   i < s->getLength() &&
-	     !(s->getChar(i) == ' ' || s->getChar(i) == '\t' ||
-	       s->getChar(i) == '\r' || s->getChar(i) == '\n');
-	   ++i) ;
-    }
-  }
-  rowIdx = -1;
-  columnIdx = 0;
-  rowTop = rowHeight = 0;
-}
-
-XFATableInfo::~XFATableInfo() {
-  gfree(columnRight);
-}
-
-void XFATableInfo::computeRowHeight(ZxElement *rowSubform) {
-  ZxNode *child;
-  ZxAttr *attr;
-  double h;
-
-  rowHeight = 0;
-  for (child = rowSubform->getFirstChild();
-       child;
-       child = child->getNextChild()) {
-    if (child->isElement("field") || child->isElement("draw")) {
-      if (!(attr = ((ZxElement *)child)->findAttr("h"))) {
-	attr = ((ZxElement *)child)->findAttr("minH");
-      }
-      h = XFAFormField::getMeasurement(attr,  0);
-      if (h > rowHeight) {
-	rowHeight = h;
-      }
-    }
-  }
-}
-
-//------------------------------------------------------------------------
-// XFAForm
-//------------------------------------------------------------------------
-
-XFAForm *XFAForm::load(PDFDoc *docA, Catalog *catalog,
-		       Object *acroFormObj, Object *xfaObj) {
-  XFAForm *xfaForm;
-  XFAFormField *field;
-  ZxDoc *xmlA;
-  ZxElement *tmpl;
-  Object catDict, resourceDictA, obj1;
-  GString *data;
-  GBool fullXFAA;
-  GString *name, *fullName;
-  GHash *nameCount, *nameIdx, *fullNameCount, *fullNameIdx;
-  char buf[4096];
-  int n, i;
-
-  if (catalog->getNumPages() == 0) {
-    return NULL;
-  }
-
-  docA->getXRef()->getCatalog(&catDict);
-  catDict.dictLookup("NeedsRendering", &obj1);
-  fullXFAA = obj1.isBool() && obj1.getBool();
-  obj1.free();
-  catDict.free();
-
-  if (xfaObj->isStream()) {
-    data = new GString();
-    xfaObj->streamReset();
-    while ((n = xfaObj->getStream()->getBlock(buf, sizeof(buf))) > 0) {
-      data->append(buf, n);
-    }
-  } else if (xfaObj->isArray()) {
-    data = new GString();
-    for (i = 1; i < xfaObj->arrayGetLength(); i += 2) {
-      if (!xfaObj->arrayGet(i, &obj1)->isStream()) {
-	error(errSyntaxError, -1, "XFA array element is wrong type");
-	obj1.free();
-	delete data;
-	return NULL;
-      }
-      obj1.streamReset();
-      while ((n = obj1.getStream()->getBlock(buf, sizeof(buf))) > 0) {
-	data->append(buf, n);
-      }
-      obj1.free();
-    }
-  } else {
-    error(errSyntaxError, -1, "XFA object is wrong type");
-    return NULL;
-  }
-
-  xmlA = ZxDoc::loadMem(data->getCString(), data->getLength());
-  delete data;
-  if (!xmlA) {
-    error(errSyntaxError, -1, "Invalid XML in XFA form");
-    return NULL;
-  }
-
-  if (acroFormObj->isDict()) {
-    acroFormObj->dictLookup("DR", &resourceDictA);
-  }
-
-  xfaForm = new XFAForm(docA, catalog->getNumPages(),
-			xmlA, &resourceDictA, fullXFAA);
-
-  resourceDictA.free();
-
-  if (xfaForm->xml->getRoot()) {
-    if ((tmpl = xfaForm->xml->getRoot()->findFirstChildElement("template"))) {
-      xfaForm->curPageNum = 0;
-      xfaForm->curXOffset = xfaForm->curYOffset = 0;
-      name = new GString();
-      fullName = new GString();
-      nameCount = new GHash();
-      nameIdx = new GHash();
-      fullNameCount = new GHash();
-      fullNameIdx = new GHash();
-      xfaForm->scanNode(tmpl, name, fullName, gFalse, NULL,
-			nameCount, nameIdx, fullNameCount, fullNameIdx,
-			NULL, catalog);
-      delete nameCount;
-      delete nameIdx;
-      delete fullNameCount;
-      delete fullNameIdx;
-      delete name;
-      delete fullName;
-
-      // apply pageOffsetX/Y (the pageSet/pageArea/contentArea offset)
-      // to all fields
-      if (xfaForm->pageSetNPages >= 1 &&
-	  xfaForm->pageSetNPages < xfaForm->nPages) {
-	for (i = xfaForm->pageSetNPages + 1; i <= xfaForm->nPages; ++i) {
-	  xfaForm->pageOffsetX[i - 1] =
-	      xfaForm->pageOffsetX[xfaForm->pageSetNPages - 1];
-	  xfaForm->pageOffsetY[i - 1] =
-	      xfaForm->pageOffsetY[xfaForm->pageSetNPages - 1];
-	}
-      }
-      for (i = 0; i < xfaForm->fields->getLength(); ++i) {
-	field = (XFAFormField *)xfaForm->fields->get(i);
-	if (field->pageNum >= 1 && field->pageNum <= xfaForm->nPages) {
-	  field->xOffset += xfaForm->pageOffsetX[field->pageNum - 1];
-	  field->yOffset += xfaForm->pageOffsetY[field->pageNum - 1];
-	}
-      }
-    }
-  }
-
-  return xfaForm;
-}
-
-XFAForm::XFAForm(PDFDoc *docA, int nPagesA, ZxDoc *xmlA,
-		 Object *resourceDictA, GBool fullXFAA):
-  Form(docA) {
-  int pg;
-
-  xml = xmlA;
-  fields = new GList();
-  resourceDictA->copy(&resourceDict);
-  fullXFA = fullXFAA;
-  nPages = nPagesA;
-  pageSetNPages = 0;
-  pageOffsetX = (double *)gmallocn(nPages, sizeof(double));
-  pageOffsetY = (double *)gmallocn(nPages, sizeof(double));
-  for (pg = 0; pg < nPages; ++pg) {
-    pageOffsetX[pg] = pageOffsetY[pg] = 0;
-  }
-}
-
-XFAForm::~XFAForm() {
-  delete xml;
-  deleteGList(fields, XFAFormField);
-  resourceDict.free();
-  gfree(pageOffsetX);
-  gfree(pageOffsetY);
-}
-
-// Scan <elem>.  Constructs the node's name and full name.  If <elem>
-// is a field, creates an XFAFormField; else scans <elem>'s children.
-void XFAForm::scanNode(ZxElement *elem,
-		       GString *parentName, GString *parentFullName,
-		       GBool inPageSet, XFATableInfo *tableInfo,
-		       GHash *nameCount, GHash *nameIdx,
-		       GHash *fullNameCount, GHash *fullNameIdx,
-		       GString *exclGroupName, Catalog *catalog) {
-  ZxAttr *attr;
-  GString *name, *fullName, *namePart, *fullNamePart;
-  GHash *childNameCount, *childNameIdx, *childFullNameCount, *childFullNameIdx;
-  int colSpan, i;
-
-  if (elem->isElement("template")) {
-    name = new GString("form");
-    fullName = new GString("form");
-    childNameCount = new GHash();
-    scanNames(elem, childNameCount);
-    childNameIdx = new GHash();
-    childFullNameCount = new GHash();
-    scanFullNames(elem, childFullNameCount);
-    childFullNameIdx = new GHash();
-  } else {
-    if ((namePart = getNodeName(elem))) {
-      name = GString::format("{0:t}.{1:t}", parentName, namePart);
-      if (nameCount->lookupInt(namePart) > 1) {
-	i = nameIdx->lookupInt(namePart);
-	name->appendf("[{0:d}]", i);
-	nameIdx->replace(namePart, i + 1);
-      }
-      childNameCount = new GHash();
-      scanNames(elem, childNameCount);
-      childNameIdx = new GHash();
-    } else {
-      name = parentName->copy();
-      childNameCount = nameCount;
-      childNameIdx = nameIdx;
-    }
-    if ((fullNamePart = getNodeFullName(elem))) {
-      fullName = GString::format("{0:t}.{1:t}", parentFullName, fullNamePart);
-      if (fullNameCount->lookupInt(fullNamePart) > 1) {
-	i = fullNameIdx->lookupInt(fullNamePart);
-	fullName->appendf("[{0:d}]", i);
-	fullNameIdx->replace(fullNamePart, i + 1);
-      }
-      childFullNameCount = new GHash();
-      scanFullNames(elem, childFullNameCount);
-      childFullNameIdx = new GHash();
-    } else {
-      fullName = parentFullName->copy();
-      childFullNameCount = fullNameCount;
-      childFullNameIdx = fullNameIdx;
-    }
-  }
-
-  if (tableInfo && (elem->isElement("field") || elem->isElement("draw"))) {
-    if ((attr = elem->findAttr("colSpan"))) {
-      colSpan = atoi(attr->getValue()->getCString());
-    } else {
-      colSpan = 1;
-    }
-  } else {
-    colSpan = 0;
-  }
-
-  if (elem->isElement("field")) {
-    scanField(elem, name, fullName, exclGroupName,
-	      inPageSet, tableInfo, colSpan, catalog);
-  } else {
-    scanNonField(elem, name, fullName, inPageSet, tableInfo, colSpan,
-		 childNameCount, childNameIdx,
-		 childFullNameCount, childFullNameIdx,
-		 catalog);
-  }
-
-  if (tableInfo) {
-    tableInfo->columnIdx += colSpan;
-  }
-
-  delete name;
-  delete fullName;
-  if (childNameCount != nameCount) {
-    delete childNameCount;
-  }
-  if (childNameIdx != nameIdx) {
-    delete childNameIdx;
-  }
-  if (childFullNameCount != fullNameCount) {
-    delete childFullNameCount;
-  }
-  if (childFullNameIdx != fullNameIdx) {
-    delete childFullNameIdx;
-  }
-}
-
-// Traverse all children of <elem>, incrementing nameCount[name] for
-// each named child.  Traversal stops at each named child.
-void XFAForm::scanNames(ZxElement *elem, GHash *nameCount) {
-  ZxNode *node;
-  ZxElement *child;
-  GString *namePart;
-
-  for (node = elem->getFirstChild(); node; node = node->getNextChild()) {
-    if (node->isElement()) {
-      child = (ZxElement *)node;
-      if ((namePart = getNodeName(child))) {
-	if (nodeIsBindGlobal(child)) {
-	  nameCount->replace(namePart, 1);
-	} else {
-	  nameCount->replace(namePart, nameCount->lookupInt(namePart) + 1);
-	}
-      } else {
-	scanNames(child, nameCount);
-      }
-    }
-  }
-}
-
-// Traverse all children of <elem>, incrementing fullNameCount[name]
-// for each full-named child.  Traversal stops at each full-named
-// child.
-void XFAForm::scanFullNames(ZxElement *elem, GHash *fullNameCount) {
-  ZxNode *node;
-  ZxElement *child;
-  GString *fullNamePart;
-
-  for (node = elem->getFirstChild(); node; node = node->getNextChild()) {
-    if (node->isElement()) {
-      child = (ZxElement *)node;
-      if ((fullNamePart = getNodeFullName(child))) {
-	if (nodeIsBindGlobal(child)) {
-	  fullNameCount->replace(fullNamePart, 1);
-	} else {
-	  fullNameCount->replace(fullNamePart,
-				 fullNameCount->lookupInt(fullNamePart) + 1);
-	}
-      } else {
-	scanFullNames(child, fullNameCount);
-      }
-    }
-  }
-}
-
-void XFAForm::scanField(ZxElement *elem, GString *name, GString *fullName,
-			GString *exclGroupName, GBool inPageSet,
-			XFATableInfo *tableInfo, int colSpan,
-			Catalog *catalog) {
-  double xSubOffset, ySubOffset, columnWidth, rowHeight;
-
-  if (curPageNum == 0) {
-    curPageNum = 1;
-  }
-
-  xSubOffset = ySubOffset = 0;
-  columnWidth = rowHeight = 0;
-  if (tableInfo) {
-    if (tableInfo->columnIdx > 0 &&
-	tableInfo->columnIdx <= tableInfo->nColumns) {
-      xSubOffset = tableInfo->columnRight[tableInfo->columnIdx - 1];
-    }
-    if (tableInfo->columnIdx + colSpan <= tableInfo->nColumns) {
-      columnWidth = tableInfo->columnRight[tableInfo->columnIdx + colSpan - 1]
-	            - xSubOffset;
-    }
-    ySubOffset = tableInfo->rowTop;
-    rowHeight = tableInfo->rowHeight;
-    curXOffset += xSubOffset;
-    curYOffset += ySubOffset;
-  }
-
-  fields->append(new XFAFormField(this, elem, name->copy(), fullName->copy(),
-				  exclGroupName ? exclGroupName->copy()
-				                : (GString *)NULL,
-				  curPageNum, curXOffset, curYOffset,
-				  columnWidth, rowHeight));
-
-  if (tableInfo) {
-    curXOffset -= xSubOffset;
-    curYOffset -= ySubOffset;
-  }
-}
-
-void XFAForm::scanNonField(ZxElement *elem, GString *name, GString *fullName,
-			   GBool inPageSet,
-			   XFATableInfo *tableInfo, int colSpan,
-			   GHash *nameCount, GHash *nameIdx,
-			   GHash *fullNameCount, GHash *fullNameIdx,
-			   Catalog *catalog) {
-  XFATableInfo *newTableInfo;
-  ZxElement *brk, *contentArea;
-  ZxNode *child;
-  ZxAttr *attr;
-  PDFRectangle *box;
-  GString *exclGroupName;
-  double xSubOffset, ySubOffset;
-  int savedPageNum;
-
-  newTableInfo = tableInfo;
-
-  if (elem->isElement("subform")) {
-
-    // update page number
-    if (((brk = elem->findFirstChildElement("breakBefore")) &&
-	 (attr = brk->findAttr("targetType")) &&
-	 !attr->getValue()->cmp("pageArea")) ||
-	((brk = elem->findFirstChildElement("break")) &&
-	 (attr = brk->findAttr("before")) &&
-	 !attr->getValue()->cmp("pageArea")) ||
-	(curPageNum < nPages &&
-	 (attr = elem->findAttr("w")) &&
-	 (box = catalog->getPage(curPageNum + 1)->getMediaBox()) &&
-	 XFAFormField::getMeasurement(attr, 0) == box->x2 - box->x1 &&
-	 (attr = elem->findAttr("h")) &&
-	 XFAFormField::getMeasurement(attr, 0) == box->y2 - box->y1)) {
-      if (curPageNum < nPages) {
-	++curPageNum;
-      }
-    }
-
-    // update tableInfo
-    if ((attr = elem->findAttr("layout"))) {
-      if (!attr->getValue()->cmp("table")) {
-	newTableInfo = new XFATableInfo(elem->findAttr("columnWidths"));
-	newTableInfo->rowIdx = -1;
-	newTableInfo->columnIdx = 0;
-      } else if (tableInfo && !attr->getValue()->cmp("row")) {
-	++tableInfo->rowIdx;
-	tableInfo->columnIdx = 0;
-	tableInfo->rowTop += tableInfo->rowHeight;
-	tableInfo->computeRowHeight(elem);
-      }
-    }
-
-    // update position
-    xSubOffset = XFAFormField::getMeasurement(elem->findAttr("x"), 0);
-    ySubOffset = XFAFormField::getMeasurement(elem->findAttr("y"), 0);
-    curXOffset += xSubOffset;
-    curYOffset += ySubOffset;
-
-  } else if (elem->isElement("area") ||
-	     elem->isElement("exclGroup")) {
-    xSubOffset = XFAFormField::getMeasurement(elem->findAttr("x"), 0);
-    ySubOffset = XFAFormField::getMeasurement(elem->findAttr("y"), 0);
-    curXOffset += xSubOffset;
-    curYOffset += ySubOffset;
-
-  } else {
-    xSubOffset = ySubOffset = 0;
-  }
-
-  savedPageNum = curPageNum;
-  if (elem->isElement("pageSet")) {
-    inPageSet = gTrue;
-    curPageNum = 0;
-
-  } else if (elem->isElement("pageArea")) {
-    if (inPageSet) {
-      if (curPageNum < nPages) {
-	++curPageNum;
-      }
-      if ((contentArea = elem->findFirstChildElement("contentArea"))) {
-	pageOffsetX[curPageNum - 1] =
-	    XFAFormField::getMeasurement(contentArea->findAttr("x"), 0);
-	pageOffsetY[curPageNum - 1] =
-	    XFAFormField::getMeasurement(contentArea->findAttr("y"), 0);
-	// looks like the contentArea offset (pageOffsetX/Y) should
-	// not be added to fields defined inside the pageArea
-	// element (?)
-	xSubOffset -= pageOffsetX[curPageNum - 1];
-	ySubOffset -= pageOffsetY[curPageNum - 1];
-	curXOffset -= pageOffsetX[curPageNum - 1];
-	curYOffset -= pageOffsetY[curPageNum - 1];
-      }
-    }
-  }
-
-  if (elem->isElement("exclGroup")) {
-    exclGroupName = name;
-  } else {
-    exclGroupName = NULL;
-  }
-
-  for (child = elem->getFirstChild(); child; child = child->getNextChild()) {
-    if (child->isElement()) {
-      scanNode((ZxElement *)child, name, fullName, inPageSet,
-	       newTableInfo, nameCount, nameIdx, fullNameCount, fullNameIdx,
-	       exclGroupName, catalog);
-    }
-  }
-
-  curXOffset -= xSubOffset;
-  curYOffset -= ySubOffset;
-
-  if (newTableInfo != tableInfo) {
-    delete newTableInfo;
-  }
-
-  if (elem->isElement("pageSet")) {
-    pageSetNPages = curPageNum;
-    curPageNum = savedPageNum;
-    inPageSet = gFalse;
-  }
-}
-
-GString *XFAForm::getNodeName(ZxElement *elem) {
-  ZxElement *bindElem;
-  ZxAttr *attr;
-
-  if (!(elem->getType()->cmp("field") &&
-	(bindElem = elem->findFirstChildElement("bind")) &&
-	(attr = bindElem->findAttr("match")) &&
-	!attr->getValue()->cmp("none")) &&
-      !elem->isElement("area") &&
-      (attr = elem->findAttr("name"))) {
-    return attr->getValue();
-  }
-  return NULL;
-}
-
-GString *XFAForm::getNodeFullName(ZxElement *elem) {
-  ZxAttr *attr;
-
-  if (!elem->isElement("area") &&
-      (attr = elem->findAttr("name"))) {
-    return attr->getValue();
-  }
-  return NULL;
-}
-
-GBool XFAForm::nodeIsBindGlobal(ZxElement *elem) {
-  ZxElement *bindElem;
-  ZxAttr *attr;
-
-  return (bindElem = elem->findFirstChildElement("bind")) &&
-         (attr = bindElem->findAttr("match")) &&
-         !attr->getValue()->cmp("global");
-}
-
-void XFAForm::draw(int pageNum, Gfx *gfx, GBool printing) {
-  GfxFontDict *fontDict;
-  Object obj1;
-  int i;
-
-  // build the font dictionary
-  fontDict = NULL;
-  if (resourceDict.isDict()) {
-    if (resourceDict.dictLookup("Font", &obj1)->isDict()) {
-      fontDict = new GfxFontDict(doc->getXRef(), NULL, obj1.getDict());
-    }
-    obj1.free();
-  }
-
-  for (i = 0; i < fields->getLength(); ++i) {
-    ((XFAFormField *)fields->get(i))->draw(pageNum, gfx, printing, fontDict);
-  }
-
-  delete fontDict;
-}
-
-int XFAForm::getNumFields() {
-  return fields->getLength();
-}
-
-FormField *XFAForm::getField(int idx) {
-  return (XFAFormField *)fields->get(idx);
-}
-
-//------------------------------------------------------------------------
-// XFAFormField
-//------------------------------------------------------------------------
-
-XFAFormField::XFAFormField(XFAForm *xfaFormA, ZxElement *xmlA,
-			   GString *nameA, GString *fullNameA,
-			   GString *exclGroupNameA,
-			   int pageNumA, double xOffsetA, double yOffsetA,
-			   double columnWidthA, double rowHeightA) {
-  xfaForm = xfaFormA;
-  xml = xmlA;
-  name = nameA;
-  fullName = fullNameA;
-  exclGroupName = exclGroupNameA;
-  pageNum = pageNumA;
-  xOffset = xOffsetA;
-  yOffset = yOffsetA;
-  columnWidth = columnWidthA;
-  rowHeight = rowHeightA;
-}
-
-XFAFormField::~XFAFormField() {
-  delete name;
-  delete fullName;
-  if (exclGroupName) {
-    delete exclGroupName;
-  }
-}
-
-int XFAFormField::getPageNum() {
-  return pageNum;
-}
-
-const char *XFAFormField::getType() {
-  ZxElement *uiElem;
-  ZxNode *node;
-
-  if ((uiElem = xml->findFirstChildElement("ui"))) {
-    for (node = uiElem->getFirstChild(); node; node = node->getNextChild()) {
-      if (node->isElement("textEdit")) {
-	return "Text";
-      } else if (node->isElement("numericEdit")) {
-	return "Numeric";
-      } else if (node->isElement("dateTimeEdit")) {
-	return "DateTime";
-      } else if (node->isElement("choiceList")) {
-	return "ChoiceList";
-      } else if (node->isElement("checkButton")) {
-	return "CheckButton";
-      } else if (node->isElement("barcode")) {
-	return "BarCode";
-      }
-      //~ other field types go here
-    }
-  }
-  return NULL;
-}
-
-Unicode *XFAFormField::getName(int *length) {
-  //~ assumes name is UTF-8
-  return utf8ToUnicode(name, length);
-}
-
-Unicode *XFAFormField::getValue(int *length) {
-  ZxElement *uiElem;
-  ZxNode *node;
-  GString *s;
-
-  //~ assumes value is UTF-8
-  s = NULL;
-  if ((uiElem = xml->findFirstChildElement("ui"))) {
-    for (node = uiElem->getFirstChild(); node; node = node->getNextChild()) {
-      if (node->isElement("textEdit")) {
-	s = getFieldValue("text");
-	break;
-      } else if (node->isElement("numericEdit")) {
-	//~ not sure if this is correct
-	s = getFieldValue("text");
-	break;
-      } else if (node->isElement("dateTimeEdit")) {
-	s = getFieldValue("text");
-	break;
-      } else if (node->isElement("checkButton")) {
-	if (!(s = getFieldValue("integer"))) {
-	  s = getFieldValue("text");
-	}
-	break;
-      } else if (node->isElement("barcode")) {
-	s = getFieldValue("text");
-	break;
-      }
-      //~ other field types go here
-    }
-  }
-  if (!s) {
-    return NULL;
-  }
-  return utf8ToUnicode(s, length);
-}
-
-Unicode *XFAFormField::utf8ToUnicode(GString *s, int *length) {
-  Unicode *u;
-  int n, size, c0, c1, c2, c3, c4, c5, i;
-
-  n = size = 0;
-  u = NULL;
-  i = 0;
-  while (i < s->getLength()) {
-    if (n == size) {
-      size = size ? size * 2 : 16;
-      u = (Unicode *)greallocn(u, size, sizeof(Unicode));
-    }
-    c0 = s->getChar(i++) & 0xff;
-    if (c0 <= 0x7f) {
-      u[n++] = c0;
-    } else if (c0 <= 0xdf && i < n) {
-      c1 = s->getChar(i++) & 0xff;
-      u[n++] = ((c0 & 0x1f) << 6) | (c1 & 0x3f);
-    } else if (c0 <= 0xef && i+1 < n) {
-      c1 = s->getChar(i++) & 0xff;
-      c2 = s->getChar(i++) & 0xff;
-      u[n++] = ((c0 & 0x0f) << 12) | ((c1 & 0x3f) << 6) | (c2 & 0x3f);
-    } else if (c0 <= 0xf7 && i+2 < n) {
-      c1 = s->getChar(i++) & 0xff;
-      c2 = s->getChar(i++) & 0xff;
-      c3 = s->getChar(i++) & 0xff;
-      u[n++] = ((c0 & 0x07) << 18) | ((c1 & 0x3f) << 12) | ((c2 & 0x3f) << 6)
-	       | (c3 & 0x3f);
-    } else if (c0 <= 0xfb && i+3 < n) {
-      c1 = s->getChar(i++) & 0xff;
-      c2 = s->getChar(i++) & 0xff;
-      c3 = s->getChar(i++) & 0xff;
-      c4 = s->getChar(i++) & 0xff;
-      u[n++] = ((c0 & 0x03) << 24) | ((c1 & 0x3f) << 18) | ((c2 & 0x3f) << 12)
-	       | ((c3 & 0x3f) << 6) | (c4 & 0x3f);
-    } else if (c0 <= 0xfd && i+4 < n) {
-      c1 = s->getChar(i++) & 0xff;
-      c2 = s->getChar(i++) & 0xff;
-      c3 = s->getChar(i++) & 0xff;
-      c4 = s->getChar(i++) & 0xff;
-      c5 = s->getChar(i++) & 0xff;
-      u[n++] = ((c0 & 0x01) << 30) | ((c1 & 0x3f) << 24) | ((c2 & 0x3f) << 18)
-	       | ((c3 & 0x3f) << 12) | ((c4 & 0x3f) << 6) | (c5 & 0x3f);
-    } else {
-      u[n++] = '?';
-    }
-  }
-  *length = n;
-  return u;
-}
-
-void XFAFormField::getBBox(double *llx, double *lly,
-			   double *urx, double *ury) {
-  double xfaX, xfaY, xfaW, xfaH, pdfX, pdfY, pdfW, pdfH;
-  int pdfRot;
-
-  getRectangle(&xfaX, &xfaY, &xfaW, &xfaH,
-	       &pdfX, &pdfY, &pdfW, &pdfH, &pdfRot);
-  *llx = pdfX;
-  *lly = pdfY;
-  *urx = pdfX + pdfW;
-  *ury = pdfY + pdfH;
-}
-
-void XFAFormField::getFont(Ref *fontID, double *fontSize) {
-  ZxElement *fontElem;
-  ZxAttr *attr;
-  GBool bold, italic;
-
-  fontID->num = fontID->gen = -1;
-  *fontSize = 0;
-  if ((fontElem = xml->findFirstChildElement("font"))) {
-    bold = italic = gFalse;
-    if ((attr = fontElem->findAttr("weight"))) {
-      if (!attr->getValue()->cmp("bold")) {
-	bold = gTrue;
-      }
-    }
-    if ((attr = fontElem->findAttr("posture"))) {
-      if (!attr->getValue()->cmp("italic")) {
-	italic = gTrue;
-      }
-    }
-    if ((attr = fontElem->findAttr("typeface"))) {
-      *fontID = findFontName(attr->getValue(), bold, italic);
-    }
-    if ((attr = fontElem->findAttr("size"))) {
-      *fontSize = getMeasurement(attr, 0);
-    }
-  }
-}
-
-void XFAFormField::getColor(double *red, double *green, double *blue) {
-  ZxElement *fontElem, *fillElem, *colorElem;
-  ZxAttr *attr;
-  int r, g, b;
-
-  *red = *green = *blue = 0;
-  if ((fontElem = xml->findFirstChildElement("font"))) {
-    if ((fillElem = fontElem->findFirstChildElement("fill"))) {
-      if ((colorElem = fillElem->findFirstChildElement("color"))) {
-	if ((attr = colorElem->findAttr("value"))) {
-	  if (sscanf(attr->getValue()->getCString(), "%d,%d,%d",
-		     &r, &g, &b) == 3) {
-	    *red = r / 255.0;
-	    *green = g / 255.0;
-	    *blue = b / 255.0;
-	  }
-	}
-      }
-    }
-  }
-}
-
-void XFAFormField::draw(int pageNumA, Gfx *gfx, GBool printing,
-			GfxFontDict *fontDict) {
-  ZxElement *uiElem;
-  ZxNode *node;
-  ZxAttr *attr;
-  GString *appearBuf;
-  MemStream *appearStream;
-  Object appearDict, appearance, resourceDict, resourceSubdict;
-  Object fontResources, fontResource, defaultFont;
-  Object obj1, obj2;
-  char *resType, *fontName;
-  double mat[6];
-  double xfaX, xfaY, xfaW, xfaH, pdfX, pdfY, pdfW, pdfH;
-  int rot3, i;
-
-  if (pageNumA != pageNum) {
-    return;
-  }
-
-  // check the 'presence' attribute
-  if ((attr = xml->findAttr("presence"))) {
-    if (!attr->getValue()->cmp("hidden") ||
-	!attr->getValue()->cmp("invisible")) {
-      return;
-    }
-  }
-
-  getRectangle(&xfaX, &xfaY, &xfaW, &xfaH, &pdfX, &pdfY, &pdfW, &pdfH, &rot3);
-
-  // generate transform matrix
-  switch (rot3) {
-  case 0:
-  default:
-    mat[0] = 1;  mat[1] = 0;
-    mat[2] = 0;  mat[3] = 1;
-    mat[4] = 0;  mat[5] = 0;
-    break;
-  case 90:
-    mat[0] =  0;  mat[1] = 1;
-    mat[2] = -1;  mat[3] = 0;
-    mat[4] =  xfaH;  mat[5] = 0;
-    break;
-  case 180:
-    mat[0] = -1;  mat[1] =  0;
-    mat[2] =  0;  mat[3] = -1;
-    mat[4] =  xfaW;  mat[5] =  xfaH;
-    break;
-  case 270:
-    mat[0] = 0;  mat[1] = -1;
-    mat[2] = 1;  mat[3] =  0;
-    mat[4] = 0;  mat[5] =  xfaW;
-    break;
-  }
-
-  // get the appearance stream data
-  appearBuf = new GString();
-#if 0 //~ for debugging
-  double debugPad = 0;
-  appearBuf->appendf("q 1 1 0 rg {0:.4f} {1:.4f} {2:.4f} {3:.4f} re f Q\n",
-		     debugPad, debugPad, xfaW - 2*debugPad, xfaH - 2*debugPad);
-#endif
-  if ((uiElem = xml->findFirstChildElement("ui"))) {
-    for (node = uiElem->getFirstChild(); node; node = node->getNextChild()) {
-      if (node->isElement("textEdit") ||
-	  node->isElement("numericEdit")) {
-	drawTextEdit(fontDict, xfaW, xfaH, rot3, appearBuf);
-	break;
-      } else if (node->isElement("dateTimeEdit")) {
-	drawTextEdit(fontDict, xfaW, xfaH, rot3, appearBuf);
-	break;
-      } else if (node->isElement("choiceList")) {
-	drawTextEdit(fontDict, xfaW, xfaH, rot3, appearBuf);
-	break;
-      } else if (node->isElement("checkButton")) {
-	drawCheckButton(fontDict, xfaW, xfaH, rot3, appearBuf);
-	break;
-      } else if (node->isElement("barcode")) {
-	drawBarCode(fontDict, xfaW, xfaH, rot3, appearBuf);
-	break;
-      }
-      //~ other field types go here
-    }
-  } else {
-    drawTextEdit(fontDict, xfaW, xfaH, rot3, appearBuf);
-  }
-
-  // create the appearance stream
-  appearDict.initDict(xfaForm->doc->getXRef());
-  appearDict.dictAdd(copyString("Length"),
-		     obj1.initInt(appearBuf->getLength()));
-  appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form"));
-  obj1.initArray(xfaForm->doc->getXRef());
-  obj1.arrayAdd(obj2.initReal(0));
-  obj1.arrayAdd(obj2.initReal(0));
-  obj1.arrayAdd(obj2.initReal(xfaW));
-  obj1.arrayAdd(obj2.initReal(xfaH));
-  appearDict.dictAdd(copyString("BBox"), &obj1);
-  obj1.initArray(xfaForm->doc->getXRef());
-  obj1.arrayAdd(obj2.initReal(mat[0]));
-  obj1.arrayAdd(obj2.initReal(mat[1]));
-  obj1.arrayAdd(obj2.initReal(mat[2]));
-  obj1.arrayAdd(obj2.initReal(mat[3]));
-  obj1.arrayAdd(obj2.initReal(mat[4]));
-  obj1.arrayAdd(obj2.initReal(mat[5]));
-  appearDict.dictAdd(copyString("Matrix"), &obj1);
-  // NB: we need to deep-copy the resource dict and font resource
-  // subdict, because the font subdict is modified, and multiple
-  // threads can be sharing these objects.
-  resourceDict.initDict(xfaForm->doc->getXRef());
-  if (xfaForm->resourceDict.isDict()) {
-    for (i = 0; i < xfaForm->resourceDict.dictGetLength(); ++i) {
-      resType = xfaForm->resourceDict.dictGetKey(i);
-      if (strcmp(resType, "Font")) {
-	xfaForm->resourceDict.dictGetVal(i, &resourceSubdict);
-	resourceDict.dictAdd(copyString(resType), &resourceSubdict);
-      }
-    }
-  }
-  fontResources.initDict(xfaForm->doc->getXRef());
-  if (xfaForm->resourceDict.isDict() &&
-      xfaForm->resourceDict.dictLookup("Font", &resourceSubdict)->isDict()) {
-    for (i = 0; i < resourceSubdict.dictGetLength(); ++i) {
-      fontName = resourceSubdict.dictGetKey(i);
-      resourceSubdict.dictGetVal(i, &fontResource);
-      fontResources.dictAdd(copyString(fontName), &fontResource);
-    }
-    resourceSubdict.free();
-  }
-  defaultFont.initDict(xfaForm->doc->getXRef());
-  defaultFont.dictAdd(copyString("Type"), obj1.initName("Font"));
-  defaultFont.dictAdd(copyString("Subtype"), obj1.initName("Type1"));
-  defaultFont.dictAdd(copyString("BaseFont"), obj1.initName("Helvetica"));
-  defaultFont.dictAdd(copyString("Encoding"), obj1.initName("WinAnsiEncoding"));
-  fontResources.dictAdd(copyString("xpdf_default_font"), &defaultFont);
-  resourceDict.dictAdd(copyString("Font"), &fontResources);
-  appearDict.dictAdd(copyString("Resources"), &resourceDict);
-  appearStream = new MemStream(appearBuf->getCString(), 0,
-			       appearBuf->getLength(), &appearDict);
-  appearance.initStream(appearStream);
-  gfx->drawAnnot(&appearance, NULL, pdfX, pdfY, pdfX + pdfW, pdfY + pdfH);
-  appearance.free();
-  delete appearBuf;
-}
-
-void XFAFormField::getRectangle(double *xfaX, double *xfaY,
-				double *xfaW, double *xfaH,
-				double *pdfX, double *pdfY,
-				double *pdfW, double *pdfH,
-				int *pdfRot) {
-  Page *page;
-  PDFRectangle *pageRect;
-  ZxElement *captionElem, *paraElem, *marginElem;
-  ZxAttr *attr;
-  double x2, y2, w2, h2, t;
-  double anchorX, anchorY;
-  int pageRot, rot;
-
-  page = xfaForm->doc->getCatalog()->getPage(pageNum);
-  pageRect = page->getMediaBox();
-  pageRot = page->getRotate();
-
-  anchorX = 0;
-  anchorY = 0;
-  if ((attr = xml->findAttr("anchorType"))) {
-    if (!attr->getValue()->cmp("topLeft")) {
-      anchorX = 0;
-      anchorY = 0;
-    } else if (!attr->getValue()->cmp("topCenter")) {
-      anchorX = 0.5;
-      anchorY = 0;
-    } else if (!attr->getValue()->cmp("topRight")) {
-      anchorX = 1;
-      anchorY = 0;
-    } else if (!attr->getValue()->cmp("middleLeft")) {
-      anchorX = 0;
-      anchorY = 0.5;
-    } else if (!attr->getValue()->cmp("middleCenter")) {
-      anchorX = 0.5;
-      anchorY = 0.5;
-    } else if (!attr->getValue()->cmp("middleRight")) {
-      anchorX = 1;
-      anchorY = 0.5;
-    } else if (!attr->getValue()->cmp("bottomLeft")) {
-      anchorX = 0;
-      anchorY = 1;
-    } else if (!attr->getValue()->cmp("bottomCenter")) {
-      anchorX = 0.5;
-      anchorY = 1;
-    } else if (!attr->getValue()->cmp("bottomRight")) {
-      anchorX = 1;
-      anchorY = 1;
-    }
-  }
-  *xfaX = getMeasurement(xml->findAttr("x"), 0) + xOffset;
-  *xfaY = getMeasurement(xml->findAttr("y"), 0) + yOffset;
-  if (!(attr = xml->findAttr("w"))) {
-    attr = xml->findAttr("minW");
-  }
-  *xfaW = getMeasurement(attr, 0);
-  if (*xfaW < columnWidth) {
-    *xfaW = columnWidth;
-  }
-  if (!(attr = xml->findAttr("h"))) {
-    attr = xml->findAttr("minH");
-  }
-  *xfaH = getMeasurement(attr, 0);
-  if (*xfaH < rowHeight) {
-    *xfaH = rowHeight;
-  }
-  if ((attr = xml->findAttr("rotate"))) {
-    rot = atoi(attr->getValue()->getCString());
-    if ((rot %= 360) < 0) {
-      rot += 360;
-    }
-  } else {
-    rot = 0;
-  }
-
-  // look for <caption reserve="...."> -- add the caption width/height
-  // (plus a margin allowance)
-  if ((captionElem = xml->findFirstChildElement("caption"))) {
-    if ((attr = captionElem->findAttr("reserve"))) {
-      t = getMeasurement(attr, 0);
-      if ((attr = captionElem->findAttr("placement"))) {
-	if (!attr->getValue()->cmp("left")) {
-	  *xfaX += t + 1.5;
-	  *xfaW -= t + 1.5;
-	} else if (!attr->getValue()->cmp("right")) {
-	  *xfaW -= t + 1.5;
-	} else if (!attr->getValue()->cmp("top")) {
-	  *xfaY += t;
-	  *xfaH -= t;
-	} else if (!attr->getValue()->cmp("bottom")) {
-	  *xfaH -= t;
-	}
-      } else {
-	// default is placement="left"
-	*xfaX += t + 1.5;
-	*xfaW -= t + 1.5;
-      }
-    }
-  }
-
-  // look for <margin>
-  if ((marginElem = xml->findFirstChildElement("margin"))) {
-    if ((attr = marginElem->findAttr("leftInset"))) {
-      t = getMeasurement(attr, 0);
-      *xfaX += t;
-      *xfaW -= t;
-    }
-    if ((attr = marginElem->findAttr("rightInset"))) {
-      t = getMeasurement(attr, 0);
-      *xfaW -= t;
-    }
-    if ((attr = marginElem->findAttr("topInset"))) {
-      t = getMeasurement(attr, 0);
-      *xfaY += t;
-      *xfaH -= t;
-    }
-    if ((attr = marginElem->findAttr("bottomInset"))) {
-      t = getMeasurement(attr, 0);
-      *xfaH -= t;
-    }
-  }
-
-  // look for <para> -- add the margins
-  if ((paraElem = xml->findFirstChildElement("para"))) {
-    if ((attr = paraElem->findAttr("marginLeft"))) {
-      t = getMeasurement(attr, 0);
-      *xfaX += t;
-      *xfaW -= t;
-    }
-    if ((attr = paraElem->findAttr("marginRight"))) {
-      t = getMeasurement(attr, 0);
-      *xfaW -= t;
-    }
-    if ((attr = paraElem->findAttr("spaceAbove"))) {
-      t = getMeasurement(attr, 0);
-      *xfaY += t;
-      *xfaH -= t;
-    }
-    if ((attr = paraElem->findAttr("spaceBelow"))) {
-      t = getMeasurement(attr, 0);
-      *xfaH -= t;
-    }
-  }
-
-  // get annot rect (UL corner, width, height) in XFA coords
-  // notes:
-  // - XFA coordinates are top-left origin, after page rotation
-  // - XFA coordinates are dependent on choice of anchor point
-  //   and field rotation
-  switch (rot) {
-  case 0:
-  default:
-    x2 = *xfaX - anchorX * *xfaW;
-    y2 = *xfaY - anchorY * *xfaH;
-    w2 = *xfaW;
-    h2 = *xfaH;
-    break;
-  case 90:
-    x2 = *xfaX - anchorY * *xfaH;
-    y2 = *xfaY - (1 - anchorX) * *xfaW;
-    w2 = *xfaH;
-    h2 = *xfaW;
-    break;
-  case 180:
-    x2 = *xfaX - (1 - anchorX) * *xfaW;
-    y2 = *xfaY - (1 - anchorY) * *xfaH;
-    w2 = *xfaW;
-    h2 = *xfaH;
-    break;
-  case 270:
-    x2 = *xfaX - (1 - anchorY) * *xfaH;
-    y2 = *xfaY - anchorX * *xfaW;
-    w2 = *xfaH;
-    h2 = *xfaW;
-    break;
-  }
-
-  // convert annot rect to PDF coords (LL corner, width, height),
-  // taking page rotation into account
-  switch (pageRot) {
-  case 0:
-  default:
-    *pdfX = pageRect->x1 + x2;
-    *pdfY = pageRect->y2 - (y2 + h2);
-    *pdfW = w2;
-    *pdfH = h2;
-    break;
-  case 90:
-    *pdfX = pageRect->x1 + y2;
-    *pdfY = pageRect->y1 + x2;
-    *pdfW = h2;
-    *pdfH = w2;
-    break;
-  case 180:
-    *pdfX = pageRect->x2 - (x2 + w2);
-    *pdfY = pageRect->y1 + y2;
-    *pdfW = w2;
-    *pdfH = h2;
-    break;
-  case 270:
-    *pdfX = pageRect->x2 - (y2 + h2);
-    *pdfY = pageRect->y1 + (x2 + w2);
-    *pdfW = h2;
-    *pdfH = w2;
-    break;
-  }
-  *pdfRot = (rot + pageRot) % 360;
-}
-
-void XFAFormField::drawTextEdit(GfxFontDict *fontDict,
-				double w, double h, int rot,
-				GString *appearBuf) {
-  ZxElement *formatElem, *pictureElem;
-  ZxNode *pictureChildElem;
-  ZxElement *valueElem, *textElem, *uiElem, *textEditElem, *combElem;
-  ZxElement *fontElem, *paraElem;
-  ZxAttr *attr;
-  GString *picture, *value, *formattedValue, *fontName;
-  double fontSize;
-  int maxChars, combCells;
-  GBool multiLine, bold, italic;
-  XFAHorizAlign hAlign;
-  XFAVertAlign vAlign;
-
-  if (!(value = getFieldValue("text"))) {
-    return;
-  }
-
-  uiElem = xml->findFirstChildElement("ui");
-
-  //--- picture formatting
-  if (uiElem &&
-      (formatElem = xml->findFirstChildElement("format")) &&
-      (pictureElem = formatElem->findFirstChildElement("picture")) &&
-      (pictureChildElem = pictureElem->getFirstChild()) &&
-      pictureChildElem->isCharData()) {
-    picture = ((ZxCharData *)pictureChildElem)->getData();
-    if (uiElem->findFirstChildElement("dateTimeEdit")) {
-      formattedValue = pictureFormatDateTime(value, picture);
-    } else if (uiElem->findFirstChildElement("numericEdit")) {
-      formattedValue = pictureFormatNumber(value, picture);
-    } else if (uiElem->findFirstChildElement("textEdit")) {
-      formattedValue = pictureFormatText(value, picture);
-    } else {
-      formattedValue = value->copy();
-    }
-  } else {
-    formattedValue = value->copy();
-  }
-
-  maxChars = 0;
-  if ((valueElem = xml->findFirstChildElement("value")) &&
-      (textElem = valueElem->findFirstChildElement("text")) &&
-      (attr = textElem->findAttr("maxChars"))) {
-    maxChars = atoi(attr->getValue()->getCString());
-  }
-
-  multiLine = gFalse;
-  combCells = 0;
-  if (uiElem &&
-      (textEditElem = uiElem->findFirstChildElement("textEdit"))) {
-    if ((attr = textEditElem->findAttr("multiLine")) &&
-	!attr->getValue()->cmp("1")) {
-      multiLine = gTrue;
-    }
-    if ((combElem = textEditElem->findFirstChildElement("comb"))) {
-      if ((attr = combElem->findAttr("numberOfCells"))) {
-	combCells = atoi(attr->getValue()->getCString());
-      } else {
-	combCells = maxChars;
-      }
-    }
-  }
-	
-  fontName = NULL;
-  fontSize = 10;
-  bold = gFalse;
-  italic = gFalse;
-  if ((fontElem = xml->findFirstChildElement("font"))) {
-    if ((attr = fontElem->findAttr("typeface"))) {
-      fontName = attr->getValue()->copy();
-    }
-    if ((attr = fontElem->findAttr("weight"))) {
-      if (!attr->getValue()->cmp("bold")) {
-	bold = gTrue;
-      }
-    }
-    if ((attr = fontElem->findAttr("posture"))) {
-      if (!attr->getValue()->cmp("italic")) {
-	italic = gTrue;
-      }
-    }
-    if ((attr = fontElem->findAttr("size"))) {
-      fontSize = getMeasurement(attr, fontSize);
-    }
-  }
-  if (!fontName) {
-    fontName = new GString("Courier");
-  }
-
-  hAlign = xfaHAlignLeft;
-  vAlign = xfaVAlignTop;
-  if ((paraElem = xml->findFirstChildElement("para"))) {
-    if ((attr = paraElem->findAttr("hAlign"))) {
-      if (!attr->getValue()->cmp("left")) {
-	hAlign = xfaHAlignLeft;
-      } else if (!attr->getValue()->cmp("center")) {
-	hAlign = xfaHAlignCenter;
-      } else if (!attr->getValue()->cmp("right")) {
-	hAlign = xfaHAlignRight;
-      }
-      //~ other hAlign values (justify, justifyAll, radix) are
-      //~   currently unsupported
-    }
-    if ((attr = paraElem->findAttr("vAlign"))) {
-      if (!attr->getValue()->cmp("top")) {
-	vAlign = xfaVAlignTop;
-      } else if (!attr->getValue()->cmp("bottom")) {
-	vAlign = xfaVAlignBottom;
-      } else if (!attr->getValue()->cmp("middle")) {
-	vAlign = xfaVAlignMiddle;
-      }
-    }
-  }
-
-  drawText(formattedValue, multiLine, combCells,
-	   fontName, bold, italic, fontSize,
-	   hAlign, vAlign, 0, 0, w, h, gFalse, fontDict, appearBuf);
-  delete fontName;
-  delete formattedValue;
-}
-
-void XFAFormField::drawCheckButton(GfxFontDict *fontDict,
-				   double w, double h, int rot,
-				   GString *appearBuf) {
-  ZxElement *items;
-  ZxNode *firstItem, *firstItemVal;
-  const char *itemType;
-  const char *onValue;
-  GString *value;
-  double x, y;
-
-  // get the "on" value for this check button
-  itemType = "integer";
-  onValue = "1";
-  if ((items = xml->findFirstChildElement("items"))) {
-    if ((firstItem = items->getFirstChild())) {
-      if (firstItem->isElement("text") && firstItem->getFirstChild()) {
-	itemType = "text";
-	if ((firstItemVal = firstItem->getFirstChild())->isCharData()) {
-	  onValue = ((ZxCharData *)firstItemVal)->getData()->getCString();
-	}
-      } else if (firstItem->isElement("integer") &&
-		 firstItem->getFirstChild()) {
-	itemType = "integer";
-	if ((firstItemVal = firstItem->getFirstChild())->isCharData()) {
-	  onValue = ((ZxCharData *)firstItemVal)->getData()->getCString();
-	}
-      }
-    }
-  }
-
-  // compare its value to the "on" value
-  if (!(value = getFieldValue(itemType))) {
-    return;
-  }
-  if (value->cmp(onValue)) {
-    return;
-  }
-
-  // adjust shape to be square
-  x = y = 0;
-  if (w > h) {
-    x += 0.5 * (w - h);
-    w = h;
-  } else {
-    y += 0.5 * (h - w);
-    h = w;
-  }
-
-  // margins
-  x += 1.5;
-  w -= 3;
-  y += 1.5;
-  h -= 3;
-
-  //~ this should look at the "shape" and "mark" attributes
-
-  appearBuf->appendf("0.5 w {0:.4f} {1:.4f} m {2:.4f} {3:.4f} l {0:.4f} {3:.4f} m {2:.4f} {1:.4f} l S\n",
-		     x, y, x + w, y + h);
-}
-
-void XFAFormField::drawBarCode(GfxFontDict *fontDict,
-			       double w, double h, int rot,
-			       GString *appearBuf) {
-  ZxElement *uiElem, *barcodeElem, *fontElem;
-  ZxAttr *attr;
-  GString *value, *value2, *barcodeType, *textLocation, *fontName, *s1, *s2;
-  XFAVertAlign textAlign;
-  double wideNarrowRatio, moduleWidth, moduleHeight, fontSize;
-  double yText, wText, yBarcode, hBarcode, wNarrow, xx;
-  GBool doText;
-  int dataLength, errorCorrectionLevel, checksum;
-  GBool bold, italic;
-  char *p;
-  int i, j, c;
-
-  //--- get field value
-  if (!(value = getFieldValue("text"))) {
-    return;
-  }
-
-  //--- get font
-  fontName = NULL;
-  fontSize = 0.2 * h;
-  bold = gFalse;
-  italic = gFalse;
-  if ((fontElem = xml->findFirstChildElement("font"))) {
-    if ((attr = fontElem->findAttr("typeface"))) {
-      fontName = attr->getValue()->copy();
-    }
-    if ((attr = fontElem->findAttr("weight"))) {
-      if (!attr->getValue()->cmp("bold")) {
-	bold = gTrue;
-      }
-    }
-    if ((attr = fontElem->findAttr("posture"))) {
-      if (!attr->getValue()->cmp("italic")) {
-	italic = gTrue;
-      }
-    }
-    if ((attr = fontElem->findAttr("size"))) {
-      fontSize = getMeasurement(attr, fontSize);
-    }
-  }
-  if (!fontName) {
-    fontName = new GString("Courier");
-  }
-
-  //--- get field attributes
-  barcodeType = NULL;
-  wideNarrowRatio = 3;
-  moduleWidth = 0;
-  moduleHeight = 0;
-  dataLength = 0;
-  errorCorrectionLevel = 0;
-  textLocation = NULL;
-  if ((uiElem = xml->findFirstChildElement("ui")) &&
-      (barcodeElem = uiElem->findFirstChildElement("barcode"))) {
-    if ((attr = barcodeElem->findAttr("type"))) {
-      barcodeType = attr->getValue();
-    }
-    if ((attr = barcodeElem->findAttr("wideNarrowRatio"))) {
-      s1 = attr->getValue();
-      if ((p = strchr(s1->getCString(), ':'))) {
-	s2 = new GString(s1, 0, (int)(p - s1->getCString()));
-	wideNarrowRatio = atof(p + 1);
-	if (wideNarrowRatio == 0) {
-	  wideNarrowRatio = 1;
-	}
-	wideNarrowRatio = atof(s2->getCString()) / wideNarrowRatio;
-	delete s2;
-      } else {
-	wideNarrowRatio = atof(s1->getCString());
-      }
-    }
-    if ((attr = barcodeElem->findAttr("moduleWidth"))) {
-      moduleWidth = getMeasurement(attr, (0.25 / 25.4) * 72.0); // 0.25mm
-    }
-    if ((attr = barcodeElem->findAttr("moduleHeight"))) {
-      moduleHeight = getMeasurement(attr, (0.5 / 25.4) * 72.0); // 0.5mm
-    }
-    if ((attr = barcodeElem->findAttr("dataLength"))) {
-      dataLength = atoi(attr->getValue()->getCString());
-    }
-    if ((attr = barcodeElem->findAttr("errorCorrectionLevel"))) {
-      errorCorrectionLevel = atoi(attr->getValue()->getCString());
-    }
-    if ((attr = barcodeElem->findAttr("textLocation"))) {
-      textLocation = attr->getValue();
-    }
-  }
-  if (!barcodeType) {
-    error(errSyntaxError, -1, "Missing 'type' attribute in XFA barcode field");
-    delete fontName;
-    return;
-  }
-
-  //--- compute the embedded text type position
-  doText = gTrue;
-  yText = yBarcode = hBarcode = 0;
-  if (textLocation && !textLocation->cmp("above")) {
-    textAlign = xfaVAlignTop;
-    yText = h;
-    yBarcode = 0;
-    hBarcode = h - fontSize;
-  } else if (textLocation && !textLocation->cmp("belowEmbedded")) {
-    textAlign = xfaVAlignBottom;
-    yText = 0;
-    yBarcode = 0;
-    hBarcode = h;
-  } else if (textLocation && !textLocation->cmp("aboveEmbedded")) {
-    textAlign = xfaVAlignTop;
-    yText = h;
-    yBarcode = 0;
-    hBarcode = h;
-  } else if (textLocation && !textLocation->cmp("none")) {
-    textAlign = xfaVAlignBottom; // make gcc happy
-    doText = gFalse;
-  } else { // default is "below"
-    textAlign = xfaVAlignBottom;
-    yText = 0;
-    yBarcode = fontSize;
-    hBarcode = h - fontSize;
-  }
-  wText = w;
-
-  //--- remove extraneous start/stop chars
-  value2 = value->copy();
-  if (!barcodeType->cmp("code3Of9")) {
-    if (value2->getLength() >= 1 && value2->getChar(0) == '*') {
-      value2->del(0);
-    }
-    if (value2->getLength() >= 1 &&
-	value2->getChar(value2->getLength() - 1) == '*') {
-      value2->del(value2->getLength() - 1);
-    }
-  }
-
-  //--- draw the bar code
-  if (!barcodeType->cmp("code3Of9")) {
-    if (!dataLength) {
-      error(errSyntaxError, -1,
-	    "Missing 'dataLength' attribute in XFA barcode field");
-      goto err;
-    }
-    appearBuf->append("0 g\n");
-    wNarrow = w / ((7 + 3 * wideNarrowRatio) * (dataLength + 2));
-    xx = 0;
-    for (i = -1; i <= value2->getLength(); ++i) {
-      if (i < 0 || i >= value2->getLength()) {
-	c = '*';
-      } else {
-	c = value2->getChar(i) & 0x7f;
-      }
-      for (j = 0; j < 10; j += 2) {
-	appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n",
-			   xx, yBarcode,
-			   (code3Of9Data[c][j] ? wideNarrowRatio : 1) * wNarrow,
-			   hBarcode);
-	xx += ((code3Of9Data[c][j] ? wideNarrowRatio : 1) +
-	       (code3Of9Data[c][j+1] ? wideNarrowRatio : 1)) * wNarrow;
-      }
-    }
-    // center the text on the drawn barcode (not the max length barcode)
-    wText = (value2->getLength() + 2) * (7 + 3 * wideNarrowRatio) * wNarrow;
-  } else if (!barcodeType->cmp("code128B")) {
-    if (!dataLength) {
-      error(errSyntaxError, -1,
-	    "Missing 'dataLength' attribute in XFA barcode field");
-      goto err;
-    }
-    appearBuf->append("0 g\n");
-    wNarrow = w / (11 * (dataLength + 3) + 2);
-    xx = 0;
-    checksum = 0;
-    for (i = -1; i <= value2->getLength() + 1; ++i) {
-      if (i == -1) {
-	// start code B
-	c = 104;
-	checksum += c;
-      } else if (i == value2->getLength()) {
-	// checksum
-	c = checksum % 103;
-      } else if (i == value2->getLength() + 1) {
-	// stop code
-	c = 106;
-      } else {
-	c = value2->getChar(i) & 0xff;
-	if (c >= 32 && c <= 127) {
-	  c -= 32;
-	} else {
-	  c = 0;
-	}	  
-	checksum += (i + 1) * c;
-      }
-      for (j = 0; j < 6; j += 2) {
-	appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n",
-			   xx, yBarcode,
-			   code128Data[c][j] * wNarrow, hBarcode);
-	xx += (code128Data[c][j] + code128Data[c][j+1]) * wNarrow;
-      }
-    }
-    // final bar of the stop code
-    appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n",
-		       xx, yBarcode, 2 * wNarrow, hBarcode);
-    // center the text on the drawn barcode (not the max length barcode)
-    wText = (11 * (value2->getLength() + 3) + 2) * wNarrow;
-  } else if (!barcodeType->cmp("pdf417")) {
-    drawPDF417Barcode(w, h, moduleWidth, moduleHeight, errorCorrectionLevel,
-		      value2, appearBuf);
-    doText = gFalse;
-  } else {
-    error(errSyntaxError, -1,
-	  "Unimplemented barcode type '{0:t}' in XFA barcode field",
-	  barcodeType);
-  }
-  //~ add other barcode types here
-
-  //--- draw the embedded text
-  if (doText) {
-    appearBuf->append("0 g\n");
-    drawText(value2, gFalse, 0,
-	     fontName, bold, italic, fontSize,
-	     xfaHAlignCenter, textAlign, 0, yText, wText, h, gTrue,
-	     fontDict, appearBuf);
-  }
-
- err:
-  delete fontName;
-  delete value2;
-}
-
-Object *XFAFormField::getResources(Object *res) {
-  return xfaForm->resourceDict.copy(res);
-}
-
-double XFAFormField::getMeasurement(ZxAttr *attr, double defaultVal) {
-  GString *s;
-
-  if (!attr) {
-    return defaultVal;
-  }
-  s = attr->getValue();
-  return getMeasurement(s, 0);
-}
-
-double XFAFormField::getMeasurement(GString *s, int begin) {
-  double val, mul;
-  GBool neg;
-  int i;
-
-  i = begin;
-  neg = gFalse;
-  if (i < s->getLength() && s->getChar(i) == '+') {
-    ++i;
-  } else if (i < s->getLength() && s->getChar(i) == '-') {
-    neg = gTrue;
-    ++i;
-  }
-  val = 0;
-  while (i < s->getLength() && s->getChar(i) >= '0' && s->getChar(i) <= '9') {
-    val = val * 10 + s->getChar(i) - '0';
-    ++i;
-  }
-  if (i < s->getLength() && s->getChar(i) == '.') {
-    ++i;
-    mul = 0.1;
-    while (i < s->getLength() && s->getChar(i) >= '0' && s->getChar(i) <= '9') {
-      val += mul * (s->getChar(i) - '0');
-      mul *= 0.1;
-      ++i;
-    }
-  }
-  if (neg) {
-    val = -val;
-  }
-  if (i+1 < s->getLength()) {
-    if (s->getChar(i) == 'i' && s->getChar(i+1) == 'n') {
-      val *= 72;
-    } else if (s->getChar(i) == 'p' && s->getChar(i+1) == 't') {
-      // no change
-    } else if (s->getChar(i) == 'c' && s->getChar(i+1) == 'm') {
-      val *= 72 / 2.54;
-    } else if (s->getChar(i) == 'm' && s->getChar(i+1) == 'm') {
-      val *= 72 / 25.4;
-    } else {
-      // default to inches
-      val *= 72;
-    }
-  } else {
-    // default to inches
-    val *= 72;
-  }
-  return val;
-}
-
-GString *XFAFormField::getFieldValue(const char *valueChildType) {
-  ZxElement *valueElem, *datasets, *data, *formElem, *elem;
-  char *p;
-
-  // check the <datasets> packet
-  p = name->getCString();
-  if (xfaForm->xml->getRoot() && !strncmp(p, "form.",  5)) {
-    if ((datasets =
-	 xfaForm->xml->getRoot()->findFirstChildElement("xfa:datasets")) &&
-	(data = datasets->findFirstChildElement("xfa:data"))) {
-      elem = findFieldInDatasets(data, p + 5);
-      if (elem &&
-	  elem->getFirstChild() &&
-	  elem->getFirstChild()->isCharData() &&
-	  ((ZxCharData *)elem->getFirstChild())->getData()->getLength() > 0) {
-	return ((ZxCharData *)elem->getFirstChild())->getData();
-      }
-    }
-  }
-  if (exclGroupName) {
-    p = exclGroupName->getCString();
-    if (xfaForm->xml->getRoot() && !strncmp(p, "form.",  5)) {
-      if ((datasets =
-	   xfaForm->xml->getRoot()->findFirstChildElement("xfa:datasets")) &&
-	  (data = datasets->findFirstChildElement("xfa:data"))) {
-	elem = findFieldInDatasets(data, p + 5);
-	if (elem &&
-	    elem->getFirstChild() &&
-	    elem->getFirstChild()->isCharData() &&
-	    ((ZxCharData *)elem->getFirstChild())->getData()->getLength() > 0) {
-	  return ((ZxCharData *)elem->getFirstChild())->getData();
-	}
-      }
-    }
-  }
-
-  // check the <form> element
-  p = fullName->getCString();
-  if (xfaForm->xml->getRoot() && !strncmp(p, "form.",  5)) {
-    if ((formElem = xfaForm->xml->getRoot()->findFirstChildElement("form"))) {
-      elem = findFieldInFormElem(formElem, p + 5);
-      if (elem &&
-	  (valueElem = elem->findFirstChildElement("value")) &&
-	  (elem = valueElem->findFirstChildElement(valueChildType))) {
-	if (elem->getFirstChild() &&
-	    elem->getFirstChild()->isCharData() &&
-	    ((ZxCharData *)elem->getFirstChild())
-	    ->getData()->getLength() > 0) {
-	  return ((ZxCharData *)elem->getFirstChild())->getData();
-	}
-      }
-    }
-  }
-
-  // check the <value> element within the field
-  if ((valueElem = xml->findFirstChildElement("value")) &&
-      (elem = valueElem->findFirstChildElement(valueChildType))) {
-    if (elem->getFirstChild() &&
-	elem->getFirstChild()->isCharData() &&
-	((ZxCharData *)elem->getFirstChild())->getData()->getLength() > 0) {
-      return ((ZxCharData *)elem->getFirstChild())->getData();
-    }
-  }
-
-  return NULL;
-}
-
-ZxElement *XFAFormField::findFieldInDatasets(ZxElement *elem, char *partName) {
-  ZxNode *node;
-  ZxElement *result;
-  GString *nodeName;
-  char *next;
-  int curIdx, idx, n;
-
-  curIdx = 0;
-  for (node = elem->getFirstChild(); node; node = node->getNextChild()) {
-    if (node->isElement()) {
-      nodeName = ((ZxElement *)node)->getType();
-      n = nodeName->getLength();
-      if (!strncmp(partName, nodeName->getCString(), n)) { 
-	if (partName[n] == '[') {
-	  idx = atoi(partName + n + 1);
-	  if (idx == curIdx) {
-	    for (++n; partName[n] && partName[n-1] != ']'; ++n) ;
-	  } else {
-	    ++curIdx;
-	    continue;
-	  }
-	}
-	if (!partName[n]) {
-	  return (ZxElement *)node;
-	} else if (partName[n] == '.') {
-	  if ((result = findFieldInDatasets((ZxElement *)node,
-					    partName + n + 1))) {
-	    return result;
-	  }
-	  break;
-	}
-      }
-    }
-  }
-
-  // search for an "ancestor match"
-  if ((next = strchr(partName, '.'))) {
-    return findFieldInDatasets(elem, next + 1);
-  }
-
-  return NULL;
-}
-
-ZxElement *XFAFormField::findFieldInFormElem(ZxElement *elem, char *partName) {
-  ZxElement *elem2;
-  ZxNode *node;
-  ZxAttr *attr;
-  GString *nodeName;
-  int curIdx, idx, n;
-
-  curIdx = 0;
-  for (node = elem->getFirstChild(); node; node = node->getNextChild()) {
-    if ((node->isElement("subform") || node->isElement("field")) &&
-	((attr = ((ZxElement *)node)->findAttr("name")))) {
-      nodeName = attr->getValue();
-      n = nodeName->getLength();
-      if (!strncmp(partName, nodeName->getCString(), n)) { 
-	if (partName[n] == '[') {
-	  idx = atoi(partName + n + 1);
-	  if (idx == curIdx) {
-	    for (++n; partName[n] && partName[n-1] != ']'; ++n) ;
-	  } else {
-	    ++curIdx;
-	    continue;
-	  }
-	}
-	if (!partName[n]) {
-	  return (ZxElement *)node;
-	} else if (partName[n] == '.') {
-	  return findFieldInFormElem((ZxElement *)node, partName + n + 1);
-	}
-      }
-    } else if (node->isElement("subform")) {
-      if ((elem2 = findFieldInFormElem((ZxElement *)node, partName))) {
-	return elem2;
-      }
-    }
-  }
-  return NULL;
-}
-
-void XFAFormField::transform(int rot, double w, double h,
-			     double *wNew, double *hNew, GString *appearBuf) {
-  switch (rot) {
-  case 0:
-  default:
-    appearBuf->appendf("1 0 0 1 0 {0:.4f} cm\n", -h);
-    break;
-  case 90:
-    appearBuf->appendf("0 1 -1 0 {0:.4f} 0 cm\n", w);
-    *wNew = h;
-    *hNew = w;
-    break;
-  case 180:
-    appearBuf->appendf("-1 0 0 -1 {0:.4f} {1:.4f} cm\n", w, h);
-    *wNew = w;
-    *hNew = h;
-    break;
-  case 270:
-    appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", h);
-    *wNew = h;
-    *hNew = w;
-    break;
-  }
-}
-
-void XFAFormField::drawText(GString *text, GBool multiLine, int combCells,
-			    GString *fontName, GBool bold,
-			    GBool italic, double fontSize,
-			    XFAHorizAlign hAlign, XFAVertAlign vAlign,
-			    double x, double y, double w, double h,
-			    GBool whiteBackground,
-			    GfxFontDict *fontDict, GString *appearBuf) {
-  GString *text2;
-  GfxFont *font;
-  const char *fontTag;
-  GString *s;
-  Unicode u;
-  double yTop, xx, yy, tw, charWidth, lineHeight;
-  double ascent, descent, rectX, rectY, rectW, rectH, blkH;
-  int nLines, line, i, j, k, c, rectI;
-
-  // convert UTF-8 to Latin1
-  //~ this currently drops all non-Latin1 characters
-  text2 = new GString();
-  i = 0;
-  while (getUTF8(text, &i, &u)) {
-    if (u <= 0xff) {
-      text2->append((char)u);
-    } else {
-      text2->append('?');
-    }
-  }
-
-  // find the font
-  if ((font = findFont(fontDict, fontName, bold, italic))) {
-    fontTag = font->getTag()->getCString();
-    ascent = font->getAscent() * fontSize;
-    descent = font->getDescent() * fontSize;
-  } else {
-    error(errSyntaxError, -1, "Couldn't find a font for '{0:t}', {1:s}, {2:s} used in XFA field",
-	  fontName, bold ? "bold" : "non-bold",
-	  italic ? "italic" : "non-italic");
-    fontTag = "xpdf_default_font";
-    ascent = 0.718 * fontSize;
-    descent = -0.207 * fontSize;
-  }
-
-  // setup
-  rectW = rectH = 0;
-  rectI = appearBuf->getLength();
-  appearBuf->append("BT\n");
-  appearBuf->appendf("/{0:s} {1:.2f} Tf\n", fontTag, fontSize);
-
-  // multi-line text
-  if (multiLine) {
-
-    // compute line height
-    lineHeight = 1.2 * fontSize;
-
-    // handle bottom/middle alignment
-    if (vAlign == xfaVAlignBottom || vAlign == xfaVAlignMiddle) {
-      nLines = 0;
-      i = 0;
-      while (i < text2->getLength()) {
-	getNextLine(text2, i, font, fontSize, w, &j, &tw, &k);
-	++nLines;
-	i = k;
-      }
-      blkH = nLines * lineHeight - (lineHeight - fontSize);
-      if (vAlign == xfaVAlignBottom) {
-	yTop = y + blkH;
-      } else {
-	yTop = y + 0.5 * (h + blkH);
-      }
-      if (yTop > y + h) {
-	yTop = y + h;
-      }
-    } else {
-      yTop = y + h;
-    }
-
-    // write a series of lines of text
-    line = 0;
-    i = 0;
-    while (i < text2->getLength()) {
-
-      getNextLine(text2, i, font, fontSize, w, &j, &tw, &k);
-      if (tw > rectW) {
-	rectW = tw;
-      }
-
-      // compute text start position
-      switch (hAlign) {
-      case xfaHAlignLeft:
-      default:
-	xx = x;
-	break;
-      case xfaHAlignCenter:
-	xx = x + 0.5 * (w - tw);
-	break;
-      case xfaHAlignRight:
-	xx = x + w - tw;
-	break;
-      }
-      yy = yTop - line * lineHeight - ascent;
-
-      // draw the line
-      appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", xx, yy);
-      appearBuf->append('(');
-      for (; i < j; ++i) {
-	c = text2->getChar(i) & 0xff;
-	if (c == '(' || c == ')' || c == '\\') {
-	  appearBuf->append('\\');
-	  appearBuf->append((char)c);
-	} else if (c < 0x20 || c >= 0x80) {
-	  appearBuf->appendf("\\{0:03o}", c);
-	} else {
-	  appearBuf->append((char)c);
-	}
-      }
-      appearBuf->append(") Tj\n");
-
-      // next line
-      i = k;
-      ++line;
-    }
-    rectH = line * lineHeight;
-    rectY = y + h - rectH;
- 
-  // comb formatting
-  } else if (combCells > 0) {
-
-    // compute comb spacing
-    tw = w / combCells;
-
-    // compute text start position
-    switch (hAlign) {
-    case xfaHAlignLeft:
-    default:
-      xx = x;
-      break;
-    case xfaHAlignCenter:
-      xx = x + (int)(0.5 * (combCells - text2->getLength())) * tw;
-      break;
-    case xfaHAlignRight:
-      xx = x + w - text2->getLength() * tw;
-      break;
-    }
-    rectW = text2->getLength() * tw;
-    switch (vAlign) {
-    case xfaVAlignTop:
-    default:
-      yy = y + h - ascent;
-      break;
-    case xfaVAlignMiddle:
-      yy = y + 0.5 * (h - (ascent + descent));
-      break;
-    case xfaVAlignBottom:
-      yy = y - descent;
-      break;
-    }
-    rectY = yy + descent;
-    rectH = ascent - descent;
-
-    // write the text string
-    for (i = 0; i < text2->getLength(); ++i) {
-      c = text2->getChar(i) & 0xff;
-      if (font && !font->isCIDFont()) {
-	charWidth = fontSize * ((Gfx8BitFont *)font)->getWidth((Guchar)c);
-	appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n",
-			   xx + i * tw + 0.5 * (tw - charWidth), yy);
-      } else {
-	appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n",
-			   xx + i * tw, yy);
-      }
-      appearBuf->append('(');
-      if (c == '(' || c == ')' || c == '\\') {
-	appearBuf->append('\\');
-	appearBuf->append((char)c);
-      } else if (c < 0x20 || c >= 0x80) {
-	appearBuf->appendf("{0:.4f} 0 Td\n", w);
-      } else {
-	appearBuf->append((char)c);
-      }
-      appearBuf->append(") Tj\n");
-    }
-
-  // regular (non-comb) formatting
-  } else {
-
-    // compute string width
-    if (font && !font->isCIDFont()) {
-      tw = 0;
-      for (i = 0; i < text2->getLength(); ++i) {
-	tw += ((Gfx8BitFont *)font)->getWidth(text2->getChar(i));
-      }
-    } else {
-      // otherwise, make a crude estimate
-      tw = text2->getLength() * 0.5;
-    }
-    tw *= fontSize;
-    rectW = tw;
-
-    // compute text start position
-    switch (hAlign) {
-    case xfaHAlignLeft:
-    default:
-      xx = x;
-      break;
-    case xfaHAlignCenter:
-      xx = x + 0.5 * (w - tw);
-      break;
-    case xfaHAlignRight:
-      xx = x + w - tw;
-      break;
-    }
-    switch (vAlign) {
-    case xfaVAlignTop:
-    default:
-      yy = y + h - ascent;
-      break;
-    case xfaVAlignMiddle:
-      yy = y + 0.5 * (h - (ascent + descent));
-      break;
-    case xfaVAlignBottom:
-      yy = y - descent;
-      break;
-    }
-    rectY = yy + descent;
-    rectH = ascent - descent;
-    appearBuf->appendf("{0:.4f} {1:.4f} Td\n", xx, yy);
-
-    // write the text string
-    appearBuf->append('(');
-    for (i = 0; i < text2->getLength(); ++i) {
-      c = text2->getChar(i) & 0xff;
-      if (c == '(' || c == ')' || c == '\\') {
-	appearBuf->append('\\');
-	appearBuf->append((char)c);
-      } else if (c < 0x20 || c >= 0x80) {
-	appearBuf->appendf("\\{0:03o}", c);
-      } else {
-	appearBuf->append((char)c);
-      }
-    }
-    appearBuf->append(") Tj\n");
-  }
-
-  // cleanup
-  appearBuf->append("ET\n");
-
-  // draw a white rectangle behind the text
-  if (whiteBackground) {
-    switch (hAlign) {
-    case xfaHAlignLeft:
-    default:
-      rectX = x;
-      break;
-    case xfaHAlignCenter:
-      rectX = x + 0.5 * (w - rectW);
-      break;
-    case xfaHAlignRight:
-      rectX = x + w - rectW;
-      break;
-    }
-    rectX -= 0.25 * fontSize;
-    rectW += 0.5 * fontSize;
-    rectY -= 0.1 * fontSize;
-    rectH += 0.2 * fontSize;
-    s = GString::format("q 1 g {0:.4f} {1:.4f} {2:.4f} {3:.4f} re f Q\n",
-			rectX, rectY, rectW, rectH);
-    appearBuf->insert(rectI, s);
-    delete s;
-  }
-
-  delete text2;
-}
-
-// Searches <fontDict> for a font matching(<fontName>, <bold>,
-// <italic>).
-GfxFont *XFAFormField::findFont(GfxFontDict *fontDict, GString *fontName,
-				GBool bold, GBool italic) {
-  GString *reqName, *testName;
-  GfxFont *font;
-  GBool foundName, foundBold, foundItalic;
-  char *p;
-  char c;
-  int i, j;
-
-  if (!fontDict) {
-    return NULL;
-  }
-
-  reqName = new GString();
-  for (i = 0; i < fontName->getLength(); ++i) {
-    c = fontName->getChar(i);
-    if (c != ' ') {
-      reqName->append(c);
-    }
-  }
-
-  for (i = 0; i < fontDict->getNumFonts(); ++i) {
-    font = fontDict->getFont(i);
-    if (!font || !font->getName()) {
-      continue;
-    }
-    testName = new GString();
-    for (j = 0; j < font->getName()->getLength(); ++j) {
-      c = font->getName()->getChar(j);
-      if (c != ' ') {
-	testName->append(c);
-      }
-    }
-    foundName = foundBold = foundItalic = gFalse;
-    for (p = testName->getCString(); *p; ++p) {
-      if (!strncasecmp(p, reqName->getCString(), reqName->getLength())) {
-	foundName = gTrue;
-      }
-      if (!strncasecmp(p, "bold", 4)) {
-	foundBold = gTrue;
-      }
-      if (!strncasecmp(p, "italic", 6) || !strncasecmp(p, "oblique", 7)) {
-	foundItalic = gTrue;
-      }
-    }
-    delete testName;
-    if (foundName && foundBold == bold && foundItalic == italic) {
-      delete reqName;
-      return font;
-    }
-  }
-
-  delete reqName;
-  return NULL;
-}
-
-// Searches the font resource dict for a font matching(<name>, <bold>,
-// <italic>), and returns the font object ID.  This does not require a
-// GfxFontDict, and it does not do any parsing of font objects beyond
-// looking at BaseFont.
-Ref XFAFormField::findFontName(GString *name, GBool bold, GBool italic) {
-  Object fontDictObj, fontObj, baseFontObj, fontRef;
-  Ref fontID;
-  GString *reqName, *testName;
-  GBool foundName, foundBold, foundItalic;
-  char *p;
-  char c;
-  int i;
-
-  fontID.num = fontID.gen = -1;
-
-  // remove space chars from the requested name
-  reqName = new GString();
-  for (i = 0; i < name->getLength(); ++i) {
-    c = name->getChar(i);
-    if (c != ' ') {
-      reqName->append(c);
-    }
-  }
-
-  if (xfaForm->resourceDict.isDict()) {
-    if (xfaForm->resourceDict.dictLookup("Font", &fontDictObj)->isDict()) {
-      for (i = 0; i < fontDictObj.dictGetLength() && fontID.num < 0; ++i) {
-	fontDictObj.dictGetVal(i, &fontObj);
-	if (fontObj.dictLookup("BaseFont", &baseFontObj)->isName()) {
-
-	  // remove space chars from the font name
-	  testName = new GString();
-	  for (p = baseFontObj.getName(); *p; ++p) {
-	    if (*p != ' ') {
-	      testName->append(p);
-	    }
-	  }
-
-	  // compare the names
-	  foundName = foundBold = foundItalic = gFalse;
-	  for (p = testName->getCString(); *p; ++p) {
-	    if (!strncasecmp(p, reqName->getCString(), reqName->getLength())) {
-	      foundName = gTrue;
-	    }
-	    if (!strncasecmp(p, "bold", 4)) {
-	      foundBold = gTrue;
-	    }
-	    if (!strncasecmp(p, "italic", 6) || !strncasecmp(p, "oblique", 7)) {
-	      foundItalic = gTrue;
-	    }
-	  }
-	  delete testName;
-	  if (foundName && foundBold == bold && foundItalic == italic) {
-	    if (fontObj.dictGetValNF(i, &fontRef)) {
-	      fontID = fontRef.getRef();
-	    }
-	    fontRef.free();
-	  }
-	}
-
-	baseFontObj.free();
-	fontObj.free();
-      }
-    }
-    fontDictObj.free();
-  }
-  delete reqName;
-
-  return fontID;
-}
-
-// 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 XFAFormField::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((Guchar)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;
-}
-
-//------------------------------------------------------------------------
-// 'picture' formatting
-//------------------------------------------------------------------------
-
-class XFAPictureNode {
-public:
-  virtual ~XFAPictureNode() {}
-  virtual GBool isLiteral() { return gFalse; }
-  virtual GBool isSign() { return gFalse; }
-  virtual GBool isDigit() { return gFalse; }
-  virtual GBool isDecPt() { return gFalse; }
-  virtual GBool isSeparator() { return gFalse; }
-  virtual GBool isYear() { return gFalse; }
-  virtual GBool isMonth() { return gFalse; }
-  virtual GBool isDay() { return gFalse; }
-  virtual GBool isHour() { return gFalse; }
-  virtual GBool isMinute() { return gFalse; }
-  virtual GBool isSecond() { return gFalse; }
-  virtual GBool isChar() { return gFalse; }
-};
-
-class XFAPictureLiteral: public XFAPictureNode {
-public:
-  XFAPictureLiteral(GString *sA) { s = sA; }
-  virtual ~XFAPictureLiteral() { delete s; }
-  virtual GBool isLiteral() { return gTrue; }
-  GString *s;
-};
-
-class XFAPictureSign: public XFAPictureNode {
-public:
-  XFAPictureSign(char cA) { c = cA; }
-  virtual GBool isSign() { return gTrue; }
-  char c;
-};
-
-class XFAPictureDigit: public XFAPictureNode {
-public:
-  XFAPictureDigit(char cA) { c = cA; pos = 0; }
-  virtual GBool isDigit() { return gTrue; }
-  char c;
-  int pos;
-};
-
-class XFAPictureDecPt: public XFAPictureNode {
-public:
-  XFAPictureDecPt() { }
-  virtual GBool isDecPt() { return gTrue; }
-};
-
-class XFAPictureSeparator: public XFAPictureNode {
-public:
-  XFAPictureSeparator() { }
-  virtual GBool isSeparator() { return gTrue; }
-};
-
-class XFAPictureYear: public XFAPictureNode {
-public:
-  XFAPictureYear(int nDigitsA) { nDigits = nDigitsA; }
-  virtual GBool isYear() { return gTrue; }
-  int nDigits;
-};
-
-class XFAPictureMonth: public XFAPictureNode {
-public:
-  XFAPictureMonth(int nDigitsA) { nDigits = nDigitsA; }
-  virtual GBool isMonth() { return gTrue; }
-  int nDigits;
-};
-
-class XFAPictureDay: public XFAPictureNode {
-public:
-  XFAPictureDay(int nDigitsA) { nDigits = nDigitsA; }
-  virtual GBool isDay() { return gTrue; }
-  int nDigits;
-};
-
-class XFAPictureHour: public XFAPictureNode {
-public:
-  XFAPictureHour(GBool is24HourA, int nDigitsA)
-    { is24Hour = is24HourA; nDigits = nDigitsA; }
-  virtual GBool isHour() { return gTrue; }
-  GBool is24Hour;
-  int nDigits;
-};
-
-class XFAPictureMinute: public XFAPictureNode {
-public:
-  XFAPictureMinute(int nDigitsA) { nDigits = nDigitsA; }
-  virtual GBool isMinute() { return gTrue; }
-  int nDigits;
-};
-
-class XFAPictureSecond: public XFAPictureNode {
-public:
-  XFAPictureSecond(int nDigitsA) { nDigits = nDigitsA; }
-  virtual GBool isSecond() { return gTrue; }
-  int nDigits;
-};
-
-class XFAPictureChar: public XFAPictureNode {
-public:
-  XFAPictureChar() {}
-  virtual GBool isChar() { return gTrue; }
-};
-
-GString *XFAFormField::pictureFormatDateTime(GString *value, GString *picture) {
-  GList *pic;
-  XFAPictureNode *node;
-  GString *ret, *s;
-  char c;
-  int year, month, day, hour, min, sec;
-  int len, picStart, picEnd, u, n, i, j;
-
-  len = value->getLength();
-  if (len == 0) {
-    return value->copy();
-  }
-
-  //--- parse the value
-
-  // expected format is yyyy(-mm(-dd)?)?Thh(:mm(:ss)?)?
-  // where:
-  // - the '-'s and ':'s are optional
-  // - the 'T' is literal
-  // - we're ignoring optional time zone info at the end
-  // (if the value is not in this canonical format, we just punt and
-  // return the value string)
-  //~ another option would be to parse the value following the
-  //~   <ui><picture> element
-  year = month = day = hour = min = sec = 0;
-  i = 0;
-  if (!(i + 4 <= len && isValidInt(value, i, 4))) {
-    return value->copy();
-  }
-  year = convertInt(value, i, 4);
-  i += 4;
-  if (i < len && value->getChar(i) == '-') {
-    ++i;
-  }
-  if (i + 2 <= len && isValidInt(value, i, 2)) {
-    month = convertInt(value, i, 2);
-    i += 2;
-    if (i < len && value->getChar(i) == '-') {
-      ++i;
-    }
-    if (i + 2 <= len && isValidInt(value, i, 2)) {
-      day = convertInt(value, i, 2);
-      i += 2;
-    }
-  }
-  if (i < len) {
-    if (value->getChar(i) != 'T') {
-      return value->copy();
-    }
-    ++i;
-    if (!(i + 2 <= len && isValidInt(value, i, 2))) {
-      return value->copy();
-    }
-    hour = convertInt(value, i, 2);
-    i += 2;
-    if (i < len && value->getChar(i) == ':') {
-      ++i;
-    }
-    if (i + 2 <= len && isValidInt(value, i, 2)) {
-      min = convertInt(value, i, 2);
-      i += 2;
-      if (i < len && value->getChar(i) == ':') {
-	++i;
-      }
-      if (i + 2 <= len && isValidInt(value, i, 2)) {
-	sec = convertInt(value, i, 2);
-	i += 2;
-      }
-    }
-  }
-  if (i < len) {
-    return value->copy();
-  }
-
-  //--- skip the category and locale in the picture
-
-  picStart = 0;
-  picEnd = picture->getLength();
-  for (i = 0; i < picture->getLength(); ++i) {
-    c = picture->getChar(i);
-    if (c == '{') {
-      picStart = i + 1;
-      for (picEnd = picStart;
-	   picEnd < picture->getLength() && picture->getChar(picEnd) != '}';
-	   ++picEnd) ;
-      break;
-    } else if (!((c >= 'a' && c <= 'z') ||
-		 (c >= 'A' && c <= 'Z') ||
-		 c == '(' ||
-		 c == ')')) {
-      break;
-    }
-  }
-
-  //--- parse the picture
-
-  pic = new GList();
-  i = picStart;
-  while (i < picEnd) {
-    c = picture->getChar(i);
-    ++i;
-    if (c == '\'') {
-      s = new GString();
-      while (i < picEnd) {
-	c = picture->getChar(i);
-	if (c == '\'') {
-	  ++i;
-	  if (i < picEnd && picture->getChar(i) == '\'') {
-	    s->append('\'');
-	    ++i;
-	  } else {
-	    break;
-	  }
-	} else if (c == '\\') {
-	  ++i;
-	  if (i == picEnd) {
-	    break;
-	  }
-	  c = picture->getChar(i);
-	  ++i;
-	  if (c == 'u' && i+4 <= picEnd) {
-	    u = 0;
-	    for (j = 0; j < 4; ++j, ++i) {
-	      c = picture->getChar(i);
-	      u <<= 4;
-	      if (c >= '0' && c <= '9') {
-		u += c - '0';
-	      } else if (c >= 'a' && c <= 'f') {
-		u += c - 'a' + 10;
-	      } else if (c >= 'A' && c <= 'F') {
-		u += c - 'A' + 10;
-	      }
-	    }
-	    //~ this should convert to UTF-8 (?)
-	    if (u <= 0xff) {
-	      s->append((char)u);
-	    }
-	  } else {
-	    s->append(c);
-	  }
-	} else {
-	  s->append(c);
-	}
-      }
-      pic->append(new XFAPictureLiteral(s));
-    } else if (c == ',' || c == '-' || c == ':' ||
-	       c == '/' || c == '.' || c == ' ') {
-      s = new GString();
-      s->append(c);
-      pic->append(new XFAPictureLiteral(s));
-    } else if (c == 'Y') {
-      for (n = 1; n < 4 && i < picEnd && picture->getChar(i) == 'Y'; ++n, ++i) ;
-      pic->append(new XFAPictureYear(n));
-    } else if (c == 'M') {
-      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'M'; ++n, ++i) ;
-      pic->append(new XFAPictureMonth(n));
-    } else if (c == 'D') {
-      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'D'; ++n, ++i) ;
-      pic->append(new XFAPictureDay(n));
-    } else if (c == 'h') {
-      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'h'; ++n, ++i) ;
-      pic->append(new XFAPictureHour(gFalse, n));
-    } else if (c == 'H') {
-      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'H'; ++n, ++i) ;
-      pic->append(new XFAPictureHour(gTrue, n));
-    } else if (c == 'M') {
-      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'M'; ++n, ++i) ;
-      pic->append(new XFAPictureMinute(n));
-    } else if (c == 'S') {
-      for (n = 1; n < 2 && i < picEnd && picture->getChar(i) == 'S'; ++n, ++i) ;
-      pic->append(new XFAPictureSecond(n));
-    }
-  }
-
-  //--- generate formatted text
-
-  ret = new GString();
-  for (i = 0; i < pic->getLength(); ++i) {
-    node = (XFAPictureNode *)pic->get(i);
-    if (node->isLiteral()) {
-      ret->append(((XFAPictureLiteral *)node)->s);
-    } else if (node->isYear()) {
-      if (((XFAPictureYear *)node)->nDigits == 2) {
-	if (year >= 1930 && year < 2030) {
-	  ret->appendf("{0:02d}", year % 100);
-	} else {
-	  ret->append("??");
-	}
-      } else {
-	ret->appendf("{0:04d}", year);
-      }
-    } else if (node->isMonth()) {
-      if (((XFAPictureMonth *)node)->nDigits == 1) {
-	ret->appendf("{0:d}", month);
-      } else {
-	ret->appendf("{0:02d}", month);
-      }
-    } else if (node->isDay()) {
-      if (((XFAPictureDay *)node)->nDigits == 1) {
-	ret->appendf("{0:d}", day);
-      } else {
-	ret->appendf("{0:02d}", day);
-      }
-    } else if (node->isHour()) {
-      if (((XFAPictureHour *)node)->is24Hour) {
-	n = hour;
-      } else {
-	n = hour % 12;
-	if (n == 0) {
-	  n = 12;
-	}
-      }
-      if (((XFAPictureHour *)node)->nDigits == 1) {
-	ret->appendf("{0:d}", n);
-      } else {
-	ret->appendf("{0:02d}", n);
-      }
-    } else if (node->isMinute()) {
-      if (((XFAPictureMinute *)node)->nDigits == 1) {
-	ret->appendf("{0:d}", min);
-      } else {
-	ret->appendf("{0:02d}", min);
-      }
-    } else if (node->isSecond()) {
-      if (((XFAPictureSecond *)node)->nDigits == 1) {
-	ret->appendf("{0:d}", sec);
-      } else {
-	ret->appendf("{0:02d}", sec);
-      }
-    }
-  }
-  deleteGList(pic, XFAPictureNode);
-
-  return ret;
-}
-
-GString *XFAFormField::pictureFormatNumber(GString *value, GString *picture) {
-  GList *pic;
-  XFAPictureNode *node;
-  GString *ret, *s;
-  GBool neg, haveDigits;
-  char c;
-  int start, decPt, trailingZero, len;
-  int picStart, picEnd, u, pos, i, j;
-
-  len = value->getLength();
-  if (len == 0) {
-    return value->copy();
-  }
-
-  //--- parse the value
-
-  // -nnnn.nnnn0000
-  //  ^   ^    ^   ^
-  //  |   |    |   +-- len
-  //  |   |    +------ trailingZero
-  //  |   +----------- decPt
-  //  +--------------- start
-  start = 0;
-  neg = gFalse;
-  if (value->getChar(start) == '-') {
-    neg = gTrue;
-    ++start;
-  } else if (value->getChar(start) == '+') {
-    ++start;
-  }
-  for (decPt = start; decPt < len && value->getChar(decPt) != '.'; ++decPt) ;
-  for (trailingZero = len;
-       trailingZero > decPt && value->getChar(trailingZero - 1) == '0';
-       --trailingZero) ;
-
-  //--- skip the category and locale in the picture
-
-  picStart = 0;
-  picEnd = picture->getLength();
-  for (i = 0; i < picture->getLength(); ++i) {
-    c = picture->getChar(i);
-    if (c == '{') {
-      picStart = i + 1;
-      for (picEnd = picStart;
-	   picEnd < picture->getLength() && picture->getChar(picEnd) != '}';
-	   ++picEnd) ;
-      break;
-    } else if (!((c >= 'a' && c <= 'z') ||
-		 (c >= 'A' && c <= 'Z') ||
-		 c == '(' ||
-		 c == ')')) {
-      break;
-    }
-  }
-
-  //--- parse the picture
-
-  pic = new GList();
-  i = picStart;
-  while (i < picEnd) {
-    c = picture->getChar(i);
-    ++i;
-    if (c == '\'') {
-      s = new GString();
-      while (i < picEnd) {
-	c = picture->getChar(i);
-	if (c == '\'') {
-	  ++i;
-	  if (i < picEnd && picture->getChar(i) == '\'') {
-	    s->append('\'');
-	    ++i;
-	  } else {
-	    break;
-	  }
-	} else if (c == '\\') {
-	  ++i;
-	  if (i == picEnd) {
-	    break;
-	  }
-	  c = picture->getChar(i);
-	  ++i;
-	  if (c == 'u' && i+4 <= picEnd) {
-	    u = 0;
-	    for (j = 0; j < 4; ++j, ++i) {
-	      c = picture->getChar(i);
-	      u <<= 4;
-	      if (c >= '0' && c <= '9') {
-		u += c - '0';
-	      } else if (c >= 'a' && c <= 'F') {
-		u += c - 'a' + 10;
-	      } else if (c >= 'A' && c <= 'F') {
-		u += c - 'A' + 10;
-	      }
-	    }
-	    //~ this should convert to UTF-8 (?)
-	    if (u <= 0xff) {
-	      s->append((char)u);
-	    }
-	  } else {
-	    s->append(c);
-	  }
-	} else {
-	  s->append(c);
-	  ++i;
-	}
-      }
-      pic->append(new XFAPictureLiteral(s));
-    } else if (c == '-' || c == ':' || c == '/' || c == ' ') {
-      s = new GString();
-      s->append(c);
-      pic->append(new XFAPictureLiteral(s));
-    } else if (c == 's' || c == 'S') {
-      pic->append(new XFAPictureSign(c));
-    } else if (c == 'Z' || c == 'z' || c == '8' || c == '9') {
-      pic->append(new XFAPictureDigit(c));
-    } else if (c == '.') {
-      pic->append(new XFAPictureDecPt());
-    } else if (c == ',') {
-      pic->append(new XFAPictureSeparator());
-    }
-  }
-  for (i = 0; i < pic->getLength(); ++i) {
-    node = (XFAPictureNode *)pic->get(i);
-    if (node->isDecPt()) {
-      break;
-    }
-  }
-  pos = 0;
-  for (j = i - 1; j >= 0; --j) {
-    node = (XFAPictureNode *)pic->get(j);
-    if (node->isDigit()) {
-      ((XFAPictureDigit *)node)->pos = pos;
-      ++pos;
-    }
-  }
-  pos = -1;
-  for (j = i + 1; j < pic->getLength(); ++j) {
-    node = (XFAPictureNode *)pic->get(j);
-    if (node->isDigit()) {
-      ((XFAPictureDigit *)node)->pos = pos;
-      --pos;
-    }
-  }
-
-  //--- generate formatted text
-
-  ret = new GString();
-  haveDigits = gFalse;
-  for (i = 0; i < pic->getLength(); ++i) {
-    node = (XFAPictureNode *)pic->get(i);
-    if (node->isLiteral()) {
-      ret->append(((XFAPictureLiteral *)node)->s);
-    } else if (node->isSign()) {
-      if (((XFAPictureSign *)node)->c == 'S') {
-	ret->append(neg ? '-' : ' ');
-      } else {
-	if (neg) {
-	  ret->append('-');
-	}
-      }
-    } else if (node->isDigit()) {
-      pos = ((XFAPictureDigit *)node)->pos;
-      c = ((XFAPictureDigit *)node)->c;
-      if (pos >= 0 && pos < decPt - start) {
-	ret->append(value->getChar(decPt - 1 - pos));
-	haveDigits = gTrue;
-      } else if (pos < 0 && -pos <= trailingZero - decPt - 1) {
-	ret->append(value->getChar(decPt - pos));
-	haveDigits = gTrue;
-      } else if (c == '8' &&
-		 pos < 0 &&
-		 -pos <= len - decPt - 1) {
-	ret->append('0');
-	haveDigits = gTrue;
-      } else if (c == '9') {
-	ret->append('0');
-	haveDigits = gTrue;
-      } else if (c == 'Z' && pos >= 0) {
-	ret->append(' ');
-      }
-    } else if (node->isDecPt()) {
-      if (!(i+1 < pic->getLength() &&
-	    ((XFAPictureNode *)pic->get(i+1))->isDigit() &&
-	    ((XFAPictureDigit *)pic->get(i+1))->c == 'z') ||
-	  trailingZero > decPt + 1) {
-	ret->append('.');
-      }
-    } else if (node->isSeparator()) {
-      if (haveDigits) {
-	ret->append(',');
-      }
-    }
-  }
-  deleteGList(pic, XFAPictureNode);
-
-  return ret;
-}
-
-GString *XFAFormField::pictureFormatText(GString *value, GString *picture) {
-  GList *pic;
-  XFAPictureNode *node;
-  GString *ret, *s;
-  char c;
-  int len, picStart, picEnd, u, i, j;
-
-  len = value->getLength();
-  if (len == 0) {
-    return value->copy();
-  }
-
-  //--- skip the category and locale in the picture
-
-  picStart = 0;
-  picEnd = picture->getLength();
-  for (i = 0; i < picture->getLength(); ++i) {
-    c = picture->getChar(i);
-    if (c == '{') {
-      picStart = i + 1;
-      for (picEnd = picStart;
-	   picEnd < picture->getLength() && picture->getChar(picEnd) != '}';
-	   ++picEnd) ;
-      break;
-    } else if (!((c >= 'a' && c <= 'z') ||
-		 (c >= 'A' && c <= 'Z') ||
-		 c == '(' ||
-		 c == ')')) {
-      break;
-    }
-  }
-
-  //--- parse the picture
-
-  pic = new GList();
-  i = picStart;
-  while (i < picEnd) {
-    c = picture->getChar(i);
-    ++i;
-    if (c == '\'') {
-      s = new GString();
-      while (i < picEnd) {
-	c = picture->getChar(i);
-	if (c == '\'') {
-	  ++i;
-	  if (i < picEnd && picture->getChar(i) == '\'') {
-	    s->append('\'');
-	    ++i;
-	  } else {
-	    break;
-	  }
-	} else if (c == '\\') {
-	  ++i;
-	  if (i == picEnd) {
-	    break;
-	  }
-	  c = picture->getChar(i);
-	  ++i;
-	  if (c == 'u' && i+4 <= picEnd) {
-	    u = 0;
-	    for (j = 0; j < 4; ++j, ++i) {
-	      c = picture->getChar(i);
-	      u <<= 4;
-	      if (c >= '0' && c <= '9') {
-		u += c - '0';
-	      } else if (c >= 'a' && c <= 'F') {
-		u += c - 'a' + 10;
-	      } else if (c >= 'A' && c <= 'F') {
-		u += c - 'A' + 10;
-	      }
-	    }
-	    //~ this should convert to UTF-8 (?)
-	    if (u <= 0xff) {
-	      s->append((char)u);
-	    }
-	  } else {
-	    s->append(c);
-	  }
-	} else {
-	  s->append(c);
-	  ++i;
-	}
-      }
-      pic->append(new XFAPictureLiteral(s));
-    } else if (c == ',' || c == '-' || c == ':' ||
-	       c == '/' || c == '.' || c == ' ') {
-      s = new GString();
-      s->append(c);
-      pic->append(new XFAPictureLiteral(s));
-    } else if (c == 'A' || c == 'X' || c == 'O' || c == '0' || c == '9') {
-      pic->append(new XFAPictureChar());
-    }
-  }
-
-  //--- generate formatted text
-
-  ret = new GString();
-  j = 0;
-  for (i = 0; i < pic->getLength(); ++i) {
-    node = (XFAPictureNode *)pic->get(i);
-    if (node->isLiteral()) {
-      ret->append(((XFAPictureLiteral *)node)->s);
-    } else if (node->isChar()) {
-      // if there are more chars in the picture than in the value,
-      // Adobe renders the value as-is, without picture formatting
-      if (j >= value->getLength()) {
-	delete ret;
-	ret = value->copy();
-	break;
-      }
-      ret->append(value->getChar(j));
-      ++j;
-    }
-  }
-  deleteGList(pic, XFAPictureNode);
-
-  return ret;
-}
-
-GBool XFAFormField::isValidInt(GString *s, int start, int len) {
-  int i;
-
-  for (i = 0; i < len; ++i) {
-    if (!(start + i < s->getLength() &&
-	  s->getChar(start + i) >= '0' &&
-	  s->getChar(start + i) <= '9')) {
-      return gFalse;
-    }
-  }
-  return gTrue;
-}
-
-int XFAFormField::convertInt(GString *s, int start, int len) {
-  char c;
-  int x, i;
-
-  x = 0;
-  for (i = 0; i < len && start + i < s->getLength(); ++i) {
-    c = s->getChar(start + i);
-    if (c < '0' || c > '9') {
-      break;
-    }
-    x = x * 10 + (c - '0');
-  }
-  return x;
-}

Deleted: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAForm.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -1,181 +0,0 @@
-//========================================================================
-//
-// XFAForm.h
-//
-// Copyright 2012 Glyph & Cog, LLC
-//
-//========================================================================
-
-#ifndef XFAFORM_H
-#define XFAFORM_H
-
-#include <aconf.h>
-
-#ifdef USE_GCC_PRAGMAS
-#pragma interface
-#endif
-
-#include "Form.h"
-
-class GHash;
-class XFATableInfo;
-class ZxDoc;
-class ZxElement;
-class ZxAttr;
-
-//------------------------------------------------------------------------
-
-enum XFAHorizAlign {
-  xfaHAlignLeft,
-  xfaHAlignCenter,
-  xfaHAlignRight
-};
-
-enum XFAVertAlign {
-  xfaVAlignTop,
-  xfaVAlignBottom,
-  xfaVAlignMiddle
-};
-
-//------------------------------------------------------------------------
-
-class XFAForm: public Form {
-public:
-
-  static XFAForm *load(PDFDoc *docA, Catalog *catalog,
-		       Object *acroFormObj, Object *xfaObj);
-
-  virtual ~XFAForm();
-
-  virtual const char *getType() { return "XFA"; }
-
-  virtual void draw(int pageNum, Gfx *gfx, GBool printing);
-
-  virtual int getNumFields();
-  virtual FormField *getField(int idx);
-
-private:
-
-  XFAForm(PDFDoc *docA, int nPagesA, ZxDoc *xmlA,
-	  Object *resourceDictA, GBool fullXFAA);
-  void scanNode(ZxElement *elem,
-		GString *parentName, GString *parentFullName,
-		GBool inPageSet, XFATableInfo *tableInfo,
-		GHash *nameCount, GHash *nameIdx,
-		GHash *fullNameCount, GHash *fullNameIdx,
-		GString *exclGroupName, Catalog *catalog);
-  void scanNames(ZxElement *elem, GHash *nameCount);
-  void scanFullNames(ZxElement *elem, GHash *fullNameCount);
-  void scanField(ZxElement *elem, GString *name, GString *fullName,
-		 GString *exclGroupName, GBool inPageSet,
-		 XFATableInfo *tableInfo, int colSpan,
-		 Catalog *catalog);
-  void scanNonField(ZxElement *elem, GString *name, GString *fullName,
-		    GBool inPageSet,
-		    XFATableInfo *tableInfo, int colSpan,
-		    GHash *nameCount, GHash *nameIdx,
-		    GHash *fullNameCount, GHash *fullNameIdx,
-		    Catalog *catalog);
-  GString *getNodeName(ZxElement *elem);
-  GString *getNodeFullName(ZxElement *elem);
-  GBool nodeIsBindGlobal(ZxElement *elem);
-
-  ZxDoc *xml;
-  GList *fields;		// [XFAFormField]
-  Object resourceDict;
-  GBool fullXFA;		// true for "Full XFA", false for
-				//   "XFA Foreground"
-  int nPages;			// number of pages in PDF file
-  double *pageOffsetX,		// x,y offset for each page
-         *pageOffsetY;
-  int pageSetNPages;		// number of pages in pageSet element
-  int curPageNum;		// current page number - used by scanFields()
-  double curXOffset,		// current x,y offset - used by scanFields()
-         curYOffset;
-
-  friend class XFAFormField;
-};
-
-//------------------------------------------------------------------------
-
-class XFAFormField: public FormField {
-public:
-
-  XFAFormField(XFAForm *xfaFormA, ZxElement *xmlA,
-	       GString *nameA, GString *fullNameA, GString *exclGroupNameA,
-	       int pageNumA, double xOffsetA, double yOffsetA,
-	       double columnWidthA, double rowHeightA);
-
-  virtual ~XFAFormField();
-
-  virtual int getPageNum();
-  virtual const char *getType();
-  virtual Unicode *getName(int *length);
-  virtual Unicode *getValue(int *length);
-  virtual void getBBox(double *llx, double *lly, double *urx, double *ury);
-  virtual void getFont(Ref *fontID, double *fontSize);
-  virtual void getColor(double *red, double *green, double *blue);
-
-  virtual Object *getResources(Object *res);
-
-private:
-
-  Unicode *utf8ToUnicode(GString *s, int *length);
-  void draw(int pageNumA, Gfx *gfx, GBool printing, GfxFontDict *fontDict);
-  void getRectangle(double *xfaX, double *xfaY,
-		    double *xfaW, double *xfaH,
-		    double *pdfX, double *pdfY,
-		    double *pdfW, double *pdfH,
-		    int *pdfRot);
-  void drawTextEdit(GfxFontDict *fontDict,
-		    double w, double h, int rot,
-		    GString *appearBuf);
-  void drawCheckButton(GfxFontDict *fontDict,
-		       double w, double h, int rot,
-		       GString *appearBuf);
-  void drawBarCode(GfxFontDict *fontDict,
-		   double w, double h, int rot,
-		   GString *appearBuf);
-  static double getMeasurement(ZxAttr *attr, double defaultVal);
-  static double getMeasurement(GString *s, int begin);
-  GString *getFieldValue(const char *valueChildType);
-  ZxElement *findFieldInDatasets(ZxElement *elem, char *partName);
-  ZxElement *findFieldInFormElem(ZxElement *elem, char *partName);
-  void transform(int rot, double w, double h,
-		 double *wNew, double *hNew, GString *appearBuf);
-  void drawText(GString *text, GBool multiLine, int combCells,
-		GString *fontName, GBool bold,
-		GBool italic, double fontSize,
-		XFAHorizAlign hAlign, XFAVertAlign vAlign,
-		double x, double y, double w, double h,
-		GBool whiteBackground,
-		GfxFontDict *fontDict, GString *appearBuf);
-  GfxFont *findFont(GfxFontDict *fontDict, GString *fontName,
-		    GBool bold, GBool italic);
-  Ref findFontName(GString *name, GBool bold, GBool italic);
-  void getNextLine(GString *text, int start,
-		   GfxFont *font, double fontSize, double wMax,
-		   int *end, double *width, int *next);
-  GString *pictureFormatDateTime(GString *value, GString *picture);
-  GString *pictureFormatNumber(GString *value, GString *picture);
-  GString *pictureFormatText(GString *value, GString *picture);
-  GBool isValidInt(GString *s, int start, int len);
-  int convertInt(GString *s, int start, int len);
-
-  XFAForm *xfaForm;
-  ZxElement *xml;
-  GString *name;
-  GString *fullName;
-  GString *exclGroupName;
-  int pageNum;
-  double xOffset, yOffset;
-  double columnWidth;		// column width, if this field is in a
-				//   table; otherwise zero
-  double rowHeight;		// row height, if this field is in a
-				//   table; otherwise zero
-
-  friend class XFAForm;
-  friend class XFATableInfo;
-};
-
-#endif

Added: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.cc	                        (rev 0)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -0,0 +1,675 @@
+//========================================================================
+//
+// XFAScanner.cc
+//
+// Copyright 2020 Glyph & Cog, LLC
+//
+//========================================================================
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include "GString.h"
+#include "GHash.h"
+#include "Object.h"
+#include "Error.h"
+#include "Zoox.h"
+#include "XFAScanner.h"
+
+//------------------------------------------------------------------------
+
+// fields have two names:
+//
+// name:
+//   - nodes with bind=global set the index to 0 ("foo[0]") regardless
+//     of the number of nodes with the same name
+//   - nodes with bind=none are dropped from the name
+//   - <area> nodes are dropped from the name
+//   - used for field value lookup in <xfa:datasets>
+//
+// fullName:
+//   - all named nodes are treated the same, regardless of bind=global
+//     or bind=none
+//   - <area> nodes are included in the name, but don't reset the
+//     numbering (i.e., <area> nodes are "transparent" with respect to
+//     node numbering)
+//   - used for field value lookup in <form>
+//   - used for matching with AcroForm names
+//
+// Both names use indexes on all nodes, even if there's only one node
+// with the name -- this isn't correct for XFA naming, but it matches
+// the AcroForm behavior.
+
+//------------------------------------------------------------------------
+
+XFAFieldLayoutInfo::XFAFieldLayoutInfo(XFAFieldLayoutHAlign hAlignA,
+				       XFAFieldLayoutVAlign vAlignA) {
+  hAlign = hAlignA;
+  vAlign = vAlignA;
+}
+
+//------------------------------------------------------------------------
+
+XFAFieldPictureInfo::XFAFieldPictureInfo(XFAFieldPictureSubtype subtypeA,
+					 GString *formatA) {
+  subtype = subtypeA;
+  format = formatA;
+}
+
+XFAFieldPictureInfo::~XFAFieldPictureInfo() {
+  delete format;
+}
+
+//------------------------------------------------------------------------
+
+XFAFieldBarcodeInfo::XFAFieldBarcodeInfo(GString *barcodeTypeA,
+					 double wideNarrowRatioA,
+					 double moduleWidthA,
+					 double moduleHeightA,
+					 int dataLengthA,
+					 int errorCorrectionLevelA,
+					 GString *textLocationA) {
+  barcodeType = barcodeTypeA;
+  wideNarrowRatio = wideNarrowRatioA;
+  moduleWidth = moduleWidthA;
+  moduleHeight = moduleHeightA;
+  dataLength = dataLengthA;
+  errorCorrectionLevel = errorCorrectionLevelA;
+  textLocation = textLocationA;
+}
+
+XFAFieldBarcodeInfo::~XFAFieldBarcodeInfo() {
+  delete barcodeType;
+  delete textLocation;
+}
+
+//------------------------------------------------------------------------
+
+XFAField::XFAField(GString *nameA, GString *fullNameA, GString *valueA,
+		   XFAFieldLayoutInfo *layoutInfoA,
+		   XFAFieldPictureInfo *pictureInfoA,
+		   XFAFieldBarcodeInfo *barcodeInfoA)
+  : name(nameA)
+  , fullName(fullNameA)
+  , value(valueA)
+  , layoutInfo(layoutInfoA)
+  , pictureInfo(pictureInfoA)
+  , barcodeInfo(barcodeInfoA)
+{
+}
+
+XFAField::~XFAField() {
+  delete name;
+  delete fullName;
+  delete value;
+  delete layoutInfo;
+  delete pictureInfo;
+  delete barcodeInfo;
+}
+
+//------------------------------------------------------------------------
+
+XFAScanner *XFAScanner::load(Object *xfaObj) {
+  GString *xfaData = readXFAStreams(xfaObj);
+  if (!xfaData) {
+    return NULL;
+  }
+  ZxDoc *xml = ZxDoc::loadMem(xfaData->getCString(), xfaData->getLength());
+  delete xfaData;
+  if (!xml) {
+    error(errSyntaxError, -1, "Invalid XML in XFA form");
+    return NULL;
+  }
+
+  XFAScanner *scanner = new XFAScanner();
+
+  if (xml->getRoot()) {
+    GHash *formValues = scanner->scanFormValues(xml->getRoot());
+    ZxElement *dataElem = NULL;
+    ZxElement *datasets =
+        xml->getRoot()->findFirstChildElement("xfa:datasets");
+    if (datasets) {
+      dataElem = datasets->findFirstChildElement("xfa:data");
+    }
+    ZxElement *tmpl = xml->getRoot()->findFirstChildElement("template");
+    if (tmpl) {
+      scanner->scanNode(tmpl, NULL, NULL, NULL, NULL, NULL,
+			dataElem, formValues);
+    }
+    deleteGHash(formValues, GString);
+  }
+
+  delete xml;
+
+  return scanner;
+}
+
+XFAScanner::XFAScanner() {
+  fields = new GHash();
+}
+
+XFAScanner::~XFAScanner() {
+  deleteGHash(fields, XFAField);
+}
+
+XFAField *XFAScanner::findField(GString *acroFormFieldName) {
+  return (XFAField *)fields->lookup(acroFormFieldName);
+}
+
+GString *XFAScanner::readXFAStreams(Object *xfaObj) {
+  GString *data = new GString();
+  char buf[4096];
+  int n;
+  if (xfaObj->isStream()) {
+    xfaObj->streamReset();
+    while ((n = xfaObj->getStream()->getBlock(buf, sizeof(buf))) > 0) {
+      data->append(buf, n);
+    }
+  } else if (xfaObj->isArray()) {
+    for (int i = 1; i < xfaObj->arrayGetLength(); i += 2) {
+      Object obj;
+      if (!xfaObj->arrayGet(i, &obj)->isStream()) {
+	error(errSyntaxError, -1, "XFA array element is wrong type");
+	obj.free();
+	delete data;
+	return NULL;
+      }
+      obj.streamReset();
+      while ((n = obj.getStream()->getBlock(buf, sizeof(buf))) > 0) {
+	data->append(buf, n);
+      }
+      obj.free();
+    }
+  } else {
+    error(errSyntaxError, -1, "XFA object is wrong type");
+    return NULL;
+  }
+  return data;
+}
+
+GHash *XFAScanner::scanFormValues(ZxElement *xmlRoot) {
+  GHash *formValues = new GHash(gTrue);
+  ZxElement *formElem = xmlRoot->findFirstChildElement("form");
+  if (formElem) {
+    scanFormNode(formElem, NULL, formValues);
+  }
+  return formValues;
+}
+
+void XFAScanner::scanFormNode(ZxElement *elem, GString *fullName,
+			      GHash *formValues) {
+  GHash *fullNameIdx = new GHash();
+  for (ZxNode *node = elem->getFirstChild();
+       node;
+       node = node->getNextChild()) {
+    if (node->isElement("value")) {
+      if (fullName) {
+	ZxNode *child1Node = ((ZxElement *)node)->getFirstChild();
+	if (child1Node && child1Node->isElement()) {
+	  ZxNode *child2Node = ((ZxElement *)child1Node)->getFirstChild();
+	  if (child2Node && child2Node->isCharData()) {
+	    formValues->add(fullName->copy(),
+			    ((ZxCharData *)child2Node)->getData()->copy());
+	  }
+	}
+      }
+    } else if (node->isElement()) {
+      ZxAttr *nameAttr = ((ZxElement *)node)->findAttr("name");
+      if (nameAttr && (node->isElement("subform") ||
+		       node->isElement("field"))) {
+	GString *nodeName = nameAttr->getValue();
+	GString *childFullName;
+	if (fullName) {
+	  childFullName = GString::format("{0:t}.{1:t}", fullName, nodeName);
+	} else {
+	  childFullName = nodeName->copy();
+	}
+	int idx = fullNameIdx->lookupInt(nodeName);
+	childFullName->appendf("[{0:d}]", idx);
+	fullNameIdx->replace(nodeName, idx + 1);
+	scanFormNode((ZxElement *)node, childFullName, formValues);
+	delete childFullName;
+      } else if (node->isElement("subform")) {
+	scanFormNode((ZxElement *)node, fullName, formValues);
+      }
+    }
+  }
+  delete fullNameIdx;
+}
+
+void XFAScanner::scanNode(ZxElement *elem,
+			  GString *parentName, GString *parentFullName,
+			  GHash *nameIdx, GHash *fullNameIdx,
+			  GString *exclGroupName, ZxElement *dataElem,
+			  GHash *formValues) {
+  GString *nodeName = getNodeName(elem);
+  GHash *childNameIdx;
+  if (!nameIdx || nodeName) {
+    childNameIdx = new GHash();
+  } else {
+    childNameIdx = nameIdx;
+  }
+  GString *nodeFullName = getNodeFullName(elem);
+  GHash *childFullNameIdx;
+  if (!fullNameIdx || (nodeFullName && !elem->isElement("area"))) {
+    childFullNameIdx = new GHash();
+  } else {
+    childFullNameIdx = fullNameIdx;
+  }
+
+  GString *childName;
+  if (nodeName) {
+    if (parentName) {
+      childName = GString::format("{0:t}.{1:t}", parentName, nodeName);
+    } else {
+      childName = nodeName->copy();
+    }
+    int idx = nameIdx->lookupInt(nodeName);
+    nameIdx->replace(nodeName, idx + 1);
+    if (nodeIsBindGlobal(elem)) {
+      childName->appendf("[0]");
+    } else {
+      childName->appendf("[{0:d}]", idx);
+    }
+  } else {
+    childName = parentName;
+  }
+  GString *childFullName;
+  if (nodeFullName) {
+    if (parentFullName) {
+      childFullName = GString::format("{0:t}.{1:t}",
+				      parentFullName, nodeFullName);
+    } else {
+      childFullName = nodeFullName->copy();
+    }
+    int idx = fullNameIdx->lookupInt(nodeFullName);
+    fullNameIdx->replace(nodeFullName, idx + 1);
+    childFullName->appendf("[{0:d}]", idx);
+  } else {
+    childFullName = parentFullName;
+  }
+
+  if (elem->isElement("field")) {
+    scanField(elem, childName, childFullName, exclGroupName,
+	      dataElem, formValues);
+  } else {
+    GString *childExclGroupName;
+    if (elem->isElement("exclGroup")) {
+      childExclGroupName = childName;
+    } else {
+      childExclGroupName = NULL;
+    }
+    for (ZxNode *child = elem->getFirstChild();
+	 child;
+	 child = child->getNextChild()) {
+      if (child->isElement()) {
+	scanNode((ZxElement *)child, childName, childFullName,
+		 childNameIdx, childFullNameIdx, childExclGroupName,
+		 dataElem, formValues);
+      }
+    }
+  }
+
+  if (childName != parentName) {
+    delete childName;
+  }
+  if (childFullName != parentFullName) {
+    delete childFullName;
+  }
+  if (childNameIdx != nameIdx) {
+    delete childNameIdx;
+  }
+  if (childFullNameIdx != fullNameIdx) {
+    delete childFullNameIdx;
+  }
+}
+
+void XFAScanner::scanField(ZxElement *elem, GString *name, GString *fullName,
+			   GString *exclGroupName, ZxElement *dataElem,
+			   GHash *formValues) {
+  GString *value = getFieldValue(elem, name, fullName, exclGroupName,
+				 dataElem, formValues);
+  XFAFieldLayoutInfo *layoutInfo = getFieldLayoutInfo(elem);
+  XFAFieldPictureInfo *pictureInfo = getFieldPictureInfo(elem);
+  XFAFieldBarcodeInfo *barcodeInfo = getFieldBarcodeInfo(elem);
+  XFAField *field = new XFAField(name->copy(), fullName->copy(), value,
+				 layoutInfo, pictureInfo, barcodeInfo);
+  fields->add(field->fullName, field);
+}
+
+GString *XFAScanner::getFieldValue(ZxElement *elem, GString *name,
+				   GString *fullName, GString *exclGroupName,
+				   ZxElement *dataElem, GHash *formValues) {
+  GString *val = NULL;
+
+  //--- check the <xfa:datasets> packet
+  val = getDatasetsValue(name->getCString(), dataElem);
+  if (!val && exclGroupName) {
+    val = (GString *)getDatasetsValue(exclGroupName->getCString(), dataElem);
+  }
+
+  //--- check the <form> element
+  if (!val) {
+    val = (GString *)formValues->lookup(fullName);
+  }
+
+  //--- check the <value> element within the field
+  if (!val) {
+    ZxElement *valueElem = elem->findFirstChildElement("value");
+    if (valueElem) {
+      ZxNode *child1Node = valueElem->getFirstChild();
+      if (child1Node && child1Node->isElement()) {
+	ZxNode *child2Node = ((ZxElement *)child1Node)->getFirstChild();
+	if (child2Node && child2Node->isCharData()) {
+	  val = ((ZxCharData *)child2Node)->getData();
+	}
+      }
+    }
+  }
+
+  //--- get the checkbutton item value
+  GString *checkbuttonItem = NULL;
+  ZxElement *uiElem = elem->findFirstChildElement("ui");
+  if (uiElem) {
+    ZxNode *uiChild = uiElem->getFirstChild();
+    if (uiChild && uiChild->isElement("checkButton")) {
+      ZxElement *itemsElem = elem->findFirstChildElement("items");
+      if (itemsElem) {
+	ZxNode *node1 = itemsElem->getFirstChild();
+	if (node1 && node1->isElement()) {
+	  ZxNode *node2 = ((ZxElement *)node1)->getFirstChild();
+	  if (node2 && node2->isCharData()) {
+	    checkbuttonItem = ((ZxCharData *)node2)->getData();
+	  }
+	}
+      }
+    }
+  }
+  // convert XFA checkbutton value to AcroForm-style On/Off value
+  if (checkbuttonItem && val) {
+    if (val->cmp(checkbuttonItem)) {
+      val = new GString("Off");
+    } else {
+      val = new GString("On");
+    }
+  } else if (val) {
+    val = val->copy();
+  }
+
+  return val;
+}
+
+GString *XFAScanner::getDatasetsValue(char *partName, ZxElement *elem) {
+  if (!elem) {
+    return NULL;
+  }
+
+  // partName = xxxx[nn].yyyy----
+  char *p = strchr(partName, '[');
+  if (!p) {
+    return NULL;
+  }
+  int partLen = (int)(p - partName);
+  int idx = atoi(p + 1);
+  p = strchr(p + 1, '.');
+  if (p) {
+    ++p;
+  }
+
+  int curIdx = 0;
+  for (ZxNode *node = elem->getFirstChild();
+       node;
+       node = node->getNextChild()) {
+    if (!node->isElement()) {
+      continue;
+    }
+    GString *nodeName = ((ZxElement *)node)->getType();
+    if (nodeName->getLength() != partLen ||
+	strncmp(nodeName->getCString(), partName, partLen)) {
+      continue;
+    }
+    if (curIdx != idx) {
+      ++curIdx;
+      continue;
+    }
+    if (p) {
+      GString *val = getDatasetsValue(p, (ZxElement *)node);
+      if (val) {
+	return val;
+      }
+      break;
+    } else {
+      ZxNode *child = ((ZxElement *)node)->getFirstChild();
+      if (!child || !child->isCharData()) {
+	return NULL;
+      }
+      return ((ZxCharData *)child)->getData();
+    }
+  }
+
+  // search for an 'ancestor match'
+  if (p) {
+    return getDatasetsValue(p, elem);
+  }
+
+  return NULL;
+}
+
+XFAFieldLayoutInfo *XFAScanner::getFieldLayoutInfo(ZxElement *elem) {
+  ZxElement *paraElem = elem->findFirstChildElement("para");
+  if (!paraElem) {
+    return NULL;
+  }
+  XFAFieldLayoutHAlign hAlign = xfaFieldLayoutHAlignLeft;
+  ZxAttr *hAlignAttr = paraElem->findAttr("hAlign");
+  if (hAlignAttr) {
+    if (!hAlignAttr->getValue()->cmp("left")) {
+      hAlign = xfaFieldLayoutHAlignLeft;
+    } else if (!hAlignAttr->getValue()->cmp("center")) {
+      hAlign = xfaFieldLayoutHAlignCenter;
+    } else if (!hAlignAttr->getValue()->cmp("right")) {
+      hAlign = xfaFieldLayoutHAlignRight;
+    }
+  }
+  XFAFieldLayoutVAlign vAlign = xfaFieldLayoutVAlignTop;
+  ZxAttr *vAlignAttr = paraElem->findAttr("vAlign");
+  if (vAlignAttr) {
+    if (!vAlignAttr->getValue()->cmp("top")) {
+      vAlign = xfaFieldLayoutVAlignTop;
+    } else if (!vAlignAttr->getValue()->cmp("middle")) {
+      vAlign = xfaFieldLayoutVAlignMiddle;
+    } else if (!vAlignAttr->getValue()->cmp("bottom")) {
+      vAlign = xfaFieldLayoutVAlignBottom;
+    }
+  }
+  return new XFAFieldLayoutInfo(hAlign, vAlign);
+}
+
+XFAFieldPictureInfo *XFAScanner::getFieldPictureInfo(ZxElement *elem) {
+  ZxElement *uiElem = elem->findFirstChildElement("ui");
+  if (!uiElem) {
+    return NULL;
+  }
+  XFAFieldPictureSubtype subtype;
+  if (uiElem->findFirstChildElement("dateTimeEdit")) {
+    subtype = xfaFieldPictureDateTime;
+  } else if (uiElem->findFirstChildElement("numericEdit")) {
+    subtype = xfaFieldPictureNumeric;
+  } else if (uiElem->findFirstChildElement("textEdit")) {
+    subtype = xfaFieldPictureText;
+  } else {
+    return NULL;
+  }
+
+  ZxElement *formatElem, *pictureElem;
+  ZxNode *pictureChildNode;
+  if (!(formatElem = elem->findFirstChildElement("format")) ||
+      !(pictureElem = formatElem->findFirstChildElement("picture")) ||
+      !(pictureChildNode = pictureElem->getFirstChild()) ||
+      !pictureChildNode->isCharData()) {
+    return NULL;
+  }
+  GString *format = ((ZxCharData *)pictureChildNode)->getData()->copy();
+
+  return new XFAFieldPictureInfo(subtype, format);
+}
+
+XFAFieldBarcodeInfo *XFAScanner::getFieldBarcodeInfo(ZxElement *elem) {
+  ZxElement *uiElem, *barcodeElem;
+  if (!(uiElem = elem->findFirstChildElement("ui")) ||
+      !(barcodeElem = uiElem->findFirstChildElement("barcode"))) {
+    return NULL;
+  }
+
+  ZxAttr *attr;
+  if (!(attr = barcodeElem->findAttr("type"))) {
+    return NULL;
+  }
+  GString *barcodeType = attr->getValue()->copy();
+
+  double wideNarrowRatio = 3;
+  if ((attr = barcodeElem->findAttr("wideNarrowRatio"))) {
+    char *s = attr->getValue()->getCString();
+    char *colon = strchr(s, ':');
+    if (colon) {
+      GString *numStr = new GString(s, (int)(colon - s));
+      double num = atof(numStr->getCString());
+      delete numStr;
+      double den = atof(colon + 1);
+      if (den == 0) {
+	wideNarrowRatio = num;
+      } else {
+	wideNarrowRatio = num / den;
+      }
+    } else {
+      wideNarrowRatio = atof(s);
+    }
+  }
+
+  double moduleWidth = (0.25 / 25.4) * 72.0; // 0.25mm
+  if ((attr = barcodeElem->findAttr("moduleWidth"))) {
+    moduleWidth = getMeasurement(attr->getValue());
+  }
+
+  double moduleHeight = (5.0 / 25.4) * 72.0; // 5mm
+  if ((attr = barcodeElem->findAttr("moduleHeight"))) {
+    moduleHeight = getMeasurement(attr->getValue());
+  }
+
+  int dataLength = 0;
+  if ((attr = barcodeElem->findAttr("dataLength"))) {
+    dataLength = atoi(attr->getValue()->getCString());
+  }
+
+  int errorCorrectionLevel = 0;
+  if ((attr = barcodeElem->findAttr("errorCorrectionLevel"))) {
+    errorCorrectionLevel = atoi(attr->getValue()->getCString());
+  }
+
+  GString *textLocation;
+  if ((attr = barcodeElem->findAttr("textLocation"))) {
+    textLocation = attr->getValue()->copy();
+  } else {
+    textLocation = new GString("below");
+  }
+
+  return new XFAFieldBarcodeInfo(barcodeType, wideNarrowRatio,
+				 moduleWidth, moduleHeight, dataLength,
+				 errorCorrectionLevel, textLocation);
+}
+
+double XFAScanner::getMeasurement(GString *s) {
+  int i = 0;
+  GBool neg = gFalse;
+  if (i < s->getLength() && s->getChar(i) == '+') {
+    ++i;
+  } else if (i < s->getLength() && s->getChar(i) == '-') {
+    neg = gTrue;
+    ++i;
+  }
+  double val = 0;
+  while (i < s->getLength() && s->getChar(i) >= '0' && s->getChar(i) <= '9') {
+    val = val * 10 + s->getChar(i) - '0';
+    ++i;
+  }
+  if (i < s->getLength() && s->getChar(i) == '.') {
+    ++i;
+    double mul = 0.1;
+    while (i < s->getLength() &&
+	   s->getChar(i) >= '0' && s->getChar(i) <= '9') {
+      val += mul * (s->getChar(i) - '0');
+      mul *= 0.1;
+      ++i;
+    }
+  }
+  if (neg) {
+    val = -val;
+  }
+  if (i+1 < s->getLength()) {
+    if (s->getChar(i) == 'i' && s->getChar(i+1) == 'n') {
+      val *= 72;
+    } else if (s->getChar(i) == 'p' && s->getChar(i+1) == 't') {
+      // no change
+    } else if (s->getChar(i) == 'c' && s->getChar(i+1) == 'm') {
+      val *= 72 / 2.54;
+    } else if (s->getChar(i) == 'm' && s->getChar(i+1) == 'm') {
+      val *= 72 / 25.4;
+    } else {
+      // default to inches
+      val *= 72;
+    }
+  } else {
+    // default to inches
+    val *= 72;
+  }
+  return val;
+}
+
+GString *XFAScanner::getNodeName(ZxElement *elem) {
+  if (elem->isElement("template") ||
+      elem->isElement("area") ||
+      elem->isElement("draw")) {
+    return NULL;
+  }
+  if (!elem->isElement("field") && nodeIsBindNone(elem)) {
+    return NULL;
+  }
+  ZxAttr *nameAttr = elem->findAttr("name");
+  if (!nameAttr) {
+    return NULL;
+  }
+  return nameAttr->getValue();
+}
+
+GString *XFAScanner::getNodeFullName(ZxElement *elem) {
+  if (elem->isElement("template") ||
+      elem->isElement("draw")) {
+    return NULL;
+  }
+  ZxAttr *nameAttr = elem->findAttr("name");
+  if (!nameAttr) {
+    return NULL;
+  }
+  return nameAttr->getValue();
+}
+
+GBool XFAScanner::nodeIsBindGlobal(ZxElement *elem) {
+  ZxElement *bindElem = elem->findFirstChildElement("bind");
+  if (!bindElem) {
+    return gFalse;
+  }
+  ZxAttr *attr = bindElem->findAttr("match");
+  return attr && !attr->getValue()->cmp("global");
+}
+
+GBool XFAScanner::nodeIsBindNone(ZxElement *elem) {
+  ZxElement *bindElem = elem->findFirstChildElement("bind");
+  if (!bindElem) {
+    return gFalse;
+  }
+  ZxAttr *attr = bindElem->findAttr("match");
+  return attr && !attr->getValue()->cmp("none");
+}

Added: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.h	                        (rev 0)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XFAScanner.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -0,0 +1,166 @@
+//========================================================================
+//
+// XFAScanner.h
+//
+// Copyright 2020 Glyph & Cog, LLC
+//
+//========================================================================
+
+#ifndef XFASCANNER_H
+#define XFASCANNER_H
+
+#include <aconf.h>
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+class GHash;
+class ZxElement;
+
+//------------------------------------------------------------------------
+
+enum XFAFieldLayoutHAlign {
+  xfaFieldLayoutHAlignLeft,
+  xfaFieldLayoutHAlignCenter,
+  xfaFieldLayoutHAlignRight
+};
+
+enum XFAFieldLayoutVAlign {
+  xfaFieldLayoutVAlignTop,
+  xfaFieldLayoutVAlignMiddle,
+  xfaFieldLayoutVAlignBottom
+};
+
+class XFAFieldLayoutInfo {
+public:
+
+  XFAFieldLayoutInfo(XFAFieldLayoutHAlign hAlignA,
+		     XFAFieldLayoutVAlign vAlignA);
+
+  XFAFieldLayoutHAlign hAlign;
+  XFAFieldLayoutVAlign vAlign;
+};
+
+//------------------------------------------------------------------------
+
+enum XFAFieldPictureSubtype {
+  xfaFieldPictureDateTime,
+  xfaFieldPictureNumeric,
+  xfaFieldPictureText
+};
+
+class XFAFieldPictureInfo {
+public:
+
+  XFAFieldPictureInfo(XFAFieldPictureSubtype subtypeA, GString *formatA);
+  ~XFAFieldPictureInfo();
+
+  XFAFieldPictureSubtype subtype;
+  GString *format;		// picture format string
+};
+
+//------------------------------------------------------------------------
+
+class XFAFieldBarcodeInfo {
+public:
+
+  XFAFieldBarcodeInfo(GString *barcodeTypeA, double wideNarrowRatioA,
+		      double moduleWidthA, double moduleHeightA,
+		      int dataLengthA, int errorCorrectionLevelA,
+		      GString *textLocationA);
+  ~XFAFieldBarcodeInfo();
+
+  GString *barcodeType;
+  double wideNarrowRatio;
+  double moduleWidth;
+  double moduleHeight;
+  int dataLength;
+  int errorCorrectionLevel;
+  GString *textLocation;
+};
+
+//------------------------------------------------------------------------
+
+class XFAField {
+public:
+
+  XFAField(GString *nameA, GString *fullNameA, GString *valueA,
+	   XFAFieldLayoutInfo *layoutInfoA,
+	   XFAFieldPictureInfo *pictureInfoA,
+	   XFAFieldBarcodeInfo *barcodeInfoA);
+  ~XFAField();
+
+  // Get the field's value, or NULL if it doesn't have a value.  Sets
+  // *[length] to the length of the Unicode string.
+  GString *getValue() { return value; }
+
+  // Return a pointer to the field's picture formatting info object,
+  // or NULL if the field doesn't have picture formatting.
+  XFAFieldPictureInfo *getPictureInfo() { return pictureInfo; }
+
+  // Return a pointer to the field's layout info object, or NULL if
+  // the field doesn't have layout info.
+  XFAFieldLayoutInfo *getLayoutInfo() { return layoutInfo; }
+
+  // Return a pointer to the field's barcode info object, or NULL if
+  // the field isn't a barcode.
+  XFAFieldBarcodeInfo *getBarcodeInfo() { return barcodeInfo; }
+
+private:
+
+  friend class XFAScanner;
+
+  GString *name;		// UTF-8
+  GString *fullName;		// UTF-8
+  GString *value;		// UTF-8
+  XFAFieldLayoutInfo *layoutInfo;
+  XFAFieldPictureInfo *pictureInfo;
+  XFAFieldBarcodeInfo *barcodeInfo;
+};
+
+//------------------------------------------------------------------------
+
+class XFAScanner {
+public:
+
+  static XFAScanner *load(Object *xfaObj);
+
+  virtual ~XFAScanner();
+
+  // Find an XFA field matchined the specified AcroForm field name.
+  // Returns NULL if there is no matching field.
+  XFAField *findField(GString *acroFormFieldName);
+
+private:
+
+  XFAScanner();
+  static GString *readXFAStreams(Object *xfaObj);
+  GHash *scanFormValues(ZxElement *xmlRoot);
+  void scanFormNode(ZxElement *elem, GString *fullName,
+		    GHash *formValues);
+  void scanNode(ZxElement *elem,
+		GString *parentName, GString *parentFullName,
+		GHash *nameIdx, GHash *fullNameIdx,
+		GString *exclGroupName, ZxElement *xmlRoot,
+		GHash *formValues);
+  void scanField(ZxElement *elem, GString *name, GString *fullName,
+		 GString *exclGroupName, ZxElement *xmlRoot,
+		 GHash *formValues);
+  GString *getFieldValue(ZxElement *elem, GString *name,
+			 GString *fullName, GString *exclGroupName,
+			 ZxElement *xmlRoot, GHash *formValues);
+  GString *getDatasetsValue(char *partName, ZxElement *elem);
+  XFAFieldLayoutInfo *getFieldLayoutInfo(ZxElement *elem);
+  XFAFieldPictureInfo *getFieldPictureInfo(ZxElement *elem);
+  XFAFieldBarcodeInfo *getFieldBarcodeInfo(ZxElement *elem);
+  double getMeasurement(GString *s);
+  GString *getNodeName(ZxElement *elem);
+  GString *getNodeFullName(ZxElement *elem);
+  GBool nodeIsBindGlobal(ZxElement *elem);
+  GBool nodeIsBindNone(ZxElement *elem);
+
+  GHash *fields;		// [XFAField]
+};
+
+#endif

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -350,7 +350,7 @@
 
     // read the xref table
     posSet = new XRefPosSet();
-    while (readXRef(&pos, posSet)) ;
+    while (readXRef(&pos, posSet, gFalse)) ;
     xrefTablePosLen = posSet->getLength();
     xrefTablePos = (GFileOffset *)gmallocn(xrefTablePosLen,
 					   sizeof(GFileOffset));
@@ -438,8 +438,10 @@
 }
 
 // Read one xref table section.  Also reads the associated trailer
-// dictionary, and returns the prev pointer (if any).
-GBool XRef::readXRef(GFileOffset *pos, XRefPosSet *posSet) {
+// dictionary, and returns the prev pointer (if any).  The [hybrid]
+// flag is true when following the XRefStm link in a hybrid-reference
+// file.
+GBool XRef::readXRef(GFileOffset *pos, XRefPosSet *posSet, GBool hybrid) {
   Parser *parser;
   Object obj;
   GBool more;
@@ -461,13 +463,14 @@
   for (i = 0; i < n && Lexer::isSpace(buf[i]); ++i) ;
 
   // parse an old-style xref table
-  if (i + 4 < n &&
+  if (!hybrid &&
+      i + 4 < n &&
       buf[i] == 'x' && buf[i+1] == 'r' && buf[i+2] == 'e' && buf[i+3] == 'f' &&
       Lexer::isSpace(buf[i+4])) {
     more = readXRefTable(pos, i + 5, posSet);
 
   // parse an xref stream
-  } else if (i < n && buf[i] >= '0' && buf[i] <= '9') {
+  } else {
     obj.initNull();
     parser = new Parser(NULL,
 	       new Lexer(NULL,
@@ -474,34 +477,30 @@
 		 str->makeSubStream(start + *pos, gFalse, 0, &obj)),
 	       gTrue);
     if (!parser->getObj(&obj, gTrue)->isInt()) {
-      goto err2;
+      goto err;
     }
     obj.free();
     if (!parser->getObj(&obj, gTrue)->isInt()) {
-      goto err2;
+      goto err;
     }
     obj.free();
     if (!parser->getObj(&obj, gTrue)->isCmd("obj")) {
-      goto err2;
+      goto err;
     }
     obj.free();
     if (!parser->getObj(&obj)->isStream()) {
-      goto err2;
+      goto err;
     }
-    more = readXRefStream(obj.getStream(), pos);
+    more = readXRefStream(obj.getStream(), pos, hybrid);
     obj.free();
     delete parser;
-
-  } else {
-    goto err1;
   }
 
   return more;
 
- err2:
+ err:
   obj.free();
   delete parser;
- err1:
   ok = gFalse;
   return gFalse;
 }
@@ -662,7 +661,7 @@
   //~ this can be a 64-bit int (?)
   if (obj.getDict()->lookup("XRefStm", &obj2)->isInt()) {
     pos2 = (GFileOffset)(Guint)obj2.getInt();
-    readXRef(&pos2, posSet);
+    readXRef(&pos2, posSet, gTrue);
     if (!ok) {
       obj2.free();
       obj.free();
@@ -679,7 +678,7 @@
   return gFalse;
 }
 
-GBool XRef::readXRefStream(Stream *xrefStr, GFileOffset *pos) {
+GBool XRef::readXRefStream(Stream *xrefStr, GFileOffset *pos, GBool hybrid) {
   Dict *dict;
   int w[3];
   GBool more;
@@ -857,125 +856,250 @@
 
 // Attempt to construct an xref table for a damaged file.
 GBool XRef::constructXRef() {
-  Parser *parser;
-  Object newTrailerDict, obj;
-  char buf[256];
-  GFileOffset pos;
-  int num, gen;
-  int newSize;
-  int streamEndsSize;
-  char *p;
-  int i;
-  GBool gotRoot;
-
-  gfree(entries);
-  size = 0;
-  entries = NULL;
-
-  gotRoot = gFalse;
-  streamEndsLen = streamEndsSize = 0;
-
+  int *streamObjNums = NULL;
+  int streamObjNumsLen = 0;
+  int streamObjNumsSize = 0;
+  int lastObjNum = -1;
+  rootNum = -1;
+  int streamEndsSize = 0;
+  streamEndsLen = 0;
+  char buf[4096 + 1];
   str->reset();
+  GFileOffset bufPos = start;
+  char *p = buf;
+  char *end = buf;
+  GBool startOfLine = gTrue;
+  GBool eof = gFalse;
   while (1) {
-    pos = str->getPos();
-    if (!str->getLine(buf, 256)) {
+    if (end - p < 256 && !eof) {
+      memcpy(buf, p, end - p);
+      bufPos += p - buf;
+      p = buf + (end - p);
+      int n = (int)(buf + 4096 - p);
+      int m = str->getBlock(p, n);
+      end = p + m;
+      *end = '\0';
+      p = buf;
+      eof = m < n;
+    }
+    if (p == end && eof) {
       break;
     }
-    p = buf;
+    if (startOfLine && !strncmp(p, "trailer", 7)) {
+      constructTrailerDict((GFileOffset)(bufPos + (p + 7 - buf)));
+      p += 7;
+      startOfLine = gFalse;
+    } else if (startOfLine && !strncmp(p, "endstream", 9)) {
+      if (streamEndsLen == streamEndsSize) {
+	streamEndsSize += 64;
+	streamEnds = (GFileOffset *)greallocn(streamEnds, streamEndsSize,
+					      sizeof(GFileOffset));
+      }
+      streamEnds[streamEndsLen++] = (GFileOffset)(bufPos + (p - buf));
+      p += 9;
+      startOfLine = gFalse;
+    } else if (startOfLine && *p >= '0' && *p <= '9') {
+      p = constructObjectEntry(p, (GFileOffset)(bufPos + (p - buf)),
+			       &lastObjNum);
+      startOfLine = gFalse;
+    } else if (p[0] == '>' && p[1] == '>') {
+      p += 2;
+      startOfLine = gFalse;
+      // skip any PDF whitespace except for '\0'
+      while (*p == '\t' || *p == '\n' || *p == '\x0c' ||
+	     *p == '\r' || *p == ' ') {
+	startOfLine = *p == '\n' || *p == '\r';
+	++p;
+      }
+      if (!strncmp(p, "stream", 6)) {
+	if (lastObjNum >= 0) {
+	  if (streamObjNumsLen == streamObjNumsSize) {
+	    streamObjNumsSize += 64;
+	    streamObjNums = (int *)greallocn(streamObjNums, streamObjNumsSize,
+					     sizeof(int));
+	  }
+	  streamObjNums[streamObjNumsLen++] = lastObjNum;
+	}
+	p += 6;
+	startOfLine = gFalse;
+      }
+    } else {
+      startOfLine = *p == '\n' || *p == '\r';
+      ++p;
+    }
+  }
 
-    // skip whitespace
-    while (*p && Lexer::isSpace(*p & 0xff)) ++p;
+  // read each stream object, check for xref or object stream
+  for (int i = 0; i < streamObjNumsLen; ++i) {
+    Object obj;
+    fetch(streamObjNums[i], entries[streamObjNums[i]].gen, &obj);
+    if (obj.isStream()) {
+      Dict *dict = obj.streamGetDict();
+      Object type;
+      dict->lookup("Type", &type);
+      if (type.isName("XRef")) {
+	saveTrailerDict(dict, gTrue);
+      } else if (type.isName("ObjStm")) {
+	constructObjectStreamEntries(&obj, streamObjNums[i]);
+      }
+      type.free();
+    }
+    obj.free();
+  }
 
-    // got trailer dictionary
-    if (!strncmp(p, "trailer", 7)) {
-      obj.initNull();
-      parser = new Parser(NULL,
+  gfree(streamObjNums);
+
+  if (rootNum < 0) {
+    error(errSyntaxError, -1, "Couldn't find trailer dictionary");
+    return gFalse;
+  }
+  return gTrue;
+}
+
+// Attempt to construct a trailer dict at [pos] in the stream.
+void XRef::constructTrailerDict(GFileOffset pos) {
+  Object newTrailerDict, obj;
+  obj.initNull();
+  Parser *parser =
+      new Parser(NULL,
 		 new Lexer(NULL,
-		   str->makeSubStream(pos + 7, gFalse, 0, &obj)),
+			   str->makeSubStream(pos, 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;
+  parser->getObj(&newTrailerDict);
+  if (newTrailerDict.isDict()) {
+    saveTrailerDict(newTrailerDict.getDict(), gFalse);
+  }
+  newTrailerDict.free();
+  delete parser;
+}
 
-    // look for object
-    } else if (isdigit(*p & 0xff)) {
-      num = atoi(p);
-      if (num > 0) {
-	do {
-	  ++p;
-	} while (*p && isdigit(*p & 0xff));
-	if (isspace(*p & 0xff)) {
-	  do {
-	    ++p;
-	  } while (*p && isspace(*p & 0xff));
-	  if (isdigit(*p & 0xff)) {
-	    gen = atoi(p);
-	    do {
-	      ++p;
-	    } while (*p && isdigit(*p & 0xff));
-	    if (isspace(*p & 0xff)) {
-	      do {
-		++p;
-	      } while (*p && isspace(*p & 0xff));
-	      if (!strncmp(p, "obj", 3)) {
-		if (num >= size) {
-		  newSize = (num + 1 + 255) & ~255;
-		  if (newSize < 0) {
-		    error(errSyntaxError, -1, "Bad object number");
-		    return gFalse;
-		  }
-		  entries = (XRefEntry *)
-		      greallocn(entries, newSize, sizeof(XRefEntry));
-		  for (i = size; i < newSize; ++i) {
-		    entries[i].offset = (GFileOffset)-1;
-		    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;
-		  if (num > last) {
-		    last = num;
-		  }
-		}
-	      }
-	    }
-	  }
-	}
+// If [dict] "looks like" a trailer dict (i.e., has a Root entry),
+// save it as the trailer dict.
+void XRef::saveTrailerDict(Dict *dict, GBool isXRefStream) {
+  Object obj;
+  dict->lookupNF("Root", &obj);
+  if (obj.isRef()) {
+    int newRootNum = obj.getRefNum();
+    // the xref stream scanning code runs after all objects are found,
+    // so we can check for a valid root object number at that point
+    if (!isXRefStream || newRootNum <= last) {
+      rootNum = newRootNum;
+      rootGen = obj.getRefGen();
+      if (!trailerDict.isNone()) {
+	trailerDict.free();
       }
+      trailerDict.initDict(dict);
+    }
+  }
+  obj.free();
+}
 
-    } else if (!strncmp(p, "endstream", 9)) {
-      if (streamEndsLen == streamEndsSize) {
-	streamEndsSize += 64;
-	streamEnds = (GFileOffset *)greallocn(streamEnds, streamEndsSize,
-					      sizeof(GFileOffset));
+// Look for an object header ("nnn ggg obj") at [p].  The first
+// character at *[p] is a digit.  [pos] is the position of *[p].
+char *XRef::constructObjectEntry(char *p, GFileOffset pos, int *objNum) {
+  // we look for non-end-of-line space characters here, to deal with
+  // situations like:
+  //    nnn          <-- garbage digits on a line
+  //    nnn nnn obj  <-- actual object
+  // and we also ignore '\0' (because it's used to terminate the
+  // buffer in this damage-scanning code)
+  int num = 0;
+  do {
+    num = (num * 10) + (*p - '0');
+    ++p;
+  } while (*p >= '0' && *p <= '9' && num < 100000000);
+  if (*p != '\t' && *p != '\x0c' && *p != ' ') {
+    return p;
+  }
+  do {
+    ++p;
+  } while (*p == '\t' || *p == '\x0c' || *p == ' ');
+  if (!(*p >= '0' && *p <= '9')) {
+    return p;
+  }
+  int gen = 0;
+  do {
+    gen = (gen * 10) + (*p - '0');
+    ++p;
+  } while (*p >= '0' && *p <= '9' && gen < 100000000);
+  if (*p != '\t' && *p != '\x0c' && *p != ' ') {
+    return p;
+  }
+  do {
+    ++p;
+  } while (*p == '\t' || *p == '\x0c' || *p == ' ');
+  if (strncmp(p, "obj", 3)) {
+    return p;
+  }
+
+  if (constructXRefEntry(num, gen, pos - start, xrefEntryUncompressed)) {
+    *objNum = num;
+  }
+
+  return p;
+}
+
+// Read the header from an object stream, and add xref entries for all
+// of its objects.
+void XRef::constructObjectStreamEntries(Object *objStr, int objStrObjNum) {
+  Object obj1, obj2;
+
+  // get the object count
+  if (!objStr->streamGetDict()->lookup("N", &obj1)->isInt()) {
+    obj1.free();
+    return;
+  }
+  int nObjects = obj1.getInt();
+  obj1.free();
+  if (nObjects <= 0 || nObjects > 1000000) {
+    return;
+  }
+
+  // parse the header: object numbers and offsets
+  Parser *parser = new Parser(NULL,
+			      new Lexer(NULL, objStr->getStream()->copy()),
+			      gFalse);
+  for (int i = 0; i < nObjects; ++i) {
+    parser->getObj(&obj1, gTrue);
+    parser->getObj(&obj2, gTrue);
+    if (obj1.isInt() && obj2.isInt()) {
+      int num = obj1.getInt();
+      if (num >= 0 && num < 1000000) {
+	constructXRefEntry(num, i, objStrObjNum, xrefEntryCompressed);
       }
-      streamEnds[streamEndsLen++] = pos;
     }
+    obj2.free();
+    obj1.free();
   }
+  delete parser;
+}
 
-  if (gotRoot) {
-    return gTrue;
+GBool XRef::constructXRefEntry(int num, int gen, GFileOffset pos,
+			       XRefEntryType type) {
+  if (num >= size) {
+    int newSize = (num + 1 + 255) & ~255;
+    if (newSize < 0) {
+      return gFalse;
+    }
+    entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry));
+    for (int i = size; i < newSize; ++i) {
+      entries[i].offset = (GFileOffset)-1;
+      entries[i].type = xrefEntryFree;
+    }
+    size = newSize;
   }
 
-  error(errSyntaxError, -1, "Couldn't find trailer dictionary");
-  return gFalse;
+  if (entries[num].type == xrefEntryFree ||
+      gen >= entries[num].gen) {
+    entries[num].offset = pos;
+    entries[num].gen = gen;
+    entries[num].type = type;
+    if (num > last) {
+      last = num;
+    }
+  }
+
+  return gTrue;
 }
 
 void XRef::setEncryption(int permFlagsA, GBool ownerPasswordOkA,
@@ -1153,6 +1277,9 @@
   gLockMutex(&objStrsMutex);
 #endif
   if (!(objStr = getObjectStream(objStrNum))) {
+#if MULTITHREADED
+    gUnlockMutex(&objStrsMutex);
+#endif
     return gFalse;
   }
   cleanObjectStreamCache();

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/XRef.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -166,11 +166,17 @@
 #endif
 
   GFileOffset getStartXref();
-  GBool readXRef(GFileOffset *pos, XRefPosSet *posSet);
+  GBool readXRef(GFileOffset *pos, XRefPosSet *posSet, GBool hybrid);
   GBool readXRefTable(GFileOffset *pos, int offset, XRefPosSet *posSet);
+  GBool readXRefStream(Stream *xrefStr, GFileOffset *pos, GBool hybrid);
   GBool readXRefStreamSection(Stream *xrefStr, int *w, int first, int n);
-  GBool readXRefStream(Stream *xrefStr, GFileOffset *pos);
   GBool constructXRef();
+  void constructTrailerDict(GFileOffset pos);
+  void saveTrailerDict(Dict *dict, GBool isXRefStream);
+  char *constructObjectEntry(char *p, GFileOffset pos, int *objNum);
+  void constructObjectStreamEntries(Object *objStr, int objStrObjNum);
+  GBool constructXRefEntry(int num, int gen, GFileOffset pos,
+			   XRefEntryType type);
   GBool getObjectStreamObject(int objStrNum, int objIdx,
 			      int objNum, Object *obj);
   ObjectStream *getObjectStream(int objStrNum);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -430,7 +430,8 @@
   GString *name, *value;
   const char *start;
   char quote, c;
-  int x, n;
+  unsigned int x;
+  int n;
 
   name = parseName();
   parseSpace();
@@ -559,7 +560,8 @@
   GString *data;
   const char *start;
   char c;
-  int x, n;
+  unsigned int x;
+  int n;
 
   data = new GString();
   while (parsePtr < parseEnd && *parsePtr != '<') {
@@ -638,7 +640,7 @@
   par->addChild(new ZxCharData(data, true));
 }
 
-void ZxDoc::appendUTF8(GString *s, int c) {
+void ZxDoc::appendUTF8(GString *s, unsigned int c) {
   if (c <= 0x7f) {
     s->append((char)c);
   } else if (c <= 0x7ff) {

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/Zoox.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -103,7 +103,7 @@
   ZxAttr *parseAttr();
   void parseContent(ZxElement *par);
   void parseCharData(ZxElement *par);
-  void appendUTF8(GString *s, int c);
+  void appendUTF8(GString *s, unsigned int c);
   void parseCDSect(ZxNode *par);
   void parseMisc(ZxNode *par);
   void parseComment(ZxNode *par);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/config.h
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/config.h	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/config.h	2021-02-01 06:11:18 UTC (rev 57588)
@@ -2,7 +2,7 @@
 //
 // config.h
 //
-// Copyright 1996-2019 Glyph & Cog, LLC
+// Copyright 1996-2021 Glyph & Cog, LLC
 //
 //========================================================================
 
@@ -14,13 +14,13 @@
 //------------------------------------------------------------------------
 
 // xpdf version
-#define xpdfVersion          "4.02"
-#define xpdfVersionNum       4.02
+#define xpdfVersion          "4.03"
+#define xpdfVersionNum       4.03
 #define xpdfMajorVersion     4
-#define xpdfMinorVersion     2
+#define xpdfMinorVersion     3
 #define xpdfUpdateVersion    0
 #define xpdfMajorVersionStr  "4"
-#define xpdfMinorVersionStr  "2"
+#define xpdfMinorVersionStr  "3"
 #define xpdfUpdateVersionStr "0"
 
 // supported PDF version
@@ -28,11 +28,11 @@
 #define supportedPDFVersionNum 2.0
 
 // copyright notice
-#define xpdfCopyright "Copyright 1996-2019 Glyph & Cog, LLC"
+#define xpdfCopyright "Copyright 1996-2021 Glyph & Cog, LLC"
 
 // Windows resource file stuff
-#define winxpdfVersion "WinXpdf 4.02"
-#define xpdfCopyrightAmp "Copyright 1996-2019 Glyph && Cog, LLC"
+#define winxpdfVersion "WinXpdf 4.03"
+#define xpdfCopyrightAmp "Copyright 1996-2021 Glyph && Cog, LLC"
 
 //------------------------------------------------------------------------
 // paper size
@@ -59,12 +59,14 @@
 #endif
 
 // system config file name (set via the configure script)
-#ifdef SYSTEM_XPDFRC
-#define xpdfSysConfigFile SYSTEM_XPDFRC
-#else
+#if defined(_WIN32)
 // under Windows, we get the directory with the executable and then
 // append this file name
 #define xpdfSysConfigFile "xpdfrc"
+#elif defined(SYSTEM_XPDFRC)
+#define xpdfSysConfigFile SYSTEM_XPDFRC
+#else
+#define xpdfSysConfigFile "/etc/xpdfrc"
 #endif
 
 //------------------------------------------------------------------------

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfdetach.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfdetach.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfdetach.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -16,6 +16,7 @@
 #include "PDFDoc.h"
 #include "CharTypes.h"
 #include "UnicodeMap.h"
+#include "UTF8.h"
 #include "Error.h"
 #include "config.h"
 
@@ -67,8 +68,7 @@
   PDFDoc *doc;
   Unicode *name;
   char uBuf[8];
-  char path[1024];
-  char *p;
+  GString *path;
   GBool ok;
   int exitCode;
   int nFiles, nameLen, n, i, j;
@@ -84,7 +84,7 @@
     ok = gFalse;
   }
   if (!ok || argc != 2 || printVersion || printHelp) {
-    fprintf(stderr, "pdfdetach version %s\n", xpdfVersion);
+    fprintf(stderr, "pdfdetach version %s [www.xpdfreader.com]\n", xpdfVersion);
     fprintf(stderr, "%s\n", xpdfCopyright);
     if (!printVersion) {
       printUsage("pdfdetach", "<PDF-file>", argDesc);
@@ -148,32 +148,24 @@
   } else if (saveAll) {
     for (i = 0; i < nFiles; ++i) {
       if (savePath[0]) {
-	n = (int)strlen(savePath);
-	if (n > (int)sizeof(path) - 2) {
-	  n = sizeof(path) - 2;
-	}
-	memcpy(path, savePath, n);
-	path[n] = '/';
-	p = path + n + 1;
+	path = new GString(savePath);
+	path->append('/');
       } else {
-	p = path;
+	path = new GString();
       }
       name = doc->getEmbeddedFileName(i);
       nameLen = doc->getEmbeddedFileNameLength(i);
       for (j = 0; j < nameLen; ++j) {
-	n = uMap->mapUnicode(name[j], uBuf, sizeof(uBuf));
-	if (p + n >= path + sizeof(path)) {
-	  break;
-	}
-	memcpy(p, uBuf, n);
-	p += n;
+	n = mapUTF8(name[j], uBuf, sizeof(uBuf));
+	path->append(uBuf, n);
       }
-      *p = '\0';
-      if (!doc->saveEmbeddedFile(i, path)) {
-	error(errIO, -1, "Error saving embedded file as '{0:s}'", p);
+      if (!doc->saveEmbeddedFileU(i, path->getCString())) {
+	error(errIO, -1, "Error saving embedded file as '{0:t}'", path);
+	delete path;
 	exitCode = 2;
 	goto err2;
       }
+      delete path;
     }
 
   // save an embedded file
@@ -183,27 +175,23 @@
       goto err2;
     }
     if (savePath[0]) {
-      p = savePath;
+      path = new GString(savePath);
     } else {
       name = doc->getEmbeddedFileName(saveNum - 1);
       nameLen = doc->getEmbeddedFileNameLength(saveNum - 1);
-      p = path;
+      path = new GString();
       for (j = 0; j < nameLen; ++j) {
-	n = uMap->mapUnicode(name[j], uBuf, sizeof(uBuf));
-	if (p + n >= path + sizeof(path)) {
-	  break;
-	}
-	memcpy(p, uBuf, n);
-	p += n;
+	n = mapUTF8(name[j], uBuf, sizeof(uBuf));
+	path->append(uBuf, n);
       }
-      *p = '\0';
-      p = path;
     }
-    if (!doc->saveEmbeddedFile(saveNum - 1, p)) {
-      error(errIO, -1, "Error saving embedded file as '{0:s}'", p);
+    if (!doc->saveEmbeddedFileU(saveNum - 1, path->getCString())) {
+      error(errIO, -1, "Error saving embedded file as '{0:t}'", path);
+      delete path;
       exitCode = 2;
       goto err2;
     }
+    delete path;
   }
 
   exitCode = 0;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdffonts.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdffonts.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdffonts.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -23,7 +23,7 @@
 #include "Dict.h"
 #include "GfxFont.h"
 #include "Annot.h"
-#include "Form.h"
+#include "AcroForm.h"
 #include "PDFDoc.h"
 #include "config.h"
 
@@ -102,7 +102,7 @@
   Page *page;
   Dict *resDict;
   Annots *annots;
-  Form *form;
+  AcroForm *form;
   Object obj1, obj2;
   int pg, i, j;
   int exitCode;
@@ -113,7 +113,7 @@
   fixCommandLine(&argc, &argv);
   ok = parseArgs(argDesc, &argc, argv);
   if (!ok || argc != 2 || printVersion || printHelp) {
-    fprintf(stderr, "pdffonts version %s\n", xpdfVersion);
+    fprintf(stderr, "pdffonts version %s [www.xpdfreader.com]\n", xpdfVersion);
     fprintf(stderr, "%s\n", xpdfCopyright);
     if (!printVersion) {
       printUsage("pdffonts", "<PDF-file>", argDesc);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfimages.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfimages.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfimages.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -87,7 +87,7 @@
   fixCommandLine(&argc, &argv);
   ok = parseArgs(argDesc, &argc, argv);
   if (!ok || argc != 3 || printVersion || printHelp) {
-    fprintf(stderr, "pdfimages version %s\n", xpdfVersion);
+    fprintf(stderr, "pdfimages version %s [www.xpdfreader.com]\n", xpdfVersion);
     fprintf(stderr, "%s\n", xpdfCopyright);
     if (!printVersion) {
       printUsage("pdfimages", "<PDF-file> <image-root>", argDesc);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfinfo.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfinfo.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdfinfo.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -102,6 +102,9 @@
   FILE *f;
   GString *metadata;
   ZxDoc *xmp;
+  int permFlags, keyLength, encVersion;
+  GBool ownerPasswordOk;
+  CryptAlgorithm encAlgorithm;
   GBool ok;
   int exitCode;
   int pg, i;
@@ -113,7 +116,7 @@
   fixCommandLine(&argc, &argv);
   ok = parseArgs(argDesc, &argc, argv);
   if (!ok || argc != 2 || printVersion || printHelp) {
-    fprintf(stderr, "pdfinfo version %s\n", xpdfVersion);
+    fprintf(stderr, "pdfinfo version %s [www.xpdfreader.com]\n", xpdfVersion);
     fprintf(stderr, "%s\n", xpdfCopyright);
     if (!printVersion) {
       printUsage("pdfinfo", "<PDF-file>", argDesc);
@@ -216,15 +219,19 @@
   printf("Pages:          %d\n", doc->getNumPages());
 
   // print encryption info
-  printf("Encrypted:      ");
   if (doc->isEncrypted()) {
-    printf("yes (print:%s copy:%s change:%s addNotes:%s)\n",
+    doc->getXRef()->getEncryption(&permFlags, &ownerPasswordOk, &keyLength,
+				  &encVersion, &encAlgorithm);
+    printf("Encrypted:      %s %d-bit\n",
+	   encAlgorithm == cryptRC4 ? "RC4" : "AES",
+	   keyLength * 8);
+    printf("Permissions:    print:%s copy:%s change:%s addNotes:%s\n",
 	   doc->okToPrint(gTrue) ? "yes" : "no",
 	   doc->okToCopy(gTrue) ? "yes" : "no",
 	   doc->okToChange(gTrue) ? "yes" : "no",
 	   doc->okToAddNotes(gTrue) ? "yes" : "no");
   } else {
-    printf("no\n");
+    printf("Encrypted:      no\n");
   }
 
   // print page size
@@ -261,18 +268,18 @@
     if (multiPage) {
       for (pg = firstPage; pg <= lastPage; ++pg) {
 	page = doc->getCatalog()->getPage(pg);
-	sprintf(buf, "Page %4d MediaBox: ", pg);
+	snprintf(buf, sizeof(buf), "Page %4d MediaBox: ", pg);
 	printBox(buf, page->getMediaBox());
-	sprintf(buf, "Page %4d CropBox:  ", pg);
+	snprintf(buf, sizeof(buf), "Page %4d CropBox:  ", pg);
 	printBox(buf, page->getCropBox());
-	sprintf(buf, "Page %4d BleedBox: ", pg);
+	snprintf(buf, sizeof(buf), "Page %4d BleedBox: ", pg);
 	printBox(buf, page->getBleedBox());
-	sprintf(buf, "Page %4d TrimBox:  ", pg);
+	snprintf(buf, sizeof(buf), "Page %4d TrimBox:  ", pg);
 	printBox(buf, page->getTrimBox());
-	sprintf(buf, "Page %4d ArtBox:   ", pg);
+	snprintf(buf, sizeof(buf), "Page %4d ArtBox:   ", pg);
 	printBox(buf, page->getArtBox());
       }
-    } else {
+    } else if (doc->getNumPages() > 0) {
       page = doc->getCatalog()->getPage(firstPage);
       printBox("MediaBox:       ", page->getMediaBox());
       printBox("CropBox:        ", page->getCropBox());

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftohtml.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftohtml.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftohtml.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -31,6 +31,7 @@
 static int lastPage = 0;
 static double zoom = 1;
 static int resolution = 150;
+static GBool noFonts = gFalse;
 static GBool skipInvisible = gFalse;
 static GBool allInvisible = gFalse;
 static char ownerPassword[33] = "\001";
@@ -45,10 +46,12 @@
    "first page to convert"},
   {"-l",       argInt,      &lastPage,      0,
    "last page to convert"},
-  {"-z",      argFP,       &zoom,     0,
+  {"-z",       argFP,       &zoom,          0,
    "initial zoom level (1.0 means 72dpi)"},
-  {"-r",      argInt,      &resolution,     0,
+  {"-r",       argInt,      &resolution,    0,
    "resolution, in DPI (default is 150)"},
+  {"-nofonts", argFlag, &noFonts,           0,
+   "do not extract embedded fonts"},
   {"-skipinvisible", argFlag, &skipInvisible, 0,
    "do not draw invisible text"},
   {"-allinvisible",  argFlag, &allInvisible,  0,
@@ -97,7 +100,7 @@
   fixCommandLine(&argc, &argv);
   ok = parseArgs(argDesc, &argc, argv);
   if (!ok || argc != 3 || printVersion || printHelp) {
-    fprintf(stderr, "pdftohtml version %s\n", xpdfVersion);
+    fprintf(stderr, "pdftohtml version %s [www.xpdfreader.com]\n", xpdfVersion);
     fprintf(stderr, "%s\n", xpdfCopyright);
     if (!printVersion) {
       printUsage("pdftohtml", "<PDF-file> <html-dir>", argDesc);
@@ -155,7 +158,7 @@
   }
 
   // create HTML directory
-  if (!createDir(htmlDir, 0755)) {
+  if (makeDir(htmlDir, 0755)) {
     error(errIO, -1, "Couldn't create HTML output directory '{0:s}'",
 	  htmlDir);
     exitCode = 2;
@@ -171,7 +174,7 @@
   htmlGen->setZoom(zoom);
   htmlGen->setDrawInvisibleText(!skipInvisible);
   htmlGen->setAllTextInvisible(allInvisible);
-  htmlGen->setExtractFontFiles(gTrue);
+  htmlGen->setExtractFontFiles(!noFonts);
   htmlGen->startDoc(doc);
 
   // convert the pages
@@ -178,13 +181,13 @@
   for (pg = firstPage; pg <= lastPage; ++pg) {
     htmlFileName = GString::format("{0:s}/page{1:d}.html", htmlDir, pg);
     pngFileName = GString::format("{0:s}/page{1:d}.png", htmlDir, pg);
-    if (!(htmlFile = fopen(htmlFileName->getCString(), "wb"))) {
+    if (!(htmlFile = openFile(htmlFileName->getCString(), "wb"))) {
       error(errIO, -1, "Couldn't open HTML file '{0:t}'", htmlFileName);
       delete htmlFileName;
       delete pngFileName;
       goto err2;
     }
-    if (!(pngFile = fopen(pngFileName->getCString(), "wb"))) {
+    if (!(pngFile = openFile(pngFileName->getCString(), "wb"))) {
       error(errIO, -1, "Couldn't open PNG file '{0:t}'", pngFileName);
       fclose(htmlFile);
       delete htmlFileName;
@@ -236,7 +239,7 @@
   int pg;
 
   htmlFileName = GString::format("{0:s}/index.html", htmlDir);
-  html = fopen(htmlFileName->getCString(), "w");
+  html = openFile(htmlFileName->getCString(), "w");
   delete htmlFileName;
   if (!html) {
     error(errIO, -1, "Couldn't open HTML file '{0:t}'", htmlFileName);

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftopng.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftopng.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftopng.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -18,6 +18,7 @@
 #include "gmempp.h"
 #include "parseargs.h"
 #include "GString.h"
+#include "gfile.h"
 #include "GlobalParams.h"
 #include "Object.h"
 #include "PDFDoc.h"
@@ -32,6 +33,7 @@
 static GBool mono = gFalse;
 static GBool gray = gFalse;
 static GBool pngAlpha = gFalse;
+static int rotate = 0;
 static char enableFreeTypeStr[16] = "";
 static char antialiasStr[16] = "";
 static char vectorAntialiasStr[16] = "";
@@ -55,6 +57,8 @@
    "generate a grayscale PNG file"},
   {"-alpha",  argFlag,     &pngAlpha,      0,
    "include an alpha channel in the PNG file"},
+  {"-rot",    argInt,      &rotate,        0,
+   "set page rotation: 0, 90, 180, or 270"},
 #if HAVE_FREETYPE_H
   {"-freetype",   argString,      enableFreeTypeStr, sizeof(enableFreeTypeStr),
    "enable FreeType font rasterizer: yes, no"},
@@ -119,7 +123,7 @@
     goto err0;
   }
   if (!ok || argc != 3 || printVersion || printHelp) {
-    fprintf(stderr, "pdftopng version %s\n", xpdfVersion);
+    fprintf(stderr, "pdftopng version %s [www.xpdfreader.com]\n", xpdfVersion);
     fprintf(stderr, "%s\n", xpdfCopyright);
     if (!printVersion) {
       printUsage("pdftopng", "<PDF-file> <PNG-root>", argDesc);
@@ -197,7 +201,7 @@
   }
   splashOut->startDoc(doc->getXRef());
   for (pg = firstPage; pg <= lastPage; ++pg) {
-    doc->displayPage(splashOut, pg, resolution, resolution, 0,
+    doc->displayPage(splashOut, pg, resolution, resolution, rotate,
 		     gFalse, gTrue, gFalse);
     if (mono) {
       if (!strcmp(pngRoot, "-")) {
@@ -207,7 +211,7 @@
 #endif
       } else {
 	pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg);
-	if (!(f = fopen(pngFile->getCString(), "wb"))) {
+	if (!(f = openFile(pngFile->getCString(), "wb"))) {
 	  exit(2);
 	}
 	delete pngFile;
@@ -225,7 +229,7 @@
 #endif
       } else {
 	pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg);
-	if (!(f = fopen(pngFile->getCString(), "wb"))) {
+	if (!(f = openFile(pngFile->getCString(), "wb"))) {
 	  exit(2);
 	}
 	delete pngFile;
@@ -244,7 +248,7 @@
 #endif
       } else {
 	pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg);
-	if (!(f = fopen(pngFile->getCString(), "wb"))) {
+	if (!(f = openFile(pngFile->getCString(), "wb"))) {
 	  exit(2);
 	}
 	delete pngFile;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftoppm.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftoppm.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftoppm.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -36,6 +36,7 @@
 #if SPLASH_CMYK
 static GBool cmyk = gFalse;
 #endif
+static int rotate = 0;
 static char enableFreeTypeStr[16] = "";
 static char antialiasStr[16] = "";
 static char vectorAntialiasStr[16] = "";
@@ -61,6 +62,8 @@
   {"-cmyk",   argFlag,     &cmyk,          0,
    "generate a CMYK PAM file"},
 #endif
+  {"-rot",    argInt,      &rotate,        0,
+   "set page rotation: 0, 90, 180, or 270"},
 #if HAVE_FREETYPE_H
   {"-freetype",   argString,      enableFreeTypeStr, sizeof(enableFreeTypeStr),
    "enable FreeType font rasterizer: yes, no"},
@@ -133,7 +136,7 @@
     ok = gFalse;
   }
   if (!ok || argc != 3 || printVersion || printHelp) {
-    fprintf(stderr, "pdftoppm version %s\n", xpdfVersion);
+    fprintf(stderr, "pdftoppm version %s [www.xpdfreader.com]\n", xpdfVersion);
     fprintf(stderr, "%s\n", xpdfCopyright);
     if (!printVersion) {
       printUsage("pdftoppm", "<PDF-file> <PPM-root>", argDesc);
@@ -226,7 +229,7 @@
   }
   splashOut->startDoc(doc->getXRef());
   for (pg = firstPage; pg <= lastPage; ++pg) {
-    doc->displayPage(splashOut, pg, resolution, resolution, 0,
+    doc->displayPage(splashOut, pg, resolution, resolution, rotate,
 		     gFalse, gTrue, gFalse);
     if (!strcmp(ppmRoot, "-")) {
 #ifdef _WIN32

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftops.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftops.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftops.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -182,7 +182,7 @@
   fixCommandLine(&argc, &argv);
   ok = parseArgs(argDesc, &argc, argv);
   if (!ok || argc < 2 || argc > 3 || printVersion || printHelp) {
-    fprintf(stderr, "pdftops version %s\n", xpdfVersion);
+    fprintf(stderr, "pdftops version %s [www.xpdfreader.com]\n", xpdfVersion);
     fprintf(stderr, "%s\n", xpdfCopyright);
     if (!printVersion) {
       printUsage("pdftops", "<PDF-file> [<PS-file>]", argDesc);
@@ -355,7 +355,7 @@
   // write PostScript file
   psOut = new PSOutputDev(psFileName->getCString(), doc,
 			  firstPage, lastPage, mode,
-			  0, 0, 0, 0, gFalse, NULL, NULL, userUnit);
+			  0, 0, 0, 0, gFalse, NULL, NULL, userUnit, gTrue);
   if (!psOut->isOk()) {
     delete psOut;
     exitCode = 2;

Modified: trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftotext.cc
===================================================================
--- trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftotext.cc	2021-02-01 02:15:42 UTC (rev 57587)
+++ trunk/Build/source/libs/xpdf/xpdf-src/xpdf/pdftotext.cc	2021-02-01 06:11:18 UTC (rev 57588)
@@ -19,6 +19,7 @@
 #include "gmempp.h"
 #include "parseargs.h"
 #include "GString.h"
+#include "GList.h"
 #include "GlobalParams.h"
 #include "Object.h"
 #include "Stream.h"
@@ -39,6 +40,7 @@
 static int lastPage = 0;
 static GBool physLayout = gFalse;
 static GBool simpleLayout = gFalse;
+static GBool simple2Layout = gFalse;
 static GBool tableLayout = gFalse;
 static GBool linePrinter = gFalse;
 static GBool rawOrder = gFalse;
@@ -58,6 +60,7 @@
 static char userPassword[33] = "\001";
 static GBool quiet = gFalse;
 static char cfgFileName[256] = "";
+static GBool listEncodings = gFalse;
 static GBool printVersion = gFalse;
 static GBool printHelp = gFalse;
 
@@ -70,6 +73,8 @@
    "maintain original physical layout"},
   {"-simple",  argFlag,     &simpleLayout,  0,
    "simple one-column page layout"},
+  {"-simple2", argFlag,     &simple2Layout, 0,
+   "simple one-column page layout, version 2"},
   {"-table",   argFlag,     &tableLayout,   0,
    "similar to -layout, but optimized for tables"},
   {"-lineprinter", argFlag, &linePrinter,   0,
@@ -108,6 +113,8 @@
    "don't print any messages or errors"},
   {"-cfg",     argString,   cfgFileName,    sizeof(cfgFileName),
    "configuration file to use in place of .xpdfrc"},
+  {"-listencodings", argFlag, &listEncodings, 0,
+   "list all available output text encodings"},
   {"-v",       argFlag,     &printVersion,  0,
    "print copyright and version info"},
   {"-h",       argFlag,     &printHelp,     0,
@@ -153,8 +160,19 @@
   // parse args
   fixCommandLine(&argc, &argv);
   ok = parseArgs(argDesc, &argc, argv);
+  if (ok && listEncodings) {
+    // list available encodings
+    globalParams = new GlobalParams(cfgFileName);
+    GList *encs = globalParams->getAvailableTextEncodings();
+    for (int i = 0; i < encs->getLength(); ++i) {
+      printf("%s\n", ((GString *)encs->get(i))->getCString());
+    }
+    deleteGList(encs, GString);
+    delete globalParams;
+    goto err0;
+  }
   if (!ok || argc < 2 || argc > 3 || printVersion || printHelp) {
-    fprintf(stderr, "pdftotext version %s\n", xpdfVersion);
+    fprintf(stderr, "pdftotext version %s [www.xpdfreader.com]\n", xpdfVersion);
     fprintf(stderr, "%s\n", xpdfCopyright);
     if (!printVersion) {
       printUsage("pdftotext", "<PDF-file> [<text-file>]", argDesc);
@@ -247,6 +265,8 @@
     textOutControl.fixedPitch = fixedPitch;
   } else if (simpleLayout) {
     textOutControl.mode = textOutSimpleLayout;
+  } else if (simple2Layout) {
+    textOutControl.mode = textOutSimple2Layout;
   } else if (linePrinter) {
     textOutControl.mode = textOutLinePrinter;
     textOutControl.fixedPitch = fixedPitch;
@@ -264,7 +284,7 @@
   textOutControl.marginTop = marginTop;
   textOutControl.marginBottom = marginBottom;
   textOut = new TextOutputDev(textFileName->getCString(), &textOutControl,
-			      gFalse);
+			      gFalse, gTrue);
   if (textOut->isOk()) {
     doc->displayPages(textOut, firstPage, lastPage, 72, 72, 0,
 		      gFalse, gTrue, gFalse);



More information about the tex-live-commits mailing list.