diff -uNrp xpdf-3.03/aconf-dj.h xpdf-3.04/aconf-dj.h --- xpdf-3.03/aconf-dj.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/aconf-dj.h 2014-05-28 20:50:50.000000000 +0200 @@ -55,11 +55,6 @@ #undef HAVE_X11_XPM_H /* - * This is defined if using t1lib. - */ -#undef HAVE_T1LIB_H - -/* * One of these is defined if using FreeType (version 1 or 2). */ #undef HAVE_FREETYPE_H diff -uNrp xpdf-3.03/aconf.h.in xpdf-3.04/aconf.h.in --- xpdf-3.03/aconf.h.in 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/aconf.h.in 2014-05-28 20:50:50.000000000 +0200 @@ -35,11 +35,6 @@ #undef USE_EXCEPTIONS /* - * Enable word list support. - */ -#undef TEXTOUT_WORD_LIST - -/* * Use fixed point (instead of floating point) arithmetic. */ #undef USE_FIXEDPOINT @@ -72,6 +67,7 @@ #undef HAVE_STD_SORT #undef HAVE_FSEEKO #undef HAVE_FSEEK64 +#undef HAVE_FSEEKI64 #undef _FILE_OFFSET_BITS #undef _LARGE_FILES #undef _LARGEFILE_SOURCE @@ -83,11 +79,6 @@ #undef HAVE_X11_XPM_H /* - * This is defined if using t1lib. - */ -#undef HAVE_T1LIB_H - -/* * One of these is defined if using FreeType 2. */ #undef HAVE_FREETYPE_H diff -uNrp xpdf-3.03/aconf-win32.h xpdf-3.04/aconf-win32.h --- xpdf-3.03/aconf-win32.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/aconf-win32.h 2014-05-28 20:50:50.000000000 +0200 @@ -35,11 +35,6 @@ #define USE_EXCEPTIONS 1 /* - * Enable word list support. - */ -#undef TEXTOUT_WORD_LIST - -/* * Use fixed point (instead of floating point) arithmetic. */ #undef USE_FIXEDPOINT @@ -72,6 +67,7 @@ #define HAVE_STD_SORT 1 #undef HAVE_FSEEKO #undef HAVE_FSEEK64 +#define HAVE_FSEEKI64 1 #undef _FILE_OFFSET_BITS #undef _LARGE_FILES #undef _LARGEFILE_SOURCE @@ -83,11 +79,6 @@ #undef HAVE_X11_XPM_H /* - * This is defined if using t1lib. - */ -#undef HAVE_T1LIB_H - -/* * One of these is defined if using FreeType (version 1 or 2). */ #undef HAVE_FREETYPE_H @@ -111,11 +102,11 @@ /* * Defined if the Splash library is avaiable. */ -#undef HAVE_SPLASH +#define HAVE_SPLASH 1 /* * Enable support for CMYK output. */ -#undef SPLASH_CMYK +#define SPLASH_CMYK 1 #endif diff -uNrp xpdf-3.03/doc/pdfdetach.1 xpdf-3.04/doc/pdfdetach.1 --- xpdf-3.03/doc/pdfdetach.1 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdfdetach.1 2014-05-28 20:50:50.000000000 +0200 @@ -1,8 +1,8 @@ -.\" Copyright 2011 Glyph & Cog, LLC -.TH pdfdetach 1 "15 August 2011" +.\" Copyright 2013-2014 Glyph & Cog, LLC +.TH pdfdetach 1 "28 May 2014" .SH NAME pdfdetach \- Portable Document Format (PDF) document embedded file -extractor (version 3.03) +extractor (version 3.04) .SH SYNOPSIS .B pdfdetach [options] @@ -90,15 +90,17 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdfinfo software and documentation are copyright 1996-2011 Glyph & +The pdfinfo software and documentation are copyright 1996-2014 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), .BR pdftops (1), .BR pdftotext (1), +.BR pdftohtml (1), .BR pdfinfo (1), .BR pdffonts (1), .BR pdftoppm (1), +.BR pdftopng (1), .BR pdfimages (1), .BR xpdfrc (5) .br diff -uNrp xpdf-3.03/doc/pdfdetach.cat xpdf-3.04/doc/pdfdetach.cat --- xpdf-3.03/doc/pdfdetach.cat 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdfdetach.cat 2014-05-28 20:50:50.000000000 +0200 @@ -4,7 +4,7 @@ pdfdetach(1) NAME pdfdetach - Portable Document Format (PDF) document embedded file - extractor (version 3.03) + extractor (version 3.04) SYNOPSIS pdfdetach [options] [PDF-file] @@ -79,14 +79,14 @@ EXIT CODES 99 Other error. AUTHOR - The pdfinfo software and documentation are copyright 1996-2011 Glyph & + The pdfinfo software and documentation are copyright 1996-2014 Glyph & Cog, LLC. SEE ALSO - xpdf(1), pdftops(1), pdftotext(1), pdfinfo(1), pdffonts(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) + xpdf(1), pdftops(1), pdftotext(1), pdftohtml(1), pdfinfo(1), pdf- + fonts(1), pdftoppm(1), pdftopng(1), pdfimages(1), xpdfrc(5) http://www.foolabs.com/xpdf/ - 15 August 2011 pdfdetach(1) + 28 May 2014 pdfdetach(1) diff -uNrp xpdf-3.03/doc/pdfdetach.hlp xpdf-3.04/doc/pdfdetach.hlp --- xpdf-3.03/doc/pdfdetach.hlp 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdfdetach.hlp 1970-01-01 01:00:00.000000000 +0100 @@ -1,101 +0,0 @@ -! Generated automatically by mantohlp -1 pdfdetach - - pdfdetach - Portable Document Format (PDF) document embedded file - - pdfdetach [options] [PDF-file] - - Pdfdetach lists or extracts embedded files (attachments) from a Porta- - ble Document Format (PDF) file. - - () - -2 ONFIGURATION_FIL - - Pdfdetach reads a configuration file at startup. It first tries to - find the user's private config file, ~/.xpdfrc. If that doesn't exist, - it looks for a system-wide config file, typically /usr/local/etc/xpdfrc - (but this location can be changed when pdfinfo is built). See the - xpdfrc(5) man page for details. - - () - -2 OPTIONS - - Some of the following options can be set with configuration file com- - mands. These are listed in square brackets with the description of the - corresponding command line option. - - -list List all of the embedded files in the PDF file. File names are - converted to the text encoding specified by the "-enc" switch. - - -save number - Save the specified embedded file. By default, this uses the - file name associated with the embedded file (as printed by the - "-list" switch); the file name can be changed with the "-o" - switch. - - -saveall - Save all of the embedded files. This uses the file names asso- - ciated with the embedded files (as printed by the "-list" - switch). By default, the files are saved in the current direc- - tory; this can be changed with the "-o" switch. - - -o path - Set the file name used when saving an embedded file with the - "-save" switch, or the directory used by "-saveall". - - -enc encoding-name - Sets the encoding to use for text output (embedded file names). - The encoding-name must be defined with the unicodeMap command - (see xpdfrc(5)). This defaults to "Latin1" (which is a built-in - encoding). [config file: textEncoding] - - -opw password - Specify the owner password for the PDF file. Providing this - will bypass all security restrictions. - - -upw password - Specify the user password for the PDF file. - - -cfg config-file - Read config-file in place of ~/.xpdfrc or the system-wide config - file. - - -v Print copyright and version information. - - -h Print usage information. (-help and --help are equivalent.) - - () - -2 XIT_CODE - - The Xpdf tools use the following exit codes: - - 0 No error. - - 1 Error opening a PDF file. - - 2 Error opening an output file. - - 3 Error related to PDF permissions. - - 99 Other error. - - () - -2 AUTHOR - - The pdfinfo software and documentation are copyright 1996-2011 Glyph & - Cog, LLC. - - () - -2 SEE_ALSO - - xpdf(1), pdftops(1), pdftotext(1), pdfinfo(1), pdffonts(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) - http://www.foolabs.com/xpdf/ - - () - diff -uNrp xpdf-3.03/doc/pdffonts.1 xpdf-3.04/doc/pdffonts.1 --- xpdf-3.03/doc/pdffonts.1 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdffonts.1 2014-05-28 20:50:50.000000000 +0200 @@ -1,8 +1,8 @@ -.\" Copyright 1999-2011 Glyph & Cog, LLC -.TH pdffonts 1 "15 August 2011" +.\" Copyright 1999-2014 Glyph & Cog, LLC +.TH pdffonts 1 "28 May 2014" .SH NAME pdffonts \- Portable Document Format (PDF) font analyzer (version -3.03) +3.04) .SH SYNOPSIS .B pdffonts [options] @@ -34,6 +34,13 @@ can't be converted to Unicode) .TP .B object ID the font dictionary object ID (number and generation) +.TP +.B location +the font location (see the +.B \-loc +and +.B \-locPS +options). .PP PDF files can contain the following types of fonts: .PP @@ -86,6 +93,14 @@ the corresponding command line option. .BI \-f " number" Specifies the first page to analyze. .TP +.B \-loc +Shows additional information on the location of the font that will be +used when the PDF file is rasterized (with xpdf, pdftoppm, etc.). +.TP +.B \-locPS +Shows additional information on the location of the font that will be +used when the PDF file is converted to PostScript (with pdftops). +.TP .BI \-l " number" Specifies the last page to analyze. .TP @@ -128,15 +143,17 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdffonts software and documentation are copyright 1996-2011 Glyph +The pdffonts software and documentation are copyright 1996-2014 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), .BR pdftops (1), .BR pdftotext (1), +.BR pdftohtml (1), .BR pdfinfo (1), .BR pdfdetach (1), .BR pdftoppm (1), +.BR pdftopng (1), .BR pdfimages (1), .BR xpdfrc (5) .br diff -uNrp xpdf-3.03/doc/pdffonts.cat xpdf-3.04/doc/pdffonts.cat --- xpdf-3.03/doc/pdffonts.cat 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdffonts.cat 2014-05-28 20:50:50.000000000 +0200 @@ -3,7 +3,7 @@ pdffonts(1) NAME - pdffonts - Portable Document Format (PDF) font analyzer (version 3.03) + pdffonts - Portable Document Format (PDF) font analyzer (version 3.04) SYNOPSIS pdffonts [options] [PDF-file] @@ -30,6 +30,9 @@ DESCRIPTION object ID the font dictionary object ID (number and generation) + location + the font location (see the -loc and -locPS options). + PDF files can contain the following types of fonts: Type 1 @@ -59,6 +62,14 @@ OPTIONS -f number Specifies the first page to analyze. + -loc Shows additional information on the location of the font that + will be used when the PDF file is rasterized (with xpdf, + pdftoppm, etc.). + + -locPS Shows additional information on the location of the font that + will be used when the PDF file is converted to PostScript (with + pdftops). + -l number Specifies the last page to analyze. @@ -91,14 +102,14 @@ EXIT CODES 99 Other error. AUTHOR - The pdffonts software and documentation are copyright 1996-2011 Glyph & + The pdffonts software and documentation are copyright 1996-2014 Glyph & Cog, LLC. SEE ALSO - xpdf(1), pdftops(1), pdftotext(1), pdfinfo(1), pdfdetach(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) + xpdf(1), pdftops(1), pdftotext(1), pdftohtml(1), pdfinfo(1), pdfde- + tach(1), pdftoppm(1), pdftopng(1), pdfimages(1), xpdfrc(5) http://www.foolabs.com/xpdf/ - 15 August 2011 pdffonts(1) + 28 May 2014 pdffonts(1) diff -uNrp xpdf-3.03/doc/pdffonts.hlp xpdf-3.04/doc/pdffonts.hlp --- xpdf-3.03/doc/pdffonts.hlp 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdffonts.hlp 1970-01-01 01:00:00.000000000 +0100 @@ -1,114 +0,0 @@ -! Generated automatically by mantohlp -1 pdffonts - - pdffonts - Portable Document Format (PDF) font analyzer (version - - pdffonts [options] [PDF-file] - - Pdffonts lists the fonts used in a Portable Document Format (PDF) file - along with various information for each font. - - The following information is listed for each font: - - name the font name, exactly as given in the PDF file (potentially - including a subset prefix) - - type the font type -- see below for details - - emb "yes" if the font is embedded in the PDF file - - sub "yes" if the font is a subset - - uni "yes" if there is an explicit "ToUnicode" map in the PDF file - (the absence of a ToUnicode map doesn't necessarily mean that - the text can't be converted to Unicode) - - object ID - the font dictionary object ID (number and generation) - - PDF files can contain the following types of fonts: - - Type 1 - Type 1C -- aka Compact Font Format (CFF) - Type 1C (OT) -- OpenType with 8-bit CFF data - Type 3 - TrueType - TrueType (OT) -- OpenType with 8-bit TrueType data - CID Type 0 -- 16-bit font with no specified type - CID Type 0C -- 16-bit PostScript CFF font - CID Type 0C (OT) -- OpenType with CID CFF data - CID TrueType -- 16-bit TrueType font - CID TrueType (OT) -- OpenType with CID TrueType data - - () - -2 ONFIGURATION_FIL - - Pdffonts reads a configuration file at startup. It first tries to find - the user's private config file, ~/.xpdfrc. If that doesn't exist, it - looks for a system-wide config file, typically /usr/local/etc/xpdfrc - (but this location can be changed when pdffonts is built). See the - xpdfrc(5) man page for details. - - () - -2 OPTIONS - - Many of the following options can be set with configuration file com- - mands. These are listed in square brackets with the description of the - corresponding command line option. - - -f number - Specifies the first page to analyze. - - -l number - Specifies the last page to analyze. - - -opw password - Specify the owner password for the PDF file. Providing this - will bypass all security restrictions. - - -upw password - Specify the user password for the PDF file. - - -cfg config-file - Read config-file in place of ~/.xpdfrc or the system-wide config - file. - - -v Print copyright and version information. - - -h Print usage information. (-help and --help are equivalent.) - - () - -2 XIT_CODE - - The Xpdf tools use the following exit codes: - - 0 No error. - - 1 Error opening a PDF file. - - 2 Error opening an output file. - - 3 Error related to PDF permissions. - - 99 Other error. - - () - -2 AUTHOR - - The pdffonts software and documentation are copyright 1996-2011 Glyph & - Cog, LLC. - - () - -2 SEE_ALSO - - xpdf(1), pdftops(1), pdftotext(1), pdfinfo(1), pdfdetach(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) - http://www.foolabs.com/xpdf/ - - () - diff -uNrp xpdf-3.03/doc/pdfimages.1 xpdf-3.04/doc/pdfimages.1 --- xpdf-3.03/doc/pdfimages.1 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdfimages.1 2014-05-28 20:50:50.000000000 +0200 @@ -1,8 +1,8 @@ -.\" Copyright 1998-2011 Glyph & Cog, LLC -.TH pdfimages 1 "15 August 2011" +.\" Copyright 1998-2014 Glyph & Cog, LLC +.TH pdfimages 1 "28 May 2014" .SH NAME pdfimages \- Portable Document Format (PDF) image extractor -(version 3.03) +(version 3.04) .SH SYNOPSIS .B pdfimages [options] @@ -15,9 +15,9 @@ Pixmap (PPM), Portable Bitmap (PBM), or Pdfimages reads the PDF file, scans one or more pages, .IR PDF-file , and writes one PPM, PBM, or JPEG file for each image, -.IR image-root - nnn . xxx , +.IR image-root - nnnn . xxx , where -.I nnn +.I nnnn is the image number and .I xxx is the image type (.ppm, .pbm, .jpg). @@ -88,16 +88,18 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdfimages software and documentation are copyright 1998-2011 Glyph +The pdfimages software and documentation are copyright 1998-2014 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), .BR pdftops (1), .BR pdftotext (1), +.BR pdftohtml (1), .BR pdfinfo (1), .BR pdffonts (1), .BR pdfdetach (1), .BR pdftoppm (1), +.BR pdftopng (1), .BR xpdfrc (5) .br .B http://www.foolabs.com/xpdf/ diff -uNrp xpdf-3.03/doc/pdfimages.cat xpdf-3.04/doc/pdfimages.cat --- xpdf-3.03/doc/pdfimages.cat 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdfimages.cat 2014-05-28 20:50:50.000000000 +0200 @@ -4,7 +4,7 @@ pdfimages(1) NAME pdfimages - Portable Document Format (PDF) image extractor (version - 3.03) + 3.04) SYNOPSIS pdfimages [options] PDF-file image-root @@ -14,8 +14,8 @@ DESCRIPTION Portable Pixmap (PPM), Portable Bitmap (PBM), or JPEG files. Pdfimages reads the PDF file, scans one or more pages, PDF-file, and - writes one PPM, PBM, or JPEG file for each image, image-root-nnn.xxx, - where nnn is the image number and xxx is the image type (.ppm, .pbm, + writes one PPM, PBM, or JPEG file for each image, image-root-nnnn.xxx, + where nnnn is the image number and xxx is the image type (.ppm, .pbm, .jpg). NB: pdfimages extracts the raw image data from the PDF file, without @@ -72,14 +72,14 @@ EXIT CODES 99 Other error. AUTHOR - The pdfimages software and documentation are copyright 1998-2011 Glyph + The pdfimages software and documentation are copyright 1998-2014 Glyph & Cog, LLC. SEE ALSO - xpdf(1), pdftops(1), pdftotext(1), pdfinfo(1), pdffonts(1), pdfde- - tach(1), pdftoppm(1), xpdfrc(5) + xpdf(1), pdftops(1), pdftotext(1), pdftohtml(1), pdfinfo(1), pdf- + fonts(1), pdfdetach(1), pdftoppm(1), pdftopng(1), xpdfrc(5) http://www.foolabs.com/xpdf/ - 15 August 2011 pdfimages(1) + 28 May 2014 pdfimages(1) diff -uNrp xpdf-3.03/doc/pdfimages.hlp xpdf-3.04/doc/pdfimages.hlp --- xpdf-3.03/doc/pdfimages.hlp 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdfimages.hlp 1970-01-01 01:00:00.000000000 +0100 @@ -1,94 +0,0 @@ -! Generated automatically by mantohlp -1 pdfimages - - pdfimages - Portable Document Format (PDF) image extractor - - pdfimages [options] PDF-file image-root - - Pdfimages saves images from a Portable Document Format (PDF) file as - Portable Pixmap (PPM), Portable Bitmap (PBM), or JPEG files. - - Pdfimages reads the PDF file, scans one or more pages, PDF-file, and - writes one PPM, PBM, or JPEG file for each image, image-root-nnn.xxx, - where nnn is the image number and xxx is the image type (.ppm, .pbm, - .jpg). - - NB: pdfimages extracts the raw image data from the PDF file, without - performing any additional transforms. Any rotation, clipping, color - inversion, etc. done by the PDF content stream is ignored. - - () - -2 ONFIGURATION_FIL - - Pdfimages reads a configuration file at startup. It first tries to - find the user's private config file, ~/.xpdfrc. If that doesn't exist, - it looks for a system-wide config file, typically /usr/local/etc/xpdfrc - (but this location can be changed when pdfimages is built). See the - xpdfrc(5) man page for details. - - () - -2 OPTIONS - - Many of the following options can be set with configuration file com- - mands. These are listed in square brackets with the description of the - corresponding command line option. - - -f number - Specifies the first page to scan. - - -l number - Specifies the last page to scan. - - -j Normally, all images are written as PBM (for monochrome images) - or PPM (for non-monochrome images) files. With this option, - images in DCT format are saved as JPEG files. All non-DCT - images are saved in PBM/PPM format as usual. - - -opw password - Specify the owner password for the PDF file. Providing this - will bypass all security restrictions. - - -upw password - Specify the user password for the PDF file. - - -q Don't print any messages or errors. [config file: errQuiet] - - -v Print copyright and version information. - - -h Print usage information. (-help and --help are equivalent.) - - () - -2 XIT_CODE - - The Xpdf tools use the following exit codes: - - 0 No error. - - 1 Error opening a PDF file. - - 2 Error opening an output file. - - 3 Error related to PDF permissions. - - 99 Other error. - - () - -2 AUTHOR - - The pdfimages software and documentation are copyright 1998-2011 Glyph - & Cog, LLC. - - () - -2 SEE_ALSO - - xpdf(1), pdftops(1), pdftotext(1), pdfinfo(1), pdffonts(1), pdfde- - tach(1), pdftoppm(1), xpdfrc(5) - http://www.foolabs.com/xpdf/ - - () - diff -uNrp xpdf-3.03/doc/pdfinfo.1 xpdf-3.04/doc/pdfinfo.1 --- xpdf-3.03/doc/pdfinfo.1 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdfinfo.1 2014-05-28 20:50:50.000000000 +0200 @@ -1,8 +1,8 @@ -.\" Copyright 1999-2011 Glyph & Cog, LLC -.TH pdfinfo 1 "15 August 2011" +.\" Copyright 1999-2014 Glyph & Cog, LLC +.TH pdfinfo 1 "28 May 2014" .SH NAME pdfinfo \- Portable Document Format (PDF) document information -extractor (version 3.03) +extractor (version 3.04) .SH SYNOPSIS .B pdfinfo [options] @@ -57,7 +57,7 @@ encrypted flag (yes/no) print and copy permissions (if encrypted) .RE .RS -page size +page size and rotation .RE .RS file size @@ -150,15 +150,17 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdfinfo software and documentation are copyright 1996-2011 Glyph & +The pdfinfo software and documentation are copyright 1996-2014 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), .BR pdftops (1), .BR pdftotext (1), +.BR pdftohtml (1), .BR pdffonts (1), .BR pdfdetach (1), .BR pdftoppm (1), +.BR pdftopng (1), .BR pdfimages (1), .BR xpdfrc (5) .br diff -uNrp xpdf-3.03/doc/pdfinfo.cat xpdf-3.04/doc/pdfinfo.cat --- xpdf-3.03/doc/pdfinfo.cat 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdfinfo.cat 2014-05-28 20:50:50.000000000 +0200 @@ -4,7 +4,7 @@ pdfinfo(1) NAME pdfinfo - Portable Document Format (PDF) document information extractor - (version 3.03) + (version 3.04) SYNOPSIS pdfinfo [options] [PDF-file] @@ -31,7 +31,7 @@ DESCRIPTION page count encrypted flag (yes/no) print and copy permissions (if encrypted) - page size + page size and rotation file size linearized (yes/no) PDF version @@ -104,14 +104,14 @@ EXIT CODES 99 Other error. AUTHOR - The pdfinfo software and documentation are copyright 1996-2011 Glyph & + The pdfinfo software and documentation are copyright 1996-2014 Glyph & Cog, LLC. SEE ALSO - xpdf(1), pdftops(1), pdftotext(1), pdffonts(1), pdfdetach(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) + xpdf(1), pdftops(1), pdftotext(1), pdftohtml(1), pdffonts(1), pdfde- + tach(1), pdftoppm(1), pdftopng(1), pdfimages(1), xpdfrc(5) http://www.foolabs.com/xpdf/ - 15 August 2011 pdfinfo(1) + 28 May 2014 pdfinfo(1) diff -uNrp xpdf-3.03/doc/pdfinfo.hlp xpdf-3.04/doc/pdfinfo.hlp --- xpdf-3.03/doc/pdfinfo.hlp 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdfinfo.hlp 1970-01-01 01:00:00.000000000 +0100 @@ -1,126 +0,0 @@ -! Generated automatically by mantohlp -1 pdfinfo - - pdfinfo - Portable Document Format (PDF) document information - - pdfinfo [options] [PDF-file] - - Pdfinfo prints the contents of the 'Info' dictionary (plus some other - useful information) from a Portable Document Format (PDF) file. - - The 'Info' dictionary contains the following values: - - title - subject - keywords - author - creator - producer - creation date - modification date - - In addition, the following information is printed: - - tagged (yes/no) - form (AcroForm / XFA / none) - page count - encrypted flag (yes/no) - print and copy permissions (if encrypted) - page size - file size - linearized (yes/no) - PDF version - metadata (only if requested) - - () - -2 ONFIGURATION_FIL - - Pdfinfo reads a configuration file at startup. It first tries to find - the user's private config file, ~/.xpdfrc. If that doesn't exist, it - looks for a system-wide config file, typically /usr/local/etc/xpdfrc - (but this location can be changed when pdfinfo is built). See the - xpdfrc(5) man page for details. - - () - -2 OPTIONS - - Many of the following options can be set with configuration file com- - mands. These are listed in square brackets with the description of the - corresponding command line option. - - -f number - Specifies the first page to examine. If multiple pages are - requested using the "-f" and "-l" options, the size of each - requested page (and, optionally, the bounding boxes for each - requested page) are printed. Otherwise, only page one is exam- - ined. - - -l number - Specifies the last page to examine. - - -box Prints the page box bounding boxes: MediaBox, CropBox, BleedBox, - TrimBox, and ArtBox. - - -meta Prints document-level metadata. (This is the "Metadata" stream - from the PDF file's Catalog object.) - - -rawdates - Prints the raw (undecoded) date strings, directly from the PDF - file. - - -enc encoding-name - Sets the encoding to use for text output. The encoding-name - must be defined with the unicodeMap command (see xpdfrc(5)). - This defaults to "Latin1" (which is a built-in encoding). [con- - fig file: textEncoding] - - -opw password - Specify the owner password for the PDF file. Providing this - will bypass all security restrictions. - - -upw password - Specify the user password for the PDF file. - - -cfg config-file - Read config-file in place of ~/.xpdfrc or the system-wide config - file. - - -v Print copyright and version information. - - -h Print usage information. (-help and --help are equivalent.) - - () - -2 XIT_CODE - - The Xpdf tools use the following exit codes: - - 0 No error. - - 1 Error opening a PDF file. - - 2 Error opening an output file. - - 3 Error related to PDF permissions. - - 99 Other error. - - () - -2 AUTHOR - - The pdfinfo software and documentation are copyright 1996-2011 Glyph & - Cog, LLC. - - () - -2 SEE_ALSO - - xpdf(1), pdftops(1), pdftotext(1), pdffonts(1), pdfdetach(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) - http://www.foolabs.com/xpdf/ - - () - diff -uNrp xpdf-3.03/doc/pdftohtml.1 xpdf-3.04/doc/pdftohtml.1 --- xpdf-3.03/doc/pdftohtml.1 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/doc/pdftohtml.1 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,106 @@ +.\" Copyright 1997-2014 Glyph & Cog, LLC +.TH pdftohtml 1 "28 May 2014" +.SH NAME +pdftohtml \- Portable Document Format (PDF) to HTML converter +(version 3.04) +.SH SYNOPSIS +.B pdftohtml +[options] +.I PDF-file +.I HTML-dir +.SH DESCRIPTION +.B Pdftohtml +converts Portable Document Format (PDF) files to HTML. +.PP +Pdftohtml reads the PDF file, +.IR PDF-file , +and places an HTML file for each page, along with auxiliary images +in the directory, +.IR HTML-dir . +The HTML directory will be created; if it already exists, pdftohtml +will report an error. +.SH CONFIGURATION FILE +Pdftohtml reads a configuration file at startup. It first tries to +find the user's private config file, ~/.xpdfrc. If that doesn't +exist, it looks for a system-wide config file, typically +/usr/local/etc/xpdfrc (but this location can be changed when pdftohtml +is built). See the +.BR xpdfrc (5) +man page for details. +.SH OPTIONS +Many of the following options can be set with configuration file +commands. These are listed in square brackets with the description of +the corresponding command line option. +.TP +.BI \-f " number" +Specifies the first page to convert. +.TP +.BI \-l " number" +Specifies the last page to convert. +.TP +.B \-r +Specifies the resolution, in DPI, for background images. The default +is 150 DPI. +.TP +.BI \-opw " password" +Specify the owner password for the PDF file. Providing this will +bypass all security restrictions. +.TP +.BI \-upw " password" +Specify the user password for the PDF file. +.TP +.B \-q +Don't print any messages or errors. +.RB "[config file: " errQuiet ] +.TP +.BI \-cfg " config-file" +Read +.I config-file +in place of ~/.xpdfrc or the system-wide config file. +.TP +.B \-v +Print copyright and version information. +.TP +.B \-h +Print usage information. +.RB ( \-help +and +.B \-\-help +are equivalent.) +.SH BUGS +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. +.SH EXIT CODES +The Xpdf tools use the following exit codes: +.TP +0 +No error. +.TP +1 +Error opening a PDF file. +.TP +2 +Error opening an output file. +.TP +3 +Error related to PDF permissions. +.TP +99 +Other error. +.SH AUTHOR +The pdftohtml software and documentation are copyright 1996-2014 Glyph +& Cog, LLC. +.SH "SEE ALSO" +.BR xpdf (1), +.BR pdftops (1), +.BR pdftotext (1), +.BR pdfinfo (1), +.BR pdffonts (1), +.BR pdfdetach (1), +.BR pdftoppm (1), +.BR pdftopng (1), +.BR pdfimages (1), +.BR xpdfrc (5) +.br +.B http://www.foolabs.com/xpdf/ diff -uNrp xpdf-3.03/doc/pdftohtml.cat xpdf-3.04/doc/pdftohtml.cat --- xpdf-3.03/doc/pdftohtml.cat 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/doc/pdftohtml.cat 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,87 @@ +pdftohtml(1) pdftohtml(1) + + + +NAME + pdftohtml - Portable Document Format (PDF) to HTML converter (version + 3.04) + +SYNOPSIS + pdftohtml [options] PDF-file HTML-dir + +DESCRIPTION + Pdftohtml converts Portable Document Format (PDF) files to HTML. + + Pdftohtml reads the PDF file, PDF-file, and places an HTML file for + each page, along with auxiliary images in the directory, HTML-dir. The + HTML directory will be created; if it already exists, pdftohtml will + report an error. + +CONFIGURATION FILE + Pdftohtml reads a configuration file at startup. It first tries to + find the user's private config file, ~/.xpdfrc. If that doesn't exist, + it looks for a system-wide config file, typically /usr/local/etc/xpdfrc + (but this location can be changed when pdftohtml is built). See the + xpdfrc(5) man page for details. + +OPTIONS + Many of the following options can be set with configuration file com- + mands. These are listed in square brackets with the description of the + corresponding command line option. + + -f number + Specifies the first page to convert. + + -l number + Specifies the last page to convert. + + -r Specifies the resolution, in DPI, for background images. The + default is 150 DPI. + + -opw password + Specify the owner password for the PDF file. Providing this + will bypass all security restrictions. + + -upw password + Specify the user password for the PDF file. + + -q Don't print any messages or errors. [config file: errQuiet] + + -cfg config-file + Read config-file in place of ~/.xpdfrc or the system-wide config + file. + + -v Print copyright and version information. + + -h Print usage information. (-help and --help are equivalent.) + +BUGS + 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. + +EXIT CODES + The Xpdf tools use the following exit codes: + + 0 No error. + + 1 Error opening a PDF file. + + 2 Error opening an output file. + + 3 Error related to PDF permissions. + + 99 Other error. + +AUTHOR + The pdftohtml software and documentation are copyright 1996-2014 Glyph + & Cog, LLC. + +SEE ALSO + xpdf(1), pdftops(1), pdftotext(1), pdfinfo(1), pdffonts(1), pdfde- + tach(1), pdftoppm(1), pdftopng(1), pdfimages(1), xpdfrc(5) + http://www.foolabs.com/xpdf/ + + + + 28 May 2014 pdftohtml(1) diff -uNrp xpdf-3.03/doc/pdftopng.1 xpdf-3.04/doc/pdftopng.1 --- xpdf-3.03/doc/pdftopng.1 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/doc/pdftopng.1 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,119 @@ +.\" Copyright 2014 Glyph & Cog, LLC +.TH pdftopng 1 "28 May 2014" +.SH NAME +pdftopng \- Portable Document Format (PDF) to Portable Network Graphics +(PNG) converter (version 3.04) +.SH SYNOPSIS +.B pdftopng +[options] +.I PDF-file PNG-root +.SH DESCRIPTION +.B Pdftopng +converts Portable Document Format (PDF) files to color, grayscale, or +monochrome image files in Portable Network Graphics (PNG) format. +.PP +Pdftopng reads the PDF file, +.IR PDF-file , +and writes one PNG file for each page, +.IR PNG-root - nnnnnn .png, +where +.I nnnnnn +is the page number. +If +.I PNG-root +is \'-', the image is sent to stdout (this is probably only useful +when converting a single page). +.SH CONFIGURATION FILE +Pdftopng reads a configuration file at startup. It first tries to +find the user's private config file, ~/.xpdfrc. If that doesn't +exist, it looks for a system-wide config file, typically +/usr/local/etc/xpdfrc (but this location can be changed when pdftopng +is built). See the +.BR xpdfrc (5) +man page for details. +.SH OPTIONS +Many of the following options can be set with configuration file +commands. These are listed in square brackets with the description of +the corresponding command line option. +.TP +.BI \-f " number" +Specifies the first page to convert. +.TP +.BI \-l " number" +Specifies the last page to convert. +.TP +.BI \-r " number" +Specifies the resolution, in DPI. The default is 150 DPI. +.TP +.B \-mono +Generate a monochrome image (instead of a color image). +.TP +.B \-gray +Generate a grayscale image (instead of a color image). +.TP +.BI \-freetype " yes | no" +Enable or disable FreeType (a TrueType / Type 1 font rasterizer). +This defaults to "yes". +.RB "[config file: " enableFreeType ] +.TP +.BI \-aa " yes | no" +Enable or disable font anti-aliasing. This defaults to "yes". +.RB "[config file: " antialias ] +.TP +.BI \-aaVector " yes | no" +Enable or disable vector anti-aliasing. This defaults to "yes". +.RB "[config file: " vectorAntialias ] +.TP +.BI \-opw " password" +Specify the owner password for the PDF file. Providing this will +bypass all security restrictions. +.TP +.BI \-upw " password" +Specify the user password for the PDF file. +.TP +.B \-q +Don't print any messages or errors. +.RB "[config file: " errQuiet ] +.TP +.B \-v +Print copyright and version information. +.TP +.B \-h +Print usage information. +.RB ( \-help +and +.B \-\-help +are equivalent.) +.SH EXIT CODES +The Xpdf tools use the following exit codes: +.TP +0 +No error. +.TP +1 +Error opening a PDF file. +.TP +2 +Error opening an output file. +.TP +3 +Error related to PDF permissions. +.TP +99 +Other error. +.SH AUTHOR +The pdftopng software and documentation are copyright 1996-2014 Glyph +& Cog, LLC. +.SH "SEE ALSO" +.BR xpdf (1), +.BR pdftops (1), +.BR pdftotext (1), +.BR pdftohtml (1), +.BR pdfinfo (1), +.BR pdffonts (1), +.BR pdfdetach (1), +.BR pdftoppm (1), +.BR pdfimages (1), +.BR xpdfrc (5) +.br +.B http://www.foolabs.com/xpdf/ diff -uNrp xpdf-3.03/doc/pdftopng.cat xpdf-3.04/doc/pdftopng.cat --- xpdf-3.03/doc/pdftopng.cat 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/doc/pdftopng.cat 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,96 @@ +pdftopng(1) pdftopng(1) + + + +NAME + pdftopng - Portable Document Format (PDF) to Portable Network Graphics + (PNG) converter (version 3.04) + +SYNOPSIS + pdftopng [options] PDF-file PNG-root + +DESCRIPTION + Pdftopng converts Portable Document Format (PDF) files to color, + grayscale, or monochrome image files in Portable Network Graphics (PNG) + format. + + Pdftopng reads the PDF file, PDF-file, and writes one PNG file for each + page, PNG-root-nnnnnn.png, where nnnnnn is the page number. If PNG- + root is '-', the image is sent to stdout (this is probably only useful + when converting a single page). + +CONFIGURATION FILE + Pdftopng reads a configuration file at startup. It first tries to find + the user's private config file, ~/.xpdfrc. If that doesn't exist, it + looks for a system-wide config file, typically /usr/local/etc/xpdfrc + (but this location can be changed when pdftopng is built). See the + xpdfrc(5) man page for details. + +OPTIONS + Many of the following options can be set with configuration file com- + mands. These are listed in square brackets with the description of the + corresponding command line option. + + -f number + Specifies the first page to convert. + + -l number + Specifies the last page to convert. + + -r number + Specifies the resolution, in DPI. The default is 150 DPI. + + -mono Generate a monochrome image (instead of a color image). + + -gray Generate a grayscale image (instead of a color image). + + -freetype yes | no + Enable or disable FreeType (a TrueType / Type 1 font raster- + izer). This defaults to "yes". [config file: enableFreeType] + + -aa yes | no + Enable or disable font anti-aliasing. This defaults to "yes". + [config file: antialias] + + -aaVector yes | no + Enable or disable vector anti-aliasing. This defaults to "yes". + [config file: vectorAntialias] + + -opw password + Specify the owner password for the PDF file. Providing this + will bypass all security restrictions. + + -upw password + Specify the user password for the PDF file. + + -q Don't print any messages or errors. [config file: errQuiet] + + -v Print copyright and version information. + + -h Print usage information. (-help and --help are equivalent.) + +EXIT CODES + The Xpdf tools use the following exit codes: + + 0 No error. + + 1 Error opening a PDF file. + + 2 Error opening an output file. + + 3 Error related to PDF permissions. + + 99 Other error. + +AUTHOR + The pdftopng software and documentation are copyright 1996-2014 Glyph & + Cog, LLC. + +SEE ALSO + xpdf(1), pdftops(1), pdftotext(1), pdftohtml(1), pdfinfo(1), pdf- + fonts(1), pdfdetach(1), pdftoppm(1), pdfimages(1), xpdfrc(5) + http://www.foolabs.com/xpdf/ + + + + 28 May 2014 pdftopng(1) diff -uNrp xpdf-3.03/doc/pdftoppm.1 xpdf-3.04/doc/pdftoppm.1 --- xpdf-3.03/doc/pdftoppm.1 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdftoppm.1 2014-05-28 20:50:50.000000000 +0200 @@ -1,8 +1,8 @@ -.\" Copyright 2005-2011 Glyph & Cog, LLC -.TH pdftoppm 1 "15 August 2011" +.\" Copyright 2005-2014 Glyph & Cog, LLC +.TH pdftoppm 1 "28 May 2014" .SH NAME pdftoppm \- Portable Document Format (PDF) to Portable Pixmap (PPM) -converter (version 3.03) +converter (version 3.04) .SH SYNOPSIS .B pdftoppm [options] @@ -53,11 +53,6 @@ Generate a monochrome PBM file (instead .B \-gray Generate a grayscale PGM file (instead of a color PPM file). .TP -.BI \-t1lib " yes | no" -Enable or disable t1lib (a Type 1 font rasterizer). This defaults to -"yes". -.RB "[config file: " enableT1lib ] -.TP .BI \-freetype " yes | no" Enable or disable FreeType (a TrueType / Type 1 font rasterizer). This defaults to "yes". @@ -109,15 +104,17 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdftoppm software and documentation are copyright 1996-2011 Glyph +The pdftoppm software and documentation are copyright 1996-2014 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), .BR pdftops (1), .BR pdftotext (1), +.BR pdftohtml (1), .BR pdfinfo (1), .BR pdffonts (1), .BR pdfdetach (1), +.BR pdftopng (1), .BR pdfimages (1), .BR xpdfrc (5) .br diff -uNrp xpdf-3.03/doc/pdftoppm.cat xpdf-3.04/doc/pdftoppm.cat --- xpdf-3.03/doc/pdftoppm.cat 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdftoppm.cat 2014-05-28 20:50:50.000000000 +0200 @@ -4,7 +4,7 @@ pdftoppm(1) NAME pdftoppm - Portable Document Format (PDF) to Portable Pixmap (PPM) con- - verter (version 3.03) + verter (version 3.04) SYNOPSIS pdftoppm [options] PDF-file PPM-root @@ -45,16 +45,12 @@ OPTIONS -gray Generate a grayscale PGM file (instead of a color PPM file). - -t1lib yes | no - Enable or disable t1lib (a Type 1 font rasterizer). This - defaults to "yes". [config file: enableT1lib] - -freetype yes | no - Enable or disable FreeType (a TrueType / Type 1 font raster- + Enable or disable FreeType (a TrueType / Type 1 font raster- izer). This defaults to "yes". [config file: enableFreeType] -aa yes | no - Enable or disable font anti-aliasing. This defaults to "yes". + Enable or disable font anti-aliasing. This defaults to "yes". [config file: antialias] -aaVector yes | no @@ -62,7 +58,7 @@ OPTIONS [config file: vectorAntialias] -opw password - Specify the owner password for the PDF file. Providing this + Specify the owner password for the PDF file. Providing this will bypass all security restrictions. -upw password @@ -88,14 +84,14 @@ EXIT CODES 99 Other error. AUTHOR - The pdftoppm software and documentation are copyright 1996-2011 Glyph & + The pdftoppm software and documentation are copyright 1996-2014 Glyph & Cog, LLC. SEE ALSO - xpdf(1), pdftops(1), pdftotext(1), pdfinfo(1), pdffonts(1), pdfde- - tach(1), pdfimages(1), xpdfrc(5) + xpdf(1), pdftops(1), pdftotext(1), pdftohtml(1), pdfinfo(1), pdf- + fonts(1), pdfdetach(1), pdftopng(1), pdfimages(1), xpdfrc(5) http://www.foolabs.com/xpdf/ - 15 August 2011 pdftoppm(1) + 28 May 2014 pdftoppm(1) diff -uNrp xpdf-3.03/doc/pdftoppm.hlp xpdf-3.04/doc/pdftoppm.hlp --- xpdf-3.03/doc/pdftoppm.hlp 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdftoppm.hlp 1970-01-01 01:00:00.000000000 +0100 @@ -1,110 +0,0 @@ -! Generated automatically by mantohlp -1 pdftoppm - - pdftoppm - Portable Document Format (PDF) to Portable Pixmap (PPM) - - pdftoppm [options] PDF-file PPM-root - - Pdftoppm converts Portable Document Format (PDF) files to color image - files in Portable Pixmap (PPM) format, grayscale image files in Porta- - ble Graymap (PGM) format, or monochrome image files in Portable Bitmap - (PBM) format. - - Pdftoppm reads the PDF file, PDF-file, and writes one PPM file for each - page, PPM-root-nnnnnn.ppm, where nnnnnn is the page number. If PPM- - root is '-', the image is sent to stdout (this is probably only useful - when converting a single page). - - () - -2 ONFIGURATION_FIL - - Pdftoppm reads a configuration file at startup. It first tries to find - the user's private config file, ~/.xpdfrc. If that doesn't exist, it - looks for a system-wide config file, typically /usr/local/etc/xpdfrc - (but this location can be changed when pdftoppm is built). See the - xpdfrc(5) man page for details. - - () - -2 OPTIONS - - Many of the following options can be set with configuration file com- - mands. These are listed in square brackets with the description of the - corresponding command line option. - - -f number - Specifies the first page to convert. - - -l number - Specifies the last page to convert. - - -r number - Specifies the resolution, in DPI. The default is 150 DPI. - - -mono Generate a monochrome PBM file (instead of a color PPM file). - - -gray Generate a grayscale PGM file (instead of a color PPM file). - - -t1lib yes | no - Enable or disable t1lib (a Type 1 font rasterizer). This - defaults to "yes". [config file: enableT1lib] - - -freetype yes | no - Enable or disable FreeType (a TrueType / Type 1 font raster- - izer). This defaults to "yes". [config file: enableFreeType] - - -aa yes | no - Enable or disable font anti-aliasing. This defaults to "yes". - [config file: antialias] - - -aaVector yes | no - Enable or disable vector anti-aliasing. This defaults to "yes". - [config file: vectorAntialias] - - -opw password - Specify the owner password for the PDF file. Providing this - will bypass all security restrictions. - - -upw password - Specify the user password for the PDF file. - - -q Don't print any messages or errors. [config file: errQuiet] - - -v Print copyright and version information. - - -h Print usage information. (-help and --help are equivalent.) - - () - -2 XIT_CODE - - The Xpdf tools use the following exit codes: - - 0 No error. - - 1 Error opening a PDF file. - - 2 Error opening an output file. - - 3 Error related to PDF permissions. - - 99 Other error. - - () - -2 AUTHOR - - The pdftoppm software and documentation are copyright 1996-2011 Glyph & - Cog, LLC. - - () - -2 SEE_ALSO - - xpdf(1), pdftops(1), pdftotext(1), pdfinfo(1), pdffonts(1), pdfde- - tach(1), pdfimages(1), xpdfrc(5) - http://www.foolabs.com/xpdf/ - - () - diff -uNrp xpdf-3.03/doc/pdftops.1 xpdf-3.04/doc/pdftops.1 --- xpdf-3.03/doc/pdftops.1 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdftops.1 2014-05-28 20:50:50.000000000 +0200 @@ -1,8 +1,8 @@ -.\" Copyright 1996-2011 Glyph & Cog, LLC -.TH pdftops 1 "15 August 2011" +.\" Copyright 1996-2014 Glyph & Cog, LLC +.TH pdftops 1 "28 May 2014" .SH NAME pdftops \- Portable Document Format (PDF) to PostScript converter -(version 3.03) +(version 3.04) .SH SYNOPSIS .B pdftops [options] @@ -172,6 +172,7 @@ lower-left corner of the paper instead. .B \-pagecrop Treat the CropBox as the PDF page size. By default, the MediaBox is used as the page size. +.RB "[config file: " psUseCropBoxAsPage ] .TP .B \-duplex Set the Duplex pagedevice entry in the PostScript file. This tells @@ -221,15 +222,17 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdftops software and documentation are copyright 1996-2011 Glyph & +The pdftops software and documentation are copyright 1996-2014 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), .BR pdftotext (1), +.BR pdftohtml (1), .BR pdfinfo (1), .BR pdffonts (1), .BR pdfdetach (1), .BR pdftoppm (1), +.BR pdftopng (1), .BR pdfimages (1), .BR xpdfrc (5) .br diff -uNrp xpdf-3.03/doc/pdftops.cat xpdf-3.04/doc/pdftops.cat --- xpdf-3.03/doc/pdftops.cat 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdftops.cat 2014-05-28 20:50:50.000000000 +0200 @@ -4,7 +4,7 @@ pdftops(1) NAME pdftops - Portable Document Format (PDF) to PostScript converter (ver- - sion 3.03) + sion 3.04) SYNOPSIS pdftops [options] [PDF-file [PS-file]] @@ -73,54 +73,54 @@ OPTIONS page PDF file, you must use -f and -l to specify a single page. No more than one of the mode options (-eps, -form) may be given. - -form Generate a PostScript form which can be imported by software - that understands forms. A form contains a single page, so if - you use this option with a multi-page PDF file, you must use -f - and -l to specify a single page. The -level1 option cannot be + -form Generate a PostScript form which can be imported by software + that understands forms. A form contains a single page, so if + you use this option with a multi-page PDF file, you must use -f + and -l to specify a single page. The -level1 option cannot be used with -form. - -opi Generate OPI comments for all images and forms which have OPI + -opi Generate OPI comments for all images and forms which have OPI information. (This option is only available if pdftops was com- piled with OPI support.) [config file: psOPI] -noembt1 - By default, any Type 1 fonts which are embedded in the PDF file + By default, any Type 1 fonts which are embedded in the PDF file are copied into the PostScript file. This option causes pdftops - to substitute base fonts instead. Embedded fonts make Post- - Script files larger, but may be necessary for readable output. + to substitute base fonts instead. Embedded fonts make Post- + Script files larger, but may be necessary for readable output. [config file: psEmbedType1Fonts] -noembtt - By default, any TrueType fonts which are embedded in the PDF - file are copied into the PostScript file. This option causes - pdftops to substitute base fonts instead. Embedded fonts make - PostScript files larger, but may be necessary for readable out- - put. Also, some PostScript interpreters do not have TrueType + By default, any TrueType fonts which are embedded in the PDF + file are copied into the PostScript file. This option causes + pdftops to substitute base fonts instead. Embedded fonts make + PostScript files larger, but may be necessary for readable out- + put. Also, some PostScript interpreters do not have TrueType rasterizers. [config file: psEmbedTrueTypeFonts] -noembcidps - By default, any CID PostScript fonts which are embedded in the - PDF file are copied into the PostScript file. This option dis- + By default, any CID PostScript fonts which are embedded in the + PDF file are copied into the PostScript file. This option dis- ables that embedding. No attempt is made to substitute for non- - embedded CID PostScript fonts. [config file: psEmbedCID- + embedded CID PostScript fonts. [config file: psEmbedCID- PostScriptFonts] -noembcidtt By default, any CID TrueType fonts which are embedded in the PDF - file are copied into the PostScript file. This option disables + file are copied into the PostScript file. This option disables that embedding. No attempt is made to substitute for non-embed- ded CID TrueType fonts. [config file: psEmbedCIDTrueTypeFonts] -preload - Convert PDF forms to PS procedures, and preload image data. - This uses more memory in the PostScript interpreter, but gener- - ates significantly smaller PS files in situations where, e.g., + Convert PDF forms to PS procedures, and preload image data. + This uses more memory in the PostScript interpreter, but gener- + ates significantly smaller PS files in situations where, e.g., the same image is drawn on every page of a long document. -paper size - Set the paper size to one of "letter", "legal", "A4", or "A3". - This can also be set to "match", which will set the paper size - to match the size specified in the PDF file. [config file: + Set the paper size to one of "letter", "legal", "A4", or "A3". + This can also be set to "match", which will set the paper size + to match the size specified in the PDF file. [config file: psPaperSize] -paperw size @@ -130,7 +130,7 @@ OPTIONS Set the paper height, in points. [config file: psPaperSize] -nocrop - By default, output is cropped to the CropBox specified in the + By default, output is cropped to the CropBox specified in the PDF file. This option disables cropping. [config file: psCrop] -expand @@ -151,7 +151,7 @@ OPTIONS -pagecrop Treat the CropBox as the PDF page size. By default, the Media- - Box is used as the page size. + Box is used as the page size. [config file: psUseCropBoxAsPage] -duplex Set the Duplex pagedevice entry in the PostScript file. This @@ -189,14 +189,14 @@ EXIT CODES 99 Other error. AUTHOR - The pdftops software and documentation are copyright 1996-2011 Glyph & + The pdftops software and documentation are copyright 1996-2014 Glyph & Cog, LLC. SEE ALSO - xpdf(1), pdftotext(1), pdfinfo(1), pdffonts(1), pdfdetach(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) + xpdf(1), pdftotext(1), pdftohtml(1), pdfinfo(1), pdffonts(1), pdfde- + tach(1), pdftoppm(1), pdftopng(1), pdfimages(1), xpdfrc(5) http://www.foolabs.com/xpdf/ - 15 August 2011 pdftops(1) + 28 May 2014 pdftops(1) diff -uNrp xpdf-3.03/doc/pdftops.hlp xpdf-3.04/doc/pdftops.hlp --- xpdf-3.03/doc/pdftops.hlp 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdftops.hlp 1970-01-01 01:00:00.000000000 +0100 @@ -1,211 +0,0 @@ -! Generated automatically by mantohlp -1 pdftops - - pdftops - Portable Document Format (PDF) to PostScript converter - - pdftops [options] [PDF-file [PS-file]] - - Pdftops converts Portable Document Format (PDF) files to PostScript so - they can be printed. - - Pdftops reads the PDF file, PDF-file, and writes a PostScript file, PS- - file. If PS-file is not specified, pdftops converts file.pdf to - file.ps (or file.eps with the -eps option). If PS-file is '-', the - PostScript is sent to stdout. - - () - -2 ONFIGURATION_FIL - - Pdftops reads a configuration file at startup. It first tries to find - the user's private config file, ~/.xpdfrc. If that doesn't exist, it - looks for a system-wide config file, typically /usr/local/etc/xpdfrc - (but this location can be changed when pdftops is built). See the - xpdfrc(5) man page for details. - - () - -2 OPTIONS - - Many of the following options can be set with configuration file com- - mands. These are listed in square brackets with the description of the - corresponding command line option. - - -f number - Specifies the first page to print. - - -l number - Specifies the last page to print. - - -level1 - Generate Level 1 PostScript. The resulting PostScript files - will be 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. [config file: psLevel] - - -level1sep - Generate Level 1 separable PostScript. All colors are converted - to CMYK. Images are written with separate stream data for the - four components. [config file: psLevel] - - -level2 - Generate Level 2 PostScript. Level 2 supports color images and - image compression. This is the default setting. [config file: - psLevel] - - -level2sep - Generate Level 2 separable PostScript. All colors are converted - to CMYK. The PostScript separation convention operators are - used to handle custom (spot) colors. [config file: psLevel] - - -level3 - Generate Level 3 PostScript. This enables all Level 2 features - plus CID font embedding and masked image generation. [config - file: psLevel] - - -level3Sep - Generate Level 3 separable PostScript. The separation handling - 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- - page PDF file, you must use -f and -l to specify a single page. - No more than one of the mode options (-eps, -form) may be given. - - -form Generate a PostScript form which can be imported by software - that understands forms. A form contains a single page, so if - you use this option with a multi-page PDF file, you must use -f - and -l to specify a single page. The -level1 option cannot be - used with -form. - - -opi Generate OPI comments for all images and forms which have OPI - information. (This option is only available if pdftops was com- - piled with OPI support.) [config file: psOPI] - - -noembt1 - By default, any Type 1 fonts which are embedded in the PDF file - are copied into the PostScript file. This option causes pdftops - to substitute base fonts instead. Embedded fonts make Post- - Script files larger, but may be necessary for readable output. - [config file: psEmbedType1Fonts] - - -noembtt - By default, any TrueType fonts which are embedded in the PDF - file are copied into the PostScript file. This option causes - pdftops to substitute base fonts instead. Embedded fonts make - PostScript files larger, but may be necessary for readable out- - put. Also, some PostScript interpreters do not have TrueType - rasterizers. [config file: psEmbedTrueTypeFonts] - - -noembcidps - By default, any CID PostScript fonts which are embedded in the - PDF file are copied into the PostScript file. This option dis- - ables that embedding. No attempt is made to substitute for non- - embedded CID PostScript fonts. [config file: psEmbedCID- - PostScriptFonts] - - -noembcidtt - By default, any CID TrueType fonts which are embedded in the PDF - file are copied into the PostScript file. This option disables - that embedding. No attempt is made to substitute for non-embed- - ded CID TrueType fonts. [config file: psEmbedCIDTrueTypeFonts] - - -preload - Convert PDF forms to PS procedures, and preload image data. - This uses more memory in the PostScript interpreter, but gener- - ates significantly smaller PS files in situations where, e.g., - the same image is drawn on every page of a long document. - - -paper size - Set the paper size to one of "letter", "legal", "A4", or "A3". - This can also be set to "match", which will set the paper size - to match the size specified in the PDF file. [config file: - psPaperSize] - - -paperw size - Set the paper width, in points. [config file: psPaperSize] - - -paperh size - Set the paper height, in points. [config file: psPaperSize] - - -nocrop - By default, output is cropped to the CropBox specified in the - PDF file. This option disables cropping. [config file: psCrop] - - -expand - Expand PDF pages smaller than the paper to fill the paper. By - default, these pages are not scaled. [config file: psExpandS- - maller] - - -noshrink - Don't scale PDF pages which are larger than the paper. By - default, pages larger than the paper are shrunk to fit. [config - file: psShrinkLarger] - - -nocenter - By default, PDF pages smaller than the paper (after any scaling) - are centered on the paper. This option causes them to be - aligned to the lower-left corner of the paper instead. [config - file: psCenter] - - -pagecrop - Treat the CropBox as the PDF page size. By default, the Media- - Box is used as the page size. - - -duplex - Set the Duplex pagedevice entry in the PostScript file. This - tells duplex-capable printers to enable duplexing. [config - file: psDuplex] - - -opw password - Specify the owner password for the PDF file. Providing this - will bypass all security restrictions. - - -upw password - Specify the user password for the PDF file. - - -q Don't print any messages or errors. [config file: errQuiet] - - -cfg config-file - Read config-file in place of ~/.xpdfrc or the system-wide config - file. - - -v Print copyright and version information. - - -h Print usage information. (-help and --help are equivalent.) - - () - -2 XIT_CODE - - The Xpdf tools use the following exit codes: - - 0 No error. - - 1 Error opening a PDF file. - - 2 Error opening an output file. - - 3 Error related to PDF permissions. - - 99 Other error. - - () - -2 AUTHOR - - The pdftops software and documentation are copyright 1996-2011 Glyph & - Cog, LLC. - - () - -2 SEE_ALSO - - xpdf(1), pdftotext(1), pdfinfo(1), pdffonts(1), pdfdetach(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) - http://www.foolabs.com/xpdf/ - - () - diff -uNrp xpdf-3.03/doc/pdftotext.1 xpdf-3.04/doc/pdftotext.1 --- xpdf-3.03/doc/pdftotext.1 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdftotext.1 2014-05-28 20:50:50.000000000 +0200 @@ -1,8 +1,8 @@ -.\" Copyright 1997-2011 Glyph & Cog, LLC -.TH pdftotext 1 "15 August 2011" +.\" Copyright 1997-2014 Glyph & Cog, LLC +.TH pdftotext 1 "28 May 2014" .SH NAME pdftotext \- Portable Document Format (PDF) to text converter -(version 3.03) +(version 3.04) .SH SYNOPSIS .B pdftotext [options] @@ -47,21 +47,50 @@ Specifies the last page to convert. .B \-layout Maintain (as best as possible) the original physical layout of the text. The default is to \'undo' physical layout (columns, -hyphenation, etc.) and output the text in reading order. -.TP -.BI \-fixed " number" -Assume fixed-pitch (or tabular) text, with the specified character -width (in points). This forces physical layout mode. +hyphenation, etc.) and output the text in reading order. If the +.B \-fixed +option is given, character spacing within each line will be determined +by the specified character pitch. +.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 +the expense of inserting extra whitespace). If the +.B \-fixed +option is given, character spacing within each line will be determined +by the specified character pitch. +.TP +.B \-lineprinter +Line printer mode uses a strict fixed-character-pitch and -height +layout. That is, the page is broken into a grid, and characters are +placed into that grid. If the grid spacing is too small for the +actual characters, the result is extra whitespace. If the grid +spacing is too large, the result is missing whitespace. The grid +spacing can be specified using the +.B \-fixed +and +.B \-linespacing +options. +If one or both are not given on the command line, pdftotext will +attempt to compute appropriate value(s). .TP .B \-raw -Keep the text in content stream order. This is a hack which often -"undoes" column formatting, etc. Use of raw mode is no longer -recommended. -.TP -.B \-htmlmeta -Generate a simple HTML file, including the meta information. This -simply wraps the text in
 and 
and prepends the meta -headers. +Keep the text in content stream order. Depending on how the PDF file +was generated, this may or may not be useful. +.TP +.BI \-fixed " number" +Specify the character pitch (character width), in points, for physical +layout, table, or line printer mode. This is ignored in all other +modes. +.TP +.BI \-linespacing " number" +Specify the line spacing, in points, for line printer mode. This is +ignored in all other modes. +.TP +.B \-clip +Text which is hidden because of clipping is removed before doing +layout, and then added back in. This can be helpful for tables where +clipped (invisible) text would overlap the next column. .TP .BI \-enc " encoding-name" Sets the encoding to use for text output. The @@ -127,15 +156,17 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The pdftotext software and documentation are copyright 1996-2011 Glyph +The pdftotext software and documentation are copyright 1996-2014 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), .BR pdftops (1), +.BR pdftohtml (1), .BR pdfinfo (1), .BR pdffonts (1), .BR pdfdetach (1), .BR pdftoppm (1), +.BR pdftopng (1), .BR pdfimages (1), .BR xpdfrc (5) .br diff -uNrp xpdf-3.03/doc/pdftotext.cat xpdf-3.04/doc/pdftotext.cat --- xpdf-3.03/doc/pdftotext.cat 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdftotext.cat 2014-05-28 20:50:50.000000000 +0200 @@ -4,27 +4,27 @@ pdftotext(1) NAME pdftotext - Portable Document Format (PDF) to text converter (version - 3.03) + 3.04) SYNOPSIS pdftotext [options] [PDF-file [text-file]] DESCRIPTION - Pdftotext converts Portable Document Format (PDF) files to plain text. + Pdftotext converts Portable Document Format (PDF) files to plain text. - Pdftotext reads the PDF file, PDF-file, and writes a text file, text- - file. If text-file is not specified, pdftotext converts file.pdf to + Pdftotext reads the PDF file, PDF-file, and writes a text file, text- + file. If text-file is not specified, pdftotext converts file.pdf to file.txt. If text-file is '-', the text is sent to stdout. CONFIGURATION FILE - Pdftotext reads a configuration file at startup. It first tries to + Pdftotext reads a configuration file at startup. It first tries to find the user's private config file, ~/.xpdfrc. If that doesn't exist, it looks for a system-wide config file, typically /usr/local/etc/xpdfrc - (but this location can be changed when pdftotext is built). See the + (but this location can be changed when pdftotext is built). See the xpdfrc(5) man page for details. OPTIONS - Many of the following options can be set with configuration file com- + Many of the following options can be set with configuration file com- mands. These are listed in square brackets with the description of the corresponding command line option. @@ -35,22 +35,44 @@ OPTIONS Specifies the last page to convert. -layout - Maintain (as best as possible) the original physical layout of - the text. The default is to 'undo' physical layout (columns, - hyphenation, etc.) and output the text in reading order. + Maintain (as best as possible) the original physical layout of + the text. The default is to 'undo' physical layout (columns, + hyphenation, etc.) and output the text in reading order. If the + -fixed option is given, character spacing within each line will + be determined by the specified character pitch. + + -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 + option is given, character spacing within each line will be + determined by the specified character pitch. + + -lineprinter + Line printer mode uses a strict fixed-character-pitch and + -height layout. That is, the page is broken into a grid, and + characters are placed into that grid. If the grid spacing is + too small for the actual characters, the result is extra white- + space. If the grid spacing is too large, the result is missing + whitespace. The grid spacing can be specified using the -fixed + and -linespacing options. If one or both are not given on the + command line, pdftotext will attempt to compute appropriate + value(s). - -fixed number - Assume fixed-pitch (or tabular) text, with the specified charac- - ter width (in points). This forces physical layout mode. + -raw Keep the text in content stream order. Depending on how the PDF + file was generated, this may or may not be useful. - -raw Keep the text in content stream order. This is a hack which - often "undoes" column formatting, etc. Use of raw mode is no - longer recommended. - - -htmlmeta - Generate a simple HTML file, including the meta information. - This simply wraps the text in
 and 
and prepends the - meta headers. + -fixed number + Specify the character pitch (character width), in points, for + physical layout, table, or line printer mode. This is ignored + in all other modes. + + -linespacing number + Specify the line spacing, in points, for line printer mode. + This is ignored in all other modes. + + -clip Text which is hidden because of clipping is removed before doing + layout, and then added back in. This can be helpful for tables + where clipped (invisible) text would overlap the next column. -enc encoding-name Sets the encoding to use for text output. The encoding-name @@ -102,14 +124,14 @@ EXIT CODES 99 Other error. AUTHOR - The pdftotext software and documentation are copyright 1996-2011 Glyph + The pdftotext software and documentation are copyright 1996-2014 Glyph & Cog, LLC. SEE ALSO - xpdf(1), pdftops(1), pdfinfo(1), pdffonts(1), pdfdetach(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) + xpdf(1), pdftops(1), pdftohtml(1), pdfinfo(1), pdffonts(1), pdfde- + tach(1), pdftoppm(1), pdftopng(1), pdfimages(1), xpdfrc(5) http://www.foolabs.com/xpdf/ - 15 August 2011 pdftotext(1) + 28 May 2014 pdftotext(1) diff -uNrp xpdf-3.03/doc/pdftotext.hlp xpdf-3.04/doc/pdftotext.hlp --- xpdf-3.03/doc/pdftotext.hlp 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/pdftotext.hlp 1970-01-01 01:00:00.000000000 +0100 @@ -1,127 +0,0 @@ -! Generated automatically by mantohlp -1 pdftotext - - pdftotext - Portable Document Format (PDF) to text converter - - pdftotext [options] [PDF-file [text-file]] - - Pdftotext converts Portable Document Format (PDF) files to plain text. - - Pdftotext reads the PDF file, PDF-file, and writes a text file, text- - file. If text-file is not specified, pdftotext converts file.pdf to - file.txt. If text-file is '-', the text is sent to stdout. - - () - -2 ONFIGURATION_FIL - - Pdftotext reads a configuration file at startup. It first tries to - find the user's private config file, ~/.xpdfrc. If that doesn't exist, - it looks for a system-wide config file, typically /usr/local/etc/xpdfrc - (but this location can be changed when pdftotext is built). See the - xpdfrc(5) man page for details. - - () - -2 OPTIONS - - Many of the following options can be set with configuration file com- - mands. These are listed in square brackets with the description of the - corresponding command line option. - - -f number - Specifies the first page to convert. - - -l number - Specifies the last page to convert. - - -layout - Maintain (as best as possible) the original physical layout of - the text. The default is to 'undo' physical layout (columns, - hyphenation, etc.) and output the text in reading order. - - -fixed number - Assume fixed-pitch (or tabular) text, with the specified charac- - ter width (in points). This forces physical layout mode. - - -raw Keep the text in content stream order. This is a hack which - often "undoes" column formatting, etc. Use of raw mode is no - longer recommended. - - -htmlmeta - Generate a simple HTML file, including the meta information. - This simply wraps the text in
 and 
and prepends the - meta headers. - - -enc encoding-name - Sets the encoding to use for text output. The encoding-name - must be defined with the unicodeMap command (see xpdfrc(5)). - The encoding name is case-sensitive. This defaults to "Latin1" - (which is a built-in encoding). [config file: textEncoding] - - -eol unix | dos | mac - Sets the end-of-line convention to use for text output. [config - file: textEOL] - - -nopgbrk - Don't insert page breaks (form feed characters) between pages. - [config file: textPageBreaks] - - -opw password - Specify the owner password for the PDF file. Providing this - will bypass all security restrictions. - - -upw password - Specify the user password for the PDF file. - - -q Don't print any messages or errors. [config file: errQuiet] - - -cfg config-file - Read config-file in place of ~/.xpdfrc or the system-wide config - file. - - -v Print copyright and version information. - - -h Print usage information. (-help and --help are equivalent.) - - () - -2 BUGS - - 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. - - () - -2 XIT_CODE - - The Xpdf tools use the following exit codes: - - 0 No error. - - 1 Error opening a PDF file. - - 2 Error opening an output file. - - 3 Error related to PDF permissions. - - 99 Other error. - - () - -2 AUTHOR - - The pdftotext software and documentation are copyright 1996-2011 Glyph - & Cog, LLC. - - () - -2 SEE_ALSO - - xpdf(1), pdftops(1), pdfinfo(1), pdffonts(1), pdfdetach(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) - http://www.foolabs.com/xpdf/ - - () - diff -uNrp xpdf-3.03/doc/sample-xpdfrc xpdf-3.04/doc/sample-xpdfrc --- xpdf-3.03/doc/sample-xpdfrc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/sample-xpdfrc 2014-05-28 20:50:50.000000000 +0200 @@ -79,9 +79,8 @@ #----- misc settings -# Enable t1lib, FreeType, and anti-aliased text. +# Enable FreeType, and anti-aliased text. -#enableT1lib yes #enableFreeType yes #antialias yes diff -uNrp xpdf-3.03/doc/xpdf.1 xpdf-3.04/doc/xpdf.1 --- xpdf-3.03/doc/xpdf.1 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/xpdf.1 2014-05-28 20:50:50.000000000 +0200 @@ -1,7 +1,7 @@ -.\" Copyright 1996-2011 Glyph & Cog, LLC -.TH xpdf 1 "15 August 2011" +.\" Copyright 1996-2014 Glyph & Cog, LLC +.TH xpdf 1 "28 May 2014" .SH NAME -xpdf \- Portable Document Format (PDF) file viewer for X (version 3.03) +xpdf \- Portable Document Format (PDF) file viewer for X (version 3.04) .SH SYNOPSIS .B xpdf [options] @@ -102,11 +102,6 @@ Start in continuous view mode, i.e., wit the whole document. .RB "[config file: " continuousView ] .TP -.BI \-t1lib " yes | no" -Enable or disable t1lib (a Type 1 font rasterizer). This defaults to -"yes". -.RB "[config file: " enableT1lib ] -.TP .BI \-freetype " yes | no" Enable or disable FreeType (a TrueType / Type 1 font rasterizer). This defaults to "yes". @@ -638,7 +633,11 @@ Redraw the window. Raise the window to the front. .TP .B closeWindow -Close the window. +Close the window. If this was the last open window, clear the window, +but don't quit from Xpdf. +.TP +.B closeWindowOrQuit +Close the window. If this was the last open window, quit from Xpdf. .TP .BI run( external-command-string ) Run an external command. The following escapes are allowed in the @@ -668,6 +667,13 @@ command string: %% => % .fi +The external command string will often contain spaces, so the whole +command must be quoted in the xpdfrc file: +.nf + + bind x "run(ls -l)" + +.fi .TP .B openOutline Open the outline pane. @@ -799,7 +805,7 @@ The default key bindings are as follows: bind w any zoomFitWidth bind alt-f any toggleFullScreenMode bind ctrl-l any redraw - bind ctrl-w any closeWindow + bind ctrl-w any closeWindowOrQuit bind ? any about bind q any quit bind Q any quit @@ -863,15 +869,17 @@ Error related to PDF permissions. 99 Other error. .SH AUTHOR -The xpdf software and documentation are copyright 1996-2011 Glyph & +The xpdf software and documentation are copyright 1996-2014 Glyph & Cog, LLC. .SH "SEE ALSO" .BR pdftops (1), .BR pdftotext (1), +.BR pdftohtml (1), .BR pdfinfo (1), .BR pdffonts (1), .BR pdfdetach (1), .BR pdftoppm (1), +.BR pdftopng (1), .BR pdfimages (1), .BR xpdfrc (5) .br diff -uNrp xpdf-3.03/doc/xpdf.cat xpdf-3.04/doc/xpdf.cat --- xpdf-3.03/doc/xpdf.cat 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/xpdf.cat 2014-05-28 20:50:50.000000000 +0200 @@ -3,7 +3,7 @@ xpdf(1) NAME - xpdf - Portable Document Format (PDF) file viewer for X (version 3.03) + xpdf - Portable Document Format (PDF) file viewer for X (version 3.04) SYNOPSIS xpdf [options] [PDF-file [page | +dest]] @@ -60,15 +60,15 @@ OPTIONS serve color table entries. This is ignored with private col- ormaps and on TrueColor visuals. [X resource: xpdf.rgbCubeSize] - -rv Set reverse video mode. This reverses the colors of everything - except images. It may not always produce great results for PDF - files which do weird things with color. This also causes the - paper color to default to black. [X resource: xpdf.reverseV- + -rv Set reverse video mode. This reverses the colors of everything + except images. It may not always produce great results for PDF + files which do weird things with color. This also causes the + paper color to default to black. [X resource: xpdf.reverseV- ideo] -papercolor color Set the "paper color", i.e., the background of the page display. - This will not work too well with PDF files that do things like + This will not work too well with PDF files that do things like filling in white behind the text. [X resource: xpdf.paperColor] -mattecolor color @@ -87,16 +87,12 @@ OPTIONS -cont Start in continuous view mode, i.e., with one vertical scroll bar for the whole document. [config file: continuousView] - -t1lib yes | no - Enable or disable t1lib (a Type 1 font rasterizer). This - defaults to "yes". [config file: enableT1lib] - -freetype yes | no - Enable or disable FreeType (a TrueType / Type 1 font raster- + Enable or disable FreeType (a TrueType / Type 1 font raster- izer). This defaults to "yes". [config file: enableFreeType] -aa yes | no - Enable or disable font anti-aliasing. This defaults to "yes". + Enable or disable font anti-aliasing. This defaults to "yes". [config file: antialias] -aaVector yes | no @@ -104,15 +100,15 @@ OPTIONS [config file: vectorAntialias] -ps PS-file - Set the default file name for PostScript output (i.e., the name + Set the default file name for PostScript output (i.e., the name which will appear in the print dialog). This can also be of the form '|command' to pipe the PostScript through a command. [con- fig file: psFile] -paper size - Set the paper size to one of "letter", "legal", "A4", or "A3". - This can also be set to "match", which will set the paper size - to match the size specified in the PDF file. [config file: + Set the paper size to one of "letter", "legal", "A4", or "A3". + This can also be set to "match", which will set the paper size + to match the size specified in the PDF file. [config file: psPaperSize] -paperw size @@ -122,14 +118,14 @@ OPTIONS Set the paper height, in points. [config file: psPaperSize] -level1 - Generate Level 1 PostScript. The resulting PostScript files - will be significantly larger (if they contain images), but will - print on Level 1 printers. This also converts all images to + Generate Level 1 PostScript. The resulting PostScript files + will be significantly larger (if they contain images), but will + print on Level 1 printers. This also converts all images to black and white. [config file: psLevel] -enc encoding-name - Sets the encoding to use for text output. The encoding-name - must be defined with the unicodeMap command (see xpdfrc(5)). + Sets the encoding to use for text output. The encoding-name + must be defined with the unicodeMap command (see xpdfrc(5)). This defaults to "Latin1" (which is a built-in encoding). [con- fig file: textEncoding] @@ -138,7 +134,7 @@ OPTIONS file: textEOL] -opw password - Specify the owner password for the PDF file. Providing this + Specify the owner password for the PDF file. Providing this will bypass all security restrictions. -upw password @@ -148,11 +144,11 @@ OPTIONS Open xpdf in full-screen mode, useful for presentations. -remote name - Start/contact xpdf remote server with specified name (see the + Start/contact xpdf remote server with specified name (see the REMOTE SERVER MODE section below). -exec command - Execute a command (see the COMMANDS section below) in an xpdf + Execute a command (see the COMMANDS section below) in an xpdf remote server window (with -remote only). -reload @@ -162,7 +158,7 @@ OPTIONS -quit Kill xpdf remote server (with -remote only). - -cmd Print commands as they're executed (useful for debugging). + -cmd Print commands as they're executed (useful for debugging). [config file: printCommands] -q Don't print any messages or errors. [config file: errQuiet] @@ -199,7 +195,7 @@ OPTIONS tips on the toolbar buttons. xpdf.fullScreenMatteColor - Sets the matte color to be used in full-screen mode. The + Sets the matte color to be used in full-screen mode. The default setting is "black". CONTROLS @@ -214,19 +210,19 @@ CONTROLS Move backward or forward along the history path. 'Page' entry box - Move to a specific page number. Click in the box to activate + Move to a specific page number. Click in the box to activate it, type the page number, then hit return. 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). binoculars button Find a text string. print button - Bring up a dialog for generating a PostScript file. The dialog - has options to set the pages to be printed and the PostScript + Bring up a dialog for generating a PostScript file. The dialog + has options to set the pages to be printed and the PostScript file name. The file name can be '-' for stdout or '|command' to pipe the PostScript through a command, e.g., '|lpr'. @@ -240,7 +236,6 @@ CONTROLS 'Quit' button Quit xpdf. - Menu Pressing the right mouse button will post a popup menu with the follow- ing commands: @@ -249,11 +244,11 @@ CONTROLS Open a new PDF file via a file requester. Open in new window... - Create a new window and open a new PDF file via a file + Create a new window and open a new PDF file via a file requester. - Reload Reload the current PDF file. Note that Xpdf will reload the - file automatically (on a page change or redraw) if it has + Reload 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. Save as... @@ -266,50 +261,45 @@ CONTROLS Rotate the page 90 degrees counterclockwise. Rotate clockwise - Rotate the page 90 degrees clockwise. The two rotate commands - are intended primarily for PDF files where the rotation isn't + Rotate the page 90 degrees clockwise. The two rotate commands + are intended primarily for PDF files where the rotation isn't correctly specified in the file. Zoom to selection Zoom in to the currently selected rectangle. - Close Close the current window. If this is the only open window, the + Close Close the current window. If this is the only open window, the document is closed, but the window is left open (i.e., this menu command won't quit xpdf). Quit Quit xpdf. - Outline - If the PDF contains an outline (a.k.a., bookmarks), there will be an - outline pane on the left side of the window. The width of the outline + If the PDF contains an outline (a.k.a., bookmarks), there will be an + outline pane on the left side of the window. The width of the outline pane is adjustable with a vertical split bar via the knob near its bot- tom end. - Text selection - Dragging the mouse with the left button held down will highlight an - arbitrary rectangle. Any text inside this rectangle will be copied to + Dragging the mouse with the left button held down will highlight an + arbitrary rectangle. Any text inside this rectangle will be copied to the X selection buffer. - Links 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 + 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 call an external command (see the WEB BROWSERS section below). - Panning Dragging the mouse with the middle button held down pans the window. - Key bindings o Open a new PDF file via a file requester. - r Reload the current PDF file. Note that Xpdf will reload the - file automatically (on a page change or redraw) if it has + r 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-L @@ -327,14 +317,14 @@ CONTROLS control-P Print. - n Move to the next page. Scrolls to the top of the page, unless + n Move to the next page. Scrolls to the top of the page, unless scroll lock is turned on. - p Move to the previous page. Scrolls to the top of the page, + p Move to the previous page. Scrolls to the top of the page, unless scroll lock is turned on. or or - 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. or or or @@ -374,9 +364,9 @@ CONTROLS q Quit xpdf. WEB BROWSERS - If you want to run xpdf automatically from netscape or mosaic (and - probably other browsers) when you click on a link to a PDF file, you - need to edit (or create) the files .mime.types and .mailcap in your + If you want to run xpdf automatically from netscape or mosaic (and + probably other browsers) when you click on a link to a PDF file, you + need to edit (or create) the files .mime.types and .mailcap in your home directory. In .mime.types add the line: application/pdf pdf @@ -388,17 +378,17 @@ WEB BROWSERS Make sure that xpdf is on your executable search path. - When you click on a URL link in a PDF file, xpdf will execute the com- - mand specified by the urlCommand config file option, replacing an - occurrence of '%s' with the URL. For example, to call netscape with + When you click on a URL link in a PDF file, xpdf will execute the com- + mand specified by the urlCommand config file option, replacing an + occurrence of '%s' with the URL. For example, to call netscape with the URL, add this line to your config file: urlCommand "netscape -remote 'openURL(%s)'" COMMANDS Xpdf's key and mouse bindings are user-configurable, using the bind and - unbind options 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 options 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. Available Commands @@ -418,14 +408,14 @@ COMMANDS Go to the last page in the PDF file. gotoLastPageNoScroll - Go to the last page in the PDF file, with the current relative + Go to the last page in the PDF file, with the current relative scroll position. nextPage Go to the next page. nextPageNoScroll - Go to the next page, with the current relative scroll position. + Go to the next page, with the current relative scroll position. prevPage Go to the previous page. @@ -459,19 +449,19 @@ COMMANDS Scroll down by n pixels, moving to the next page if appropriate. scrollToTopEdge - Scroll to the top edge of the current page, with no horizontal + Scroll to the top edge of the current page, with no horizontal movement. scrollToBottomEdge - Scroll to the bottom edge of the current page, with no horizon- + Scroll to the bottom edge of the current page, with no horizon- tal movement. scrollToLeftEdge - Scroll to the left edge of the current page, with no vertical + Scroll to the left edge of the current page, with no vertical movement. scrollToRightEdge - Scroll to the right edge of the current page, with no vertical + Scroll to the right edge of the current page, with no vertical movement. scrollToTopLeft @@ -507,7 +497,7 @@ COMMANDS Rotate the page 90 degrees counterclockwise. setSelection(pg,ulx,uly,lrx,lry) - Set the selection to the specified coordinates on the specified + Set the selection to the specified coordinates on the specified page. continuousMode @@ -540,7 +530,7 @@ COMMANDS Open a specified PDF file in a new window. openFileAtDest(file,dest) - Open a specified PDF file in this window and go to a named des- + Open a specified PDF file in this window and go to a named des- tination. openFileAtDestInNewWin(file,dest) @@ -554,10 +544,15 @@ COMMANDS raise Raise the window to the front. closeWindow - Close the window. + Close the window. If this was the last open window, clear the + window, but don't quit from Xpdf. + + closeWindowOrQuit + Close the window. If this was the last open window, quit from + Xpdf. run(external-command-string) - Run an external command. The following escapes are allowed in + Run an external command. The following escapes are allowed in the command string: %f => PDF file name (or an empty string if no @@ -582,6 +577,11 @@ COMMANDS %k => y coordinate of the mouse pointer %% => % + The external command string will often contain spaces, so the + whole command must be quoted in the xpdfrc file: + + bind x "run(ls -l)" + openOutline Open the outline pane. @@ -703,7 +703,7 @@ COMMANDS bind w any zoomFitWidth bind alt-f any toggleFullScreenMode bind ctrl-l any redraw - bind ctrl-w any closeWindow + bind ctrl-w any closeWindowOrQuit bind ? any about bind q any quit bind Q any quit @@ -755,14 +755,14 @@ EXIT CODES 99 Other error. AUTHOR - The xpdf software and documentation are copyright 1996-2011 Glyph & + The xpdf software and documentation are copyright 1996-2014 Glyph & Cog, LLC. SEE ALSO - pdftops(1), pdftotext(1), pdfinfo(1), pdffonts(1), pdfdetach(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) + pdftops(1), pdftotext(1), pdftohtml(1), pdfinfo(1), pdffonts(1), pdfde- + tach(1), pdftoppm(1), pdftopng(1), pdfimages(1), xpdfrc(5) http://www.foolabs.com/xpdf/ - 15 August 2011 xpdf(1) + 28 May 2014 xpdf(1) diff -uNrp xpdf-3.03/doc/xpdf.hlp xpdf-3.04/doc/xpdf.hlp --- xpdf-3.03/doc/xpdf.hlp 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/xpdf.hlp 1970-01-01 01:00:00.000000000 +0100 @@ -1,782 +0,0 @@ -! Generated automatically by mantohlp -1 xpdf - - xpdf - Portable Document Format (PDF) file viewer for X (version 3.03) - - xpdf [options] [PDF-file [page | +dest]] - - Xpdf is a viewer for Portable Document Format (PDF) files. (These are - also sometimes also called 'Acrobat' files, from the name of Adobe's - PDF software.) Xpdf runs under the X Window System on UNIX, VMS, and - OS/2. - - To run xpdf, simply type: - - xpdf file.pdf - - where file.pdf is your PDF file. The file name can be followed by a - number specifying the page which should be displayed first, e.g.: - - xpdf file.pdf 18 - - You can also give a named destination, prefixed with '+' in place of - the page number. (This is only useful with PDF files that provide - named destination targets.) - - You can also start xpdf without opening any files: - - xpdf - - () - -2 ONFIGURATION_FIL - - Xpdf reads a configuration file at startup. It first tries to find the - user's private config file, ~/.xpdfrc. If that doesn't exist, it looks - for a system-wide config file, typically /usr/local/etc/xpdfrc (but - this location can be changed when xpdf is built). See the xpdfrc(5) - man page for details. - - () - -2 OPTIONS - - Many of the following options can be set with configuration file com- - mands or X resources. These are listed in square brackets with the - description of the corresponding command line option. - - -g geometry - Set the initial window geometry. (-geometry is equivalent.) [X - resource: xpdf.geometry] - - -title title - Set the window title. By default, the title will be "xpdf: - foo.pdf". [X resource: xpdf.title] - - -cmap Install a private colormap. This is ignored on TrueColor visu- - als. [X resource: xpdf.installCmap] - - -rgb number - Set the size of largest RGB cube xpdf will try to allocate. The - default is 5 (for a 5x5x5 cube); set to a smaller number to con- - serve color table entries. This is ignored with private col- - ormaps and on TrueColor visuals. [X resource: xpdf.rgbCubeSize] - - -rv Set reverse video mode. This reverses the colors of everything - except images. It may not always produce great results for PDF - files which do weird things with color. This also causes the - paper color to default to black. [X resource: xpdf.reverseV- - ideo] - - -papercolor color - Set the "paper color", i.e., the background of the page display. - This will not work too well with PDF files that do things like - filling in white behind the text. [X resource: xpdf.paperColor] - - -mattecolor color - Set the matte color, i.e., the color used for background outside - the actual page area. (There is a separate setting, - xpdf.fullScreenMatteColor, for full-screen mode.) [X resource: - xpdf.matteColor] - - -z zoom - Set the initial zoom factor. A number specifies a zoom percent- - age, where 100 means 72 dpi. You may also specify 'page', to - fit the page to the window size, or 'width', to fit the page - width to the window width. [config file: initialZoom; or X - resource: xpdf.initialZoom] - - -cont Start in continuous view mode, i.e., with one vertical scroll - bar for the whole document. [config file: continuousView] - - -t1lib yes | no - Enable or disable t1lib (a Type 1 font rasterizer). This - defaults to "yes". [config file: enableT1lib] - - -freetype yes | no - Enable or disable FreeType (a TrueType / Type 1 font raster- - izer). This defaults to "yes". [config file: enableFreeType] - - -aa yes | no - Enable or disable font anti-aliasing. This defaults to "yes". - [config file: antialias] - - -aaVector yes | no - Enable or disable vector anti-aliasing. This defaults to "yes". - [config file: vectorAntialias] - - -ps PS-file - Set the default file name for PostScript output (i.e., the name - which will appear in the print dialog). This can also be of the - form '|command' to pipe the PostScript through a command. [con- - fig file: psFile] - - -paper size - Set the paper size to one of "letter", "legal", "A4", or "A3". - This can also be set to "match", which will set the paper size - to match the size specified in the PDF file. [config file: - psPaperSize] - - -paperw size - Set the paper width, in points. [config file: psPaperSize] - - -paperh size - Set the paper height, in points. [config file: psPaperSize] - - -level1 - Generate Level 1 PostScript. The resulting PostScript files - will be significantly larger (if they contain images), but will - print on Level 1 printers. This also converts all images to - black and white. [config file: psLevel] - - -enc encoding-name - Sets the encoding to use for text output. The encoding-name - must be defined with the unicodeMap command (see xpdfrc(5)). - This defaults to "Latin1" (which is a built-in encoding). [con- - fig file: textEncoding] - - -eol unix | dos | mac - Sets the end-of-line convention to use for text output. [config - file: textEOL] - - -opw password - Specify the owner password for the PDF file. Providing this - will bypass all security restrictions. - - -upw password - Specify the user password for the PDF file. - - -fullscreen - Open xpdf in full-screen mode, useful for presentations. - - -remote name - Start/contact xpdf remote server with specified name (see the - REMOTE SERVER MODE section below). - - -exec command - Execute a command (see the COMMANDS section below) in an xpdf - remote server window (with -remote only). - - -reload - Reload xpdf remote server window (with -remote only). - - -raise Raise xpdf remote server window (with -remote only). - - -quit Kill xpdf remote server (with -remote only). - - -cmd Print commands as they're executed (useful for debugging). - [config file: printCommands] - - -q Don't print any messages or errors. [config file: errQuiet] - - -cfg config-file - Read config-file in place of ~/.xpdfrc or the system-wide config - file. - - -v Print copyright and version information. - - -h Print usage information. (-help and --help are equivalent.) - - Several other standard X options and resources will work as expected: - - -display display - [X resource: xpdf.display] - - -fg color - (-foreground is equivalent.) [X resource: xpdf*Foreground] - - -bg color - (-background is equivalent.) [X resource: xpdf*Background] - - -font font - (-fn is equivalent.) [X resource: xpdf*fontList] - - The color and font options only affect the user interface elements, not - the PDF display (the 'paper'). - - The following X resources do not have command line option equivalents: - - xpdf.toolTipEnable - Enables (if set to true) or disables (if set to false) the tool- - tips on the toolbar buttons. - - xpdf.fullScreenMatteColor - Sets the matte color to be used in full-screen mode. The - default setting is "black". - - () - -2 CONTROLS - - On-screen controls, at the bottom of the xpdf window - left/right arrow buttons - Move to the previous/next page. - - double left/right arrow buttons - Move backward or forward by ten pages. - - dashed left/right arrow buttons - Move backward or forward along the history path. - - 'Page' entry box - Move to a specific page number. Click in the box to activate - it, type the page number, then hit return. - - zoom popup menu - Change the zoom factor (see the description of the -z option - above). - - binoculars button - Find a text string. - - print button - Bring up a dialog for generating a PostScript file. The dialog - has options to set the pages to be printed and the PostScript - file name. The file name can be '-' for stdout or '|command' to - pipe the PostScript through a command, e.g., '|lpr'. - - '?' button - Bring up the 'about xpdf' window. - - link info - The space between the '?' and 'Quit' buttons is used to show the - URL or external file name when the mouse is over a link. - - 'Quit' button - Quit xpdf. - - Menu - Pressing the right mouse button will post a popup menu with the follow- - ing commands: - - Open... - Open a new PDF file via a file requester. - - Open in new window... - Create a new window and open a new PDF file via a file - requester. - - Reload 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. - - Save as... - Save the current file via a file requester. - - Continuous view - Toggles between single page and continuous view modes. - - Rotate counterclockwise - Rotate the page 90 degrees counterclockwise. - - Rotate clockwise - Rotate the page 90 degrees clockwise. The two rotate commands - are intended primarily for PDF files where the rotation isn't - correctly specified in the file. - - Zoom to selection - Zoom in to the currently selected rectangle. - - Close Close the current window. If this is the only open window, the - document is closed, but the window is left open (i.e., this menu - command won't quit xpdf). - - Quit Quit xpdf. - - Outline - If the PDF contains an outline (a.k.a., bookmarks), there will be an - outline pane on the left side of the window. The width of the outline - pane is adjustable with a vertical split bar via the knob near its bot- - tom end. - - Text selection - Dragging the mouse with the left button held down will highlight an - arbitrary rectangle. Any text inside this rectangle will be copied to - the X selection buffer. - - Links - 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 call an external command (see the - WEB BROWSERS section below). - - Panning - Dragging the mouse with the middle button held down pans the window. - - Key bindings - o Open a new PDF file via a file requester. - - r 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-L - Redraw the current page. - - control-W - Close the current window. - - f or control-F - Find a text string. - - control-G - Find next occurrence. - - control-P - Print. - - n Move to the next page. Scrolls to the top of the page, unless - scroll lock is turned on. - - p Move to the previous page. Scrolls to the top of the page, - unless scroll lock is turned on. - - or or - Scroll down on the current page; if already at bottom, move to - next page. - - or or or - Scroll up on the current page; if already at top, move to previ- - ous page. - - v Move forward along the history path. - - b Move backward along the history path. - - Scroll to top of current page. - - Scroll to bottom of current page. - - control- - Scroll to first page of document. - - control- - Scroll to last page of document. - - arrows Scroll the current page. - - g Activate the page number text field ("goto page"). - - 0 Set the zoom factor to 125%. - - + Zoom in (increment the zoom factor by 1). - - - Zoom out (decrement the zoom factor by 1). - - z Set the zoom factor to 'page' (fit page to window). - - w Set the zoom factor to 'width' (fit page width to window). - - alt-F Toggle full-screen mode. - - q Quit xpdf. - - () - -2 WEB_BROWSERS - - If you want to run xpdf automatically from netscape or mosaic (and - probably other browsers) when you click on a link to a PDF file, you - need to edit (or create) the files .mime.types and .mailcap in your - home directory. In .mime.types add the line: - - application/pdf pdf - - In .mailcap add the lines: - - # Use xpdf to view PDF files. - application/pdf; xpdf -q %s - - Make sure that xpdf is on your executable search path. - - When you click on a URL link in a PDF file, xpdf will execute the com- - mand specified by the urlCommand config file option, replacing an - occurrence of '%s' with the URL. For example, to call netscape with - the URL, add this line to your config file: - - urlCommand "netscape -remote 'openURL(%s)'" - - () - -2 COMMANDS - - Xpdf's key and mouse bindings are user-configurable, using the bind and - unbind options 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. - - Available Commands - The following commands are supported: - - gotoPage(page) - Go to the specified page. - - gotoPageNoScroll(page) - Go to the specified page, with the current relative scroll posi- - tion. - - gotoDest(dest) - Go to a named destination. - - gotoLastPage - Go to the last page in the PDF file. - - gotoLastPageNoScroll - Go to the last page in the PDF file, with the current relative - scroll position. - - nextPage - Go to the next page. - - nextPageNoScroll - Go to the next page, with the current relative scroll position. - - prevPage - Go to the previous page. - - prevPageNoScroll - Go to the previous page, with the current relative scroll posi- - tion. - - pageUp Scroll up by one screenful. - - pageDown - Scroll down by one screenful. - - scrollLeft(n) - Scroll left by n pixels. - - scrollRight(n) - Scroll right by n pixels. - - scrollUp(n) - Scroll up by n pixels. - - scrollDown(n) - Scroll down by n pixels. - - scrollUpPrevPage(n) - Scroll up by n pixels, moving to the previous page if appropri- - ate. - - scrollDownPrevPage(n) - Scroll down by n pixels, moving to the next page if appropriate. - - scrollToTopEdge - Scroll to the top edge of the current page, with no horizontal - movement. - - scrollToBottomEdge - Scroll to the bottom edge of the current page, with no horizon- - tal movement. - - scrollToLeftEdge - Scroll to the left edge of the current page, with no vertical - movement. - - scrollToRightEdge - Scroll to the right edge of the current page, with no vertical - movement. - - scrollToTopLeft - Scroll to the top-left corner of the current page. - - scrollToBottomRight - Scroll to the bottom-right corner of the current page. - - goForward - Move forward along the history path. - - goBackward - Move backward along the history path. - - zoomPercent(z) - Set the zoom factor to z%. - - zoomFitPage - Set the zoom factor to fit-page. - - zoomFitWidth - Set the zoom factor to fit-width. - - zoomIn Zoom in - go to the next higher zoom factor. - - zoomOut - Zoom out - go the next lower zoom factor. - - rotateCW - Rotate the page 90 degrees clockwise. - - rotateCCW - Rotate the page 90 degrees counterclockwise. - - setSelection(pg,ulx,uly,lrx,lry) - Set the selection to the specified coordinates on the specified - page. - - continuousMode - Go to continuous view mode. - - singlePageMode - Go to single-page view mode. - - toggleContinuousMode - Toggle between continuous and single page view modes. - - fullScreenMode - Go to full-screen mode. - - windowMode - Go to window (non-full-screen) mode. - - toggleFullScreenMode - Toggle between full-screen and window modes. - - open Open a PDF file in this window, using the open dialog. - - openInNewWin - Open a PDF file in a new window, using the open dialog. - - openFile(file) - Open a specified PDF file in this window. - - openFileInNewWin(file) - Open a specified PDF file in a new window. - - openFileAtDest(file,dest) - Open a specified PDF file in this window and go to a named des- - tination. - - openFileAtDestInNewWin(file,dest) - Open a specified PDF file in a new window and go to a named des- - tination. - - reload Reload the current PDF file. - - redraw Redraw the window. - - raise Raise the window to the front. - - closeWindow - Close the window. - - run(external-command-string) - Run an external command. The following escapes are allowed in - the command string: - - %f => PDF file name (or an empty string if no - file is open) - %b => PDF file base name, i.e., file name minus - the extension (or an empty string if no - file is open) - %u => link URL (or an empty string if not over - a URL link) - %p => current page number (or an empty string if - no file is open) - %x => selection upper-left x coordinate - (or 0 if there is no selection) - %y => selection upper-left y coordinate - (or 0 if there is no selection) - %X => selection lower-right x coordinate - (or 0 if there is no selection) - %Y => selection lower-right y coordinate - (or 0 if there is no selection) - %i => page containing the mouse pointer - %j => x coordinate of the mouse pointer - %k => y coordinate of the mouse pointer - %% => % - - openOutline - Open the outline pane. - - closeOutline - Close the outline pane. - - toggleOutline - Toggle the outline pane between open and closed. - - scrollOutlineDown(n) - Scroll the outline down by n increments. - - scrollOutlineUp(n) - Scroll the outline up by n increments. - - focusToDocWin - Set the keyboard focus to the main document window. - - focusToPageNum - Set the keyboard focus to the page number text box. - - find Open the 'find' dialog. - - findNext - Finds the next occurrence of the search string (no dialog). - - print Open the 'print' dialog. - - about Open the 'about' dialog. - - quit Quit from xpdf. - - The following commands depend on the current mouse position: - - startSelection - Start a selection, which will be extended as the mouse moves. - - endSelection - End a selection. - - startPan - Start a pan, which will scroll the document as the mouse moves - - endPan End a pan. - - postPopupMenu - Display the popup menu. - - followLink - Follow a hyperlink (does nothing if the mouse is not over a - link). - - followLinkInNewWin - Follow a hyperlink, opening PDF files in a new window (does - nothing if the mouse is not over a link). For links to non-PDF - files, this command is identical to followLink. - - followLinkNoSel - Same as followLink, but does nothing if there is a non-empty - selection. (This is useful as a mouse button binding.) - - followLinkInNewWinNoSel - Same as followLinkInNewWin, but does nothing if there is a non- - empty selection. (This is useful as a mouse button binding.) - - Default Bindings - The default mouse bindings are as follows: - - bind mousePress1 any startSelection - bind mouseRelease1 any endSelection followLinkNoSel - bind mousePress2 any startPan - bind mouseRelease2 any endPan - bind mousePress3 any postPopupMenu - bind mousePress4 any scrollUpPrevPage(16) - bind mousePress5 any scrollDownNextPage(16) - bind mousePress6 any scrollLeft(16) - bind mousePress7 any scrollRight(16) - - The default key bindings are as follows: - - bind ctrl-home any gotoPage(1) - bind home any scrollToTopLeft - bind ctrl-end any gotoLastPage - bind end any scrollToBottomRight - bind pgup any pageUp - bind backspace any pageUp - bind delete any pageUp - bind pgdn any pageDown - bind space any pageDown - bind left any scrollLeft(16) - bind right any scrollRight(16) - bind up any scrollUp(16) - bind down any scrollDown(16) - bind o any open - bind O any open - bind r any reload - bind R any reload - bind f any find - bind F any find - bind ctrl-f any find - bind ctrl-g any findNext - bind ctrl-p any print - bind n scrLockOff nextPage - bind N scrLockOff nextPage - bind n scrLockOn nextPageNoScroll - bind N scrLockOn nextPageNoScroll - bind p scrLockOff prevPage - bind P scrLockOff prevPage - bind p scrLockOn prevPageNoScroll - bind P scrLockOn prevPageNoScroll - bind v any goForward - bind b any goBackward - bind g any focusToPageNum - bind 0 any zoomPercent(125) - bind + any zoomIn - bind - any zoomOut - bind z any zoomFitPage - bind w any zoomFitWidth - bind alt-f any toggleFullScreenMode - bind ctrl-l any redraw - bind ctrl-w any closeWindow - bind ? any about - bind q any quit - bind Q any quit - - Previous versions of xpdf included a "viKeys" X resource. It is no - longer available, but the following bindings are equivalent: - - bind h any scrollLeft(16) - bind l any scrollRight(16) - bind k any scrollUp(16) - bind j any scrollDown(16) - - () - -2 REMOTE_SERVER_MODE - - Xpdf can be started in remote server mode by specifying a server name - (in addition to the file name and page number). For example: - - xpdf -remote myServer file.pdf - - If there is currently no xpdf running in server mode with the name - 'myServer', a new xpdf window will be opened. If another command: - - xpdf -remote myServer another.pdf 9 - - is issued, a new copy of xpdf will not be started. Instead, the first - xpdf (the server) will load another.pdf and display page nine. If the - file name is the same: - - xpdf -remote myServer another.pdf 4 - - the xpdf server will simply display the specified page. - - The -raise option tells the server to raise its window; it can be spec- - ified with or without a file name and page number. - - The -quit option tells the server to close its window and exit. - - () - -2 XIT_CODE - - The Xpdf tools use the following exit codes: - - 0 No error. - - 1 Error opening a PDF file. - - 2 Error opening an output file. - - 3 Error related to PDF permissions. - - 99 Other error. - - () - -2 AUTHOR - - The xpdf software and documentation are copyright 1996-2011 Glyph & - Cog, LLC. - - () - -2 SEE_ALSO - - pdftops(1), pdftotext(1), pdfinfo(1), pdffonts(1), pdfdetach(1), - pdftoppm(1), pdfimages(1), xpdfrc(5) - http://www.foolabs.com/xpdf/ - - () - diff -uNrp xpdf-3.03/doc/xpdfrc.5 xpdf-3.04/doc/xpdfrc.5 --- xpdf-3.03/doc/xpdfrc.5 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/xpdfrc.5 2014-05-28 20:50:50.000000000 +0200 @@ -1,7 +1,7 @@ -.\" Copyright 2002-2011 Glyph & Cog, LLC -.TH xpdfrc 5 "15 August 2011" +.\" Copyright 2002-2014 Glyph & Cog, LLC +.TH xpdfrc 5 "28 May 2014" .SH NAME -xpdfrc \- configuration file for Xpdf tools (version 3.03) +xpdfrc \- configuration file for Xpdf tools (version 3.04) .SH DESCRIPTION All of the Xpdf tools read a single configuration file. If you have a .I .xpdfrc @@ -17,6 +17,9 @@ The xpdfrc file consists of a series of 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. +.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 @@ -259,6 +262,10 @@ If set to "yes", PostScript output is cr in the PDF file; otherwise no cropping is done. This defaults to "yes". .TP +.BR psUseCropBoxAsPage " yes | no" +If set to "yes", PostScript output treats the CropBox as the page size. +By default, this is "no", and the MediaBox is used as the page size. +.TP .BR psExpandSmaller " yes | no" If set to "yes", PDF pages smaller than the PostScript imageable area are expanded to fill the imageable area. Otherwise, no scalling is @@ -299,12 +306,23 @@ the Xpdf tools were compiled with OPI su If set to "yes", the ASCIIHexEncode filter will be used instead of ASCII85Encode for binary data. This defaults to "no". .TP +.BR psLZW " yes | no" +If set to "yes", the LZWEncode filter will be used for lossless +compression in PostScript output; if set to "no", the RunLengthEncode +filter will be used instead. LZW generates better compression +(smaller PS files), but may not be supported by some printers. This +defaults to "yes". +.TP .BR psUncompressPreloadedImages " yes | no" If set to "yes", all preloaded images in PS files will uncompressed. If set to "no", the original compressed images will be used when possible. The "yes" setting is useful to work around certain buggy PostScript interpreters. This defaults to "no". .TP +.BR psMinLineWidth " float" +Set the minimum line width, in points, for PostScript output. The +default value is 0 (no minimum). +.TP .BR psRasterResolution " float" Set the resolution (in dpi) for rasterized pages in PostScript output. (Pdftops will rasterize pages which use transparency.) This defaults @@ -314,6 +332,11 @@ to 300. If set to "yes", rasterized pages in PS files will be monochrome (8-bit gray) instead of color. This defaults to "no". .TP +.BR psRasterSliceSize " pixels" +When rasterizing pages, pdftops splits the page into horizontal +"slices", to limit memory usage. This option sets the maximum slice +size, in pixels. This defaults to 20000000 (20 million). +.TP .BR psAlwaysRasterize " yes | no" If set to "yes", all PostScript output will be rasterized. This defaults to "no". @@ -359,7 +382,7 @@ If set to "yes", text extraction will ke "no", text extraction will discard tiny (smaller than 3 point) characters after the first 50000 per page, avoiding extremely slow run times for PDF files that use special fonts to do shading or -cross-hatching. This defaults to "no". +cross-hatching. This defaults to "yes". .SH MISCELLANEOUS SETTINGS .TP .BR initialZoom " \fIpercentage\fR | page | width" @@ -373,12 +396,6 @@ If set to "yes", xpdf will start in cont one vertical screoll bar for the whole document. This defaults to "no". .TP -.BR enableT1lib " yes | no" -Enables or disables use of t1lib (a Type 1 font rasterizer). This is -only relevant if the Xpdf tools were built with t1lib support. -("enableT1lib" replaces the old "t1libControl" option.) This option -defaults to "yes". -.TP .BR enableFreeType " yes | no" Enables or disables use of FreeType (a TrueType / Type 1 font rasterizer). This is only relevant if the Xpdf tools were built with @@ -499,6 +516,21 @@ setting mapNumericCharNames to "no" is u this leads to usable text, and in other cases it leads to gibberish -- there is no way for Xpdf to tell. This defaults to "no". .TP +.BI mapExtTrueTypeFontsViaUnicode " yes | no" +When rasterizing text using an external TrueType font, there are two +options for handling character codes. If +mapExtTrueTypeFontsViaUnicode is set to "yes", Xpdf will use the font +encoding/ToUnicode info to map character codes to Unicode, and then +use the font's Unicode cmap to map Unicode to GIDs. If +mapExtTrueTypeFontsViaUnicode is set to "no", Xpdf will assume the +character codes are GIDs (i.e., use an identity mapping). This +defaults to "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 bind " modifiers-key context command ..." Add a key or mouse button binding. .I Modifiers @@ -633,7 +665,6 @@ textEncoding UTF-8 textEOL unix # misc options -enableT1lib yes enableFreeType yes launchCommand viewer-script urlCommand "netscape \-remote 'openURL(%s)'" @@ -649,16 +680,18 @@ Depending on build options, it may be pl 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-2011 Glyph & +The Xpdf software and documentation are copyright 1996-2014 Glyph & Cog, LLC. .SH "SEE ALSO" .BR xpdf (1), .BR pdftops (1), .BR pdftotext (1), +.BR pdftohtml (1), .BR pdfinfo (1), .BR pdffonts (1), .BR pdfdetach (1), .BR pdftoppm (1), +.BR pdftopng (1), .BR pdfimages (1) .br .B http://www.foolabs.com/xpdf/ diff -uNrp xpdf-3.03/doc/xpdfrc.cat xpdf-3.04/doc/xpdfrc.cat --- xpdf-3.03/doc/xpdfrc.cat 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/xpdfrc.cat 2014-05-28 20:50:50.000000000 +0200 @@ -3,7 +3,7 @@ xpdfrc(5) NAME - xpdfrc - configuration file for Xpdf tools (version 3.03) + xpdfrc - configuration file for Xpdf tools (version 3.04) DESCRIPTION All of the Xpdf tools read a single configuration file. If you have a @@ -17,65 +17,68 @@ DESCRIPTION line. Blank lines and lines starting with a '#' (comments) are ignored. - The following sections list all of the configuration options, sorted + Arguments may be quoted, using "double-quote" characters, e.g., for + file names that contain spaces. + + The following sections list all of the configuration options, sorted into functional groups. There is an examples section at the end. INCLUDE FILES include config-file - Includes the specified config file. The effect of this is - equivalent to inserting the contents of config-file directly - into the parent config file in place of the include command. + Includes the specified config file. The effect of this is + equivalent to inserting the contents of config-file directly + into the parent config file in place of the include command. Config files can be nested arbitrarily deeply. CHARACTER MAPPING nameToUnicode map-file - Specifies a file with the mapping from character names to Uni- - code. This is used to handle PDF fonts that have valid encod- - ings but no ToUnicode entry. Each line of a nameToUnicode file + Specifies a file with the mapping from character names to Uni- + code. This is used to handle PDF fonts that have valid encod- + ings but no ToUnicode entry. Each line of a nameToUnicode file looks like this: hex-string name - The hex-string is the Unicode (UCS-2) character index, and name - is the corresponding character name. Multiple nameToUnicode - files can be used; if a character name is given more than once, - the code in the last specified file is used. There is a built- - in default nameToUnicode table with all of Adobe's standard + The hex-string is the Unicode (UCS-2) character index, and name + is the corresponding character name. Multiple nameToUnicode + files can be used; if a character name is given more than once, + the code in the last specified file is used. There is a built- + in default nameToUnicode table with all of Adobe's standard character names. cidToUnicode registry-ordering map-file Specifies the file with the mapping from character collection to - Unicode. Each line of a cidToUnicode file represents one char- + Unicode. Each line of a cidToUnicode file represents one char- acter: hex-string - The hex-string is the Unicode (UCS-2) index for that character. - The first line maps CID 0, the second line CID 1, etc. File - size is determined by size of the character collection. Only + The hex-string is the Unicode (UCS-2) index for that character. + The first line maps CID 0, the second line CID 1, etc. File + size is determined by size of the character collection. Only one file is allowed per character collection; the last specified file is used. There are no built-in cidToUnicode mappings. unicodeToUnicode font-name-substring map-file - This is used to work around PDF fonts which have incorrect Uni- + This is used to work around PDF fonts which have incorrect Uni- code information. It specifies a file which maps from the given - (incorrect) Unicode indexes to the correct ones. The mapping - will be used for any font whose name contains font-name-sub- - string. Each line of a unicodeToUnicode file represents one + (incorrect) Unicode indexes to the correct ones. The mapping + will be used for any font whose name contains font-name-sub- + string. Each line of a unicodeToUnicode file represents one Unicode character: in-hex out-hex1 out-hex2 ... - The in-hex field is an input (incorrect) Unicode index, and the - rest of the fields are one or more output (correct) Unicode - indexes. Each occurrence of in-hex will be converted to the + The in-hex field is an input (incorrect) Unicode index, and the + rest of the fields are one or more output (correct) Unicode + indexes. Each occurrence of in-hex will be converted to the specified output sequence. unicodeMap encoding-name map-file - Specifies the file with mapping from Unicode to encoding-name. + Specifies the file with mapping from Unicode to encoding-name. These encodings are used for text output (see below). Each line - of a unicodeMap file represents a range of one or more Unicode - characters which maps linearly to a range in the output encod- + of a unicodeMap file represents a range of one or more Unicode + characters which maps linearly to a range in the output encod- ing: in-start-hex in-end-hex out-start-hex @@ -84,149 +87,154 @@ CHARACTER MAPPING in-hex out-hex - The in-start-hex and in-end-hex fields (or the single in-hex - field) specify the Unicode range. The out-start-hex field (or - the out-hex field) specifies the start of the output encoding - range. The length of the out-start-hex (or out-hex) string + The in-start-hex and in-end-hex fields (or the single in-hex + field) specify the Unicode range. The out-start-hex field (or + the out-hex field) specifies the start of the output encoding + range. The length of the out-start-hex (or out-hex) string determines the length of the output characters (e.g., UTF-8 uses - different numbers of bytes to represent characters in different - ranges). Entries must be given in increasing Unicode order. - Only one file is allowed per encoding; the last specified file - is used. The Latin1, ASCII7, Symbol, ZapfDingbats, UTF-8, and + different numbers of bytes to represent characters in different + ranges). Entries must be given in increasing Unicode order. + Only one file is allowed per encoding; the last specified file + is used. The Latin1, ASCII7, Symbol, ZapfDingbats, UTF-8, and UCS-2 encodings are predefined. cMapDir registry-ordering dir - Specifies a search directory, dir, for CMaps for the reg- - istry-ordering character collection. There can be multiple - directories for a particular collection. There are no default + Specifies a search directory, dir, for CMaps for the reg- + istry-ordering character collection. There can be multiple + directories for a particular collection. There are no default CMap directories. toUnicodeDir dir - Specifies a search directory, dir, for ToUnicode CMaps. There - can be multiple ToUnicode directories. There are no default + Specifies a search directory, dir, for ToUnicode CMaps. There + can be multiple ToUnicode directories. There are no default ToUnicode directories. GENERAL FONT CONFIGURATION fontFile PDF-font-name font-file - Maps a PDF font, PDF-font-name, to a font for display or Post- - Script output. The font file, font-file, can be any type - allowed in a PDF file. This command can be used for 8-bit or + Maps a PDF font, PDF-font-name, to a font for display or Post- + Script output. The font file, font-file, can be any type + allowed in a PDF file. This command can be used for 8-bit or 16-bit (CID) fonts. fontDir dir - Specifies a search directory for font files. There can be mul- + Specifies a search directory for font files. There can be mul- tiple fontDir commands; all of the specified directories will be - searched in order. The font files can be Type 1 (.pfa or .pfb) + searched in order. The font files can be Type 1 (.pfa or .pfb) or TrueType (.ttf or .ttc); other files in the directory will be - ignored. The font file name (not including the extension) must - exactly match the PDF font name. This search is performed if - the font name doesn't match any of the fonts declared with the + ignored. The font file name (not including the extension) must + exactly match the PDF font name. This search is performed if + the font name doesn't match any of the fonts declared with the fontFile command. There are no default fontDir directories. fontFileCC registry-ordering font-file - Maps the registry-ordering character collection to a font for - display or PostScript output. This mapping is used if the font - name doesn't match any of the fonts declared with the fontFile, + Maps the registry-ordering character collection to a font for + display or PostScript output. This mapping is used if the font + name doesn't match any of the fonts declared with the fontFile, fontDir, psResidentFont16, or psResidentFontCC commands. POSTSCRIPT FONT CONFIGURATION psFontPassthrough yes | no If set to "yes", pass 8-bit font names through to the PostScript - output without substitution. Fonts which are not embedded in - the PDF file are expected to be available on the printer. This + output without substitution. Fonts which are not embedded in + the PDF file are expected to be available on the printer. This defaults to "no". psResidentFont PDF-font-name PS-font-name When the 8-bit font PDF-font-name is used (without embedding) in - a PDF file, it will be translated to the PostScript font - PS-font-name, which is assumed to be resident in the printer. - Typically, PDF-font-name and PS-font-name are the same. By + a PDF file, it will be translated to the PostScript font + PS-font-name, which is assumed to be resident in the printer. + Typically, PDF-font-name and PS-font-name are the same. By default, only the Base-14 fonts are assumed to be resident. psResidentFont16 PDF-font-name wMode PS-font-name encoding When the 16-bit (CID) font PDF-font-name with writing mode wMode is used (without embedding) in a PDF file, it will be translated - to the PostScript font PS-font-name, which is assumbed to be - resident in the printer. The writing mode must be either 'H' - for horizontal or 'V' for vertical. The resident font is - assumed to use the specified encoding (which must have been + to the PostScript font PS-font-name, which is assumbed to be + resident in the printer. The writing mode must be either 'H' + for horizontal or 'V' for vertical. The resident font is + assumed to use the specified encoding (which must have been defined with the unicodeMap command). psResidentFontCC registry-ordering wMode PS-font-name encoding - When a 16-bit (CID) font using the registry-ordering character + When a 16-bit (CID) font using the registry-ordering character collection and wMode writing mode is used (without embedding) in - a PDF file, the PostScript font, PS-font-name, is substituted - for it. The substituted font is assumbed to be resident in the - printer. The writing mode must be either 'H' for horizontal or + a PDF file, the PostScript font, PS-font-name, is substituted + for it. The substituted font is assumbed to be resident in the + printer. The writing mode must be either 'H' for horizontal or 'V' for vertical. The resident font is assumed to use the spec- ified encoding (which must have been defined with the unicodeMap command). psEmbedType1Fonts yes | no - If set to "no", prevents embedding of Type 1 fonts in generated + If set to "no", prevents embedding of Type 1 fonts in generated PostScript. This defaults to "yes". psEmbedTrueTypeFonts yes | no - If set to "no", prevents embedding of TrueType fonts in gener- + If set to "no", prevents embedding of TrueType fonts in gener- ated PostScript. This defaults to "yes". psEmbedCIDTrueTypeFonts yes | no If set to "no", prevents embedding of CID TrueType fonts in gen- erated PostScript. For Level 3 PostScript, this generates a CID - font, for lower levels it generates a non-CID composite font. + font, for lower levels it generates a non-CID composite font. This defaults to "yes". psEmbedCIDPostScriptFonts yes | no - If set to "no", prevents embedding of CID PostScript fonts in - generated PostScript. For Level 3 PostScript, this generates a - CID font, for lower levels it generates a non-CID composite + If set to "no", prevents embedding of CID PostScript fonts in + generated PostScript. For Level 3 PostScript, this generates a + CID font, for lower levels it generates a non-CID composite font. This defaults to "yes". POSTSCRIPT CONTROL psPaperSize width(pts) height(pts) Sets the paper size for PostScript output. The width and height - parameters give the paper size in PostScript points (1 point = + parameters give the paper size in PostScript points (1 point = 1/72 inch). psPaperSize letter | legal | A4 | A3 | match - Sets the paper size for PostScript output to a standard size. - The default paper size is set when xpdf and pdftops are built, + Sets the paper size for PostScript output to a standard size. + The default paper size is set when xpdf and pdftops are built, typically to "letter" or "A4". This can also be set to "match", which will set the paper size to match the size specified in the PDF file. psImageableArea llx lly urx ury - Sets the imageable area for PostScript output. The four inte- - gers are the coordinates of the lower-left and upper-right cor- + Sets the imageable area for PostScript output. The four inte- + gers are the coordinates of the lower-left and upper-right cor- ners of the imageable region, specified in points (with the ori- gin being the lower-left corner of the paper). This defaults to - the full paper size; the psPaperSize option will reset the + the full paper size; the psPaperSize option will reset the imageable area coordinates. psCrop yes | no - If set to "yes", PostScript output is cropped to the CropBox - specified in the PDF file; otherwise no cropping is done. This + If set to "yes", PostScript output is cropped to the CropBox + specified in the PDF file; otherwise no cropping is done. This defaults to "yes". + psUseCropBoxAsPage yes | no + If set to "yes", PostScript output treats the CropBox as the + page size. By default, this is "no", and the MediaBox is used + as the page size. + psExpandSmaller yes | no If set to "yes", PDF pages smaller than the PostScript imageable - area are expanded to fill the imageable area. Otherwise, no + area are expanded to fill the imageable area. Otherwise, no scalling is done on smaller pages. This defaults to "no". psShrinkLarger yes | no - If set to yes, PDF pages larger than the PostScript imageable - area are shrunk to fit the imageable area. Otherwise, no scal- + If set to yes, PDF pages larger than the PostScript imageable + area are shrunk to fit the imageable area. Otherwise, no scal- ing is done on larger pages. This defaults to "yes". psCenter yes | no - If set to yes, PDF pages smaller than the PostScript imageable - area (after any scaling) are centered in the imageable area. - Otherwise, they are aligned at the lower-left corner of the + If set to yes, PDF pages smaller than the PostScript imageable + area (after any scaling) are centered in the imageable area. + Otherwise, they are aligned at the lower-left corner of the imageable area. This defaults to "yes". psDuplex yes | no - If set to "yes", the generated PostScript will set the "Duplex" - pagedevice entry. This tells duplex-capable printers to enable + If set to "yes", the generated PostScript will set the "Duplex" + pagedevice entry. This tells duplex-capable printers to enable duplexing. This defaults to "no". psLevel level1 | level1sep | level2 | level2sep | level3 | level3Sep @@ -234,28 +242,39 @@ POSTSCRIPT CONTROL "level2". psPreload yes | no - If set to "yes", PDF forms are converted to PS procedures, and - image data is preloaded. This uses more memory in the Post- + If set to "yes", PDF forms are converted to PS procedures, and + image data is preloaded. This uses more memory in the Post- Script interpreter, but generates significantly smaller PS files in situations where, e.g., the same image is drawn on every page of a long document. This defaults to "no". psOPI yes | no - If set to "yes", generates PostScript OPI comments for all - images and forms which have OPI information. This option is + If set to "yes", generates PostScript OPI comments for all + images and forms which have OPI information. This option is only available if the Xpdf tools were compiled with OPI support. This defaults to "no". psASCIIHex yes | no - If set to "yes", the ASCIIHexEncode filter will be used instead + If set to "yes", the ASCIIHexEncode filter will be used instead of ASCII85Encode for binary data. This defaults to "no". + psLZW yes | no + If set to "yes", the LZWEncode filter will be used for lossless + compression in PostScript output; if set to "no", the RunLength- + Encode filter will be used instead. LZW generates better com- + pression (smaller PS files), but may not be supported by some + printers. This defaults to "yes". + psUncompressPreloadedImages yes | no - If set to "yes", all preloaded images in PS files will uncom- + If set to "yes", all preloaded images in PS files will uncom- pressed. If set to "no", the original compressed images will be - used when possible. The "yes" setting is useful to work around + used when possible. The "yes" setting is useful to work around certain buggy PostScript interpreters. This defaults to "no". + psMinLineWidth float + Set the minimum line width, in points, for PostScript output. + The default value is 0 (no minimum). + psRasterResolution float Set the resolution (in dpi) for rasterized pages in PostScript output. (Pdftops will rasterize pages which use transparency.) @@ -265,6 +284,11 @@ POSTSCRIPT CONTROL If set to "yes", rasterized pages in PS files will be monochrome (8-bit gray) instead of color. This defaults to "no". + psRasterSliceSize pixels + When rasterizing pages, pdftops splits the page into horizontal + "slices", to limit memory usage. This option sets the maximum + slice size, in pixels. This defaults to 20000000 (20 million). + psAlwaysRasterize yes | no If set to "yes", all PostScript output will be rasterized. This defaults to "no". @@ -307,7 +331,7 @@ TEXT CONTROL set to "no", text extraction will discard tiny (smaller than 3 point) characters after the first 50000 per page, avoiding extremely slow run times for PDF files that use special fonts to - do shading or cross-hatching. This defaults to "no". + do shading or cross-hatching. This defaults to "yes". MISCELLANEOUS SETTINGS initialZoom percentage | page | width @@ -321,136 +345,145 @@ MISCELLANEOUS SETTINGS with one vertical screoll bar for the whole document. This defaults to "no". - enableT1lib yes | no - Enables or disables use of t1lib (a Type 1 font rasterizer). - This is only relevant if the Xpdf tools were built with t1lib - support. ("enableT1lib" replaces the old "t1libControl" - option.) This option defaults to "yes". - enableFreeType yes | no - Enables or disables use of FreeType (a TrueType / Type 1 font + Enables or disables use of FreeType (a TrueType / Type 1 font rasterizer). This is only relevant if the Xpdf tools were built with FreeType support. ("enableFreeType" replaces the old "freetypeControl" option.) This option defaults to "yes". enableFreeType yes | no - Enables or disables use of FreeType (a TrueType / Type 1 font + Enables or disables use of FreeType (a TrueType / Type 1 font rasterizer). This is only relevant if the Xpdf tools were built with FreeType support. ("enableFreeType" replaces the old "freetypeControl" option.) This option defaults to "yes". disableFreeTypeHinting yes | no - If this is set to "yes", FreeType hinting will be forced off. + If this is set to "yes", FreeType hinting will be forced off. This option defaults to "no". antialias yes | no - Enables or disables font anti-aliasing in the PDF rasterizer. + Enables or disables font anti-aliasing in the PDF rasterizer. This option affects all font rasterizers. ("antialias" replaces the anti-aliasing control provided by the old "t1libControl" and "freetypeControl" options.) This default to "yes". vectorAntialias yes | no - Enables or disables anti-aliasing of vector graphics in the PDF + Enables or disables anti-aliasing of vector graphics in the PDF rasterizer. This defaults to "yes". antialiasPrinting yes | no - If this is "yes", bitmaps sent to the printer will be - antialiased (according to the "antialias" and "vectorAntialias" - settings). If this is "no", printed bitmaps will not be + If this is "yes", bitmaps sent to the printer will be + antialiased (according to the "antialias" and "vectorAntialias" + settings). If this is "no", printed bitmaps will not be antialiased. This defaults to "no". strokeAdjust yes | no - Enables or disables stroke adjustment. Stroke adjustment moves + Enables or disables stroke adjustment. Stroke adjustment moves horizontal and vertical lines by up to half a pixel to make them - look "cleaner" when vector anti-aliasing is enabled. This + look "cleaner" when vector anti-aliasing is enabled. This defaults to "yes". screenType dispersed | clustered | stochasticClustered - Sets the halftone screen type, which will be used when generat- - ing a monochrome (1-bit) bitmap. The three options are dis- - persed-dot dithering, clustered-dot dithering (with a round dot - and 45-degree screen angle), and stochastic clustered-dot - dithering. By default, "stochasticClustered" is used for reso- + Sets the halftone screen type, which will be used when generat- + ing a monochrome (1-bit) bitmap. The three options are dis- + persed-dot dithering, clustered-dot dithering (with a round dot + and 45-degree screen angle), and stochastic clustered-dot + dithering. By default, "stochasticClustered" is used for reso- lutions of 300 dpi and higher, and "dispersed" is used for reso- lutions lower then 300 dpi. screenSize integer - Sets the size of the (square) halftone screen threshold matrix. - By default, this is 4 for dispersed-dot dithering, 10 for clus- - tered-dot dithering, and 100 for stochastic clustered-dot + Sets the size of the (square) halftone screen threshold matrix. + By default, this is 4 for dispersed-dot dithering, 10 for clus- + tered-dot dithering, and 100 for stochastic clustered-dot dithering. screenDotRadius integer - Sets the halftone screen dot radius. This is only used when - screenType is set to stochasticClustered, and it defaults to 2. - In clustered-dot mode, the dot radius is half of the screen + Sets the halftone screen dot radius. This is only used when + screenType is set to stochasticClustered, and it defaults to 2. + In clustered-dot mode, the dot radius is half of the screen size. Dispersed-dot dithering doesn't have a dot radius. screenGamma float Sets the halftone screen gamma correction parameter. Gamma val- - ues greater than 1 make the output brighter; gamma values less + ues greater than 1 make the output brighter; gamma values less than 1 make it darker. The default value is 1. screenBlackThreshold float - When halftoning, all values below this threshold are forced to + When halftoning, all values below this threshold are forced to solid black. This parameter is a floating point value between 0 (black) and 1 (white). The default value is 0. screenWhiteThreshold float - When halftoning, all values above this threshold are forced to + When halftoning, all values above this threshold are forced to solid white. This parameter is a floating point value between 0 (black) and 1 (white). The default value is 1. minLineWidth float - Set the minimum line width, in device pixels. This affects the - rasterizer only, not the PostScript converter (except when it - uses rasterization to handle transparency). The default value + Set the minimum line width, in device pixels. This affects the + rasterizer only, not the PostScript converter (except when it + uses rasterization to handle transparency). The default value is 0 (no minimum). 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". overprintPreview yes | no If set to "yes", generate overprint preview output, honoring the - OP/op/OPM settings in the PDF file. Ignored for non-CMYK out- + OP/op/OPM settings in the PDF file. Ignored for non-CMYK out- put. The default value is "no". 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). urlCommand command - Sets the command executed when you click on a URL link. The - string "%s" will be replaced with the URL. (See the example + Sets the command executed when you click on a URL link. The + string "%s" will be replaced with the URL. (See the example below.) This has no default value. 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. mapNumericCharNames yes | no - If set to "yes", the Xpdf tools will attempt to map various + If set to "yes", the Xpdf tools will attempt to map various numeric character names sometimes used in font subsets. In some - cases this leads to usable text, and in other cases it leads to + cases this leads to usable text, and in other cases it leads to gibberish -- there is no way for Xpdf to tell. This defaults to "yes". mapUnknownCharNames yes | no - If set to "yes", and mapNumericCharNames is set to "no", the - Xpdf tools will apply a simple pass-through mapping (Unicode - index = character code) for all unrecognized glyph names. (For - CID fonts, setting mapNumericCharNames to "no" is unnecessary.) - In some cases, this leads to usable text, and in other cases it - leads to gibberish -- there is no way for Xpdf to tell. This + If set to "yes", and mapNumericCharNames is set to "no", the + Xpdf tools will apply a simple pass-through mapping (Unicode + index = character code) for all unrecognized glyph names. (For + CID fonts, setting mapNumericCharNames to "no" is unnecessary.) + In some cases, this leads to usable text, and in other cases it + leads to gibberish -- there is no way for Xpdf to tell. This defaults to "no". + mapExtTrueTypeFontsViaUnicode yes | no + When rasterizing text using an external TrueType font, there are + two options for handling character codes. If mapExtTrueType- + FontsViaUnicode is set to "yes", Xpdf will use the font encod- + ing/ToUnicode info to map character codes to Unicode, and then + use the font's Unicode cmap to map Unicode to GIDs. If mapExt- + TrueTypeFontsViaUnicode is set to "no", Xpdf will assume the + character codes are GIDs (i.e., use an identity mapping). This + defaults to "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". + bind modifiers-key context command ... Add a key or mouse button binding. Modifiers can be zero or more of: @@ -572,7 +605,6 @@ EXAMPLES textEOL unix # misc options - enableT1lib yes enableFreeType yes launchCommand viewer-script urlCommand "netscape -remote 'openURL(%s)'" @@ -588,14 +620,14 @@ FILES read in place of the system-wide file. AUTHOR - The Xpdf software and documentation are copyright 1996-2011 Glyph & + The Xpdf software and documentation are copyright 1996-2014 Glyph & Cog, LLC. SEE ALSO - xpdf(1), pdftops(1), pdftotext(1), pdfinfo(1), pdffonts(1), pdfde- - tach(1), pdftoppm(1), pdfimages(1) + xpdf(1), pdftops(1), pdftotext(1), pdftohtml(1), pdfinfo(1), pdf- + fonts(1), pdfdetach(1), pdftoppm(1), pdftopng(1), pdfimages(1) http://www.foolabs.com/xpdf/ - 15 August 2011 xpdfrc(5) + 28 May 2014 xpdfrc(5) diff -uNrp xpdf-3.03/doc/xpdfrc.hlp xpdf-3.04/doc/xpdfrc.hlp --- xpdf-3.03/doc/xpdfrc.hlp 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/doc/xpdfrc.hlp 1970-01-01 01:00:00.000000000 +0100 @@ -1,612 +0,0 @@ -! Generated automatically by mantohlp -1 xpdfrc -2 NCLUDE_FILE - - xpdfrc - configuration file for Xpdf tools (version 3.03) - - include config-file - Includes the specified config file. The effect of this is - equivalent to inserting the contents of config-file directly - into the parent config file in place of the include command. - Config files can be nested arbitrarily deeply. - - () - -2 HARACTER_MAPPIN - - nameToUnicode map-file - Specifies a file with the mapping from character names to Uni- - code. This is used to handle PDF fonts that have valid encod- - ings but no ToUnicode entry. Each line of a nameToUnicode file - looks like this: - - hex-string name - - The hex-string is the Unicode (UCS-2) character index, and name - is the corresponding character name. Multiple nameToUnicode - files can be used; if a character name is given more than once, - the code in the last specified file is used. There is a built- - in default nameToUnicode table with all of Adobe's standard - character names. - - cidToUnicode registry-ordering map-file - Specifies the file with the mapping from character collection to - Unicode. Each line of a cidToUnicode file represents one char- - acter: - - hex-string - - The hex-string is the Unicode (UCS-2) index for that character. - The first line maps CID 0, the second line CID 1, etc. File - size is determined by size of the character collection. Only - one file is allowed per character collection; the last specified - file is used. There are no built-in cidToUnicode mappings. - - unicodeToUnicode font-name-substring map-file - This is used to work around PDF fonts which have incorrect Uni- - code information. It specifies a file which maps from the given - (incorrect) Unicode indexes to the correct ones. The mapping - will be used for any font whose name contains font-name-sub- - string. Each line of a unicodeToUnicode file represents one - Unicode character: - - in-hex out-hex1 out-hex2 ... - - The in-hex field is an input (incorrect) Unicode index, and the - rest of the fields are one or more output (correct) Unicode - indexes. Each occurrence of in-hex will be converted to the - specified output sequence. - - unicodeMap encoding-name map-file - Specifies the file with mapping from Unicode to encoding-name. - These encodings are used for text output (see below). Each line - of a unicodeMap file represents a range of one or more Unicode - characters which maps linearly to a range in the output encod- - ing: - - in-start-hex in-end-hex out-start-hex - - Entries for single characters can be abbreviated to: - - in-hex out-hex - - The in-start-hex and in-end-hex fields (or the single in-hex - field) specify the Unicode range. The out-start-hex field (or - the out-hex field) specifies the start of the output encoding - range. The length of the out-start-hex (or out-hex) string - determines the length of the output characters (e.g., UTF-8 uses - different numbers of bytes to represent characters in different - ranges). Entries must be given in increasing Unicode order. - Only one file is allowed per encoding; the last specified file - is used. The Latin1, ASCII7, Symbol, ZapfDingbats, UTF-8, and - UCS-2 encodings are predefined. - - cMapDir registry-ordering dir - Specifies a search directory, dir, for CMaps for the reg- - istry-ordering character collection. There can be multiple - directories for a particular collection. There are no default - CMap directories. - - toUnicodeDir dir - Specifies a search directory, dir, for ToUnicode CMaps. There - can be multiple ToUnicode directories. There are no default - ToUnicode directories. - - () - -2 ENERAL_FONT_CONFIGURATIO - - fontFile PDF-font-name font-file - Maps a PDF font, PDF-font-name, to a font for display or Post- - Script output. The font file, font-file, can be any type - allowed in a PDF file. This command can be used for 8-bit or - 16-bit (CID) fonts. - - fontDir dir - Specifies a search directory for font files. There can be mul- - tiple fontDir commands; all of the specified directories will be - searched in order. The font files can be Type 1 (.pfa or .pfb) - or TrueType (.ttf or .ttc); other files in the directory will be - ignored. The font file name (not including the extension) must - exactly match the PDF font name. This search is performed if - the font name doesn't match any of the fonts declared with the - fontFile command. There are no default fontDir directories. - - fontFileCC registry-ordering font-file - Maps the registry-ordering character collection to a font for - display or PostScript output. This mapping is used if the font - name doesn't match any of the fonts declared with the fontFile, - fontDir, psResidentFont16, or psResidentFontCC commands. - - () - -2 OSTSCRIPT_FONT_CONFIGURATIO - - psFontPassthrough yes | no - If set to "yes", pass 8-bit font names through to the PostScript - output without substitution. Fonts which are not embedded in - the PDF file are expected to be available on the printer. This - defaults to "no". - - psResidentFont PDF-font-name PS-font-name - When the 8-bit font PDF-font-name is used (without embedding) in - a PDF file, it will be translated to the PostScript font - PS-font-name, which is assumed to be resident in the printer. - Typically, PDF-font-name and PS-font-name are the same. By - default, only the Base-14 fonts are assumed to be resident. - - psResidentFont16 PDF-font-name wMode PS-font-name encoding - When the 16-bit (CID) font PDF-font-name with writing mode wMode - is used (without embedding) in a PDF file, it will be translated - to the PostScript font PS-font-name, which is assumbed to be - resident in the printer. The writing mode must be either 'H' - for horizontal or 'V' for vertical. The resident font is - assumed to use the specified encoding (which must have been - defined with the unicodeMap command). - - psResidentFontCC registry-ordering wMode PS-font-name encoding - When a 16-bit (CID) font using the registry-ordering character - collection and wMode writing mode is used (without embedding) in - a PDF file, the PostScript font, PS-font-name, is substituted - for it. The substituted font is assumbed to be resident in the - printer. The writing mode must be either 'H' for horizontal or - 'V' for vertical. The resident font is assumed to use the spec- - ified encoding (which must have been defined with the unicodeMap - command). - - psEmbedType1Fonts yes | no - If set to "no", prevents embedding of Type 1 fonts in generated - PostScript. This defaults to "yes". - - psEmbedTrueTypeFonts yes | no - If set to "no", prevents embedding of TrueType fonts in gener- - ated PostScript. This defaults to "yes". - - psEmbedCIDTrueTypeFonts yes | no - If set to "no", prevents embedding of CID TrueType fonts in gen- - erated PostScript. For Level 3 PostScript, this generates a CID - font, for lower levels it generates a non-CID composite font. - This defaults to "yes". - - psEmbedCIDPostScriptFonts yes | no - If set to "no", prevents embedding of CID PostScript fonts in - generated PostScript. For Level 3 PostScript, this generates a - CID font, for lower levels it generates a non-CID composite - font. This defaults to "yes". - - () - -2 OSTSCRIPT_CONTRO - - psPaperSize width(pts) height(pts) - Sets the paper size for PostScript output. The width and height - parameters give the paper size in PostScript points (1 point = - 1/72 inch). - - psPaperSize letter | legal | A4 | A3 | match - Sets the paper size for PostScript output to a standard size. - The default paper size is set when xpdf and pdftops are built, - typically to "letter" or "A4". This can also be set to "match", - which will set the paper size to match the size specified in the - PDF file. - - psImageableArea llx lly urx ury - Sets the imageable area for PostScript output. The four inte- - gers are the coordinates of the lower-left and upper-right cor- - ners of the imageable region, specified in points (with the ori- - gin being the lower-left corner of the paper). This defaults to - the full paper size; the psPaperSize option will reset the - imageable area coordinates. - - psCrop yes | no - If set to "yes", PostScript output is cropped to the CropBox - specified in the PDF file; otherwise no cropping is done. This - defaults to "yes". - - psExpandSmaller yes | no - If set to "yes", PDF pages smaller than the PostScript imageable - area are expanded to fill the imageable area. Otherwise, no - scalling is done on smaller pages. This defaults to "no". - - psShrinkLarger yes | no - If set to yes, PDF pages larger than the PostScript imageable - area are shrunk to fit the imageable area. Otherwise, no scal- - ing is done on larger pages. This defaults to "yes". - - psCenter yes | no - If set to yes, PDF pages smaller than the PostScript imageable - area (after any scaling) are centered in the imageable area. - Otherwise, they are aligned at the lower-left corner of the - imageable area. This defaults to "yes". - - psDuplex yes | no - If set to "yes", the generated PostScript will set the "Duplex" - pagedevice entry. This tells duplex-capable printers to enable - duplexing. This defaults to "no". - - psLevel level1 | level1sep | level2 | level2sep | level3 | level3Sep - Sets the PostScript level to generate. This defaults to - "level2". - - psPreload yes | no - If set to "yes", PDF forms are converted to PS procedures, and - image data is preloaded. This uses more memory in the Post- - Script interpreter, but generates significantly smaller PS files - in situations where, e.g., the same image is drawn on every page - of a long document. This defaults to "no". - - psOPI yes | no - If set to "yes", generates PostScript OPI comments for all - images and forms which have OPI information. This option is - only available if the Xpdf tools were compiled with OPI support. - This defaults to "no". - - psASCIIHex yes | no - If set to "yes", the ASCIIHexEncode filter will be used instead - of ASCII85Encode for binary data. This defaults to "no". - - psUncompressPreloadedImages yes | no - If set to "yes", all preloaded images in PS files will uncom- - pressed. If set to "no", the original compressed images will be - used when possible. The "yes" setting is useful to work around - certain buggy PostScript interpreters. This defaults to "no". - - psRasterResolution float - Set the resolution (in dpi) for rasterized pages in PostScript - output. (Pdftops will rasterize pages which use transparency.) - This defaults to 300. - - psRasterMono yes | no - If set to "yes", rasterized pages in PS files will be monochrome - (8-bit gray) instead of color. This defaults to "no". - - psAlwaysRasterize yes | no - If set to "yes", all PostScript output will be rasterized. This - defaults to "no". - - psFile file-or-command - Sets the default PostScript file or print command for xpdf. - Commands start with a '|' character; anything else is a file. - If the file name or command contains spaces it must be quoted. - This defaults to unset, which tells xpdf to generate a name of - the form .ps for a PDF file .pdf. - - fontDir dir - See the description above, in the DISPLAY FONTS section. - - () - -2 EXT_CONTRO - - textEncoding encoding-name - Sets the encoding to use for text output. (This can be overrid- - den with the "-enc" switch on the command line.) The encod- - ing-name must be defined with the unicodeMap command (see - above). This defaults to "Latin1". - - textEOL unix | dos | mac - Sets the end-of-line convention to use for text output. The - options are: - - unix = LF - dos = CR+LF - mac = CR - - (This can be overridden with the "-eol" switch on the command - line.) The default value is based on the OS where xpdf and - pdftotext were built. - - textPageBreaks yes | no - If set to "yes", text extraction will insert page breaks (form - feed characters) between pages. This defaults to "yes". - - textKeepTinyChars yes | no - If set to "yes", text extraction will keep all characters. If - set to "no", text extraction will discard tiny (smaller than 3 - point) characters after the first 50000 per page, avoiding - extremely slow run times for PDF files that use special fonts to - do shading or cross-hatching. This defaults to "no". - - () - -2 ISCELLANEOUS_SETTING - - initialZoom percentage | page | width - Sets the initial zoom factor. A number specifies a zoom per- - centage, where 100 means 72 dpi. You may also specify 'page', - to fit the page to the window size, or 'width', to fit the page - width to the window width. - - continuousView yes | no - If set to "yes", xpdf will start in continuous view mode, i.e., - with one vertical screoll bar for the whole document. This - defaults to "no". - - enableT1lib yes | no - Enables or disables use of t1lib (a Type 1 font rasterizer). - This is only relevant if the Xpdf tools were built with t1lib - support. ("enableT1lib" replaces the old "t1libControl" - option.) This option defaults to "yes". - - enableFreeType yes | no - Enables or disables use of FreeType (a TrueType / Type 1 font - rasterizer). This is only relevant if the Xpdf tools were built - with FreeType support. ("enableFreeType" replaces the old - "freetypeControl" option.) This option defaults to "yes". - - enableFreeType yes | no - Enables or disables use of FreeType (a TrueType / Type 1 font - rasterizer). This is only relevant if the Xpdf tools were built - with FreeType support. ("enableFreeType" replaces the old - "freetypeControl" option.) This option defaults to "yes". - - disableFreeTypeHinting yes | no - If this is set to "yes", FreeType hinting will be forced off. - This option defaults to "no". - - antialias yes | no - Enables or disables font anti-aliasing in the PDF rasterizer. - This option affects all font rasterizers. ("antialias" replaces - the anti-aliasing control provided by the old "t1libControl" and - "freetypeControl" options.) This default to "yes". - - vectorAntialias yes | no - Enables or disables anti-aliasing of vector graphics in the PDF - rasterizer. This defaults to "yes". - - antialiasPrinting yes | no - If this is "yes", bitmaps sent to the printer will be - antialiased (according to the "antialias" and "vectorAntialias" - settings). If this is "no", printed bitmaps will not be - antialiased. This defaults to "no". - - strokeAdjust yes | no - Enables or disables stroke adjustment. Stroke adjustment moves - horizontal and vertical lines by up to half a pixel to make them - look "cleaner" when vector anti-aliasing is enabled. This - defaults to "yes". - - screenType dispersed | clustered | stochasticClustered - Sets the halftone screen type, which will be used when generat- - ing a monochrome (1-bit) bitmap. The three options are dis- - persed-dot dithering, clustered-dot dithering (with a round dot - and 45-degree screen angle), and stochastic clustered-dot - dithering. By default, "stochasticClustered" is used for reso- - lutions of 300 dpi and higher, and "dispersed" is used for reso- - lutions lower then 300 dpi. - - screenSize integer - Sets the size of the (square) halftone screen threshold matrix. - By default, this is 4 for dispersed-dot dithering, 10 for clus- - tered-dot dithering, and 100 for stochastic clustered-dot - dithering. - - screenDotRadius integer - Sets the halftone screen dot radius. This is only used when - screenType is set to stochasticClustered, and it defaults to 2. - In clustered-dot mode, the dot radius is half of the screen - size. Dispersed-dot dithering doesn't have a dot radius. - - screenGamma float - Sets the halftone screen gamma correction parameter. Gamma val- - ues greater than 1 make the output brighter; gamma values less - than 1 make it darker. The default value is 1. - - screenBlackThreshold float - When halftoning, all values below this threshold are forced to - solid black. This parameter is a floating point value between 0 - (black) and 1 (white). The default value is 0. - - screenWhiteThreshold float - When halftoning, all values above this threshold are forced to - solid white. This parameter is a floating point value between 0 - (black) and 1 (white). The default value is 1. - - minLineWidth float - Set the minimum line width, in device pixels. This affects the - rasterizer only, not the PostScript converter (except when it - uses rasterization to handle transparency). The default value - is 0 (no minimum). - - drawAnnotations yes | no - If set to "no", annotations will not be drawn or printed. The - default value is "yes". - - overprintPreview yes | no - If set to "yes", generate overprint preview output, honoring the - OP/op/OPM settings in the PDF file. Ignored for non-CMYK out- - put. The default value is "no". - - 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 - simply try to execute the file (after prompting the user). - - urlCommand command - Sets the command executed when you click on a URL link. The - string "%s" will be replaced with the URL. (See the example - below.) This has no default value. - - movieCommand command - 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. - - mapNumericCharNames yes | no - If set to "yes", the Xpdf tools will attempt to map various - numeric character names sometimes used in font subsets. In some - cases this leads to usable text, and in other cases it leads to - gibberish -- there is no way for Xpdf to tell. This defaults to - "yes". - - mapUnknownCharNames yes | no - If set to "yes", and mapNumericCharNames is set to "no", the - Xpdf tools will apply a simple pass-through mapping (Unicode - index = character code) for all unrecognized glyph names. (For - CID fonts, setting mapNumericCharNames to "no" is unnecessary.) - In some cases, this leads to usable text, and in other cases it - leads to gibberish -- there is no way for Xpdf to tell. This - defaults to "no". - - bind modifiers-key context command ... - Add a key or mouse button binding. Modifiers can be zero or - more of: - - shift- - ctrl- - alt- - - Key can be a regular ASCII character, or any one of: - - space - tab - return - enter - backspace - insert - delete - home - end - pgup - pgdn - left / right / up / down (arrow keys) - f1 .. f35 (function keys) - mousePress1 .. mousePress7 (mouse buttons) - mouseRelease1 .. mouseRelease7 (mouse buttons) - - Context is either "any" or a comma-separated combination of: - - fullScreen / window (full screen mode on/off) - continuous / singlePage (continuous mode on/off) - 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 - above list. - - 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 - was defined for the exact same modifiers, key, and context. All - tokens (modifiers, key, context, commands) are case-sensitive. - - Example key bindings: - - # bind ctrl-a in any context to the nextPage - # command - bind ctrl-a any nextPage - - # bind uppercase B, when in continuous mode - # with scroll lock on, to the reload command - # followed by the prevPage command - bind B continuous,scrLockOn reload prevPage - - 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- - tiple contexts). - - printCommands yes | no - 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 - from all of the Xpdf tools. This defaults to "no". - - () - -2 EXAMPLES - - The following is a sample xpdfrc file. - - # from the Thai support package - nameToUnicode /usr/local/share/xpdf/Thai.nameToUnicode - - # from the Japanese support package - cidToUnicode Adobe-Japan1 /usr/local/share/xpdf/Adobe-Japan1.cidToUnicode - unicodeMap JISX0208 /usr/local/share/xpdf/JISX0208.unicodeMap - cMapDir Adobe-Japan1 /usr/local/share/xpdf/cmap/Adobe-Japan1 - - # use the Base-14 Type 1 fonts from ghostscript - fontFile Times-Roman /usr/local/share/ghostscript/fonts/n021003l.pfb - fontFile Times-Italic /usr/local/share/ghostscript/fonts/n021023l.pfb - fontFile Times-Bold /usr/local/share/ghostscript/fonts/n021004l.pfb - fontFile Times-BoldItalic /usr/local/share/ghostscript/fonts/n021024l.pfb - fontFile Helvetica /usr/local/share/ghostscript/fonts/n019003l.pfb - fontFile Helvetica-Oblique /usr/local/share/ghostscript/fonts/n019023l.pfb - fontFile Helvetica-Bold /usr/local/share/ghostscript/fonts/n019004l.pfb - fontFile Helvetica-BoldOblique /usr/local/share/ghostscript/fonts/n019024l.pfb - fontFile Courier /usr/local/share/ghostscript/fonts/n022003l.pfb - fontFile Courier-Oblique /usr/local/share/ghostscript/fonts/n022023l.pfb - fontFile Courier-Bold /usr/local/share/ghostscript/fonts/n022004l.pfb - fontFile Courier-BoldOblique /usr/local/share/ghostscript/fonts/n022024l.pfb - fontFile Symbol /usr/local/share/ghostscript/fonts/s050000l.pfb - fontFile ZapfDingbats /usr/local/share/ghostscript/fonts/d050000l.pfb - - # use the Bakoma Type 1 fonts - # (this assumes they happen to be installed in /usr/local/fonts/bakoma) - fontDir /usr/local/fonts/bakoma - - # set some PostScript options - psPaperSize letter - psDuplex no - psLevel level2 - psEmbedType1Fonts yes - psEmbedTrueTypeFonts yes - psFile "| lpr -Pprinter5" - - # assume that the PostScript printer has the Univers and - # Univers-Bold fonts - psResidentFont Univers Univers - psResidentFont Univers-Bold Univers-Bold - - # set the text output options - textEncoding UTF-8 - textEOL unix - - # misc options - enableT1lib yes - enableFreeType yes - launchCommand viewer-script - urlCommand "netscape -remote 'openURL(%s)'" - - () - -2 FILES - - /usr/local/etc/xpdfrc - This is the default location for the system-wide configuration - file. Depending on build options, it may be placed elsewhere. - - $HOME/.xpdfrc - This is the user's configuration file. If it exists, it will be - read in place of the system-wide file. - - () - -2 AUTHOR - - The Xpdf software and documentation are copyright 1996-2011 Glyph & - Cog, LLC. - - () - -2 SEE_ALSO - - xpdf(1), pdftops(1), pdftotext(1), pdfinfo(1), pdffonts(1), pdfde- - tach(1), pdftoppm(1), pdfimages(1) - http://www.foolabs.com/xpdf/ - - () - diff -uNrp xpdf-3.03/fofi/FoFiBase.cc xpdf-3.04/fofi/FoFiBase.cc --- xpdf-3.03/fofi/FoFiBase.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/fofi/FoFiBase.cc 2014-05-28 20:50:50.000000000 +0200 @@ -84,7 +84,7 @@ int FoFiBase::getU8(int pos, GBool *ok) int FoFiBase::getS16BE(int pos, GBool *ok) { int x; - if (pos < 0 || pos+1 >= len || pos > INT_MAX - 1) { + if (pos < 0 || pos > INT_MAX - 1 || pos+1 >= len) { *ok = gFalse; return 0; } @@ -99,7 +99,7 @@ int FoFiBase::getS16BE(int pos, GBool *o int FoFiBase::getU16BE(int pos, GBool *ok) { int x; - if (pos < 0 || pos+1 >= len || pos > INT_MAX - 1) { + if (pos < 0 || pos > INT_MAX - 1 || pos+1 >= len) { *ok = gFalse; return 0; } @@ -111,7 +111,7 @@ int FoFiBase::getU16BE(int pos, GBool *o int FoFiBase::getS32BE(int pos, GBool *ok) { int x; - if (pos < 0 || pos+3 >= len || pos > INT_MAX - 3) { + if (pos < 0 || pos > INT_MAX - 3 || pos+3 >= len) { *ok = gFalse; return 0; } @@ -128,7 +128,7 @@ int FoFiBase::getS32BE(int pos, GBool *o Guint FoFiBase::getU32BE(int pos, GBool *ok) { Guint x; - if (pos < 0 || pos+3 >= len || pos > INT_MAX - 3) { + if (pos < 0 || pos > INT_MAX - 3 || pos+3 >= len) { *ok = gFalse; return 0; } @@ -142,7 +142,7 @@ Guint FoFiBase::getU32BE(int pos, GBool Guint FoFiBase::getU32LE(int pos, GBool *ok) { Guint x; - if (pos < 0 || pos+3 >= len || pos > INT_MAX - 3) { + if (pos < 0 || pos > INT_MAX - 3 || pos+3 >= len) { *ok = gFalse; return 0; } @@ -157,7 +157,7 @@ Guint FoFiBase::getUVarBE(int pos, int s Guint x; int i; - if (pos < 0 || pos + size > len || pos > INT_MAX - size) { + if (pos < 0 || pos > INT_MAX - size || pos + size > len) { *ok = gFalse; return 0; } diff -uNrp xpdf-3.03/fofi/FoFiIdentifier.cc xpdf-3.04/fofi/FoFiIdentifier.cc --- xpdf-3.03/fofi/FoFiIdentifier.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/fofi/FoFiIdentifier.cc 2014-05-28 20:50:50.000000000 +0200 @@ -16,6 +16,9 @@ #include #include #include "gtypes.h" +#include "gmem.h" +#include "GString.h" +#include "GList.h" #include "FoFiIdentifier.h" //------------------------------------------------------------------------ @@ -436,12 +439,23 @@ FoFiIdentifierType FoFiIdentifier::ident FoFiIdentifierType FoFiIdentifier::identifyFile(char *fileName) { FileReader *reader; FoFiIdentifierType type; + int n; if (!(reader = FileReader::make(fileName))) { return fofiIdError; } type = identify(reader); delete reader; + + // Mac OS X dfont files don't have any sort of header or magic number, + // so look at the file name extension + if (type == fofiIdUnknown) { + n = (int)strlen(fileName); + if (n >= 6 && !strcmp(fileName + n - 6, ".dfont")) { + type = fofiIdDfont; + } + } + return type; } @@ -630,3 +644,244 @@ static FoFiIdentifierType identifyCFF(Re return fofiIdCFF8Bit; } } + +//------------------------------------------------------------------------ + +static GList *getTTCFontList(FILE *f); +static GList *getDfontFontList(FILE *f); + +GList *FoFiIdentifier::getFontList(char *fileName) { + FILE *f; + char buf[4]; + GList *ret; + + if (!(f = fopen(fileName, "rb"))) { + return NULL; + } + if (fread(buf, 1, 4, f) == 4 && + buf[0] == 0x74 && // 'ttcf' + buf[1] == 0x74 && + buf[2] == 0x63 && + buf[3] == 0x66) { + ret = getTTCFontList(f); + } else { + ret = getDfontFontList(f); + } + fclose(f); + return ret; +} + +static GList *getTTCFontList(FILE *f) { + Guchar buf[12]; + Guchar *buf2; + int fileLength, nFonts; + int tabDirOffset, nTables, nameTabOffset, nNames, stringsOffset; + int stringPlatform, stringLength, stringOffset; + GBool stringUnicode; + int i, j; + GList *ret; + + fseek(f, 0, SEEK_END); + fileLength = (int)ftell(f); + if (fileLength < 0) { + goto err1; + } + fseek(f, 8, SEEK_SET); + if (fread(buf, 1, 4, f) != 4) { + goto err1; + } + nFonts = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + if (nFonts < 0 || + 12 + 4 * nFonts > fileLength) { + goto err1; + } + ret = new GList(); + for (i = 0; i < nFonts; ++i) { + fseek(f, 12 + 4 * i, SEEK_SET); + if (fread(buf, 1, 4, f) != 4) { + goto err2; + } + tabDirOffset = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + if (tabDirOffset < 0 || + tabDirOffset + 12 < 0 || + tabDirOffset + 12 > fileLength) { + goto err2; + } + fseek(f, tabDirOffset, SEEK_SET); + if (fread(buf, 1, 12, f) != 12) { + goto err2; + } + nTables = (buf[4] << 8) | buf[5]; + if (tabDirOffset + 12 + 16 * nTables < 0 || + tabDirOffset + 12 + 16 * nTables > fileLength) { + goto err2; + } + buf2 = (Guchar *)gmallocn(nTables, 16); + if ((int)fread(buf2, 1, 16 * nTables, f) != 16 * nTables) { + goto err3; + } + nameTabOffset = 0; // make gcc happy + for (j = 0; j < nTables; ++j) { + if (buf2[16*j + 0] == 'n' && + buf2[16*j + 1] == 'a' && + buf2[16*j + 2] == 'm' && + buf2[16*j + 3] == 'e') { + nameTabOffset = (buf2[16*j + 8] << 24) | (buf2[16*j + 9] << 16) | + (buf2[16*j + 10] << 8) | buf2[16*j + 11]; + break; + } + } + gfree(buf2); + if (j >= nTables) { + goto err2; + } + if (nameTabOffset < 0 || + nameTabOffset + 6 < 0 || + nameTabOffset + 6 > fileLength) { + goto err2; + } + fseek(f, nameTabOffset, SEEK_SET); + if (fread(buf, 1, 6, f) != 6) { + goto err2; + } + nNames = (buf[2] << 8) | buf[3]; + stringsOffset = (buf[4] << 8) | buf[5]; + if (nameTabOffset + 6 + 12 * nNames < 0 || + nameTabOffset + 6 + 12 * nNames > fileLength || + nameTabOffset + stringsOffset < 0) { + goto err2; + } + buf2 = (Guchar *)gmallocn(nNames, 12); + if ((int)fread(buf2, 1, 12 * nNames, f) != 12 * nNames) { + goto err3; + } + for (j = 0; j < nNames; ++j) { + if (buf2[12*j + 6] == 0 && // 0x0004 = full name + buf2[12*j + 7] == 4) { + break; + } + } + if (j >= nNames) { + goto err3; + } + stringPlatform = (buf2[12*j] << 8) | buf2[12*j + 1]; + // stringEncoding = (buf2[12*j + 2] << 8) | buf2[12*j + 3]; + stringUnicode = stringPlatform == 0 || stringPlatform == 3; + stringLength = (buf2[12*j + 8] << 8) | buf2[12*j + 9]; + stringOffset = nameTabOffset + stringsOffset + + ((buf2[12*j + 10] << 8) | buf2[12*j + 11]); + gfree(buf2); + if (stringOffset < 0 || + stringOffset + stringLength < 0 || + stringOffset + stringLength > fileLength) { + goto err2; + } + buf2 = (Guchar *)gmalloc(stringLength); + fseek(f, stringOffset, SEEK_SET); + if ((int)fread(buf2, 1, stringLength, f) != stringLength) { + goto err3; + } + if (stringUnicode) { + stringLength /= 2; + for (j = 0; j < stringLength; ++j) { + buf2[j] = buf2[2*j + 1]; + } + } + ret->append(new GString((char *)buf2, stringLength)); + gfree(buf2); + } + return ret; + + err3: + gfree(buf2); + err2: + deleteGList(ret, GString); + err1: + return NULL; +} + +static GList *getDfontFontList(FILE *f) { + Guchar buf[16]; + Guchar *resMap; + int fileLength, resMapOffset, resMapLength; + int resTypeListOffset, resNameListOffset, nTypes; + int refListOffset, nFonts, nameOffset, nameLen; + int offset, i; + GList *ret; + + fseek(f, 0, SEEK_END); + fileLength = (int)ftell(f); + if (fileLength < 0) { + goto err1; + } + fseek(f, 0, SEEK_SET); + if (fread((char *)buf, 1, 16, f) != 16) { + goto err1; + } + resMapOffset = (buf[4] << 24) | (buf[5] << 16) | (buf[6] << 8) | buf[7]; + resMapLength = (buf[12] << 24) | (buf[13] << 16) | (buf[14] << 8) | buf[15]; + if (resMapOffset < 0 || + resMapOffset >= fileLength || + resMapLength < 0 || + resMapOffset + resMapLength > fileLength || + resMapOffset + resMapLength < 0) { + goto err1; + } + if (resMapLength > 32768) { + // sanity check - this probably isn't a dfont file + goto err1; + } + resMap = (Guchar *)gmalloc(resMapLength); + fseek(f, resMapOffset, SEEK_SET); + if ((int)fread((char *)resMap, 1, resMapLength, f) != resMapLength) { + goto err2; + } + resTypeListOffset = (resMap[24] << 8) | resMap[25]; + resNameListOffset = (resMap[26] << 8) | resMap[27]; + nTypes = ((resMap[28] << 8) | resMap[29]) + 1; + if (resTypeListOffset + 2 + nTypes * 8 > resMapLength || + resNameListOffset >= resMapLength) { + goto err2; + } + for (i = 0; i < nTypes; ++i) { + offset = resTypeListOffset + 2 + 8 * i; + if (resMap[offset] == 0x73 && // 'sfnt' + resMap[offset+1] == 0x66 && + resMap[offset+2] == 0x6e && + resMap[offset+3] == 0x74) { + nFonts = ((resMap[offset+4] << 8) | resMap[offset+5]) + 1; + refListOffset = (resMap[offset+6] << 8) | resMap[offset+7]; + break; + } + } + if (i >= nTypes) { + goto err2; + } + if (resTypeListOffset + refListOffset >= resMapLength || + resTypeListOffset + refListOffset + nFonts * 12 > resMapLength) { + goto err2; + } + ret = new GList(); + for (i = 0; i < nFonts; ++i) { + offset = resTypeListOffset + refListOffset + 12 * i; + nameOffset = (resMap[offset+2] << 8) | resMap[offset+3]; + offset = resNameListOffset + nameOffset; + if (offset >= resMapLength) { + goto err3; + } + nameLen = resMap[offset]; + if (offset + 1 + nameLen > resMapLength) { + goto err3; + } + ret->append(new GString((char *)resMap + offset + 1, nameLen)); + } + gfree(resMap); + return ret; + + err3: + deleteGList(ret, GString); + err2: + gfree(resMap); + err1: + return NULL; +} diff -uNrp xpdf-3.03/fofi/FoFiIdentifier.h xpdf-3.04/fofi/FoFiIdentifier.h --- xpdf-3.03/fofi/FoFiIdentifier.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/fofi/FoFiIdentifier.h 2014-05-28 20:50:50.000000000 +0200 @@ -15,6 +15,8 @@ #pragma interface #endif +class GList; + //------------------------------------------------------------------------ // FoFiIdentifier //------------------------------------------------------------------------ @@ -28,6 +30,7 @@ enum FoFiIdentifierType { fofiIdTrueTypeCollection, // TrueType collection fofiIdOpenTypeCFF8Bit, // OpenType wrapper with 8-bit CFF font fofiIdOpenTypeCFFCID, // OpenType wrapper with CID CFF font + fofiIdDfont, // Mac OS X dfont fofiIdUnknown, // unknown type fofiIdError // error in reading the file }; @@ -35,10 +38,18 @@ enum FoFiIdentifierType { class FoFiIdentifier { public: + // Identify a font file. static FoFiIdentifierType identifyMem(char *file, int len); static FoFiIdentifierType identifyFile(char *fileName); static FoFiIdentifierType identifyStream(int (*getChar)(void *data), void *data); + + // Return a list of font names (GString *) in a font collection + // file. Indexes into the returned list are indexes into the + // collection. This function is only useful with TrueType + // collections and Mac dfont files. Returns NULL on error + // (including invalid font type). + static GList *getFontList(char *fileName); }; #endif diff -uNrp xpdf-3.03/fofi/FoFiTrueType.cc xpdf-3.04/fofi/FoFiTrueType.cc --- xpdf-3.03/fofi/FoFiTrueType.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/fofi/FoFiTrueType.cc 2014-05-28 20:50:50.000000000 +0200 @@ -275,10 +275,11 @@ static const char *macGlyphNames[258] = // FoFiTrueType //------------------------------------------------------------------------ -FoFiTrueType *FoFiTrueType::make(char *fileA, int lenA) { +FoFiTrueType *FoFiTrueType::make(char *fileA, int lenA, int fontNum, + GBool allowHeadlessCFF) { FoFiTrueType *ff; - ff = new FoFiTrueType(fileA, lenA, gFalse); + ff = new FoFiTrueType(fileA, lenA, gFalse, fontNum, gFalse, allowHeadlessCFF); if (!ff->parsedOk) { delete ff; return NULL; @@ -286,15 +287,20 @@ FoFiTrueType *FoFiTrueType::make(char *f return ff; } -FoFiTrueType *FoFiTrueType::load(char *fileName) { +FoFiTrueType *FoFiTrueType::load(char *fileName, int fontNum, + GBool allowHeadlessCFF) { FoFiTrueType *ff; char *fileA; - int lenA; + int lenA, n; + GBool isDfontA; if (!(fileA = FoFiBase::readFile(fileName, &lenA))) { return NULL; } - ff = new FoFiTrueType(fileA, lenA, gTrue); + n = (int)strlen(fileName); + isDfontA = n >= 6 && !strcmp(fileName + n - 6, ".dfont"); + ff = new FoFiTrueType(fileA, lenA, gTrue, fontNum, isDfontA, + allowHeadlessCFF); if (!ff->parsedOk) { delete ff; return NULL; @@ -302,7 +308,9 @@ FoFiTrueType *FoFiTrueType::load(char *f return ff; } -FoFiTrueType::FoFiTrueType(char *fileA, int lenA, GBool freeFileDataA): +FoFiTrueType::FoFiTrueType(char *fileA, int lenA, GBool freeFileDataA, + int fontNum, GBool isDfontA, + GBool allowHeadlessCFF): FoFiBase(fileA, lenA, freeFileDataA) { tables = NULL; @@ -310,9 +318,10 @@ FoFiTrueType::FoFiTrueType(char *fileA, cmaps = NULL; nCmaps = 0; nameToGID = NULL; + isDfont = isDfontA; parsedOk = gFalse; - parse(); + parse(fontNum, allowHeadlessCFF); } FoFiTrueType::~FoFiTrueType() { @@ -363,7 +372,26 @@ int FoFiTrueType::mapCodeToGID(int i, in if (c < 0 || c >= cmaps[i].len - 6) { return 0; } - gid = getU8(cmaps[i].offset + 6 + c, &ok); + gid = getU8(pos + 6 + c, &ok); + break; + case 2: + // this only handles single-byte codes + if (c < 0 || c > 0xff) { + return 0; + } + // check that: subHeaderKeys[0] = 0 + // subHeaders[0].firstCode = 0 + // subHeaders[0].entryCount = 256 + // subHeaders[0].idDelta = 0 + if (getU16BE(pos + 6, &ok) != 0 || + getU16BE(pos + 518 + 0, &ok) != 0 || + getU16BE(pos + 518 + 2, &ok) != 256 || + getU16BE(pos + 518 + 4, &ok) != 0) { + return 0; + } + // subHeaders[0].idRangeOffset is a byte offset from itself + pos = pos + 518 + 6 + getU16BE(pos + 518 + 6, &ok); + gid = getU16BE(pos + 2 * c, &ok); break; case 4: segCnt = getU16BE(pos + 6, &ok) / 2; @@ -1022,7 +1050,7 @@ void FoFiTrueType::writeTTF(FoFiOutputFu if (!missingCmap && !missingName && !missingPost && !missingOS2 && !unsortedLoca && !emptyCmap && !badCmapLen && !abbrevHMTX && nZeroLengthTables == 0 && nBogusTables == 0 && - !name && !codeToGID) { + !name && !codeToGID && !isDfont) { (*outputFunc)(outputStream, (char *)file, len); goto done1; } @@ -1632,6 +1660,14 @@ void FoFiTrueType::cvtSfnts(FoFiOutputFu // table, cmpTrueTypeLocaOffset uses offset as its primary sort key, // and idx as its secondary key (ensuring that adjacent entries with // the same pos value remain in the same order) + // + // NB: a glyph description containing 12 zero bytes should be a + // valid empty glyph (from my reading of the TrueType spec), but + // Acrobat chokes on this (which is an issue when an Xpdf-generated + // PS file is converted back to PDF - with Ghostscript or + // Distiller), so we drop any glyph descriptions of 12 or fewer + // bytes -- an empty glyph description generates an empty glyph with + // no errors locaTable = (TrueTypeLoca *)gmallocn(nGlyphs + 1, sizeof(TrueTypeLoca)); i = seekTable("loca"); pos = tables[i].offset; @@ -1670,11 +1706,11 @@ void FoFiTrueType::cvtSfnts(FoFiOutputFu *maxUsedGlyph = -1; for (i = 0; i <= nGlyphs; ++i) { locaTable[i].newOffset = pos; - pos += locaTable[i].len; - if (pos & 3) { - pos += 4 - (pos & 3); - } - if (locaTable[i].len > 0) { + if (locaTable[i].len > 12) { + pos += locaTable[i].len; + if (pos & 3) { + pos += 4 - (pos & 3); + } *maxUsedGlyph = i; } } @@ -1737,14 +1773,17 @@ void FoFiTrueType::cvtSfnts(FoFiOutputFu checksum = 0; glyfPos = tables[seekTable("glyf")].offset; for (j = 0; j < nGlyphs; ++j) { - length += locaTable[j].len; - if (length & 3) { - length += 4 - (length & 3); - } - if (checkRegion(glyfPos + locaTable[j].origOffset, locaTable[j].len)) { - checksum += - computeTableChecksum(file + glyfPos + locaTable[j].origOffset, - locaTable[j].len); + if (locaTable[j].len > 12) { + length += locaTable[j].len; + if (length & 3) { + length += 4 - (length & 3); + } + if (checkRegion(glyfPos + locaTable[j].origOffset, + locaTable[j].len)) { + checksum += + computeTableChecksum(file + glyfPos + locaTable[j].origOffset, + locaTable[j].len); + } } } } else { @@ -1858,7 +1897,7 @@ void FoFiTrueType::cvtSfnts(FoFiOutputFu } else if (i == t42GlyfTable) { glyfPos = tables[seekTable("glyf")].offset; for (j = 0; j < nGlyphs; ++j) { - if (locaTable[j].len > 0 && + if (locaTable[j].len > 12 && checkRegion(glyfPos + locaTable[j].origOffset, locaTable[j].len)) { dumpString(file + glyfPos + locaTable[j].origOffset, locaTable[j].len, outputFunc, outputStream); @@ -1950,35 +1989,43 @@ Guint FoFiTrueType::computeTableChecksum return checksum; } -void FoFiTrueType::parse() { +void FoFiTrueType::parse(int fontNum, GBool allowHeadlessCFF) { Guint topTag; - int pos, ver, i, j; + int offset, pos, ver, i, j; parsedOk = gTrue; - // look for a collection (TTC) - topTag = getU32BE(0, &parsedOk); - if (!parsedOk) { - return; - } - if (topTag == ttcfTag) { - pos = getU32BE(12, &parsedOk); + // check for a dfont or TrueType collection (TTC) + // offset = start of actual TrueType font file (table positions are + // relative to this + // pos = position of table directory (relative to offset) + if (isDfont) { + parseDfont(fontNum, &offset, &pos); + } else { + offset = 0; + topTag = getU32BE(0, &parsedOk); if (!parsedOk) { return; } - } else { - pos = 0; + if (topTag == ttcfTag) { + parseTTC(fontNum, &pos); + } else { + pos = 0; + } + } + if (!parsedOk) { + return; } // check the sfnt version - ver = getU32BE(pos, &parsedOk); + ver = getU32BE(offset + pos, &parsedOk); if (!parsedOk) { return; } openTypeCFF = ver == 0x4f54544f; // 'OTTO' // read the table directory - nTables = getU16BE(pos + 4, &parsedOk); + nTables = getU16BE(offset + pos + 4, &parsedOk); if (!parsedOk) { return; } @@ -1986,10 +2033,10 @@ void FoFiTrueType::parse() { pos += 12; j = 0; for (i = 0; i < nTables; ++i) { - tables[j].tag = getU32BE(pos, &parsedOk); - tables[j].checksum = getU32BE(pos + 4, &parsedOk); - tables[j].offset = (int)getU32BE(pos + 8, &parsedOk); - tables[j].len = (int)getU32BE(pos + 12, &parsedOk); + tables[j].tag = getU32BE(offset + pos, &parsedOk); + tables[j].checksum = getU32BE(offset + pos + 4, &parsedOk); + tables[j].offset = offset + (int)getU32BE(offset + pos + 8, &parsedOk); + tables[j].len = (int)getU32BE(offset + pos + 12, &parsedOk); if (tables[j].offset + tables[j].len >= tables[j].offset && tables[j].offset + tables[j].len <= len) { // ignore any bogus entries in the table directory @@ -2002,10 +2049,23 @@ void FoFiTrueType::parse() { return; } - // check for tables that are required by both the TrueType spec and - // the Type 42 spec - if (seekTable("head") < 0 || - seekTable("hhea") < 0 || + // check for the head table; allow for a head-less OpenType CFF font + headlessCFF = gFalse; + if (seekTable("head") < 0) { + if (openTypeCFF && allowHeadlessCFF) { + headlessCFF = gTrue; + nGlyphs = 0; + bbox[0] = bbox[1] = bbox[2] = bbox[3] = 0; + locaFmt = 0; + return; + } + parsedOk = gFalse; + return; + } + + // check for other tables that are required by both the TrueType + // spec and the Type 42 spec + if (seekTable("hhea") < 0 || seekTable("maxp") < 0 || seekTable("hmtx") < 0 || (!openTypeCFF && seekTable("loca") < 0) || @@ -2016,7 +2076,7 @@ void FoFiTrueType::parse() { } // read the cmaps - if ((i = seekTable("cmap")) >= 0) { + if ((i = seekTable("cmap")) >= 0 && tables[i].len >= 4) { pos = tables[i].offset + 2; nCmaps = getU16BE(pos, &parsedOk); pos += 2; @@ -2035,8 +2095,6 @@ void FoFiTrueType::parse() { if (!parsedOk) { return; } - } else { - nCmaps = 0; } // get the number of glyphs from the maxp table @@ -2087,6 +2145,74 @@ void FoFiTrueType::parse() { readPostTable(); } +// Get the table directory position +void FoFiTrueType::parseTTC(int fontNum, int *pos) { + int nFonts; + + nFonts = getU32BE(8, &parsedOk); + if (!parsedOk) { + return; + } + if (fontNum < 0 || fontNum >= nFonts) { + parsedOk = gFalse; + return; + } + *pos = getU32BE(12 + 4 * fontNum, &parsedOk); +} + +void FoFiTrueType::parseDfont(int fontNum, int *offset, int *startPos) { + int resMapOffset, resDataOffset; + int resTypeListOffset, nTypes, typeTag; + int nFonts, refListOffset, dataOffset; + int pos, i; + + resDataOffset = getU32BE(0, &parsedOk); + resMapOffset = getU32BE(4, &parsedOk); + if (!parsedOk) { + return; + } + + resTypeListOffset = getU16BE(resMapOffset + 24, &parsedOk); + // resNameListOffset = getU16BE(resMapOffset + 26, &parsedOk); + nTypes = getU16BE(resMapOffset + 28, &parsedOk) + 1; + if (!parsedOk) { + return; + } + + pos = 0; // make gcc happy + for (i = 0; i < nTypes; ++i) { + pos = resMapOffset + resTypeListOffset + 2 + 8*i; + typeTag = getU32BE(pos, &parsedOk); + if (!parsedOk) { + return; + } + if (typeTag == 0x73666e74) { // 'sfnt' + break; + } + } + if (i >= nTypes) { + parsedOk = gFalse; + return; + } + nFonts = getU16BE(pos + 4, &parsedOk) + 1; + refListOffset = getU16BE(pos + 6, &parsedOk); + if (!parsedOk) { + return; + } + if (fontNum < 0 || fontNum >= nFonts) { + parsedOk = gFalse; + return; + } + pos = resMapOffset + resTypeListOffset + refListOffset + 12 * fontNum; + dataOffset = getU32BE(pos + 4, &parsedOk) & 0x00ffffff; + if (!parsedOk) { + return; + } + // the data offset points to a 4-byte length field, which we skip over + *offset = resDataOffset + dataOffset + 4; + *startPos = 0; +} + void FoFiTrueType::readPostTable() { GString *name; int tablePos, postFmt, stringIdx, stringPos; diff -uNrp xpdf-3.03/fofi/FoFiTrueType.h xpdf-3.04/fofi/FoFiTrueType.h --- xpdf-3.03/fofi/FoFiTrueType.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/fofi/FoFiTrueType.h 2014-05-28 20:50:50.000000000 +0200 @@ -30,11 +30,19 @@ struct TrueTypeCmap; class FoFiTrueType: public FoFiBase { public: - // Create a FoFiTrueType object from a memory buffer. - static FoFiTrueType *make(char *fileA, int lenA); - - // Create a FoFiTrueType object from a file on disk. - static FoFiTrueType *load(char *fileName); + // Create a FoFiTrueType object from a memory buffer. If + // is true, OpenType CFF fonts without the 'head' + // table are permitted -- this is useful when calling the convert* + // functions. + static FoFiTrueType *make(char *fileA, int lenA, int fontNum, + GBool allowHeadlessCFF = gFalse); + + // Create a FoFiTrueType object from a file on disk. If + // is true, OpenType CFF fonts without the 'head' + // table are permitted -- this is useful when calling the convert* + // functions. + static FoFiTrueType *load(char *fileName, int fontNum, + GBool allowHeadlessCFF = gFalse); virtual ~FoFiTrueType(); @@ -42,6 +50,12 @@ public: // if it's a TrueType font (or OpenType font with TrueType data). GBool isOpenTypeCFF() { return openTypeCFF; } + // Returns true if this is an OpenType CFF font that is missing the + // 'head' table. This is a violation of the OpenType spec, but the + // embedded CFF font can be usable for some purposes (e.g., the + // convert* functions). + GBool isHeadlessCFF() { return headlessCFF; } + // Return the number of cmaps defined by this font. int getNumCmaps(); @@ -148,7 +162,8 @@ public: private: - FoFiTrueType(char *fileA, int lenA, GBool freeFileDataA); + FoFiTrueType(char *fileA, int lenA, GBool freeFileDataA, + int fontNum, GBool isDfont, GBool allowHeadlessCFF); void cvtEncoding(char **encoding, FoFiOutputFunc outputFunc, void *outputStream); @@ -164,7 +179,9 @@ private: FoFiOutputFunc outputFunc, void *outputStream); Guint computeTableChecksum(Guchar *data, int length); - void parse(); + void parse(int fontNum, GBool allowHeadlessCFF); + void parseTTC(int fontNum, int *pos); + void parseDfont(int fontNum, int *offset, int *pos); void readPostTable(); int seekTable(const char *tag); @@ -177,6 +194,8 @@ private: int bbox[4]; GHash *nameToGID; GBool openTypeCFF; + GBool headlessCFF; + GBool isDfont; GBool parsedOk; }; diff -uNrp xpdf-3.03/fofi/FoFiType1C.cc xpdf-3.04/fofi/FoFiType1C.cc --- xpdf-3.03/fofi/FoFiType1C.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/fofi/FoFiType1C.cc 2014-05-28 20:50:50.000000000 +0200 @@ -387,24 +387,42 @@ void FoFiType1C::convertToType1(char *ps delete buf; } if (privateDicts[0].nStemSnapH) { - eexecWrite(&eb, "/StemSnapH ["); - for (i = 0; i < privateDicts[0].nStemSnapH; ++i) { - buf = GString::format("{0:s}{1:.4g}", - i > 0 ? " " : "", privateDicts[0].stemSnapH[i]); - eexecWrite(&eb, buf->getCString()); - delete buf; + // the StemSnapH array should be unique values in ascending order -- + // if not, just skip it + for (i = 1; i < privateDicts[0].nStemSnapH; ++i) { + if (privateDicts[0].stemSnapH[i-1] >= privateDicts[0].stemSnapH[i]) { + break; + } + } + if (i == privateDicts[0].nStemSnapH) { + eexecWrite(&eb, "/StemSnapH ["); + for (i = 0; i < privateDicts[0].nStemSnapH; ++i) { + buf = GString::format("{0:s}{1:.4g}", + i > 0 ? " " : "", privateDicts[0].stemSnapH[i]); + eexecWrite(&eb, buf->getCString()); + delete buf; + } + eexecWrite(&eb, "] def\n"); } - eexecWrite(&eb, "] def\n"); } if (privateDicts[0].nStemSnapV) { - eexecWrite(&eb, "/StemSnapV ["); - for (i = 0; i < privateDicts[0].nStemSnapV; ++i) { - buf = GString::format("{0:s}{1:.4g}", - i > 0 ? " " : "", privateDicts[0].stemSnapV[i]); - eexecWrite(&eb, buf->getCString()); - delete buf; + // the StemSnapV array should be unique values in ascending order -- + // if not, just skip it + for (i = 1; i < privateDicts[0].nStemSnapV; ++i) { + if (privateDicts[0].stemSnapV[i-1] >= privateDicts[0].stemSnapV[i]) { + break; + } + } + if (i == privateDicts[0].nStemSnapV) { + eexecWrite(&eb, "/StemSnapV ["); + for (i = 0; i < privateDicts[0].nStemSnapV; ++i) { + buf = GString::format("{0:s}{1:.4g}", + i > 0 ? " " : "", privateDicts[0].stemSnapV[i]); + eexecWrite(&eb, buf->getCString()); + delete buf; + } + eexecWrite(&eb, "] def\n"); } - eexecWrite(&eb, "] def\n"); } if (privateDicts[0].hasForceBold) { buf = GString::format("/ForceBold {0:s} def\n", @@ -719,24 +737,42 @@ void FoFiType1C::convertToCIDType0(char delete buf; } if (privateDicts[i].nStemSnapH) { - (*outputFunc)(outputStream, "/StemSnapH [", 12); - for (j = 0; j < privateDicts[i].nStemSnapH; ++j) { - buf = GString::format("{0:s}{1:.4g}", - j > 0 ? " " : "", privateDicts[i].stemSnapH[j]); - (*outputFunc)(outputStream, buf->getCString(), buf->getLength()); - delete buf; + // the StemSnapH array should be unique values in ascending order -- + // if not, just skip it + for (j = 1; j < privateDicts[i].nStemSnapH; ++j) { + if (privateDicts[i].stemSnapH[j-1] >= privateDicts[i].stemSnapH[j]) { + break; + } + } + if (j == privateDicts[0].nStemSnapH) { + (*outputFunc)(outputStream, "/StemSnapH [", 12); + for (j = 0; j < privateDicts[i].nStemSnapH; ++j) { + buf = GString::format("{0:s}{1:.4g}", + j > 0 ? " " : "", privateDicts[i].stemSnapH[j]); + (*outputFunc)(outputStream, buf->getCString(), buf->getLength()); + delete buf; + } + (*outputFunc)(outputStream, "] def\n", 6); } - (*outputFunc)(outputStream, "] def\n", 6); } if (privateDicts[i].nStemSnapV) { - (*outputFunc)(outputStream, "/StemSnapV [", 12); - for (j = 0; j < privateDicts[i].nStemSnapV; ++j) { - buf = GString::format("{0:s}{1:.4g}", - j > 0 ? " " : "", privateDicts[i].stemSnapV[j]); - (*outputFunc)(outputStream, buf->getCString(), buf->getLength()); - delete buf; + // the StemSnapV array should be unique values in ascending order -- + // if not, just skip it + for (j = 1; j < privateDicts[i].nStemSnapV; ++j) { + if (privateDicts[i].stemSnapV[j-1] >= privateDicts[i].stemSnapV[j]) { + break; + } + } + if (j == privateDicts[0].nStemSnapV) { + (*outputFunc)(outputStream, "/StemSnapV [", 12); + for (j = 0; j < privateDicts[i].nStemSnapV; ++j) { + buf = GString::format("{0:s}{1:.4g}", + j > 0 ? " " : "", privateDicts[i].stemSnapV[j]); + (*outputFunc)(outputStream, buf->getCString(), buf->getLength()); + delete buf; + } + (*outputFunc)(outputStream, "] def\n", 6); } - (*outputFunc)(outputStream, "] def\n", 6); } if (privateDicts[i].hasForceBold) { buf = GString::format("/ForceBold {0:s} def\n", @@ -1017,24 +1053,44 @@ void FoFiType1C::convertToType0(char *ps delete buf; } if (privateDicts[fd].nStemSnapH) { - eexecWrite(&eb, "/StemSnapH ["); - for (k = 0; k < privateDicts[fd].nStemSnapH; ++k) { - buf = GString::format("{0:s}{1:.4g}", - k > 0 ? " " : "", privateDicts[fd].stemSnapH[k]); - eexecWrite(&eb, buf->getCString()); - delete buf; + // the StemSnapH array should be unique values in ascending order -- + // if not, just skip it + for (k = 1; k < privateDicts[fd].nStemSnapH; ++k) { + if (privateDicts[fd].stemSnapH[k-1] >= privateDicts[fd].stemSnapH[k]) { + break; + } + } + if (k == privateDicts[0].nStemSnapH) { + eexecWrite(&eb, "/StemSnapH ["); + for (k = 0; k < privateDicts[fd].nStemSnapH; ++k) { + buf = GString::format("{0:s}{1:.4g}", + k > 0 ? " " : "", + privateDicts[fd].stemSnapH[k]); + eexecWrite(&eb, buf->getCString()); + delete buf; + } + eexecWrite(&eb, "] def\n"); } - eexecWrite(&eb, "] def\n"); } if (privateDicts[fd].nStemSnapV) { - eexecWrite(&eb, "/StemSnapV ["); - for (k = 0; k < privateDicts[fd].nStemSnapV; ++k) { - buf = GString::format("{0:s}{1:.4g}", - k > 0 ? " " : "", privateDicts[fd].stemSnapV[k]); - eexecWrite(&eb, buf->getCString()); - delete buf; + // the StemSnapV array should be unique values in ascending order -- + // if not, just skip it + for (k = 1; k < privateDicts[fd].nStemSnapV; ++k) { + if (privateDicts[fd].stemSnapV[k-1] >= privateDicts[fd].stemSnapV[k]) { + break; + } + } + if (k == privateDicts[0].nStemSnapV) { + eexecWrite(&eb, "/StemSnapV ["); + for (k = 0; k < privateDicts[fd].nStemSnapV; ++k) { + buf = GString::format("{0:s}{1:.4g}", + k > 0 ? " " : "", + privateDicts[fd].stemSnapV[k]); + eexecWrite(&eb, buf->getCString()); + delete buf; + } + eexecWrite(&eb, "] def\n"); } - eexecWrite(&eb, "] def\n"); } if (privateDicts[fd].hasForceBold) { buf = GString::format("/ForceBold {0:s} def\n", diff -uNrp xpdf-3.03/fofi/Makefile.in xpdf-3.04/fofi/Makefile.in --- xpdf-3.03/fofi/Makefile.in 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/fofi/Makefile.in 2014-05-28 20:50:50.000000000 +0200 @@ -14,7 +14,7 @@ VPATH = @srcdir@ GOOSRCDIR = $(srcdir)/../goo GOOLIBDIR = ../goo -CXXFLAGS = @CXXFLAGS@ @DEFS@ -I.. -I$(GOOSRCDIR) -I$(srcdir) +CXXFLAGS = @CXXFLAGS@ @DEFS@ -I.. -I$(srcdir)/.. -I$(GOOSRCDIR) -I$(srcdir) CXX = @CXX@ AR = @AR@ @@ -68,4 +68,4 @@ clean: depend: $(CXX) $(CXXFLAGS) -MM $(CXX_SRC) >Makefile.dep -include Makefile.dep +-include Makefile.dep diff -uNrp xpdf-3.03/goo/gfile.cc xpdf-3.04/goo/gfile.cc --- xpdf-3.03/goo/gfile.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/goo/gfile.cc 2014-05-28 20:50:50.000000000 +0200 @@ -155,7 +156,7 @@ GString *appendToPath(GString *path, con tmp = new GString(path); tmp->append('/'); tmp->append(fileName); - GetFullPathName(tmp->getCString(), sizeof(buf), buf, &fp); + GetFullPathNameA(tmp->getCString(), sizeof(buf), buf, &fp); delete tmp; path->clear(); path->append(buf); @@ -550,6 +552,14 @@ GBool openTempFile(GString **name, FILE #endif } +GBool createDir(char *path, int mode) { +#ifdef _WIN32 + return !mkdir(path); +#else + return !mkdir(path, mode); +#endif +} + GBool executeCommand(char *cmd) { #ifdef VMS return system(cmd) ? gTrue : gFalse; @@ -688,6 +698,30 @@ char *getLine(char *buf, int size, FILE return buf; } +int gfseek(FILE *f, GFileOffset offset, int whence) { +#if HAVE_FSEEKO + return fseeko(f, offset, whence); +#elif HAVE_FSEEK64 + return fseek64(f, offset, whence); +#elif HAVE_FSEEKI64 + return _fseeki64(f, offset, whence); +#else + return fseek(f, offset, whence); +#endif +} + +GFileOffset gftell(FILE *f) { +#if HAVE_FSEEKO + return ftello(f); +#elif HAVE_FSEEK64 + return ftell64(f); +#elif HAVE_FSEEKI64 + return _ftelli64(f); +#else + return ftell(f); +#endif +} + //------------------------------------------------------------------------ // GDir and GDirEntry //------------------------------------------------------------------------ @@ -715,8 +749,8 @@ GDirEntry::GDirEntry(char *dirPath, char #else s = new GString(dirPath); appendToPath(s, nameA); #ifdef _WIN32 - fa = GetFileAttributes(s->getCString()); + fa = GetFileAttributesA(s->getCString()); dir = (fa != 0xFFFFFFFF && (fa & FILE_ATTRIBUTE_DIRECTORY)); #else if (stat(s->getCString(), &st) == 0) @@ -734,15 +768,16 @@ GDirEntry::~GDirEntry() { #if defined(_WIN32) GString *tmp; tmp = path->copy(); tmp->append("/*.*"); - hnd = FindFirstFile(tmp->getCString(), &ffd); + hnd = FindFirstFileA(tmp->getCString(), &ffd); delete tmp; #elif defined(ACORN) #elif defined(MACOS) +#elif defined(ANDROID) #else dir = opendir(name); #ifdef VMS @@ -753,13 +788,14 @@ GDir::GDir(char *name, GBool doStatA) { if (hnd) { FindClose(hnd); hnd = NULL; } #elif defined(ACORN) #elif defined(MACOS) +#elif defined(ANDROID) #else if (dir) closedir(dir); @@ -769,10 +805,10 @@ GDir::~GDir() { #if defined(_WIN32) if (hnd) { e = new GDirEntry(path->getCString(), ffd.cFileName, doStat); - if (hnd && !FindNextFile(hnd, &ffd)) { + if (hnd && !FindNextFileA(hnd, &ffd)) { FindClose(hnd); hnd = NULL; } @@ -781,6 +817,7 @@ GDirEntry *GDir::getNextEntry() { } #elif defined(ACORN) #elif defined(MACOS) +#elif defined(ANDROID) #elif defined(VMS) struct dirent *ent; e = NULL; @@ -813,17 +850,18 @@ GDirEntry *GDir::getNextEntry() { if (hnd) FindClose(hnd); tmp = path->copy(); tmp->append("/*.*"); - hnd = FindFirstFile(tmp->getCString(), &ffd); + hnd = FindFirstFileA(tmp->getCString(), &ffd); delete tmp; #elif defined(ACORN) #elif defined(MACOS) +#elif defined(ANDROID) #else if (dir) rewinddir(dir); diff -uNrp xpdf-3.03/goo/gfile.h xpdf-3.04/goo/gfile.h --- xpdf-3.03/goo/gfile.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/goo/gfile.h 2014-05-28 20:50:50.000000000 +0200 @@ -24,6 +24,7 @@ #elif defined(ACORN) #elif defined(MACOS) # include +#elif defined(ANDROID) #else # include # include @@ -84,12 +85,15 @@ extern time_t getModTime(char *fileName) // reopened later for reading, but not for writing. The string // should be "w" or "wb". Returns true on success. extern GBool openTempFile(GString **name, FILE **f, - const char *mode, char *ext); + const char *mode, const char *ext); + +// Create a directory. Returns true on success. +extern GBool createDir(char *path, int mode); // Execute . Returns true on success. extern GBool executeCommand(char *cmd); @@ -106,6 +110,28 @@ extern FILE *openFile(const char *path, // conventions. extern char *getLine(char *buf, int size, FILE *f); +// Type used by gfseek/gftell for file offsets. This will be 64 bits +// on systems that support it. +#if HAVE_FSEEKO +typedef off_t GFileOffset; +#define GFILEOFFSET_MAX 0x7fffffffffffffffLL +#elif HAVE_FSEEK64 +typedef long long GFileOffset; +#define GFILEOFFSET_MAX 0x7fffffffffffffffLL +#elif HAVE_FSEEKI64 +typedef __int64 GFileOffset; +#define GFILEOFFSET_MAX 0x7fffffffffffffffLL +#else +typedef long GFileOffset; +#define GFILEOFFSET_MAX LONG_MAX +#endif + +// Like fseek, but uses a 64-bit file offset if available. +extern int gfseek(FILE *f, GFileOffset offset, int whence); + +// Like ftell, but returns a 64-bit file offset if available. +extern GFileOffset gftell(FILE *f); + //------------------------------------------------------------------------ // GDir and GDirEntry //------------------------------------------------------------------------ @@ -136,11 +162,12 @@ private: #if defined(_WIN32) - WIN32_FIND_DATA ffd; + WIN32_FIND_DATAA ffd; HANDLE hnd; #elif defined(ACORN) #elif defined(MACOS) +#elif defined(ANDROID) #else DIR *dir; // the DIR structure from opendir() #ifdef VMS diff -uNrp xpdf-3.03/goo/Makefile.in xpdf-3.04/goo/Makefile.in --- xpdf-3.03/goo/Makefile.in 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/goo/Makefile.in 2014-05-28 20:50:50.000000000 +0200 @@ -11,8 +11,8 @@ SHELL = /bin/sh srcdir = @srcdir@ VPATH = @srcdir@ -CFLAGS = @CFLAGS@ @DEFS@ -I.. -I$(srcdir) -CXXFLAGS = @CXXFLAGS@ @DEFS@ -I.. -I$(srcdir) +CFLAGS = @CFLAGS@ @DEFS@ -I.. -I$(srcdir)/.. -I$(srcdir) +CXXFLAGS = @CXXFLAGS@ @DEFS@ -I.. -I$(srcdir)/.. -I$(srcdir) CC = @CC@ CXX = @CXX@ @@ -68,4 +68,4 @@ depend: $(CXX) $(CXXFLAGS) -MM $(CXX_SRC) >Makefile.dep $(CC) $(CFLAGS) -MM $(C_SRC) >>Makefile.dep -include Makefile.dep +-include Makefile.dep diff -uNrp xpdf-3.03/splash/Makefile.in xpdf-3.04/splash/Makefile.in --- xpdf-3.03/splash/Makefile.in 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/Makefile.in 2014-05-28 20:50:50.000000000 +0200 @@ -16,7 +16,7 @@ GOOLIBDIR = ../goo FOFISRCDIR = $(srcdir)/../fofi FOFILIBDIR = ../fofi -CXXFLAGS = @CXXFLAGS@ @DEFS@ -I.. -I$(GOOSRCDIR) -I$(FOFISRCDIR) -I$(srcdir) @t1_CFLAGS@ @freetype2_CFLAGS@ +CXXFLAGS = @CXXFLAGS@ @DEFS@ -I.. -I$(srcdir)/.. -I$(GOOSRCDIR) -I$(FOFISRCDIR) -I$(srcdir) @freetype2_CFLAGS@ CXX = @CXX@ AR = @AR@ @@ -48,9 +48,6 @@ CXX_SRC = \ $(srcdir)/SplashPattern.cc \ $(srcdir)/SplashScreen.cc \ $(srcdir)/SplashState.cc \ - $(srcdir)/SplashT1Font.cc \ - $(srcdir)/SplashT1FontEngine.cc \ - $(srcdir)/SplashT1FontFile.cc \ $(srcdir)/SplashXPath.cc \ $(srcdir)/SplashXPathScanner.cc @@ -75,9 +72,6 @@ SPLASH_OBJS = \ SplashPattern.o \ SplashScreen.o \ SplashState.o \ - SplashT1Font.o \ - SplashT1FontEngine.o \ - SplashT1FontFile.o \ SplashXPath.o \ SplashXPathScanner.o @@ -96,4 +90,4 @@ clean: depend: $(CXX) $(CXXFLAGS) -MM $(CXX_SRC) >Makefile.dep -include Makefile.dep +-include Makefile.dep diff -uNrp xpdf-3.03/splash/Splash.cc xpdf-3.04/splash/Splash.cc --- xpdf-3.03/splash/Splash.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/Splash.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // Splash.cc // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #include @@ -29,7 +31,7 @@ //------------------------------------------------------------------------ -#define splashAAGamma 1.5 +#define splashAAGamma 0.67 // distance of Bezier control point from center for circle approximation // = (4 * (sqrt(2) - 1) / 3) * r @@ -46,41 +48,12 @@ static inline Guchar clip255(int x) { return x < 0 ? 0 : x > 255 ? 255 : x; } -// The PDF spec says that all pixels whose *centers* lie within the -// image target region get painted, so we want to round n+0.5 down to -// n. But this causes problems, e.g., with PDF files that fill a -// rectangle with black and then draw an image to the exact same -// rectangle, so we instead use the fill scan conversion rule. -// However, the correct rule works better for glyphs, so we also -// provide that option in fillImageMask. -#if 0 -static inline int imgCoordMungeLower(SplashCoord x) { - return splashCeil(x + 0.5) - 1; -} -static inline int imgCoordMungeUpper(SplashCoord x) { - return splashCeil(x + 0.5) - 1; -} -#else -static inline int imgCoordMungeLower(SplashCoord x) { - return splashFloor(x); -} -static inline int imgCoordMungeUpper(SplashCoord x) { - return splashFloor(x) + 1; -} -static inline int imgCoordMungeLowerC(SplashCoord x, GBool glyphMode) { - return glyphMode ? (splashCeil(x + 0.5) - 1) : splashFloor(x); -} -static inline int imgCoordMungeUpperC(SplashCoord x, GBool glyphMode) { - return glyphMode ? (splashCeil(x + 0.5) - 1) : (splashFloor(x) + 1); -} -#endif - // Used by drawImage and fillImageMask to divide the target // quadrilateral into sections. struct ImageSection { int y0, y1; // actual y range int ia0, ia1; // vertex indices for edge A - int ib0, ib1; // vertex indices for edge A + int ib0, ib1; // vertex indices for edge B SplashCoord xa0, ya0, xa1, ya1; // edge A SplashCoord dxdya; // slope of edge A SplashCoord xb0, yb0, xb1, yb1; // edge B @@ -94,41 +67,26 @@ struct ImageSection { #define splashPipeMaxStages 9 struct SplashPipe { - // pixel coordinates - int x, y; - // source pattern SplashPattern *pattern; // source alpha and color Guchar aInput; - GBool usesShape; - SplashColorPtr cSrc; SplashColor cSrcVal; - // non-isolated group alpha0 - Guchar *alpha0Ptr; - - // soft mask - SplashColorPtr softMaskPtr; - - // destination alpha and color - SplashColorPtr destColorPtr; - int destColorMask; - Guchar *destAlphaPtr; - - // shape - Guchar shape; - - // result alpha and color + // special cases and result color GBool noTransparency; + GBool shapeOnly; SplashPipeResultColorCtrl resultColorCtrl; // non-isolated group correction + // (this is only used when Splash::composite() is called to composite + // a non-isolated group onto the backdrop) GBool nonIsolatedGroup; // the "run" function - void (Splash::*run)(SplashPipe *pipe); + void (Splash::*run)(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); }; SplashPipeResultColorCtrl Splash::pipeResultColorNoAlphaBlend[] = { @@ -208,36 +166,37 @@ inline void Splash::updateModY(int y) { // pipeline //------------------------------------------------------------------------ -inline void Splash::pipeInit(SplashPipe *pipe, int x, int y, - SplashPattern *pattern, SplashColorPtr cSrc, +inline void Splash::pipeInit(SplashPipe *pipe, SplashPattern *pattern, Guchar aInput, GBool usesShape, GBool nonIsolatedGroup) { - pipeSetXY(pipe, x, y); pipe->pattern = NULL; // source color - if (pattern) { - if (pattern->isStatic()) { - pattern->getColor(x, y, pipe->cSrcVal); - } else { - pipe->pattern = pattern; - } - pipe->cSrc = pipe->cSrcVal; + if (pattern && pattern->isStatic()) { + pattern->getColor(0, 0, pipe->cSrcVal); + pipe->pattern = NULL; } else { - pipe->cSrc = cSrc; + pipe->pattern = pattern; } // source alpha pipe->aInput = aInput; - pipe->usesShape = usesShape; - // result alpha - if (aInput == 255 && !state->softMask && !usesShape && - !state->inNonIsolatedGroup && !nonIsolatedGroup) { - pipe->noTransparency = gTrue; - } else { - pipe->noTransparency = gFalse; - } + // special cases + pipe->noTransparency = aInput == 255 && + !state->softMask && + !usesShape && + !state->inNonIsolatedGroup && + !state->inKnockoutGroup && + !nonIsolatedGroup && + state->overprintMask == 0xffffffff; + pipe->shapeOnly = aInput == 255 && + !state->softMask && + usesShape && + !state->inNonIsolatedGroup && + !state->inKnockoutGroup && + !nonIsolatedGroup && + state->overprintMask == 0xffffffff; // result color if (pipe->noTransparency) { @@ -255,33 +214,48 @@ inline void Splash::pipeInit(SplashPipe // select the 'run' function pipe->run = &Splash::pipeRun; if (!pipe->pattern && pipe->noTransparency && !state->blendFunc) { - if (bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr) { + if (bitmap->mode == splashModeMono1 && !bitmap->alpha) { pipe->run = &Splash::pipeRunSimpleMono1; - } else if (bitmap->mode == splashModeMono8 && pipe->destAlphaPtr) { + } else if (bitmap->mode == splashModeMono8 && bitmap->alpha) { pipe->run = &Splash::pipeRunSimpleMono8; - } else if (bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr) { + } else if (bitmap->mode == splashModeRGB8 && bitmap->alpha) { pipe->run = &Splash::pipeRunSimpleRGB8; - } else if (bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr) { + } else if (bitmap->mode == splashModeBGR8 && bitmap->alpha) { pipe->run = &Splash::pipeRunSimpleBGR8; #if SPLASH_CMYK - } else if (bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr) { + } else if (bitmap->mode == splashModeCMYK8 && bitmap->alpha) { pipe->run = &Splash::pipeRunSimpleCMYK8; #endif } + } else if (!pipe->pattern && pipe->shapeOnly && !state->blendFunc) { + if (bitmap->mode == splashModeMono1 && !bitmap->alpha) { + pipe->run = &Splash::pipeRunShapeMono1; + } else if (bitmap->mode == splashModeMono8 && bitmap->alpha) { + pipe->run = &Splash::pipeRunShapeMono8; + } else if (bitmap->mode == splashModeRGB8 && bitmap->alpha) { + pipe->run = &Splash::pipeRunShapeRGB8; + } else if (bitmap->mode == splashModeBGR8 && bitmap->alpha) { + pipe->run = &Splash::pipeRunShapeBGR8; +#if SPLASH_CMYK + } else if (bitmap->mode == splashModeCMYK8 && bitmap->alpha) { + pipe->run = &Splash::pipeRunShapeCMYK8; +#endif + } } else if (!pipe->pattern && !pipe->noTransparency && !state->softMask && - pipe->usesShape && - !(state->inNonIsolatedGroup && alpha0Bitmap->alpha) && + usesShape && + !(state->inNonIsolatedGroup && groupBackBitmap->alpha) && + !state->inKnockoutGroup && !state->blendFunc && !pipe->nonIsolatedGroup) { - if (bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr) { + if (bitmap->mode == splashModeMono1 && !bitmap->alpha) { pipe->run = &Splash::pipeRunAAMono1; - } else if (bitmap->mode == splashModeMono8 && pipe->destAlphaPtr) { + } else if (bitmap->mode == splashModeMono8 && bitmap->alpha) { pipe->run = &Splash::pipeRunAAMono8; - } else if (bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr) { + } else if (bitmap->mode == splashModeRGB8 && bitmap->alpha) { pipe->run = &Splash::pipeRunAARGB8; - } else if (bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr) { + } else if (bitmap->mode == splashModeBGR8 && bitmap->alpha) { pipe->run = &Splash::pipeRunAABGR8; #if SPLASH_CMYK - } else if (bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr) { + } else if (bitmap->mode == splashModeCMYK8 && bitmap->alpha) { pipe->run = &Splash::pipeRunAACMYK8; #endif } @@ -289,932 +263,1654 @@ inline void Splash::pipeInit(SplashPipe } // general case -void Splash::pipeRun(SplashPipe *pipe) { - Guchar aSrc, aDest, alphaI, alphaIm1, alpha0, aResult; - SplashColor cSrcNonIso, cDest, cBlend; - SplashColorPtr cSrc; - Guchar cResult0, cResult1, cResult2, cResult3; - int t; +void Splash::pipeRun(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar *shapePtr2; + Guchar shape, aSrc, aDest, alphaI, alphaIm1, alpha0, aResult; + SplashColor cSrc, cDest, cBlend; + Guchar shapeVal, cResult0, cResult1, cResult2, cResult3; + int cSrcStride, shapeStride, x, lastX, t, i; + SplashColorPtr destColorPtr; + Guchar destColorMask; + Guchar *destAlphaPtr; + SplashColorPtr color0Ptr; + Guchar color0Mask; + Guchar *alpha0Ptr; + SplashColorPtr softMaskPtr; #if SPLASH_CMYK SplashColor cSrc2, cDest2; #endif - //----- source color - - // static pattern: handled in pipeInit - // fixed color: handled in pipeInit + if (cSrcPtr && !pipe->pattern) { + cSrcStride = bitmapComps; + } else { + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; + } - // dynamic pattern - if (pipe->pattern) { - pipe->pattern->getColor(pipe->x, pipe->y, pipe->cSrcVal); + if (shapePtr) { + shapePtr2 = shapePtr; + shapeStride = 1; + for (; x0 <= x1; ++x0) { + if (*shapePtr2) { + break; + } + cSrcPtr += cSrcStride; + ++shapePtr2; + } + } else { + shapeVal = 0xff; + shapePtr2 = &shapeVal; + shapeStride = 0; + } + if (x0 > x1) { + return; + } + updateModX(x0); + updateModY(y); + lastX = x0; + + if (bitmap->mode == splashModeMono1) { + destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)]; + destColorMask = 0x80 >> (x0 & 7); + } else { + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0 * bitmapComps]; + destColorMask = 0; // make gcc happy + } + if (bitmap->alpha) { + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; + } else { + destAlphaPtr = NULL; + } + if (state->softMask) { + softMaskPtr = &state->softMask->data[y * state->softMask->rowSize + x0]; + } else { + softMaskPtr = NULL; + } + if (state->inKnockoutGroup) { + if (bitmap->mode == splashModeMono1) { + color0Ptr = + &groupBackBitmap->data[(groupBackY + y) * groupBackBitmap->rowSize + + ((groupBackX + x0) >> 3)]; + color0Mask = 0x80 >> ((groupBackX + x0) & 7); + } else { + color0Ptr = + &groupBackBitmap->data[(groupBackY + y) * groupBackBitmap->rowSize + + (groupBackX + x0) * bitmapComps]; + color0Mask = 0; // make gcc happy + } + } else { + color0Ptr = NULL; + color0Mask = 0; // make gcc happy + } + if (state->inNonIsolatedGroup && groupBackBitmap->alpha) { + alpha0Ptr = + &groupBackBitmap->alpha[(groupBackY + y) * groupBackBitmap->width + + (groupBackX + x0)]; + } else { + alpha0Ptr = NULL; } - if (pipe->noTransparency && !state->blendFunc) { + for (x = x0; x <= x1; ++x) { - //----- write destination pixel + //----- shape - switch (bitmap->mode) { - case splashModeMono1: - cResult0 = state->grayTransfer[pipe->cSrc[0]]; - if (state->screen->test(pipe->x, pipe->y, cResult0)) { - *pipe->destColorPtr |= pipe->destColorMask; + shape = *shapePtr2; + if (!shape) { + if (bitmap->mode == splashModeMono1) { + destColorPtr += destColorMask & 1; + destColorMask = (destColorMask << 7) | (destColorMask >> 1); } else { - *pipe->destColorPtr &= ~pipe->destColorMask; - } - if (!(pipe->destColorMask >>= 1)) { - pipe->destColorMask = 0x80; - ++pipe->destColorPtr; + destColorPtr += bitmapComps; } - break; - case splashModeMono8: - *pipe->destColorPtr++ = state->grayTransfer[pipe->cSrc[0]]; - break; - case splashModeRGB8: - *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]]; - *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]]; - *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]]; - break; - case splashModeBGR8: - *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]]; - *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]]; - *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]]; - break; -#if SPLASH_CMYK - case splashModeCMYK8: - if (state->overprintMask & 1) { - pipe->destColorPtr[0] = state->cmykTransferC[pipe->cSrc[0]]; + if (destAlphaPtr) { + ++destAlphaPtr; } - if (state->overprintMask & 2) { - pipe->destColorPtr[1] = state->cmykTransferM[pipe->cSrc[1]]; + if (softMaskPtr) { + ++softMaskPtr; } - if (state->overprintMask & 4) { - pipe->destColorPtr[2] = state->cmykTransferY[pipe->cSrc[2]]; + if (color0Ptr) { + if (bitmap->mode == splashModeMono1) { + color0Ptr += color0Mask & 1; + color0Mask = (color0Mask << 7) | (color0Mask >> 1); + } else { + color0Ptr += bitmapComps; + } } - if (state->overprintMask & 8) { - pipe->destColorPtr[3] = state->cmykTransferK[pipe->cSrc[3]]; + if (alpha0Ptr) { + ++alpha0Ptr; } - pipe->destColorPtr += 4; - break; -#endif + cSrcPtr += cSrcStride; + shapePtr2 += shapeStride; + continue; } - if (pipe->destAlphaPtr) { - *pipe->destAlphaPtr++ = 255; + lastX = x; + + //----- source color + + // static pattern: handled in pipeInit + // fixed color: handled in pipeInit + + // dynamic pattern + if (pipe->pattern) { + pipe->pattern->getColor(x, y, pipe->cSrcVal); } - } else { + if (pipe->noTransparency && !state->blendFunc) { - //----- read destination pixel + //----- write destination pixel - switch (bitmap->mode) { - case splashModeMono1: - cDest[0] = (*pipe->destColorPtr & pipe->destColorMask) ? 0xff : 0x00; - break; - case splashModeMono8: - cDest[0] = *pipe->destColorPtr; - break; - case splashModeRGB8: - cDest[0] = pipe->destColorPtr[0]; - cDest[1] = pipe->destColorPtr[1]; - cDest[2] = pipe->destColorPtr[2]; - break; - case splashModeBGR8: - cDest[0] = pipe->destColorPtr[2]; - cDest[1] = pipe->destColorPtr[1]; - cDest[2] = pipe->destColorPtr[0]; - break; + switch (bitmap->mode) { + case splashModeMono1: + cResult0 = state->grayTransfer[cSrcPtr[0]]; + if (state->screen->test(x, y, cResult0)) { + *destColorPtr |= destColorMask; + } else { + *destColorPtr &= ~destColorMask; + } + destColorPtr += destColorMask & 1; + destColorMask = (destColorMask << 7) | (destColorMask >> 1); + break; + case splashModeMono8: + *destColorPtr++ = state->grayTransfer[cSrcPtr[0]]; + break; + case splashModeRGB8: + destColorPtr[0] = state->rgbTransferR[cSrcPtr[0]]; + destColorPtr[1] = state->rgbTransferG[cSrcPtr[1]]; + destColorPtr[2] = state->rgbTransferB[cSrcPtr[2]]; + destColorPtr += 3; + break; + case splashModeBGR8: + destColorPtr[0] = state->rgbTransferB[cSrcPtr[2]]; + destColorPtr[1] = state->rgbTransferG[cSrcPtr[1]]; + destColorPtr[2] = state->rgbTransferR[cSrcPtr[0]]; + destColorPtr += 3; + break; #if SPLASH_CMYK - case splashModeCMYK8: - cDest[0] = pipe->destColorPtr[0]; - cDest[1] = pipe->destColorPtr[1]; - cDest[2] = pipe->destColorPtr[2]; - cDest[3] = pipe->destColorPtr[3]; - break; + case splashModeCMYK8: + destColorPtr[0] = state->cmykTransferC[cSrcPtr[0]]; + destColorPtr[1] = state->cmykTransferM[cSrcPtr[1]]; + destColorPtr[2] = state->cmykTransferY[cSrcPtr[2]]; + destColorPtr[3] = state->cmykTransferK[cSrcPtr[3]]; + destColorPtr += 4; + break; #endif - } - if (pipe->destAlphaPtr) { - aDest = *pipe->destAlphaPtr; - } else { - aDest = 0xff; - } + } + if (destAlphaPtr) { + *destAlphaPtr++ = 255; + } - //----- source alpha + } else { // if (noTransparency && !blendFunc) + + //----- read destination pixel + // (or backdrop color, for knockout groups) + + if (color0Ptr) { + + switch (bitmap->mode) { + case splashModeMono1: + cDest[0] = (*color0Ptr & color0Mask) ? 0xff : 0x00; + color0Ptr += color0Mask & 1; + color0Mask = (color0Mask << 7) | (color0Mask >> 1); + break; + case splashModeMono8: + cDest[0] = *color0Ptr++; + break; + case splashModeRGB8: + cDest[0] = color0Ptr[0]; + cDest[1] = color0Ptr[1]; + cDest[2] = color0Ptr[2]; + color0Ptr += 3; + break; + case splashModeBGR8: + cDest[2] = color0Ptr[0]; + cDest[1] = color0Ptr[1]; + cDest[0] = color0Ptr[2]; + color0Ptr += 3; + break; +#if SPLASH_CMYK + case splashModeCMYK8: + cDest[0] = color0Ptr[0]; + cDest[1] = color0Ptr[1]; + cDest[2] = color0Ptr[2]; + cDest[3] = color0Ptr[3]; + color0Ptr += 4; + break; +#endif + } + + } else { + + switch (bitmap->mode) { + case splashModeMono1: + cDest[0] = (*destColorPtr & destColorMask) ? 0xff : 0x00; + break; + case splashModeMono8: + cDest[0] = *destColorPtr; + break; + case splashModeRGB8: + cDest[0] = destColorPtr[0]; + cDest[1] = destColorPtr[1]; + cDest[2] = destColorPtr[2]; + break; + case splashModeBGR8: + cDest[0] = destColorPtr[2]; + cDest[1] = destColorPtr[1]; + cDest[2] = destColorPtr[0]; + break; +#if SPLASH_CMYK + case splashModeCMYK8: + cDest[0] = destColorPtr[0]; + cDest[1] = destColorPtr[1]; + cDest[2] = destColorPtr[2]; + cDest[3] = destColorPtr[3]; + break; +#endif + } + + } - if (state->softMask) { - if (pipe->usesShape) { - aSrc = div255(div255(pipe->aInput * *pipe->softMaskPtr++) * - pipe->shape); + if (destAlphaPtr) { + aDest = *destAlphaPtr; } else { - aSrc = div255(pipe->aInput * *pipe->softMaskPtr++); + aDest = 0xff; + } + + //----- overprint + + for (i = 0; i < bitmapComps; ++i) { +#if SPLASH_CMYK + if (state->overprintMask & (1 << i)) { + cSrc[i] = cSrcPtr[i]; + } else { + cSrc[i] = div255(aDest * cDest[i]); + } +#else + cSrc[i] = cSrcPtr[i]; +#endif } - } else if (pipe->usesShape) { - aSrc = div255(pipe->aInput * pipe->shape); - } else { - aSrc = pipe->aInput; - } - //----- non-isolated group correction + //----- source alpha - if (pipe->nonIsolatedGroup) { - // This path is only used when Splash::composite() is called to - // composite a non-isolated group onto the backdrop. In this - // case, pipe->shape is the source (group) alpha. - if (pipe->shape == 0) { - // this value will be multiplied by zero later, so it doesn't - // matter what we use - cSrc = pipe->cSrc; + if (softMaskPtr) { + if (shapePtr) { + aSrc = div255(div255(pipe->aInput * *softMaskPtr++) * shape); + } else { + aSrc = div255(pipe->aInput * *softMaskPtr++); + } + } else if (shapePtr) { + aSrc = div255(pipe->aInput * shape); } else { - t = (aDest * 255) / pipe->shape - aDest; + aSrc = pipe->aInput; + } + + //----- non-isolated group correction + + if (pipe->nonIsolatedGroup) { + // This path is only used when Splash::composite() is called to + // composite a non-isolated group onto the backdrop. In this + // case, shape is the source (group) alpha. + t = (aDest * 255) / shape - aDest; switch (bitmap->mode) { #if SPLASH_CMYK case splashModeCMYK8: - cSrcNonIso[3] = clip255(pipe->cSrc[3] + - ((pipe->cSrc[3] - cDest[3]) * t) / 255); + cSrc[3] = clip255(cSrc[3] + ((cSrc[3] - cDest[3]) * t) / 255); #endif case splashModeRGB8: case splashModeBGR8: - cSrcNonIso[2] = clip255(pipe->cSrc[2] + - ((pipe->cSrc[2] - cDest[2]) * t) / 255); - cSrcNonIso[1] = clip255(pipe->cSrc[1] + - ((pipe->cSrc[1] - cDest[1]) * t) / 255); + cSrc[2] = clip255(cSrc[2] + ((cSrc[2] - cDest[2]) * t) / 255); + cSrc[1] = clip255(cSrc[1] + ((cSrc[1] - cDest[1]) * t) / 255); case splashModeMono1: case splashModeMono8: - cSrcNonIso[0] = clip255(pipe->cSrc[0] + - ((pipe->cSrc[0] - cDest[0]) * t) / 255); + cSrc[0] = clip255(cSrc[0] + ((cSrc[0] - cDest[0]) * t) / 255); break; } - cSrc = cSrcNonIso; } - } else { - cSrc = pipe->cSrc; - } - //----- blend function + //----- blend function - if (state->blendFunc) { + if (state->blendFunc) { #if SPLASH_CMYK - if (bitmap->mode == splashModeCMYK8) { - // convert colors to additive - cSrc2[0] = 0xff - cSrc[0]; - cSrc2[1] = 0xff - cSrc[1]; - cSrc2[2] = 0xff - cSrc[2]; - cSrc2[3] = 0xff - cSrc[3]; - cDest2[0] = 0xff - cDest[0]; - cDest2[1] = 0xff - cDest[1]; - cDest2[2] = 0xff - cDest[2]; - cDest2[3] = 0xff - cDest[3]; - (*state->blendFunc)(cSrc2, cDest2, cBlend, bitmap->mode); - // convert result back to subtractive - cBlend[0] = 0xff - cBlend[0]; - cBlend[1] = 0xff - cBlend[1]; - cBlend[2] = 0xff - cBlend[2]; - cBlend[3] = 0xff - cBlend[3]; - } else + if (bitmap->mode == splashModeCMYK8) { + // convert colors to additive + cSrc2[0] = 0xff - cSrc[0]; + cSrc2[1] = 0xff - cSrc[1]; + cSrc2[2] = 0xff - cSrc[2]; + cSrc2[3] = 0xff - cSrc[3]; + cDest2[0] = 0xff - cDest[0]; + cDest2[1] = 0xff - cDest[1]; + cDest2[2] = 0xff - cDest[2]; + cDest2[3] = 0xff - cDest[3]; + (*state->blendFunc)(cSrc2, cDest2, cBlend, bitmap->mode); + // convert result back to subtractive + cBlend[0] = 0xff - cBlend[0]; + cBlend[1] = 0xff - cBlend[1]; + cBlend[2] = 0xff - cBlend[2]; + cBlend[3] = 0xff - cBlend[3]; + } else #endif - (*state->blendFunc)(cSrc, cDest, cBlend, bitmap->mode); - } - - //----- result alpha and non-isolated group element correction + (*state->blendFunc)(cSrc, cDest, cBlend, bitmap->mode); + } - if (pipe->noTransparency) { - alphaI = alphaIm1 = aResult = 255; - } else { - aResult = aSrc + aDest - div255(aSrc * aDest); + //----- result alpha and non-isolated group element correction // alphaI = alpha_i // alphaIm1 = alpha_(i-1) - if (pipe->alpha0Ptr) { - alpha0 = *pipe->alpha0Ptr++; - alphaI = aResult + alpha0 - div255(aResult * alpha0); - alphaIm1 = alpha0 + aDest - div255(alpha0 * aDest); + + if (pipe->noTransparency) { + alphaI = alphaIm1 = aResult = 255; + } else if (alpha0Ptr) { + if (color0Ptr) { + // non-isolated, knockout + aResult = aSrc; + alpha0 = *alpha0Ptr++; + alphaI = aSrc + alpha0 - div255(aSrc * alpha0); + alphaIm1 = alpha0; + } else { + // non-isolated, non-knockout + aResult = aSrc + aDest - div255(aSrc * aDest); + alpha0 = *alpha0Ptr++; + alphaI = aResult + alpha0 - div255(aResult * alpha0); + alphaIm1 = alpha0 + aDest - div255(alpha0 * aDest); + } } else { - alphaI = aResult; - alphaIm1 = aDest; + if (color0Ptr) { + // isolated, knockout + aResult = aSrc; + alphaI = aSrc; + alphaIm1 = 0; + } else { + // isolated, non-knockout + aResult = aSrc + aDest - div255(aSrc * aDest); + alphaI = aResult; + alphaIm1 = aDest; + } } - } - //----- result color + //----- result color - cResult0 = cResult1 = cResult2 = cResult3 = 0; // make gcc happy + cResult0 = cResult1 = cResult2 = cResult3 = 0; // make gcc happy - switch (pipe->resultColorCtrl) { + switch (pipe->resultColorCtrl) { - case splashPipeResultColorNoAlphaBlendMono: - cResult0 = state->grayTransfer[div255((255 - aDest) * cSrc[0] + - aDest * cBlend[0])]; - break; - case splashPipeResultColorNoAlphaBlendRGB: - cResult0 = state->rgbTransferR[div255((255 - aDest) * cSrc[0] + - aDest * cBlend[0])]; - cResult1 = state->rgbTransferG[div255((255 - aDest) * cSrc[1] + - aDest * cBlend[1])]; - cResult2 = state->rgbTransferB[div255((255 - aDest) * cSrc[2] + - aDest * cBlend[2])]; - break; + case splashPipeResultColorNoAlphaBlendMono: + cResult0 = state->grayTransfer[div255((255 - aDest) * cSrc[0] + + aDest * cBlend[0])]; + break; + case splashPipeResultColorNoAlphaBlendRGB: + cResult0 = state->rgbTransferR[div255((255 - aDest) * cSrc[0] + + aDest * cBlend[0])]; + cResult1 = state->rgbTransferG[div255((255 - aDest) * cSrc[1] + + aDest * cBlend[1])]; + cResult2 = state->rgbTransferB[div255((255 - aDest) * cSrc[2] + + aDest * cBlend[2])]; + break; #if SPLASH_CMYK - case splashPipeResultColorNoAlphaBlendCMYK: - cResult0 = state->cmykTransferC[div255((255 - aDest) * cSrc[0] + - aDest * cBlend[0])]; - cResult1 = state->cmykTransferM[div255((255 - aDest) * cSrc[1] + - aDest * cBlend[1])]; - cResult2 = state->cmykTransferY[div255((255 - aDest) * cSrc[2] + - aDest * cBlend[2])]; - cResult3 = state->cmykTransferK[div255((255 - aDest) * cSrc[3] + - aDest * cBlend[3])]; - break; + case splashPipeResultColorNoAlphaBlendCMYK: + cResult0 = state->cmykTransferC[div255((255 - aDest) * cSrc[0] + + aDest * cBlend[0])]; + cResult1 = state->cmykTransferM[div255((255 - aDest) * cSrc[1] + + aDest * cBlend[1])]; + cResult2 = state->cmykTransferY[div255((255 - aDest) * cSrc[2] + + aDest * cBlend[2])]; + cResult3 = state->cmykTransferK[div255((255 - aDest) * cSrc[3] + + aDest * cBlend[3])]; + break; #endif - case splashPipeResultColorAlphaNoBlendMono: - if (alphaI == 0) { - cResult0 = 0; - } else { - cResult0 = state->grayTransfer[((alphaI - aSrc) * cDest[0] + - aSrc * cSrc[0]) / alphaI]; - } - break; - case splashPipeResultColorAlphaNoBlendRGB: - if (alphaI == 0) { - cResult0 = 0; - cResult1 = 0; - cResult2 = 0; - } else { - cResult0 = state->rgbTransferR[((alphaI - aSrc) * cDest[0] + - aSrc * cSrc[0]) / alphaI]; - cResult1 = state->rgbTransferG[((alphaI - aSrc) * cDest[1] + - aSrc * cSrc[1]) / alphaI]; - cResult2 = state->rgbTransferB[((alphaI - aSrc) * cDest[2] + - aSrc * cSrc[2]) / alphaI]; - } - break; + case splashPipeResultColorAlphaNoBlendMono: + if (alphaI == 0) { + cResult0 = 0; + } else { + cResult0 = state->grayTransfer[((alphaI - aSrc) * cDest[0] + + aSrc * cSrc[0]) / alphaI]; + } + break; + case splashPipeResultColorAlphaNoBlendRGB: + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + } else { + cResult0 = state->rgbTransferR[((alphaI - aSrc) * cDest[0] + + aSrc * cSrc[0]) / alphaI]; + cResult1 = state->rgbTransferG[((alphaI - aSrc) * cDest[1] + + aSrc * cSrc[1]) / alphaI]; + cResult2 = state->rgbTransferB[((alphaI - aSrc) * cDest[2] + + aSrc * cSrc[2]) / alphaI]; + } + break; #if SPLASH_CMYK - case splashPipeResultColorAlphaNoBlendCMYK: - if (alphaI == 0) { - cResult0 = 0; - cResult1 = 0; - cResult2 = 0; - cResult3 = 0; - } else { - cResult0 = state->cmykTransferC[((alphaI - aSrc) * cDest[0] + - aSrc * cSrc[0]) / alphaI]; - cResult1 = state->cmykTransferM[((alphaI - aSrc) * cDest[1] + - aSrc * cSrc[1]) / alphaI]; - cResult2 = state->cmykTransferY[((alphaI - aSrc) * cDest[2] + - aSrc * cSrc[2]) / alphaI]; - cResult3 = state->cmykTransferK[((alphaI - aSrc) * cDest[3] + - aSrc * cSrc[3]) / alphaI]; - } - break; + case splashPipeResultColorAlphaNoBlendCMYK: + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + cResult3 = 0; + } else { + cResult0 = state->cmykTransferC[((alphaI - aSrc) * cDest[0] + + aSrc * cSrc[0]) / alphaI]; + cResult1 = state->cmykTransferM[((alphaI - aSrc) * cDest[1] + + aSrc * cSrc[1]) / alphaI]; + cResult2 = state->cmykTransferY[((alphaI - aSrc) * cDest[2] + + aSrc * cSrc[2]) / alphaI]; + cResult3 = state->cmykTransferK[((alphaI - aSrc) * cDest[3] + + aSrc * cSrc[3]) / alphaI]; + } + break; #endif - case splashPipeResultColorAlphaBlendMono: - if (alphaI == 0) { - cResult0 = 0; - } else { - cResult0 = state->grayTransfer[((alphaI - aSrc) * cDest[0] + - aSrc * ((255 - alphaIm1) * cSrc[0] + - alphaIm1 * cBlend[0]) / 255) / - alphaI]; - } - break; - case splashPipeResultColorAlphaBlendRGB: - if (alphaI == 0) { - cResult0 = 0; - cResult1 = 0; - cResult2 = 0; - } else { - cResult0 = state->rgbTransferR[((alphaI - aSrc) * cDest[0] + - aSrc * ((255 - alphaIm1) * cSrc[0] + - alphaIm1 * cBlend[0]) / 255) / - alphaI]; - cResult1 = state->rgbTransferG[((alphaI - aSrc) * cDest[1] + - aSrc * ((255 - alphaIm1) * cSrc[1] + - alphaIm1 * cBlend[1]) / 255) / - alphaI]; - cResult2 = state->rgbTransferB[((alphaI - aSrc) * cDest[2] + - aSrc * ((255 - alphaIm1) * cSrc[2] + - alphaIm1 * cBlend[2]) / 255) / - alphaI]; - } - break; + case splashPipeResultColorAlphaBlendMono: + if (alphaI == 0) { + cResult0 = 0; + } else { + cResult0 = state->grayTransfer[((alphaI - aSrc) * cDest[0] + + aSrc * ((255 - alphaIm1) * cSrc[0] + + alphaIm1 * cBlend[0]) / 255) / + alphaI]; + } + break; + case splashPipeResultColorAlphaBlendRGB: + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + } else { + cResult0 = state->rgbTransferR[((alphaI - aSrc) * cDest[0] + + aSrc * ((255 - alphaIm1) * cSrc[0] + + alphaIm1 * cBlend[0]) / 255) / + alphaI]; + cResult1 = state->rgbTransferG[((alphaI - aSrc) * cDest[1] + + aSrc * ((255 - alphaIm1) * cSrc[1] + + alphaIm1 * cBlend[1]) / 255) / + alphaI]; + cResult2 = state->rgbTransferB[((alphaI - aSrc) * cDest[2] + + aSrc * ((255 - alphaIm1) * cSrc[2] + + alphaIm1 * cBlend[2]) / 255) / + alphaI]; + } + break; #if SPLASH_CMYK - case splashPipeResultColorAlphaBlendCMYK: - if (alphaI == 0) { - cResult0 = 0; - cResult1 = 0; - cResult2 = 0; - cResult3 = 0; - } else { - cResult0 = state->cmykTransferC[((alphaI - aSrc) * cDest[0] + - aSrc * ((255 - alphaIm1) * cSrc[0] + - alphaIm1 * cBlend[0]) / 255) / - alphaI]; - cResult1 = state->cmykTransferM[((alphaI - aSrc) * cDest[1] + - aSrc * ((255 - alphaIm1) * cSrc[1] + - alphaIm1 * cBlend[1]) / 255) / - alphaI]; - cResult2 = state->cmykTransferY[((alphaI - aSrc) * cDest[2] + - aSrc * ((255 - alphaIm1) * cSrc[2] + - alphaIm1 * cBlend[2]) / 255) / - alphaI]; - cResult3 = state->cmykTransferK[((alphaI - aSrc) * cDest[3] + - aSrc * ((255 - alphaIm1) * cSrc[3] + - alphaIm1 * cBlend[3]) / 255) / - alphaI]; - } - break; + case splashPipeResultColorAlphaBlendCMYK: + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + cResult3 = 0; + } else { + cResult0 = + state->cmykTransferC[((alphaI - aSrc) * cDest[0] + + aSrc * ((255 - alphaIm1) * cSrc[0] + + alphaIm1 * cBlend[0]) / 255) / + alphaI]; + cResult1 = + state->cmykTransferM[((alphaI - aSrc) * cDest[1] + + aSrc * ((255 - alphaIm1) * cSrc[1] + + alphaIm1 * cBlend[1]) / 255) / + alphaI]; + cResult2 = + state->cmykTransferY[((alphaI - aSrc) * cDest[2] + + aSrc * ((255 - alphaIm1) * cSrc[2] + + alphaIm1 * cBlend[2]) / 255) / + alphaI]; + cResult3 = + state->cmykTransferK[((alphaI - aSrc) * cDest[3] + + aSrc * ((255 - alphaIm1) * cSrc[3] + + alphaIm1 * cBlend[3]) / 255) / + alphaI]; + } + break; #endif - } + } - //----- write destination pixel + //----- write destination pixel - switch (bitmap->mode) { - case splashModeMono1: - if (state->screen->test(pipe->x, pipe->y, cResult0)) { - *pipe->destColorPtr |= pipe->destColorMask; - } else { - *pipe->destColorPtr &= ~pipe->destColorMask; - } - if (!(pipe->destColorMask >>= 1)) { - pipe->destColorMask = 0x80; - ++pipe->destColorPtr; - } - break; - case splashModeMono8: - *pipe->destColorPtr++ = cResult0; - break; - case splashModeRGB8: - *pipe->destColorPtr++ = cResult0; - *pipe->destColorPtr++ = cResult1; - *pipe->destColorPtr++ = cResult2; - break; - case splashModeBGR8: - *pipe->destColorPtr++ = cResult2; - *pipe->destColorPtr++ = cResult1; - *pipe->destColorPtr++ = cResult0; - break; + switch (bitmap->mode) { + case splashModeMono1: + if (state->screen->test(x, y, cResult0)) { + *destColorPtr |= destColorMask; + } else { + *destColorPtr &= ~destColorMask; + } + destColorPtr += destColorMask & 1; + destColorMask = (destColorMask << 7) | (destColorMask >> 1); + break; + case splashModeMono8: + *destColorPtr++ = cResult0; + break; + case splashModeRGB8: + destColorPtr[0] = cResult0; + destColorPtr[1] = cResult1; + destColorPtr[2] = cResult2; + destColorPtr += 3; + break; + case splashModeBGR8: + destColorPtr[0] = cResult2; + destColorPtr[1] = cResult1; + destColorPtr[2] = cResult0; + destColorPtr += 3; + break; #if SPLASH_CMYK - case splashModeCMYK8: - if (state->overprintMask & 1) { - pipe->destColorPtr[0] = cResult0; - } - if (state->overprintMask & 2) { - pipe->destColorPtr[1] = cResult1; - } - if (state->overprintMask & 4) { - pipe->destColorPtr[2] = cResult2; + case splashModeCMYK8: + destColorPtr[0] = cResult0; + destColorPtr[1] = cResult1; + destColorPtr[2] = cResult2; + destColorPtr[3] = cResult3; + destColorPtr += 4; + break; +#endif } - if (state->overprintMask & 8) { - pipe->destColorPtr[3] = cResult3; + if (destAlphaPtr) { + *destAlphaPtr++ = aResult; } - pipe->destColorPtr += 4; - break; -#endif - } - if (pipe->destAlphaPtr) { - *pipe->destAlphaPtr++ = aResult; - } - } + } // if (noTransparency && !blendFunc) + + cSrcPtr += cSrcStride; + shapePtr2 += shapeStride; + } // for (x ...) - ++pipe->x; + updateModX(lastX); } // special case: // !pipe->pattern && pipe->noTransparency && !state->blendFunc && -// bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr) { -void Splash::pipeRunSimpleMono1(SplashPipe *pipe) { +// bitmap->mode == splashModeMono1 && !bitmap->alpha) { +void Splash::pipeRunSimpleMono1(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { Guchar cResult0; + SplashColorPtr destColorPtr; + Guchar destColorMask; + int cSrcStride, x; - //----- write destination pixel - cResult0 = state->grayTransfer[pipe->cSrc[0]]; - if (state->screen->test(pipe->x, pipe->y, cResult0)) { - *pipe->destColorPtr |= pipe->destColorMask; + if (cSrcPtr) { + cSrcStride = 1; } else { - *pipe->destColorPtr &= ~pipe->destColorMask; + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; } - if (!(pipe->destColorMask >>= 1)) { - pipe->destColorMask = 0x80; - ++pipe->destColorPtr; + if (x0 > x1) { + return; } + updateModX(x0); + updateModX(x1); + updateModY(y); + + destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)]; + destColorMask = 0x80 >> (x0 & 7); + + for (x = x0; x <= x1; ++x) { + + //----- write destination pixel + cResult0 = state->grayTransfer[cSrcPtr[0]]; + if (state->screen->test(x, y, cResult0)) { + *destColorPtr |= destColorMask; + } else { + *destColorPtr &= ~destColorMask; + } + destColorPtr += destColorMask & 1; + destColorMask = (destColorMask << 7) | (destColorMask >> 1); - ++pipe->x; + cSrcPtr += cSrcStride; + } } // special case: // !pipe->pattern && pipe->noTransparency && !state->blendFunc && -// bitmap->mode == splashModeMono8 && pipe->destAlphaPtr) { -void Splash::pipeRunSimpleMono8(SplashPipe *pipe) { - //----- write destination pixel - *pipe->destColorPtr++ = state->grayTransfer[pipe->cSrc[0]]; - *pipe->destAlphaPtr++ = 255; +// bitmap->mode == splashModeMono8 && bitmap->alpha) { +void Splash::pipeRunSimpleMono8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + int cSrcStride, x; + + if (cSrcPtr) { + cSrcStride = 1; + } else { + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; + } + if (x0 > x1) { + return; + } + updateModX(x0); + updateModX(x1); + updateModY(y); + + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; + + for (x = x0; x <= x1; ++x) { - ++pipe->x; + //----- write destination pixel + *destColorPtr++ = state->grayTransfer[cSrcPtr[0]]; + *destAlphaPtr++ = 255; + + cSrcPtr += cSrcStride; + } } // special case: // !pipe->pattern && pipe->noTransparency && !state->blendFunc && -// bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr) { -void Splash::pipeRunSimpleRGB8(SplashPipe *pipe) { - //----- write destination pixel - *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]]; - *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]]; - *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]]; - *pipe->destAlphaPtr++ = 255; - - ++pipe->x; +// bitmap->mode == splashModeRGB8 && bitmap->alpha) { +void Splash::pipeRunSimpleRGB8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + int cSrcStride, x; + + if (cSrcPtr) { + cSrcStride = 3; + } else { + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; + } + if (x0 > x1) { + return; + } + updateModX(x0); + updateModX(x1); + updateModY(y); + + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; + + for (x = x0; x <= x1; ++x) { + + //----- write destination pixel + destColorPtr[0] = state->rgbTransferR[cSrcPtr[0]]; + destColorPtr[1] = state->rgbTransferG[cSrcPtr[1]]; + destColorPtr[2] = state->rgbTransferB[cSrcPtr[2]]; + destColorPtr += 3; + *destAlphaPtr++ = 255; + + cSrcPtr += cSrcStride; + } +} + +// special case: +// !pipe->pattern && pipe->noTransparency && !state->blendFunc && +// bitmap->mode == splashModeBGR8 && bitmap->alpha) { +void Splash::pipeRunSimpleBGR8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + int cSrcStride, x; + + if (cSrcPtr) { + cSrcStride = 3; + } else { + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; + } + if (x0 > x1) { + return; + } + updateModX(x0); + updateModX(x1); + updateModY(y); + + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; + + for (x = x0; x <= x1; ++x) { + + //----- write destination pixel + destColorPtr[0] = state->rgbTransferB[cSrcPtr[2]]; + destColorPtr[1] = state->rgbTransferG[cSrcPtr[1]]; + destColorPtr[2] = state->rgbTransferR[cSrcPtr[0]]; + destColorPtr += 3; + *destAlphaPtr++ = 255; + + cSrcPtr += cSrcStride; + } +} + +#if SPLASH_CMYK +// special case: +// !pipe->pattern && pipe->noTransparency && !state->blendFunc && +// bitmap->mode == splashModeCMYK8 && bitmap->alpha) { +void Splash::pipeRunSimpleCMYK8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + int cSrcStride, x; + + if (cSrcPtr) { + cSrcStride = 4; + } else { + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; + } + if (x0 > x1) { + return; + } + updateModX(x0); + updateModX(x1); + updateModY(y); + + destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; + + for (x = x0; x <= x1; ++x) { + + //----- write destination pixel + destColorPtr[0] = state->cmykTransferC[cSrcPtr[0]]; + destColorPtr[1] = state->cmykTransferM[cSrcPtr[1]]; + destColorPtr[2] = state->cmykTransferY[cSrcPtr[2]]; + destColorPtr[3] = state->cmykTransferK[cSrcPtr[3]]; + destColorPtr += 4; + *destAlphaPtr++ = 255; + + cSrcPtr += cSrcStride; + } +} +#endif + + +// special case: +// !pipe->pattern && pipe->shapeOnly && !state->blendFunc && +// bitmap->mode == splashModeMono1 && !bitmap->alpha +void Splash::pipeRunShapeMono1(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, cDest0, cResult0; + SplashColorPtr destColorPtr; + Guchar destColorMask; + 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; + + destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)]; + destColorMask = 0x80 >> (x0 & 7); + + for (x = x0; x <= x1; ++x) { + + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += destColorMask & 1; + destColorMask = (destColorMask << 7) | (destColorMask >> 1); + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; + + //----- read destination pixel + cDest0 = (*destColorPtr & destColorMask) ? 0xff : 0x00; + + //----- source alpha + aSrc = shape; + + //----- result color + // note: aDest = alphaI = aResult = 0xff + cResult0 = state->grayTransfer[(Guchar)div255((0xff - aSrc) * cDest0 + + aSrc * cSrcPtr[0])]; + + //----- write destination pixel + if (state->screen->test(x, y, cResult0)) { + *destColorPtr |= destColorMask; + } else { + *destColorPtr &= ~destColorMask; + } + destColorPtr += destColorMask & 1; + destColorMask = (destColorMask << 7) | (destColorMask >> 1); + + cSrcPtr += cSrcStride; + ++shapePtr; + } + + updateModX(lastX); +} + +// special case: +// !pipe->pattern && pipe->shapeOnly && !state->blendFunc && +// bitmap->mode == splashModeMono8 && bitmap->alpha +void Splash::pipeRunShapeMono8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, aResult, cDest0, cResult0; + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + 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; + + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; + + for (x = x0; x <= x1; ++x) { + + //----- shape + shape = *shapePtr; + if (!shape) { + ++destColorPtr; + ++destAlphaPtr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; + + //----- read destination pixel + cDest0 = *destColorPtr; + aDest = *destAlphaPtr; + + //----- source alpha + aSrc = shape; + + //----- result alpha and non-isolated group element correction + aResult = aSrc + aDest - div255(aSrc * aDest); + alphaI = aResult; + + //----- result color + if (alphaI == 0) { + cResult0 = 0; + } else { + cResult0 = state->grayTransfer[(Guchar)(((alphaI - aSrc) * cDest0 + + aSrc * cSrcPtr[0]) / alphaI)]; + } + + //----- write destination pixel + *destColorPtr++ = cResult0; + *destAlphaPtr++ = aResult; + + cSrcPtr += cSrcStride; + ++shapePtr; + } + + updateModX(lastX); +} + +// special case: +// !pipe->pattern && pipe->shapeOnly && !state->blendFunc && +// bitmap->mode == splashModeRGB8 && bitmap->alpha +void Splash::pipeRunShapeRGB8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, aResult; + Guchar cDest0, cDest1, cDest2; + Guchar cResult0, cResult1, cResult2; + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + 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; + + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; + + for (x = x0; x <= x1; ++x) { + + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += 3; + ++destAlphaPtr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; + + //----- read destination pixel + cDest0 = destColorPtr[0]; + cDest1 = destColorPtr[1]; + cDest2 = destColorPtr[2]; + aDest = *destAlphaPtr; + + //----- source alpha + aSrc = shape; + + //----- result alpha and non-isolated group element correction + aResult = aSrc + aDest - div255(aSrc * aDest); + alphaI = aResult; + + //----- result color + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + } else { + cResult0 = state->rgbTransferR[(Guchar)(((alphaI - aSrc) * cDest0 + + aSrc * cSrcPtr[0]) / alphaI)]; + cResult1 = state->rgbTransferG[(Guchar)(((alphaI - aSrc) * cDest1 + + aSrc * cSrcPtr[1]) / alphaI)]; + cResult2 = state->rgbTransferB[(Guchar)(((alphaI - aSrc) * cDest2 + + aSrc * cSrcPtr[2]) / 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 && pipe->shapeOnly && !state->blendFunc && +// bitmap->mode == splashModeBGR8 && bitmap->alpha +void Splash::pipeRunShapeBGR8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, aResult; + Guchar cDest0, cDest1, cDest2; + Guchar cResult0, cResult1, cResult2; + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + 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; + + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; + + for (x = x0; x <= x1; ++x) { + + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += 3; + ++destAlphaPtr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; + + //----- read destination pixel + cDest0 = destColorPtr[2]; + cDest1 = destColorPtr[1]; + cDest2 = destColorPtr[0]; + aDest = *destAlphaPtr; + + //----- source alpha + aSrc = shape; + + //----- result alpha and non-isolated group element correction + aResult = aSrc + aDest - div255(aSrc * aDest); + alphaI = aResult; + + //----- result color + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + } else { + cResult0 = state->rgbTransferR[(Guchar)(((alphaI - aSrc) * cDest0 + + aSrc * cSrcPtr[0]) / alphaI)]; + cResult1 = state->rgbTransferG[(Guchar)(((alphaI - aSrc) * cDest1 + + aSrc * cSrcPtr[1]) / alphaI)]; + cResult2 = state->rgbTransferB[(Guchar)(((alphaI - aSrc) * cDest2 + + aSrc * cSrcPtr[2]) / alphaI)]; + } + + //----- write destination pixel + destColorPtr[0] = cResult2; + destColorPtr[1] = cResult1; + destColorPtr[2] = cResult0; + destColorPtr += 3; + *destAlphaPtr++ = aResult; + + cSrcPtr += cSrcStride; + ++shapePtr; + } + + updateModX(lastX); } -// special case: -// !pipe->pattern && pipe->noTransparency && !state->blendFunc && -// bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr) { -void Splash::pipeRunSimpleBGR8(SplashPipe *pipe) { - //----- write destination pixel - *pipe->destColorPtr++ = state->rgbTransferB[pipe->cSrc[2]]; - *pipe->destColorPtr++ = state->rgbTransferG[pipe->cSrc[1]]; - *pipe->destColorPtr++ = state->rgbTransferR[pipe->cSrc[0]]; - *pipe->destAlphaPtr++ = 255; +#if SPLASH_CMYK +// special case: +// !pipe->pattern && pipe->shapeOnly && !state->blendFunc && +// bitmap->mode == splashModeCMYK8 && bitmap->alpha +void Splash::pipeRunShapeCMYK8(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; + 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; + + destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; + + for (x = x0; x <= x1; ++x) { + + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += 4; + ++destAlphaPtr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; + + //----- read destination pixel + cDest0 = destColorPtr[0]; + cDest1 = destColorPtr[1]; + cDest2 = destColorPtr[2]; + cDest3 = destColorPtr[3]; + aDest = *destAlphaPtr; + + //----- overprint + if (state->overprintMask & 1) { + cSrc0 = cSrcPtr[0]; + } else { + cSrc0 = div255(aDest * cDest0); + } + if (state->overprintMask & 2) { + cSrc1 = cSrcPtr[1]; + } else { + cSrc1 = div255(aDest * cDest1); + } + if (state->overprintMask & 4) { + cSrc2 = cSrcPtr[2]; + } else { + cSrc2 = div255(aDest * cDest2); + } + if (state->overprintMask & 8) { + cSrc3 = cSrcPtr[3]; + } else { + cSrc3 = div255(aDest * cDest3); + } + + //----- source alpha + aSrc = shape; + + //----- result alpha and non-isolated group element correction + aResult = aSrc + aDest - div255(aSrc * aDest); + alphaI = aResult; + + //----- result color + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + cResult3 = 0; + } else { + cResult0 = state->cmykTransferC[(Guchar)(((alphaI - aSrc) * cDest0 + + aSrc * cSrc0) / alphaI)]; + cResult1 = state->cmykTransferM[(Guchar)(((alphaI - aSrc) * cDest1 + + aSrc * cSrc1) / alphaI)]; + cResult2 = state->cmykTransferY[(Guchar)(((alphaI - aSrc) * cDest2 + + aSrc * cSrc2) / alphaI)]; + cResult3 = state->cmykTransferK[(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 + + +// special case: +// !pipe->pattern && !pipe->noTransparency && !state->softMask && +// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && +// !pipe->nonIsolatedGroup && +// bitmap->mode == splashModeMono1 && !bitmap->alpha +void Splash::pipeRunAAMono1(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, cDest0, cResult0; + SplashColorPtr destColorPtr; + Guchar destColorMask; + 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; + + destColorPtr = &bitmap->data[y * bitmap->rowSize + (x0 >> 3)]; + destColorMask = 0x80 >> (x0 & 7); + + for (x = x0; x <= x1; ++x) { + + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += destColorMask & 1; + destColorMask = (destColorMask << 7) | (destColorMask >> 1); + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; + + //----- read destination pixel + cDest0 = (*destColorPtr & destColorMask) ? 0xff : 0x00; + + //----- source alpha + aSrc = div255(pipe->aInput * shape); + + //----- result color + // note: aDest = alphaI = aResult = 0xff + cResult0 = state->grayTransfer[(Guchar)div255((0xff - aSrc) * cDest0 + + aSrc * cSrcPtr[0])]; + + //----- write destination pixel + if (state->screen->test(x, y, cResult0)) { + *destColorPtr |= destColorMask; + } else { + *destColorPtr &= ~destColorMask; + } + destColorPtr += destColorMask & 1; + destColorMask = (destColorMask << 7) | (destColorMask >> 1); + + cSrcPtr += cSrcStride; + ++shapePtr; + } - ++pipe->x; + updateModX(lastX); } -#if SPLASH_CMYK // special case: -// !pipe->pattern && pipe->noTransparency && !state->blendFunc && -// bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr) { -void Splash::pipeRunSimpleCMYK8(SplashPipe *pipe) { - //----- write destination pixel - if (state->overprintMask & 1) { - pipe->destColorPtr[0] = state->cmykTransferC[pipe->cSrc[0]]; - } - if (state->overprintMask & 2) { - pipe->destColorPtr[1] = state->cmykTransferM[pipe->cSrc[1]]; +// !pipe->pattern && !pipe->noTransparency && !state->softMask && +// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && +// !pipe->nonIsolatedGroup && +// bitmap->mode == splashModeMono8 && bitmap->alpha +void Splash::pipeRunAAMono8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, aResult, cDest0, cResult0; + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + int cSrcStride, x, lastX; + + if (cSrcPtr) { + cSrcStride = 1; + } else { + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; } - if (state->overprintMask & 4) { - pipe->destColorPtr[2] = state->cmykTransferY[pipe->cSrc[2]]; + for (; x0 <= x1; ++x0) { + if (*shapePtr) { + break; + } + cSrcPtr += cSrcStride; + ++shapePtr; } - if (state->overprintMask & 8) { - pipe->destColorPtr[3] = state->cmykTransferK[pipe->cSrc[3]]; + if (x0 > x1) { + return; } - pipe->destColorPtr += 4; - *pipe->destAlphaPtr++ = 255; + updateModX(x0); + updateModY(y); + lastX = x0; - ++pipe->x; -} -#endif + destColorPtr = &bitmap->data[y * bitmap->rowSize + x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; + for (x = x0; x <= x1; ++x) { -// special case: -// !pipe->pattern && !pipe->noTransparency && !state->softMask && -// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && -// !pipe->nonIsolatedGroup && -// bitmap->mode == splashModeMono1 && !pipe->destAlphaPtr -void Splash::pipeRunAAMono1(SplashPipe *pipe) { - Guchar aSrc; - SplashColor cDest; - Guchar cResult0; + //----- shape + shape = *shapePtr; + if (!shape) { + ++destColorPtr; + ++destAlphaPtr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; - //----- read destination pixel - cDest[0] = (*pipe->destColorPtr & pipe->destColorMask) ? 0xff : 0x00; + //----- read destination pixel + cDest0 = *destColorPtr; + aDest = *destAlphaPtr; - //----- source alpha - aSrc = div255(pipe->aInput * pipe->shape); + //----- source alpha + aSrc = div255(pipe->aInput * shape); - //----- result color - // note: aDest = alpha2 = aResult = 0xff - cResult0 = state->grayTransfer[(Guchar)div255((0xff - aSrc) * cDest[0] + - aSrc * pipe->cSrc[0])]; + //----- result alpha and non-isolated group element correction + aResult = aSrc + aDest - div255(aSrc * aDest); + alphaI = aResult; - //----- write destination pixel - if (state->screen->test(pipe->x, pipe->y, cResult0)) { - *pipe->destColorPtr |= pipe->destColorMask; - } else { - *pipe->destColorPtr &= ~pipe->destColorMask; - } - if (!(pipe->destColorMask >>= 1)) { - pipe->destColorMask = 0x80; - ++pipe->destColorPtr; + //----- result color + if (alphaI == 0) { + cResult0 = 0; + } else { + cResult0 = state->grayTransfer[(Guchar)(((alphaI - aSrc) * cDest0 + + aSrc * cSrcPtr[0]) / alphaI)]; + } + + //----- write destination pixel + *destColorPtr++ = cResult0; + *destAlphaPtr++ = aResult; + + cSrcPtr += cSrcStride; + ++shapePtr; } - ++pipe->x; + updateModX(lastX); } // special case: // !pipe->pattern && !pipe->noTransparency && !state->softMask && // pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && // !pipe->nonIsolatedGroup && -// bitmap->mode == splashModeMono8 && pipe->destAlphaPtr -void Splash::pipeRunAAMono8(SplashPipe *pipe) { - Guchar aSrc, aDest, alpha2, aResult; - SplashColor cDest; - Guchar cResult0; - - //----- read destination pixel - cDest[0] = *pipe->destColorPtr; - aDest = *pipe->destAlphaPtr; - - //----- source alpha - aSrc = div255(pipe->aInput * pipe->shape); - - //----- result alpha and non-isolated group element correction - aResult = aSrc + aDest - div255(aSrc * aDest); - alpha2 = aResult; +// bitmap->mode == splashModeRGB8 && bitmap->alpha +void Splash::pipeRunAARGB8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, aResult; + Guchar cDest0, cDest1, cDest2; + Guchar cResult0, cResult1, cResult2; + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + int cSrcStride, x, lastX; - //----- result color - if (alpha2 == 0) { - cResult0 = 0; + if (cSrcPtr) { + cSrcStride = 3; } else { - cResult0 = state->grayTransfer[(Guchar)(((alpha2 - aSrc) * cDest[0] + - aSrc * pipe->cSrc[0]) / alpha2)]; + 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; - //----- write destination pixel - *pipe->destColorPtr++ = cResult0; - *pipe->destAlphaPtr++ = aResult; + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; - ++pipe->x; -} + for (x = x0; x <= x1; ++x) { -// special case: -// !pipe->pattern && !pipe->noTransparency && !state->softMask && -// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && -// !pipe->nonIsolatedGroup && -// bitmap->mode == splashModeRGB8 && pipe->destAlphaPtr -void Splash::pipeRunAARGB8(SplashPipe *pipe) { - Guchar aSrc, aDest, alpha2, aResult; - SplashColor cDest; - Guchar cResult0, cResult1, cResult2; + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += 3; + ++destAlphaPtr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; - //----- read destination pixel - cDest[0] = pipe->destColorPtr[0]; - cDest[1] = pipe->destColorPtr[1]; - cDest[2] = pipe->destColorPtr[2]; - aDest = *pipe->destAlphaPtr; - - //----- source alpha - aSrc = div255(pipe->aInput * pipe->shape); - - //----- result alpha and non-isolated group element correction - aResult = aSrc + aDest - div255(aSrc * aDest); - alpha2 = aResult; - - //----- result color - if (alpha2 == 0) { - cResult0 = 0; - cResult1 = 0; - cResult2 = 0; - } else { - cResult0 = state->rgbTransferR[(Guchar)(((alpha2 - aSrc) * cDest[0] + - aSrc * pipe->cSrc[0]) / alpha2)]; - cResult1 = state->rgbTransferG[(Guchar)(((alpha2 - aSrc) * cDest[1] + - aSrc * pipe->cSrc[1]) / alpha2)]; - cResult2 = state->rgbTransferB[(Guchar)(((alpha2 - aSrc) * cDest[2] + - aSrc * pipe->cSrc[2]) / alpha2)]; - } - - //----- write destination pixel - *pipe->destColorPtr++ = cResult0; - *pipe->destColorPtr++ = cResult1; - *pipe->destColorPtr++ = cResult2; - *pipe->destAlphaPtr++ = aResult; + //----- read destination pixel + cDest0 = destColorPtr[0]; + cDest1 = destColorPtr[1]; + cDest2 = destColorPtr[2]; + aDest = *destAlphaPtr; - ++pipe->x; -} + //----- source alpha + aSrc = div255(pipe->aInput * shape); -// special case: -// !pipe->pattern && !pipe->noTransparency && !state->softMask && -// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && -// !pipe->nonIsolatedGroup && -// bitmap->mode == splashModeBGR8 && pipe->destAlphaPtr -void Splash::pipeRunAABGR8(SplashPipe *pipe) { - Guchar aSrc, aDest, alpha2, aResult; - SplashColor cDest; - Guchar cResult0, cResult1, cResult2; + //----- result alpha and non-isolated group element correction + aResult = aSrc + aDest - div255(aSrc * aDest); + alphaI = aResult; + + //----- result color + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + } else { + cResult0 = state->rgbTransferR[(Guchar)(((alphaI - aSrc) * cDest0 + + aSrc * cSrcPtr[0]) / alphaI)]; + cResult1 = state->rgbTransferG[(Guchar)(((alphaI - aSrc) * cDest1 + + aSrc * cSrcPtr[1]) / alphaI)]; + cResult2 = state->rgbTransferB[(Guchar)(((alphaI - aSrc) * cDest2 + + aSrc * cSrcPtr[2]) / alphaI)]; + } + + //----- write destination pixel + destColorPtr[0] = cResult0; + destColorPtr[1] = cResult1; + destColorPtr[2] = cResult2; + destColorPtr += 3; + *destAlphaPtr++ = aResult; - //----- read destination pixel - cDest[0] = pipe->destColorPtr[2]; - cDest[1] = pipe->destColorPtr[1]; - cDest[2] = pipe->destColorPtr[0]; - aDest = *pipe->destAlphaPtr; - - //----- source alpha - aSrc = div255(pipe->aInput * pipe->shape); - - //----- result alpha and non-isolated group element correction - aResult = aSrc + aDest - div255(aSrc * aDest); - alpha2 = aResult; - - //----- result color - if (alpha2 == 0) { - cResult0 = 0; - cResult1 = 0; - cResult2 = 0; - } else { - cResult0 = state->rgbTransferR[(Guchar)(((alpha2 - aSrc) * cDest[0] + - aSrc * pipe->cSrc[0]) / alpha2)]; - cResult1 = state->rgbTransferG[(Guchar)(((alpha2 - aSrc) * cDest[1] + - aSrc * pipe->cSrc[1]) / alpha2)]; - cResult2 = state->rgbTransferB[(Guchar)(((alpha2 - aSrc) * cDest[2] + - aSrc * pipe->cSrc[2]) / alpha2)]; - } - - //----- write destination pixel - *pipe->destColorPtr++ = cResult2; - *pipe->destColorPtr++ = cResult1; - *pipe->destColorPtr++ = cResult0; - *pipe->destAlphaPtr++ = aResult; + cSrcPtr += cSrcStride; + ++shapePtr; + } - ++pipe->x; + updateModX(lastX); } -#if SPLASH_CMYK // special case: // !pipe->pattern && !pipe->noTransparency && !state->softMask && // pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && // !pipe->nonIsolatedGroup && -// bitmap->mode == splashModeCMYK8 && pipe->destAlphaPtr -void Splash::pipeRunAACMYK8(SplashPipe *pipe) { - Guchar aSrc, aDest, alpha2, aResult; - SplashColor cDest; - Guchar cResult0, cResult1, cResult2, cResult3; - - //----- read destination pixel - cDest[0] = pipe->destColorPtr[0]; - cDest[1] = pipe->destColorPtr[1]; - cDest[2] = pipe->destColorPtr[2]; - cDest[3] = pipe->destColorPtr[3]; - aDest = *pipe->destAlphaPtr; - - //----- source alpha - aSrc = div255(pipe->aInput * pipe->shape); - - //----- result alpha and non-isolated group element correction - aResult = aSrc + aDest - div255(aSrc * aDest); - alpha2 = aResult; - - //----- result color - if (alpha2 == 0) { - cResult0 = 0; - cResult1 = 0; - cResult2 = 0; - cResult3 = 0; - } else { - cResult0 = state->cmykTransferC[(Guchar)(((alpha2 - aSrc) * cDest[0] + - aSrc * pipe->cSrc[0]) / alpha2)]; - cResult1 = state->cmykTransferM[(Guchar)(((alpha2 - aSrc) * cDest[1] + - aSrc * pipe->cSrc[1]) / alpha2)]; - cResult2 = state->cmykTransferY[(Guchar)(((alpha2 - aSrc) * cDest[2] + - aSrc * pipe->cSrc[2]) / alpha2)]; - cResult3 = state->cmykTransferK[(Guchar)(((alpha2 - aSrc) * cDest[3] + - aSrc * pipe->cSrc[3]) / alpha2)]; - } - - //----- write destination pixel - if (state->overprintMask & 1) { - pipe->destColorPtr[0] = cResult0; - } - if (state->overprintMask & 2) { - pipe->destColorPtr[1] = cResult1; - } - if (state->overprintMask & 4) { - pipe->destColorPtr[2] = cResult2; - } - if (state->overprintMask & 8) { - pipe->destColorPtr[3] = cResult3; - } - pipe->destColorPtr += 4; - *pipe->destAlphaPtr++ = aResult; - - ++pipe->x; -} -#endif - +// bitmap->mode == splashModeBGR8 && bitmap->alpha +void Splash::pipeRunAABGR8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr) { + Guchar shape, aSrc, aDest, alphaI, aResult; + Guchar cDest0, cDest1, cDest2; + Guchar cResult0, cResult1, cResult2; + SplashColorPtr destColorPtr; + Guchar *destAlphaPtr; + int cSrcStride, x, lastX; -inline void Splash::pipeSetXY(SplashPipe *pipe, int x, int y) { - pipe->x = x; - pipe->y = y; - if (state->softMask) { - pipe->softMaskPtr = - &state->softMask->data[y * state->softMask->rowSize + x]; - } - switch (bitmap->mode) { - case splashModeMono1: - pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + (x >> 3)]; - pipe->destColorMask = 0x80 >> (x & 7); - break; - case splashModeMono8: - pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + x]; - break; - case splashModeRGB8: - case splashModeBGR8: - pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x]; - break; -#if SPLASH_CMYK - case splashModeCMYK8: - pipe->destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x]; - break; -#endif - } - if (bitmap->alpha) { - pipe->destAlphaPtr = &bitmap->alpha[y * bitmap->width + x]; - } else { - pipe->destAlphaPtr = NULL; - } - if (state->inNonIsolatedGroup && alpha0Bitmap->alpha) { - pipe->alpha0Ptr = - &alpha0Bitmap->alpha[(alpha0Y + y) * alpha0Bitmap->width + - (alpha0X + x)]; + if (cSrcPtr) { + cSrcStride = 3; } else { - pipe->alpha0Ptr = NULL; - } -} - -inline void Splash::pipeIncX(SplashPipe *pipe) { - ++pipe->x; - if (state->softMask) { - ++pipe->softMaskPtr; + cSrcPtr = pipe->cSrcVal; + cSrcStride = 0; } - switch (bitmap->mode) { - case splashModeMono1: - if (!(pipe->destColorMask >>= 1)) { - pipe->destColorMask = 0x80; - ++pipe->destColorPtr; + for (; x0 <= x1; ++x0) { + if (*shapePtr) { + break; } - break; - case splashModeMono8: - ++pipe->destColorPtr; - break; - case splashModeRGB8: - case splashModeBGR8: - pipe->destColorPtr += 3; - break; -#if SPLASH_CMYK - case splashModeCMYK8: - pipe->destColorPtr += 4; - break; -#endif + cSrcPtr += cSrcStride; + ++shapePtr; } - if (pipe->destAlphaPtr) { - ++pipe->destAlphaPtr; - } - if (pipe->alpha0Ptr) { - ++pipe->alpha0Ptr; + if (x0 > x1) { + return; } -} + updateModX(x0); + updateModY(y); + lastX = x0; -inline void Splash::drawPixel(SplashPipe *pipe, int x, int y, GBool noClip) { - if (noClip || state->clip->test(x, y)) { - pipeSetXY(pipe, x, y); - (this->*pipe->run)(pipe); - updateModX(x); - updateModY(y); - } -} + destColorPtr = &bitmap->data[y * bitmap->rowSize + 3 * x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; -inline void Splash::drawAAPixelInit() { - aaBufY = -1; -} + for (x = x0; x <= x1; ++x) { -inline void Splash::drawAAPixel(SplashPipe *pipe, int x, int y) { -#if splashAASize == 4 - static int bitCount4[16] = { 0, 1, 1, 2, 1, 2, 2, 3, - 1, 2, 2, 3, 2, 3, 3, 4 }; - int w; -#else - int xx, yy; -#endif - SplashColorPtr p; - int x0, x1, t; + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += 3; + ++destAlphaPtr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; - if (x < 0 || x >= bitmap->width || - y < state->clip->getYMinI() || y > state->clip->getYMaxI()) { - return; - } + //----- read destination pixel + cDest0 = destColorPtr[2]; + cDest1 = destColorPtr[1]; + cDest2 = destColorPtr[0]; + aDest = *destAlphaPtr; - // update aaBuf - if (y != aaBufY) { - memset(aaBuf->getDataPtr(), 0xff, - aaBuf->getRowSize() * aaBuf->getHeight()); - x0 = 0; - x1 = bitmap->width - 1; - state->clip->clipAALine(aaBuf, &x0, &x1, y); - aaBufY = y; - } + //----- source alpha + aSrc = div255(pipe->aInput * shape); - // compute the shape value -#if splashAASize == 4 - p = aaBuf->getDataPtr() + (x >> 1); - w = aaBuf->getRowSize(); - if (x & 1) { - t = bitCount4[*p & 0x0f] + bitCount4[p[w] & 0x0f] + - bitCount4[p[2*w] & 0x0f] + bitCount4[p[3*w] & 0x0f]; - } else { - t = bitCount4[*p >> 4] + bitCount4[p[w] >> 4] + - bitCount4[p[2*w] >> 4] + bitCount4[p[3*w] >> 4]; - } -#else - t = 0; - for (yy = 0; yy < splashAASize; ++yy) { - for (xx = 0; xx < splashAASize; ++xx) { - p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + - ((x * splashAASize + xx) >> 3); - t += (*p >> (7 - ((x * splashAASize + xx) & 7))) & 1; + //----- result alpha and non-isolated group element correction + aResult = aSrc + aDest - div255(aSrc * aDest); + alphaI = aResult; + + //----- result color + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + } else { + cResult0 = state->rgbTransferR[(Guchar)(((alphaI - aSrc) * cDest0 + + aSrc * cSrcPtr[0]) / alphaI)]; + cResult1 = state->rgbTransferG[(Guchar)(((alphaI - aSrc) * cDest1 + + aSrc * cSrcPtr[1]) / alphaI)]; + cResult2 = state->rgbTransferB[(Guchar)(((alphaI - aSrc) * cDest2 + + aSrc * cSrcPtr[2]) / alphaI)]; } - } -#endif - // draw the pixel - if (t != 0) { - pipeSetXY(pipe, x, y); - pipe->shape = div255(aaGamma[t] * pipe->shape); - (this->*pipe->run)(pipe); - updateModX(x); - updateModY(y); + //----- write destination pixel + destColorPtr[0] = cResult2; + destColorPtr[1] = cResult1; + destColorPtr[2] = cResult0; + destColorPtr += 3; + *destAlphaPtr++ = aResult; + + cSrcPtr += cSrcStride; + ++shapePtr; } + + updateModX(lastX); } -inline void Splash::drawSpan(SplashPipe *pipe, int x0, int x1, int y, - GBool noClip) { - int x; +#if SPLASH_CMYK +// special case: +// !pipe->pattern && !pipe->noTransparency && !state->softMask && +// pipe->usesShape && !pipe->alpha0Ptr && !state->blendFunc && +// !pipe->nonIsolatedGroup && +// bitmap->mode == splashModeCMYK8 && bitmap->alpha +void Splash::pipeRunAACMYK8(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; + int cSrcStride, x, lastX; - if (noClip) { - pipeSetXY(pipe, x0, y); - for (x = x0; x <= x1; ++x) { - (this->*pipe->run)(pipe); - } - updateModX(x0); - updateModX(x1); - updateModY(y); - } else { - if (x0 < state->clip->getXMinI()) { - x0 = state->clip->getXMinI(); - } - if (x1 > state->clip->getXMaxI()) { - x1 = state->clip->getXMaxI(); - } - pipeSetXY(pipe, x0, y); - for (x = x0; x <= x1; ++x) { - if (state->clip->test(x, y)) { - (this->*pipe->run)(pipe); - updateModX(x); - updateModY(y); - } else { - pipeIncX(pipe); - } + 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; -inline void Splash::drawAALine(SplashPipe *pipe, int x0, int x1, int y) { -#if splashAASize == 4 - static int bitCount4[16] = { 0, 1, 1, 2, 1, 2, 2, 3, - 1, 2, 2, 3, 2, 3, 3, 4 }; - SplashColorPtr p0, p1, p2, p3; - int t; -#else - SplashColorPtr p; - int xx, yy, t; -#endif - int x; + destColorPtr = &bitmap->data[y * bitmap->rowSize + 4 * x0]; + destAlphaPtr = &bitmap->alpha[y * bitmap->width + x0]; -#if splashAASize == 4 - p0 = aaBuf->getDataPtr() + (x0 >> 1); - p1 = p0 + aaBuf->getRowSize(); - p2 = p1 + aaBuf->getRowSize(); - p3 = p2 + aaBuf->getRowSize(); -#endif - pipeSetXY(pipe, x0, y); for (x = x0; x <= x1; ++x) { - // compute the shape value -#if splashAASize == 4 - if (x & 1) { - t = bitCount4[*p0 & 0x0f] + bitCount4[*p1 & 0x0f] + - bitCount4[*p2 & 0x0f] + bitCount4[*p3 & 0x0f]; - ++p0; ++p1; ++p2; ++p3; + //----- shape + shape = *shapePtr; + if (!shape) { + destColorPtr += 4; + ++destAlphaPtr; + cSrcPtr += cSrcStride; + ++shapePtr; + continue; + } + lastX = x; + + //----- read destination pixel + cDest0 = destColorPtr[0]; + cDest1 = destColorPtr[1]; + cDest2 = destColorPtr[2]; + cDest3 = destColorPtr[3]; + aDest = *destAlphaPtr; + + //----- overprint + if (state->overprintMask & 1) { + cSrc0 = cSrcPtr[0]; } else { - t = bitCount4[*p0 >> 4] + bitCount4[*p1 >> 4] + - bitCount4[*p2 >> 4] + bitCount4[*p3 >> 4]; + cSrc0 = div255(aDest * cDest0); } -#else - t = 0; - for (yy = 0; yy < splashAASize; ++yy) { - for (xx = 0; xx < splashAASize; ++xx) { - p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + - ((x * splashAASize + xx) >> 3); - t += (*p >> (7 - ((x * splashAASize + xx) & 7))) & 1; - } + if (state->overprintMask & 2) { + cSrc1 = cSrcPtr[1]; + } else { + cSrc1 = div255(aDest * cDest1); } -#endif - - if (t != 0) { - pipe->shape = aaGamma[t]; - (this->*pipe->run)(pipe); - updateModX(x); - updateModY(y); + if (state->overprintMask & 4) { + cSrc2 = cSrcPtr[2]; + } else { + cSrc2 = div255(aDest * cDest2); + } + if (state->overprintMask & 8) { + cSrc3 = cSrcPtr[3]; } else { - pipeIncX(pipe); + cSrc3 = div255(aDest * cDest3); + } + + //----- source alpha + aSrc = div255(pipe->aInput * shape); + + //----- result alpha and non-isolated group element correction + aResult = aSrc + aDest - div255(aSrc * aDest); + alphaI = aResult; + + //----- result color + if (alphaI == 0) { + cResult0 = 0; + cResult1 = 0; + cResult2 = 0; + cResult3 = 0; + } else { + cResult0 = state->cmykTransferC[(Guchar)(((alphaI - aSrc) * cDest0 + + aSrc * cSrc0) / alphaI)]; + cResult1 = state->cmykTransferM[(Guchar)(((alphaI - aSrc) * cDest1 + + aSrc * cSrc1) / alphaI)]; + cResult2 = state->cmykTransferY[(Guchar)(((alphaI - aSrc) * cDest2 + + aSrc * cSrc2) / alphaI)]; + cResult3 = state->cmykTransferK[(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 + //------------------------------------------------------------------------ @@ -1238,21 +1934,18 @@ Splash::Splash(SplashBitmap *bitmapA, GB int i; bitmap = bitmapA; + bitmapComps = splashColorModeNComps[bitmap->mode]; vectorAntialias = vectorAntialiasA; inShading = gFalse; state = new SplashState(bitmap->width, bitmap->height, vectorAntialias, screenParams); + scanBuf = (Guchar *)gmalloc(bitmap->width); if (vectorAntialias) { - aaBuf = new SplashBitmap(splashAASize * bitmap->width, splashAASize, - 1, splashModeMono1, gFalse); - for (i = 0; i <= splashAASize * splashAASize; ++i) { + for (i = 0; i <= 255; ++i) { aaGamma[i] = (Guchar)splashRound( - splashPow((SplashCoord)i / - (SplashCoord)(splashAASize * splashAASize), - splashAAGamma) * 255); + splashPow((SplashCoord)i / (SplashCoord)255, + splashAAGamma) * 255); } - } else { - aaBuf = NULL; } minLineWidth = 0; clearModRegion(); @@ -1264,21 +1957,18 @@ Splash::Splash(SplashBitmap *bitmapA, GB int i; bitmap = bitmapA; + bitmapComps = splashColorModeNComps[bitmap->mode]; vectorAntialias = vectorAntialiasA; inShading = gFalse; state = new SplashState(bitmap->width, bitmap->height, vectorAntialias, screenA); + scanBuf = (Guchar *)gmalloc(bitmap->width); if (vectorAntialias) { - aaBuf = new SplashBitmap(splashAASize * bitmap->width, splashAASize, - 1, splashModeMono1, gFalse); - for (i = 0; i <= splashAASize * splashAASize; ++i) { + for (i = 0; i <= 255; ++i) { aaGamma[i] = (Guchar)splashRound( - splashPow((SplashCoord)i / - (SplashCoord)(splashAASize * splashAASize), - splashAAGamma) * 255); + splashPow((SplashCoord)i / (SplashCoord)255, + splashAAGamma) * 255); } - } else { - aaBuf = NULL; } minLineWidth = 0; clearModRegion(); @@ -1290,9 +1980,7 @@ Splash::~Splash() { restoreState(); } delete state; - if (vectorAntialias) { - delete aaBuf; - } + gfree(scanBuf); } //------------------------------------------------------------------------ @@ -1375,6 +2063,10 @@ GBool Splash::getInNonIsolatedGroup() { return state->inNonIsolatedGroup; } +GBool Splash::getInKnockoutGroup() { + return state->inKnockoutGroup; +} + //------------------------------------------------------------------------ // state write //------------------------------------------------------------------------ @@ -1442,28 +2134,30 @@ void Splash::setStrokeAdjust(GBool strok void Splash::clipResetToRect(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1) { - state->clip->resetToRect(x0, y0, x1, y1); + state->clipResetToRect(x0, y0, x1, y1); } SplashError Splash::clipToRect(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1) { - return state->clip->clipToRect(x0, y0, x1, y1); + return state->clipToRect(x0, y0, x1, y1); } SplashError Splash::clipToPath(SplashPath *path, GBool eo) { - return state->clip->clipToPath(path, state->matrix, state->flatness, eo); + return state->clipToPath(path, eo); } void Splash::setSoftMask(SplashBitmap *softMask) { state->setSoftMask(softMask); } -void Splash::setInNonIsolatedGroup(SplashBitmap *alpha0BitmapA, - int alpha0XA, int alpha0YA) { - alpha0Bitmap = alpha0BitmapA; - alpha0X = alpha0XA; - alpha0Y = alpha0YA; - state->inNonIsolatedGroup = gTrue; +void Splash::setInTransparencyGroup(SplashBitmap *groupBackBitmapA, + int groupBackXA, int groupBackYA, + GBool nonIsolated, GBool knockout) { + groupBackBitmap = groupBackBitmapA; + groupBackX = groupBackXA; + groupBackY = groupBackYA; + state->inNonIsolatedGroup = nonIsolated; + state->inKnockoutGroup = knockout; } void Splash::setTransfer(Guchar *red, Guchar *green, Guchar *blue, @@ -1539,9 +2233,9 @@ void Splash::clear(SplashColorPtr color, for (y = 0; y < bitmap->height; ++y) { p = row; for (x = 0; x < bitmap->width; ++x) { - *p++ = color[2]; - *p++ = color[1]; *p++ = color[0]; + *p++ = color[1]; + *p++ = color[2]; } row += bitmap->rowSize; } @@ -1560,9 +2254,9 @@ void Splash::clear(SplashColorPtr color, for (y = 0; y < bitmap->height; ++y) { p = row; for (x = 0; x < bitmap->width; ++x) { - *p++ = color[0]; - *p++ = color[1]; *p++ = color[2]; + *p++ = color[1]; + *p++ = color[0]; } row += bitmap->rowSize; } @@ -1606,7 +2300,7 @@ void Splash::clear(SplashColorPtr color, SplashError Splash::stroke(SplashPath *path) { SplashPath *path2, *dPath; - SplashCoord d1, d2, t1, t2, w; + SplashCoord t0, t1, t2, t3, w, w2; if (debugMode) { printf("stroke [dash:%d] [width:%.2f]:\n", @@ -1628,31 +2322,41 @@ SplashError Splash::stroke(SplashPath *p } } - // transform a unit square, and take the half the max of the two - // diagonals; the product of this number and the line width is the - // (approximate) transformed line width - t1 = state->matrix[0] + state->matrix[2]; - t2 = state->matrix[1] + state->matrix[3]; - d1 = t1 * t1 + t2 * t2; - t1 = state->matrix[0] - state->matrix[2]; - t2 = state->matrix[1] - state->matrix[3]; - d2 = t1 * t1 + t2 * t2; - if (d2 > d1) { - d1 = d2; - } - d1 *= 0.5; - if (d1 > 0 && - d1 * state->lineWidth * state->lineWidth < minLineWidth * minLineWidth) { - w = minLineWidth / splashSqrt(d1); - strokeWide(path2, w); + // Compute an approximation of the transformed line width. + // Given a CTM of [m0 m1], + // [m2 m3] + // if |m0|*|m3| >= |m1|*|m2| then use min{|m0|,|m3|}, else + // use min{|m1|,|m2|}. + // This handles the common cases -- [s 0 ] and [0 s] -- + // [0 +/-s] [+/-s 0] + // well, and still does something reasonable for the uncommon + // case transforms. + t0 = splashAbs(state->matrix[0]); + t1 = splashAbs(state->matrix[1]); + t2 = splashAbs(state->matrix[2]); + t3 = splashAbs(state->matrix[3]); + if (t0 * t3 >= t1 * t2) { + w = (t0 < t3) ? t0 : t3; + } else { + w = (t1 < t2) ? t1 : t2; + } + w2 = w * state->lineWidth; + // if there is a min line width set, and the transformed line width + // is smaller, use the min line width + if (w > 0 && w2 < minLineWidth) { + strokeWide(path2, minLineWidth / w); } else if (bitmap->mode == splashModeMono1) { - // this gets close to Adobe's behavior in mono mode - if (d1 <= 2) { + // in monochrome mode, use 0-width lines for any transformed line + // width <= 1 -- lines less than 1 pixel wide look too fat without + // antialiasing + if (w2 < 1.001) { strokeNarrow(path2); } else { strokeWide(path2, state->lineWidth); } } else { + // in gray and color modes, only use 0-width lines if the line + // width is explicitly set to 0 if (state->lineWidth == 0) { strokeNarrow(path2); } else { @@ -1678,9 +2382,9 @@ void Splash::strokeNarrow(SplashPath *pa xPath = new SplashXPath(path, state->matrix, state->flatness, gFalse); - pipeInit(&pipe, 0, 0, state->strokePattern, NULL, + pipeInit(&pipe, state->strokePattern, (Guchar)splashRound(state->strokeAlpha * 255), - gFalse, gFalse); + gTrue, gFalse); for (i = 0, seg = xPath->segs; i < xPath->length; ++i, ++seg) { if (seg->y0 <= seg->y1) { @@ -1695,22 +2399,25 @@ void Splash::strokeNarrow(SplashPath *pa x1 = splashFloor(seg->x0); } if ((clipRes = state->clip->testRect(x0 <= x1 ? x0 : x1, y0, - x0 <= x1 ? x1 : x0, y1)) + x0 <= x1 ? x1 : x0, y1, + state->strokeAdjust)) != splashClipAllOutside) { if (y0 == y1) { if (x0 <= x1) { - drawSpan(&pipe, x0, x1, y0, clipRes == splashClipAllInside); + drawStrokeSpan(&pipe, x0, x1, y0, clipRes == splashClipAllInside); } else { - drawSpan(&pipe, x1, x0, y0, clipRes == splashClipAllInside); + drawStrokeSpan(&pipe, x1, x0, y0, clipRes == splashClipAllInside); } } else { dxdy = seg->dxdy; - if (y0 < state->clip->getYMinI()) { - y0 = state->clip->getYMinI(); + y = state->clip->getYMinI(state->strokeAdjust); + if (y0 < y) { + y0 = y; x0 = splashFloor(seg->x0 + ((SplashCoord)y0 - seg->y0) * dxdy); } - if (y1 > state->clip->getYMaxI()) { - y1 = state->clip->getYMaxI(); + y = state->clip->getYMaxI(state->strokeAdjust); + if (y1 > y) { + y1 = y; x1 = splashFloor(seg->x0 + ((SplashCoord)y1 - seg->y0) * dxdy); } if (x0 <= x1) { @@ -1723,9 +2430,10 @@ void Splash::strokeNarrow(SplashPath *pa xb = x1 + 1; } if (xa == xb) { - drawPixel(&pipe, xa, y, clipRes == splashClipAllInside); + drawStrokeSpan(&pipe, xa, xa, y, clipRes == splashClipAllInside); } else { - drawSpan(&pipe, xa, xb - 1, y, clipRes == splashClipAllInside); + drawStrokeSpan(&pipe, xa, xb - 1, y, + clipRes == splashClipAllInside); } xa = xb; } @@ -1739,9 +2447,10 @@ void Splash::strokeNarrow(SplashPath *pa xb = x1 - 1; } if (xa == xb) { - drawPixel(&pipe, xa, y, clipRes == splashClipAllInside); + drawStrokeSpan(&pipe, xa, xa, y, clipRes == splashClipAllInside); } else { - drawSpan(&pipe, xb + 1, xa, y, clipRes == splashClipAllInside); + drawStrokeSpan(&pipe, xb + 1, xa, y, + clipRes == splashClipAllInside); } xa = xb; } @@ -1762,6 +2471,32 @@ void Splash::strokeNarrow(SplashPath *pa delete xPath; } +void Splash::drawStrokeSpan(SplashPipe *pipe, int x0, int x1, int y, + GBool noClip) { + int x; + + x = state->clip->getXMinI(state->strokeAdjust); + if (x > x0) { + x0 = x; + } + x = state->clip->getXMaxI(state->strokeAdjust); + if (x < x1) { + x1 = x; + } + if (x0 > x1) { + return; + } + for (x = x0; x <= x1; ++x) { + scanBuf[x] = 0xff; + } + if (!noClip) { + if (!state->clip->clipSpanBinary(scanBuf, y, x0, x1, state->strokeAdjust)) { + return; + } + } + (this->*pipe->run)(pipe, x0, x1, y, scanBuf + x0, NULL); +} + void Splash::strokeWide(SplashPath *path, SplashCoord w) { SplashPath *path2; @@ -2012,10 +2747,11 @@ SplashError Splash::fillWithPattern(Spla SplashPattern *pattern, SplashCoord alpha) { SplashPipe pipe; + SplashPath *path2; SplashXPath *xPath; SplashXPathScanner *scanner; - int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y; - SplashClipResult clipRes, clipRes2; + int xMin, yMin, xMax, yMax, x, y, t; + SplashClipResult clipRes; if (path->length == 0) { return splashErrEmptyPath; @@ -2025,93 +2761,180 @@ SplashError Splash::fillWithPattern(Spla return splashOk; } - // add stroke adjustment hints for filled rectangles -- this only - // applies to paths that consist of a single subpath - // (this appears to match Acrobat's behavior) - if (state->strokeAdjust && !path->hints) { - int n; - n = path->getLength(); - if (n == 4 && - !(path->flags[0] & splashPathClosed) && - !(path->flags[1] & splashPathLast) && - !(path->flags[2] & splashPathLast)) { - path->close(gTrue); - path->addStrokeAdjustHint(0, 2, 0, 4); - path->addStrokeAdjustHint(1, 3, 0, 4); - } else if (n == 5 && - (path->flags[0] & splashPathClosed) && - !(path->flags[1] & splashPathLast) && - !(path->flags[2] & splashPathLast) && - !(path->flags[3] & splashPathLast)) { - path->addStrokeAdjustHint(0, 2, 0, 4); - path->addStrokeAdjustHint(1, 3, 0, 4); - } - } + path2 = tweakFillPath(path); - xPath = new SplashXPath(path, state->matrix, state->flatness, gTrue); - if (vectorAntialias && !inShading) { - xPath->aaScale(); + xPath = new SplashXPath(path2, state->matrix, state->flatness, gTrue); + if (path2 != path) { + delete path2; } - xPath->sort(); - yMinI = state->clip->getYMinI(); - yMaxI = state->clip->getYMaxI(); - if (vectorAntialias && !inShading) { - yMinI = yMinI * splashAASize; - yMaxI = (yMaxI + 1) * splashAASize - 1; - } - scanner = new SplashXPathScanner(xPath, eo, yMinI, yMaxI); - - // get the min and max x and y values - if (vectorAntialias && !inShading) { - scanner->getBBoxAA(&xMinI, &yMinI, &xMaxI, &yMaxI); - } else { - scanner->getBBox(&xMinI, &yMinI, &xMaxI, &yMaxI); + xMin = xPath->getXMin(); + yMin = xPath->getYMin(); + xMax = xPath->getXMax(); + yMax = xPath->getYMax(); + if (xMin > xMax || yMin > yMax) { + delete xPath; + return splashOk; } + scanner = new SplashXPathScanner(xPath, eo, yMin, yMax); // check clipping - if ((clipRes = state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI)) + if ((clipRes = state->clip->testRect(xMin, yMin, xMax, yMax, + state->strokeAdjust)) != splashClipAllOutside) { - if (scanner->hasPartialClip()) { - clipRes = splashClipPartial; + + if ((t = state->clip->getXMinI(state->strokeAdjust)) > xMin) { + xMin = t; + } + if ((t = state->clip->getXMaxI(state->strokeAdjust)) < xMax) { + xMax = t; + } + if ((t = state->clip->getYMinI(state->strokeAdjust)) > yMin) { + yMin = t; + } + if ((t = state->clip->getYMaxI(state->strokeAdjust)) < yMax) { + yMax = t; + } + if (xMin > xMax || yMin > yMax) { + delete scanner; + delete xPath; + return splashOk; } - pipeInit(&pipe, 0, yMinI, pattern, NULL, (Guchar)splashRound(alpha * 255), - vectorAntialias && !inShading, gFalse); + pipeInit(&pipe, pattern, (Guchar)splashRound(alpha * 255), + gTrue, gFalse); // draw the spans if (vectorAntialias && !inShading) { - for (y = yMinI; y <= yMaxI; ++y) { - scanner->renderAALine(aaBuf, &x0, &x1, y); + for (y = yMin; y <= yMax; ++y) { + scanner->getSpan(scanBuf, y, xMin, xMax); if (clipRes != splashClipAllInside) { - state->clip->clipAALine(aaBuf, &x0, &x1, y); + state->clip->clipSpan(scanBuf, y, xMin, xMax, state->strokeAdjust); } - drawAALine(&pipe, x0, x1, y); + for (x = xMin; x <= xMax; ++x) { + scanBuf[x] = aaGamma[scanBuf[x]]; + } + (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); } } else { - for (y = yMinI; y <= yMaxI; ++y) { - while (scanner->getNextSpan(y, &x0, &x1)) { - if (clipRes == splashClipAllInside) { - drawSpan(&pipe, x0, x1, y, gTrue); - } else { - // limit the x range - if (x0 < state->clip->getXMinI()) { - x0 = state->clip->getXMinI(); - } - if (x1 > state->clip->getXMaxI()) { - x1 = state->clip->getXMaxI(); - } - clipRes2 = state->clip->testSpan(x0, x1, y); - drawSpan(&pipe, x0, x1, y, clipRes2 == splashClipAllInside); - } + for (y = yMin; y <= yMax; ++y) { + scanner->getSpanBinary(scanBuf, y, xMin, xMax); + if (clipRes != splashClipAllInside) { + state->clip->clipSpanBinary(scanBuf, y, xMin, xMax, + state->strokeAdjust); } + (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); } } } - opClipRes = clipRes; + opClipRes = clipRes; + + delete scanner; + delete xPath; + return splashOk; +} + +// Applies various tweaks to a fill path: +// (1) add stroke adjust hints to a filled rectangle +// (2) applies a minimum width to a zero-width filled rectangle (so +// stroke adjustment works correctly +// (3) convert a degenerate fill ('moveto lineto fill' and 'moveto +// lineto closepath fill') to a minimum-width filled rectangle +// +// These tweaks only apply to paths with a single subpath. +// +// Returns either the unchanged input path or a new path (in which +// case the returned path must be deleted by the caller). +SplashPath *Splash::tweakFillPath(SplashPath *path) { + SplashPath *path2; + SplashCoord xx0, yy0, xx1, yy1, dx, dy, d, wx, wy, w; + int n; + + if (!state->strokeAdjust || path->hints) { + return path; + } + + n = path->getLength(); + if (!((n == 2) || + (n == 3 && + path->flags[1] == 0) || + (n == 4 && + path->flags[1] == 0 && + path->flags[2] == 0) || + (n == 5 && + path->flags[1] == 0 && + path->flags[2] == 0 && + path->flags[3] == 0))) { + return path; + } + + path2 = path; + + // degenerate fill (2 or 3 points) or rectangle of (nearly) zero + // width --> replace with a min-width rectangle and hint + if (n == 2 || + (n == 3 && (path->flags[0] & splashPathClosed)) || + (n == 3 && (splashAbs(path->pts[0].x - path->pts[2].x) < 0.001 && + splashAbs(path->pts[0].y - path->pts[2].y) < 0.001)) || + ((n == 4 || + (n == 5 && (path->flags[0] & splashPathClosed))) && + ((splashAbs(path->pts[0].x - path->pts[1].x) < 0.001 && + splashAbs(path->pts[0].y - path->pts[1].y) < 0.001 && + splashAbs(path->pts[2].x - path->pts[3].x) < 0.001 && + splashAbs(path->pts[2].y - path->pts[3].y) < 0.001) || + (splashAbs(path->pts[0].x - path->pts[3].x) < 0.001 && + splashAbs(path->pts[0].y - path->pts[3].y) < 0.001 && + splashAbs(path->pts[1].x - path->pts[2].x) < 0.001 && + splashAbs(path->pts[1].y - path->pts[2].y) < 0.001)))) { + wx = state->matrix[0] + state->matrix[2]; + wy = state->matrix[1] + state->matrix[3]; + w = sqrt(wx*wx + wy*wy); + if (w < 0.001) { + w = 0; + } else { + // min width is 0.1 -- this constant is minWidth * sqrt(2) + w = (SplashCoord)0.1414 / w; + } + xx0 = path->pts[0].x; + yy0 = path->pts[0].y; + if (n <= 3) { + xx1 = path->pts[1].x; + yy1 = path->pts[1].y; + } else { + xx1 = path->pts[2].x; + yy1 = path->pts[2].y; + } + dx = xx1 - xx0; + dy = yy1 - yy0; + d = sqrt(dx * dx + dy * dy); + if (d < 0.001) { + d = 0; + } else { + d = w / d; + } + dx *= d; + dy *= d; + path2 = new SplashPath(); + path2->moveTo(xx0 + dy, yy0 - dx); + path2->lineTo(xx1 + dy, yy1 - dx); + path2->lineTo(xx1 - dy, yy1 + dx); + path2->lineTo(xx0 - dy, yy0 + dx); + path2->close(gTrue); + path2->addStrokeAdjustHint(0, 2, 0, 4); + path2->addStrokeAdjustHint(1, 3, 0, 4); + + // unclosed rectangle --> close and hint + } else if (n == 4 && !(path->flags[0] & splashPathClosed)) { + path2->close(gTrue); + path2->addStrokeAdjustHint(0, 2, 0, 4); + path2->addStrokeAdjustHint(1, 3, 0, 4); + + // closed rectangle --> hint + } else if (n == 5 && (path->flags[0] & splashPathClosed)) { + path2->addStrokeAdjustHint(0, 2, 0, 4); + path2->addStrokeAdjustHint(1, 3, 0, 4); + } - delete scanner; - delete xPath; - return splashOk; + return path2; } GBool Splash::pathAllOutside(SplashPath *path) { @@ -2177,7 +3000,8 @@ GBool Splash::pathAllOutside(SplashPath xMaxI = splashFloor(xMax2); yMaxI = splashFloor(yMax2); - return state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI) == + return state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI, + state->strokeAdjust) == splashClipAllOutside; } @@ -2185,49 +3009,63 @@ SplashError Splash::xorFill(SplashPath * SplashPipe pipe; SplashXPath *xPath; SplashXPathScanner *scanner; - int xMinI, yMinI, xMaxI, yMaxI, x0, x1, y; - SplashClipResult clipRes, clipRes2; + int xMin, yMin, xMax, yMax, y, t; + SplashClipResult clipRes; SplashBlendFunc origBlendFunc; if (path->length == 0) { return splashErrEmptyPath; } + if (pathAllOutside(path)) { + opClipRes = splashClipAllOutside; + return splashOk; + } xPath = new SplashXPath(path, state->matrix, state->flatness, gTrue); - xPath->sort(); - scanner = new SplashXPathScanner(xPath, eo, state->clip->getYMinI(), - state->clip->getYMaxI()); - - // get the min and max x and y values - scanner->getBBox(&xMinI, &yMinI, &xMaxI, &yMaxI); + xMin = xPath->getXMin(); + yMin = xPath->getYMin(); + xMax = xPath->getXMax(); + yMax = xPath->getYMax(); + if (xMin > xMax || yMin > yMax) { + delete xPath; + return splashOk; + } + scanner = new SplashXPathScanner(xPath, eo, yMin, yMax); // check clipping - if ((clipRes = state->clip->testRect(xMinI, yMinI, xMaxI, yMaxI)) + if ((clipRes = state->clip->testRect(xMin, yMin, xMax, yMax, + state->strokeAdjust)) != splashClipAllOutside) { - if (scanner->hasPartialClip()) { - clipRes = splashClipPartial; + + if ((t = state->clip->getXMinI(state->strokeAdjust)) > xMin) { + xMin = t; + } + if ((t = state->clip->getXMaxI(state->strokeAdjust)) < xMax) { + xMax = t; + } + if ((t = state->clip->getYMinI(state->strokeAdjust)) > yMin) { + yMin = t; + } + if ((t = state->clip->getYMaxI(state->strokeAdjust)) < yMax) { + yMax = t; + } + if (xMin > xMax || yMin > yMax) { + delete scanner; + delete xPath; + return splashOk; } origBlendFunc = state->blendFunc; state->blendFunc = &blendXor; - pipeInit(&pipe, 0, yMinI, state->fillPattern, NULL, 255, gFalse, gFalse); + pipeInit(&pipe, state->fillPattern, 255, gTrue, gFalse); // draw the spans - for (y = yMinI; y <= yMaxI; ++y) { - while (scanner->getNextSpan(y, &x0, &x1)) { - if (clipRes == splashClipAllInside) { - drawSpan(&pipe, x0, x1, y, gTrue); - } else { - // limit the x range - if (x0 < state->clip->getXMinI()) { - x0 = state->clip->getXMinI(); - } - if (x1 > state->clip->getXMaxI()) { - x1 = state->clip->getXMaxI(); - } - clipRes2 = state->clip->testSpan(x0, x1, y); - drawSpan(&pipe, x0, x1, y, clipRes2 == splashClipAllInside); - } + for (y = yMin; y <= yMax; ++y) { + scanner->getSpanBinary(scanBuf, y, xMin, xMax); + if (clipRes != splashClipAllInside) { + state->clip->clipSpanBinary(scanBuf, y, xMin, xMax, + state->strokeAdjust); } + (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); } state->blendFunc = origBlendFunc; } @@ -2278,105 +3116,86 @@ SplashError Splash::fillGlyph(SplashCoor SplashError Splash::fillGlyph2(int x0, int y0, SplashGlyphBitmap *glyph) { SplashPipe pipe; SplashClipResult clipRes; - GBool noClip; - int alpha0; Guchar alpha; Guchar *p; - int x1, y1, xx, xx1, yy; + int xMin, yMin, xMax, yMax; + int x, y, xg, yg, xx, t; - if ((clipRes = state->clip->testRect(x0 - glyph->x, - y0 - glyph->y, - x0 - glyph->x + glyph->w - 1, - y0 - glyph->y + glyph->h - 1)) + xg = x0 - glyph->x; + yg = y0 - glyph->y; + xMin = xg; + xMax = xg + glyph->w - 1; + yMin = yg; + yMax = yg + glyph->h - 1; + if ((clipRes = state->clip->testRect(xMin, yMin, xMax, yMax, + state->strokeAdjust)) != splashClipAllOutside) { - noClip = clipRes == splashClipAllInside; - - if (noClip) { + pipeInit(&pipe, state->fillPattern, + (Guchar)splashRound(state->fillAlpha * 255), + gTrue, gFalse); + if (clipRes == splashClipAllInside) { if (glyph->aa) { - pipeInit(&pipe, x0 - glyph->x, y0 - glyph->y, state->fillPattern, NULL, - (Guchar)splashRound(state->fillAlpha * 255), gTrue, gFalse); p = glyph->data; - for (yy = 0, y1 = y0 - glyph->y; yy < glyph->h; ++yy, ++y1) { - pipeSetXY(&pipe, x0 - glyph->x, y1); - for (xx = 0, x1 = x0 - glyph->x; xx < glyph->w; ++xx, ++x1) { - alpha = *p++; - if (alpha != 0) { - pipe.shape = alpha; - (this->*pipe.run)(&pipe); - updateModX(x1); - updateModY(y1); - } else { - pipeIncX(&pipe); - } - } + for (y = yMin; y <= yMax; ++y) { + (this->*pipe.run)(&pipe, xMin, xMax, y, + glyph->data + (y - yMin) * glyph->w, NULL); } } else { - pipeInit(&pipe, x0 - glyph->x, y0 - glyph->y, state->fillPattern, NULL, - (Guchar)splashRound(state->fillAlpha * 255), gFalse, gFalse); p = glyph->data; - for (yy = 0, y1 = y0 - glyph->y; yy < glyph->h; ++yy, ++y1) { - pipeSetXY(&pipe, x0 - glyph->x, y1); - for (xx = 0, x1 = x0 - glyph->x; xx < glyph->w; xx += 8) { - alpha0 = *p++; - for (xx1 = 0; xx1 < 8 && xx + xx1 < glyph->w; ++xx1, ++x1) { - if (alpha0 & 0x80) { - (this->*pipe.run)(&pipe); - updateModX(x1); - updateModY(y1); - } else { - pipeIncX(&pipe); - } - alpha0 <<= 1; + for (y = yMin; y <= yMax; ++y) { + for (x = xMin; x <= xMax; x += 8) { + alpha = *p++; + for (xx = 0; xx < 8 && x + xx <= xMax; ++xx) { + scanBuf[x + xx] = (alpha & 0x80) ? 0xff : 0x00; + alpha <<= 1; } } + (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); } } } else { - if (glyph->aa) { - pipeInit(&pipe, x0 - glyph->x, y0 - glyph->y, state->fillPattern, NULL, - (Guchar)splashRound(state->fillAlpha * 255), gTrue, gFalse); - p = glyph->data; - for (yy = 0, y1 = y0 - glyph->y; yy < glyph->h; ++yy, ++y1) { - pipeSetXY(&pipe, x0 - glyph->x, y1); - for (xx = 0, x1 = x0 - glyph->x; xx < glyph->w; ++xx, ++x1) { - if (state->clip->test(x1, y1)) { - alpha = *p++; - if (alpha != 0) { - pipe.shape = alpha; - (this->*pipe.run)(&pipe); - updateModX(x1); - updateModY(y1); - } else { - pipeIncX(&pipe); - } - } else { - pipeIncX(&pipe); - ++p; - } + if ((t = state->clip->getXMinI(state->strokeAdjust)) > xMin) { + xMin = t; + } + if ((t = state->clip->getXMaxI(state->strokeAdjust)) < xMax) { + xMax = t; + } + if ((t = state->clip->getYMinI(state->strokeAdjust)) > yMin) { + yMin = t; + } + if ((t = state->clip->getYMaxI(state->strokeAdjust)) < yMax) { + yMax = t; + } + if (xMin <= xMax && yMin <= yMax) { + if (glyph->aa) { + for (y = yMin; y <= yMax; ++y) { + p = glyph->data + (y - yg) * glyph->w + (xMin - xg); + memcpy(scanBuf + xMin, p, xMax - xMin + 1); + state->clip->clipSpan(scanBuf, y, xMin, xMax, + state->strokeAdjust); + (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); } - } - } else { - pipeInit(&pipe, x0 - glyph->x, y0 - glyph->y, state->fillPattern, NULL, - (Guchar)splashRound(state->fillAlpha * 255), gFalse, gFalse); - p = glyph->data; - for (yy = 0, y1 = y0 - glyph->y; yy < glyph->h; ++yy, ++y1) { - pipeSetXY(&pipe, x0 - glyph->x, y1); - for (xx = 0, x1 = x0 - glyph->x; xx < glyph->w; xx += 8) { - alpha0 = *p++; - for (xx1 = 0; xx1 < 8 && xx + xx1 < glyph->w; ++xx1, ++x1) { - if (state->clip->test(x1, y1)) { - if (alpha0 & 0x80) { - (this->*pipe.run)(&pipe); - updateModX(x1); - updateModY(y1); - } else { - pipeIncX(&pipe); - } - } else { - pipeIncX(&pipe); + } else { + for (y = yMin; y <= yMax; ++y) { + p = glyph->data + (y - yg) * ((glyph->w + 7) >> 3) + + ((xMin - xg) >> 3); + alpha = *p++; + xx = (xMin - xg) & 7; + alpha <<= xx; + for (x = xMin; xx < 8 && x <= xMax; ++x, ++xx) { + scanBuf[x] = (alpha & 0x80) ? 255 : 0; + alpha <<= 1; + } + for (; x <= xMax; x += 8) { + alpha = *p++; + for (xx = 0; xx < 8 && x + xx <= xMax; ++xx) { + scanBuf[x + xx] = (alpha & 0x80) ? 255 : 0; + alpha <<= 1; } - alpha0 <<= 1; } + state->clip->clipSpanBinary(scanBuf, y, xMin, xMax, + state->strokeAdjust); + (this->*pipe.run)(&pipe, xMin, xMax, y, scanBuf + xMin, NULL); } } } @@ -2387,12 +3206,28 @@ SplashError Splash::fillGlyph2(int x0, i return splashOk; } +void Splash::getImageBounds(SplashCoord xyMin, SplashCoord xyMax, + int *xyMinI, int *xyMaxI) { + if (state->strokeAdjust) { + splashStrokeAdjust(xyMin, xyMax, xyMinI, xyMaxI); + } else { + *xyMinI = splashFloor(xyMin); + *xyMaxI = splashFloor(xyMax); + if (*xyMaxI <= *xyMinI) { + *xyMaxI = *xyMinI + 1; + } + } +} + +// The glyphMode flag is not currently used, but may be useful if the +// stroke adjustment behavior is changed. SplashError Splash::fillImageMask(SplashImageMaskSource src, void *srcData, int w, int h, SplashCoord *mat, - GBool glyphMode) { + GBool glyphMode, GBool interpolate) { SplashBitmap *scaledMask; SplashClipResult clipRes; GBool minorAxisZero; + SplashCoord wSize, hSize, t0, t1; int x0, y0, x1, y1, scaledWidth, scaledHeight; if (debugMode) { @@ -2406,68 +3241,269 @@ SplashError Splash::fillImageMask(Splash return splashErrSingularMatrix; } - minorAxisZero = mat[1] == 0 && mat[2] == 0; + 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 (wSize > 2 * w && hSize > 2 * h && wSize * hSize > 1000000) { + upscaleMask(src, srcData, w, h, mat, glyphMode, interpolate); // scaling only - if (mat[0] > 0 && minorAxisZero && mat[3] > 0) { - x0 = imgCoordMungeLowerC(mat[4], glyphMode); - y0 = imgCoordMungeLowerC(mat[5], glyphMode); - x1 = imgCoordMungeUpperC(mat[0] + mat[4], glyphMode); - y1 = imgCoordMungeUpperC(mat[3] + mat[5], glyphMode); - // make sure narrow images cover at least one pixel - if (x0 == x1) { - ++x1; - } - if (y0 == y1) { - ++y1; - } - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1); + } 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(src, srcData, w, h, scaledWidth, scaledHeight); + scaledMask = scaleMask(src, srcData, w, h, scaledWidth, scaledHeight, + interpolate); blitMask(scaledMask, x0, y0, clipRes); delete scaledMask; } // scaling plus vertical flip } else if (mat[0] > 0 && minorAxisZero && mat[3] < 0) { - x0 = imgCoordMungeLowerC(mat[4], glyphMode); - y0 = imgCoordMungeLowerC(mat[3] + mat[5], glyphMode); - x1 = imgCoordMungeUpperC(mat[0] + mat[4], glyphMode); - y1 = imgCoordMungeUpperC(mat[5], glyphMode); - // make sure narrow images cover at least one pixel - if (x0 == x1) { - ++x1; + 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(src, srcData, w, h, scaledWidth, scaledHeight, + interpolate); + vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1); + blitMask(scaledMask, x0, y0, clipRes); + delete scaledMask; } - if (y0 == y1) { - ++y1; + + // 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(src, srcData, w, h, scaledWidth, scaledHeight, + interpolate); + horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1); + blitMask(scaledMask, x0, y0, clipRes); + delete scaledMask; } - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1); + + // 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(src, srcData, w, h, scaledWidth, scaledHeight); + scaledMask = scaleMask(src, srcData, w, h, scaledWidth, scaledHeight, + interpolate); vertFlipImage(scaledMask, scaledWidth, scaledHeight, 1); + horizFlipImage(scaledMask, scaledWidth, scaledHeight, 1); blitMask(scaledMask, x0, y0, clipRes); delete scaledMask; } // all other cases } else { - arbitraryTransformMask(src, srcData, w, h, mat, glyphMode); + arbitraryTransformMask(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 ((tt = state->clip->getXMaxI(state->strokeAdjust) + 1) < xMaxI) { + xMaxI = tt; + } + if ((tt = state->clip->getYMinI(state->strokeAdjust)) > yMinI) { + yMinI = tt; + } + if ((tt = state->clip->getYMaxI(state->strokeAdjust) + 1) < yMaxI) { + yMaxI = tt; + } + } + + // 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 + 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] *= 255; + } + } + + // 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; + } + if (x1 >= srcWidth) { + x1 = srcWidth - 1; + } + if (y0 < 0) { + y0 = 0; + } + if (y1 >= srcHeight) { + y1 = srcHeight - 1; + } + pix0 = ((SplashCoord)1 - sx) * unscaledImage[y0 * srcWidth + x0] + + sx * unscaledImage[y0 * srcWidth + x1]; + pix1 = ((SplashCoord)1 - sx) * unscaledImage[y1 * srcWidth + x0] + + sx * 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; + } + } + } + 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(SplashImageMaskSource src, void *srcData, int srcWidth, int srcHeight, - SplashCoord *mat, GBool glyphMode) { + SplashCoord *mat, GBool glyphMode, + GBool interpolate) { SplashBitmap *scaledMask; - SplashClipResult clipRes, clipRes2; + SplashClipResult clipRes; SplashPipe pipe; int scaledWidth, scaledHeight, t0, t1; SplashCoord r00, r01, r10, r11, det, ir00, ir01, ir10, ir11; @@ -2484,29 +3520,26 @@ void Splash::arbitraryTransformMask(Spla vx[3] = mat[0] + mat[4]; vy[3] = mat[1] + mat[5]; // clipping - xMin = imgCoordMungeLowerC(vx[0], glyphMode); - xMax = imgCoordMungeUpperC(vx[0], glyphMode); - yMin = imgCoordMungeLowerC(vy[0], glyphMode); - yMax = imgCoordMungeUpperC(vy[0], glyphMode); + xMin = splashRound(vx[0]); + xMax = splashRound(vx[0]); + yMin = splashRound(vy[0]); + yMax = splashRound(vy[0]); for (i = 1; i < 4; ++i) { - t0 = imgCoordMungeLowerC(vx[i], glyphMode); + t0 = splashRound(vx[i]); if (t0 < xMin) { xMin = t0; - } - t0 = imgCoordMungeUpperC(vx[i], glyphMode); - if (t0 > xMax) { + } else if (t0 > xMax) { xMax = t0; } - t1 = imgCoordMungeLowerC(vy[i], glyphMode); + t1 = splashRound(vy[i]); if (t1 < yMin) { yMin = t1; - } - t1 = imgCoordMungeUpperC(vy[i], glyphMode); - if (t1 > yMax) { + } else if (t1 > yMax) { yMax = t1; } } - clipRes = state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1); + clipRes = state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1, + state->strokeAdjust); opClipRes = clipRes; if (clipRes == splashClipAllOutside) { return; @@ -2514,33 +3547,25 @@ void Splash::arbitraryTransformMask(Spla // compute the scale factors if (mat[0] >= 0) { - t0 = imgCoordMungeUpperC(mat[0] + mat[4], glyphMode) - - imgCoordMungeLowerC(mat[4], glyphMode); + t0 = splashRound(mat[0] + mat[4]) - splashRound(mat[4]); } else { - t0 = imgCoordMungeUpperC(mat[4], glyphMode) - - imgCoordMungeLowerC(mat[0] + mat[4], glyphMode); + t0 = splashRound(mat[4]) - splashRound(mat[0] + mat[4]); } if (mat[1] >= 0) { - t1 = imgCoordMungeUpperC(mat[1] + mat[5], glyphMode) - - imgCoordMungeLowerC(mat[5], glyphMode); + t1 = splashRound(mat[1] + mat[5]) - splashRound(mat[5]); } else { - t1 = imgCoordMungeUpperC(mat[5], glyphMode) - - imgCoordMungeLowerC(mat[1] + mat[5], glyphMode); + t1 = splashRound(mat[5]) - splashRound(mat[1] + mat[5]); } scaledWidth = t0 > t1 ? t0 : t1; if (mat[2] >= 0) { - t0 = imgCoordMungeUpperC(mat[2] + mat[4], glyphMode) - - imgCoordMungeLowerC(mat[4], glyphMode); + t0 = splashRound(mat[2] + mat[4]) - splashRound(mat[4]); } else { - t0 = imgCoordMungeUpperC(mat[4], glyphMode) - - imgCoordMungeLowerC(mat[2] + mat[4], glyphMode); + t0 = splashRound(mat[4]) - splashRound(mat[2] + mat[4]); } if (mat[3] >= 0) { - t1 = imgCoordMungeUpperC(mat[3] + mat[5], glyphMode) - - imgCoordMungeLowerC(mat[5], glyphMode); + t1 = splashRound(mat[3] + mat[5]) - splashRound(mat[5]); } else { - t1 = imgCoordMungeUpperC(mat[5], glyphMode) - - imgCoordMungeLowerC(mat[3] + mat[5], glyphMode); + t1 = splashRound(mat[5]) - splashRound(mat[3] + mat[5]); } scaledHeight = t0 > t1 ? t0 : t1; if (scaledWidth == 0) { @@ -2567,19 +3592,28 @@ void Splash::arbitraryTransformMask(Spla // scale the input image scaledMask = scaleMask(src, srcData, srcWidth, srcHeight, - scaledWidth, scaledHeight); + scaledWidth, scaledHeight, interpolate); // construct the three sections - i = (vy[2] <= vy[3]) ? 2 : 3; - if (vy[1] <= vy[i]) { + i = 0; + if (vy[1] < vy[i]) { i = 1; } - if (vy[0] < vy[i] || (i != 3 && vy[0] == vy[i])) { - i = 0; + 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 (vy[i] == vy[(i+1) & 3]) { - section[0].y0 = imgCoordMungeLowerC(vy[i], glyphMode); - section[0].y1 = imgCoordMungeUpperC(vy[(i+2) & 3], glyphMode) - 1; + 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; @@ -2593,8 +3627,8 @@ void Splash::arbitraryTransformMask(Spla } nSections = 1; } else { - section[0].y0 = imgCoordMungeLowerC(vy[i], glyphMode); - section[2].y1 = imgCoordMungeUpperC(vy[(i+2) & 3], glyphMode) - 1; + 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]) { @@ -2605,8 +3639,8 @@ void Splash::arbitraryTransformMask(Spla section[0].ib1 = section[2].ib0 = (i+1) & 3; } if (vy[(i+1) & 3] < vy[(i+3) & 3]) { - section[1].y0 = imgCoordMungeLowerC(vy[(i+1) & 3], glyphMode); - section[2].y0 = imgCoordMungeUpperC(vy[(i+3) & 3], glyphMode); + 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; @@ -2619,8 +3653,8 @@ void Splash::arbitraryTransformMask(Spla section[1].ib1 = (i+2) & 3; } } else { - section[1].y0 = imgCoordMungeLowerC(vy[(i+3) & 3], glyphMode); - section[2].y0 = imgCoordMungeUpperC(vy[(i+1) & 3], glyphMode); + 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; @@ -2653,11 +3687,9 @@ void Splash::arbitraryTransformMask(Spla } // initialize the pixel pipe - pipeInit(&pipe, 0, 0, state->fillPattern, NULL, - (Guchar)splashRound(state->fillAlpha * 255), gTrue, gFalse); - if (vectorAntialias) { - drawAAPixelInit(); - } + pipeInit(&pipe, state->fillPattern, + (Guchar)splashRound(state->fillAlpha * 255), + gTrue, gFalse); // make sure narrow images cover at least one pixel if (nSections == 1) { @@ -2675,23 +3707,30 @@ void Splash::arbitraryTransformMask(Spla // scan all pixels inside the target region for (i = 0; i < nSections; ++i) { for (y = section[i].y0; y <= section[i].y1; ++y) { - xa = imgCoordMungeLowerC(section[i].xa0 + - ((SplashCoord)y + 0.5 - section[i].ya0) * - section[i].dxdya, - glyphMode); - xb = imgCoordMungeUpperC(section[i].xb0 + - ((SplashCoord)y + 0.5 - section[i].yb0) * - section[i].dxdyb, - glyphMode); + 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; } - if (clipRes != splashClipAllInside) { - clipRes2 = state->clip->testSpan(xa, xb - 1, y); - } else { - clipRes2 = clipRes; + // check the scanBuf bounds + if (xa >= bitmap->width || xb < 0) { + continue; + } + if (xa < 0) { + xa = 0; } + if (xb > bitmap->width) { + xb = bitmap->width; + } + // 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 + @@ -2710,13 +3749,19 @@ void Splash::arbitraryTransformMask(Spla } else if (yy >= scaledHeight) { yy = scaledHeight - 1; } - pipe.shape = scaledMask->data[yy * scaledWidth + xx]; - if (vectorAntialias && clipRes2 != splashClipAllInside) { - drawAAPixel(&pipe, x, y); + 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 { - drawPixel(&pipe, x, y, clipRes2 == splashClipAllInside); + 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); } } @@ -2726,7 +3771,8 @@ void Splash::arbitraryTransformMask(Spla // Scale an image mask into a SplashBitmap. SplashBitmap *Splash::scaleMask(SplashImageMaskSource src, void *srcData, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight) { + int scaledWidth, int scaledHeight, + GBool interpolate) { SplashBitmap *dest; dest = new SplashBitmap(scaledWidth, scaledHeight, 1, splashModeMono8, @@ -2744,8 +3790,13 @@ SplashBitmap *Splash::scaleMask(SplashIm scaleMaskYuXd(src, srcData, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } else { - scaleMaskYuXu(src, srcData, srcWidth, srcHeight, - scaledWidth, scaledHeight, dest); + if (interpolate) { + scaleMaskYuXuI(src, srcData, srcWidth, srcHeight, + scaledWidth, scaledHeight, dest); + } else { + scaleMaskYuXu(src, srcData, srcWidth, srcHeight, + scaledWidth, scaledHeight, dest); + } } } return dest; @@ -3057,60 +4108,158 @@ void Splash::scaleMaskYuXu(SplashImageMa gfree(lineBuf); } +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; + } + + // 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 * lineBuf0[xSrc0] + + ((SplashCoord)1 - xs) * lineBuf0[xSrc1]) * 255); + lineBuf1[x] = (Guchar)(int) + ((xs * lineBuf1[xSrc0] + + ((SplashCoord)1 - xs) * lineBuf1[xSrc1]) * 255); + } + + 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 * lineBuf1[xSrc0] + + ((SplashCoord)1 - xs) * lineBuf1[xSrc1]) * 255); + } + + ++yBuf; + } + + // do the vertical interpolation + for (x = 0; x < scaledWidth; ++x) { + + pix = (Guchar)(int)(ys * lineBuf0[x] + + ((SplashCoord)1 - ys) * lineBuf1[x]); + + // store the pixel + *destPtr++ = pix; + } + } + + gfree(lineBuf1); + gfree(lineBuf0); +} + void Splash::blitMask(SplashBitmap *src, int xDest, int yDest, SplashClipResult clipRes) { SplashPipe pipe; - Guchar *p; - int w, h, x, y; + int w, h, x0, x1, y0, y1, y, t; w = src->getWidth(); h = src->getHeight(); - if (vectorAntialias && clipRes != splashClipAllInside) { - pipeInit(&pipe, xDest, yDest, state->fillPattern, NULL, - (Guchar)splashRound(state->fillAlpha * 255), gTrue, gFalse); - drawAAPixelInit(); - p = src->getDataPtr(); + pipeInit(&pipe, state->fillPattern, + (Guchar)splashRound(state->fillAlpha * 255), + gTrue, gFalse); + if (clipRes == splashClipAllInside) { for (y = 0; y < h; ++y) { - for (x = 0; x < w; ++x) { - pipe.shape = *p++; - drawAAPixel(&pipe, xDest + x, yDest + y); - } + (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y, + src->getDataPtr() + y * w, NULL); } } else { - pipeInit(&pipe, xDest, yDest, state->fillPattern, NULL, - (Guchar)splashRound(state->fillAlpha * 255), gTrue, gFalse); - p = src->getDataPtr(); - if (clipRes == splashClipAllInside) { - for (y = 0; y < h; ++y) { - pipeSetXY(&pipe, xDest, yDest + y); - for (x = 0; x < w; ++x) { - if (*p) { - pipe.shape = *p; - (this->*pipe.run)(&pipe); - } else { - pipeIncX(&pipe); - } - ++p; - } - } - updateModX(xDest); - updateModX(xDest + w - 1); - updateModY(yDest); - updateModY(yDest + h - 1); - } else { - for (y = 0; y < h; ++y) { - pipeSetXY(&pipe, xDest, yDest + y); - for (x = 0; x < w; ++x) { - if (*p && state->clip->test(xDest + x, yDest + y)) { - pipe.shape = *p; - (this->*pipe.run)(&pipe); - updateModX(xDest + x); - updateModY(yDest + y); - } else { - pipeIncX(&pipe); - } - ++p; + 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->getDataPtr() + (y - yDest) * 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); } } } @@ -3118,11 +4267,13 @@ void Splash::blitMask(SplashBitmap *src, SplashError Splash::drawImage(SplashImageSource src, void *srcData, SplashColorMode srcMode, GBool srcAlpha, - int w, int h, SplashCoord *mat) { + 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; @@ -3142,11 +4293,8 @@ SplashError Splash::drawImage(SplashImag nComps = 1; break; case splashModeRGB8: - ok = srcMode == splashModeRGB8; - nComps = 3; - break; case splashModeBGR8: - ok = srcMode == splashModeBGR8; + ok = srcMode == splashModeRGB8; nComps = 3; break; #if SPLASH_CMYK @@ -3168,82 +4316,315 @@ SplashError Splash::drawImage(SplashImag return splashErrSingularMatrix; } - minorAxisZero = mat[1] == 0 && mat[2] == 0; + 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 (wSize > 2 * w && hSize > 2 * h && wSize * hSize > 1000000) { + upscaleImage(src, srcData, srcMode, nComps, srcAlpha, + w, h, mat, interpolate); // scaling only - if (mat[0] > 0 && minorAxisZero && mat[3] > 0) { - x0 = imgCoordMungeLower(mat[4]); - y0 = imgCoordMungeLower(mat[5]); - x1 = imgCoordMungeUpper(mat[0] + mat[4]); - y1 = imgCoordMungeUpper(mat[3] + mat[5]); - // make sure narrow images cover at least one pixel - if (x0 == x1) { - ++x1; + } 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(src, srcData, srcMode, nComps, srcAlpha, w, h, + scaledWidth, scaledHeight, interpolate); + blitImage(scaledImg, srcAlpha, x0, y0, clipRes); + delete scaledImg; + } + + // 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(src, srcData, srcMode, nComps, srcAlpha, w, h, + scaledWidth, scaledHeight, interpolate); + vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); + blitImage(scaledImg, srcAlpha, x0, y0, clipRes); + delete scaledImg; } - if (y0 == y1) { - ++y1; + + // 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(src, srcData, srcMode, nComps, srcAlpha, w, h, + scaledWidth, scaledHeight, interpolate); + horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); + blitImage(scaledImg, srcAlpha, x0, y0, clipRes); + delete scaledImg; } - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1); + + // 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(src, srcData, srcMode, nComps, srcAlpha, w, h, - scaledWidth, scaledHeight); + scaledWidth, scaledHeight, interpolate); + vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); + horizFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); blitImage(scaledImg, srcAlpha, x0, y0, clipRes); delete scaledImg; } - - // scaling plus vertical flip - } else if (mat[0] > 0 && minorAxisZero && mat[3] < 0) { - x0 = imgCoordMungeLower(mat[4]); - y0 = imgCoordMungeLower(mat[3] + mat[5]); - x1 = imgCoordMungeUpper(mat[0] + mat[4]); - y1 = imgCoordMungeUpper(mat[5]); - if (x0 == x1) { - if (mat[4] + mat[0] * 0.5 < x0) { - --x0; + + // all other cases + } else { + arbitraryTransformImage(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; + int rowSize, 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 ((tt = state->clip->getXMaxI(state->strokeAdjust) + 1) < xMaxI) { + xMaxI = tt; + } + if ((tt = state->clip->getYMinI(state->strokeAdjust)) > yMinI) { + yMinI = tt; + } + if ((tt = state->clip->getYMaxI(state->strokeAdjust) + 1) < yMaxI) { + yMaxI = tt; + } + } + + // 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)gmallocn(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); + } + } else { + unscaledAlpha = NULL; + for (y = 0, p = unscaledImage; y < srcHeight; ++y, p += srcWidth * nComps) { + (*src)(srcData, p, NULL); + } + } + + // draw it + pixelBuf = (SplashColorPtr)gmallocn(xMaxI - xMinI, nComps); + pipeInit(&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 * srcWidth + x0) * nComps]; + q01 = &unscaledImage[(y0 * srcWidth + x1) * nComps]; + q10 = &unscaledImage[(y1 * srcWidth + x0) * nComps]; + q11 = &unscaledImage[(y1 * srcWidth + x1) * nComps]; + for (i = 0; i < nComps; ++i) { + pix0 = ((SplashCoord)1 - sx) * *q00++ + sx * *q01++; + pix1 = ((SplashCoord)1 - sx) * *q10++ + sx * *q11++; + *p++ = (Guchar)splashRound(((SplashCoord)1 - sy) * pix0 + + sy * pix1); + } + if (srcAlpha) { + pix0 = ((SplashCoord)1 - sx) * unscaledAlpha[y0 * srcWidth + x0] + + sx * unscaledAlpha[y0 * srcWidth + x1]; + pix1 = ((SplashCoord)1 - sx) * unscaledAlpha[y1 * srcWidth + x0] + + sx * 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 { - ++x1; + x0 = splashFloor(ix); + y0 = splashFloor(iy); + if (x0 >= 0 && x0 < srcWidth && y0 >= 0 && y0 < srcHeight) { + q = &unscaledImage[(y0 * srcWidth + 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 (y0 == y1) { - if (mat[5] + mat[1] * 0.5 < y0) { - --y0; + if (clipRes != splashClipAllInside) { + if (vectorAntialias) { + state->clip->clipSpan(scanBuf, y, xMinI, xMaxI - 1, + state->strokeAdjust); } else { - ++y1; + state->clip->clipSpanBinary(scanBuf, y, xMinI, xMaxI - 1, + state->strokeAdjust); } } - clipRes = state->clip->testRect(x0, y0, x1 - 1, y1 - 1); - opClipRes = clipRes; - if (clipRes != splashClipAllOutside) { - scaledWidth = x1 - x0; - scaledHeight = y1 - y0; - scaledImg = scaleImage(src, srcData, srcMode, nComps, srcAlpha, w, h, - scaledWidth, scaledHeight); - vertFlipImage(scaledImg, scaledWidth, scaledHeight, nComps); - blitImage(scaledImg, srcAlpha, x0, y0, clipRes); - delete scaledImg; - } - - // all other cases - } else { - arbitraryTransformImage(src, srcData, srcMode, nComps, srcAlpha, - w, h, mat); + (this->*pipe.run)(&pipe, xMinI, xMaxI - 1, y, scanBuf + xMinI, pixelBuf); } - return splashOk; + gfree(pixelBuf); + gfree(unscaledImage); + gfree(unscaledAlpha); } void Splash::arbitraryTransformImage(SplashImageSource src, void *srcData, SplashColorMode srcMode, int nComps, GBool srcAlpha, int srcWidth, int srcHeight, - SplashCoord *mat) { + SplashCoord *mat, GBool interpolate) { SplashBitmap *scaledImg; - SplashClipResult clipRes, clipRes2; + SplashClipResult clipRes; SplashPipe pipe; - SplashColor pixel; + SplashColorPtr pixelBuf; int scaledWidth, scaledHeight, t0, t1; SplashCoord r00, r01, r10, r11, det, ir00, ir01, ir10, ir11; SplashCoord vx[4], vy[4]; @@ -3259,29 +4640,26 @@ void Splash::arbitraryTransformImage(Spl vx[3] = mat[0] + mat[4]; vy[3] = mat[1] + mat[5]; // clipping - xMin = imgCoordMungeLower(vx[0]); - xMax = imgCoordMungeUpper(vx[0]); - yMin = imgCoordMungeLower(vy[0]); - yMax = imgCoordMungeUpper(vy[0]); + xMin = splashRound(vx[0]); + xMax = splashRound(vx[0]); + yMin = splashRound(vy[0]); + yMax = splashRound(vy[0]); for (i = 1; i < 4; ++i) { - t0 = imgCoordMungeLower(vx[i]); + t0 = splashRound(vx[i]); if (t0 < xMin) { xMin = t0; - } - t0 = imgCoordMungeUpper(vx[i]); - if (t0 > xMax) { + } else if (t0 > xMax) { xMax = t0; } - t1 = imgCoordMungeLower(vy[i]); + t1 = splashRound(vy[i]); if (t1 < yMin) { yMin = t1; - } - t1 = imgCoordMungeUpper(vy[i]); - if (t1 > yMax) { + } else if (t1 > yMax) { yMax = t1; } } - clipRes = state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1); + clipRes = state->clip->testRect(xMin, yMin, xMax - 1, yMax - 1, + state->strokeAdjust); opClipRes = clipRes; if (clipRes == splashClipAllOutside) { return; @@ -3289,25 +4667,25 @@ void Splash::arbitraryTransformImage(Spl // compute the scale factors if (mat[0] >= 0) { - t0 = imgCoordMungeUpper(mat[0] + mat[4]) - imgCoordMungeLower(mat[4]); + t0 = splashRound(mat[0] + mat[4]) - splashRound(mat[4]); } else { - t0 = imgCoordMungeUpper(mat[4]) - imgCoordMungeLower(mat[0] + mat[4]); + t0 = splashRound(mat[4]) - splashRound(mat[0] + mat[4]); } if (mat[1] >= 0) { - t1 = imgCoordMungeUpper(mat[1] + mat[5]) - imgCoordMungeLower(mat[5]); + t1 = splashRound(mat[1] + mat[5]) - splashRound(mat[5]); } else { - t1 = imgCoordMungeUpper(mat[5]) - imgCoordMungeLower(mat[1] + mat[5]); + t1 = splashRound(mat[5]) - splashRound(mat[1] + mat[5]); } scaledWidth = t0 > t1 ? t0 : t1; if (mat[2] >= 0) { - t0 = imgCoordMungeUpper(mat[2] + mat[4]) - imgCoordMungeLower(mat[4]); + t0 = splashRound(mat[2] + mat[4]) - splashRound(mat[4]); } else { - t0 = imgCoordMungeUpper(mat[4]) - imgCoordMungeLower(mat[2] + mat[4]); + t0 = splashRound(mat[4]) - splashRound(mat[2] + mat[4]); } if (mat[3] >= 0) { - t1 = imgCoordMungeUpper(mat[3] + mat[5]) - imgCoordMungeLower(mat[5]); + t1 = splashRound(mat[3] + mat[5]) - splashRound(mat[5]); } else { - t1 = imgCoordMungeUpper(mat[5]) - imgCoordMungeLower(mat[3] + mat[5]); + t1 = splashRound(mat[5]) - splashRound(mat[3] + mat[5]); } scaledHeight = t0 > t1 ? t0 : t1; if (scaledWidth == 0) { @@ -3334,7 +4712,8 @@ void Splash::arbitraryTransformImage(Spl // scale the input image scaledImg = scaleImage(src, srcData, srcMode, nComps, srcAlpha, - srcWidth, srcHeight, scaledWidth, scaledHeight); + srcWidth, srcHeight, scaledWidth, scaledHeight, + interpolate); // construct the three sections i = 0; @@ -3354,8 +4733,8 @@ void Splash::arbitraryTransformImage(Spl i = (i-1) & 3; } if (splashAbs(vy[i] - vy[(i+1) & 3]) <= 0.000001) { - section[0].y0 = imgCoordMungeLower(vy[i]); - section[0].y1 = imgCoordMungeUpper(vy[(i+2) & 3]) - 1; + 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; @@ -3369,8 +4748,8 @@ void Splash::arbitraryTransformImage(Spl } nSections = 1; } else { - section[0].y0 = imgCoordMungeLower(vy[i]); - section[2].y1 = imgCoordMungeUpper(vy[(i+2) & 3]) - 1; + 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]) { @@ -3381,8 +4760,8 @@ void Splash::arbitraryTransformImage(Spl section[0].ib1 = section[2].ib0 = (i+1) & 3; } if (vy[(i+1) & 3] < vy[(i+3) & 3]) { - section[1].y0 = imgCoordMungeLower(vy[(i+1) & 3]); - section[2].y0 = imgCoordMungeUpper(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; @@ -3395,8 +4774,8 @@ void Splash::arbitraryTransformImage(Spl section[1].ib1 = (i+2) & 3; } } else { - section[1].y0 = imgCoordMungeLower(vy[(i+3) & 3]); - section[2].y0 = imgCoordMungeUpper(vy[(i+1) & 3]); + 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; @@ -3429,13 +4808,9 @@ void Splash::arbitraryTransformImage(Spl } // initialize the pixel pipe - pipeInit(&pipe, 0, 0, NULL, pixel, + pipeInit(&pipe, NULL, (Guchar)splashRound(state->fillAlpha * 255), - srcAlpha || (vectorAntialias && clipRes != splashClipAllInside), - gFalse); - if (vectorAntialias) { - drawAAPixelInit(); - } + gTrue, gFalse); // make sure narrow images cover at least one pixel if (nSections == 1) { @@ -3450,24 +4825,46 @@ void Splash::arbitraryTransformImage(Spl } } + 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 = imgCoordMungeLower(section[i].xa0 + - ((SplashCoord)y + 0.5 - section[i].ya0) * - section[i].dxdya); - xb = imgCoordMungeUpper(section[i].xb0 + - ((SplashCoord)y + 0.5 - section[i].yb0) * - section[i].dxdyb); + 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) { - clipRes2 = state->clip->testSpan(xa, xb - 1, y); - } else { - clipRes2 = clipRes; + 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 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 + @@ -3486,21 +4883,19 @@ void Splash::arbitraryTransformImage(Spl } else if (yy >= scaledHeight) { yy = scaledHeight - 1; } - scaledImg->getPixel(xx, yy, pixel); + // get the color + scaledImg->getPixel(xx, yy, pixelBuf + (x - xa) * bitmapComps); + // apply alpha if (srcAlpha) { - pipe.shape = scaledImg->alpha[yy * scaledWidth + xx]; - } else { - pipe.shape = 255; - } - if (vectorAntialias && clipRes2 != splashClipAllInside) { - drawAAPixel(&pipe, x, y); - } else { - drawPixel(&pipe, x, y, clipRes2 == splashClipAllInside); + scanBuf[x] = div255(scanBuf[x] * + scaledImg->alpha[yy * scaledWidth + xx]); } } + (this->*pipe.run)(&pipe, xa, xb - 1, y, scanBuf + xa, pixelBuf); } } + gfree(pixelBuf); delete scaledImg; } @@ -3508,7 +4903,8 @@ void Splash::arbitraryTransformImage(Spl SplashBitmap *Splash::scaleImage(SplashImageSource src, void *srcData, SplashColorMode srcMode, int nComps, GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight) { + int scaledWidth, int scaledHeight, + GBool interpolate) { SplashBitmap *dest; dest = new SplashBitmap(scaledWidth, scaledHeight, 1, srcMode, srcAlpha); @@ -3525,8 +4921,13 @@ SplashBitmap *Splash::scaleImage(SplashI scaleImageYuXd(src, srcData, srcMode, nComps, srcAlpha, srcWidth, srcHeight, scaledWidth, scaledHeight, dest); } else { - scaleImageYuXu(src, srcData, srcMode, nComps, srcAlpha, - srcWidth, srcHeight, scaledWidth, scaledHeight, dest); + if (interpolate) { + scaleImageYuXuI(src, srcData, srcMode, nComps, srcAlpha, + srcWidth, srcHeight, scaledWidth, scaledHeight, dest); + } else { + scaleImageYuXu(src, srcData, srcMode, nComps, srcAlpha, + srcWidth, srcHeight, scaledWidth, scaledHeight, dest); + } } } return dest; @@ -3654,27 +5055,6 @@ void Splash::scaleImageYdXd(SplashImageS *destPtr++ = (Guchar)pix2; break; - case splashModeBGR8: - - // 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) >> 23; - pix1 = (pix1 * d) >> 23; - pix2 = (pix2 * d) >> 23; - - // store the pixel - *destPtr++ = (Guchar)pix2; - *destPtr++ = (Guchar)pix1; - *destPtr++ = (Guchar)pix0; - break; - #if SPLASH_CMYK case splashModeCMYK8: @@ -3703,6 +5083,7 @@ void Splash::scaleImageYdXd(SplashImageS case splashModeMono1: // mono1 is not allowed + case splashModeBGR8: // bgr8 is not allowed default: break; } @@ -3812,8 +5193,6 @@ void Splash::scaleImageYdXu(SplashImageS // store the pixel switch (srcMode) { - case splashModeMono1: // mono1 is not allowed - break; case splashModeMono8: for (i = 0; i < xStep; ++i) { *destPtr++ = (Guchar)pix[0]; @@ -3826,13 +5205,6 @@ void Splash::scaleImageYdXu(SplashImageS *destPtr++ = (Guchar)pix[2]; } break; - case splashModeBGR8: - for (i = 0; i < xStep; ++i) { - *destPtr++ = (Guchar)pix[2]; - *destPtr++ = (Guchar)pix[1]; - *destPtr++ = (Guchar)pix[0]; - } - break; #if SPLASH_CMYK case splashModeCMYK8: for (i = 0; i < xStep; ++i) { @@ -3843,6 +5215,10 @@ void Splash::scaleImageYdXu(SplashImageS } break; #endif + case splashModeMono1: // mono1 is not allowed + case splashModeBGR8: // BGR8 is not allowed + default: + break; } // process alpha @@ -3942,8 +5318,6 @@ void Splash::scaleImageYuXd(SplashImageS // store the pixel switch (srcMode) { - case splashModeMono1: // mono1 is not allowed - break; case splashModeMono8: for (i = 0; i < yStep; ++i) { destPtr = destPtr0 + (i * scaledWidth + x) * nComps; @@ -3958,14 +5332,6 @@ void Splash::scaleImageYuXd(SplashImageS *destPtr++ = (Guchar)pix[2]; } break; - case splashModeBGR8: - for (i = 0; i < yStep; ++i) { - destPtr = destPtr0 + (i * scaledWidth + x) * nComps; - *destPtr++ = (Guchar)pix[2]; - *destPtr++ = (Guchar)pix[1]; - *destPtr++ = (Guchar)pix[0]; - } - break; #if SPLASH_CMYK case splashModeCMYK8: for (i = 0; i < yStep; ++i) { @@ -3977,6 +5343,10 @@ void Splash::scaleImageYuXd(SplashImageS } break; #endif + case splashModeMono1: // mono1 is not allowed + case splashModeBGR8: // BGR8 is not allowed + default: + break; } // process alpha @@ -4071,8 +5441,6 @@ void Splash::scaleImageYuXu(SplashImageS // store the pixel switch (srcMode) { - case splashModeMono1: // mono1 is not allowed - break; case splashModeMono8: for (i = 0; i < yStep; ++i) { for (j = 0; j < xStep; ++j) { @@ -4091,16 +5459,6 @@ void Splash::scaleImageYuXu(SplashImageS } } break; - case splashModeBGR8: - for (i = 0; i < yStep; ++i) { - for (j = 0; j < xStep; ++j) { - destPtr = destPtr0 + (i * scaledWidth + xx + j) * nComps; - *destPtr++ = (Guchar)pix[2]; - *destPtr++ = (Guchar)pix[1]; - *destPtr++ = (Guchar)pix[0]; - } - } - break; #if SPLASH_CMYK case splashModeCMYK8: for (i = 0; i < yStep; ++i) { @@ -4114,6 +5472,10 @@ void Splash::scaleImageYuXu(SplashImageS } break; #endif + case splashModeMono1: // mono1 is not allowed + case splashModeBGR8: // BGR8 is not allowed + default: + break; } // process alpha @@ -4140,6 +5502,177 @@ void Splash::scaleImageYuXu(SplashImageS gfree(lineBuf); } +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; + + // 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); + } + yBuf = 0; + } + + // 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; + } + for (i = 0; i < nComps; ++i) { + lineBuf0[x*nComps+i] = (Guchar)(int) + (xs * lineBuf0[xSrc0*nComps+i] + + ((SplashCoord)1 - xs) * lineBuf0[xSrc1*nComps+i]); + lineBuf1[x*nComps+i] = (Guchar)(int) + (xs * lineBuf1[xSrc0*nComps+i] + + ((SplashCoord)1 - xs) * lineBuf1[xSrc1*nComps+i]); + } + if (srcAlpha) { + alphaLineBuf0[x] = (Guchar)(int) + (xs * alphaLineBuf0[xSrc0] + + ((SplashCoord)1 - xs) * alphaLineBuf0[xSrc1]); + alphaLineBuf1[x] = (Guchar)(int) + (xs * alphaLineBuf1[xSrc0] + + ((SplashCoord)1 - xs) * alphaLineBuf1[xSrc1]); + } + } + + 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 * lineBuf1[xSrc0*nComps+i] + + ((SplashCoord)1 - xs) * lineBuf1[xSrc1*nComps+i]); + } + if (srcAlpha) { + alphaLineBuf1[x] = + (Guchar)(int)(xs * alphaLineBuf1[xSrc0] + + ((SplashCoord)1 - xs) * alphaLineBuf1[xSrc1]); + } + } + + ++yBuf; + } + + // do the vertical interpolation + for (x = 0; x < scaledWidth; ++x) { + + for (i = 0; i < nComps; ++i) { + pix[i] = (Guchar)(int)(ys * lineBuf0[x*nComps+i] + + ((SplashCoord)1 - ys) * 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 * alphaLineBuf0[x] + + ((SplashCoord)1 - ys) * alphaLineBuf1[x]); + } + } + } + + gfree(alphaLineBuf1); + gfree(alphaLineBuf0); + gfree(lineBuf1); + gfree(lineBuf0); +} + void Splash::vertFlipImage(SplashBitmap *img, int width, int height, int nComps) { Guchar *lineBuf; @@ -4167,12 +5700,43 @@ void Splash::vertFlipImage(SplashBitmap 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 (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--; + } + } + } + gfree(lineBuf); +} + void Splash::blitImage(SplashBitmap *src, GBool srcAlpha, int xDest, int yDest, SplashClipResult clipRes) { SplashPipe pipe; - SplashColor pixel; - Guchar *ap; - int w, h, x0, y0, x1, y1, x, y; + int w, h, x0, y0, x1, y1, y; // split the image into clipped and unclipped regions w = src->getWidth(); @@ -4200,8 +5764,8 @@ void Splash::blitImage(SplashBitmap *src x1 = x0; } if ((y1 = splashFloor(state->clip->getYMax()) - yDest) > h) { - y1 = h; - } + y1 = h; + } if (y1 < y0) { y1 = y0; } @@ -4210,31 +5774,24 @@ void Splash::blitImage(SplashBitmap *src // draw the unclipped region if (x0 < w && y0 < h && x0 < x1 && y0 < y1) { - pipeInit(&pipe, xDest + x0, yDest + y0, NULL, pixel, - (Guchar)splashRound(state->fillAlpha * 255), srcAlpha, gFalse); + pipeInit(&pipe, NULL, + (Guchar)splashRound(state->fillAlpha * 255), + srcAlpha, gFalse); if (srcAlpha) { for (y = y0; y < y1; ++y) { - pipeSetXY(&pipe, xDest + x0, yDest + y); - ap = src->getAlphaPtr() + y * w + x0; - for (x = x0; x < x1; ++x) { - src->getPixel(x, y, pixel); - pipe.shape = *ap++; - (this->*pipe.run)(&pipe); - } + (this->*pipe.run)(&pipe, xDest + x0, xDest + x1 - 1, yDest + y, + src->getAlphaPtr() + y * w + x0, + src->getDataPtr() + y * src->getRowSize() + + x0 * bitmapComps); } } else { for (y = y0; y < y1; ++y) { - pipeSetXY(&pipe, xDest + x0, yDest + y); - for (x = x0; x < x1; ++x) { - src->getPixel(x, y, pixel); - (this->*pipe.run)(&pipe); - } + (this->*pipe.run)(&pipe, xDest + x0, xDest + x1 - 1, yDest + y, + NULL, + src->getDataPtr() + y * src->getRowSize() + + x0 * bitmapComps); } } - updateModX(xDest + x0); - updateModX(xDest + x1 - 1); - updateModY(yDest + y0); - updateModY(yDest + y1 - 1); } // draw the clipped regions @@ -4257,66 +5814,62 @@ void Splash::blitImageClipped(SplashBitm int xSrc, int ySrc, int xDest, int yDest, int w, int h) { SplashPipe pipe; - SplashColor pixel; - Guchar *ap; - int x, y; + int y; - if (vectorAntialias) { - pipeInit(&pipe, xDest, yDest, NULL, pixel, - (Guchar)splashRound(state->fillAlpha * 255), gTrue, gFalse); - drawAAPixelInit(); - if (srcAlpha) { - for (y = 0; y < h; ++y) { - ap = src->getAlphaPtr() + (ySrc + y) * src->getWidth() + xSrc; - for (x = 0; x < w; ++x) { - src->getPixel(xSrc + x, ySrc + y, pixel); - pipe.shape = *ap++; - drawAAPixel(&pipe, xDest + x, yDest + y); - } - } - } else { - for (y = 0; y < h; ++y) { - for (x = 0; x < w; ++x) { - src->getPixel(xSrc + x, ySrc + y, pixel); - pipe.shape = 255; - drawAAPixel(&pipe, xDest + x, yDest + y); - } + if (xDest < 0) { + xSrc -= xDest; + w += xDest; + xDest = 0; + } + if (xDest + w > bitmap->width) { + w = bitmap->width - xDest; + } + if (yDest < 0) { + ySrc -= yDest; + h += yDest; + yDest = 0; + } + if (yDest + h > bitmap->height) { + h = bitmap->height - yDest; + } + if (w <= 0 || h <= 0) { + return; + } + + pipeInit(&pipe, NULL, + (Guchar)splashRound(state->fillAlpha * 255), + gTrue, gFalse); + if (srcAlpha) { + for (y = 0; y < h; ++y) { + memcpy(scanBuf + xDest, + src->getAlphaPtr() + (ySrc + y) * src->getWidth() + 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->getDataPtr() + (ySrc + y) * src->getRowSize() + + xSrc * bitmapComps); } } else { - pipeInit(&pipe, xDest, yDest, NULL, pixel, - (Guchar)splashRound(state->fillAlpha * 255), srcAlpha, gFalse); - if (srcAlpha) { - for (y = 0; y < h; ++y) { - ap = src->getAlphaPtr() + (ySrc + y) * src->getWidth() + xSrc; - pipeSetXY(&pipe, xDest, yDest + y); - for (x = 0; x < w; ++x) { - if (state->clip->test(xDest + x, yDest + y)) { - src->getPixel(xSrc + x, ySrc + y, pixel); - pipe.shape = *ap++; - (this->*pipe.run)(&pipe); - updateModX(xDest + x); - updateModY(yDest + y); - } else { - pipeIncX(&pipe); - ++ap; - } - } - } - } else { - for (y = 0; y < h; ++y) { - pipeSetXY(&pipe, xDest, yDest + y); - for (x = 0; x < w; ++x) { - if (state->clip->test(xDest + x, yDest + y)) { - src->getPixel(xSrc + x, ySrc + y, pixel); - (this->*pipe.run)(&pipe); - updateModX(xDest + x); - updateModY(yDest + y); - } else { - pipeIncX(&pipe); - } - } + 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->getDataPtr() + (ySrc + y) * src->getRowSize() + + xSrc * bitmapComps); } } } @@ -4325,82 +5878,82 @@ SplashError Splash::composite(SplashBitm int xDest, int yDest, int w, int h, GBool noClip, GBool nonIsolated) { SplashPipe pipe; - SplashColor pixel; - Guchar alpha; - Guchar *ap; - int x, y; + int x0, x1, y0, y1, y, t; if (src->mode != bitmap->mode) { return splashErrModeMismatch; } - if (src->alpha) { - pipeInit(&pipe, xDest, yDest, NULL, pixel, - (Guchar)splashRound(state->fillAlpha * 255), gTrue, nonIsolated); - if (noClip) { + pipeInit(&pipe, NULL, + (Guchar)splashRound(state->fillAlpha * 255), + !noClip || src->alpha != NULL, nonIsolated); + if (noClip) { + if (src->alpha) { for (y = 0; y < h; ++y) { - pipeSetXY(&pipe, xDest, yDest + y); - ap = src->getAlphaPtr() + (ySrc + y) * src->getWidth() + xSrc; - for (x = 0; x < w; ++x) { - src->getPixel(xSrc + x, ySrc + y, pixel); - alpha = *ap++; - // this uses shape instead of alpha, which isn't technically - // correct, but works out the same - pipe.shape = alpha; - (this->*pipe.run)(&pipe); - } + // 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->getAlphaPtr() + + (ySrc + y) * src->getWidth() + xSrc, + src->getDataPtr() + (ySrc + y) * src->getRowSize() + + xSrc * bitmapComps); } - updateModX(xDest); - updateModX(xDest + w - 1); - updateModY(yDest); - updateModY(yDest + h - 1); } else { for (y = 0; y < h; ++y) { - pipeSetXY(&pipe, xDest, yDest + y); - ap = src->getAlphaPtr() + (ySrc + y) * src->getWidth() + xSrc; - for (x = 0; x < w; ++x) { - src->getPixel(xSrc + x, ySrc + y, pixel); - alpha = *ap++; - if (state->clip->test(xDest + x, yDest + y)) { - // this uses shape instead of alpha, which isn't technically - // correct, but works out the same - pipe.shape = alpha; - (this->*pipe.run)(&pipe); - updateModX(xDest + x); - updateModY(yDest + y); - } else { - pipeIncX(&pipe); - } - } + (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y, + NULL, + src->getDataPtr() + (ySrc + y) * src->getRowSize() + + xSrc * bitmapComps); } } } else { - pipeInit(&pipe, xDest, yDest, NULL, pixel, - (Guchar)splashRound(state->fillAlpha * 255), gFalse, nonIsolated); - if (noClip) { - for (y = 0; y < h; ++y) { - pipeSetXY(&pipe, xDest, yDest + y); - for (x = 0; x < w; ++x) { - src->getPixel(xSrc + x, ySrc + y, pixel); - (this->*pipe.run)(&pipe); + 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->getAlphaPtr() + (ySrc + y - yDest) * src->getWidth() + + (xSrc + x0 - xDest), + x1 - x0); + if (!state->clip->clipSpanBinary(scanBuf, y, x0, x1 - 1, + state->strokeAdjust)) { + continue; + } + // 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->getDataPtr() + + (ySrc + y - yDest) * src->getRowSize() + + (xSrc + x0 - xDest) * bitmapComps); } - } - updateModX(xDest); - updateModX(xDest + w - 1); - updateModY(yDest); - updateModY(yDest + h - 1); - } else { - for (y = 0; y < h; ++y) { - pipeSetXY(&pipe, xDest, yDest + y); - for (x = 0; x < w; ++x) { - src->getPixel(xSrc + x, ySrc + y, pixel); - if (state->clip->test(xDest + x, yDest + y)) { - (this->*pipe.run)(&pipe); - updateModX(xDest + x); - updateModY(yDest + y); - } else { - pipeIncX(&pipe); + } else { + for (y = y0; y < y1; ++y) { + memset(scanBuf + x0, 0xff, x1 - x0); + if (!state->clip->clipSpanBinary(scanBuf, y, x0, x1 - 1, + state->strokeAdjust)) { + continue; } + (this->*pipe.run)(&pipe, xDest, xDest + w - 1, yDest + y, + scanBuf + x0, + src->getDataPtr() + + (ySrc + y - yDest) * src->getRowSize() + + (xSrc - xDest) * bitmapComps); } } } @@ -4535,9 +6088,7 @@ SplashError Splash::blitTransparent(Spla for (y = 0; y < h; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + xDest]; q = &src->data[(ySrc + y) * src->rowSize + xSrc]; - for (x = 0; x < w; ++x) { - *p++ = *q++; - } + memcpy(p, q, w); } break; case splashModeRGB8: @@ -4545,11 +6096,7 @@ SplashError Splash::blitTransparent(Spla for (y = 0; y < h; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + 3 * xDest]; q = &src->data[(ySrc + y) * src->rowSize + 3 * xSrc]; - for (x = 0; x < w; ++x) { - *p++ = *q++; - *p++ = *q++; - *p++ = *q++; - } + memcpy(p, q, 3 * w); } break; #if SPLASH_CMYK @@ -4557,12 +6104,7 @@ SplashError Splash::blitTransparent(Spla for (y = 0; y < h; ++y) { p = &bitmap->data[(yDest + y) * bitmap->rowSize + 4 * xDest]; q = &src->data[(ySrc + y) * src->rowSize + 4 * xSrc]; - for (x = 0; x < w; ++x) { - *p++ = *q++; - *p++ = *q++; - *p++ = *q++; - *p++ = *q++; - } + memcpy(p, q, 4 * w); } break; #endif @@ -4571,9 +6113,7 @@ SplashError Splash::blitTransparent(Spla if (bitmap->alpha) { for (y = 0; y < h; ++y) { q = &bitmap->alpha[(yDest + y) * bitmap->width + xDest]; - for (x = 0; x < w; ++x) { - *q++ = 0x00; - } + memset(q, 0, w); } } @@ -5002,11 +6542,9 @@ void Splash::dumpXPath(SplashXPath *path int i; for (i = 0; i < path->length; ++i) { - printf(" %4d: x0=%8.2f y0=%8.2f x1=%8.2f y1=%8.2f %s%s%s\n", + printf(" %4d: x0=%8.2f y0=%8.2f x1=%8.2f y1=%8.2f count=%d\n", i, (double)path->segs[i].x0, (double)path->segs[i].y0, (double)path->segs[i].x1, (double)path->segs[i].y1, - (path->segs[i].flags & splashXPathHoriz) ? "H" : " ", - (path->segs[i].flags & splashXPathVert) ? "V" : " ", - (path->segs[i].flags & splashXPathFlip) ? "P" : " "); + path->segs[i].count); } } diff -uNrp xpdf-3.03/splash/SplashClip.cc xpdf-3.04/splash/SplashClip.cc --- xpdf-3.03/splash/SplashClip.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashClip.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashClip.cc // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #include @@ -17,52 +19,52 @@ #include "SplashPath.h" #include "SplashXPath.h" #include "SplashXPathScanner.h" -#include "SplashBitmap.h" #include "SplashClip.h" //------------------------------------------------------------------------ -// SplashClip.flags -//------------------------------------------------------------------------ -#define splashClipEO 0x01 // use even-odd rule +// Compute x * y / 255, where x and y are in [0, 255]. +static inline Guchar mul255(Guchar x, Guchar y) { + int z; + + z = (int)x * (int)y; + return (Guchar)((z + (z >> 8) + 0x80) >> 8); +} //------------------------------------------------------------------------ // SplashClip //------------------------------------------------------------------------ -SplashClip::SplashClip(SplashCoord x0, SplashCoord y0, - SplashCoord x1, SplashCoord y1, - GBool antialiasA) { - antialias = antialiasA; - if (x0 < x1) { - xMin = x0; - xMax = x1; - } else { - xMin = x1; - xMax = x0; - } - if (y0 < y1) { - yMin = y0; - yMax = y1; - } else { - yMin = y1; - yMax = y0; - } - xMinI = splashFloor(xMin); - yMinI = splashFloor(yMin); - xMaxI = splashCeil(xMax) - 1; - yMaxI = splashCeil(yMax) - 1; +SplashClip::SplashClip(int hardXMinA, int hardYMinA, + int hardXMaxA, int hardYMaxA) { + int w; + + hardXMin = hardXMinA; + hardYMin = hardYMinA; + hardXMax = hardXMaxA; + hardYMax = hardYMaxA; + xMin = hardXMin; + yMin = hardYMin; + xMax = hardXMax; + yMax = hardYMax; + intBoundsValid = gFalse; paths = NULL; - flags = NULL; + eo = NULL; scanners = NULL; length = size = 0; + if ((w = hardXMax + 1) <= 0) { + w = 1; + } + buf = (Guchar *)gmalloc(w); } SplashClip::SplashClip(SplashClip *clip) { - int yMinAA, yMaxAA; - int i; + int w, i; - antialias = clip->antialias; + hardXMin = clip->hardXMin; + hardYMin = clip->hardYMin; + hardXMax = clip->hardXMax; + hardYMax = clip->hardYMax; xMin = clip->xMin; yMin = clip->yMin; xMax = clip->xMax; @@ -71,25 +73,23 @@ SplashClip::SplashClip(SplashClip *clip) yMinI = clip->yMinI; xMaxI = clip->xMaxI; yMaxI = clip->yMaxI; + intBoundsValid = clip->intBoundsValid; + intBoundsStrokeAdjust = clip->intBoundsStrokeAdjust; length = clip->length; size = clip->size; paths = (SplashXPath **)gmallocn(size, sizeof(SplashXPath *)); - flags = (Guchar *)gmallocn(size, sizeof(Guchar)); + eo = (Guchar *)gmallocn(size, sizeof(Guchar)); scanners = (SplashXPathScanner **) gmallocn(size, sizeof(SplashXPathScanner *)); for (i = 0; i < length; ++i) { paths[i] = clip->paths[i]->copy(); - flags[i] = clip->flags[i]; - if (antialias) { - yMinAA = yMinI * splashAASize; - yMaxAA = (yMaxI + 1) * splashAASize - 1; - } else { - yMinAA = yMinI; - yMaxAA = yMaxI; - } - scanners[i] = new SplashXPathScanner(paths[i], flags[i] & splashClipEO, - yMinAA, yMaxAA); + eo[i] = clip->eo[i]; + scanners[i] = new SplashXPathScanner(paths[i], eo[i], yMinI, yMaxI); + } + if ((w = splashCeil(xMax)) <= 0) { + w = 1; } + buf = (Guchar *)gmalloc(w); } SplashClip::~SplashClip() { @@ -100,8 +100,9 @@ SplashClip::~SplashClip() { delete scanners[i]; } gfree(paths); - gfree(flags); + gfree(eo); gfree(scanners); + gfree(buf); } void SplashClip::grow(int nPaths) { @@ -113,7 +114,7 @@ void SplashClip::grow(int nPaths) { size *= 2; } paths = (SplashXPath **)greallocn(paths, size, sizeof(SplashXPath *)); - flags = (Guchar *)greallocn(flags, size, sizeof(Guchar)); + eo = (Guchar *)greallocn(eo, size, sizeof(Guchar)); scanners = (SplashXPathScanner **) greallocn(scanners, size, sizeof(SplashXPathScanner *)); } @@ -121,17 +122,18 @@ void SplashClip::grow(int nPaths) { void SplashClip::resetToRect(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1) { - int i; + int w, i; for (i = 0; i < length; ++i) { delete paths[i]; delete scanners[i]; } gfree(paths); - gfree(flags); + gfree(eo); gfree(scanners); + gfree(buf); paths = NULL; - flags = NULL; + eo = NULL; scanners = NULL; length = size = 0; @@ -149,10 +151,11 @@ void SplashClip::resetToRect(SplashCoord yMin = y1; yMax = y0; } - xMinI = splashFloor(xMin); - yMinI = splashFloor(yMin); - xMaxI = splashCeil(xMax) - 1; - yMaxI = splashCeil(yMax) - 1; + intBoundsValid = gFalse; + if ((w = splashCeil(xMax)) <= 0) { + w = 1; + } + buf = (Guchar *)gmalloc(w); } SplashError SplashClip::clipToRect(SplashCoord x0, SplashCoord y0, @@ -160,240 +163,358 @@ SplashError SplashClip::clipToRect(Splas if (x0 < x1) { if (x0 > xMin) { xMin = x0; - xMinI = splashFloor(xMin); + intBoundsValid = gFalse; } if (x1 < xMax) { xMax = x1; - xMaxI = splashCeil(xMax) - 1; + intBoundsValid = gFalse; } } else { if (x1 > xMin) { xMin = x1; - xMinI = splashFloor(xMin); + intBoundsValid = gFalse; } if (x0 < xMax) { xMax = x0; - xMaxI = splashCeil(xMax) - 1; + intBoundsValid = gFalse; } } if (y0 < y1) { if (y0 > yMin) { yMin = y0; - yMinI = splashFloor(yMin); + intBoundsValid = gFalse; } if (y1 < yMax) { yMax = y1; - yMaxI = splashCeil(yMax) - 1; + intBoundsValid = gFalse; } } else { if (y1 > yMin) { yMin = y1; - yMinI = splashFloor(yMin); + intBoundsValid = gFalse; } if (y0 < yMax) { yMax = y0; - yMaxI = splashCeil(yMax) - 1; + intBoundsValid = gFalse; } } return splashOk; } SplashError SplashClip::clipToPath(SplashPath *path, SplashCoord *matrix, - SplashCoord flatness, GBool eo) { + SplashCoord flatness, GBool eoA) { SplashXPath *xPath; - int yMinAA, yMaxAA; + SplashCoord t; xPath = new SplashXPath(path, matrix, flatness, gTrue); // check for an empty path if (xPath->length == 0) { - xMax = xMin - 1; - yMax = yMin - 1; - xMaxI = splashCeil(xMax) - 1; - yMaxI = splashCeil(yMax) - 1; + xMin = yMin = 1; + xMax = yMax = 0; + intBoundsValid = gFalse; delete xPath; + return splashOk; + } // check for a rectangle - } else if (xPath->length == 4 && - ((xPath->segs[0].x0 == xPath->segs[0].x1 && - xPath->segs[0].x0 == xPath->segs[1].x0 && - xPath->segs[0].x0 == xPath->segs[3].x1 && - xPath->segs[2].x0 == xPath->segs[2].x1 && - xPath->segs[2].x0 == xPath->segs[1].x1 && - xPath->segs[2].x0 == xPath->segs[3].x0 && - xPath->segs[1].y0 == xPath->segs[1].y1 && - xPath->segs[1].y0 == xPath->segs[0].y1 && - xPath->segs[1].y0 == xPath->segs[2].y0 && - xPath->segs[3].y0 == xPath->segs[3].y1 && - xPath->segs[3].y0 == xPath->segs[0].y0 && - xPath->segs[3].y0 == xPath->segs[2].y1) || - (xPath->segs[0].y0 == xPath->segs[0].y1 && - xPath->segs[0].y0 == xPath->segs[1].y0 && - xPath->segs[0].y0 == xPath->segs[3].y1 && - xPath->segs[2].y0 == xPath->segs[2].y1 && - xPath->segs[2].y0 == xPath->segs[1].y1 && - xPath->segs[2].y0 == xPath->segs[3].y0 && - xPath->segs[1].x0 == xPath->segs[1].x1 && - xPath->segs[1].x0 == xPath->segs[0].x1 && - xPath->segs[1].x0 == xPath->segs[2].x0 && - xPath->segs[3].x0 == xPath->segs[3].x1 && - xPath->segs[3].x0 == xPath->segs[0].x0 && - xPath->segs[3].x0 == xPath->segs[2].x1))) { - clipToRect(xPath->segs[0].x0, xPath->segs[0].y0, - xPath->segs[2].x0, xPath->segs[2].y0); + if (xPath->length == 4 && + xPath->segs[0].y0 == xPath->segs[0].y1 && + xPath->segs[1].x0 == xPath->segs[1].x1 && + xPath->segs[2].x0 == xPath->segs[2].x1 && + xPath->segs[3].y0 == xPath->segs[3].y1) { + clipToRect(xPath->segs[1].x0, xPath->segs[0].y0, + xPath->segs[2].x0, xPath->segs[3].y0); delete xPath; - - } else { - grow(1); - if (antialias) { - xPath->aaScale(); - } - xPath->sort(); - paths[length] = xPath; - flags[length] = eo ? splashClipEO : 0; - if (antialias) { - yMinAA = yMinI * splashAASize; - yMaxAA = (yMaxI + 1) * splashAASize - 1; - } else { - yMinAA = yMinI; - yMaxAA = yMaxI; - } - scanners[length] = new SplashXPathScanner(xPath, eo, yMinAA, yMaxAA); - ++length; + return splashOk; } - - return splashOk; -} - -GBool SplashClip::test(int x, int y) { - int i; - - // check the rectangle - if (x < xMinI || x > xMaxI || y < yMinI || y > yMaxI) { - return gFalse; + if (xPath->length == 4 && + xPath->segs[0].x0 == xPath->segs[0].x1 && + xPath->segs[1].y0 == xPath->segs[1].y1 && + xPath->segs[2].x0 == xPath->segs[2].x1 && + xPath->segs[3].y0 == xPath->segs[3].y1) { + clipToRect(xPath->segs[0].x0, xPath->segs[1].y0, + xPath->segs[2].x0, xPath->segs[3].y0); + delete xPath; + return splashOk; + } + if (xPath->length == 4 && + xPath->segs[0].x0 == xPath->segs[0].x1 && + xPath->segs[1].x0 == xPath->segs[1].x1 && + xPath->segs[2].y0 == xPath->segs[2].y1 && + xPath->segs[3].y0 == xPath->segs[3].y1) { + clipToRect(xPath->segs[0].x0, xPath->segs[2].y0, + xPath->segs[1].x0, xPath->segs[3].y0); + delete xPath; + return splashOk; } - // check the paths - if (antialias) { - for (i = 0; i < length; ++i) { - if (!scanners[i]->test(x * splashAASize, y * splashAASize)) { - return gFalse; - } - } - } else { - for (i = 0; i < length; ++i) { - if (!scanners[i]->test(x, y)) { - return gFalse; - } - } + grow(1); + paths[length] = xPath; + eo[length] = (Guchar)eoA; + if ((t = xPath->getXMin()) > xMin) { + xMin = t; } + if ((t = xPath->getYMin()) > yMin) { + yMin = t; + } + if ((t = xPath->getXMax() + 1) < xMax) { + xMax = t; + } + if ((t = xPath->getYMax() + 1) < yMax) { + yMax = t; + } + intBoundsValid = gFalse; + scanners[length] = new SplashXPathScanner(xPath, eoA, splashFloor(yMin), + splashCeil(yMax) - 1); + ++length; - return gTrue; + return splashOk; } SplashClipResult SplashClip::testRect(int rectXMin, int rectYMin, - int rectXMax, int rectYMax) { - // This tests the rectangle: - // x = [rectXMin, rectXMax + 1) (note: rect coords are ints) + int rectXMax, int rectYMax, + GBool strokeAdjust) { + // In general, this function tests the rectangle: + // x = [rectXMin, rectXMax + 1) (note: coords are ints) // y = [rectYMin, rectYMax + 1) // against the clipping region: - // x = [xMin, xMax) (note: clipping coords are fp) + // x = [xMin, xMax) (note: coords are fp) // y = [yMin, yMax) - if ((SplashCoord)(rectXMax + 1) <= xMin || (SplashCoord)rectXMin >= xMax || - (SplashCoord)(rectYMax + 1) <= yMin || (SplashCoord)rectYMin >= yMax) { - return splashClipAllOutside; - } - if ((SplashCoord)rectXMin >= xMin && (SplashCoord)(rectXMax + 1) <= xMax && - (SplashCoord)rectYMin >= yMin && (SplashCoord)(rectYMax + 1) <= yMax && - length == 0) { - return splashClipAllInside; + + if (strokeAdjust && length == 0) { + // special case for stroke adjustment with a simple clipping + // rectangle -- the clipping region is: + // x = [xMinI, xMaxI + 1) + // y = [yMinI, yMaxI + 1) + updateIntBounds(strokeAdjust); + if (xMinI > xMaxI || yMinI > yMaxI) { + return splashClipAllOutside; + } + if (rectXMax + 1 <= xMinI || + rectXMin >= xMaxI + 1 || + rectYMax + 1 <= yMinI || + rectYMin >= yMaxI + 1) { + return splashClipAllOutside; + } + if (rectXMin >= xMinI && + rectXMax <= xMaxI && + rectYMin >= yMinI && + rectYMax <= yMaxI) { + return splashClipAllInside; + } + } else { + if (xMin >= xMax || yMin >= yMax) { + return splashClipAllOutside; + } + if ((SplashCoord)(rectXMax + 1) <= xMin || + (SplashCoord)rectXMin >= xMax || + (SplashCoord)(rectYMax + 1) <= yMin || + (SplashCoord)rectYMin >= yMax) { + return splashClipAllOutside; + } + if (length == 0 && + (SplashCoord)rectXMin >= xMin && + (SplashCoord)(rectXMax + 1) <= xMax && + (SplashCoord)rectYMin >= yMin && + (SplashCoord)(rectYMax + 1) <= yMax) { + return splashClipAllInside; + } } return splashClipPartial; } -SplashClipResult SplashClip::testSpan(int spanXMin, int spanXMax, int spanY) { - int i; +void SplashClip::clipSpan(Guchar *line, int y, int x0, int x1, + GBool strokeAdjust) { + SplashCoord d; + int x0a, x1a, x, i; - // This tests the rectangle: - // x = [spanXMin, spanXMax + 1) (note: span coords are ints) - // y = [spanY, spanY + 1) - // against the clipping region: - // x = [xMin, xMax) (note: clipping coords are fp) - // y = [yMin, yMax) - if ((SplashCoord)(spanXMax + 1) <= xMin || (SplashCoord)spanXMin >= xMax || - (SplashCoord)(spanY + 1) <= yMin || (SplashCoord)spanY >= yMax) { - return splashClipAllOutside; - } - if (!((SplashCoord)spanXMin >= xMin && (SplashCoord)(spanXMax + 1) <= xMax && - (SplashCoord)spanY >= yMin && (SplashCoord)(spanY + 1) <= yMax)) { - return splashClipPartial; - } - if (antialias) { - for (i = 0; i < length; ++i) { - if (!scanners[i]->testSpan(spanXMin * splashAASize, - spanXMax * splashAASize + (splashAASize - 1), - spanY * splashAASize)) { - return splashClipPartial; + updateIntBounds(strokeAdjust); + + //--- clip to the integer rectangle + + if (y < yMinI || y > yMaxI || + x1 < xMinI || x0 > xMaxI) { + memset(line + x0, 0, x1 - x0 + 1); + return; + } + + if (x0 > xMinI) { + x0a = x0; + } else { + x0a = xMinI; + memset(line + x0, 0, x0a - x0); + } + + if (x1 < xMaxI) { + x1a = x1; + } else { + x1a = xMaxI; + memset(line + x1a + 1, 0, x1 - x1a); + } + + if (x0a > x1a) { + return; + } + + //--- clip to the floating point rectangle + // (if stroke adjustment is disabled) + + if (!strokeAdjust) { + + // clip left edge (xMin) + if (x0a == xMinI) { + d = (SplashCoord)(xMinI + 1) - xMin; + line[x0a] = (Guchar)(int)((SplashCoord)line[x0a] * d); + } + + // clip right edge (xMax) + if (x1a == xMaxI) { + d = xMax - (SplashCoord)xMaxI; + line[x1a] = (Guchar)(int)((SplashCoord)line[x1a] * d); + } + + // clip top edge (yMin) + if (y == yMinI) { + d = (SplashCoord)(yMinI + 1) - yMin; + for (x = x0a; x <= x1a; ++x) { + line[x] = (Guchar)(int)((SplashCoord)line[x] * d); } } - } else { - for (i = 0; i < length; ++i) { - if (!scanners[i]->testSpan(spanXMin, spanXMax, spanY)) { - return splashClipPartial; + + // clip bottom edge (yMax) + if (y == yMaxI) { + d = yMax - (SplashCoord)yMaxI; + for (x = x0a; x <= x1a; ++x) { + line[x] = (Guchar)(int)((SplashCoord)line[x] * d); } } } - return splashClipAllInside; + + if (length == 0) { + return; + } + + //--- clip to the paths + + for (i = 0; i < length; ++i) { + scanners[i]->getSpan(buf, y, x0a, x1a); + for (x = x0a; x <= x1a; ++x) { + line[x] = mul255(line[x], buf[x]); + } + } } -void SplashClip::clipAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y) { - int xx0, xx1, xx, yy, i; - SplashColorPtr p; - - // zero out pixels with x < xMin - xx0 = *x0 * splashAASize; - xx1 = splashFloor(xMin * splashAASize); - if (xx1 > aaBuf->getWidth()) { - xx1 = aaBuf->getWidth(); - } - if (xx0 < xx1) { - xx0 &= ~7; - for (yy = 0; yy < splashAASize; ++yy) { - p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx0 >> 3); - for (xx = xx0; xx + 7 < xx1; xx += 8) { - *p++ = 0; - } - if (xx < xx1) { - *p &= 0xff >> (xx1 & 7); - } +GBool SplashClip::clipSpanBinary(Guchar *line, int y, int x0, int x1, + GBool strokeAdjust) { + int x0a, x1a, x0b, x1b, x, i; + Guchar any; + + updateIntBounds(strokeAdjust); + + if (y < yMinI || y > yMaxI || + x1 < xMinI || x0 > xMaxI) { + if (x0 <= x1) { + memset(line + x0, 0, x1 - x0 + 1); } - *x0 = splashFloor(xMin); + return gFalse; } - // zero out pixels with x > xMax - xx0 = splashFloor(xMax * splashAASize) + 1; - if (xx0 < 0) { - xx0 = 0; - } - xx1 = (*x1 + 1) * splashAASize; - if (xx0 < xx1) { - for (yy = 0; yy < splashAASize; ++yy) { - p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx0 >> 3); - xx = xx0; - if (xx & 7) { - *p &= 0xff00 >> (xx & 7); - xx = (xx & ~7) + 8; - ++p; - } - for (; xx < xx1; xx += 8) { - *p++ = 0; + if (x0 > xMinI) { + x0a = x0; + } else { + x0a = xMinI; + memset(line + x0, 0, x0a - x0); + } + + if (x1 < xMaxI) { + x1a = x1; + } else { + x1a = xMaxI; + memset(line + x1a + 1, 0, x1 - x1a); + } + + if (x0a > x1a) { + return gFalse; + } + + if (length == 0) { + for (x = x0a; x <= x1a; ++x) { + if (line[x]) { + return gTrue; } } - *x1 = splashFloor(xMax); + return gFalse; } - // check the paths + any = 0; for (i = 0; i < length; ++i) { - scanners[i]->clipAALine(aaBuf, x0, x1, y); + scanners[i]->getSpanBinary(buf, y, x0a, x1a); + for (x0b = x0a; x0b <= x1a && !buf[x0b]; ++x0b) ; + if (x0a < x0b) { + memset(line + x0a, 0, x0b - x0a); + } + for (x1b = x1a; x1b >= x0b && !buf[x1b]; --x1b) ; + if (x1b < x1a) { + memset(line + x1b + 1, 0, x1a - x1b); + } + for (x = x0b; x <= x1b; ++x) { + line[x] &= buf[x]; + any |= line[x]; + } + } + + return any != 0; +} + +int SplashClip::getXMinI(GBool strokeAdjust) { + updateIntBounds(strokeAdjust); + return xMinI; +} + +int SplashClip::getXMaxI(GBool strokeAdjust) { + updateIntBounds(strokeAdjust); + return xMaxI; +} + +int SplashClip::getYMinI(GBool strokeAdjust) { + updateIntBounds(strokeAdjust); + return yMinI; +} + +int SplashClip::getYMaxI(GBool strokeAdjust) { + updateIntBounds(strokeAdjust); + return yMaxI; +} + +void SplashClip::updateIntBounds(GBool strokeAdjust) { + if (intBoundsValid && strokeAdjust == intBoundsStrokeAdjust) { + return; } + if (strokeAdjust && length == 0) { + splashStrokeAdjust(xMin, xMax, &xMinI, &xMaxI); + splashStrokeAdjust(yMin, yMax, &yMinI, &yMaxI); + } else { + xMinI = splashFloor(xMin); + yMinI = splashFloor(yMin); + xMaxI = splashCeil(xMax); + yMaxI = splashCeil(yMax); + } + if (xMinI < hardXMin) { + xMinI = hardXMin; + } + if (yMinI < hardYMin) { + yMinI = hardYMin; + } + if (xMaxI > hardXMax) { + xMaxI = hardXMax; + } + if (yMaxI > hardYMax) { + yMaxI = hardYMax; + } + // the clipping code uses [xMinI, xMaxI] instead of [xMinI, xMaxI) + --xMaxI; + --yMaxI; + intBoundsValid = gTrue; + intBoundsStrokeAdjust = strokeAdjust; } diff -uNrp xpdf-3.03/splash/SplashClip.h xpdf-3.04/splash/SplashClip.h --- xpdf-3.03/splash/SplashClip.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashClip.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashClip.h // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #ifndef SPLASHCLIP_H @@ -37,9 +39,8 @@ class SplashClip { public: // Create a clip, for the given rectangle. - SplashClip(SplashCoord x0, SplashCoord y0, - SplashCoord x1, SplashCoord y1, - GBool antialiasA); + SplashClip(int hardXMinA, int hardYMinA, + int hardXMaxA, int hardYMaxA); // Copy a clip. SplashClip *copy() { return new SplashClip(this); } @@ -56,10 +57,7 @@ public: // Interesect the clip with . SplashError clipToPath(SplashPath *path, SplashCoord *matrix, - SplashCoord flatness, GBool eo); - - // Returns true if (,) is inside the clip. - GBool test(int x, int y); + SplashCoord flatness, GBool eoA); // Tests a rectangle against the clipping region. Returns one of: // - splashClipAllInside if the entire rectangle is inside the @@ -71,15 +69,19 @@ public: // - splashClipPartial if the rectangle is part inside and part // outside the clipping region SplashClipResult testRect(int rectXMin, int rectYMin, - int rectXMax, int rectYMax); - - // Similar to testRect, but tests a horizontal span. - SplashClipResult testSpan(int spanXMin, int spanXMax, int spanY); + int rectXMax, int rectYMax, + GBool strokeAdjust); - // Clips an anti-aliased line by setting pixels to zero. On entry, - // all non-zero pixels are between and . This function - // will update and . - void clipAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y); + // Clip a scan line. Modifies line[] by multiplying with clipping + // shape values for one scan line: ([x0, x1], y). + void clipSpan(Guchar *line, int y, int x0, int x1, + GBool strokeAdjust); + + // Like clipSpan(), but uses the values 0 and 255 only. + // Returns true if there are any non-zero values in the result + // (i.e., returns false if the entire line is clipped out). + GBool clipSpanBinary(Guchar *line, int y, int x0, int x1, + GBool strokeAdjust); // Get the rectangle part of the clip region. SplashCoord getXMin() { return xMin; } @@ -88,10 +90,10 @@ public: SplashCoord getYMax() { return yMax; } // Get the rectangle part of the clip region, in integer coordinates. - int getXMinI() { return xMinI; } - int getXMaxI() { return xMaxI; } - int getYMinI() { return yMinI; } - int getYMaxI() { return yMaxI; } + int getXMinI(GBool strokeAdjust); + int getXMaxI(GBool strokeAdjust); + int getYMinI(GBool strokeAdjust); + int getYMaxI(GBool strokeAdjust); // Get the number of arbitrary paths used by the clip region. int getNumPaths() { return length; } @@ -100,14 +102,25 @@ private: SplashClip(SplashClip *clip); void grow(int nPaths); + void updateIntBounds(GBool strokeAdjust); + + int hardXMin, hardYMin, // coordinates cannot fall outside of + hardXMax, hardYMax; // [hardXMin, hardXMax), [hardYMin, hardYMax) + + SplashCoord xMin, yMin, // current clip bounding rectangle + xMax, yMax; // (these coordinates may be adjusted if + // stroke adjustment is enabled) - GBool antialias; - SplashCoord xMin, yMin, xMax, yMax; int xMinI, yMinI, xMaxI, yMaxI; + GBool intBoundsValid; // true if xMinI, etc. are valid + GBool intBoundsStrokeAdjust; // value of strokeAdjust used to compute + // xMinI, etc. + SplashXPath **paths; - Guchar *flags; + Guchar *eo; SplashXPathScanner **scanners; int length, size; + Guchar *buf; }; #endif diff -uNrp xpdf-3.03/splash/SplashFontEngine.cc xpdf-3.04/splash/SplashFontEngine.cc --- xpdf-3.03/splash/SplashFontEngine.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashFontEngine.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashFontEngine.cc // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #include @@ -10,19 +12,14 @@ #pragma implementation #endif -#if HAVE_T1LIB_H -#include -#endif - #include #include #ifdef HAVE_UNISTD_H # include #endif #include "gmem.h" #include "GString.h" #include "SplashMath.h" -#include "SplashT1FontEngine.h" #include "SplashFTFontEngine.h" #include "SplashFontFile.h" #include "SplashFontFileID.h" @@ -40,9 +37,6 @@ extern "C" int unlink(char *filename); //------------------------------------------------------------------------ SplashFontEngine::SplashFontEngine( -#if HAVE_T1LIB_H - GBool enableT1lib, -#endif #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H GBool enableFreeType, Guint freeTypeFlags, @@ -54,13 +48,6 @@ SplashFontEngine::SplashFontEngine( fontCache[i] = NULL; } -#if HAVE_T1LIB_H - if (enableT1lib) { - t1Engine = SplashT1FontEngine::init(aa); - } else { - t1Engine = NULL; - } -#endif #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H if (enableFreeType) { ftEngine = SplashFTFontEngine::init(aa, freeTypeFlags); @@ -79,11 +66,6 @@ SplashFontEngine::~SplashFontEngine() { } } -#if HAVE_T1LIB_H - if (t1Engine) { - delete t1Engine; - } -#endif #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H if (ftEngine) { delete ftEngine; @@ -107,24 +89,29 @@ SplashFontFile *SplashFontEngine::getFon } SplashFontFile *SplashFontEngine::loadType1Font(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else char *fileName, GBool deleteFile, +#endif const char **enc) { SplashFontFile *fontFile; fontFile = NULL; -#if HAVE_T1LIB_H - if (!fontFile && t1Engine) { - fontFile = t1Engine->loadType1Font(idA, fileName, deleteFile, enc); - } -#endif #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H if (!fontFile && ftEngine) { - fontFile = ftEngine->loadType1Font(idA, fileName, deleteFile, enc); + fontFile = ftEngine->loadType1Font(idA, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName, deleteFile, +#endif + enc); } #endif -#ifndef _WIN32 +#if !LOAD_FONTS_FROM_MEM && !defined(_WIN32) // delete the (temporary) font file -- with Unix hard link // semantics, this will remove the last link; otherwise it will // return an error, leaving the file to be deleted later (if @@ -138,24 +125,29 @@ SplashFontFile *SplashFontEngine::loadTy } SplashFontFile *SplashFontEngine::loadType1CFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else char *fileName, GBool deleteFile, +#endif const char **enc) { SplashFontFile *fontFile; fontFile = NULL; -#if HAVE_T1LIB_H - if (!fontFile && t1Engine) { - fontFile = t1Engine->loadType1CFont(idA, fileName, deleteFile, enc); - } -#endif #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H if (!fontFile && ftEngine) { - fontFile = ftEngine->loadType1CFont(idA, fileName, deleteFile, enc); + fontFile = ftEngine->loadType1CFont(idA, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName, deleteFile, +#endif + enc); } #endif -#ifndef _WIN32 +#if !LOAD_FONTS_FROM_MEM && !defined(_WIN32) // delete the (temporary) font file -- with Unix hard link // semantics, this will remove the last link; otherwise it will // return an error, leaving the file to be deleted later (if @@ -169,19 +161,29 @@ SplashFontFile *SplashFontEngine::loadTy } SplashFontFile *SplashFontEngine::loadOpenTypeT1CFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else char *fileName, GBool deleteFile, +#endif const char **enc) { SplashFontFile *fontFile; fontFile = NULL; #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H if (!fontFile && ftEngine) { - fontFile = ftEngine->loadOpenTypeT1CFont(idA, fileName, deleteFile, enc); + fontFile = ftEngine->loadOpenTypeT1CFont(idA, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName, deleteFile, +#endif + enc); } #endif -#ifndef _WIN32 +#if !LOAD_FONTS_FROM_MEM && !defined(_WIN32) // delete the (temporary) font file -- with Unix hard link // semantics, this will remove the last link; otherwise it will // return an error, leaving the file to be deleted later (if @@ -195,18 +197,29 @@ SplashFontFile *SplashFontEngine::loadOp } SplashFontFile *SplashFontEngine::loadCIDFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf +#else char *fileName, - GBool deleteFile) { + GBool deleteFile +#endif + ) { SplashFontFile *fontFile; fontFile = NULL; #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H if (!fontFile && ftEngine) { - fontFile = ftEngine->loadCIDFont(idA, fileName, deleteFile); + fontFile = ftEngine->loadCIDFont(idA, +#if LOAD_FONTS_FROM_MEM + fontBuf +#else + fileName, deleteFile +#endif + ); } #endif -#ifndef _WIN32 +#if !LOAD_FONTS_FROM_MEM && !defined(_WIN32) // delete the (temporary) font file -- with Unix hard link // semantics, this will remove the last link; otherwise it will // return an error, leaving the file to be deleted later (if @@ -220,8 +233,12 @@ SplashFontFile *SplashFontEngine::loadCI } SplashFontFile *SplashFontEngine::loadOpenTypeCFFFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else char *fileName, GBool deleteFile, +#endif int *codeToGID, int codeToGIDLen) { SplashFontFile *fontFile; @@ -229,12 +246,17 @@ SplashFontFile *SplashFontEngine::loadOp fontFile = NULL; #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H if (!fontFile && ftEngine) { - fontFile = ftEngine->loadOpenTypeCFFFont(idA, fileName, deleteFile, + fontFile = ftEngine->loadOpenTypeCFFFont(idA, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName, deleteFile, +#endif codeToGID, codeToGIDLen); } #endif -#ifndef _WIN32 +#if !LOAD_FONTS_FROM_MEM && !defined(_WIN32) // delete the (temporary) font file -- with Unix hard link // semantics, this will remove the last link; otherwise it will // return an error, leaving the file to be deleted later (if @@ -248,9 +270,13 @@ SplashFontFile *SplashFontEngine::loadOp } SplashFontFile *SplashFontEngine::loadTrueTypeFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else char *fileName, - int fontNum, GBool deleteFile, +#endif + int fontNum, int *codeToGID, int codeToGIDLen, char *fontName) { @@ -259,8 +285,13 @@ SplashFontFile *SplashFontEngine::loadTr fontFile = NULL; #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H if (!fontFile && ftEngine) { - fontFile = ftEngine->loadTrueTypeFont(idA, fileName, fontNum, deleteFile, - codeToGID, codeToGIDLen); + fontFile = ftEngine->loadTrueTypeFont(idA, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName, deleteFile, +#endif + fontNum, codeToGID, codeToGIDLen); } #endif @@ -268,7 +299,7 @@ SplashFontFile *SplashFontEngine::loadTr gfree(codeToGID); } -#ifndef _WIN32 +#if !LOAD_FONTS_FROM_MEM && !defined(_WIN32) // delete the (temporary) font file -- with Unix hard link // semantics, this will remove the last link; otherwise it will // return an error, leaving the file to be deleted later (if diff -uNrp xpdf-3.03/splash/SplashFontEngine.h xpdf-3.04/splash/SplashFontEngine.h --- xpdf-3.03/splash/SplashFontEngine.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashFontEngine.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashFontEngine.h // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #ifndef SPLASHFONTENGINE_H @@ -14,8 +16,8 @@ #endif #include "gtypes.h" +class GString; -class SplashT1FontEngine; class SplashFTFontEngine; class SplashDTFontEngine; class SplashDT4FontEngine; @@ -40,9 +42,6 @@ public: // Create a font engine. SplashFontEngine( -#if HAVE_T1LIB_H - GBool enableT1lib, -#endif #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H GBool enableFreeType, Guint freeTypeFlags, @@ -56,19 +55,48 @@ public: SplashFontFile *getFontFile(SplashFontFileID *id); // Load fonts - these create new SplashFontFile objects. - SplashFontFile *loadType1Font(SplashFontFileID *idA, char *fileName, - GBool deleteFile, const char **enc); - SplashFontFile *loadType1CFont(SplashFontFileID *idA, char *fileName, - GBool deleteFile, const char **enc); - SplashFontFile *loadOpenTypeT1CFont(SplashFontFileID *idA, char *fileName, - GBool deleteFile, const char **enc); - SplashFontFile *loadCIDFont(SplashFontFileID *idA, char *fileName, - GBool deleteFile); - SplashFontFile *loadOpenTypeCFFFont(SplashFontFileID *idA, char *fileName, - GBool deleteFile, + SplashFontFile *loadType1Font(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else + char *fileName, GBool deleteFile, +#endif + const char **enc); + SplashFontFile *loadType1CFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else + char *fileName, GBool deleteFile, +#endif + const char **enc); + SplashFontFile *loadOpenTypeT1CFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else + char *fileName, GBool deleteFile, +#endif + const char **enc); + SplashFontFile *loadCIDFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf +#else + char *fileName, GBool deleteFile +#endif + ); + SplashFontFile *loadOpenTypeCFFFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else + char *fileName, GBool deleteFile, +#endif int *codeToGID, int codeToGIDLen); - SplashFontFile *loadTrueTypeFont(SplashFontFileID *idA, char *fileName, - int fontNum, GBool deleteFile, + SplashFontFile *loadTrueTypeFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else + char *fileName, GBool deleteFile, +#endif + int fontNum, int *codeToGID, int codeToGIDLen, char *fontName); @@ -87,9 +115,6 @@ private: SplashFont *fontCache[splashFontCacheSize]; -#if HAVE_T1LIB_H - SplashT1FontEngine *t1Engine; -#endif #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H SplashFTFontEngine *ftEngine; #endif diff -uNrp xpdf-3.03/splash/SplashFontFile.cc xpdf-3.04/splash/SplashFontFile.cc --- xpdf-3.03/splash/SplashFontFile.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashFontFile.cc 2014-05-28 20:50:50.000000000 +0200 @@ -28,19 +30,32 @@ extern "C" int unlink(char *filename); // SplashFontFile //------------------------------------------------------------------------ -SplashFontFile::SplashFontFile(SplashFontFileID *idA, char *fileNameA, - GBool deleteFileA) { +SplashFontFile::SplashFontFile(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBufA +#else + char *fileNameA, GBool deleteFileA +#endif + ) { id = idA; +#if LOAD_FONTS_FROM_MEM + fontBuf = fontBufA; +#else fileName = new GString(fileNameA); deleteFile = deleteFileA; +#endif refCnt = 0; } SplashFontFile::~SplashFontFile() { +#if LOAD_FONTS_FROM_MEM + delete fontBuf; +#else if (deleteFile) { unlink(fileName->getCString()); } delete fileName; +#endif delete id; } diff -uNrp xpdf-3.03/splash/SplashFontFile.h xpdf-3.04/splash/SplashFontFile.h --- xpdf-3.03/splash/SplashFontFile.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashFontFile.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashFontFile.h // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #ifndef SPLASHFONTFILE_H @@ -46,12 +48,21 @@ public: protected: - SplashFontFile(SplashFontFileID *idA, char *fileNameA, - GBool deleteFileA); + SplashFontFile(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBufA +#else + char *fileNameA, GBool deleteFileA +#endif + ); SplashFontFileID *id; +#if LOAD_FONTS_FROM_MEM + GString *fontBuf; +#else GString *fileName; GBool deleteFile; +#endif int refCnt; friend class SplashFontEngine; diff -uNrp xpdf-3.03/splash/SplashFTFont.cc xpdf-3.04/splash/SplashFTFont.cc --- xpdf-3.03/splash/SplashFTFont.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashFTFont.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashFTFont.cc // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #include @@ -242,23 +244,24 @@ GBool SplashFTFont::makeGlyph(int c, int return gFalse; } - flags = 0; - if (aa) { - flags |= FT_LOAD_NO_BITMAP; - } + // Set up the load flags: + // * disable bitmaps because they look ugly when scaled, rotated, + // etc. + // * disable autohinting because it can fail badly with font subsets + // that use invalid glyph names (the FreeType autohinter depends + // on the glyph name to figure out how to autohint the glyph) + // * but enable light autohinting for Type 1 fonts because regular + // hinting looks pretty bad, and the invalid glyph name issue + // seems to be very rare (Type 1 fonts are mostly used for + // substitution, in which case the full font is being used, which + // means we have the glyph names) + flags = FT_LOAD_NO_BITMAP; if (ff->engine->flags & splashFTNoHinting) { flags |= FT_LOAD_NO_HINTING; - } else if (ff->trueType) { - // FT2's autohinting doesn't always work very well (especially with - // font subsets), so turn it off if anti-aliasing is enabled; if - // anti-aliasing is disabled, this seems to be a tossup - some fonts - // look better with hinting, some without, so leave hinting on - if (aa) { - flags |= FT_LOAD_NO_AUTOHINT; - } - } else if (ff->type1) { - // Type 1 fonts seem to look better with 'light' hinting mode + } else if (ff->useLightHinting) { flags |= FT_LOAD_TARGET_LIGHT; + } else { + flags |= FT_LOAD_NO_AUTOHINT; } if (FT_Load_Glyph(ff->face, gid, flags)) { return gFalse; diff -uNrp xpdf-3.03/splash/SplashFTFontEngine.cc xpdf-3.04/splash/SplashFTFontEngine.cc --- xpdf-3.03/splash/SplashFTFontEngine.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashFTFontEngine.cc 2014-05-28 20:50:50.000000000 +0200 @@ -23,6 +25,10 @@ #include "FoFiType1C.h" #include "SplashFTFontFile.h" #include "SplashFTFontEngine.h" +#include FT_MODULE_H +#ifdef FT_CFF_DRIVER_H +# include FT_CFF_DRIVER_H +#endif #ifdef VMS #if (__VMS_VER < 70000000) @@ -36,6 +42,12 @@ static void fileWrite(void *stream, cons fwrite(data, 1, len, (FILE *)stream); } +#if LOAD_FONTS_FROM_MEM +static void gstringWrite(void *stream, const char *data, int len) { + ((GString *)stream)->append(data, len); +} +#endif + //------------------------------------------------------------------------ // SplashFTFontEngine //------------------------------------------------------------------------ @@ -68,29 +80,117 @@ SplashFTFontEngine::~SplashFTFontEngine( } SplashFontFile *SplashFTFontEngine::loadType1Font(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else char *fileName, GBool deleteFile, +#endif const char **enc) { - return SplashFTFontFile::loadType1Font(this, idA, fileName, deleteFile, enc); + return SplashFTFontFile::loadType1Font(this, idA, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName, deleteFile, +#endif + enc, gTrue); } SplashFontFile *SplashFTFontEngine::loadType1CFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else char *fileName, GBool deleteFile, +#endif const char **enc) { - return SplashFTFontFile::loadType1Font(this, idA, fileName, deleteFile, enc); + return SplashFTFontFile::loadType1Font(this, idA, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName, deleteFile, +#endif + enc, gFalse); } SplashFontFile *SplashFTFontEngine::loadOpenTypeT1CFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else char *fileName, GBool deleteFile, +#endif const char **enc) { - return SplashFTFontFile::loadType1Font(this, idA, fileName, deleteFile, enc); + FoFiTrueType *ff; +#if LOAD_FONTS_FROM_MEM + GString *fontBuf2; +#else + GString *tmpFileName; + FILE *tmpFile; +#endif + SplashFontFile *ret; + +#if LOAD_FONTS_FROM_MEM + if (!(ff = FoFiTrueType::make(fontBuf->getCString(), fontBuf->getLength(), + 0, gTrue))) { +#else + if (!(ff = FoFiTrueType::load(fileName, 0, gTrue))) { +#endif + return NULL; + } + if (ff->isHeadlessCFF()) { +#if LOAD_FONTS_FROM_MEM + fontBuf2 = new GString(); + ff->convertToType1(NULL, enc, gFalse, &gstringWrite, fontBuf2); + delete ff; + ret = SplashFTFontFile::loadType1Font(this, idA, fontBuf2, enc, + gFalse); + if (ret) { + delete fontBuf; + } else { + delete fontBuf2; + } +#else + tmpFileName = NULL; + if (!openTempFile(&tmpFileName, &tmpFile, "wb", NULL)) { + delete ff; + return NULL; + } + ff->convertToType1(NULL, enc, gFalse, &fileWrite, tmpFile); + delete ff; + fclose(tmpFile); + ret = SplashFTFontFile::loadType1Font(this, idA, tmpFileName->getCString(), + gTrue, enc, gFalse); + if (ret) { + if (deleteFile) { + unlink(fileName); + } + } else { + unlink(tmpFileName->getCString()); + } + delete tmpFileName; +#endif + } else { + delete ff; + ret = SplashFTFontFile::loadType1Font(this, idA, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName, deleteFile, +#endif + enc, gFalse); + } + return ret; } SplashFontFile *SplashFTFontEngine::loadCIDFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf +#else char *fileName, - GBool deleteFile) { + GBool deleteFile +#endif + ) { FoFiType1C *ff; int *cidToGIDMap; int nCIDs; @@ -100,14 +200,24 @@ SplashFontFile *SplashFTFontEngine::load if (useCIDs) { cidToGIDMap = NULL; nCIDs = 0; +#if LOAD_FONTS_FROM_MEM + } else if ((ff = FoFiType1C::make(fontBuf->getCString(), + fontBuf->getLength()))) { +#else } else if ((ff = FoFiType1C::load(fileName))) { +#endif cidToGIDMap = ff->getCIDToGIDMap(&nCIDs); delete ff; } else { cidToGIDMap = NULL; nCIDs = 0; } - ret = SplashFTFontFile::loadCIDFont(this, idA, fileName, deleteFile, + ret = SplashFTFontFile::loadCIDFont(this, idA, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName, deleteFile, +#endif cidToGIDMap, nCIDs); if (!ret) { gfree(cidToGIDMap); @@ -116,32 +226,90 @@ SplashFontFile *SplashFTFontEngine::load } SplashFontFile *SplashFTFontEngine::loadOpenTypeCFFFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else char *fileName, GBool deleteFile, +#endif int *codeToGID, int codeToGIDLen) { FoFiTrueType *ff; - GBool isCID; +#if LOAD_FONTS_FROM_MEM + GString *fontBuf2; +#else + GString *tmpFileName; + FILE *tmpFile; +#endif + char *cffStart; + int cffLength; int *cidToGIDMap; int nCIDs; SplashFontFile *ret; +#if LOAD_FONTS_FROM_MEM + if (!(ff = FoFiTrueType::make(fontBuf->getCString(), fontBuf->getLength(), + 0, gTrue))) { +#else + if (!(ff = FoFiTrueType::load(fileName, 0, gTrue))) { +#endif + return NULL; + } cidToGIDMap = NULL; nCIDs = 0; - isCID = gFalse; - if (!codeToGID) { + if (ff->isHeadlessCFF()) { + if (!ff->getCFFBlock(&cffStart, &cffLength)) { + return NULL; + } +#if LOAD_FONTS_FROM_MEM + fontBuf2 = new GString(cffStart, cffLength); + if (!useCIDs) { + cidToGIDMap = ff->getCIDToGIDMap(&nCIDs); + } + ret = SplashFTFontFile::loadCIDFont(this, idA, fontBuf2, + cidToGIDMap, nCIDs); + if (ret) { + delete fontBuf; + } else { + delete fontBuf2; + } +#else + tmpFileName = NULL; + if (!openTempFile(&tmpFileName, &tmpFile, "wb", NULL)) { + delete ff; + return NULL; + } + fwrite(cffStart, 1, cffLength, tmpFile); + fclose(tmpFile); if (!useCIDs) { - if ((ff = FoFiTrueType::load(fileName))) { - if (ff->isOpenTypeCFF()) { - cidToGIDMap = ff->getCIDToGIDMap(&nCIDs); - } - delete ff; + cidToGIDMap = ff->getCIDToGIDMap(&nCIDs); + } + ret = SplashFTFontFile::loadCIDFont(this, idA, + tmpFileName->getCString(), gTrue, + cidToGIDMap, nCIDs); + if (ret) { + if (deleteFile) { + unlink(fileName); } + } else { + unlink(tmpFileName->getCString()); + } + delete tmpFileName; +#endif + } else { + if (!codeToGID && !useCIDs && ff->isOpenTypeCFF()) { + cidToGIDMap = ff->getCIDToGIDMap(&nCIDs); } + ret = SplashFTFontFile::loadCIDFont(this, idA, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName, deleteFile, +#endif + codeToGID ? codeToGID : cidToGIDMap, + codeToGID ? codeToGIDLen : nCIDs); } - ret = SplashFTFontFile::loadCIDFont(this, idA, fileName, deleteFile, - codeToGID ? codeToGID : cidToGIDMap, - codeToGID ? codeToGIDLen : nCIDs); + delete ff; if (!ret) { gfree(cidToGIDMap); } @@ -149,31 +317,59 @@ SplashFontFile *SplashFTFontEngine::load } SplashFontFile *SplashFTFontEngine::loadTrueTypeFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else char *fileName, - int fontNum, GBool deleteFile, +#endif + int fontNum, int *codeToGID, int codeToGIDLen) { FoFiTrueType *ff; +#if LOAD_FONTS_FROM_MEM + GString *fontBuf2; +#else GString *tmpFileName; FILE *tmpFile; +#endif SplashFontFile *ret; - //~ this should use fontNum to load the correct font - if (!(ff = FoFiTrueType::load(fileName))) { +#if LOAD_FONTS_FROM_MEM + if (!(ff = FoFiTrueType::make(fontBuf->getCString(), fontBuf->getLength(), + fontNum))) { +#else + if (!(ff = FoFiTrueType::load(fileName, fontNum))) { +#endif return NULL; } +#if LOAD_FONTS_FROM_MEM + fontBuf2 = new GString; + ff->writeTTF(&gstringWrite, fontBuf2); +#else tmpFileName = NULL; if (!openTempFile(&tmpFileName, &tmpFile, "wb", NULL)) { delete ff; return NULL; } ff->writeTTF(&fileWrite, tmpFile); - delete ff; fclose(tmpFile); +#endif + delete ff; ret = SplashFTFontFile::loadTrueTypeFont(this, idA, - tmpFileName->getCString(), fontNum, - gTrue, codeToGID, codeToGIDLen); +#if LOAD_FONTS_FROM_MEM + fontBuf2, +#else + tmpFileName->getCString(), gTrue, +#endif + 0, codeToGID, codeToGIDLen); +#if LOAD_FONTS_FROM_MEM + if (ret) { + delete fontBuf; + } else { + delete fontBuf2; + } +#else if (ret) { if (deleteFile) { unlink(fileName); @@ -182,6 +378,7 @@ SplashFontFile *SplashFTFontEngine::load unlink(tmpFileName->getCString()); } delete tmpFileName; +#endif return ret; } diff -uNrp xpdf-3.03/splash/SplashFTFontEngine.h xpdf-3.04/splash/SplashFTFontEngine.h --- xpdf-3.03/splash/SplashFTFontEngine.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashFTFontEngine.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashFTFontEngine.h // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #ifndef SPLASHFTFONTENGINE_H @@ -18,6 +20,7 @@ #include #include FT_FREETYPE_H #include "gtypes.h" +class GString; class SplashFontFile; class SplashFontFileID; @@ -34,19 +37,48 @@ public: ~SplashFTFontEngine(); // Load fonts. - SplashFontFile *loadType1Font(SplashFontFileID *idA, char *fileName, - GBool deleteFile, const char **enc); - SplashFontFile *loadType1CFont(SplashFontFileID *idA, char *fileName, - GBool deleteFile, const char **enc); - SplashFontFile *loadOpenTypeT1CFont(SplashFontFileID *idA, char *fileName, - GBool deleteFile, const char **enc); - SplashFontFile *loadCIDFont(SplashFontFileID *idA, char *fileName, - GBool deleteFile); - SplashFontFile *loadOpenTypeCFFFont(SplashFontFileID *idA, char *fileName, - GBool deleteFile, + SplashFontFile *loadType1Font(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else + char *fileName, GBool deleteFile, +#endif + const char **enc); + SplashFontFile *loadType1CFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else + char *fileName, GBool deleteFile, +#endif + const char **enc); + SplashFontFile *loadOpenTypeT1CFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else + char *fileName, GBool deleteFile, +#endif + const char **enc); + SplashFontFile *loadCIDFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf +#else + char *fileName, GBool deleteFile +#endif + ); + SplashFontFile *loadOpenTypeCFFFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else + char *fileName, GBool deleteFile, +#endif int *codeToGID, int codeToGIDLen); - SplashFontFile *loadTrueTypeFont(SplashFontFileID *idA, char *fileName, - int fontNum, GBool deleteFile, + SplashFontFile *loadTrueTypeFont(SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBuf, +#else + char *fileName, GBool deleteFile, +#endif + int fontNum, int *codeToGID, int codeToGIDLen); private: diff -uNrp xpdf-3.03/splash/SplashFTFontFile.cc xpdf-3.04/splash/SplashFTFontFile.cc --- xpdf-3.03/splash/SplashFTFontFile.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashFTFontFile.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashFTFontFile.cc // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #include @@ -13,6 +15,7 @@ #endif #include "gmem.h" +#include "GString.h" #include "SplashFTFontEngine.h" #include "SplashFTFont.h" #include "SplashFTFontFile.h" @@ -23,15 +26,25 @@ SplashFontFile *SplashFTFontFile::loadType1Font(SplashFTFontEngine *engineA, SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBufA, +#else char *fileNameA, GBool deleteFileA, - const char **encA) { +#endif + const char **encA, + GBool useLightHintingA) { FT_Face faceA; int *codeToGIDA; const char *name; int i; +#if LOAD_FONTS_FROM_MEM + if (FT_New_Memory_Face(engineA->lib, (FT_Byte *)fontBufA->getCString(), + fontBufA->getLength(), 0, &faceA)) { +#else if (FT_New_Face(engineA->lib, fileNameA, 0, &faceA)) { +#endif return NULL; } codeToGIDA = (int *)gmallocn(256, sizeof(int)); @@ -42,59 +55,101 @@ SplashFontFile *SplashFTFontFile::loadTy } } - return new SplashFTFontFile(engineA, idA, fileNameA, deleteFileA, - faceA, codeToGIDA, 256, gFalse, gTrue); + return new SplashFTFontFile(engineA, idA, +#if LOAD_FONTS_FROM_MEM + fontBufA, +#else + fileNameA, deleteFileA, +#endif + faceA, codeToGIDA, 256, + gFalse, useLightHintingA); } SplashFontFile *SplashFTFontFile::loadCIDFont(SplashFTFontEngine *engineA, SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBufA, +#else char *fileNameA, GBool deleteFileA, +#endif int *codeToGIDA, int codeToGIDLenA) { FT_Face faceA; +#if LOAD_FONTS_FROM_MEM + if (FT_New_Memory_Face(engineA->lib, (FT_Byte *)fontBufA->getCString(), + fontBufA->getLength(), 0, &faceA)) { +#else if (FT_New_Face(engineA->lib, fileNameA, 0, &faceA)) { +#endif return NULL; } - return new SplashFTFontFile(engineA, idA, fileNameA, deleteFileA, + return new SplashFTFontFile(engineA, idA, +#if LOAD_FONTS_FROM_MEM + fontBufA, +#else + fileNameA, deleteFileA, +#endif faceA, codeToGIDA, codeToGIDLenA, gFalse, gFalse); } SplashFontFile *SplashFTFontFile::loadTrueTypeFont(SplashFTFontEngine *engineA, SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBufA, +#else char *fileNameA, - int fontNum, GBool deleteFileA, +#endif + int fontNum, int *codeToGIDA, int codeToGIDLenA) { FT_Face faceA; +#if LOAD_FONTS_FROM_MEM + if (FT_New_Memory_Face(engineA->lib, (FT_Byte *)fontBufA->getCString(), + fontBufA->getLength(), fontNum, &faceA)) { +#else if (FT_New_Face(engineA->lib, fileNameA, fontNum, &faceA)) { +#endif return NULL; } - return new SplashFTFontFile(engineA, idA, fileNameA, deleteFileA, + return new SplashFTFontFile(engineA, idA, +#if LOAD_FONTS_FROM_MEM + fontBufA, +#else + fileNameA, deleteFileA, +#endif faceA, codeToGIDA, codeToGIDLenA, gTrue, gFalse); } SplashFTFontFile::SplashFTFontFile(SplashFTFontEngine *engineA, SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBufA, +#else char *fileNameA, GBool deleteFileA, +#endif FT_Face faceA, int *codeToGIDA, int codeToGIDLenA, - GBool trueTypeA, GBool type1A): + GBool trueTypeA, GBool useLightHintingA): +#if LOAD_FONTS_FROM_MEM + SplashFontFile(idA, fontBufA) +#else SplashFontFile(idA, fileNameA, deleteFileA) +#endif { engine = engineA; face = faceA; codeToGID = codeToGIDA; codeToGIDLen = codeToGIDLenA; trueType = trueTypeA; - type1 = type1A; + useLightHinting = useLightHintingA; } SplashFTFontFile::~SplashFTFontFile() { diff -uNrp xpdf-3.03/splash/SplashFTFontFile.h xpdf-3.04/splash/SplashFTFontFile.h --- xpdf-3.03/splash/SplashFTFontFile.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashFTFontFile.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashFTFontFile.h // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #ifndef SPLASHFTFONTFILE_H @@ -30,17 +32,31 @@ class SplashFTFontFile: public SplashFon public: static SplashFontFile *loadType1Font(SplashFTFontEngine *engineA, - SplashFontFileID *idA, char *fileNameA, - GBool deleteFileA, const char **encA); + SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBufA, +#else + char *fileNameA, GBool deleteFileA, +#endif + const char **encA, + GBool useLightHintingA); static SplashFontFile *loadCIDFont(SplashFTFontEngine *engineA, - SplashFontFileID *idA, char *fileNameA, - GBool deleteFileA, + SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBufA, +#else + char *fileNameA, GBool deleteFileA, +#endif int *codeToGIDA, int codeToGIDLenA); static SplashFontFile *loadTrueTypeFont(SplashFTFontEngine *engineA, SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBufA, +#else char *fileNameA, - int fontNum, GBool deleteFileA, +#endif + int fontNum, int *codeToGIDA, int codeToGIDLenA); @@ -55,17 +71,21 @@ private: SplashFTFontFile(SplashFTFontEngine *engineA, SplashFontFileID *idA, +#if LOAD_FONTS_FROM_MEM + GString *fontBufA, +#else char *fileNameA, GBool deleteFileA, +#endif FT_Face faceA, int *codeToGIDA, int codeToGIDLenA, - GBool trueTypeA, GBool type1A); + GBool trueTypeA, GBool useLightHintingA); SplashFTFontEngine *engine; FT_Face face; int *codeToGID; int codeToGIDLen; GBool trueType; - GBool type1; + GBool useLightHinting; friend class SplashFTFont; }; diff -uNrp xpdf-3.03/splash/Splash.h xpdf-3.04/splash/Splash.h --- xpdf-3.03/splash/Splash.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/Splash.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // Splash.h // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #ifndef SPLASH_H @@ -97,6 +99,7 @@ public: SplashClip *getClip(); SplashBitmap *getSoftMask(); GBool getInNonIsolatedGroup(); + GBool getInKnockoutGroup(); //----- state write @@ -125,8 +128,9 @@ public: // NB: uses untransformed coordinates. SplashError clipToPath(SplashPath *path, GBool eo); void setSoftMask(SplashBitmap *softMask); - void setInNonIsolatedGroup(SplashBitmap *alpha0BitmapA, - int alpha0XA, int alpha0YA); + void setInTransparencyGroup(SplashBitmap *groupBackBitmapA, + int groupBackXA, int groupBackYA, + GBool nonIsolated, GBool knockout); void setTransfer(Guchar *red, Guchar *green, Guchar *blue, Guchar *gray); void setOverprintMask(Guint overprintMask); @@ -172,7 +176,7 @@ public: // top line. SplashError fillImageMask(SplashImageMaskSource src, void *srcData, int w, int h, SplashCoord *mat, - GBool glyphMode); + GBool glyphMode, GBool interpolate); // Draw an image. This will read lines of pixels from // , starting with the top line. These pixels are assumed to @@ -182,16 +186,16 @@ public: // are supported: // source target // ------ ------ - // Mono1 Mono1 // Mono8 Mono1 -- with dithering // Mono8 Mono8 // RGB8 RGB8 - // BGR8 BGR8 + // BGR8 RGB8 // CMYK8 CMYK8 // The matrix behaves as for fillImageMask. SplashError drawImage(SplashImageSource src, void *srcData, SplashColorMode srcMode, GBool srcAlpha, - int w, int h, SplashCoord *mat); + int w, int h, SplashCoord *mat, + GBool interpolate); // Composite a rectangular region from onto this Splash // object. @@ -245,37 +249,53 @@ public: private: - void pipeInit(SplashPipe *pipe, int x, int y, - SplashPattern *pattern, SplashColorPtr cSrc, + void pipeInit(SplashPipe *pipe, SplashPattern *pattern, Guchar aInput, GBool usesShape, GBool nonIsolatedGroup); - void pipeRun(SplashPipe *pipe); - void pipeRunSimpleMono1(SplashPipe *pipe); - void pipeRunSimpleMono8(SplashPipe *pipe); - void pipeRunSimpleRGB8(SplashPipe *pipe); - void pipeRunSimpleBGR8(SplashPipe *pipe); + void pipeRun(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunSimpleMono1(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunSimpleMono8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunSimpleRGB8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunSimpleBGR8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); +#if SPLASH_CMYK + void pipeRunSimpleCMYK8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); +#endif + void pipeRunShapeMono1(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunShapeMono8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunShapeRGB8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunShapeBGR8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); #if SPLASH_CMYK - void pipeRunSimpleCMYK8(SplashPipe *pipe); + void pipeRunShapeCMYK8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); #endif - void pipeRunAAMono1(SplashPipe *pipe); - void pipeRunAAMono8(SplashPipe *pipe); - void pipeRunAARGB8(SplashPipe *pipe); - void pipeRunAABGR8(SplashPipe *pipe); + void pipeRunAAMono1(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunAAMono8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunAARGB8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); + void pipeRunAABGR8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); #if SPLASH_CMYK - void pipeRunAACMYK8(SplashPipe *pipe); + void pipeRunAACMYK8(SplashPipe *pipe, int x0, int x1, int y, + Guchar *shapePtr, SplashColorPtr cSrcPtr); #endif - void pipeSetXY(SplashPipe *pipe, int x, int y); - void pipeIncX(SplashPipe *pipe); - void drawPixel(SplashPipe *pipe, int x, int y, GBool noClip); - void drawAAPixelInit(); - void drawAAPixel(SplashPipe *pipe, int x, int y); - void drawSpan(SplashPipe *pipe, int x0, int x1, int y, GBool noClip); - void drawAALine(SplashPipe *pipe, int x0, int x1, int y); void transform(SplashCoord *matrix, SplashCoord xi, SplashCoord yi, SplashCoord *xo, SplashCoord *yo); void updateModX(int x); void updateModY(int y); void strokeNarrow(SplashPath *path); + void drawStrokeSpan(SplashPipe *pipe, int x0, int x1, int y, GBool noClip); void strokeWide(SplashPath *path, SplashCoord w); SplashPath *flattenPath(SplashPath *path, SplashCoord *matrix, SplashCoord flatness); @@ -288,14 +308,23 @@ private: SplashPath *makeDashedPath(SplashPath *xPath); SplashError fillWithPattern(SplashPath *path, GBool eo, SplashPattern *pattern, SplashCoord alpha); + SplashPath *tweakFillPath(SplashPath *path); GBool pathAllOutside(SplashPath *path); 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(SplashImageMaskSource src, void *srcData, int srcWidth, int srcHeight, - SplashCoord *mat, GBool glyphMode); + SplashCoord *mat, GBool glyphMode, + GBool interpolate); SplashBitmap *scaleMask(SplashImageMaskSource src, void *srcData, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight); + int scaledWidth, int scaledHeight, + GBool interpolate); void scaleMaskYdXd(SplashImageMaskSource src, void *srcData, int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, @@ -312,17 +341,26 @@ private: int srcWidth, int srcHeight, int scaledWidth, int scaledHeight, SplashBitmap *dest); + void scaleMaskYuXuI(SplashImageMaskSource src, void *srcData, + int srcWidth, int srcHeight, + 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(SplashImageSource src, void *srcData, SplashColorMode srcMode, int nComps, GBool srcAlpha, int srcWidth, int srcHeight, - SplashCoord *mat); + SplashCoord *mat, GBool interpolate); SplashBitmap *scaleImage(SplashImageSource src, void *srcData, SplashColorMode srcMode, int nComps, GBool srcAlpha, int srcWidth, int srcHeight, - int scaledWidth, int scaledHeight); + int scaledWidth, int scaledHeight, + GBool interpolate); void scaleImageYdXd(SplashImageSource src, void *srcData, SplashColorMode srcMode, int nComps, GBool srcAlpha, int srcWidth, int srcHeight, @@ -343,8 +381,15 @@ private: 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, @@ -359,13 +404,13 @@ private: static int pipeNonIsoGroupCorrection[]; SplashBitmap *bitmap; + int bitmapComps; SplashState *state; - SplashBitmap *aaBuf; - int aaBufY; - SplashBitmap *alpha0Bitmap; // for non-isolated groups, this is the - // bitmap containing the alpha0 values - int alpha0X, alpha0Y; // offset within alpha0Bitmap - Guchar aaGamma[splashAASize * splashAASize + 1]; + Guchar *scanBuf; + SplashBitmap // for transparency groups, this is the bitmap + *groupBackBitmap; // containing the alpha0/color0 values + int groupBackX, groupBackY; // offset within groupBackBitmap + Guchar aaGamma[256]; SplashCoord minLineWidth; int modXMin, modYMin, modXMax, modYMax; SplashClipResult opClipRes; diff -uNrp xpdf-3.03/splash/SplashMath.h xpdf-3.04/splash/SplashMath.h --- xpdf-3.03/splash/SplashMath.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashMath.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashMath.h // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #ifndef SPLASHMATH_H @@ -35,19 +37,18 @@ static inline int splashFloor(SplashCoor Gushort oldCW, newCW, t; int result; - __asm__ volatile("fldl %4\n" - "fnstcw %0\n" + __asm__ volatile("fnstcw %0\n" "movw %0, %3\n" "andw $0xf3ff, %3\n" "orw $0x0400, %3\n" "movw %3, %1\n" // round down "fldcw %1\n" - "fistpl %2\n" + "fistl %2\n" "fldcw %0\n" : "=m" (oldCW), "=m" (newCW), "=m" (result), "=r" (t) - : "m" (x)); + : "t" (x)); return result; #elif defined(_WIN32) && defined(_M_IX86) // floor() and (int)() are implemented separately, which results // in changing the FPCW multiple times - so we optimize it with // some inline assembly @@ -81,19 +82,18 @@ static inline int splashCeil(SplashCoord Gushort oldCW, newCW, t; int result; - __asm__ volatile("fldl %4\n" - "fnstcw %0\n" + __asm__ volatile("fnstcw %0\n" "movw %0, %3\n" "andw $0xf3ff, %3\n" "orw $0x0800, %3\n" "movw %3, %1\n" // round up "fldcw %1\n" - "fistpl %2\n" + "fistl %2\n" "fldcw %0\n" : "=m" (oldCW), "=m" (newCW), "=m" (result), "=r" (t) - : "m" (x)); + : "t" (x)); return result; #elif defined(_WIN32) && defined(_M_IX86) // ceil() and (int)() are implemented separately, which results // in changing the FPCW multiple times - so we optimize it with // some inline assembly @@ -128,19 +128,18 @@ static inline int splashRound(SplashCoor int result; x += 0.5; - __asm__ volatile("fldl %4\n" - "fnstcw %0\n" + __asm__ volatile("fnstcw %0\n" "movw %0, %3\n" "andw $0xf3ff, %3\n" "orw $0x0400, %3\n" "movw %3, %1\n" // round down "fldcw %1\n" - "fistpl %2\n" + "fistl %2\n" "fldcw %0\n" : "=m" (oldCW), "=m" (newCW), "=m" (result), "=r" (t) - : "m" (x)); + : "t" (x)); return result; #elif defined(_WIN32) && defined(_M_IX86) // this could use round-to-nearest mode and avoid the "+0.5", // but that produces slightly different results (because i+0.5 // sometimes rounds up and sometimes down using the even rule) @@ -223,4 +222,68 @@ static inline GBool splashCheckDet(Splas #endif } +// Perform stroke adjustment on a SplashCoord range [xMin, xMax), +// resulting in an int range [*xMinI, *xMaxI). +// +// There are several options: +// +// 1. Round both edge coordinates. +// Pro: adjacent strokes/fills line up without any gaps or +// overlaps +// Con: lines with the same original floating point width can +// end up with different integer widths, e.g.: +// xMin = 10.1 xMax = 11.3 (width = 1.2) +// --> xMinI = 10 xMaxI = 11 (width = 1) +// but +// xMin = 10.4 xMax = 11.6 (width = 1.2) +// --> xMinI = 10 xMaxI = 12 (width = 2) +// +// 2. Round the min coordinate; add the ceiling of the width. +// Pro: lines with the same original floating point width will +// always end up with the same integer width +// Con: adjacent strokes/fills can have overlaps (which is +// problematic with transparency) +// (This could use floor on the min coordinate, instead of +// rounding, with similar results.) +// (If the width is rounded instead of using ceiling, the results +// Are similar, except that adjacent strokes/fills can have gaps +// as well as overlaps.) +// +// 3. Use floor on the min coordinate and ceiling on the max +// coordinate. +// Pro: lines always end up at least as wide as the original +// floating point width +// Con: adjacent strokes/fills can have overlaps, and lines with +// the same original floating point width can end up with +// different integer widths; the integer width can be more +// than one pixel wider than the original width, e.g.: +// xMin = 10.9 xMax = 12.1 (width = 1.2) +// --> xMinI = 10 xMaxI = 13 (width = 3) +// but +// xMin = 10.1 xMax = 11.3 (width = 1.2) +// --> xMinI = 10 xMaxI = 12 (width = 2) +static inline void splashStrokeAdjust(SplashCoord xMin, SplashCoord xMax, + int *xMinI, int *xMaxI) { + int x0, x1; + + // NB: enable exactly one of these. +#if 1 // 1. Round both edge coordinates. + x0 = splashRound(xMin); + x1 = splashRound(xMax); +#endif +#if 0 // 2. Round the min coordinate; add the ceiling of the width. + x0 = splashRound(xMin); + x1 = x0 + splashCeil(xMax - xMin); +#endif +#if 0 // 3. Use floor on the min coord and ceiling on the max coord. + x0 = splashFloor(xMin); + x1 = splashCeil(xMax); +#endif + if (x1 == x0) { + ++x1; + } + *xMinI = x0; + *xMaxI = x1; +} + #endif diff -uNrp xpdf-3.03/splash/SplashPath.cc xpdf-3.04/splash/SplashPath.cc --- xpdf-3.03/splash/SplashPath.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashPath.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashPath.cc // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #include @@ -53,6 +55,7 @@ SplashPath::SplashPath(SplashPath *path) memcpy(hints, path->hints, hintsLength * sizeof(SplashPathHint)); } else { hints = NULL; + hintsLength = hintsSize = 0; } } diff -uNrp xpdf-3.03/splash/SplashPath.h xpdf-3.04/splash/SplashPath.h --- xpdf-3.03/splash/SplashPath.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashPath.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashPath.h // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #ifndef SPLASHPATH_H @@ -94,7 +96,7 @@ public: // Get the points on the path. int getLength() { return length; } - void getPoint(int i, double *x, double *y, Guchar *f) + void getPoint(int i, SplashCoord *x, SplashCoord *y, Guchar *f) { *x = pts[i].x; *y = pts[i].y; *f = flags[i]; } // Get the current point. diff -uNrp xpdf-3.03/splash/SplashState.cc xpdf-3.04/splash/SplashState.cc --- xpdf-3.03/splash/SplashState.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashState.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashState.cc // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #include @@ -45,7 +47,7 @@ SplashState::SplashState(int width, int blendFunc = NULL; strokeAlpha = 1; fillAlpha = 1; - lineWidth = 0; + lineWidth = 1; lineCap = splashLineCapButt; lineJoin = splashLineJoinMiter; miterLimit = 10; @@ -54,10 +56,12 @@ SplashState::SplashState(int width, int lineDashLength = 0; lineDashPhase = 0; strokeAdjust = gFalse; - clip = new SplashClip(0, 0, width, height, vectorAntialias); + clip = new SplashClip(0, 0, width, height); + clipIsShared = gFalse; softMask = NULL; deleteSoftMask = gFalse; inNonIsolatedGroup = gFalse; + inKnockoutGroup = gFalse; for (i = 0; i < 256; ++i) { rgbTransferR[i] = (Guchar)i; rgbTransferG[i] = (Guchar)i; @@ -87,7 +91,7 @@ SplashState::SplashState(int width, int blendFunc = NULL; strokeAlpha = 1; fillAlpha = 1; - lineWidth = 0; + lineWidth = 1; lineCap = splashLineCapButt; lineJoin = splashLineJoinMiter; miterLimit = 10; @@ -96,10 +100,12 @@ SplashState::SplashState(int width, int lineDashLength = 0; lineDashPhase = 0; strokeAdjust = gFalse; - clip = new SplashClip(0, 0, width, height, vectorAntialias); + clip = new SplashClip(0, 0, width, height); + clipIsShared = gFalse; softMask = NULL; deleteSoftMask = gFalse; inNonIsolatedGroup = gFalse; + inKnockoutGroup = gFalse; for (i = 0; i < 256; ++i) { rgbTransferR[i] = (Guchar)i; rgbTransferG[i] = (Guchar)i; @@ -137,10 +143,12 @@ SplashState::SplashState(SplashState *st } lineDashPhase = state->lineDashPhase; strokeAdjust = state->strokeAdjust; - clip = state->clip->copy(); + clip = state->clip; + clipIsShared = gTrue; softMask = state->softMask; deleteSoftMask = gFalse; inNonIsolatedGroup = state->inNonIsolatedGroup; + inKnockoutGroup = state->inKnockoutGroup; memcpy(rgbTransferR, state->rgbTransferR, 256); memcpy(rgbTransferG, state->rgbTransferG, 256); memcpy(rgbTransferB, state->rgbTransferB, 256); @@ -158,7 +166,9 @@ SplashState::~SplashState() { delete fillPattern; delete screen; gfree(lineDash); - delete clip; + if (!clipIsShared) { + delete clip; + } if (deleteSoftMask && softMask) { delete softMask; } @@ -192,6 +202,32 @@ void SplashState::setLineDash(SplashCoor lineDashPhase = lineDashPhaseA; } +void SplashState::clipResetToRect(SplashCoord x0, SplashCoord y0, + SplashCoord x1, SplashCoord y1) { + if (clipIsShared) { + clip = clip->copy(); + clipIsShared = gFalse; + } + clip->resetToRect(x0, y0, x1, y1); +} + +SplashError SplashState::clipToRect(SplashCoord x0, SplashCoord y0, + SplashCoord x1, SplashCoord y1) { + if (clipIsShared) { + clip = clip->copy(); + clipIsShared = gFalse; + } + return clip->clipToRect(x0, y0, x1, y1); +} + +SplashError SplashState::clipToPath(SplashPath *path, GBool eo) { + if (clipIsShared) { + clip = clip->copy(); + clipIsShared = gFalse; + } + return clip->clipToPath(path, matrix, flatness, eo); +} + void SplashState::setSoftMask(SplashBitmap *softMaskA) { if (deleteSoftMask) { delete softMask; diff -uNrp xpdf-3.03/splash/SplashState.h xpdf-3.04/splash/SplashState.h --- xpdf-3.03/splash/SplashState.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashState.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashState.h // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #ifndef SPLASHSTATE_H @@ -19,6 +21,7 @@ class SplashPattern; class SplashScreen; class SplashClip; class SplashBitmap; +class SplashPath; //------------------------------------------------------------------------ // line cap values @@ -67,6 +70,12 @@ public: void setLineDash(SplashCoord *lineDashA, int lineDashLengthA, SplashCoord lineDashPhaseA); + void clipResetToRect(SplashCoord x0, SplashCoord y0, + SplashCoord x1, SplashCoord y1); + SplashError clipToRect(SplashCoord x0, SplashCoord y0, + SplashCoord x1, SplashCoord y1); + SplashError clipToPath(SplashPath *path, GBool eo); + // Set the soft mask bitmap. void setSoftMask(SplashBitmap *softMaskA); @@ -94,9 +103,11 @@ private: SplashCoord lineDashPhase; GBool strokeAdjust; SplashClip *clip; + GBool clipIsShared; SplashBitmap *softMask; GBool deleteSoftMask; GBool inNonIsolatedGroup; + GBool inKnockoutGroup; Guchar rgbTransferR[256], rgbTransferG[256], rgbTransferB[256]; diff -uNrp xpdf-3.03/splash/SplashT1Font.cc xpdf-3.04/splash/SplashT1Font.cc --- xpdf-3.03/splash/SplashT1Font.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashT1Font.cc 1970-01-01 01:00:00.000000000 +0100 @@ -1,290 +0,0 @@ -//======================================================================== -// -// SplashT1Font.cc -// -//======================================================================== - -#include - -#if HAVE_T1LIB_H - -#ifdef USE_GCC_PRAGMAS -#pragma implementation -#endif - -#include -#include -#include "gmem.h" -#include "SplashMath.h" -#include "SplashGlyphBitmap.h" -#include "SplashPath.h" -#include "SplashT1FontEngine.h" -#include "SplashT1FontFile.h" -#include "SplashT1Font.h" - -//------------------------------------------------------------------------ - -static Guchar bitReverse[256] = { - 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, - 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0, - 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, - 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8, - 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, - 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4, - 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, - 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc, - 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, - 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2, - 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, - 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa, - 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, - 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6, - 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, - 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe, - 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, - 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1, - 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, - 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9, - 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, - 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5, - 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, - 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd, - 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, - 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3, - 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, - 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb, - 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, - 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7, - 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, - 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff -}; - -//------------------------------------------------------------------------ -// SplashT1Font -//------------------------------------------------------------------------ - -SplashT1Font::SplashT1Font(SplashT1FontFile *fontFileA, SplashCoord *matA, - SplashCoord *textMatA): - SplashFont(fontFileA, matA, textMatA, fontFileA->engine->aa) -{ - T1_TMATRIX matrix; - BBox bbox; - SplashCoord bbx0, bby0, bbx1, bby1; - int x, y; - - t1libID = T1_CopyFont(fontFileA->t1libID); - outlineID = -1; - - // compute font size - size = (float)splashDist(0, 0, mat[2], mat[3]); - - // transform the four corners of the font bounding box -- the min - // and max values form the bounding box of the transformed font - bbox = T1_GetFontBBox(t1libID); - bbx0 = 0.001 * bbox.llx; - bby0 = 0.001 * bbox.lly; - bbx1 = 0.001 * bbox.urx; - bby1 = 0.001 * bbox.ury; - // some fonts are completely broken, so we fake it (with values - // large enough that most glyphs should fit) - if (bbx0 == 0 && bby0 == 0 && bbx1 == 0 && bby1 == 0) { - bbx0 = bby0 = -0.5; - bbx1 = bby1 = 1.5; - } - x = (int)(mat[0] * bbx0 + mat[2] * bby0); - xMin = xMax = x; - y = (int)(mat[1] * bbx0 + mat[3] * bby0); - yMin = yMax = y; - x = (int)(mat[0] * bbx0 + mat[2] * bby1); - if (x < xMin) { - xMin = x; - } else if (x > xMax) { - xMax = x; - } - y = (int)(mat[1] * bbx0 + mat[3] * bby1); - if (y < yMin) { - yMin = y; - } else if (y > yMax) { - yMax = y; - } - x = (int)(mat[0] * bbx1 + mat[2] * bby0); - if (x < xMin) { - xMin = x; - } else if (x > xMax) { - xMax = x; - } - y = (int)(mat[1] * bbx1 + mat[3] * bby0); - if (y < yMin) { - yMin = y; - } else if (y > yMax) { - yMax = y; - } - x = (int)(mat[0] * bbx1 + mat[2] * bby1); - if (x < xMin) { - xMin = x; - } else if (x > xMax) { - xMax = x; - } - y = (int)(mat[1] * bbx1 + mat[3] * bby1); - if (y < yMin) { - yMin = y; - } else if (y > yMax) { - yMax = y; - } - // This is a kludge: some buggy PDF generators embed fonts with - // zero bounding boxes. - if (xMax == xMin) { - xMin = 0; - xMax = (int)size; - } - if (yMax == yMin) { - yMin = 0; - yMax = (int)(1.2 * size); - } - // Another kludge: an unusually large xMin or yMin coordinate is - // probably wrong. - if (xMin > 0) { - xMin = 0; - } - if (yMin > 0) { - yMin = 0; - } - // Another kludge: t1lib doesn't correctly handle fonts with - // real (non-integer) bounding box coordinates. - if (xMax - xMin > 5000) { - xMin = 0; - xMax = (int)size; - } - if (yMax - yMin > 5000) { - yMin = 0; - yMax = (int)(1.2 * size); - } - - // transform the font - matrix.cxx = (double)mat[0] / size; - matrix.cxy = (double)mat[1] / size; - matrix.cyx = (double)mat[2] / size; - matrix.cyy = (double)mat[3] / size; - T1_TransformFont(t1libID, &matrix); -} - -SplashT1Font::~SplashT1Font() { - T1_DeleteFont(t1libID); - if (outlineID >= 0) { - T1_DeleteFont(outlineID); - } -} - -GBool SplashT1Font::getGlyph(int c, int xFrac, int yFrac, - SplashGlyphBitmap *bitmap) { - return SplashFont::getGlyph(c, 0, 0, bitmap); -} - -GBool SplashT1Font::makeGlyph(int c, int xFrac, int yFrac, - SplashGlyphBitmap *bitmap) { - GLYPH *glyph; - int n, i; - - if (aa) { - glyph = T1_AASetChar(t1libID, c, size, NULL); - } else { - glyph = T1_SetChar(t1libID, c, size, NULL); - } - if (!glyph) { - return gFalse; - } - - bitmap->x = -glyph->metrics.leftSideBearing; - bitmap->y = glyph->metrics.ascent; - bitmap->w = glyph->metrics.rightSideBearing - glyph->metrics.leftSideBearing; - bitmap->h = glyph->metrics.ascent - glyph->metrics.descent; - bitmap->aa = aa; - if (aa) { - bitmap->data = (Guchar *)glyph->bits; - bitmap->freeData = gFalse; - } else { - n = bitmap->h * ((bitmap->w + 7) >> 3); - bitmap->data = (Guchar *)gmalloc(n); - for (i = 0; i < n; ++i) { - bitmap->data[i] = bitReverse[glyph->bits[i] & 0xff]; - } - bitmap->freeData = gTrue; - } - - return gTrue; -} - -SplashPath *SplashT1Font::getGlyphPath(int c) { - T1_TMATRIX matrix; - SplashPath *path; - T1_OUTLINE *outline; - T1_PATHSEGMENT *seg; - T1_BEZIERSEGMENT *bez; - int x, y, x1, y1; - GBool needClose; - - if (outlineID < 0) { - outlineID = T1_CopyFont(((SplashT1FontFile *)fontFile)->t1libID); - outlineSize = (float)splashDist(0, 0, textMat[2], textMat[3]); - matrix.cxx = (double)textMat[0] / outlineSize; - matrix.cxy = (double)textMat[1] / outlineSize; - matrix.cyx = (double)textMat[2] / outlineSize; - matrix.cyy = (double)textMat[3] / outlineSize; - // t1lib doesn't seem to handle small sizes correctly here, so set - // the size to 1000, and scale the resulting coordinates later - outlineMul = (float)(outlineSize / 65536000.0); - outlineSize = 1000; - T1_TransformFont(outlineID, &matrix); - } - - path = new SplashPath(); - if ((outline = T1_GetCharOutline(outlineID, c, outlineSize, NULL))) { - // NB: t1lib uses integer coordinates here; we keep a running - // (x,y) total as integers, so that the final point in the path is - // exactly the same as the first point, thus avoiding weird - // mitered join glitches - x = y = 0; - needClose = gFalse; - for (seg = outline; seg; seg = seg->link) { - switch (seg->type) { - case T1_PATHTYPE_MOVE: - if (needClose) { - path->close(); - needClose = gFalse; - } - x += seg->dest.x; - y += seg->dest.y; - path->moveTo(outlineMul * x, -outlineMul * y); - break; - case T1_PATHTYPE_LINE: - x += seg->dest.x; - y += seg->dest.y; - path->lineTo(outlineMul * x, -outlineMul * y); - needClose = gTrue; - break; - case T1_PATHTYPE_BEZIER: - bez = (T1_BEZIERSEGMENT *)seg; - x1 = x + bez->dest.x; - y1 = y + bez->dest.y; - path->curveTo(outlineMul * (x + bez->B.x), - -outlineMul * (y + bez->B.y), - outlineMul * (x + bez->C.x), - -outlineMul * (y + bez->C.y), - outlineMul * x1, - -outlineMul * y1); - x = x1; - y = y1; - needClose = gTrue; - break; - } - } - if (needClose) { - path->close(); - } - T1_FreeOutline(outline); - } - - return path; -} - -#endif // HAVE_T1LIB_H diff -uNrp xpdf-3.03/splash/SplashT1FontEngine.cc xpdf-3.04/splash/SplashT1FontEngine.cc --- xpdf-3.03/splash/SplashT1FontEngine.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashT1FontEngine.cc 1970-01-01 01:00:00.000000000 +0100 @@ -1,124 +0,0 @@ -//======================================================================== -// -// SplashT1FontEngine.cc -// -//======================================================================== - -#include - -#if HAVE_T1LIB_H - -#ifdef USE_GCC_PRAGMAS -#pragma implementation -#endif - -#include -#include -#ifndef WIN32 -# include -#endif -#include -#include "GString.h" -#include "gfile.h" -#include "FoFiType1C.h" -#include "SplashT1FontFile.h" -#include "SplashT1FontEngine.h" - -#ifdef VMS -#if (__VMS_VER < 70000000) -extern "C" int unlink(char *filename); -#endif -#endif - -//------------------------------------------------------------------------ - -int SplashT1FontEngine::t1libInitCount = 0; - -//------------------------------------------------------------------------ - -static void fileWrite(void *stream, const char *data, int len) { - fwrite(data, 1, len, (FILE *)stream); -} - -//------------------------------------------------------------------------ -// SplashT1FontEngine -//------------------------------------------------------------------------ - -SplashT1FontEngine::SplashT1FontEngine(GBool aaA) { - aa = aaA; -} - -SplashT1FontEngine *SplashT1FontEngine::init(GBool aaA) { - // grayVals[i] = round(i * 255 / 16) - static unsigned long grayVals[17] = { - 0, 16, 32, 48, 64, 80, 96, 112, 128, 143, 159, 175, 191, 207, 223, 239, 255 - }; - - //~ for multithreading: need a mutex here - if (t1libInitCount == 0) { - T1_SetBitmapPad(8); - if (!T1_InitLib(NO_LOGFILE | IGNORE_CONFIGFILE | IGNORE_FONTDATABASE | - T1_NO_AFM)) { - return NULL; - } - if (aaA) { - T1_AASetBitsPerPixel(8); - T1_AASetLevel(T1_AA_HIGH); - T1_AAHSetGrayValues(grayVals); - } else { - T1_AANSetGrayValues(0, 1); - } - } - ++t1libInitCount; - - return new SplashT1FontEngine(aaA); -} - -SplashT1FontEngine::~SplashT1FontEngine() { - //~ for multithreading: need a mutex here - if (--t1libInitCount == 0) { - T1_CloseLib(); - } -} - -SplashFontFile *SplashT1FontEngine::loadType1Font(SplashFontFileID *idA, - char *fileName, - GBool deleteFile, - const char **enc) { - return SplashT1FontFile::loadType1Font(this, idA, fileName, deleteFile, enc); -} - -SplashFontFile *SplashT1FontEngine::loadType1CFont(SplashFontFileID *idA, - char *fileName, - GBool deleteFile, - const char **enc) { - FoFiType1C *ff; - GString *tmpFileName; - FILE *tmpFile; - SplashFontFile *ret; - - if (!(ff = FoFiType1C::load(fileName))) { - return NULL; - } - tmpFileName = NULL; - if (!openTempFile(&tmpFileName, &tmpFile, "wb", NULL)) { - delete ff; - return NULL; - } - ff->convertToType1(NULL, NULL, gTrue, &fileWrite, tmpFile); - delete ff; - fclose(tmpFile); - ret = SplashT1FontFile::loadType1Font(this, idA, tmpFileName->getCString(), - gTrue, enc); - if (ret) { - if (deleteFile) { - unlink(fileName); - } - } else { - unlink(tmpFileName->getCString()); - } - delete tmpFileName; - return ret; -} - -#endif // HAVE_T1LIB_H diff -uNrp xpdf-3.03/splash/SplashT1FontEngine.h xpdf-3.04/splash/SplashT1FontEngine.h --- xpdf-3.03/splash/SplashT1FontEngine.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashT1FontEngine.h 1970-01-01 01:00:00.000000000 +0100 @@ -1,53 +0,0 @@ -//======================================================================== -// -// SplashT1FontEngine.h -// -//======================================================================== - -#ifndef SPLASHT1FONTENGINE_H -#define SPLASHT1FONTENGINE_H - -#include - -#if HAVE_T1LIB_H - -#ifdef USE_GCC_PRAGMAS -#pragma interface -#endif - -#include "gtypes.h" - -class SplashFontFile; -class SplashFontFileID; - -//------------------------------------------------------------------------ -// SplashT1FontEngine -//------------------------------------------------------------------------ - -class SplashT1FontEngine { -public: - - static SplashT1FontEngine *init(GBool aaA); - - ~SplashT1FontEngine(); - - // Load fonts. - SplashFontFile *loadType1Font(SplashFontFileID *idA, char *fileName, - GBool deleteFile, const char **enc); - SplashFontFile *loadType1CFont(SplashFontFileID *idA, char *fileName, - GBool deleteFile, const char **enc); - -private: - - SplashT1FontEngine(GBool aaA); - - static int t1libInitCount; - GBool aa; - - friend class SplashT1FontFile; - friend class SplashT1Font; -}; - -#endif // HAVE_T1LIB_H - -#endif diff -uNrp xpdf-3.03/splash/SplashT1FontFile.cc xpdf-3.04/splash/SplashT1FontFile.cc --- xpdf-3.03/splash/SplashT1FontFile.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashT1FontFile.cc 1970-01-01 01:00:00.000000000 +0100 @@ -1,98 +0,0 @@ -//======================================================================== -// -// SplashT1FontFile.cc -// -//======================================================================== - -#include - -#if HAVE_T1LIB_H - -#ifdef USE_GCC_PRAGMAS -#pragma implementation -#endif - -#include -#include -#include "gmem.h" -#include "SplashT1FontEngine.h" -#include "SplashT1Font.h" -#include "SplashT1FontFile.h" - -//------------------------------------------------------------------------ -// SplashT1FontFile -//------------------------------------------------------------------------ - -SplashFontFile *SplashT1FontFile::loadType1Font(SplashT1FontEngine *engineA, - SplashFontFileID *idA, - char *fileNameA, - GBool deleteFileA, - const char **encA) { - int t1libIDA; - const char **encTmp; - char *encStrTmp; - int encStrSize; - char *encPtr; - int i; - - // load the font file - if ((t1libIDA = T1_AddFont(fileNameA)) < 0) { - return NULL; - } - T1_LoadFont(t1libIDA); - - // reencode it - encStrSize = 0; - for (i = 0; i < 256; ++i) { - if (encA[i]) { - encStrSize += strlen(encA[i]) + 1; - } - } - encTmp = (const char **)gmallocn(257, sizeof(char *)); - encStrTmp = (char *)gmallocn(encStrSize, sizeof(char)); - encPtr = encStrTmp; - for (i = 0; i < 256; ++i) { - if (encA[i]) { - strcpy(encPtr, encA[i]); - encTmp[i] = encPtr; - encPtr += strlen(encPtr) + 1; - } else { - encTmp[i] = ".notdef"; - } - } - encTmp[256] = "custom"; - T1_ReencodeFont(t1libIDA, (char **)encTmp); - - return new SplashT1FontFile(engineA, idA, fileNameA, deleteFileA, - t1libIDA, encTmp, encStrTmp); -} - -SplashT1FontFile::SplashT1FontFile(SplashT1FontEngine *engineA, - SplashFontFileID *idA, - char *fileNameA, GBool deleteFileA, - int t1libIDA, const char **encA, - char *encStrA): - SplashFontFile(idA, fileNameA, deleteFileA) -{ - engine = engineA; - t1libID = t1libIDA; - enc = encA; - encStr = encStrA; -} - -SplashT1FontFile::~SplashT1FontFile() { - gfree(encStr); - gfree(enc); - T1_DeleteFont(t1libID); -} - -SplashFont *SplashT1FontFile::makeFont(SplashCoord *mat, - SplashCoord *textMat) { - SplashFont *font; - - font = new SplashT1Font(this, mat, textMat); - font->initCache(); - return font; -} - -#endif // HAVE_T1LIB_H diff -uNrp xpdf-3.03/splash/SplashT1FontFile.h xpdf-3.04/splash/SplashT1FontFile.h --- xpdf-3.03/splash/SplashT1FontFile.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashT1FontFile.h 1970-01-01 01:00:00.000000000 +0100 @@ -1,58 +0,0 @@ -//======================================================================== -// -// SplashT1FontFile.h -// -//======================================================================== - -#ifndef SPLASHT1FONTFILE_H -#define SPLASHT1FONTFILE_H - -#include - -#if HAVE_T1LIB_H - -#ifdef USE_GCC_PRAGMAS -#pragma interface -#endif - -#include "SplashFontFile.h" - -class SplashT1FontEngine; - -//------------------------------------------------------------------------ -// SplashT1FontFile -//------------------------------------------------------------------------ - -class SplashT1FontFile: public SplashFontFile { -public: - - static SplashFontFile *loadType1Font(SplashT1FontEngine *engineA, - SplashFontFileID *idA, - char *fileNameA, GBool deleteFileA, - const char **encA); - - virtual ~SplashT1FontFile(); - - // Create a new SplashT1Font, i.e., a scaled instance of this font - // file. - virtual SplashFont *makeFont(SplashCoord *mat, - SplashCoord *textMat); - -private: - - SplashT1FontFile(SplashT1FontEngine *engineA, - SplashFontFileID *idA, - char *fileNameA, GBool deleteFileA, - int t1libIDA, const char **encA, char *encStrA); - - SplashT1FontEngine *engine; - int t1libID; // t1lib font ID - const char **enc; - char *encStr; - - friend class SplashT1Font; -}; - -#endif // HAVE_T1LIB_H - -#endif diff -uNrp xpdf-3.03/splash/SplashT1Font.h xpdf-3.04/splash/SplashT1Font.h --- xpdf-3.03/splash/SplashT1Font.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashT1Font.h 1970-01-01 01:00:00.000000000 +0100 @@ -1,57 +0,0 @@ -//======================================================================== -// -// SplashT1Font.h -// -//======================================================================== - -#ifndef SPLASHT1FONT_H -#define SPLASHT1FONT_H - -#include - -#if HAVE_T1LIB_H - -#ifdef USE_GCC_PRAGMAS -#pragma interface -#endif - -#include "SplashFont.h" - -class SplashT1FontFile; - -//------------------------------------------------------------------------ -// SplashT1Font -//------------------------------------------------------------------------ - -class SplashT1Font: public SplashFont { -public: - - SplashT1Font(SplashT1FontFile *fontFileA, SplashCoord *matA, - SplashCoord *textMatA); - - virtual ~SplashT1Font(); - - // Munge xFrac and yFrac before calling SplashFont::getGlyph. - virtual GBool getGlyph(int c, int xFrac, int yFrac, - SplashGlyphBitmap *bitmap); - - // Rasterize a glyph. The and values are the same - // as described for getGlyph. - virtual GBool makeGlyph(int c, int xFrac, int yFrac, - SplashGlyphBitmap *bitmap); - - // Return the path for a glyph. - virtual SplashPath *getGlyphPath(int c); - -private: - - int t1libID; // t1lib font ID - int outlineID; // t1lib font ID for glyph outlines - float size; - float outlineSize; // size for glyph outlines - float outlineMul; -}; - -#endif // HAVE_T1LIB_H - -#endif diff -uNrp xpdf-3.03/splash/SplashXPath.cc xpdf-3.04/splash/SplashXPath.cc --- xpdf-3.03/splash/SplashXPath.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashXPath.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashXPath.cc // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #include @@ -54,12 +56,10 @@ inline void SplashXPath::transform(Splas SplashXPath::SplashXPath(SplashPath *path, SplashCoord *matrix, SplashCoord flatness, GBool closeSubpaths) { - SplashPathHint *hint; SplashXPathPoint *pts; - SplashXPathAdjust *adjusts, *adjust; SplashCoord x0, y0, x1, y1, x2, y2, x3, y3, xsp, ysp; - SplashCoord adj0, adj1; - int curSubpath, curSubpathX, i, j; + SplashCoord xMinFP, xMaxFP, yMinFP, yMaxFP; + int curSubpath, i; // transform the points pts = (SplashXPathPoint *)gmallocn(path->length, sizeof(SplashXPathPoint)); @@ -67,78 +67,16 @@ SplashXPath::SplashXPath(SplashPath *pat transform(matrix, path->pts[i].x, path->pts[i].y, &pts[i].x, &pts[i].y); } - // set up the stroke adjustment hints + // do stroke adjustment if (path->hints) { - adjusts = (SplashXPathAdjust *)gmallocn(path->hintsLength, - sizeof(SplashXPathAdjust)); - for (i = 0; i < path->hintsLength; ++i) { - hint = &path->hints[i]; - x0 = pts[hint->ctrl0 ].x; y0 = pts[hint->ctrl0 ].y; - x1 = pts[hint->ctrl0 + 1].x; y1 = pts[hint->ctrl0 + 1].y; - x2 = pts[hint->ctrl1 ].x; y2 = pts[hint->ctrl1 ].y; - x3 = pts[hint->ctrl1 + 1].x; y3 = pts[hint->ctrl1 + 1].y; - if (x0 == x1 && x2 == x3) { - adjusts[i].vert = gTrue; - adj0 = x0; - adj1 = x2; - } else if (y0 == y1 && y2 == y3) { - adjusts[i].vert = gFalse; - adj0 = y0; - adj1 = y2; - } else { - gfree(adjusts); - adjusts = NULL; - break; - } - if (adj0 > adj1) { - x0 = adj0; - adj0 = adj1; - adj1 = x0; - } - adjusts[i].x0a = adj0 - 0.01; - adjusts[i].x0b = adj0 + 0.01; - adjusts[i].xma = (SplashCoord)0.5 * (adj0 + adj1) - 0.01; - adjusts[i].xmb = (SplashCoord)0.5 * (adj0 + adj1) + 0.01; - adjusts[i].x1a = adj1 - 0.01; - adjusts[i].x1b = adj1 + 0.01; - // rounding both edge coordinates can result in lines of - // different widths (e.g., adj=10.1, adj1=11.3 --> x0=10, x1=11; - // adj0=10.4, adj1=11.6 --> x0=10, x1=12), but it has the - // benefit of making adjacent strokes/fills line up without any - // gaps between them - x0 = splashRound(adj0); - x1 = splashRound(adj1); - if (x1 == x0) { - x1 = x1 + 1; - } - adjusts[i].x0 = (SplashCoord)x0; - adjusts[i].x1 = (SplashCoord)x1 - 0.01; - adjusts[i].xm = (SplashCoord)0.5 * (adjusts[i].x0 + adjusts[i].x1); - adjusts[i].firstPt = hint->firstPt; - adjusts[i].lastPt = hint->lastPt; - } - - } else { - adjusts = NULL; - } - - // perform stroke adjustment - if (adjusts) { - for (i = 0, adjust = adjusts; i < path->hintsLength; ++i, ++adjust) { - for (j = adjust->firstPt; j <= adjust->lastPt; ++j) { - strokeAdjust(adjust, &pts[j].x, &pts[j].y); - } - } - gfree(adjusts); + strokeAdjust(pts, path->hints, path->hintsLength); } segs = NULL; length = size = 0; x0 = y0 = xsp = ysp = 0; // make gcc happy - adj0 = adj1 = 0; // make gcc happy curSubpath = 0; - curSubpathX = 0; i = 0; while (i < path->length) { @@ -149,7 +87,6 @@ SplashXPath::SplashXPath(SplashPath *pat xsp = x0; ysp = y0; curSubpath = i; - curSubpathX = length; ++i; } else { @@ -197,32 +134,130 @@ SplashXPath::SplashXPath(SplashPath *pat } gfree(pts); + +#if HAVE_STD_SORT + std::sort(segs, segs + length, SplashXPathSeg::cmpY); +#else + qsort(segs, length, sizeof(SplashXPathSeg), &SplashXPathSeg::cmpY); +#endif + + if (length == 0) { + xMin = yMin = xMax = yMax = 0; + } else { + if (segs[0].x0 < segs[0].x1) { + xMinFP = segs[0].x0; + xMaxFP = segs[0].x1; + } else { + xMinFP = segs[0].x1; + xMaxFP = segs[0].x0; + } + yMinFP = segs[0].y0; + yMaxFP = segs[0].y1; + for (i = 1; i < length; ++i) { + if (segs[i].x0 < xMinFP) { + xMinFP = segs[i].x0; + } else if (segs[i].x0 > xMaxFP) { + xMaxFP = segs[i].x0; + } + if (segs[i].x1 < xMinFP) { + xMinFP = segs[i].x1; + } else if (segs[i].x1 > xMaxFP) { + xMaxFP = segs[i].x1; + } + if (segs[i].y1 > yMaxFP) { + yMaxFP = segs[i].y1; + } + } + xMin = splashFloor(xMinFP); + yMin = splashFloor(yMinFP); + xMax = splashFloor(xMaxFP); + yMax = splashFloor(yMaxFP); + } } -// Apply the stroke adjust hints to point : (*, *). -void SplashXPath::strokeAdjust(SplashXPathAdjust *adjust, - SplashCoord *xp, SplashCoord *yp) { - SplashCoord x, y; +void SplashXPath::strokeAdjust(SplashXPathPoint *pts, + SplashPathHint *hints, int nHints) { + SplashXPathAdjust *adjusts, *adjust; + SplashPathHint *hint; + SplashCoord x0, y0, x1, y1, x2, y2, x3, y3; + SplashCoord adj0, adj1, d; + int xi0, xi1; + int i, j; - if (adjust->vert) { - x = *xp; - if (x > adjust->x0a && x < adjust->x0b) { - *xp = adjust->x0; - } else if (x > adjust->xma && x < adjust->xmb) { - *xp = adjust->xm; - } else if (x > adjust->x1a && x < adjust->x1b) { - *xp = adjust->x1; + // set up the stroke adjustment hints + adjusts = (SplashXPathAdjust *)gmallocn(nHints, sizeof(SplashXPathAdjust)); + for (i = 0; i < nHints; ++i) { + hint = &hints[i]; + x0 = pts[hint->ctrl0 ].x; y0 = pts[hint->ctrl0 ].y; + x1 = pts[hint->ctrl0 + 1].x; y1 = pts[hint->ctrl0 + 1].y; + x2 = pts[hint->ctrl1 ].x; y2 = pts[hint->ctrl1 ].y; + x3 = pts[hint->ctrl1 + 1].x; y3 = pts[hint->ctrl1 + 1].y; + if (x0 == x1 && x2 == x3) { + adjusts[i].vert = gTrue; + adj0 = x0; + adj1 = x2; + } else if (y0 == y1 && y2 == y3) { + adjusts[i].vert = gFalse; + adj0 = y0; + adj1 = y2; + } else { + goto done; } - } else { - y = *yp; - if (y > adjust->x0a && y < adjust->x0b) { - *yp = adjust->x0; - } else if (y > adjust->xma && y < adjust->xmb) { - *yp = adjust->xm; - } else if (y > adjust->x1a && y < adjust->x1b) { - *yp = adjust->x1; + if (adj0 > adj1) { + x0 = adj0; + adj0 = adj1; + adj1 = x0; + } + d = adj1 - adj0; + if (d > 0.04) { + d = 0.01; + } else { + d *= 0.25; + } + adjusts[i].x0a = adj0 - d; + adjusts[i].x0b = adj0 + d; + adjusts[i].xma = (SplashCoord)0.5 * (adj0 + adj1) - d; + adjusts[i].xmb = (SplashCoord)0.5 * (adj0 + adj1) + d; + adjusts[i].x1a = adj1 - d; + adjusts[i].x1b = adj1 + d; + splashStrokeAdjust(adj0, adj1, &xi0, &xi1); + adjusts[i].x0 = (SplashCoord)xi0; + // the "minus epsilon" thing here is needed when vector + // antialiasing is turned off -- otherwise stroke adjusted lines + // will touch an extra pixel on one edge + adjusts[i].x1 = (SplashCoord)xi1 - 0.001; + adjusts[i].xm = (SplashCoord)0.5 * (adjusts[i].x0 + adjusts[i].x1); + adjusts[i].firstPt = hint->firstPt; + adjusts[i].lastPt = hint->lastPt; + } + + // perform stroke adjustment + for (i = 0, adjust = adjusts; i < nHints; ++i, ++adjust) { + for (j = adjust->firstPt; j <= adjust->lastPt; ++j) { + if (adjust->vert) { + x0 = pts[j].x; + if (x0 > adjust->x0a && x0 < adjust->x0b) { + pts[j].x = adjust->x0; + } else if (x0 > adjust->xma && x0 < adjust->xmb) { + pts[j].x = adjust->xm; + } else if (x0 > adjust->x1a && x0 < adjust->x1b) { + pts[j].x = adjust->x1; + } + } else { + y0 = pts[j].y; + if (y0 > adjust->x0a && y0 < adjust->x0b) { + pts[j].y = adjust->x0; + } else if (y0 > adjust->xma && y0 < adjust->xmb) { + pts[j].y = adjust->xm; + } else if (y0 > adjust->x1a && y0 < adjust->x1b) { + pts[j].y = adjust->x1; + } + } } } + + done: + gfree(adjusts); } SplashXPath::SplashXPath(SplashXPath *xPath) { @@ -230,6 +265,10 @@ SplashXPath::SplashXPath(SplashXPath *xP size = xPath->size; segs = (SplashXPathSeg *)gmallocn(size, sizeof(SplashXPathSeg)); memcpy(segs, xPath->segs, length * sizeof(SplashXPathSeg)); + xMin = xPath->xMin; + yMin = xPath->yMin; + xMax = xPath->xMax; + yMax = xPath->yMax; } SplashXPath::~SplashXPath() { @@ -341,115 +380,38 @@ void SplashXPath::addCurve(SplashCoord x void SplashXPath::addSegment(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1) { grow(1); - segs[length].x0 = x0; - segs[length].y0 = y0; - segs[length].x1 = x1; - segs[length].y1 = y1; - segs[length].flags = 0; - if (y1 == y0) { - segs[length].dxdy = segs[length].dydx = 0; - segs[length].flags |= splashXPathHoriz; - if (x1 == x0) { - segs[length].flags |= splashXPathVert; - } - } else if (x1 == x0) { - segs[length].dxdy = segs[length].dydx = 0; - segs[length].flags |= splashXPathVert; + if (y0 <= y1) { + segs[length].x0 = x0; + segs[length].y0 = y0; + segs[length].x1 = x1; + segs[length].y1 = y1; + segs[length].count = 1; } else { + segs[length].x0 = x1; + segs[length].y0 = y1; + segs[length].x1 = x0; + segs[length].y1 = y0; + segs[length].count = -1; + } #if USE_FIXEDPOINT - if (FixedPoint::divCheck(x1 - x0, y1 - y0, &segs[length].dxdy)) { - segs[length].dydx = (SplashCoord)1 / segs[length].dxdy; - } else { - segs[length].dxdy = segs[length].dydx = 0; - if (splashAbs(x1 - x0) > splashAbs(y1 - y0)) { - segs[length].flags |= splashXPathHoriz; - } else { - segs[length].flags |= splashXPathVert; - } - } + if (y0 == y1 || x0 == x1 || + !FixedPoint::divCheck(x1 - x0, y1 - y0, &segs[length].dxdy) || + !FixedPoint::divCheck(y1 - y0, x1 - x0, &segs[length].dydx)) { + segs[length].dxdy = 0; + segs[length].dydx = 0; + } #else + if (y0 == y1 || x0 == x1) { + segs[length].dxdy = 0; + segs[length].dydx = 0; + } else { segs[length].dxdy = (x1 - x0) / (y1 - y0); - segs[length].dydx = (SplashCoord)1 / segs[length].dxdy; -#endif - } - if (y0 > y1) { - segs[length].flags |= splashXPathFlip; - } - ++length; -} - -void SplashXPath::aaScale() { - SplashXPathSeg *seg; - int i; - - for (i = 0, seg = segs; i < length; ++i, ++seg) { - seg->x0 *= splashAASize; - seg->y0 *= splashAASize; - seg->x1 *= splashAASize; - seg->y1 *= splashAASize; - } -} - -#if HAVE_STD_SORT - -struct cmpXPathSegsFunctor { - bool operator()(const SplashXPathSeg &seg0, const SplashXPathSeg &seg1) { - SplashCoord x0, y0, x1, y1; - - if (seg0.flags & splashXPathFlip) { - x0 = seg0.x1; - y0 = seg0.y1; + if (segs[length].dxdy == 0) { + segs[length].dydx = 0; } else { - x0 = seg0.x0; - y0 = seg0.y0; + segs[length].dydx = 1 / segs[length].dxdy; } - if (seg1.flags & splashXPathFlip) { - x1 = seg1.x1; - y1 = seg1.y1; - } else { - x1 = seg1.x0; - y1 = seg1.y0; - } - return (y0 != y1) ? (y0 < y1) : (x0 < x1); - } -}; - -#else // HAVE_STD_SORT - -static int cmpXPathSegs(const void *arg0, const void *arg1) { - SplashXPathSeg *seg0 = (SplashXPathSeg *)arg0; - SplashXPathSeg *seg1 = (SplashXPathSeg *)arg1; - SplashCoord x0, y0, x1, y1; - - if (seg0->flags & splashXPathFlip) { - x0 = seg0->x1; - y0 = seg0->y1; - } else { - x0 = seg0->x0; - y0 = seg0->y0; } - if (seg1->flags & splashXPathFlip) { - x1 = seg1->x1; - y1 = seg1->y1; - } else { - x1 = seg1->x0; - y1 = seg1->y0; - } - if (y0 != y1) { - return (y0 > y1) ? 1 : -1; - } - if (x0 != x1) { - return (x0 > x1) ? 1 : -1; - } - return 0; -} - -#endif // HAVE_STD_SORT - -void SplashXPath::sort() { -#if HAVE_STD_SORT - std::sort(segs, segs + length, cmpXPathSegsFunctor()); -#else - qsort(segs, length, sizeof(SplashXPathSeg), &cmpXPathSegs); #endif + ++length; } diff -uNrp xpdf-3.03/splash/SplashXPath.h xpdf-3.04/splash/SplashXPath.h --- xpdf-3.03/splash/SplashXPath.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashXPath.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashXPath.h // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #ifndef SPLASHXPATH_H @@ -16,7 +18,8 @@ #include "SplashTypes.h" class SplashPath; -struct SplashXPathAdjust; +struct SplashXPathPoint; +struct SplashPathHint; //------------------------------------------------------------------------ @@ -27,18 +30,44 @@ struct SplashXPathAdjust; //------------------------------------------------------------------------ struct SplashXPathSeg { - SplashCoord x0, y0; // first endpoint + SplashCoord x0, y0; // first endpoint (y0 <= y1) SplashCoord x1, y1; // second endpoint SplashCoord dxdy; // slope: delta-x / delta-y SplashCoord dydx; // slope: delta-y / delta-x - Guint flags; -}; + int count; // EO/NZWN counter increment + + //----- used by SplashXPathScanner + SplashCoord xCur0, xCur1; // current x values + +#if HAVE_STD_SORT + static bool cmpY(const SplashXPathSeg &seg0, + const SplashXPathSeg &seg1) { + return seg0.y0 < seg1.y0; + } +#else + static int cmpY(const void *seg0, const void *seg1) { + SplashCoord cmp; + + cmp = ((SplashXPathSeg *)seg0)->y0 + - ((SplashXPathSeg *)seg1)->y0; + return (cmp > 0) ? 1 : (cmp < 0) ? -1 : 0; + } +#endif -#define splashXPathHoriz 0x01 // segment is vertical (y0 == y1) - // (dxdy is undef) -#define splashXPathVert 0x02 // segment is horizontal (x0 == x1) - // (dydx is undef) -#define splashXPathFlip 0x04 // y0 > y1 + static int cmpX(SplashXPathSeg *seg0, SplashXPathSeg *seg1) { + SplashCoord cmp; + + if ((cmp = seg0->xCur0 - seg1->xCur0) == 0) { + cmp = seg0->dxdy - seg1->dxdy; + } + return (cmp > 0) ? 1 : (cmp < 0) ? -1 : 0; + } + + static int cmpXi(const void *p0, const void *p1) { + return cmpX(*(SplashXPathSeg **)p0, *(SplashXPathSeg **)p1); + } + +}; //------------------------------------------------------------------------ // SplashXPath @@ -59,20 +88,18 @@ public: ~SplashXPath(); - // Multiply all coordinates by splashAASize, in preparation for - // anti-aliased rendering. - void aaScale(); - - // Sort by upper coordinate (lower y), in y-major order. - void sort(); + int getXMin() { return xMin; } + int getXMax() { return xMax; } + int getYMin() { return yMin; } + int getYMax() { return yMax; } private: SplashXPath(SplashXPath *xPath); void transform(SplashCoord *matrix, SplashCoord xi, SplashCoord yi, SplashCoord *xo, SplashCoord *yo); - void strokeAdjust(SplashXPathAdjust *adjust, - SplashCoord *xp, SplashCoord *yp); + void strokeAdjust(SplashXPathPoint *pts, + SplashPathHint *hints, int nHints); void grow(int nSegs); void addCurve(SplashCoord x0, SplashCoord y0, SplashCoord x1, SplashCoord y1, @@ -85,6 +112,8 @@ private: SplashXPathSeg *segs; int length, size; // length and size of segs array + int xMin, xMax; + int yMin, yMax; friend class SplashXPathScanner; friend class SplashClip; diff -uNrp xpdf-3.03/splash/SplashXPathScanner.cc xpdf-3.04/splash/SplashXPathScanner.cc --- xpdf-3.03/splash/SplashXPathScanner.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashXPathScanner.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashXPathScanner.cc // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #include @@ -16,523 +18,550 @@ #include #endif #include "gmem.h" +#include "GList.h" #include "SplashMath.h" #include "SplashXPath.h" -#include "SplashBitmap.h" #include "SplashXPathScanner.h" //------------------------------------------------------------------------ -struct SplashIntersect { - int y; - int x0, x1; // intersection of segment with [y, y+1) - int count; // EO/NZWN counter increment -}; +#define minVertStep 0.05 -#if HAVE_STD_SORT +//------------------------------------------------------------------------ -struct cmpIntersectFunctor { - bool operator()(const SplashIntersect &i0, const SplashIntersect &i1) { - return (i0.y != i1.y) ? (i0.y < i1.y) : (i0.x0 < i1.x0); - } -}; +SplashXPathScanner::SplashXPathScanner(SplashXPath *xPathA, GBool eoA, + int yMinA, int yMaxA) { + xPath = xPathA; + eo = eoA; + yMin = yMinA; + yMax = yMaxA; -#else // HAVE_STD_SORT + activeSegs = new GList(); + nextSeg = 0; + yNext = xPath->yMin; +} -static int cmpIntersect(const void *p0, const void *p1) { - SplashIntersect *i0 = (SplashIntersect *)p0; - SplashIntersect *i1 = (SplashIntersect *)p1; - int cmp; +SplashXPathScanner::~SplashXPathScanner() { + delete activeSegs; +} - if ((cmp = i0->y - i1->y) == 0) { - cmp = i0->x0 - i1->x0; +void SplashXPathScanner::getSpan(Guchar *line, int y, int x0, int x1) { + SplashXPathSeg *seg, *seg0; + SplashCoord y0, y1, y1p; + GBool intersect, last; + int eoMask, state0, state1, count, i; + + //--- clear the scan line buffer + memset(line + x0, 0, x1 - x0 + 1); + + //--- reset the path + if (yNext != y) { + delete activeSegs; + activeSegs = new GList(); + nextSeg = 0; + while (nextSeg < xPath->length) { + seg = &xPath->segs[nextSeg]; + if (seg->y0 >= y) { + break; + } + if (seg->y0 != seg->y1 && seg->y1 > y) { + if (seg->y0 == y) { + seg->xCur0 = seg->x0; + } else { + seg->xCur0 = seg->x0 + ((SplashCoord)y - seg->y0) * seg->dxdy; + } + activeSegs->append(seg); + } + ++nextSeg; + } + activeSegs->sort(&SplashXPathSeg::cmpXi); } - return cmp; -} -#endif // HAVE_STD_SORT + //--- process the scan line + y0 = y; + while (y0 < y + 1) { -//------------------------------------------------------------------------ -// SplashXPathScanner -//------------------------------------------------------------------------ + //--- delete finished segs + i = 0; + while (i < activeSegs->getLength()) { + seg = (SplashXPathSeg *)activeSegs->get(i); + if (seg->y1 <= y0) { + activeSegs->del(i); + } else { + ++i; + } + } -SplashXPathScanner::SplashXPathScanner(SplashXPath *xPathA, GBool eoA, - int clipYMin, int clipYMax) { - SplashXPathSeg *seg; - SplashCoord xMinFP, yMinFP, xMaxFP, yMaxFP; - int i; + //--- check for bottom of path + if (!activeSegs->getLength() && nextSeg >= xPath->length) { + break; + } - xPath = xPathA; - eo = eoA; - partialClip = gFalse; + //--- sort activeSegs + sortActiveSegs(); - // compute the bbox - if (xPath->length == 0) { - xMin = yMin = 1; - xMax = yMax = 0; - } else { - seg = &xPath->segs[0]; - if (seg->x0 <= seg->x1) { - xMinFP = seg->x0; - xMaxFP = seg->x1; - } else { - xMinFP = seg->x1; - xMaxFP = seg->x0; + //--- add waiting segs + while (nextSeg < xPath->length) { + seg = &xPath->segs[nextSeg]; + if (seg->y0 > y0) { + break; + } + if (seg->y0 != seg->y1) { + seg->xCur0 = seg->x0; + insertActiveSeg(seg); + } + ++nextSeg; } - if (seg->flags & splashXPathFlip) { - yMinFP = seg->y1; - yMaxFP = seg->y0; - } else { - yMinFP = seg->y0; - yMaxFP = seg->y1; + + //--- get the next "interesting" y value + y1 = y + 1; + if (nextSeg < xPath->length && xPath->segs[nextSeg].y0 < y1) { + y1 = xPath->segs[nextSeg].y0; } - for (i = 1; i < xPath->length; ++i) { - seg = &xPath->segs[i]; - if (seg->x0 < xMinFP) { - xMinFP = seg->x0; - } else if (seg->x0 > xMaxFP) { - xMaxFP = seg->x0; - } - if (seg->x1 < xMinFP) { - xMinFP = seg->x1; - } else if (seg->x1 > xMaxFP) { - xMaxFP = seg->x1; - } - if (seg->flags & splashXPathFlip) { - if (seg->y0 > yMaxFP) { - yMaxFP = seg->y0; - } + for (i = 0; i < activeSegs->getLength(); ++i) { + seg = (SplashXPathSeg *)activeSegs->get(i); + if (seg->y1 < y1) { + y1 = seg->y1; + } + } + + //--- compute xCur1 values, check for intersections + seg0 = NULL; + intersect = gFalse; + for (i = 0; i < activeSegs->getLength(); ++i) { + seg = (SplashXPathSeg *)activeSegs->get(i); + if (seg->y1 == y1) { + seg->xCur1 = seg->x1; } else { - if (seg->y1 > yMaxFP) { - yMaxFP = seg->y1; - } + seg->xCur1 = seg->x0 + (y1 - seg->y0) * seg->dxdy; } + if (seg0 && seg0->xCur1 > seg->xCur1) { + intersect = gTrue; + } + seg0 = seg; } - xMin = splashFloor(xMinFP); - xMax = splashFloor(xMaxFP); - yMin = splashFloor(yMinFP); - yMax = splashFloor(yMaxFP); - if (clipYMin > yMin) { - yMin = clipYMin; - partialClip = gTrue; - } - if (clipYMax < yMax) { - yMax = clipYMax; - partialClip = gTrue; + + //--- draw rectangles + if (intersect) { + for (; y0 < y1; y0 += minVertStep) { + if ((y1p = y0 + minVertStep) >= y1) { + y1p = y1; + last = gTrue; + } else { + last = gFalse; + } + state0 = state1 = count = 0; + seg0 = NULL; + eoMask = eo ? 1 : 0xffffffff; + for (i = 0; i < activeSegs->getLength(); ++i) { + seg = (SplashXPathSeg *)activeSegs->get(i); + if (last && seg->y1 == y1) { + seg->xCur1 = seg->x1; + } else { + seg->xCur1 = seg->x0 + (y1p - seg->y0) * seg->dxdy; + } + count += seg->count; + state1 = count & eoMask; + if (!state0 && state1) { + seg0 = seg; + } else if (state0 && !state1) { + drawRectangle(line, x0, x1, y0, y1p, seg0->xCur0, seg->xCur0); + } + state0 = state1; + } + for (i = 0; i < activeSegs->getLength(); ++i) { + seg = (SplashXPathSeg *)activeSegs->get(i); + seg->xCur0 = seg->xCur1; + } + sortActiveSegs(); + } + + //--- draw trapezoids + } else { + state0 = state1 = count = 0; + seg0 = NULL; + eoMask = eo ? 1 : 0xffffffff; + for (i = 0; i < activeSegs->getLength(); ++i) { + seg = (SplashXPathSeg *)activeSegs->get(i); + count += seg->count; + state1 = count & eoMask; + if (!state0 && state1) { + seg0 = seg; + } else if (state0 && !state1) { + drawTrapezoid(line, x0, x1, y0, y1, + seg0->xCur0, seg0->xCur1, seg0->dydx, + seg->xCur0, seg->xCur1, seg->dydx); + } + state0 = state1; + } + for (i = 0; i < activeSegs->getLength(); ++i) { + seg = (SplashXPathSeg *)activeSegs->get(i); + seg->xCur0 = seg->xCur1; + } } - } - allInter = NULL; - inter = NULL; - computeIntersections(); - interY = yMin - 1; -} + //--- next slice + y0 = y1; + } -SplashXPathScanner::~SplashXPathScanner() { - gfree(inter); - gfree(allInter); + yNext = y + 1; } -void SplashXPathScanner::getBBoxAA(int *xMinA, int *yMinA, - int *xMaxA, int *yMaxA) { - *xMinA = xMin / splashAASize; - *yMinA = yMin / splashAASize; - *xMaxA = xMax / splashAASize; - *yMaxA = yMax / splashAASize; -} +void SplashXPathScanner::getSpanBinary(Guchar *line, int y, int x0, int x1) { + SplashXPathSeg *seg; + int xx0, xx1, xx; + int eoMask, state0, state1, count, i; -void SplashXPathScanner::getSpanBounds(int y, int *spanXMin, int *spanXMax) { - int interBegin, interEnd, xx, i; + //--- clear the scan line buffer + memset(line + x0, 0, x1 - x0 + 1); - if (y < yMin || y > yMax) { - interBegin = interEnd = 0; - } else { - interBegin = inter[y - yMin]; - interEnd = inter[y - yMin + 1]; - } - if (interBegin < interEnd) { - *spanXMin = allInter[interBegin].x0; - xx = allInter[interBegin].x1; - for (i = interBegin + 1; i < interEnd; ++i) { - if (allInter[i].x1 > xx) { - xx = allInter[i].x1; + //--- reset the path + if (yNext != y) { + delete activeSegs; + activeSegs = new GList(); + nextSeg = 0; + while (nextSeg < xPath->length) { + seg = &xPath->segs[nextSeg]; + if (seg->y0 >= y) { + break; + } + if (seg->y1 > y) { + if (seg->y0 == y) { + seg->xCur0 = seg->x0; + } else { + seg->xCur0 = seg->x0 + ((SplashCoord)y - seg->y0) * seg->dxdy; + } + activeSegs->append(seg); } + ++nextSeg; } - *spanXMax = xx; - } else { - *spanXMin = xMax + 1; - *spanXMax = xMax; + activeSegs->sort(&SplashXPathSeg::cmpXi); } -} -GBool SplashXPathScanner::test(int x, int y) { - int interBegin, interEnd, count, i; - - if (y < yMin || y > yMax) { - return gFalse; - } - interBegin = inter[y - yMin]; - interEnd = inter[y - yMin + 1]; - count = 0; - for (i = interBegin; i < interEnd && allInter[i].x0 <= x; ++i) { - if (x <= allInter[i].x1) { - return gTrue; + //--- delete finished segs + i = 0; + while (i < activeSegs->getLength()) { + seg = (SplashXPathSeg *)activeSegs->get(i); + if (seg->y1 <= y) { + activeSegs->del(i); + } else { + ++i; } - count += allInter[i].count; } - return eo ? (count & 1) : (count != 0); -} -GBool SplashXPathScanner::testSpan(int x0, int x1, int y) { - int interBegin, interEnd, count, xx1, i; + //--- sort activeSegs + sortActiveSegs(); - if (y < yMin || y > yMax) { - return gFalse; - } - interBegin = inter[y - yMin]; - interEnd = inter[y - yMin + 1]; - count = 0; - for (i = interBegin; i < interEnd && allInter[i].x1 < x0; ++i) { - count += allInter[i].count; + //--- add waiting segs + while (nextSeg < xPath->length) { + seg = &xPath->segs[nextSeg]; + if (seg->y0 >= y + 1) { + break; + } + seg->xCur0 = seg->x0; + insertActiveSeg(seg); + ++nextSeg; + } + + //--- compute xCur1 values + for (i = 0; i < activeSegs->getLength(); ++i) { + seg = (SplashXPathSeg *)activeSegs->get(i); + if (seg->y1 <= y + 1) { + seg->xCur1 = seg->x1; + } else { + seg->xCur1 = seg->x0 + ((SplashCoord)(y + 1) - seg->y0) * seg->dxdy; + } } - // invariant: the subspan [x0,xx1] is inside the path - xx1 = x0 - 1; - while (xx1 < x1) { - if (i >= interEnd) { - return gFalse; - } - if (allInter[i].x0 > xx1 + 1 && - !(eo ? (count & 1) : (count != 0))) { - return gFalse; + //--- draw spans + state0 = state1 = count = 0; + eoMask = eo ? 1 : 0xffffffff; + xx0 = xx1 = 0; // make gcc happy + for (i = 0; i < activeSegs->getLength(); ++i) { + seg = (SplashXPathSeg *)activeSegs->get(i); + if (seg->y0 <= y && seg->y0 < seg->y1) { + count += seg->count; + state1 = count & eoMask; + } + if (state0) { + xx = splashCeil(seg->xCur0) - 1; + if (xx > xx1) { + xx1 = xx; + } + xx = splashFloor(seg->xCur1); + if (xx < xx0) { + xx0 = xx; + } + xx = splashCeil(seg->xCur1) - 1; + if (xx > xx1) { + xx1 = xx; + } + } else { + if (seg->xCur0 < seg->xCur1) { + xx0 = splashFloor(seg->xCur0); + xx1 = splashCeil(seg->xCur1) - 1; + } else { + xx0 = splashFloor(seg->xCur1); + xx1 = splashCeil(seg->xCur0) - 1; + } } - if (allInter[i].x1 > xx1) { - xx1 = allInter[i].x1; + if (!state1) { + if (xx0 < x0) { + xx0 = x0; + } + if (xx1 > x1) { + xx1 = x1; + } + for (xx = xx0; xx <= xx1; ++xx) { + line[xx] = 0xff; + } } - count += allInter[i].count; - ++i; + state0 = state1; + } + + //--- update xCur0 values + for (i = 0; i < activeSegs->getLength(); ++i) { + seg = (SplashXPathSeg *)activeSegs->get(i); + seg->xCur0 = seg->xCur1; } - return gTrue; + yNext = y + 1; } -GBool SplashXPathScanner::getNextSpan(int y, int *x0, int *x1) { - int interEnd, xx0, xx1; +inline void SplashXPathScanner::addArea(Guchar *line, int x, SplashCoord a) { + int a2, t; - if (y < yMin || y > yMax) { - return gFalse; + a2 = splashRound(a * 255); + if (a2 <= 0) { + return; } - if (interY != y) { - interY = y; - interIdx = inter[y - yMin]; - interCount = 0; + t = line[x] + a2; + if (t > 255) { + t = 255; + } + line[x] = t; +} + +// Draw a trapezoid with edges: +// top: (xa0, y0) - (xb0, y0) +// left: (xa0, y0) - (xa1, y1) +// right: (xb0, y0) - (xb1, y1) +// bottom: (xa1, y1) - (xb1, y1) +void SplashXPathScanner::drawTrapezoid(Guchar *line, int xMin, int xMax, + SplashCoord y0, SplashCoord y1, + SplashCoord xa0, SplashCoord xa1, + SplashCoord dydxa, + SplashCoord xb0, SplashCoord xb1, + SplashCoord dydxb) { + SplashCoord a, dy; + int x0, x1, x2, x3, x; + + // check for a rectangle + if (dydxa == 0 && dydxb == 0 && xa0 >= xMin && xb0 <= xMax) { + x0 = splashFloor(xa0); + x3 = splashFloor(xb0); + dy = y1 - y0; + if (x0 == x3) { + addArea(line, x0, (xb0 - xa0) * dy); + } else { + addArea(line, x0, ((SplashCoord)1 - (xa0 - x0)) * dy); + for (x = x0 + 1; x <= x3 - 1; ++x) { + addArea(line, x, y1 - y0); + } + addArea(line, x3, (xb0 - x3) * (y1 - y0)); + } + return; } - interEnd = inter[y - yMin + 1]; - if (interIdx >= interEnd) { - return gFalse; + + if (dydxa > 0) { + x0 = splashFloor(xa0); + x1 = splashFloor(xa1); + } else { + x0 = splashFloor(xa1); + x1 = splashFloor(xa0); + } + if (x0 < xMin) { + x0 = xMin; + } + if (dydxb > 0) { + x2 = splashFloor(xb0); + x3 = splashFloor(xb1); + } else { + x2 = splashFloor(xb1); + x3 = splashFloor(xb0); } - xx0 = allInter[interIdx].x0; - xx1 = allInter[interIdx].x1; - interCount += allInter[interIdx].count; - ++interIdx; - while (interIdx < interEnd && - (allInter[interIdx].x0 <= xx1 || - (eo ? (interCount & 1) : (interCount != 0)))) { - if (allInter[interIdx].x1 > xx1) { - xx1 = allInter[interIdx].x1; + if (x3 > xMax) { + x3 = xMax; + } + for (x = x0; x <= x3; ++x) { + a = y1 - y0; + if (x <= x1) { + a -= areaLeft(x, xa0, y0, xa1, y1, dydxa); + } + if (x >= x2) { + a -= areaRight(x, xb0, y0, xb1, y1, dydxb); } - interCount += allInter[interIdx].count; - ++interIdx; + addArea(line, x, a); } - *x0 = xx0; - *x1 = xx1; - return gTrue; } -void SplashXPathScanner::computeIntersections() { - SplashXPathSeg *seg; - SplashCoord segXMin, segXMax, segYMin, segYMax, xx0, xx1; - int x, y, y0, y1, i; - - if (yMin > yMax) { - return; - } +// Compute area within a pixel slice ((xp,y0)-(xp+1,y1)) to the left +// of a trapezoid edge ((x0,y0)-(x1,y1)). +SplashCoord SplashXPathScanner::areaLeft(int xp, + SplashCoord x0, SplashCoord y0, + SplashCoord x1, SplashCoord y1, + SplashCoord dydx) { + SplashCoord a, ya, yb; - // build the list of all intersections - allInterLen = 0; - allInterSize = 16; - allInter = (SplashIntersect *)gmallocn(allInterSize, - sizeof(SplashIntersect)); - for (i = 0; i < xPath->length; ++i) { - seg = &xPath->segs[i]; - if (seg->flags & splashXPathFlip) { - segYMin = seg->y1; - segYMax = seg->y0; + if (dydx >= 0) { + if (x0 >= xp) { + if (x1 <= xp + 1) { + a = ((x0 + x1) * 0.5 - xp) * (y1 - y0); + } else { + yb = y0 + ((SplashCoord)(xp + 1) - x0) * dydx; + a = (y1 - y0) - ((SplashCoord)(xp + 1) - x0) * (yb - y0) * 0.5; + } } else { - segYMin = seg->y0; - segYMax = seg->y1; + if (x1 <= xp + 1) { + ya = y0 + ((SplashCoord)xp - x0) * dydx; + a = (x1 - xp) * (y1 - ya) * 0.5; + } else { + // ya = y1 - (x1 - xp - 0.5) * dydx; + // a = y1 - ya; + a = (x1 - xp - 0.5) * dydx; + } } - if (seg->flags & splashXPathHoriz) { - y = splashFloor(seg->y0); - if (y >= yMin && y <= yMax) { - addIntersection(segYMin, segYMax, seg->flags, - y, splashFloor(seg->x0), splashFloor(seg->x1)); - } - } else if (seg->flags & splashXPathVert) { - y0 = splashFloor(segYMin); - if (y0 < yMin) { - y0 = yMin; - } - y1 = splashFloor(segYMax); - if (y1 > yMax) { - y1 = yMax; - } - x = splashFloor(seg->x0); - for (y = y0; y <= y1; ++y) { - addIntersection(segYMin, segYMax, seg->flags, y, x, x); + } else { + if (x0 <= xp + 1) { + if (x1 >= xp) { + a = ((x0 + x1) * 0.5 - xp) * (y1 - y0); + } else { + ya = y0 + ((SplashCoord)xp - x0) * dydx; + a = (x0 - xp) * (ya - y0) * 0.5; } } else { - if (seg->x0 < seg->x1) { - segXMin = seg->x0; - segXMax = seg->x1; - } else { - segXMin = seg->x1; - segXMax = seg->x0; - } - y0 = splashFloor(segYMin); - if (y0 < yMin) { - y0 = yMin; - } - y1 = splashFloor(segYMax); - if (y1 > yMax) { - y1 = yMax; - } - // this loop could just add seg->dxdy to xx1 on each iteration, - // but that introduces numerical accuracy problems - xx1 = seg->x0 + ((SplashCoord)y0 - seg->y0) * seg->dxdy; - for (y = y0; y <= y1; ++y) { - xx0 = xx1; - xx1 = seg->x0 + ((SplashCoord)(y + 1) - seg->y0) * seg->dxdy; - // the segment may not actually extend to the top and/or bottom edges - if (xx0 < segXMin) { - xx0 = segXMin; - } else if (xx0 > segXMax) { - xx0 = segXMax; - } - if (xx1 < segXMin) { - xx1 = segXMin; - } else if (xx1 > segXMax) { - xx1 = segXMax; - } - addIntersection(segYMin, segYMax, seg->flags, y, - splashFloor(xx0), splashFloor(xx1)); + if (x1 >= xp) { + yb = y0 + ((SplashCoord)(xp + 1) - x0) * dydx; + a = (y1 - y0) - ((SplashCoord)(xp + 1) - x1) * (y1 - yb) * 0.5; + } else { + // ya = y0 + (xp - x0 + 0.5) * dydx; + // a = ya - y0; + a = ((SplashCoord)xp - x0 + 0.5) * dydx; } } } -#if HAVE_STD_SORT - std::sort(allInter, allInter + allInterLen, cmpIntersectFunctor()); -#else - qsort(allInter, allInterLen, sizeof(SplashIntersect), cmpIntersect); -#endif + return a; +} - // build the list of y pointers - inter = (int *)gmallocn(yMax - yMin + 2, sizeof(int)); - i = 0; - for (y = yMin; y <= yMax; ++y) { - inter[y - yMin] = i; - while (i < allInterLen && allInter[i].y <= y) { - ++i; +// Compute area within a pixel slice ((xp,y0)-(xp+1,y1)) to the left +// of a trapezoid edge ((x0,y0)-(x1,y1)). +SplashCoord SplashXPathScanner::areaRight(int xp, + SplashCoord x0, SplashCoord y0, + SplashCoord x1, SplashCoord y1, + SplashCoord dydx) { + SplashCoord a, ya, yb; + + if (dydx >= 0) { + if (x0 >= xp) { + if (x1 <= xp + 1) { + a = ((SplashCoord)(xp + 1) - (x0 + x1) * 0.5) * (y1 - y0); + } else { + yb = y0 + ((SplashCoord)(xp + 1) - x0) * dydx; + a = ((SplashCoord)(xp + 1) - x0) * (yb - y0) * 0.5; + } + } else { + if (x1 <= xp + 1) { + ya = y0 + ((SplashCoord)xp - x0) * dydx; + a = (y1 - y0) - (x1 - xp) * (y1 - ya) * 0.5; + } else { + // ya = y0 + (xp - x0 + 0.5) * dydx; + // a = ya - y0; + a = ((SplashCoord)xp + 0.5 - x0) * dydx; + } + } + } else { + if (x0 <= xp + 1) { + if (x1 >= xp) { + a = ((SplashCoord)(xp + 1) - (x0 + x1) * 0.5) * (y1 - y0); + } else { + ya = y0 + ((SplashCoord)xp - x0) * dydx; + a = (y1 - y0) - (x0 - xp) * (ya - y0) * 0.5; + } + } else { + if (x1 >= xp) { + yb = y0 + ((SplashCoord)(xp + 1) - x0) * dydx; + a = ((SplashCoord)(xp + 1) - x1) * (y1 - yb) * 0.5; + } else { + // ya = y1 - (x1 - xp - 0.5) * dydx; + // a = y1 - ya; + a = (x1 - xp - 0.5) * dydx; + } } } - inter[yMax - yMin + 1] = i; + return a; } -void SplashXPathScanner::addIntersection(double segYMin, double segYMax, - Guint segFlags, - int y, int x0, int x1) { - if (allInterLen == allInterSize) { - allInterSize *= 2; - allInter = (SplashIntersect *)greallocn(allInter, allInterSize, - sizeof(SplashIntersect)); - } - allInter[allInterLen].y = y; - if (x0 < x1) { - allInter[allInterLen].x0 = x0; - allInter[allInterLen].x1 = x1; - } else { - allInter[allInterLen].x0 = x1; - allInter[allInterLen].x1 = x0; +void SplashXPathScanner::drawRectangle(Guchar *line, int xMin, int xMax, + SplashCoord y0, SplashCoord y1, + SplashCoord x0, SplashCoord x1) { + SplashCoord dy, a; + int xx0, xx1, x; + + xx0 = splashFloor(x0); + if (xx0 < xMin) { + xx0 = xMin; } - if (segYMin <= y && - (SplashCoord)y < segYMax && - !(segFlags & splashXPathHoriz)) { - allInter[allInterLen].count = eo ? 1 - : (segFlags & splashXPathFlip) ? 1 : -1; - } else { - allInter[allInterLen].count = 0; + xx1 = splashFloor(x1); + if (xx1 > xMax) { + xx1 = xMax; } - ++allInterLen; -} - -void SplashXPathScanner::renderAALine(SplashBitmap *aaBuf, - int *x0, int *x1, int y) { - int xx0, xx1, xx, xxMin, xxMax, yy, interEnd; - Guchar mask; - SplashColorPtr p; - - memset(aaBuf->getDataPtr(), 0, aaBuf->getRowSize() * aaBuf->getHeight()); - xxMin = aaBuf->getWidth(); - xxMax = -1; - if (yMin <= yMax) { - if (splashAASize * y < yMin) { - interIdx = inter[0]; - } else if (splashAASize * y > yMax) { - interIdx = inter[yMax - yMin + 1]; - } else { - interIdx = inter[splashAASize * y - yMin]; + dy = y1 - y0; + for (x = xx0; x <= xx1; ++x) { + a = dy; + if ((SplashCoord)x < x0) { + a -= (x0 - x) * dy; } - for (yy = 0; yy < splashAASize; ++yy) { - if (splashAASize * y + yy < yMin) { - interEnd = inter[0]; - } else if (splashAASize * y + yy > yMax) { - interEnd = inter[yMax - yMin + 1]; - } else { - interEnd = inter[splashAASize * y + yy - yMin + 1]; - } - interCount = 0; - while (interIdx < interEnd) { - xx0 = allInter[interIdx].x0; - xx1 = allInter[interIdx].x1; - interCount += allInter[interIdx].count; - ++interIdx; - while (interIdx < interEnd && - (allInter[interIdx].x0 <= xx1 || - (eo ? (interCount & 1) : (interCount != 0)))) { - if (allInter[interIdx].x1 > xx1) { - xx1 = allInter[interIdx].x1; - } - interCount += allInter[interIdx].count; - ++interIdx; - } - if (xx0 < 0) { - xx0 = 0; - } - ++xx1; - if (xx1 > aaBuf->getWidth()) { - xx1 = aaBuf->getWidth(); - } - // set [xx0, xx1) to 1 - if (xx0 < xx1) { - xx = xx0; - p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3); - if (xx & 7) { - mask = 0xff >> (xx & 7); - if ((xx & ~7) == (xx1 & ~7)) { - mask &= (Guchar)(0xff00 >> (xx1 & 7)); - } - *p++ |= mask; - xx = (xx & ~7) + 8; - } - for (; xx + 7 < xx1; xx += 8) { - *p++ |= 0xff; - } - if (xx < xx1) { - *p |= (Guchar)(0xff00 >> (xx1 & 7)); - } - } - if (xx0 < xxMin) { - xxMin = xx0; - } - if (xx1 > xxMax) { - xxMax = xx1; - } - } + if ((SplashCoord)(x + 1) > x1) { + a -= ((SplashCoord)(x + 1) - x1) * dy; } + addArea(line, x, a); } - *x0 = xxMin / splashAASize; - *x1 = (xxMax - 1) / splashAASize; } -void SplashXPathScanner::clipAALine(SplashBitmap *aaBuf, - int *x0, int *x1, int y) { - int xx0, xx1, xx, yy, interEnd; - Guchar mask; - SplashColorPtr p; - - for (yy = 0; yy < splashAASize; ++yy) { - xx = *x0 * splashAASize; - if (yMin <= yMax) { - if (splashAASize * y + yy < yMin) { - interIdx = interEnd = inter[0]; - } else if (splashAASize * y + yy > yMax) { - interIdx = interEnd = inter[yMax - yMin + 1]; - } else { - interIdx = inter[splashAASize * y + yy - yMin]; - if (splashAASize * y + yy > yMax) { - interEnd = inter[yMax - yMin + 1]; - } else { - interEnd = inter[splashAASize * y + yy - yMin + 1]; +void SplashXPathScanner::sortActiveSegs() { + SplashXPathSeg *seg0, *seg1; + int i, j, k; + + if (activeSegs->getLength() < 2) { + return; + } + seg0 = (SplashXPathSeg *)activeSegs->get(0); + for (i = 1; i < activeSegs->getLength(); ++i) { + seg1 = (SplashXPathSeg *)activeSegs->get(i); + if (SplashXPathSeg::cmpX(seg0, seg1) > 0) { + for (j = i - 1; j > 0; --j) { + if (SplashXPathSeg::cmpX((SplashXPathSeg *)activeSegs->get(j - 1), + seg1) <= 0) { + break; } } - interCount = 0; - while (interIdx < interEnd && xx < (*x1 + 1) * splashAASize) { - xx0 = allInter[interIdx].x0; - xx1 = allInter[interIdx].x1; - interCount += allInter[interIdx].count; - ++interIdx; - while (interIdx < interEnd && - (allInter[interIdx].x0 <= xx1 || - (eo ? (interCount & 1) : (interCount != 0)))) { - if (allInter[interIdx].x1 > xx1) { - xx1 = allInter[interIdx].x1; - } - interCount += allInter[interIdx].count; - ++interIdx; - } - if (xx0 > aaBuf->getWidth()) { - xx0 = aaBuf->getWidth(); - } - // set [xx, xx0) to 0 - if (xx < xx0) { - p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3); - if (xx & 7) { - mask = (Guchar)(0xff00 >> (xx & 7)); - if ((xx & ~7) == (xx0 & ~7)) { - mask |= 0xff >> (xx0 & 7); - } - *p++ &= mask; - xx = (xx & ~7) + 8; - } - for (; xx + 7 < xx0; xx += 8) { - *p++ = 0x00; - } - if (xx < xx0) { - *p &= 0xff >> (xx0 & 7); - } - } - if (xx1 >= xx) { - xx = xx1 + 1; - } + for (k = i; k > j; --k) { + activeSegs->put(k, activeSegs->get(k-1)); } + activeSegs->put(j, seg1); + } else { + seg0 = seg1; } - xx0 = (*x1 + 1) * splashAASize; - // set [xx, xx0) to 0 - if (xx < xx0) { - p = aaBuf->getDataPtr() + yy * aaBuf->getRowSize() + (xx >> 3); - if (xx & 7) { - mask = (Guchar)(0xff00 >> (xx & 7)); - if ((xx & ~7) == (xx0 & ~7)) { - mask &= 0xff >> (xx0 & 7); - } - *p++ &= mask; - xx = (xx & ~7) + 8; - } - for (; xx + 7 < xx0; xx += 8) { - *p++ = 0x00; - } - if (xx < xx0) { - *p &= 0xff >> (xx0 & 7); - } + } +} + +void SplashXPathScanner::insertActiveSeg(SplashXPathSeg *seg) { + int i; + + for (i = 0; i < activeSegs->getLength(); ++i) { + if (SplashXPathSeg::cmpX(seg, (SplashXPathSeg *)activeSegs->get(i)) < 0) { + break; } } + activeSegs->insert(i, seg); } diff -uNrp xpdf-3.03/splash/SplashXPathScanner.h xpdf-3.04/splash/SplashXPathScanner.h --- xpdf-3.03/splash/SplashXPathScanner.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/splash/SplashXPathScanner.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,6 +2,8 @@ // // SplashXPathScanner.h // +// Copyright 2003-2013 Glyph & Cog, LLC +// //======================================================================== #ifndef SPLASHXPATHSCANNER_H @@ -15,9 +17,8 @@ #include "SplashTypes.h" +class GList; class SplashXPath; -class SplashBitmap; -struct SplashIntersect; //------------------------------------------------------------------------ // SplashXPathScanner @@ -28,68 +29,47 @@ public: // Create a new SplashXPathScanner object. must be sorted. SplashXPathScanner(SplashXPath *xPathA, GBool eoA, - int clipYMin, int clipYMax); + int yMinA, int yMaxA); ~SplashXPathScanner(); - // Return the path's bounding box. - void getBBox(int *xMinA, int *yMinA, int *xMaxA, int *yMaxA) - { *xMinA = xMin; *yMinA = yMin; *xMaxA = xMax; *yMaxA = yMax; } - - // Return the path's bounding box. - void getBBoxAA(int *xMinA, int *yMinA, int *xMaxA, int *yMaxA); - - // Returns true if at least part of the path was outside the - // clipYMin/clipYMax bounds passed to the constructor. - GBool hasPartialClip() { return partialClip; } - - // Return the min/max x values for the span at . - void getSpanBounds(int y, int *spanXMin, int *spanXMax); - - // Returns true if (,) is inside the path. - GBool test(int x, int y); - - // Returns true if the entire span ([,], ) is inside the - // path. - GBool testSpan(int x0, int x1, int y); - - // Returns the next span inside the path at . If is - // different than the previous call to getNextSpan, this returns the - // first span at ; otherwise it returns the next span (relative - // to the previous call to getNextSpan). Returns false if there are - // no more spans at . - GBool getNextSpan(int y, int *x0, int *x1); - - // Renders one anti-aliased line into . Returns the min and - // max x coordinates with non-zero pixels in and . - void renderAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y); - - // Clips an anti-aliased line by setting pixels to zero. On entry, - // all non-zero pixels are between and . This function - // will update and . - void clipAALine(SplashBitmap *aaBuf, int *x0, int *x1, int y); + // Compute shape values for a scan line. Fills in line[] with shape + // values for one scan line: ([x0, x1], y). The values are in [0, + // 255]. + void getSpan(Guchar *line, int y, int x0, int x1); + + // Like getSpan(), but uses the values 0 and 255 only. Writes 255 + // for all pixels which include non-zero area inside the path. + void getSpanBinary(Guchar *line, int y, int x0, int x1); private: - void computeIntersections(); - void addIntersection(double segYMin, double segYMax, - Guint segFlags, - int y, int x0, int x1); + inline void addArea(Guchar *line, int x, SplashCoord a); + void drawTrapezoid(Guchar *line, int xMin, int xMax, + SplashCoord y0, SplashCoord y1, + SplashCoord xa0, SplashCoord xa1, SplashCoord dydxa, + SplashCoord xb0, SplashCoord xb1, SplashCoord dydxb); + SplashCoord areaLeft(int xp, + SplashCoord x0, SplashCoord y0, + SplashCoord x1, SplashCoord y1, + SplashCoord dydx); + SplashCoord areaRight(int xp, + SplashCoord x0, SplashCoord y0, + SplashCoord x1, SplashCoord y1, + SplashCoord dydx); + void drawRectangle(Guchar *line, int xMin, int xMax, + SplashCoord y0, SplashCoord y1, + SplashCoord x0, SplashCoord x1); + void sortActiveSegs(); + void insertActiveSeg(SplashXPathSeg *seg); SplashXPath *xPath; GBool eo; - int xMin, yMin, xMax, yMax; - GBool partialClip; + int yMin, yMax; - SplashIntersect *allInter; // array of intersections - int allInterLen; // number of intersections in - int allInterSize; // size of the array - int *inter; // indexes into for each y value - int interY; // current y value - used by getNextSpan - int interIdx; // current index into - used by - // getNextSpan - int interCount; // current EO/NZWN counter - used by - // getNextSpan + GList *activeSegs; // [SplashXPathSeg] + int nextSeg; + int yNext; }; #endif diff -uNrp xpdf-3.03/xpdf/AcroForm.cc xpdf-3.04/xpdf/AcroForm.cc --- xpdf-3.03/xpdf/AcroForm.cc 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/AcroForm.cc 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,1897 @@ +//======================================================================== +// +// AcroForm.cc +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include +#include +#include "gmem.h" +#include "GString.h" +#include "GList.h" +#include "Error.h" +#include "Object.h" +#include "PDFDoc.h" +#include "TextString.h" +#include "Gfx.h" +#include "GfxFont.h" +#include "OptionalContent.h" +#include "Annot.h" +#include "Lexer.h" +#include "AcroForm.h" + +//------------------------------------------------------------------------ + +#define acroFormFlagReadOnly (1 << 0) // all +#define acroFormFlagRequired (1 << 1) // all +#define acroFormFlagNoExport (1 << 2) // all +#define acroFormFlagMultiline (1 << 12) // text +#define acroFormFlagPassword (1 << 13) // text +#define acroFormFlagNoToggleToOff (1 << 14) // button +#define acroFormFlagRadio (1 << 15) // button +#define acroFormFlagPushbutton (1 << 16) // button +#define acroFormFlagCombo (1 << 17) // choice +#define acroFormFlagEdit (1 << 18) // choice +#define acroFormFlagSort (1 << 19) // choice +#define acroFormFlagFileSelect (1 << 20) // text +#define acroFormFlagMultiSelect (1 << 21) // choice +#define acroFormFlagDoNotSpellCheck (1 << 22) // text, choice +#define acroFormFlagDoNotScroll (1 << 23) // text +#define acroFormFlagComb (1 << 24) // text +#define acroFormFlagRadiosInUnison (1 << 25) // button +#define acroFormFlagRichText (1 << 25) // text +#define acroFormFlagCommitOnSelChange (1 << 26) // choice + +#define acroFormQuadLeft 0 +#define acroFormQuadCenter 1 +#define acroFormQuadRight 2 + +#define annotFlagHidden 0x0002 +#define annotFlagPrint 0x0004 +#define annotFlagNoView 0x0020 + +// distance of Bezier control point from center for circle approximation +// = (4 * (sqrt(2) - 1) / 3) * r +#define bezierCircle 0.55228475 + +//------------------------------------------------------------------------ + +// map an annotation ref to a page number +class AcroFormAnnotPage { +public: + AcroFormAnnotPage(int annotNumA, int annotGenA, int pageNumA) + { annotNum = annotNumA; annotGen = annotGenA; pageNum = pageNumA; } + int annotNum; + int annotGen; + int pageNum; +}; + +//------------------------------------------------------------------------ +// AcroForm +//------------------------------------------------------------------------ + +AcroForm *AcroForm::load(PDFDoc *docA, Catalog *catalog, Object *acroFormObjA) { + AcroForm *acroForm; + Object fieldsObj, obj1, obj2; + int i; + + acroForm = new AcroForm(docA, acroFormObjA); + + if (acroFormObjA->dictLookup("NeedAppearances", &obj1)->isBool()) { + acroForm->needAppearances = obj1.getBool(); + } + obj1.free(); + + acroForm->buildAnnotPageList(catalog); + + if (!acroFormObjA->dictLookup("Fields", &obj1)->isArray()) { + if (!obj1.isNull()) { + error(errSyntaxError, -1, "AcroForm Fields entry is wrong type"); + } + obj1.free(); + delete acroForm; + return NULL; + } + for (i = 0; i < obj1.arrayGetLength(); ++i) { + obj1.arrayGetNF(i, &obj2); + acroForm->scanField(&obj2); + obj2.free(); + } + obj1.free(); + + return acroForm; +} + +AcroForm::AcroForm(PDFDoc *docA, Object *acroFormObjA): Form(docA) { + acroFormObjA->copy(&acroFormObj); + needAppearances = gFalse; + annotPages = new GList(); + fields = new GList(); +} + +AcroForm::~AcroForm() { + acroFormObj.free(); + deleteGList(annotPages, AcroFormAnnotPage); + deleteGList(fields, AcroFormField); +} + +void AcroForm::buildAnnotPageList(Catalog *catalog) { + Object annotsObj, annotObj; + int pageNum, i; + + for (pageNum = 1; pageNum <= catalog->getNumPages(); ++pageNum) { + if (catalog->getPage(pageNum)->getAnnots(&annotsObj)->isArray()) { + for (i = 0; i < annotsObj.arrayGetLength(); ++i) { + if (annotsObj.arrayGetNF(i, &annotObj)->isRef()) { + annotPages->append(new AcroFormAnnotPage(annotObj.getRefNum(), + annotObj.getRefGen(), + pageNum)); + } + annotObj.free(); + } + } + annotsObj.free(); + } + //~ sort the list +} + +int AcroForm::lookupAnnotPage(Object *annotRef) { + AcroFormAnnotPage *annotPage; + int num, gen, i; + + if (!annotRef->isRef()) { + return 0; + } + num = annotRef->getRefNum(); + gen = annotRef->getRefGen(); + //~ use bin search + for (i = 0; i < annotPages->getLength(); ++i) { + annotPage = (AcroFormAnnotPage *)annotPages->get(i); + if (annotPage->annotNum == num && annotPage->annotGen == gen) { + return annotPage->pageNum; + } + } + return 0; +} + +void AcroForm::scanField(Object *fieldRef) { + AcroFormField *field; + Object fieldObj, kidsObj, kidRef, kidObj, subtypeObj; + GBool isTerminal; + int i; + + fieldRef->fetch(doc->getXRef(), &fieldObj); + if (!fieldObj.isDict()) { + error(errSyntaxError, -1, "AcroForm field object is wrong type"); + fieldObj.free(); + return; + } + + // if this field has a Kids array, and all of the kids have a Parent + // reference (i.e., they're all form fields, not widget + // annotations), then this is a non-terminal field, and we need to + // scan the kids + isTerminal = gTrue; + if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) { + isTerminal = gFalse; + for (i = 0; !isTerminal && i < kidsObj.arrayGetLength(); ++i) { + kidsObj.arrayGet(i, &kidObj); + if (kidObj.isDict()) { + if (kidObj.dictLookup("Parent", &subtypeObj)->isNull()) { + isTerminal = gTrue; + } + subtypeObj.free(); + } + kidObj.free(); + } + if (!isTerminal) { + for (i = 0; !isTerminal && i < kidsObj.arrayGetLength(); ++i) { + kidsObj.arrayGetNF(i, &kidRef); + scanField(&kidRef); + kidRef.free(); + } + } + } + kidsObj.free(); + + if (isTerminal) { + if ((field = AcroFormField::load(this, fieldRef))) { + fields->append(field); + } + } + + fieldObj.free(); +} + +void AcroForm::draw(int pageNum, Gfx *gfx, GBool printing) { + int i; + + for (i = 0; i < fields->getLength(); ++i) { + ((AcroFormField *)fields->get(i))->draw(pageNum, gfx, printing); + } +} + +int AcroForm::getNumFields() { + return fields->getLength(); +} + +FormField *AcroForm::getField(int idx) { + return (AcroFormField *)fields->get(idx); +} + +//------------------------------------------------------------------------ +// AcroFormField +//------------------------------------------------------------------------ + +AcroFormField *AcroFormField::load(AcroForm *acroFormA, Object *fieldRefA) { + GString *typeStr; + TextString *nameA; + Guint flagsA; + GBool haveFlags; + Object fieldObjA, parentObj, parentObj2, obj1, obj2; + AcroFormFieldType typeA; + AcroFormField *field; + + fieldRefA->fetch(acroFormA->doc->getXRef(), &fieldObjA); + + //----- get field info + + if (fieldObjA.dictLookup("T", &obj1)->isString()) { + nameA = new TextString(obj1.getString()); + } else { + nameA = new TextString(); + } + obj1.free(); + + if (fieldObjA.dictLookup("FT", &obj1)->isName()) { + typeStr = new GString(obj1.getName()); + } else { + typeStr = NULL; + } + obj1.free(); + + if (fieldObjA.dictLookup("Ff", &obj1)->isInt()) { + flagsA = (Guint)obj1.getInt(); + haveFlags = gTrue; + } else { + flagsA = 0; + haveFlags = gFalse; + } + obj1.free(); + + //----- get info from parent non-terminal fields + + fieldObjA.dictLookup("Parent", &parentObj); + while (parentObj.isDict()) { + + if (parentObj.dictLookup("T", &obj1)->isString()) { + if (nameA->getLength()) { + nameA->insert(0, (Unicode)'.'); + } + nameA->insert(0, obj1.getString()); + } + obj1.free(); + + if (!typeStr) { + if (parentObj.dictLookup("FT", &obj1)->isName()) { + typeStr = new GString(obj1.getName()); + } + obj1.free(); + } + + if (!haveFlags) { + if (parentObj.dictLookup("Ff", &obj1)->isInt()) { + flagsA = (Guint)obj1.getInt(); + haveFlags = gTrue; + } + obj1.free(); + } + + parentObj.dictLookup("Parent", &parentObj2); + parentObj.free(); + parentObj = parentObj2; + } + parentObj.free(); + + if (!typeStr) { + error(errSyntaxError, -1, "Missing type in AcroForm field"); + goto err1; + } else if (!typeStr->cmp("Btn")) { + if (flagsA & acroFormFlagPushbutton) { + typeA = acroFormFieldPushbutton; + } else if (flagsA & acroFormFlagRadio) { + typeA = acroFormFieldRadioButton; + } else { + typeA = acroFormFieldCheckbox; + } + } else if (!typeStr->cmp("Tx")) { + if (flagsA & acroFormFlagFileSelect) { + typeA = acroFormFieldFileSelect; + } else if (flagsA & acroFormFlagMultiline) { + typeA = acroFormFieldMultilineText; + } else { + typeA = acroFormFieldText; + } + } else if (!typeStr->cmp("Ch")) { + if (flagsA & acroFormFlagCombo) { + typeA = acroFormFieldComboBox; + } else { + typeA = acroFormFieldListBox; + } + } else if (!typeStr->cmp("Sig")) { + typeA = acroFormFieldSignature; + } else { + error(errSyntaxError, -1, "Invalid type in AcroForm field"); + goto err1; + } + delete typeStr; + + field = new AcroFormField(acroFormA, fieldRefA, &fieldObjA, + typeA, nameA, flagsA); + fieldObjA.free(); + return field; + + err1: + delete typeStr; + delete nameA; + fieldObjA.free(); + return NULL; +} + +AcroFormField::AcroFormField(AcroForm *acroFormA, + Object *fieldRefA, Object *fieldObjA, + AcroFormFieldType typeA, TextString *nameA, + Guint flagsA) { + acroForm = acroFormA; + fieldRefA->copy(&fieldRef); + fieldObjA->copy(&fieldObj); + type = typeA; + name = nameA; + flags = flagsA; +} + +AcroFormField::~AcroFormField() { + fieldRef.free(); + fieldObj.free(); + delete name; +} + +const char *AcroFormField::getType() { + switch (type) { + case acroFormFieldPushbutton: return "PushButton"; + case acroFormFieldRadioButton: return "RadioButton"; + case acroFormFieldCheckbox: return "Checkbox"; + case acroFormFieldFileSelect: return "FileSelect"; + case acroFormFieldMultilineText: return "MultilineText"; + case acroFormFieldText: return "Text"; + case acroFormFieldComboBox: return "ComboBox"; + case acroFormFieldListBox: return "ListBox"; + case acroFormFieldSignature: return "Signature"; + default: return NULL; + } +} + +Unicode *AcroFormField::getName(int *length) { + Unicode *u, *ret; + int n; + + u = name->getUnicode(); + n = name->getLength(); + ret = (Unicode *)gmallocn(n, sizeof(Unicode)); + memcpy(ret, u, n * sizeof(Unicode)); + *length = n; + return ret; +} + +Unicode *AcroFormField::getValue(int *length) { + Object obj1; + Unicode *u; + char *s; + TextString *ts; + int n, i; + + 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; + } + *length = n; + return u; + } 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; + return u; + } else { + return NULL; + } +} + +void AcroFormField::draw(int pageNum, Gfx *gfx, GBool printing) { + Object kidsObj, annotRef, annotObj; + int i; + + // find the annotation object(s) + if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) { + for (i = 0; i < kidsObj.arrayGetLength(); ++i) { + kidsObj.arrayGetNF(i, &annotRef); + annotRef.fetch(acroForm->doc->getXRef(), &annotObj); + drawAnnot(pageNum, gfx, printing, &annotRef, &annotObj); + annotObj.free(); + annotRef.free(); + } + } else { + drawAnnot(pageNum, gfx, printing, &fieldRef, &fieldObj); + } + kidsObj.free(); +} + +void AcroFormField::drawAnnot(int pageNum, Gfx *gfx, GBool printing, + Object *annotRef, Object *annotObj) { + Object obj1, obj2; + double xMin, yMin, xMax, yMax, t; + int annotFlags; + GBool oc; + + if (!annotObj->isDict()) { + return; + } + + //----- get the page number + + // the "P" (page) field in annotations is optional, so we can't + // depend on it here + if (acroForm->lookupAnnotPage(annotRef) != pageNum) { + return; + } + + //----- check annotation flags + + if (annotObj->dictLookup("F", &obj1)->isInt()) { + annotFlags = obj1.getInt(); + } else { + annotFlags = 0; + } + obj1.free(); + if ((annotFlags & annotFlagHidden) || + (printing && !(annotFlags & annotFlagPrint)) || + (!printing && (annotFlags & annotFlagNoView))) { + return; + } + + //----- check the optional content entry + + annotObj->dictLookupNF("OC", &obj1); + if (acroForm->doc->getOptionalContent()->evalOCObject(&obj1, &oc) && !oc) { + obj1.free(); + return; + } + obj1.free(); + + //----- get the bounding box + + if (annotObj->dictLookup("Rect", &obj1)->isArray() && + obj1.arrayGetLength() == 4) { + xMin = yMin = xMax = yMax = 0; + if (obj1.arrayGet(0, &obj2)->isNum()) { + xMin = obj2.getNum(); + } + obj2.free(); + if (obj1.arrayGet(1, &obj2)->isNum()) { + yMin = obj2.getNum(); + } + obj2.free(); + if (obj1.arrayGet(2, &obj2)->isNum()) { + xMax = obj2.getNum(); + } + obj2.free(); + if (obj1.arrayGet(3, &obj2)->isNum()) { + yMax = obj2.getNum(); + } + obj2.free(); + if (xMin > xMax) { + t = xMin; xMin = xMax; xMax = t; + } + if (yMin > yMax) { + t = yMin; yMin = yMax; yMax = t; + } + } else { + error(errSyntaxError, -1, "Bad bounding box for annotation"); + obj1.free(); + return; + } + obj1.free(); + + //----- draw it + + if (acroForm->needAppearances) { + drawNewAppearance(gfx, annotObj->getDict(), + xMin, yMin, xMax, yMax); + } else { + drawExistingAppearance(gfx, annotObj->getDict(), + xMin, yMin, xMax, yMax); + } +} + +// Draw the existing appearance stream for a single annotation +// attached to this field. +void AcroFormField::drawExistingAppearance(Gfx *gfx, Dict *annot, + double xMin, double yMin, + double xMax, double yMax) { + Object apObj, asObj, appearance, obj1; + + //----- get the appearance stream + + if (annot->lookup("AP", &apObj)->isDict()) { + apObj.dictLookup("N", &obj1); + if (obj1.isDict()) { + if (annot->lookup("AS", &asObj)->isName()) { + obj1.dictLookupNF(asObj.getName(), &appearance); + } else if (obj1.dictGetLength() == 1) { + obj1.dictGetValNF(0, &appearance); + } else { + obj1.dictLookupNF("Off", &appearance); + } + asObj.free(); + } else { + apObj.dictLookupNF("N", &appearance); + } + obj1.free(); + } + apObj.free(); + + //----- draw it + + if (!appearance.isNone()) { + gfx->drawAnnot(&appearance, NULL, xMin, yMin, xMax, yMax); + appearance.free(); + } +} + +// Regenerate the appearnce for this field, and draw it. +void AcroFormField::drawNewAppearance(Gfx *gfx, Dict *annot, + double xMin, double yMin, + double xMax, double yMax) { + Object appearance, mkObj, ftObj, appearDict, drObj, apObj, asObj; + Object obj1, obj2, obj3; + Dict *mkDict; + MemStream *appearStream; + GfxFontDict *fontDict; + GBool hasCaption; + double dx, dy, r; + GString *caption, *da; + GString **text; + GBool *selection; + AnnotBorderType borderType; + double borderWidth; + double *borderDash; + GString *appearanceState; + int borderDashLength, rot, quadding, comb, nOptions, topIdx, i, j; + + appearBuf = new GString(); + + // get the appearance characteristics (MK) dictionary + if (annot->lookup("MK", &mkObj)->isDict()) { + mkDict = mkObj.getDict(); + } else { + mkDict = NULL; + } + + // draw the background + if (mkDict) { + if (mkDict->lookup("BG", &obj1)->isArray() && + obj1.arrayGetLength() > 0) { + setColor(obj1.getArray(), gTrue, 0); + appearBuf->appendf("0 0 {0:.4f} {1:.4f} re f\n", + xMax - xMin, yMax - yMin); + } + obj1.free(); + } + + // get the field type + fieldLookup("FT", &ftObj); + + // draw the border + borderType = annotBorderSolid; + borderWidth = 1; + borderDash = NULL; + borderDashLength = 0; + if (annot->lookup("BS", &obj1)->isDict()) { + if (obj1.dictLookup("S", &obj2)->isName()) { + if (obj2.isName("S")) { + borderType = annotBorderSolid; + } else if (obj2.isName("D")) { + borderType = annotBorderDashed; + } else if (obj2.isName("B")) { + borderType = annotBorderBeveled; + } else if (obj2.isName("I")) { + borderType = annotBorderInset; + } else if (obj2.isName("U")) { + borderType = annotBorderUnderlined; + } + } + obj2.free(); + if (obj1.dictLookup("W", &obj2)->isNum()) { + borderWidth = obj2.getNum(); + } + obj2.free(); + if (obj1.dictLookup("D", &obj2)->isArray()) { + borderDashLength = obj2.arrayGetLength(); + borderDash = (double *)gmallocn(borderDashLength, sizeof(double)); + for (i = 0; i < borderDashLength; ++i) { + if (obj2.arrayGet(i, &obj3)->isNum()) { + borderDash[i] = obj3.getNum(); + } else { + borderDash[i] = 1; + } + obj3.free(); + } + } + obj2.free(); + } else { + obj1.free(); + if (annot->lookup("Border", &obj1)->isArray()) { + if (obj1.arrayGetLength() >= 3) { + if (obj1.arrayGet(2, &obj2)->isNum()) { + borderWidth = obj2.getNum(); + } + obj2.free(); + if (obj1.arrayGetLength() >= 4) { + if (obj1.arrayGet(3, &obj2)->isArray()) { + borderType = annotBorderDashed; + borderDashLength = obj2.arrayGetLength(); + borderDash = (double *)gmallocn(borderDashLength, sizeof(double)); + for (i = 0; i < borderDashLength; ++i) { + if (obj2.arrayGet(i, &obj3)->isNum()) { + borderDash[i] = obj3.getNum(); + } else { + borderDash[i] = 1; + } + obj3.free(); + } + } else { + // Adobe draws no border at all if the last element is of + // the wrong type. + borderWidth = 0; + } + obj2.free(); + } + } + } + } + obj1.free(); + if (mkDict) { + if (borderWidth > 0) { + mkDict->lookup("BC", &obj1); + if (!(obj1.isArray() && obj1.arrayGetLength() > 0)) { + mkDict->lookup("BG", &obj1); + } + if (obj1.isArray() && obj1.arrayGetLength() > 0) { + dx = xMax - xMin; + dy = yMax - yMin; + + // radio buttons with no caption have a round border + hasCaption = mkDict->lookup("CA", &obj2)->isString(); + obj2.free(); + if (ftObj.isName("Btn") && (flags & acroFormFlagRadio) && !hasCaption) { + r = 0.5 * (dx < dy ? dx : dy); + switch (borderType) { + case annotBorderDashed: + appearBuf->append("["); + for (i = 0; i < borderDashLength; ++i) { + appearBuf->appendf(" {0:.4f}", borderDash[i]); + } + appearBuf->append("] 0 d\n"); + // fall through to the solid case + case annotBorderSolid: + case annotBorderUnderlined: + appearBuf->appendf("{0:.4f} w\n", borderWidth); + setColor(obj1.getArray(), gFalse, 0); + drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * borderWidth, "s"); + break; + case annotBorderBeveled: + case annotBorderInset: + appearBuf->appendf("{0:.4f} w\n", 0.5 * borderWidth); + setColor(obj1.getArray(), gFalse, 0); + drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * borderWidth, "s"); + setColor(obj1.getArray(), gFalse, + borderType == annotBorderBeveled ? 1 : -1); + drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * borderWidth); + setColor(obj1.getArray(), gFalse, + borderType == annotBorderBeveled ? -1 : 1); + drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * borderWidth); + break; + } + + } else { + switch (borderType) { + case annotBorderDashed: + appearBuf->append("["); + for (i = 0; i < borderDashLength; ++i) { + appearBuf->appendf(" {0:.4f}", borderDash[i]); + } + appearBuf->append("] 0 d\n"); + // fall through to the solid case + case annotBorderSolid: + appearBuf->appendf("{0:.4f} w\n", borderWidth); + setColor(obj1.getArray(), gFalse, 0); + appearBuf->appendf("{0:.4f} {0:.4f} {1:.4f} {2:.4f} re s\n", + 0.5 * borderWidth, + dx - borderWidth, dy - borderWidth); + break; + case annotBorderBeveled: + case annotBorderInset: + setColor(obj1.getArray(), gTrue, + borderType == annotBorderBeveled ? 1 : -1); + appearBuf->append("0 0 m\n"); + appearBuf->appendf("0 {0:.4f} l\n", dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", dx, dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + dx - borderWidth, dy - borderWidth); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + borderWidth, dy - borderWidth); + appearBuf->appendf("{0:.4f} {0:.4f} l\n", borderWidth); + appearBuf->append("f\n"); + setColor(obj1.getArray(), gTrue, + borderType == annotBorderBeveled ? -1 : 1); + appearBuf->append("0 0 m\n"); + appearBuf->appendf("{0:.4f} 0 l\n", dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", dx, dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + dx - borderWidth, dy - borderWidth); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + dx - borderWidth, borderWidth); + appearBuf->appendf("{0:.4f} {0:.4f} l\n", borderWidth); + appearBuf->append("f\n"); + break; + case annotBorderUnderlined: + appearBuf->appendf("{0:.4f} w\n", borderWidth); + setColor(obj1.getArray(), gFalse, 0); + appearBuf->appendf("0 0 m {0:.4f} 0 l s\n", dx); + break; + } + + // clip to the inside of the border + appearBuf->appendf("{0:.4f} {0:.4f} {1:.4f} {2:.4f} re W n\n", + borderWidth, + dx - 2 * borderWidth, dy - 2 * borderWidth); + } + } + obj1.free(); + } + } + gfree(borderDash); + + // get the resource dictionary + fieldLookup("DR", &drObj); + + // build the font dictionary + if (drObj.isDict() && drObj.dictLookup("Font", &obj1)->isDict()) { + fontDict = new GfxFontDict(acroForm->doc->getXRef(), NULL, obj1.getDict()); + } else { + fontDict = NULL; + } + obj1.free(); + + // get the default appearance string + if (fieldLookup("DA", &obj1)->isString()) { + da = obj1.getString()->copy(); + } else { + da = NULL; + } + obj1.free(); + + // get the rotation value + rot = 0; + if (mkDict) { + if (mkDict->lookup("R", &obj1)->isInt()) { + rot = obj1.getInt(); + } + obj1.free(); + } + + // get the appearance state + annot->lookup("AP", &apObj); + annot->lookup("AS", &asObj); + appearanceState = NULL; + if (asObj.isName()) { + appearanceState = new GString(asObj.getName()); + } else if (apObj.isDict()) { + apObj.dictLookup("N", &obj1); + if (obj1.isDict() && obj1.dictGetLength() == 1) { + appearanceState = new GString(obj1.dictGetKey(0)); + } + obj1.free(); + } + if (!appearanceState) { + appearanceState = new GString("Off"); + } + asObj.free(); + apObj.free(); + + // draw the field contents + if (ftObj.isName("Btn")) { + caption = NULL; + if (mkDict) { + if (mkDict->lookup("CA", &obj1)->isString()) { + caption = obj1.getString()->copy(); + } + obj1.free(); + } + // radio button + if (flags & acroFormFlagRadio) { + //~ Acrobat doesn't draw a caption if there is no AP dict (?) + if (fieldLookup("V", &obj1) + ->isName(appearanceState->getCString())) { + if (caption) { + drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter, + gFalse, gTrue, rot, xMin, yMin, xMax, yMax, borderWidth); + } else { + if (mkDict) { + if (mkDict->lookup("BC", &obj2)->isArray() && + obj2.arrayGetLength() > 0) { + dx = xMax - xMin; + dy = yMax - yMin; + setColor(obj2.getArray(), gTrue, 0); + drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy), "f"); + } + obj2.free(); + } + } + } + obj1.free(); + // pushbutton + } else if (flags & acroFormFlagPushbutton) { + if (caption) { + drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter, + gFalse, gFalse, rot, xMin, yMin, xMax, yMax, borderWidth); + } + // checkbox + } else { + fieldLookup("V", &obj1); + if (obj1.isName() && !obj1.isName("Off")) { + if (!caption) { + caption = new GString("3"); // ZapfDingbats checkmark + } + drawText(caption, da, fontDict, gFalse, 0, acroFormQuadCenter, + gFalse, gTrue, rot, xMin, yMin, xMax, yMax, borderWidth); + } + obj1.free(); + } + if (caption) { + delete caption; + } + } else if (ftObj.isName("Tx")) { + //~ value strings can be Unicode + if (!fieldLookup("V", &obj1)->isString()) { + obj1.free(); + fieldLookup("DV", &obj1); + } + if (obj1.isString()) { + if (fieldLookup("Q", &obj2)->isInt()) { + quadding = obj2.getInt(); + } else { + quadding = acroFormQuadLeft; + } + obj2.free(); + comb = 0; + if (flags & acroFormFlagComb) { + if (fieldLookup("MaxLen", &obj2)->isInt()) { + comb = obj2.getInt(); + } + obj2.free(); + } + drawText(obj1.getString(), da, fontDict, + flags & acroFormFlagMultiline, comb, quadding, + gTrue, gFalse, rot, xMin, yMin, xMax, yMax, borderWidth); + } + obj1.free(); + } else if (ftObj.isName("Ch")) { + //~ value/option strings can be Unicode + if (fieldLookup("Q", &obj1)->isInt()) { + quadding = obj1.getInt(); + } else { + quadding = acroFormQuadLeft; + } + obj1.free(); + // combo box + if (flags & acroFormFlagCombo) { + if (fieldLookup("V", &obj1)->isString()) { + drawText(obj1.getString(), da, fontDict, + gFalse, 0, quadding, gTrue, gFalse, rot, + xMin, yMin, xMax, yMax, borderWidth); + //~ Acrobat draws a popup icon on the right side + } + obj1.free(); + // list box + } else { + if (fieldObj.dictLookup("Opt", &obj1)->isArray()) { + nOptions = obj1.arrayGetLength(); + // get the option text + text = (GString **)gmallocn(nOptions, sizeof(GString *)); + for (i = 0; i < nOptions; ++i) { + text[i] = NULL; + obj1.arrayGet(i, &obj2); + if (obj2.isString()) { + text[i] = obj2.getString()->copy(); + } else if (obj2.isArray() && obj2.arrayGetLength() == 2) { + if (obj2.arrayGet(1, &obj3)->isString()) { + text[i] = obj3.getString()->copy(); + } + obj3.free(); + } + obj2.free(); + if (!text[i]) { + text[i] = new GString(); + } + } + // get the selected option(s) + selection = (GBool *)gmallocn(nOptions, sizeof(GBool)); + //~ need to use the I field in addition to the V field + fieldLookup("V", &obj2); + for (i = 0; i < nOptions; ++i) { + selection[i] = gFalse; + if (obj2.isString()) { + if (!obj2.getString()->cmp(text[i])) { + selection[i] = gTrue; + } + } else if (obj2.isArray()) { + for (j = 0; j < obj2.arrayGetLength(); ++j) { + if (obj2.arrayGet(j, &obj3)->isString() && + !obj3.getString()->cmp(text[i])) { + selection[i] = gTrue; + } + obj3.free(); + } + } + } + obj2.free(); + // get the top index + if (fieldObj.dictLookup("TI", &obj2)->isInt()) { + topIdx = obj2.getInt(); + } else { + topIdx = 0; + } + obj2.free(); + // draw the text + drawListBox(text, selection, nOptions, topIdx, da, fontDict, quadding, + xMin, yMin, xMax, yMax, borderWidth); + for (i = 0; i < nOptions; ++i) { + delete text[i]; + } + gfree(text); + gfree(selection); + } + obj1.free(); + } + } else if (ftObj.isName("Sig")) { + //~unimp + } else { + error(errSyntaxError, -1, "Unknown field type"); + } + + delete appearanceState; + if (da) { + delete da; + } + + // build the appearance stream dictionary + appearDict.initDict(acroForm->doc->getXRef()); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(acroForm->doc->getXRef()); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(xMax - xMin)); + obj1.arrayAdd(obj2.initReal(yMax - yMin)); + appearDict.dictAdd(copyString("BBox"), &obj1); + + // set the resource dictionary + if (drObj.isDict()) { + appearDict.dictAdd(copyString("Resources"), drObj.copy(&obj1)); + } + drObj.free(); + + // build the appearance stream + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.initStream(appearStream); + + // draw it + gfx->drawAnnot(&appearance, NULL, xMin, yMin, xMax, yMax); + + appearance.free(); + delete appearBuf; + appearBuf = NULL; + if (fontDict) { + delete fontDict; + } + ftObj.free(); + mkObj.free(); +} + +// Set the current fill or stroke color, based on (which should +// have 1, 3, or 4 elements). If is +1, color is brightened; +// if is -1, color is darkened; otherwise color is not +// modified. +void AcroFormField::setColor(Array *a, GBool fill, int adjust) { + Object obj1; + double color[4]; + int nComps, i; + + nComps = a->getLength(); + if (nComps > 4) { + nComps = 4; + } + for (i = 0; i < nComps && i < 4; ++i) { + if (a->get(i, &obj1)->isNum()) { + color[i] = obj1.getNum(); + } else { + color[i] = 0; + } + obj1.free(); + } + if (nComps == 4) { + adjust = -adjust; + } + if (adjust > 0) { + for (i = 0; i < nComps; ++i) { + color[i] = 0.5 * color[i] + 0.5; + } + } else if (adjust < 0) { + for (i = 0; i < nComps; ++i) { + color[i] = 0.5 * color[i]; + } + } + if (nComps == 4) { + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:c}\n", + color[0], color[1], color[2], color[3], + fill ? 'k' : 'K'); + } else if (nComps == 3) { + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:s}\n", + color[0], color[1], color[2], + fill ? "rg" : "RG"); + } else { + appearBuf->appendf("{0:.2f} {1:c}\n", + color[0], + fill ? 'g' : 'G'); + } +} + +// Draw the variable text or caption for a field. +void AcroFormField::drawText(GString *text, GString *da, GfxFontDict *fontDict, + GBool multiline, int comb, int quadding, + GBool txField, GBool forceZapfDingbats, int rot, + double xMin, double yMin, double xMax, double yMax, + double border) { + 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; + + //~ if there is no MK entry, this should use the existing content stream, + //~ and only replace the marked content portion of it + //~ (this is only relevant for Tx fields) + + // check for a Unicode string + //~ this currently drops all non-Latin1 characters + if (text->getLength() >= 2 && + text->getChar(0) == '\xfe' && text->getChar(1) == '\xff') { + text2 = new GString(); + for (i = 2; i+1 < text->getLength(); i += 2) { + c = ((text->getChar(i) & 0xff) << 8) + (text->getChar(i+1) & 0xff); + if (c <= 0xff) { + text2->append((char)c); + } else { + text2->append('?'); + } + } + } else { + text2 = text; + } + + // parse the default appearance string + tfPos = tmPos = -1; + if (da) { + daToks = new GList(); + i = 0; + while (i < da->getLength()) { + while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) { + ++i; + } + if (i < da->getLength()) { + for (j = i + 1; + j < da->getLength() && !Lexer::isSpace(da->getChar(j)); + ++j) ; + daToks->append(new GString(da, i, j - i)); + i = j; + } + } + for (i = 2; i < daToks->getLength(); ++i) { + if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) { + tfPos = i - 2; + } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) { + tmPos = i - 6; + } + } + } else { + daToks = NULL; + } + + // force ZapfDingbats + //~ this should create the font if needed (?) + if (forceZapfDingbats) { + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos); + if (tok->cmp("/ZaDb")) { + tok->clear(); + tok->append("/ZaDb"); + } + } + } + + // get the font and font size + font = NULL; + fontSize = 0; + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos); + if (tok->getLength() >= 1 && tok->getChar(0) == '/') { + if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) { + error(errSyntaxError, -1, "Unknown font in field's DA string"); + } + } else { + error(errSyntaxError, -1, + "Invalid font name in 'Tf' operator in field's DA string"); + } + tok = (GString *)daToks->get(tfPos + 1); + fontSize = atof(tok->getCString()); + } else { + error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); + } + + // setup + if (txField) { + appearBuf->append("/Tx BMC\n"); + } + 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; + } 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; + } else if (rot == 270) { + appearBuf->appendf("0 -1 1 0 0 {0:.4f} cm\n", yMax - yMin); + dx = yMax - yMin; + dy = xMax - xMin; + } else { // assume rot == 0 + dx = xMax - xMin; + dy = yMax - yMin; + } + appearBuf->append("BT\n"); + + // multi-line text + if (multiline) { + // note: the comb flag is ignored in multiline mode + + wMax = dx - 2 * border - 4; + + // compute font autosize + if (fontSize == 0) { + for (fontSize = 20; fontSize > 1; --fontSize) { + y = dy - 3; + w2 = 0; + i = 0; + while (i < text2->getLength()) { + getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k); + if (w > w2) { + w2 = w; + } + i = k; + y -= fontSize; + } + // approximate the descender for the last line + if (y >= 0.33 * fontSize) { + break; + } + } + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos + 1); + tok->clear(); + tok->appendf("{0:.2f}", fontSize); + } + } + + // starting y coordinate + // (note: each line of text starts with a Td operator that moves + // down a line) + y = dy - 3; + + // set the font matrix + if (tmPos >= 0) { + tok = (GString *)daToks->get(tmPos + 4); + tok->clear(); + tok->append('0'); + tok = (GString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.4f}", y); + } + + // write the DA string + if (daToks) { + for (i = 0; i < daToks->getLength(); ++i) { + appearBuf->append((GString *)daToks->get(i))->append(' '); + } + } + + // write the font matrix (if not part of the DA string) + if (tmPos < 0) { + appearBuf->appendf("1 0 0 1 0 {0:.4f} Tm\n", y); + } + + // write a series of lines of text + i = 0; + xPrev = 0; + while (i < text2->getLength()) { + + getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k); + + // compute text start position + switch (quadding) { + case acroFormQuadLeft: + default: + x = border + 2; + break; + case acroFormQuadCenter: + x = (dx - w) / 2; + break; + case acroFormQuadRight: + x = dx - border - 2 - w; + break; + } + + // draw the line + appearBuf->appendf("{0:.4f} {1:.4f} Td\n", x - xPrev, -fontSize); + appearBuf->append('('); + for (; i < j; ++i) { + c = text2->getChar(i) & 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append(c); + } + } + appearBuf->append(") Tj\n"); + + // next line + i = k; + xPrev = x; + } + + // single-line text + } else { + //~ replace newlines with spaces? - what does Acrobat do? + + // comb formatting + if (comb > 0) { + + // compute comb spacing + w = (dx - 2 * border) / comb; + + // compute font autosize + if (fontSize == 0) { + fontSize = dy - 2 * border; + if (w < fontSize) { + fontSize = w; + } + fontSize = floor(fontSize); + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos + 1); + tok->clear(); + tok->appendf("{0:.4f}", fontSize); + } + } + + // compute text start position + switch (quadding) { + case acroFormQuadLeft: + default: + x = border + 2; + break; + case acroFormQuadCenter: + x = border + 2 + 0.5 * (comb - text2->getLength()) * w; + break; + case acroFormQuadRight: + x = border + 2 + (comb - text2->getLength()) * w; + break; + } + y = 0.5 * dy - 0.4 * fontSize; + + // set the font matrix + if (tmPos >= 0) { + tok = (GString *)daToks->get(tmPos + 4); + tok->clear(); + tok->appendf("{0:.4f}", x); + tok = (GString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.4f}", y); + } + + // write the DA string + if (daToks) { + for (i = 0; i < daToks->getLength(); ++i) { + appearBuf->append((GString *)daToks->get(i))->append(' '); + } + } + + // write the font matrix (if not part of the DA string) + if (tmPos < 0) { + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y); + } + + // write the text string + //~ this should center (instead of left-justify) each character within + //~ its comb cell + for (i = 0; i < 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(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("{0:.4f} 0 Td\n", w); + } else { + appearBuf->append(c); + } + appearBuf->append(") Tj\n"); + } + + // regular (non-comb) formatting + } else { + + // compute string width + if (font && !font->isCIDFont()) { + w = 0; + for (i = 0; i < text2->getLength(); ++i) { + w += ((Gfx8BitFont *)font)->getWidth(text2->getChar(i)); + } + } else { + // otherwise, make a crude estimate + w = text2->getLength() * 0.5; + } + + // compute font autosize + if (fontSize == 0) { + fontSize = dy - 2 * border; + fontSize2 = (dx - 4 - 2 * border) / w; + if (fontSize2 < fontSize) { + fontSize = fontSize2; + } + fontSize = floor(fontSize); + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos + 1); + tok->clear(); + tok->appendf("{0:.4f}", fontSize); + } + } + + // compute text start position + w *= fontSize; + switch (quadding) { + case acroFormQuadLeft: + default: + x = border + 2; + break; + case acroFormQuadCenter: + x = (dx - w) / 2; + break; + case acroFormQuadRight: + x = dx - border - 2 - w; + break; + } + y = 0.5 * dy - 0.4 * fontSize; + + // set the font matrix + if (tmPos >= 0) { + tok = (GString *)daToks->get(tmPos + 4); + tok->clear(); + tok->appendf("{0:.4f}", x); + tok = (GString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.4f}", y); + } + + // write the DA string + if (daToks) { + for (i = 0; i < daToks->getLength(); ++i) { + appearBuf->append((GString *)daToks->get(i))->append(' '); + } + } + + // write the font matrix (if not part of the DA string) + if (tmPos < 0) { + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y); + } + + // 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(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append(c); + } + } + appearBuf->append(") Tj\n"); + } + } + + // cleanup + appearBuf->append("ET\n"); + appearBuf->append("Q\n"); + if (txField) { + appearBuf->append("EMC\n"); + } + + if (daToks) { + deleteGList(daToks, GString); + } + if (text2 != text) { + delete text2; + } +} + +// Draw the variable text or caption for a field. +void AcroFormField::drawListBox(GString **text, GBool *selection, + int nOptions, int topIdx, + GString *da, GfxFontDict *fontDict, + GBool quadding, double xMin, double yMin, + double xMax, double yMax, double border) { + GList *daToks; + GString *tok; + GfxFont *font; + double fontSize, fontSize2, x, y, w, wMax; + int tfPos, tmPos, i, j, c; + + //~ if there is no MK entry, this should use the existing content stream, + //~ and only replace the marked content portion of it + //~ (this is only relevant for Tx fields) + + // parse the default appearance string + tfPos = tmPos = -1; + if (da) { + daToks = new GList(); + i = 0; + while (i < da->getLength()) { + while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) { + ++i; + } + if (i < da->getLength()) { + for (j = i + 1; + j < da->getLength() && !Lexer::isSpace(da->getChar(j)); + ++j) ; + daToks->append(new GString(da, i, j - i)); + i = j; + } + } + for (i = 2; i < daToks->getLength(); ++i) { + if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) { + tfPos = i - 2; + } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) { + tmPos = i - 6; + } + } + } else { + daToks = NULL; + } + + // get the font and font size + font = NULL; + fontSize = 0; + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos); + if (tok->getLength() >= 1 && tok->getChar(0) == '/') { + if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) { + error(errSyntaxError, -1, "Unknown font in field's DA string"); + } + } else { + error(errSyntaxError, -1, + "Invalid font name in 'Tf' operator in field's DA string"); + } + tok = (GString *)daToks->get(tfPos + 1); + fontSize = atof(tok->getCString()); + } else { + error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); + } + + // compute font autosize + if (fontSize == 0) { + wMax = 0; + for (i = 0; i < nOptions; ++i) { + if (font && !font->isCIDFont()) { + w = 0; + for (j = 0; j < text[i]->getLength(); ++j) { + w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); + } + } else { + // otherwise, make a crude estimate + w = text[i]->getLength() * 0.5; + } + if (w > wMax) { + wMax = w; + } + } + fontSize = yMax - yMin - 2 * border; + fontSize2 = (xMax - xMin - 4 - 2 * border) / wMax; + if (fontSize2 < fontSize) { + fontSize = fontSize2; + } + fontSize = floor(fontSize); + if (tfPos >= 0) { + tok = (GString *)daToks->get(tfPos + 1); + tok->clear(); + tok->appendf("{0:.4f}", fontSize); + } + } + + // draw the text + y = yMax - yMin - 1.1 * fontSize; + for (i = topIdx; i < nOptions; ++i) { + + // setup + appearBuf->append("q\n"); + + // draw the background if selected + if (selection[i]) { + appearBuf->append("0 g f\n"); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} re f\n", + border, + y - 0.2 * fontSize, + xMax - xMin - 2 * border, + 1.1 * fontSize); + } + + // setup + appearBuf->append("BT\n"); + + // compute string width + if (font && !font->isCIDFont()) { + w = 0; + for (j = 0; j < text[i]->getLength(); ++j) { + w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); + } + } else { + // otherwise, make a crude estimate + w = text[i]->getLength() * 0.5; + } + + // compute text start position + w *= fontSize; + switch (quadding) { + case acroFormQuadLeft: + default: + x = border + 2; + break; + case acroFormQuadCenter: + x = (xMax - xMin - w) / 2; + break; + case acroFormQuadRight: + x = xMax - xMin - border - 2 - w; + break; + } + + // set the font matrix + if (tmPos >= 0) { + tok = (GString *)daToks->get(tmPos + 4); + tok->clear(); + tok->appendf("{0:.4f}", x); + tok = (GString *)daToks->get(tmPos + 5); + tok->clear(); + tok->appendf("{0:.4f}", y); + } + + // write the DA string + if (daToks) { + for (j = 0; j < daToks->getLength(); ++j) { + appearBuf->append((GString *)daToks->get(j))->append(' '); + } + } + + // write the font matrix (if not part of the DA string) + if (tmPos < 0) { + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", x, y); + } + + // change the text color if selected + if (selection[i]) { + appearBuf->append("1 g\n"); + } + + // write the text string + appearBuf->append('('); + for (j = 0; j < text[i]->getLength(); ++j) { + c = text[i]->getChar(j) & 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append(c); + } + } + appearBuf->append(") Tj\n"); + + // cleanup + appearBuf->append("ET\n"); + appearBuf->append("Q\n"); + + // next line + y -= 1.1 * fontSize; + } + + if (daToks) { + deleteGList(daToks, GString); + } +} + +// Figure out how much text will fit on the next line. Returns: +// *end = one past the last character to be included +// *width = width of the characters start .. end-1 +// *next = index of first character on the following line +void AcroFormField::getNextLine(GString *text, int start, + GfxFont *font, double fontSize, double wMax, + int *end, double *width, int *next) { + double w, dw; + int j, k, c; + + // figure out how much text will fit on the line + //~ what does Adobe do with tabs? + w = 0; + for (j = start; j < text->getLength() && w <= wMax; ++j) { + c = text->getChar(j) & 0xff; + if (c == 0x0a || c == 0x0d) { + break; + } + if (font && !font->isCIDFont()) { + dw = ((Gfx8BitFont *)font)->getWidth(c) * fontSize; + } else { + // otherwise, make a crude estimate + dw = 0.5 * fontSize; + } + w += dw; + } + if (w > wMax) { + for (k = j; k > start && text->getChar(k-1) != ' '; --k) ; + for (; k > start && text->getChar(k-1) == ' '; --k) ; + if (k > start) { + j = k; + } + if (j == start) { + // handle the pathological case where the first character is + // too wide to fit on the line all by itself + j = start + 1; + } + } + *end = j; + + // compute the width + w = 0; + for (k = start; k < j; ++k) { + if (font && !font->isCIDFont()) { + dw = ((Gfx8BitFont *)font)->getWidth(text->getChar(k)) * fontSize; + } else { + // otherwise, make a crude estimate + dw = 0.5 * fontSize; + } + w += dw; + } + *width = w; + + // next line + while (j < text->getLength() && text->getChar(j) == ' ') { + ++j; + } + if (j < text->getLength() && text->getChar(j) == 0x0d) { + ++j; + } + if (j < text->getLength() && text->getChar(j) == 0x0a) { + ++j; + } + *next = j; +} + +// Draw an (approximate) circle of radius centered at (, ). +// is used to draw the circle ("f", "s", or "b"). +void AcroFormField::drawCircle(double cx, double cy, double r, + const char *cmd) { + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + cx + r, cy); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx + r, cy + bezierCircle * r, + cx + bezierCircle * r, cy + r, + cx, cy + r); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx - bezierCircle * r, cy + r, + cx - r, cy + bezierCircle * r, + cx - r, cy); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx - r, cy - bezierCircle * r, + cx - bezierCircle * r, cy - r, + cx, cy - r); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx + bezierCircle * r, cy - r, + cx + r, cy - bezierCircle * r, + cx + r, cy); + appearBuf->appendf("{0:s}\n", cmd); +} + +// Draw the top-left half of an (approximate) circle of radius +// centered at (, ). +void AcroFormField::drawCircleTopLeft(double cx, double cy, double r) { + double r2; + + r2 = r / sqrt(2.0); + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + cx + r2, cy + r2); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx + (1 - bezierCircle) * r2, + cy + (1 + bezierCircle) * r2, + cx - (1 - bezierCircle) * r2, + cy + (1 + bezierCircle) * r2, + cx - r2, + cy + r2); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx - (1 + bezierCircle) * r2, + cy + (1 - bezierCircle) * r2, + cx - (1 + bezierCircle) * r2, + cy - (1 - bezierCircle) * r2, + cx - r2, + cy - r2); + appearBuf->append("S\n"); +} + +// Draw the bottom-right half of an (approximate) circle of radius +// centered at (, ). +void AcroFormField::drawCircleBottomRight(double cx, double cy, double r) { + double r2; + + r2 = r / sqrt(2.0); + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + cx - r2, cy - r2); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx - (1 - bezierCircle) * r2, + cy - (1 + bezierCircle) * r2, + cx + (1 - bezierCircle) * r2, + cy - (1 + bezierCircle) * r2, + cx + r2, + cy - r2); + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", + cx + (1 + bezierCircle) * r2, + cy - (1 - bezierCircle) * r2, + cx + (1 + bezierCircle) * r2, + cy + (1 - bezierCircle) * r2, + cx + r2, + cy + r2); + appearBuf->append("S\n"); +} + +Object *AcroFormField::getResources(Object *res) { + Object kidsObj, annotObj, obj1; + int i; + + if (acroForm->needAppearances) { + fieldLookup("DR", res); + } else { + res->initArray(acroForm->doc->getXRef()); + // find the annotation object(s) + if (fieldObj.dictLookup("Kids", &kidsObj)->isArray()) { + for (i = 0; i < kidsObj.arrayGetLength(); ++i) { + kidsObj.arrayGet(i, &annotObj); + if (annotObj.isDict()) { + if (getAnnotResources(annotObj.getDict(), &obj1)->isDict()) { + res->arrayAdd(&obj1); + } else { + obj1.free(); + } + } + annotObj.free(); + } + } else { + if (getAnnotResources(fieldObj.getDict(), &obj1)->isDict()) { + res->arrayAdd(&obj1); + } else { + obj1.free(); + } + } + kidsObj.free(); + } + + return res; +} + +Object *AcroFormField::getAnnotResources(Dict *annot, Object *res) { + Object apObj, asObj, appearance, obj1; + + // get the appearance stream + if (annot->lookup("AP", &apObj)->isDict()) { + apObj.dictLookup("N", &obj1); + if (obj1.isDict()) { + if (annot->lookup("AS", &asObj)->isName()) { + obj1.dictLookup(asObj.getName(), &appearance); + } else if (obj1.dictGetLength() == 1) { + obj1.dictGetVal(0, &appearance); + } else { + obj1.dictLookup("Off", &appearance); + } + asObj.free(); + } else { + obj1.copy(&appearance); + } + obj1.free(); + } + apObj.free(); + + if (appearance.isStream()) { + appearance.streamGetDict()->lookup("Resources", res); + } else { + res->initNull(); + } + appearance.free(); + + return res; +} + +// Look up an inheritable field dictionary entry. +Object *AcroFormField::fieldLookup(const char *key, Object *obj) { + return fieldLookup(fieldObj.getDict(), key, obj); +} + +Object *AcroFormField::fieldLookup(Dict *dict, const char *key, Object *obj) { + Object parent; + + 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); + } + parent.free(); + return obj; +} diff -uNrp xpdf-3.03/xpdf/AcroForm.h xpdf-3.04/xpdf/AcroForm.h --- xpdf-3.03/xpdf/AcroForm.h 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/AcroForm.h 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,128 @@ +//======================================================================== +// +// AcroForm.h +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef ACROFORM_H +#define ACROFORM_H + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include "Form.h" + +class TextString; +class GfxFont; +class GfxFontDict; + +//------------------------------------------------------------------------ + +class AcroForm: public Form { +public: + + static AcroForm *load(PDFDoc *docA, Catalog *catalog, Object *acroFormObjA); + + virtual ~AcroForm(); + + virtual const char *getType() { return "AcroForm"; } + + virtual void draw(int pageNum, Gfx *gfx, GBool printing); + + virtual int getNumFields(); + virtual FormField *getField(int idx); + +private: + + AcroForm(PDFDoc *docA, Object *acroFormObjA); + void buildAnnotPageList(Catalog *catalog); + int lookupAnnotPage(Object *annotRef); + void scanField(Object *fieldRef); + + Object acroFormObj; + GBool needAppearances; + GList *annotPages; // [AcroFormAnnotPage] + GList *fields; // [AcroFormField] + + friend class AcroFormField; +}; + +//------------------------------------------------------------------------ + +enum AcroFormFieldType { + acroFormFieldPushbutton, + acroFormFieldRadioButton, + acroFormFieldCheckbox, + acroFormFieldFileSelect, + acroFormFieldMultilineText, + acroFormFieldText, + acroFormFieldComboBox, + acroFormFieldListBox, + acroFormFieldSignature +}; + +class AcroFormField: public FormField { +public: + + static AcroFormField *load(AcroForm *acroFormA, Object *fieldRefA); + + virtual ~AcroFormField(); + + virtual const char *getType(); + virtual Unicode *getName(int *length); + virtual Unicode *getValue(int *length); + + virtual Object *getResources(Object *res); + +private: + + AcroFormField(AcroForm *acroFormA, Object *fieldRefA, Object *fieldObjA, + AcroFormFieldType typeA, TextString *nameA, + Guint flagsA); + void draw(int pageNum, Gfx *gfx, GBool printing); + void drawAnnot(int pageNum, Gfx *gfx, GBool printing, + Object *annotRef, Object *annotObj); + void drawExistingAppearance(Gfx *gfx, Dict *annot, + double xMin, double yMin, + double xMax, double yMax); + void drawNewAppearance(Gfx *gfx, Dict *annot, + double xMin, double yMin, + double xMax, double yMax); + void setColor(Array *a, GBool fill, int adjust); + void drawText(GString *text, GString *da, GfxFontDict *fontDict, + GBool multiline, int comb, int quadding, + GBool txField, GBool forceZapfDingbats, int rot, + double xMin, double yMin, double xMax, double yMax, + double border); + void drawListBox(GString **text, GBool *selection, + int nOptions, int topIdx, + GString *da, GfxFontDict *fontDict, + GBool quadding, double xMin, double yMin, + double xMax, double yMax, double border); + void getNextLine(GString *text, int start, + GfxFont *font, double fontSize, double wMax, + int *end, double *width, int *next); + void drawCircle(double cx, double cy, double r, const char *cmd); + void drawCircleTopLeft(double cx, double cy, double r); + void drawCircleBottomRight(double cx, double cy, double r); + Object *getAnnotResources(Dict *annot, Object *res); + Object *fieldLookup(const char *key, Object *obj); + Object *fieldLookup(Dict *dict, const char *key, Object *obj); + + AcroForm *acroForm; + Object fieldRef; + Object fieldObj; + AcroFormFieldType type; + TextString *name; + Guint flags; + GString *appearBuf; + + friend class AcroForm; +}; + +#endif diff -uNrp xpdf-3.03/xpdf/Annot.cc xpdf-3.04/xpdf/Annot.cc --- xpdf-3.03/xpdf/Annot.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Annot.cc 2014-05-28 20:50:50.000000000 +0200 @@ -24,56 +24,44 @@ #include "Lexer.h" #include "PDFDoc.h" #include "OptionalContent.h" +#include "Form.h" #include "Annot.h" +// the MSVC math.h doesn't define this +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + //------------------------------------------------------------------------ #define annotFlagHidden 0x0002 #define annotFlagPrint 0x0004 #define annotFlagNoView 0x0020 -#define fieldFlagReadOnly 0x00000001 -#define fieldFlagRequired 0x00000002 -#define fieldFlagNoExport 0x00000004 -#define fieldFlagMultiline 0x00001000 -#define fieldFlagPassword 0x00002000 -#define fieldFlagNoToggleToOff 0x00004000 -#define fieldFlagRadio 0x00008000 -#define fieldFlagPushbutton 0x00010000 -#define fieldFlagCombo 0x00020000 -#define fieldFlagEdit 0x00040000 -#define fieldFlagSort 0x00080000 -#define fieldFlagFileSelect 0x00100000 -#define fieldFlagMultiSelect 0x00200000 -#define fieldFlagDoNotSpellCheck 0x00400000 -#define fieldFlagDoNotScroll 0x00800000 -#define fieldFlagComb 0x01000000 -#define fieldFlagRichText 0x02000000 -#define fieldFlagRadiosInUnison 0x02000000 -#define fieldFlagCommitOnSelChange 0x04000000 - -#define fieldQuadLeft 0 -#define fieldQuadCenter 1 -#define fieldQuadRight 2 - // distance of Bezier control point from center for circle approximation // = (4 * (sqrt(2) - 1) / 3) * r #define bezierCircle 0.55228475 +#define lineEndSize1 6 +#define lineEndSize2 10 +#define lineArrowAngle (M_PI / 6) + //------------------------------------------------------------------------ // AnnotBorderStyle //------------------------------------------------------------------------ AnnotBorderStyle::AnnotBorderStyle(AnnotBorderType typeA, double widthA, double *dashA, int dashLengthA, - double rA, double gA, double bA) { + double *colorA, int nColorCompsA) { type = typeA; width = widthA; dash = dashA; dashLength = dashLengthA; - r = rA; - g = gA; - b = bA; + color[0] = colorA[0]; + color[1] = colorA[1]; + color[2] = colorA[2]; + color[3] = colorA[3]; + nColorComps = nColorCompsA; } AnnotBorderStyle::~AnnotBorderStyle() { @@ -92,7 +80,8 @@ Annot::Annot(PDFDoc *docA, Dict *dict, R double borderWidth; double *borderDash; int borderDashLength; - double borderR, borderG, borderB; + double borderColor[4]; + int nBorderColorComps; double t; int i; @@ -160,9 +149,11 @@ Annot::Annot(PDFDoc *docA, Dict *dict, R borderWidth = 1; borderDash = NULL; borderDashLength = 0; - borderR = 0; - borderG = 0; - borderB = 1; + nBorderColorComps = 3; + borderColor[0] = 0; + borderColor[1] = 0; + borderColor[2] = 1; + borderColor[3] = 0; if (dict->lookup("BS", &obj1)->isDict()) { if (obj1.dictLookup("S", &obj2)->isName()) { if (obj2.isName("S")) { @@ -223,28 +214,31 @@ Annot::Annot(PDFDoc *docA, Dict *dict, R } obj2.free(); } + } else { + // an empty Border array also means "no border" + borderWidth = 0; } } } obj1.free(); - if (dict->lookup("C", &obj1)->isArray() && obj1.arrayGetLength() == 3) { - if (obj1.arrayGet(0, &obj2)->isNum()) { - borderR = obj2.getNum(); - } - obj1.free(); - if (obj1.arrayGet(1, &obj2)->isNum()) { - borderG = obj2.getNum(); - } - obj1.free(); - if (obj1.arrayGet(2, &obj2)->isNum()) { - borderB = obj2.getNum(); + if (dict->lookup("C", &obj1)->isArray() && + (obj1.arrayGetLength() == 1 || + obj1.arrayGetLength() == 3 || + obj1.arrayGetLength() == 4)) { + nBorderColorComps = obj1.arrayGetLength(); + for (i = 0; i < nBorderColorComps; ++i) { + if (obj1.arrayGet(i, &obj2)->isNum()) { + borderColor[i] = obj2.getNum(); + } else { + borderColor[i] = 0; + } + obj2.free(); } - obj1.free(); } obj1.free(); borderStyle = new AnnotBorderStyle(borderType, borderWidth, borderDash, borderDashLength, - borderR, borderG, borderB); + borderColor, nBorderColorComps); //----- get the appearance state @@ -304,350 +298,188 @@ Annot::~Annot() { ocObj.free(); } -void Annot::generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm) { - Object mkObj, ftObj, appearDict, drObj, obj1, obj2, obj3; - Dict *mkDict; - MemStream *appearStream; - GfxFontDict *fontDict; - GBool hasCaption; - double w, dx, dy, r; - double *dash; - GString *caption, *da; - GString **text; - GBool *selection; - int rot, dashLength, ff, quadding, comb, nOptions, topIdx, i, j; - - // must be a Widget annotation - if (type && type->cmp("Widget")) { - return; - } - - appearBuf = new GString(); - - // get the appearance characteristics (MK) dictionary - if (annot->lookup("MK", &mkObj)->isDict()) { - mkDict = mkObj.getDict(); - } else { - mkDict = NULL; - } - - // draw the background - if (mkDict) { - if (mkDict->lookup("BG", &obj1)->isArray() && - obj1.arrayGetLength() > 0) { - setColor(obj1.getArray(), gTrue, 0); - appearBuf->appendf("0 0 {0:.2f} {1:.2f} re f\n", - xMax - xMin, yMax - yMin); - } - obj1.free(); - } - - // get the field type - fieldLookup(field, acroForm, "FT", &ftObj); - - // get the field flags (Ff) value - if (fieldLookup(field, acroForm, "Ff", &obj1)->isInt()) { - ff = obj1.getInt(); - } else { - ff = 0; - } - obj1.free(); - - // draw the border - if (mkDict) { - w = borderStyle->getWidth(); - if (w > 0) { - mkDict->lookup("BC", &obj1); - if (!(obj1.isArray() && obj1.arrayGetLength() > 0)) { - mkDict->lookup("BG", &obj1); - } - if (obj1.isArray() && obj1.arrayGetLength() > 0) { - dx = xMax - xMin; - dy = yMax - yMin; - - // radio buttons with no caption have a round border - hasCaption = mkDict->lookup("CA", &obj2)->isString(); - obj2.free(); - if (ftObj.isName("Btn") && (ff & fieldFlagRadio) && !hasCaption) { - r = 0.5 * (dx < dy ? dx : dy); - switch (borderStyle->getType()) { - case annotBorderDashed: - appearBuf->append("["); - borderStyle->getDash(&dash, &dashLength); - for (i = 0; i < dashLength; ++i) { - appearBuf->appendf(" {0:.2f}", dash[i]); - } - appearBuf->append("] 0 d\n"); - // fall through to the solid case - case annotBorderSolid: - case annotBorderUnderlined: - appearBuf->appendf("{0:.2f} w\n", w); - setColor(obj1.getArray(), gFalse, 0); - drawCircle(0.5 * dx, 0.5 * dy, r - 0.5 * w, gFalse); - break; - case annotBorderBeveled: - case annotBorderInset: - appearBuf->appendf("{0:.2f} w\n", 0.5 * w); - setColor(obj1.getArray(), gFalse, 0); - drawCircle(0.5 * dx, 0.5 * dy, r - 0.25 * w, gFalse); - setColor(obj1.getArray(), gFalse, - borderStyle->getType() == annotBorderBeveled ? 1 : -1); - drawCircleTopLeft(0.5 * dx, 0.5 * dy, r - 0.75 * w); - setColor(obj1.getArray(), gFalse, - borderStyle->getType() == annotBorderBeveled ? -1 : 1); - drawCircleBottomRight(0.5 * dx, 0.5 * dy, r - 0.75 * w); - break; - } - - } else { - switch (borderStyle->getType()) { - case annotBorderDashed: - appearBuf->append("["); - borderStyle->getDash(&dash, &dashLength); - for (i = 0; i < dashLength; ++i) { - appearBuf->appendf(" {0:.2f}", dash[i]); - } - appearBuf->append("] 0 d\n"); - // fall through to the solid case - case annotBorderSolid: - appearBuf->appendf("{0:.2f} w\n", w); - setColor(obj1.getArray(), gFalse, 0); - appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re s\n", - 0.5 * w, dx - w, dy - w); - break; - case annotBorderBeveled: - case annotBorderInset: - setColor(obj1.getArray(), gTrue, - borderStyle->getType() == annotBorderBeveled ? 1 : -1); - appearBuf->append("0 0 m\n"); - appearBuf->appendf("0 {0:.2f} l\n", dy); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", w, dy - w); - appearBuf->appendf("{0:.2f} {0:.2f} l\n", w); - appearBuf->append("f\n"); - setColor(obj1.getArray(), gTrue, - borderStyle->getType() == annotBorderBeveled ? -1 : 1); - appearBuf->append("0 0 m\n"); - appearBuf->appendf("{0:.2f} 0 l\n", dx); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx, dy); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, dy - w); - appearBuf->appendf("{0:.2f} {1:.2f} l\n", dx - w, w); - appearBuf->appendf("{0:.2f} {0:.2f} l\n", w); - appearBuf->append("f\n"); - break; - case annotBorderUnderlined: - appearBuf->appendf("{0:.2f} w\n", w); - setColor(obj1.getArray(), gFalse, 0); - appearBuf->appendf("0 0 m {0:.2f} 0 l s\n", dx); - break; - } +void Annot::generateAnnotAppearance() { + Object obj; - // clip to the inside of the border - appearBuf->appendf("{0:.2f} {0:.2f} {1:.2f} {2:.2f} re W n\n", - w, dx - 2 * w, dy - 2 * w); - } + appearance.fetch(doc->getXRef(), &obj); + if (!obj.isStream()) { + if (type) { + if (!type->cmp("Line")) { + generateLineAppearance(); + } else if (!type->cmp("PolyLine")) { + generatePolyLineAppearance(); + } else if (!type->cmp("Polygon")) { + generatePolygonAppearance(); } - obj1.free(); } } + obj.free(); +} - // get the resource dictionary - fieldLookup(field, acroForm, "DR", &drObj); +//~ this doesn't draw the caption +void Annot::generateLineAppearance() { + Object annotObj, gfxStateDict, appearDict, obj1, obj2; + MemStream *appearStream; + double x1, y1, x2, y2, dx, dy, len, w; + double lx1, ly1, lx2, ly2; + double tx1, ty1, tx2, ty2; + double ax1, ay1, ax2, ay2; + double bx1, by1, bx2, by2; + double leaderLen, leaderExtLen, leaderOffLen; + AnnotLineEndType lineEnd1, lineEnd2; + GBool fill; - // build the font dictionary - if (drObj.isDict() && drObj.dictLookup("Font", &obj1)->isDict()) { - fontDict = new GfxFontDict(doc->getXRef(), NULL, obj1.getDict()); - } else { - fontDict = NULL; + if (!getObject(&annotObj)->isDict()) { + annotObj.free(); + return; } - obj1.free(); - // get the default appearance string - if (fieldLookup(field, acroForm, "DA", &obj1)->isNull()) { - obj1.free(); - acroForm->lookup("DA", &obj1); - } - if (obj1.isString()) { - da = obj1.getString()->copy(); - } else { - da = NULL; + appearBuf = new GString(); + + //----- check for transparency + if (annotObj.dictLookup("CA", &obj1)->isNum()) { + gfxStateDict.initDict(doc->getXRef()); + gfxStateDict.dictAdd(copyString("ca"), obj1.copy(&obj2)); + appearBuf->append("/GS1 gs\n"); } obj1.free(); - // get the rotation value - rot = 0; - if (mkDict) { - if (mkDict->lookup("R", &obj1)->isInt()) { - rot = obj1.getInt(); + //----- set line style, colors + setLineStyle(borderStyle, &w); + setStrokeColor(borderStyle->getColor(), borderStyle->getNumColorComps()); + fill = gFalse; + if (annotObj.dictLookup("IC", &obj1)->isArray()) { + if (setFillColor(&obj1)) { + fill = gTrue; } - obj1.free(); } + obj1.free(); - // draw the field contents - if (ftObj.isName("Btn")) { - caption = NULL; - if (mkDict) { - if (mkDict->lookup("CA", &obj1)->isString()) { - caption = obj1.getString()->copy(); - } - obj1.free(); - } - // radio button - if (ff & fieldFlagRadio) { - //~ Acrobat doesn't draw a caption if there is no AP dict (?) - if (fieldLookup(field, acroForm, "V", &obj1) - ->isName(appearanceState->getCString())) { - if (caption) { - drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter, - gFalse, gTrue, rot); - } else { - if (mkDict) { - if (mkDict->lookup("BC", &obj2)->isArray() && - obj2.arrayGetLength() > 0) { - dx = xMax - xMin; - dy = yMax - yMin; - setColor(obj2.getArray(), gTrue, 0); - drawCircle(0.5 * dx, 0.5 * dy, 0.2 * (dx < dy ? dx : dy), - gTrue); - } - obj2.free(); - } - } - } - obj1.free(); - // pushbutton - } else if (ff & fieldFlagPushbutton) { - if (caption) { - drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter, - gFalse, gFalse, rot); - } - // checkbox + //----- get line properties + if (annotObj.dictLookup("L", &obj1)->isArray() && + obj1.arrayGetLength() == 4) { + if (obj1.arrayGet(0, &obj2)->isNum()) { + x1 = obj2.getNum(); } else { - fieldLookup(field, acroForm, "V", &obj1); - if (obj1.isName() && !obj1.isName("Off")) { - if (!caption) { - caption = new GString("3"); // ZapfDingbats checkmark - } - drawText(caption, da, fontDict, gFalse, 0, fieldQuadCenter, - gFalse, gTrue, rot); - } - obj1.free(); - } - if (caption) { - delete caption; - } - } else if (ftObj.isName("Tx")) { - //~ value strings can be Unicode - if (!fieldLookup(field, acroForm, "V", &obj1)->isString()) { + obj2.free(); obj1.free(); - fieldLookup(field, acroForm, "DV", &obj1); + goto err1; } - if (obj1.isString()) { - if (fieldLookup(field, acroForm, "Q", &obj2)->isInt()) { - quadding = obj2.getInt(); - } else { - quadding = fieldQuadLeft; - } + obj2.free(); + if (obj1.arrayGet(1, &obj2)->isNum()) { + y1 = obj2.getNum(); + } else { obj2.free(); - comb = 0; - if (ff & fieldFlagComb) { - if (fieldLookup(field, acroForm, "MaxLen", &obj2)->isInt()) { - comb = obj2.getInt(); - } - obj2.free(); - } - drawText(obj1.getString(), da, fontDict, - ff & fieldFlagMultiline, comb, quadding, gTrue, gFalse, rot); + obj1.free(); + goto err1; } - obj1.free(); - } else if (ftObj.isName("Ch")) { - //~ value/option strings can be Unicode - if (fieldLookup(field, acroForm, "Q", &obj1)->isInt()) { - quadding = obj1.getInt(); + obj2.free(); + if (obj1.arrayGet(2, &obj2)->isNum()) { + x2 = obj2.getNum(); } else { - quadding = fieldQuadLeft; - } - obj1.free(); - // combo box - if (ff & fieldFlagCombo) { - if (fieldLookup(field, acroForm, "V", &obj1)->isString()) { - drawText(obj1.getString(), da, fontDict, - gFalse, 0, quadding, gTrue, gFalse, rot); - //~ Acrobat draws a popup icon on the right side - } + obj2.free(); obj1.free(); - // list box + goto err1; + } + obj2.free(); + if (obj1.arrayGet(3, &obj2)->isNum()) { + y2 = obj2.getNum(); } else { - if (field->lookup("Opt", &obj1)->isArray()) { - nOptions = obj1.arrayGetLength(); - // get the option text - text = (GString **)gmallocn(nOptions, sizeof(GString *)); - for (i = 0; i < nOptions; ++i) { - text[i] = NULL; - obj1.arrayGet(i, &obj2); - if (obj2.isString()) { - text[i] = obj2.getString()->copy(); - } else if (obj2.isArray() && obj2.arrayGetLength() == 2) { - if (obj2.arrayGet(1, &obj3)->isString()) { - text[i] = obj3.getString()->copy(); - } - obj3.free(); - } - obj2.free(); - if (!text[i]) { - text[i] = new GString(); - } - } - // get the selected option(s) - selection = (GBool *)gmallocn(nOptions, sizeof(GBool)); - //~ need to use the I field in addition to the V field - fieldLookup(field, acroForm, "V", &obj2); - for (i = 0; i < nOptions; ++i) { - selection[i] = gFalse; - if (obj2.isString()) { - if (!obj2.getString()->cmp(text[i])) { - selection[i] = gTrue; - } - } else if (obj2.isArray()) { - for (j = 0; j < obj2.arrayGetLength(); ++j) { - if (obj2.arrayGet(j, &obj3)->isString() && - !obj3.getString()->cmp(text[i])) { - selection[i] = gTrue; - } - obj3.free(); - } - } - } - obj2.free(); - // get the top index - if (field->lookup("TI", &obj2)->isInt()) { - topIdx = obj2.getInt(); - } else { - topIdx = 0; - } - obj2.free(); - // draw the text - drawListBox(text, selection, nOptions, topIdx, da, fontDict, quadding); - for (i = 0; i < nOptions; ++i) { - delete text[i]; - } - gfree(text); - gfree(selection); - } + obj2.free(); obj1.free(); + goto err1; } - } else if (ftObj.isName("Sig")) { - //~unimp + obj2.free(); } else { - error(errSyntaxError, -1, "Unknown field type"); + obj1.free(); + goto err1; } + obj1.free(); + lineEnd1 = lineEnd2 = annotLineEndNone; + if (annotObj.dictLookup("LE", &obj1)->isArray() && + obj1.arrayGetLength() == 2) { + lineEnd1 = parseLineEndType(obj1.arrayGet(0, &obj2)); + obj2.free(); + lineEnd2 = parseLineEndType(obj1.arrayGet(1, &obj2)); + obj2.free(); + } + obj1.free(); + if (annotObj.dictLookup("LL", &obj1)->isNum()) { + leaderLen = obj1.getNum(); + } else { + leaderLen = 0; + } + obj1.free(); + if (annotObj.dictLookup("LLE", &obj1)->isNum()) { + leaderExtLen = obj1.getNum(); + } else { + leaderExtLen = 0; + } + obj1.free(); + if (annotObj.dictLookup("LLO", &obj1)->isNum()) { + leaderOffLen = obj1.getNum(); + } else { + leaderOffLen = 0; + } + obj1.free(); + + //----- compute positions + x1 -= xMin; + y1 -= yMin; + x2 -= xMin; + y2 -= yMin; + dx = x2 - x1; + dy = y2 - y1; + len = sqrt(dx*dx + dy*dy); + if (len > 0) { + dx /= len; + dy /= len; + } + if (leaderLen != 0) { + ax1 = x1 + leaderOffLen * dy; + ay1 = y1 - leaderOffLen * dx; + lx1 = ax1 + leaderLen * dy; + ly1 = ay1 - leaderLen * dx; + bx1 = lx1 + leaderExtLen * dy; + by1 = ly1 - leaderExtLen * dx; + ax2 = x2 + leaderOffLen * dy; + ay2 = y2 - leaderOffLen * dx; + lx2 = ax2 + leaderLen * dy; + ly2 = ay2 - leaderLen * dx; + bx2 = lx2 + leaderExtLen * dy; + by2 = ly2 - leaderExtLen * dx; + } else { + lx1 = x1; + ly1 = y1; + lx2 = x2; + ly2 = y2; + ax1 = ay1 = ax2 = ay2 = 0; // make gcc happy + bx1 = by1 = bx2 = by2 = 0; + } + adjustLineEndpoint(lineEnd1, lx1, ly1, dx, dy, w, &tx1, &ty1); + adjustLineEndpoint(lineEnd2, lx2, ly2, -dx, -dy, w, &tx2, &ty2); + + //----- draw leaders + if (leaderLen != 0) { + appearBuf->appendf("{0:.4f} {1:.4f} m {2:.4f} {3:.4f} l\n", + ax1, ay1, bx1, by1); + appearBuf->appendf("{0:.4f} {1:.4f} m {2:.4f} {3:.4f} l\n", + ax2, ay2 , bx2, by2); + } + + //----- draw the line + appearBuf->appendf("{0:.4f} {1:.4f} m {2:.4f} {3:.4f} l\n", + tx1, ty1, tx2, ty2); + appearBuf->append("S\n"); - if (da) { - delete da; + //----- draw the arrows + if (borderStyle->getType() == annotBorderDashed) { + appearBuf->append("[] 0 d\n"); } + drawLineArrow(lineEnd1, lx1, ly1, dx, dy, w, fill); + drawLineArrow(lineEnd2, lx2, ly2, -dx, -dy, w, fill); - // build the appearance stream dictionary + //----- build the appearance stream dictionary appearDict.initDict(doc->getXRef()); appearDict.dictAdd(copyString("Length"), obj1.initInt(appearBuf->getLength())); @@ -658,757 +490,482 @@ void Annot::generateFieldAppearance(Dict obj1.arrayAdd(obj2.initReal(xMax - xMin)); obj1.arrayAdd(obj2.initReal(yMax - yMin)); appearDict.dictAdd(copyString("BBox"), &obj1); - - // set the resource dictionary - if (drObj.isDict()) { - appearDict.dictAdd(copyString("Resources"), drObj.copy(&obj1)); + if (gfxStateDict.isDict()) { + obj1.initDict(doc->getXRef()); + obj2.initDict(doc->getXRef()); + obj2.dictAdd(copyString("GS1"), &gfxStateDict); + obj1.dictAdd(copyString("ExtGState"), &obj2); + appearDict.dictAdd(copyString("Resources"), &obj1); } - drObj.free(); - // build the appearance stream + //----- build the appearance stream appearStream = new MemStream(appearBuf->getCString(), 0, appearBuf->getLength(), &appearDict); appearance.free(); appearance.initStream(appearStream); - if (fontDict) { - delete fontDict; - } - ftObj.free(); - mkObj.free(); + err1: + annotObj.free(); } -// Set the current fill or stroke color, based on (which should -// have 1, 3, or 4 elements). If is +1, color is brightened; -// if is -1, color is darkened; otherwise color is not -// modified. -void Annot::setColor(Array *a, GBool fill, int adjust) { - Object obj1; - double color[4]; - int nComps, i; +//~ this doesn't handle line ends (arrows) +void Annot::generatePolyLineAppearance() { + Object annotObj, gfxStateDict, appearDict, obj1, obj2; + MemStream *appearStream; + double x1, y1, w; + int i; - nComps = a->getLength(); - if (nComps > 4) { - nComps = 4; - } - for (i = 0; i < nComps && i < 4; ++i) { - if (a->get(i, &obj1)->isNum()) { - color[i] = obj1.getNum(); - } else { - color[i] = 0; - } - obj1.free(); - } - if (nComps == 4) { - adjust = -adjust; - } - if (adjust > 0) { - for (i = 0; i < nComps; ++i) { - color[i] = 0.5 * color[i] + 0.5; - } - } else if (adjust < 0) { - for (i = 0; i < nComps; ++i) { - color[i] = 0.5 * color[i]; - } - } - if (nComps == 4) { - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:c}\n", - color[0], color[1], color[2], color[3], - fill ? 'k' : 'K'); - } else if (nComps == 3) { - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:s}\n", - color[0], color[1], color[2], - fill ? "rg" : "RG"); - } else { - appearBuf->appendf("{0:.2f} {1:c}\n", - color[0], - fill ? 'g' : 'G'); + if (!getObject(&annotObj)->isDict()) { + annotObj.free(); + return; } -} -// Draw the variable text or caption for a field. -void Annot::drawText(GString *text, GString *da, GfxFontDict *fontDict, - GBool multiline, int comb, int quadding, - GBool txField, GBool forceZapfDingbats, int rot) { - GString *text2; - GList *daToks; - GString *tok; - GfxFont *font; - double dx, dy; - double fontSize, fontSize2, border, x, xPrev, y, w, w2, wMax; - int tfPos, tmPos, i, j, k, c; - - //~ if there is no MK entry, this should use the existing content stream, - //~ and only replace the marked content portion of it - //~ (this is only relevant for Tx fields) - - // check for a Unicode string - //~ this currently drops all non-Latin1 characters - if (text->getLength() >= 2 && - text->getChar(0) == '\xfe' && text->getChar(1) == '\xff') { - text2 = new GString(); - for (i = 2; i+1 < text->getLength(); i += 2) { - c = ((text->getChar(i) & 0xff) << 8) + (text->getChar(i+1) & 0xff); - if (c <= 0xff) { - text2->append((char)c); - } else { - text2->append('?'); - } - } - } else { - text2 = text; - } + appearBuf = new GString(); - // parse the default appearance string - tfPos = tmPos = -1; - if (da) { - daToks = new GList(); - i = 0; - while (i < da->getLength()) { - while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) { - ++i; - } - if (i < da->getLength()) { - for (j = i + 1; - j < da->getLength() && !Lexer::isSpace(da->getChar(j)); - ++j) ; - daToks->append(new GString(da, i, j - i)); - i = j; - } - } - for (i = 2; i < daToks->getLength(); ++i) { - if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) { - tfPos = i - 2; - } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) { - tmPos = i - 6; - } - } - } else { - daToks = NULL; + //----- check for transparency + if (annotObj.dictLookup("CA", &obj1)->isNum()) { + gfxStateDict.initDict(doc->getXRef()); + gfxStateDict.dictAdd(copyString("ca"), obj1.copy(&obj2)); + appearBuf->append("/GS1 gs\n"); } + obj1.free(); - // force ZapfDingbats - //~ this should create the font if needed (?) - if (forceZapfDingbats) { - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos); - if (tok->cmp("/ZaDb")) { - tok->clear(); - tok->append("/ZaDb"); - } - } - } + //----- set line style, colors + setLineStyle(borderStyle, &w); + setStrokeColor(borderStyle->getColor(), borderStyle->getNumColorComps()); + // fill = gFalse; + // if (annotObj.dictLookup("IC", &obj1)->isArray()) { + // if (setFillColor(&obj1)) { + // fill = gTrue; + // } + // } + // obj1.free(); - // get the font and font size - font = NULL; - fontSize = 0; - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos); - if (tok->getLength() >= 1 && tok->getChar(0) == '/') { - if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) { - error(errSyntaxError, -1, "Unknown font in field's DA string"); - } - } else { - error(errSyntaxError, -1, - "Invalid font name in 'Tf' operator in field's DA string"); - } - tok = (GString *)daToks->get(tfPos + 1); - fontSize = atof(tok->getCString()); - } else { - error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); + //----- draw line + if (!annotObj.dictLookup("Vertices", &obj1)->isArray()) { + obj1.free(); + goto err1; } - - // get the border width - border = borderStyle->getWidth(); - - // setup - if (txField) { - appearBuf->append("/Tx BMC\n"); - } - appearBuf->append("q\n"); - if (rot == 90) { - appearBuf->appendf("0 1 -1 0 {0:.2f} 0 cm\n", xMax - xMin); - dx = yMax - yMin; - dy = xMax - xMin; - } else if (rot == 180) { - appearBuf->appendf("-1 0 0 -1 {0:.2f} {1:.2f} cm\n", - xMax - xMin, yMax - yMin); - dx = xMax - yMax; - dy = yMax - yMin; - } else if (rot == 270) { - appearBuf->appendf("0 -1 1 0 0 {0:.2f} cm\n", yMax - yMin); - dx = yMax - yMin; - dy = xMax - xMin; - } else { // assume rot == 0 - dx = xMax - xMin; - dy = yMax - yMin; - } - appearBuf->append("BT\n"); - - // multi-line text - if (multiline) { - // note: the comb flag is ignored in multiline mode - - wMax = dx - 2 * border - 4; - - // compute font autosize - if (fontSize == 0) { - for (fontSize = 20; fontSize > 1; --fontSize) { - y = dy - 3; - w2 = 0; - i = 0; - while (i < text2->getLength()) { - getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k); - if (w > w2) { - w2 = w; - } - i = k; - y -= fontSize; - } - // approximate the descender for the last line - if (y >= 0.33 * fontSize) { - break; - } - } - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos + 1); - tok->clear(); - tok->appendf("{0:.2f}", fontSize); - } - } - - // starting y coordinate - // (note: each line of text starts with a Td operator that moves - // down a line) - y = dy - 3; - - // set the font matrix - if (tmPos >= 0) { - tok = (GString *)daToks->get(tmPos + 4); - tok->clear(); - tok->append('0'); - tok = (GString *)daToks->get(tmPos + 5); - tok->clear(); - tok->appendf("{0:.2f}", y); - } - - // write the DA string - if (daToks) { - for (i = 0; i < daToks->getLength(); ++i) { - appearBuf->append((GString *)daToks->get(i))->append(' '); - } + for (i = 0; i+1 < obj1.arrayGetLength(); i += 2) { + if (!obj1.arrayGet(i, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; } - - // write the font matrix (if not part of the DA string) - if (tmPos < 0) { - appearBuf->appendf("1 0 0 1 0 {0:.2f} Tm\n", y); - } - - // write a series of lines of text - i = 0; - xPrev = 0; - while (i < text2->getLength()) { - - getNextLine(text2, i, font, fontSize, wMax, &j, &w, &k); - - // compute text start position - switch (quadding) { - case fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = (dx - w) / 2; - break; - case fieldQuadRight: - x = dx - border - 2 - w; - break; - } - - // draw the line - appearBuf->appendf("{0:.2f} {1:.2f} Td\n", x - xPrev, -fontSize); - appearBuf->append('('); - for (; i < j; ++i) { - c = text2->getChar(i) & 0xff; - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append(c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("\\{0:03o}", c); - } else { - appearBuf->append(c); - } - } - appearBuf->append(") Tj\n"); - - // next line - i = k; - xPrev = x; + x1 = obj2.getNum(); + obj2.free(); + if (!obj1.arrayGet(i+1, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; } - - // single-line text - } else { - //~ replace newlines with spaces? - what does Acrobat do? - - // comb formatting - if (comb > 0) { - - // compute comb spacing - w = (dx - 2 * border) / comb; - - // compute font autosize - if (fontSize == 0) { - fontSize = dy - 2 * border; - if (w < fontSize) { - fontSize = w; - } - fontSize = floor(fontSize); - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos + 1); - tok->clear(); - tok->appendf("{0:.2f}", fontSize); - } - } - - // compute text start position - switch (quadding) { - case fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = border + 2 + 0.5 * (comb - text2->getLength()) * w; - break; - case fieldQuadRight: - x = border + 2 + (comb - text2->getLength()) * w; - break; - } - y = 0.5 * dy - 0.4 * fontSize; - - // set the font matrix - if (tmPos >= 0) { - tok = (GString *)daToks->get(tmPos + 4); - tok->clear(); - tok->appendf("{0:.2f}", x); - tok = (GString *)daToks->get(tmPos + 5); - tok->clear(); - tok->appendf("{0:.2f}", y); - } - - // write the DA string - if (daToks) { - for (i = 0; i < daToks->getLength(); ++i) { - appearBuf->append((GString *)daToks->get(i))->append(' '); - } - } - - // write the font matrix (if not part of the DA string) - if (tmPos < 0) { - appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y); - } - - // write the text string - //~ this should center (instead of left-justify) each character within - //~ its comb cell - for (i = 0; i < text2->getLength(); ++i) { - if (i > 0) { - appearBuf->appendf("{0:.2f} 0 Td\n", w); - } - appearBuf->append('('); - c = text2->getChar(i) & 0xff; - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append(c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("{0:.2f} 0 Td\n", w); - } else { - appearBuf->append(c); - } - appearBuf->append(") Tj\n"); - } - - // regular (non-comb) formatting + y1 = obj2.getNum(); + obj2.free(); + x1 -= xMin; + y1 -= yMin; + if (i == 0) { + appearBuf->appendf("{0:.4f} {1:.4f} m\n", x1, y1); } else { + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x1, y1); + } + } + appearBuf->append("S\n"); + obj1.free(); - // compute string width - if (font && !font->isCIDFont()) { - w = 0; - for (i = 0; i < text2->getLength(); ++i) { - w += ((Gfx8BitFont *)font)->getWidth(text2->getChar(i)); - } - } else { - // otherwise, make a crude estimate - w = text2->getLength() * 0.5; - } + //----- build the appearance stream dictionary + appearDict.initDict(doc->getXRef()); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(doc->getXRef()); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(xMax - xMin)); + obj1.arrayAdd(obj2.initReal(yMax - yMin)); + appearDict.dictAdd(copyString("BBox"), &obj1); + if (gfxStateDict.isDict()) { + obj1.initDict(doc->getXRef()); + obj2.initDict(doc->getXRef()); + obj2.dictAdd(copyString("GS1"), &gfxStateDict); + obj1.dictAdd(copyString("ExtGState"), &obj2); + appearDict.dictAdd(copyString("Resources"), &obj1); + } - // compute font autosize - if (fontSize == 0) { - fontSize = dy - 2 * border; - fontSize2 = (dx - 4 - 2 * border) / w; - if (fontSize2 < fontSize) { - fontSize = fontSize2; - } - fontSize = floor(fontSize); - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos + 1); - tok->clear(); - tok->appendf("{0:.2f}", fontSize); - } - } + //----- build the appearance stream + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.free(); + appearance.initStream(appearStream); - // compute text start position - w *= fontSize; - switch (quadding) { - case fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = (dx - w) / 2; - break; - case fieldQuadRight: - x = dx - border - 2 - w; - break; - } - y = 0.5 * dy - 0.4 * fontSize; + err1: + annotObj.free(); +} - // set the font matrix - if (tmPos >= 0) { - tok = (GString *)daToks->get(tmPos + 4); - tok->clear(); - tok->appendf("{0:.2f}", x); - tok = (GString *)daToks->get(tmPos + 5); - tok->clear(); - tok->appendf("{0:.2f}", y); - } +void Annot::generatePolygonAppearance() { + Object annotObj, gfxStateDict, appearDict, obj1, obj2; + MemStream *appearStream; + double x1, y1; + int i; - // write the DA string - if (daToks) { - for (i = 0; i < daToks->getLength(); ++i) { - appearBuf->append((GString *)daToks->get(i))->append(' '); - } - } + if (!getObject(&annotObj)->isDict()) { + annotObj.free(); + return; + } - // write the font matrix (if not part of the DA string) - if (tmPos < 0) { - appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y); - } + appearBuf = new GString(); - // 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(c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("\\{0:03o}", c); - } else { - appearBuf->append(c); - } - } - appearBuf->append(") Tj\n"); - } + //----- check for transparency + if (annotObj.dictLookup("CA", &obj1)->isNum()) { + gfxStateDict.initDict(doc->getXRef()); + gfxStateDict.dictAdd(copyString("ca"), obj1.copy(&obj2)); + appearBuf->append("/GS1 gs\n"); } + obj1.free(); - // cleanup - appearBuf->append("ET\n"); - appearBuf->append("Q\n"); - if (txField) { - appearBuf->append("EMC\n"); + //----- set fill color + if (!annotObj.dictLookup("IC", &obj1)->isArray() || + !setFillColor(&obj1)) { + obj1.free(); + goto err1; } + obj1.free(); - if (daToks) { - deleteGList(daToks, GString); - } - if (text2 != text) { - delete text2; + //----- fill polygon + if (!annotObj.dictLookup("Vertices", &obj1)->isArray()) { + obj1.free(); + goto err1; } -} - -// Draw the variable text or caption for a field. -void Annot::drawListBox(GString **text, GBool *selection, - int nOptions, int topIdx, - GString *da, GfxFontDict *fontDict, GBool quadding) { - GList *daToks; - GString *tok; - GfxFont *font; - double fontSize, fontSize2, border, x, y, w, wMax; - int tfPos, tmPos, i, j, c; - - //~ if there is no MK entry, this should use the existing content stream, - //~ and only replace the marked content portion of it - //~ (this is only relevant for Tx fields) - - // parse the default appearance string - tfPos = tmPos = -1; - if (da) { - daToks = new GList(); - i = 0; - while (i < da->getLength()) { - while (i < da->getLength() && Lexer::isSpace(da->getChar(i))) { - ++i; - } - if (i < da->getLength()) { - for (j = i + 1; - j < da->getLength() && !Lexer::isSpace(da->getChar(j)); - ++j) ; - daToks->append(new GString(da, i, j - i)); - i = j; - } + for (i = 0; i+1 < obj1.arrayGetLength(); i += 2) { + if (!obj1.arrayGet(i, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; } - for (i = 2; i < daToks->getLength(); ++i) { - if (i >= 2 && !((GString *)daToks->get(i))->cmp("Tf")) { - tfPos = i - 2; - } else if (i >= 6 && !((GString *)daToks->get(i))->cmp("Tm")) { - tmPos = i - 6; - } + x1 = obj2.getNum(); + obj2.free(); + if (!obj1.arrayGet(i+1, &obj2)->isNum()) { + obj2.free(); + obj1.free(); + goto err1; } - } else { - daToks = NULL; - } - - // get the font and font size - font = NULL; - fontSize = 0; - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos); - if (tok->getLength() >= 1 && tok->getChar(0) == '/') { - if (!fontDict || !(font = fontDict->lookup(tok->getCString() + 1))) { - error(errSyntaxError, -1, "Unknown font in field's DA string"); - } + y1 = obj2.getNum(); + obj2.free(); + x1 -= xMin; + y1 -= yMin; + if (i == 0) { + appearBuf->appendf("{0:.4f} {1:.4f} m\n", x1, y1); } else { - error(errSyntaxError, -1, - "Invalid font name in 'Tf' operator in field's DA string"); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x1, y1); } - tok = (GString *)daToks->get(tfPos + 1); - fontSize = atof(tok->getCString()); - } else { - error(errSyntaxError, -1, "Missing 'Tf' operator in field's DA string"); } + appearBuf->append("f\n"); + obj1.free(); - // get the border width - border = borderStyle->getWidth(); - - // compute font autosize - if (fontSize == 0) { - wMax = 0; - for (i = 0; i < nOptions; ++i) { - if (font && !font->isCIDFont()) { - w = 0; - for (j = 0; j < text[i]->getLength(); ++j) { - w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); - } - } else { - // otherwise, make a crude estimate - w = text[i]->getLength() * 0.5; - } - if (w > wMax) { - wMax = w; - } - } - fontSize = yMax - yMin - 2 * border; - fontSize2 = (xMax - xMin - 4 - 2 * border) / wMax; - if (fontSize2 < fontSize) { - fontSize = fontSize2; - } - fontSize = floor(fontSize); - if (tfPos >= 0) { - tok = (GString *)daToks->get(tfPos + 1); - tok->clear(); - tok->appendf("{0:.2f}", fontSize); - } + //----- build the appearance stream dictionary + appearDict.initDict(doc->getXRef()); + appearDict.dictAdd(copyString("Length"), + obj1.initInt(appearBuf->getLength())); + appearDict.dictAdd(copyString("Subtype"), obj1.initName("Form")); + obj1.initArray(doc->getXRef()); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(0)); + obj1.arrayAdd(obj2.initReal(xMax - xMin)); + obj1.arrayAdd(obj2.initReal(yMax - yMin)); + appearDict.dictAdd(copyString("BBox"), &obj1); + if (gfxStateDict.isDict()) { + obj1.initDict(doc->getXRef()); + obj2.initDict(doc->getXRef()); + obj2.dictAdd(copyString("GS1"), &gfxStateDict); + obj1.dictAdd(copyString("ExtGState"), &obj2); + appearDict.dictAdd(copyString("Resources"), &obj1); } - // draw the text - y = yMax - yMin - 1.1 * fontSize; - for (i = topIdx; i < nOptions; ++i) { - - // setup - appearBuf->append("q\n"); - - // draw the background if selected - if (selection[i]) { - appearBuf->append("0 g f\n"); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} re f\n", - border, - y - 0.2 * fontSize, - xMax - xMin - 2 * border, - 1.1 * fontSize); - } - - // setup - appearBuf->append("BT\n"); - - // compute string width - if (font && !font->isCIDFont()) { - w = 0; - for (j = 0; j < text[i]->getLength(); ++j) { - w += ((Gfx8BitFont *)font)->getWidth(text[i]->getChar(j)); - } - } else { - // otherwise, make a crude estimate - w = text[i]->getLength() * 0.5; - } - - // compute text start position - w *= fontSize; - switch (quadding) { - case fieldQuadLeft: - default: - x = border + 2; - break; - case fieldQuadCenter: - x = (xMax - xMin - w) / 2; - break; - case fieldQuadRight: - x = xMax - xMin - border - 2 - w; - break; - } - - // set the font matrix - if (tmPos >= 0) { - tok = (GString *)daToks->get(tmPos + 4); - tok->clear(); - tok->appendf("{0:.2f}", x); - tok = (GString *)daToks->get(tmPos + 5); - tok->clear(); - tok->appendf("{0:.2f}", y); - } - - // write the DA string - if (daToks) { - for (j = 0; j < daToks->getLength(); ++j) { - appearBuf->append((GString *)daToks->get(j))->append(' '); - } - } + //----- build the appearance stream + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.free(); + appearance.initStream(appearStream); - // write the font matrix (if not part of the DA string) - if (tmPos < 0) { - appearBuf->appendf("1 0 0 1 {0:.2f} {1:.2f} Tm\n", x, y); - } - - // change the text color if selected - if (selection[i]) { - appearBuf->append("1 g\n"); - } - - // write the text string - appearBuf->append('('); - for (j = 0; j < text[i]->getLength(); ++j) { - c = text[i]->getChar(j) & 0xff; - if (c == '(' || c == ')' || c == '\\') { - appearBuf->append('\\'); - appearBuf->append(c); - } else if (c < 0x20 || c >= 0x80) { - appearBuf->appendf("\\{0:03o}", c); - } else { - appearBuf->append(c); - } - } - appearBuf->append(") Tj\n"); + err1: + annotObj.free(); +} - // cleanup - appearBuf->append("ET\n"); - appearBuf->append("Q\n"); +void Annot::setLineStyle(AnnotBorderStyle *bs, double *lineWidth) { + double *dash; + double w; + int dashLength, i; - // next line - y -= 1.1 * fontSize; + if ((w = borderStyle->getWidth()) <= 0) { + w = 0.1; } + *lineWidth = w; + appearBuf->appendf("{0:.4f} w\n", w); + // this treats beveled/inset/underline as solid + if (borderStyle->getType() == annotBorderDashed) { + borderStyle->getDash(&dash, &dashLength); + appearBuf->append("["); + for (i = 0; i < dashLength; ++i) { + appearBuf->appendf(" {0:.4f}", dash[i]); + } + appearBuf->append("] 0 d\n"); + } + appearBuf->append("0 j\n0 J\n"); +} - if (daToks) { - deleteGList(daToks, GString); +void Annot::setStrokeColor(double *color, int nComps) { + switch (nComps) { + case 0: + appearBuf->append("0 G\n"); + break; + case 1: + appearBuf->appendf("{0:.2f} G\n", color[0]); + break; + case 3: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} RG\n", + color[0], color[1], color[2]); + break; + case 4: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} K\n", + color[0], color[1], color[2], color[3]); + break; } } -// Figure out how much text will fit on the next line. Returns: -// *end = one past the last character to be included -// *width = width of the characters start .. end-1 -// *next = index of first character on the following line -void Annot::getNextLine(GString *text, int start, - GfxFont *font, double fontSize, double wMax, - int *end, double *width, int *next) { - double w, dw; - int j, k, c; +GBool Annot::setFillColor(Object *colorObj) { + Object obj; + double color[4]; + int i; - // figure out how much text will fit on the line - //~ what does Adobe do with tabs? - w = 0; - for (j = start; j < text->getLength() && w <= wMax; ++j) { - c = text->getChar(j) & 0xff; - if (c == 0x0a || c == 0x0d) { - break; - } - if (font && !font->isCIDFont()) { - dw = ((Gfx8BitFont *)font)->getWidth(c) * fontSize; - } else { - // otherwise, make a crude estimate - dw = 0.5 * fontSize; - } - w += dw; + if (!colorObj->isArray()) { + return gFalse; } - 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; + for (i = 0; i < colorObj->arrayGetLength(); ++i) { + if (colorObj->arrayGet(i, &obj)->isNum()) { + color[i] = obj.getNum(); } else { - // otherwise, make a crude estimate - dw = 0.5 * fontSize; + color[i] = 0; } - w += dw; + obj.free(); + } + switch (colorObj->arrayGetLength()) { + case 1: + appearBuf->appendf("{0:.2f} g\n", color[0]); + return gTrue; + case 3: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} rg\n", + color[0], color[1], color[2]); + return gTrue; + case 4: + appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.3f} k\n", + color[0], color[1], + color[2], color[3]); + return gTrue; } - *width = w; + return gFalse; +} - // next line - while (j < text->getLength() && text->getChar(j) == ' ') { - ++j; +AnnotLineEndType Annot::parseLineEndType(Object *obj) { + if (obj->isName("None")) { + return annotLineEndNone; + } else if (obj->isName("Square")) { + return annotLineEndSquare; + } else if (obj->isName("Circle")) { + return annotLineEndCircle; + } else if (obj->isName("Diamond")) { + return annotLineEndDiamond; + } else if (obj->isName("OpenArrow")) { + return annotLineEndOpenArrow; + } else if (obj->isName("ClosedArrow")) { + return annotLineEndClosedArrow; + } else if (obj->isName("Butt")) { + return annotLineEndButt; + } else if (obj->isName("ROpenArrow")) { + return annotLineEndROpenArrow; + } else if (obj->isName("RClosedArrow")) { + return annotLineEndRClosedArrow; + } else if (obj->isName("Slash")) { + return annotLineEndSlash; + } else { + return annotLineEndNone; } - if (j < text->getLength() && text->getChar(j) == 0x0d) { - ++j; +} + +void Annot::adjustLineEndpoint(AnnotLineEndType lineEnd, + double x, double y, double dx, double dy, + double w, double *tx, double *ty) { + switch (lineEnd) { + case annotLineEndNone: + w = 0; + break; + case annotLineEndSquare: + w *= lineEndSize1; + break; + case annotLineEndCircle: + w *= lineEndSize1; + break; + case annotLineEndDiamond: + w *= lineEndSize1; + break; + case annotLineEndOpenArrow: + w = 0; + break; + case annotLineEndClosedArrow: + w *= lineEndSize2 * cos(lineArrowAngle); + break; + case annotLineEndButt: + w = 0; + break; + case annotLineEndROpenArrow: + w *= lineEndSize2 * cos(lineArrowAngle); + break; + case annotLineEndRClosedArrow: + w *= lineEndSize2 * cos(lineArrowAngle); + break; + case annotLineEndSlash: + w = 0; + break; } - if (j < text->getLength() && text->getChar(j) == 0x0a) { - ++j; + *tx = x + w * dx; + *ty = y + w * dy; +} + +void Annot::drawLineArrow(AnnotLineEndType lineEnd, + double x, double y, double dx, double dy, + double w, GBool fill) { + switch (lineEnd) { + case annotLineEndNone: + break; + case annotLineEndSquare: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*dx + 0.5*w*dy, + y + w*dy - 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + 0.5*w*dy, + y - 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - 0.5*w*dy, + y + 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*dx - 0.5*w*dy, + y + w*dy + 0.5*w*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndCircle: + w *= lineEndSize1; + drawCircle(x + 0.5*w*dx, y + 0.5*w*dy, 0.5*w, fill ? "b" : "s"); + break; + case annotLineEndDiamond: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", x, y); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + 0.5*w*dx - 0.5*w*dy, + y + 0.5*w*dy + 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*dx, + y + w*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + 0.5*w*dx + 0.5*w*dy, + y + 0.5*w*dy - 0.5*w*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndOpenArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*cos(lineArrowAngle)*dx + w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x, y); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx - w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy + w*sin(lineArrowAngle)*dx); + appearBuf->append("S\n"); + break; + case annotLineEndClosedArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*cos(lineArrowAngle)*dx + w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", x, y); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx - w*sin(lineArrowAngle)*dy, + y + w*cos(lineArrowAngle)*dy + w*sin(lineArrowAngle)*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndButt: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + 0.5*w*dy, + y - 0.5*w*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - 0.5*w*dy, + y + 0.5*w*dx); + appearBuf->append("S\n"); + break; + case annotLineEndROpenArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*sin(lineArrowAngle)*dy, + y - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx, + y + w*cos(lineArrowAngle)*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - w*sin(lineArrowAngle)*dy, + y + w*sin(lineArrowAngle)*dx); + appearBuf->append("S\n"); + break; + case annotLineEndRClosedArrow: + w *= lineEndSize2; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + w*sin(lineArrowAngle)*dy, + y - w*sin(lineArrowAngle)*dx); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x + w*cos(lineArrowAngle)*dx, + y + w*cos(lineArrowAngle)*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - w*sin(lineArrowAngle)*dy, + y + w*sin(lineArrowAngle)*dx); + appearBuf->append(fill ? "b\n" : "s\n"); + break; + case annotLineEndSlash: + w *= lineEndSize1; + appearBuf->appendf("{0:.4f} {1:.4f} m\n", + x + 0.5*w*cos(lineArrowAngle)*dy + - 0.5*w*sin(lineArrowAngle)*dx, + y - 0.5*w*cos(lineArrowAngle)*dx + - 0.5*w*sin(lineArrowAngle)*dy); + appearBuf->appendf("{0:.4f} {1:.4f} l\n", + x - 0.5*w*cos(lineArrowAngle)*dy + + 0.5*w*sin(lineArrowAngle)*dx, + y + 0.5*w*cos(lineArrowAngle)*dx + + 0.5*w*sin(lineArrowAngle)*dy); + appearBuf->append("S\n"); + break; } - *next = j; } // Draw an (approximate) circle of radius centered at (, ). -// If is true, the circle is filled; otherwise it is stroked. -void Annot::drawCircle(double cx, double cy, double r, GBool fill) { - appearBuf->appendf("{0:.2f} {1:.2f} m\n", +// is used to draw the circle ("f", "s", or "b"). +void Annot::drawCircle(double cx, double cy, double r, const char *cmd) { + appearBuf->appendf("{0:.4f} {1:.4f} m\n", cx + r, cy); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx + r, cy + bezierCircle * r, cx + bezierCircle * r, cy + r, cx, cy + r); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx - bezierCircle * r, cy + r, cx - r, cy + bezierCircle * r, cx - r, cy); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx - r, cy - bezierCircle * r, cx - bezierCircle * r, cy - r, cx, cy - r); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx + bezierCircle * r, cy - r, cx + r, cy - bezierCircle * r, cx + r, cy); - appearBuf->append(fill ? "f\n" : "s\n"); + appearBuf->appendf("{0:s}\n", cmd); } // Draw the top-left half of an (approximate) circle of radius @@ -1417,16 +974,16 @@ void Annot::drawCircleTopLeft(double cx, double r2; r2 = r / sqrt(2.0); - appearBuf->appendf("{0:.2f} {1:.2f} m\n", + appearBuf->appendf("{0:.4f} {1:.4f} m\n", cx + r2, cy + r2); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx + (1 - bezierCircle) * r2, cy + (1 + bezierCircle) * r2, cx - (1 - bezierCircle) * r2, cy + (1 + bezierCircle) * r2, cx - r2, cy + r2); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx - (1 + bezierCircle) * r2, cy + (1 - bezierCircle) * r2, cx - (1 + bezierCircle) * r2, @@ -1442,16 +999,16 @@ void Annot::drawCircleBottomRight(double double r2; r2 = r / sqrt(2.0); - appearBuf->appendf("{0:.2f} {1:.2f} m\n", + appearBuf->appendf("{0:.4f} {1:.4f} m\n", cx - r2, cy - r2); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx - (1 - bezierCircle) * r2, cy - (1 + bezierCircle) * r2, cx + (1 - bezierCircle) * r2, cy - (1 + bezierCircle) * r2, cx + r2, cy - r2); - appearBuf->appendf("{0:.2f} {1:.2f} {2:.2f} {3:.2f} {4:.2f} {5:.2f} c\n", + appearBuf->appendf("{0:.4f} {1:.4f} {2:.4f} {3:.4f} {4:.4f} {5:.4f} c\n", cx + (1 + bezierCircle) * r2, cy - (1 - bezierCircle) * r2, cx + (1 + bezierCircle) * r2, @@ -1461,32 +1018,7 @@ void Annot::drawCircleBottomRight(double appearBuf->append("S\n"); } -// Look up an inheritable field dictionary entry. -Object *Annot::fieldLookup(Dict *field, Dict *acroForm, - const char *key, Object *obj) { - Dict *dict; - Object parent; - - dict = field; - if (!dict->lookup(key, obj)->isNull()) { - return obj; - } - obj->free(); - if (dict->lookup("Parent", &parent)->isDict()) { - fieldLookup(parent.getDict(), acroForm, key, obj); - } else if (acroForm) { - // some fields don't specify a parent, so we check the AcroForm - // dictionary just in case - fieldLookup(acroForm, NULL, key, obj); - } else { - obj->initNull(); - } - parent.free(); - return obj; -} - void Annot::draw(Gfx *gfx, GBool printing) { - Object obj; GBool oc, isLink; // check the flags @@ -1503,10 +1035,8 @@ void Annot::draw(Gfx *gfx, GBool printin // draw the appearance stream isLink = type && !type->cmp("Link"); - appearance.fetch(doc->getXRef(), &obj); - gfx->drawAnnot(&obj, isLink ? borderStyle : (AnnotBorderStyle *)NULL, + gfx->drawAnnot(&appearance, isLink ? borderStyle : (AnnotBorderStyle *)NULL, xMin, yMin, xMax, yMax); - obj.free(); } Object *Annot::getObject(Object *obj) { @@ -1524,8 +1054,9 @@ Object *Annot::getObject(Object *obj) { Annots::Annots(PDFDoc *docA, Object *annotsObj) { Annot *annot; - Object obj1; + Object obj1, obj2; Ref ref; + GBool drawWidgetAnnots; int size; int i; @@ -1535,6 +1066,13 @@ Annots::Annots(PDFDoc *docA, Object *ann nAnnots = 0; if (annotsObj->isArray()) { + // Kludge: some PDF files define an empty AcroForm, but still + // include Widget-type annotations -- in that case, we want to + // draw the widgets (since the form code won't). This really + // ought to look for Widget-type annotations that are not included + // in any form field. + drawWidgetAnnots = !doc->getCatalog()->getForm() || + doc->getCatalog()->getForm()->getNumFields() == 0; for (i = 0; i < annotsObj->arrayGetLength(); ++i) { if (annotsObj->arrayGetNF(i, &obj1)->isRef()) { ref = obj1.getRef(); @@ -1544,16 +1082,20 @@ Annots::Annots(PDFDoc *docA, Object *ann ref.num = ref.gen = -1; } if (obj1.isDict()) { - annot = new Annot(doc, obj1.getDict(), &ref); - if (annot->isOk()) { - if (nAnnots >= size) { - size += 16; - annots = (Annot **)greallocn(annots, size, sizeof(Annot *)); + if (drawWidgetAnnots || + !obj1.dictLookup("Subtype", &obj2)->isName("Widget")) { + annot = new Annot(doc, obj1.getDict(), &ref); + if (annot->isOk()) { + if (nAnnots >= size) { + size += 16; + annots = (Annot **)greallocn(annots, size, sizeof(Annot *)); + } + annots[nAnnots++] = annot; + } else { + delete annot; } - annots[nAnnots++] = annot; - } else { - delete annot; } + obj2.free(); } obj1.free(); } @@ -1569,69 +1111,11 @@ Annots::~Annots() { gfree(annots); } -void Annots::generateAppearances() { - Dict *acroForm; - Object obj1, obj2; - Ref ref; - int i; - - acroForm = doc->getCatalog()->getAcroForm()->isDict() ? - doc->getCatalog()->getAcroForm()->getDict() : NULL; - if (acroForm->lookup("Fields", &obj1)->isArray()) { - for (i = 0; i < obj1.arrayGetLength(); ++i) { - if (obj1.arrayGetNF(i, &obj2)->isRef()) { - ref = obj2.getRef(); - obj2.free(); - obj1.arrayGet(i, &obj2); - } else { - ref.num = ref.gen = -1; - } - if (obj2.isDict()) { - scanFieldAppearances(obj2.getDict(), &ref, NULL, acroForm); - } - obj2.free(); - } - } - obj1.free(); -} - -void Annots::scanFieldAppearances(Dict *node, Ref *ref, Dict *parent, - Dict *acroForm) { - Annot *annot; - Object obj1, obj2; - Ref ref2; +void Annots::generateAnnotAppearances() { int i; - // non-terminal node: scan the children - if (node->lookup("Kids", &obj1)->isArray()) { - for (i = 0; i < obj1.arrayGetLength(); ++i) { - if (obj1.arrayGetNF(i, &obj2)->isRef()) { - ref2 = obj2.getRef(); - obj2.free(); - obj1.arrayGet(i, &obj2); - } else { - ref2.num = ref2.gen = -1; - } - if (obj2.isDict()) { - scanFieldAppearances(obj2.getDict(), &ref2, node, acroForm); - } - obj2.free(); - } - obj1.free(); - return; - } - obj1.free(); - - // terminal node: this is either a combined annot/field dict, or an - // annot dict whose parent is a field - if ((annot = findAnnot(ref))) { - node->lookupNF("Parent", &obj1); - if (!parent || !obj1.isNull()) { - annot->generateFieldAppearance(node, node, acroForm); - } else { - annot->generateFieldAppearance(parent, node, acroForm); - } - obj1.free(); + for (i = 0; i < nAnnots; ++i) { + annots[i]->generateAnnotAppearance(); } } diff -uNrp xpdf-3.03/xpdf/Annot.h xpdf-3.04/xpdf/Annot.h --- xpdf-3.03/xpdf/Annot.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Annot.h 2014-05-28 20:50:50.000000000 +0200 @@ -38,15 +38,15 @@ public: AnnotBorderStyle(AnnotBorderType typeA, double widthA, double *dashA, int dashLengthA, - double rA, double gA, double bA); + double *colorA, int nColorCompsA); ~AnnotBorderStyle(); AnnotBorderType getType() { return type; } double getWidth() { return width; } void getDash(double **dashA, int *dashLengthA) { *dashA = dash; *dashLengthA = dashLength; } - void getColor(double *rA, double *gA, double *bA) - { *rA = r; *gA = g; *bA = b; } + int getNumColorComps() { return nColorComps; } + double *getColor() { return color; } private: @@ -54,7 +54,23 @@ private: double width; double *dash; int dashLength; - double r, g, b; + double color[4]; + int nColorComps; +}; + +//------------------------------------------------------------------------ + +enum AnnotLineEndType { + annotLineEndNone, + annotLineEndSquare, + annotLineEndCircle, + annotLineEndDiamond, + annotLineEndOpenArrow, + annotLineEndClosedArrow, + annotLineEndButt, + annotLineEndROpenArrow, + annotLineEndRClosedArrow, + annotLineEndSlash }; //------------------------------------------------------------------------ @@ -85,25 +101,26 @@ public: GBool match(Ref *refA) { return ref.num == refA->num && ref.gen == refA->gen; } - void generateFieldAppearance(Dict *field, Dict *annot, Dict *acroForm); + void generateAnnotAppearance(); private: - void setColor(Array *a, GBool fill, int adjust); - void drawText(GString *text, GString *da, GfxFontDict *fontDict, - GBool multiline, int comb, int quadding, - GBool txField, GBool forceZapfDingbats, int rot); - void drawListBox(GString **text, GBool *selection, - int nOptions, int topIdx, - GString *da, GfxFontDict *fontDict, GBool quadding); - void getNextLine(GString *text, int start, - GfxFont *font, double fontSize, double wMax, - int *end, double *width, int *next); - void drawCircle(double cx, double cy, double r, GBool fill); + void generateLineAppearance(); + void generatePolyLineAppearance(); + void generatePolygonAppearance(); + void setLineStyle(AnnotBorderStyle *bs, double *lineWidth); + void setStrokeColor(double *color, int nComps); + GBool setFillColor(Object *colorObj); + AnnotLineEndType parseLineEndType(Object *obj); + void adjustLineEndpoint(AnnotLineEndType lineEnd, + double x, double y, double dx, double dy, + double w, double *tx, double *ty); + void drawLineArrow(AnnotLineEndType lineEnd, + double x, double y, double dx, double dy, + double w, GBool fill); + void drawCircle(double cx, double cy, double r, const char *cmd); void drawCircleTopLeft(double cx, double cy, double r); void drawCircleBottomRight(double cx, double cy, double r); - Object *fieldLookup(Dict *field, Dict *acroForm, - const char *key, Object *obj); PDFDoc *doc; XRef *xref; // the xref table for this PDF file @@ -137,9 +154,9 @@ public: int getNumAnnots() { return nAnnots; } Annot *getAnnot(int i) { return annots[i]; } - // (Re)generate the appearance streams for all annotations belonging - // to a form field. - void generateAppearances(); + // Generate an appearance stream for any non-form-field annotation + // that is missing it. + void generateAnnotAppearances(); private: diff -uNrp xpdf-3.03/xpdf/Catalog.cc xpdf-3.04/xpdf/Catalog.cc --- xpdf-3.03/xpdf/Catalog.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Catalog.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // Catalog.cc // -// Copyright 1996-2007 Glyph & Cog, LLC +// Copyright 1996-2013 Glyph & Cog, LLC // //======================================================================== @@ -27,7 +27,8 @@ #include "Page.h" #include "Error.h" #include "Link.h" -#include "PDFDocEncoding.h" +#include "Form.h" +#include "TextString.h" #include "Catalog.h" //------------------------------------------------------------------------ @@ -69,23 +70,20 @@ PageTreeNode::~PageTreeNode() { class EmbeddedFile { public: - EmbeddedFile(Unicode *nameA, int nameLenA, Object *streamRefA); + EmbeddedFile(TextString *nameA, Object *streamRefA); ~EmbeddedFile(); - Unicode *name; - int nameLen; + TextString *name; Object streamRef; }; -EmbeddedFile::EmbeddedFile(Unicode *nameA, int nameLenA, - Object *streamRefA) { +EmbeddedFile::EmbeddedFile(TextString *nameA, Object *streamRefA) { name = nameA; - nameLen = nameLenA; streamRefA->copy(&streamRef); } EmbeddedFile::~EmbeddedFile() { - gfree(name); + delete name; streamRef.free(); } @@ -105,6 +103,7 @@ Catalog::Catalog(PDFDoc *docA) { pageRefs = NULL; numPages = 0; baseURI = NULL; + form = NULL; embeddedFiles = NULL; xref->getCatalog(&catDict); @@ -165,6 +164,10 @@ Catalog::Catalog(PDFDoc *docA) { // get the AcroForm dictionary catDict.dictLookup("AcroForm", &acroForm); + if (!acroForm.isNull()) { + form = Form::load(doc, this, &acroForm); + } + // get the OCProperties dictionary catDict.dictLookup("OCProperties", &ocProperties); @@ -205,6 +208,9 @@ Catalog::~Catalog() { structTreeRoot.free(); outline.free(); acroForm.free(); + if (form) { + delete form; + } ocProperties.free(); if (embeddedFiles) { deleteGList(embeddedFiles, EmbeddedFile); @@ -236,7 +242,8 @@ GString *Catalog::readMetadata() { GString *s; Dict *dict; Object obj; - int c; + char buf[4096]; + int n; if (!metadata.isStream()) { return NULL; @@ -249,8 +256,8 @@ GString *Catalog::readMetadata() { obj.free(); s = new GString(); metadata.streamReset(); - while ((c = metadata.streamGetChar()) != EOF) { - s->append(c); + while ((n = metadata.streamGetBlock(buf, sizeof(buf))) > 0) { + s->append(buf, n); } metadata.streamClose(); return s; @@ -615,6 +622,12 @@ void Catalog::readFileAttachmentAnnots(O Object pageNode, kids, kid, annots, annot, subtype, fileSpec, contents; int i; + // check for an invalid object reference (e.g., in a damaged PDF file) + if (pageNodeRef->getRefNum() < 0 || + pageNodeRef->getRefNum() >= xref->getNumObjects()) { + return; + } + // check for a page tree loop if (pageNodeRef->isRef()) { if (touchedObjs[pageNodeRef->getRefNum()]) { @@ -661,42 +674,22 @@ void Catalog::readFileAttachmentAnnots(O void Catalog::readEmbeddedFile(Object *fileSpec, Object *name1) { Object name2, efObj, streamObj; GString *s; - Unicode *name; - int nameLen, i; + TextString *name; if (fileSpec->isDict()) { if (fileSpec->dictLookup("UF", &name2)->isString()) { - s = name2.getString(); + name = new TextString(name2.getString()); } else { name2.free(); if (fileSpec->dictLookup("F", &name2)->isString()) { - s = name2.getString(); + name = new TextString(name2.getString()); } else if (name1 && name1->isString()) { - s = name1->getString(); - } else { - s = NULL; - } - } - if (s) { - if ((s->getChar(0) & 0xff) == 0xfe && - (s->getChar(1) & 0xff) == 0xff) { - nameLen = (s->getLength() - 2) / 2; - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - for (i = 0; i < nameLen; ++i) { - name[i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | - (s->getChar(3 + 2*i) & 0xff); - } + name = new TextString(name1->getString()); } else { - nameLen = s->getLength(); - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - for (i = 0; i < nameLen; ++i) { - name[i] = pdfDocEncoding[s->getChar(i) & 0xff]; - } + s = new GString("?"); + name = new TextString(s); + delete s; } - } else { - nameLen = 1; - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - name[0] = '?'; } name2.free(); if (fileSpec->dictLookup("EF", &efObj)->isDict()) { @@ -704,13 +697,13 @@ void Catalog::readEmbeddedFile(Object *f if (!embeddedFiles) { embeddedFiles = new GList(); } - embeddedFiles->append(new EmbeddedFile(name, nameLen, &streamObj)); + embeddedFiles->append(new EmbeddedFile(name, &streamObj)); } else { - gfree(name); + delete name; } streamObj.free(); } else { - gfree(name); + delete name; } efObj.free(); } @@ -721,11 +714,15 @@ int Catalog::getNumEmbeddedFiles() { } Unicode *Catalog::getEmbeddedFileName(int idx) { - return ((EmbeddedFile *)embeddedFiles->get(idx))->name; + return ((EmbeddedFile *)embeddedFiles->get(idx))->name->getUnicode(); } int Catalog::getEmbeddedFileNameLength(int idx) { - return ((EmbeddedFile *)embeddedFiles->get(idx))->nameLen; + return ((EmbeddedFile *)embeddedFiles->get(idx))->name->getLength(); +} + +Object *Catalog::getEmbeddedFileStreamRef(int idx) { + return &((EmbeddedFile *)embeddedFiles->get(idx))->streamRef; } Object *Catalog::getEmbeddedFileStreamObj(int idx, Object *strObj) { diff -uNrp xpdf-3.03/xpdf/Catalog.h xpdf-3.04/xpdf/Catalog.h --- xpdf-3.03/xpdf/Catalog.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Catalog.h 2014-05-28 20:50:50.000000000 +0200 @@ -26,6 +26,7 @@ class PageAttrs; struct Ref; class LinkDest; class PageTreeNode; +class Form; //------------------------------------------------------------------------ // Catalog @@ -82,12 +83,15 @@ public: Object *getAcroForm() { return &acroForm; } + Form *getForm() { return form; } + Object *getOCProperties() { return &ocProperties; } // Get the list of embedded files. int getNumEmbeddedFiles(); Unicode *getEmbeddedFileName(int idx); int getEmbeddedFileNameLength(int idx); + Object *getEmbeddedFileStreamRef(int idx); Object *getEmbeddedFileStreamObj(int idx, Object *strObj); private: @@ -106,6 +110,7 @@ private: Object structTreeRoot; // structure tree root dictionary Object outline; // outline dictionary Object acroForm; // AcroForm dictionary + Form *form; // parsed form Object ocProperties; // OCProperties dictionary GList *embeddedFiles; // embedded file list [EmbeddedFile] GBool ok; // true if catalog is valid diff -uNrp xpdf-3.03/xpdf/CharCodeToUnicode.cc xpdf-3.04/xpdf/CharCodeToUnicode.cc --- xpdf-3.03/xpdf/CharCodeToUnicode.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/CharCodeToUnicode.cc 2014-05-28 20:50:50.000000000 +0200 @@ -167,7 +167,7 @@ CharCodeToUnicode *CharCodeToUnicode::pa while (getLine(buf, sizeof(buf), f)) { ++line; if (!(tok = strtok(buf, " \t\r\n")) || - !parseHex(tok, strlen(tok), &u0)) { + !parseHex(tok, (int)strlen(tok), &u0)) { error(errSyntaxWarning, -1, "Bad line ({0:d}) in unicodeToUnicode file '{1:t}'", line, fileName); @@ -178,7 +178,7 @@ CharCodeToUnicode *CharCodeToUnicode::pa if (!(tok = strtok(NULL, " \t\r\n"))) { break; } - if (!parseHex(tok, strlen(tok), &uBuf[n])) { + if (!parseHex(tok, (int)strlen(tok), &uBuf[n])) { error(errSyntaxWarning, -1, "Bad line ({0:d}) in unicodeToUnicode file '{1:t}'", line, fileName); @@ -336,23 +336,21 @@ void CharCodeToUnicode::parseCMap1(int ( if (code1 > maxCode || code2 > maxCode) { error(errSyntaxWarning, -1, "Invalid entry in bfrange block in ToUnicode CMap"); - if (code1 > maxCode) { - code1 = maxCode; - } if (code2 > maxCode) { code2 = maxCode; } } if (!strcmp(tok3, "[")) { i = 0; - while (pst->getToken(tok1, sizeof(tok1), &n1) && - code1 + i <= code2) { + while (pst->getToken(tok1, sizeof(tok1), &n1)) { if (!strcmp(tok1, "]")) { break; } if (tok1[0] == '<' && tok1[n1 - 1] == '>') { - tok1[n1 - 1] = '\0'; - addMapping(code1 + i, tok1 + 1, n1 - 2, 0); + if (code1 + i <= code2) { + tok1[n1 - 1] = '\0'; + addMapping(code1 + i, tok1 + 1, n1 - 2, 0); + } } else { error(errSyntaxWarning, -1, "Illegal entry in bfrange block in ToUnicode CMap"); diff -uNrp xpdf-3.03/xpdf/CharCodeToUnicode.h xpdf-3.04/xpdf/CharCodeToUnicode.h --- xpdf-3.03/xpdf/CharCodeToUnicode.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/CharCodeToUnicode.h 2014-05-28 20:50:50.000000000 +0200 @@ -74,6 +74,8 @@ public: // code supported by the mapping. CharCode getLength() { return mapLen; } + GBool isIdentity() { return !map; } + private: void parseCMap1(int (*getCharFunc)(void *), void *data, int nBits); diff -uNrp xpdf-3.03/xpdf/CMap.cc xpdf-3.04/xpdf/CMap.cc --- xpdf-3.03/xpdf/CMap.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/CMap.cc 2014-05-28 20:50:50.000000000 +0200 @@ -283,34 +283,36 @@ void CMap::copyVector(CMapVectorEntry *d void CMap::addCIDs(Guint start, Guint end, Guint nBytes, CID firstCID) { CMapVectorEntry *vec; - CID cid; - int byte; - Guint i, j; + int byte, byte0, byte1; + Guint start1, end1, i, j, k; - vec = vector; - for (i = nBytes - 1; i >= 1; --i) { - byte = (start >> (8 * i)) & 0xff; - if (!vec[byte].isVector) { - vec[byte].isVector = gTrue; - vec[byte].vector = - (CMapVectorEntry *)gmallocn(256, sizeof(CMapVectorEntry)); - for (j = 0; j < 256; ++j) { - vec[byte].vector[j].isVector = gFalse; - vec[byte].vector[j].cid = 0; + start1 = start & 0xffffff00; + end1 = end & 0xffffff00; + for (i = start1; i <= end1; i += 0x100) { + vec = vector; + for (j = nBytes - 1; j >= 1; --j) { + byte = (i >> (8 * j)) & 0xff; + if (!vec[byte].isVector) { + vec[byte].isVector = gTrue; + vec[byte].vector = + (CMapVectorEntry *)gmallocn(256, sizeof(CMapVectorEntry)); + for (k = 0; k < 256; ++k) { + vec[byte].vector[k].isVector = gFalse; + vec[byte].vector[k].cid = 0; + } } + vec = vec[byte].vector; } - vec = vec[byte].vector; - } - cid = firstCID; - for (byte = (int)(start & 0xff); byte <= (int)(end & 0xff); ++byte) { - if (vec[byte].isVector) { - error(errSyntaxError, -1, - "Invalid CID ({0:x} - {1:x} [{2:d} bytes]) in CMap", - start, end, nBytes); - } else { - vec[byte].cid = cid; + byte0 = (i < start) ? (start & 0xff) : 0; + byte1 = (i + 0xff > end) ? (end & 0xff) : 0xff; + for (byte = byte0; byte <= byte1; ++byte) { + if (vec[byte].isVector) { + error(errSyntaxError, -1, "Invalid CID ({0:x} [{1:d} bytes]) in CMap", + i, nBytes); + } else { + vec[byte].cid = firstCID + ((i + byte) - start); + } } - ++cid; } } diff -uNrp xpdf-3.03/xpdf/config.h xpdf-3.04/xpdf/config.h --- xpdf-3.03/xpdf/config.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/config.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // config.h // -// Copyright 1996-2011 Glyph & Cog, LLC +// Copyright 1996-2014 Glyph & Cog, LLC // //======================================================================== @@ -14,13 +14,13 @@ //------------------------------------------------------------------------ // xpdf version -#define xpdfVersion "3.03" -#define xpdfVersionNum 3.03 +#define xpdfVersion "3.04" +#define xpdfVersionNum 3.04 #define xpdfMajorVersion 3 -#define xpdfMinorVersion 3 +#define xpdfMinorVersion 4 #define xpdfUpdateVersion 0 #define xpdfMajorVersionStr "3" -#define xpdfMinorVersionStr "3" +#define xpdfMinorVersionStr "4" #define xpdfUpdateVersionStr "0" // supported PDF version @@ -28,11 +28,11 @@ #define supportedPDFVersionNum 1.7 // copyright notice -#define xpdfCopyright "Copyright 1996-2011 Glyph & Cog, LLC" +#define xpdfCopyright "Copyright 1996-2014 Glyph & Cog, LLC" // Windows resource file stuff -#define winxpdfVersion "WinXpdf 3.03" -#define xpdfCopyrightAmp "Copyright 1996-2011 Glyph && Cog, LLC" +#define winxpdfVersion "WinXpdf 3.04" +#define xpdfCopyrightAmp "Copyright 1996-2014 Glyph && Cog, LLC" //------------------------------------------------------------------------ // paper size @@ -52,7 +52,7 @@ //------------------------------------------------------------------------ // user config file name, relative to the user's home directory -#if defined(VMS) || defined(WIN32) +#if defined(VMS) || defined(_WIN32) #define xpdfUserConfigFile "xpdfrc" #else #define xpdfUserConfigFile ".xpdfrc" @@ -83,7 +83,7 @@ #define pclose _pclose #endif -#if defined(VMS) || defined(VMCMS) || defined(DOS) || defined(OS2) || defined(__EMX__) || defined(WIN32) || defined(__DJGPP__) || defined(MACOS) +#if defined(VMS) || defined(VMCMS) || defined(DOS) || defined(OS2) || defined(__EMX__) || defined(_WIN32) || defined(__DJGPP__) || defined(MACOS) #define POPEN_READ_MODE "rb" #else #define POPEN_READ_MODE "r" diff -uNrp xpdf-3.03/xpdf/Decrypt.cc xpdf-3.04/xpdf/Decrypt.cc --- xpdf-3.03/xpdf/Decrypt.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Decrypt.cc 2014-05-28 20:50:50.000000000 +0200 @@ -16,13 +16,12 @@ #include "gmem.h" #include "Decrypt.h" -static void aesKeyExpansion(DecryptAESState *s, - Guchar *objKey, int objKeyLen); -static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last); static void aes256KeyExpansion(DecryptAES256State *s, Guchar *objKey, int objKeyLen); static void aes256DecryptBlock(DecryptAES256State *s, Guchar *in, GBool last); static void sha256(Guchar *msg, int msgLen, Guchar *hash); +static void sha384(Guchar *msg, int msgLen, Guchar *hash); +static void sha512(Guchar *msg, int msgLen, Guchar *hash); static Guchar passwordPad[32] = { 0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, @@ -45,6 +44,7 @@ GBool Decrypt::makeFileKey(int encVersio DecryptAES256State state; Guchar test[127 + 56], test2[32]; GString *userPassword2; + const char *userPW; Guchar fState[256]; Guchar tmpKey[16]; Guchar fx, fy; @@ -52,7 +52,7 @@ GBool Decrypt::makeFileKey(int encVersio *ownerPasswordOk = gFalse; - if (encRevision == 5) { + if (encRevision == 5 || encRevision == 6) { // check the owner password if (ownerPassword) { @@ -65,6 +65,10 @@ GBool Decrypt::makeFileKey(int encVersio memcpy(test + len, ownerKey->getCString() + 32, 8); memcpy(test + len + 8, userKey->getCString(), 48); sha256(test, len + 56, test); + if (encRevision == 6) { + r6Hash(test, 32, ownerPassword->getCString(), len, + userKey->getCString()); + } if (!memcmp(test, ownerKey->getCString(), 32)) { // compute the file key from the owner password @@ -72,6 +76,10 @@ GBool Decrypt::makeFileKey(int encVersio memcpy(test + len, ownerKey->getCString() + 40, 8); memcpy(test + len + 8, userKey->getCString(), 48); sha256(test, len + 56, test); + if (encRevision == 6) { + r6Hash(test, 32, ownerPassword->getCString(), len, + userKey->getCString()); + } aes256KeyExpansion(&state, test, 32); for (i = 0; i < 16; ++i) { state.cbc[i] = 0; @@ -90,34 +98,45 @@ GBool Decrypt::makeFileKey(int encVersio // check the user password if (userPassword) { //~ this is supposed to convert the password to UTF-8 using "SASLprep" + userPW = userPassword->getCString(); len = userPassword->getLength(); if (len > 127) { len = 127; } - memcpy(test, userPassword->getCString(), len); - memcpy(test + len, userKey->getCString() + 32, 8); + } else { + userPW = ""; + len = 0; + } + memcpy(test, userPW, len); + memcpy(test + len, userKey->getCString() + 32, 8); + sha256(test, len + 8, test); + if (encRevision == 6) { + r6Hash(test, 32, userPW, len, NULL); + } + if (!memcmp(test, userKey->getCString(), 32)) { + + // compute the file key from the user password + memcpy(test, userPW, len); + memcpy(test + len, userKey->getCString() + 40, 8); sha256(test, len + 8, test); - if (!memcmp(test, userKey->getCString(), 32)) { - - // compute the file key from the user password - memcpy(test, userPassword->getCString(), len); - memcpy(test + len, userKey->getCString() + 40, 8); - sha256(test, len + 8, test); - aes256KeyExpansion(&state, test, 32); - for (i = 0; i < 16; ++i) { - state.cbc[i] = 0; - } - aes256DecryptBlock(&state, (Guchar *)userEnc->getCString(), gFalse); - memcpy(fileKey, state.buf, 16); - aes256DecryptBlock(&state, (Guchar *)userEnc->getCString() + 16, - gFalse); - memcpy(fileKey + 16, state.buf, 16); - - return gTrue; + if (encRevision == 6) { + r6Hash(test, 32, userPW, len, NULL); + } + aes256KeyExpansion(&state, test, 32); + for (i = 0; i < 16; ++i) { + state.cbc[i] = 0; } + aes256DecryptBlock(&state, (Guchar *)userEnc->getCString(), gFalse); + memcpy(fileKey, state.buf, 16); + aes256DecryptBlock(&state, (Guchar *)userEnc->getCString() + 16, + gFalse); + memcpy(fileKey + 16, state.buf, 16); + + return gTrue; } return gFalse; + } else { // try using the supplied owner password to generate the user password @@ -172,6 +191,61 @@ GBool Decrypt::makeFileKey(int encVersio } } +void Decrypt::r6Hash(Guchar *key, int keyLen, const char *pwd, int pwdLen, + char *userKey) { + Guchar key1[64*(127+64+48)]; + DecryptAESState state128; + int n, i, j, k; + + i = 0; + while (1) { + memcpy(key1, pwd, pwdLen); + memcpy(key1 + pwdLen, key, keyLen); + n = pwdLen + keyLen; + if (userKey) { + memcpy(key1 + pwdLen + keyLen, userKey, 48); + n += 48; + } + for (j = 1; j < 64; ++j) { + memcpy(key1 + j * n, key1, n); + } + n *= 64; + aesKeyExpansion(&state128, key, 16, gFalse); + for (j = 0; j < 16; ++j) { + state128.cbc[j] = key[16+j]; + } + for (j = 0; j < n; j += 16) { + aesEncryptBlock(&state128, key1 + j); + memcpy(key1 + j, state128.buf, 16); + } + k = 0; + for (j = 0; j < 16; ++j) { + k += key1[j] % 3; + } + k %= 3; + switch (k) { + case 0: + sha256(key1, n, key); + keyLen = 32; + break; + case 1: + sha384(key1, n, key); + keyLen = 48; + break; + case 2: + sha512(key1, n, key); + keyLen = 64; + break; + } + // from the spec, it appears that i should be incremented after + // the test, but that doesn't match what Adobe does + ++i; + if (i >= 64 && key1[n - 1] <= i - 32) { + break; + } + } +} + GBool Decrypt::makeFileKey2(int encVersion, int encRevision, int keyLength, GString *ownerKey, GString *userKey, int permissions, GString *fileID, @@ -305,8 +379,6 @@ DecryptStream::~DecryptStream() { } void DecryptStream::reset() { - int i; - str->reset(); switch (algo) { case cryptRC4: @@ -315,17 +387,13 @@ void DecryptStream::reset() { state.rc4.buf = EOF; break; case cryptAES: - aesKeyExpansion(&state.aes, objKey, objKeyLength); - for (i = 0; i < 16; ++i) { - state.aes.cbc[i] = str->getChar(); - } + aesKeyExpansion(&state.aes, objKey, objKeyLength, gTrue); + str->getBlock((char *)state.aes.cbc, 16); state.aes.bufIdx = 16; break; case cryptAES256: aes256KeyExpansion(&state.aes256, objKey, objKeyLength); - for (i = 0; i < 16; ++i) { - state.aes256.cbc[i] = str->getChar(); - } + str->getBlock((char *)state.aes256.cbc, 16); state.aes256.bufIdx = 16; break; } @@ -333,7 +401,7 @@ void DecryptStream::reset() { int DecryptStream::getChar() { Guchar in[16]; - int c, i; + int c; c = EOF; // make gcc happy switch (algo) { @@ -350,11 +418,8 @@ int DecryptStream::getChar() { break; case cryptAES: if (state.aes.bufIdx == 16) { - for (i = 0; i < 16; ++i) { - if ((c = str->getChar()) == EOF) { - return EOF; - } - in[i] = (Guchar)c; + if (str->getBlock((char *)in, 16) != 16) { + return EOF; } aesDecryptBlock(&state.aes, in, str->lookChar() == EOF); } @@ -366,11 +431,8 @@ int DecryptStream::getChar() { break; case cryptAES256: if (state.aes256.bufIdx == 16) { - for (i = 0; i < 16; ++i) { - if ((c = str->getChar()) == EOF) { - return EOF; - } - in[i] = (Guchar)c; + if (str->getBlock((char *)in, 16) != 16) { + return EOF; } aes256DecryptBlock(&state.aes256, in, str->lookChar() == EOF); } @@ -386,7 +448,7 @@ int DecryptStream::getChar() { int DecryptStream::lookChar() { Guchar in[16]; - int c, i; + int c; c = EOF; // make gcc happy switch (algo) { @@ -402,11 +464,8 @@ int DecryptStream::lookChar() { break; case cryptAES: if (state.aes.bufIdx == 16) { - for (i = 0; i < 16; ++i) { - if ((c = str->getChar()) == EOF) { - return EOF; - } - in[i] = c; + if (str->getBlock((char *)in, 16) != 16) { + return EOF; } aesDecryptBlock(&state.aes, in, str->lookChar() == EOF); } @@ -418,11 +477,8 @@ int DecryptStream::lookChar() { break; case cryptAES256: if (state.aes256.bufIdx == 16) { - for (i = 0; i < 16; ++i) { - if ((c = str->getChar()) == EOF) { - return EOF; - } - in[i] = c; + if (str->getBlock((char *)in, 16) != 16) { + return EOF; } aes256DecryptBlock(&state.aes256, in, str->lookChar() == EOF); } @@ -540,6 +596,14 @@ static inline Guint rotWord(Guint x) { return ((x << 8) & 0xffffffff) | (x >> 24); } +static inline void subBytes(Guchar *state) { + int i; + + for (i = 0; i < 16; ++i) { + state[i] = sbox[state[i]]; + } +} + static inline void invSubBytes(Guchar *state) { int i; @@ -548,6 +612,29 @@ static inline void invSubBytes(Guchar *s } } +static inline void shiftRows(Guchar *state) { + Guchar t; + + t = state[4]; + state[4] = state[5]; + state[5] = state[6]; + state[6] = state[7]; + state[7] = t; + + t = state[8]; + state[8] = state[10]; + state[10] = t; + t = state[9]; + state[9] = state[11]; + state[11] = t; + + t = state[15]; + state[15] = state[14]; + state[14] = state[13]; + state[13] = state[12]; + state[12] = t; +} + static inline void invShiftRows(Guchar *state) { Guchar t; @@ -571,6 +658,22 @@ static inline void invShiftRows(Guchar * state[15] = t; } +// {02} \cdot s +static inline Guchar mul02(Guchar s) { + Guchar s2; + + s2 = (s & 0x80) ? ((s << 1) ^ 0x1b) : (s << 1); + return s2; +} + +// {03} \cdot s +static inline Guchar mul03(Guchar s) { + Guchar s2; + + s2 = (s & 0x80) ? ((s << 1) ^ 0x1b) : (s << 1); + return s ^ s2; +} + // {09} \cdot s static inline Guchar mul09(Guchar s) { Guchar s2, s4, s8; @@ -611,6 +714,22 @@ static inline Guchar mul0e(Guchar s) { return s2 ^ s4 ^ s8; } +static inline void mixColumns(Guchar *state) { + int c; + Guchar s0, s1, s2, s3; + + for (c = 0; c < 4; ++c) { + s0 = state[c]; + s1 = state[4+c]; + s2 = state[8+c]; + s3 = state[12+c]; + state[c] = mul02(s0) ^ mul03(s1) ^ s2 ^ s3; + state[4+c] = s0 ^ mul02(s1) ^ mul03(s2) ^ s3; + state[8+c] = s0 ^ s1 ^ mul02(s2) ^ mul03(s3); + state[12+c] = mul03(s0) ^ s1 ^ s2 ^ mul02(s3); + } +} + static inline void invMixColumns(Guchar *state) { int c; Guchar s0, s1, s2, s3; @@ -654,8 +773,9 @@ static inline void addRoundKey(Guchar *s } } -static void aesKeyExpansion(DecryptAESState *s, - Guchar *objKey, int objKeyLen) { +void aesKeyExpansion(DecryptAESState *s, + Guchar *objKey, int objKeyLen, + GBool decrypt) { Guint temp; int i, round; @@ -672,12 +792,50 @@ static void aesKeyExpansion(DecryptAESSt } s->w[i] = s->w[i-4] ^ temp; } + if (decrypt) { + for (round = 1; round <= 9; ++round) { + invMixColumnsW(&s->w[round * 4]); + } + } +} + +void aesEncryptBlock(DecryptAESState *s, Guchar *in) { + int c, round; + + // initial state + CBC + for (c = 0; c < 4; ++c) { + s->state[c] = in[4*c] ^ s->cbc[4*c]; + s->state[4+c] = in[4*c+1] ^ s->cbc[4*c+1]; + s->state[8+c] = in[4*c+2] ^ s->cbc[4*c+2]; + s->state[12+c] = in[4*c+3] ^ s->cbc[4*c+3]; + } + + // round 0 + addRoundKey(s->state, &s->w[0]); + + // rounds 1 .. 9 for (round = 1; round <= 9; ++round) { - invMixColumnsW(&s->w[round * 4]); + subBytes(s->state); + shiftRows(s->state); + mixColumns(s->state); + addRoundKey(s->state, &s->w[round * 4]); + } + + // round 10 + subBytes(s->state); + shiftRows(s->state); + addRoundKey(s->state, &s->w[10 * 4]); + + // output + save for next CBC + for (c = 0; c < 4; ++c) { + s->buf[4*c] = s->cbc[4*c] = s->state[c]; + s->buf[4*c+1] = s->cbc[4*c+1] = s->state[4+c]; + s->buf[4*c+2] = s->cbc[4*c+2] = s->state[8+c]; + s->buf[4*c+3] = s->cbc[4*c+3] = s->state[12+c]; } } -static void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last) { +void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last) { int c, round, n, i; // initial state @@ -844,151 +1002,187 @@ static inline Gulong md5Round4(Gulong a, return b + rotateLeft((a + (c ^ (b | ~d)) + Xk + Ti), s); } -void md5(Guchar *msg, int msgLen, Guchar *digest) { +void md5Start(MD5State *state) { + state->a = 0x67452301; + state->b = 0xefcdab89; + state->c = 0x98badcfe; + state->d = 0x10325476; + state->bufLen = 0; + state->msgLen = 0; +} + +static void md5ProcessBlock(MD5State *state) { Gulong x[16]; - Gulong a, b, c, d, aa, bb, cc, dd; - int n64; - int i, j, k; + Gulong a, b, c, d; + int i; - // sanity check - if (msgLen < 0) { - return; + for (i = 0; i < 16; ++i) { + x[i] = state->buf[4*i] | (state->buf[4*i+1] << 8) | + (state->buf[4*i+2] << 16) | (state->buf[4*i+3] << 24); } - // compute number of 64-byte blocks - // (length + pad byte (0x80) + 8 bytes for length) - n64 = (msgLen + 1 + 8 + 63) / 64; - - // initialize a, b, c, d - a = 0x67452301; - b = 0xefcdab89; - c = 0x98badcfe; - d = 0x10325476; - - // loop through blocks - k = 0; - for (i = 0; i < n64; ++i) { - - // grab a 64-byte block - for (j = 0; j < 16 && k < msgLen - 3; ++j, k += 4) - x[j] = (((((msg[k+3] << 8) + msg[k+2]) << 8) + msg[k+1]) << 8) + msg[k]; - if (i == n64 - 1) { - if (k == msgLen - 3) - x[j] = 0x80000000 + (((msg[k+2] << 8) + msg[k+1]) << 8) + msg[k]; - else if (k == msgLen - 2) - x[j] = 0x800000 + (msg[k+1] << 8) + msg[k]; - else if (k == msgLen - 1) - x[j] = 0x8000 + msg[k]; - else - x[j] = 0x80; - ++j; - while (j < 16) - x[j++] = 0; - x[14] = msgLen << 3; - } - - // save a, b, c, d - aa = a; - bb = b; - cc = c; - dd = d; - - // round 1 - a = md5Round1(a, b, c, d, x[0], 7, 0xd76aa478); - d = md5Round1(d, a, b, c, x[1], 12, 0xe8c7b756); - c = md5Round1(c, d, a, b, x[2], 17, 0x242070db); - b = md5Round1(b, c, d, a, x[3], 22, 0xc1bdceee); - a = md5Round1(a, b, c, d, x[4], 7, 0xf57c0faf); - d = md5Round1(d, a, b, c, x[5], 12, 0x4787c62a); - c = md5Round1(c, d, a, b, x[6], 17, 0xa8304613); - b = md5Round1(b, c, d, a, x[7], 22, 0xfd469501); - a = md5Round1(a, b, c, d, x[8], 7, 0x698098d8); - d = md5Round1(d, a, b, c, x[9], 12, 0x8b44f7af); - c = md5Round1(c, d, a, b, x[10], 17, 0xffff5bb1); - b = md5Round1(b, c, d, a, x[11], 22, 0x895cd7be); - a = md5Round1(a, b, c, d, x[12], 7, 0x6b901122); - d = md5Round1(d, a, b, c, x[13], 12, 0xfd987193); - c = md5Round1(c, d, a, b, x[14], 17, 0xa679438e); - b = md5Round1(b, c, d, a, x[15], 22, 0x49b40821); - - // round 2 - a = md5Round2(a, b, c, d, x[1], 5, 0xf61e2562); - d = md5Round2(d, a, b, c, x[6], 9, 0xc040b340); - c = md5Round2(c, d, a, b, x[11], 14, 0x265e5a51); - b = md5Round2(b, c, d, a, x[0], 20, 0xe9b6c7aa); - a = md5Round2(a, b, c, d, x[5], 5, 0xd62f105d); - d = md5Round2(d, a, b, c, x[10], 9, 0x02441453); - c = md5Round2(c, d, a, b, x[15], 14, 0xd8a1e681); - b = md5Round2(b, c, d, a, x[4], 20, 0xe7d3fbc8); - a = md5Round2(a, b, c, d, x[9], 5, 0x21e1cde6); - d = md5Round2(d, a, b, c, x[14], 9, 0xc33707d6); - c = md5Round2(c, d, a, b, x[3], 14, 0xf4d50d87); - b = md5Round2(b, c, d, a, x[8], 20, 0x455a14ed); - a = md5Round2(a, b, c, d, x[13], 5, 0xa9e3e905); - d = md5Round2(d, a, b, c, x[2], 9, 0xfcefa3f8); - c = md5Round2(c, d, a, b, x[7], 14, 0x676f02d9); - b = md5Round2(b, c, d, a, x[12], 20, 0x8d2a4c8a); - - // round 3 - a = md5Round3(a, b, c, d, x[5], 4, 0xfffa3942); - d = md5Round3(d, a, b, c, x[8], 11, 0x8771f681); - c = md5Round3(c, d, a, b, x[11], 16, 0x6d9d6122); - b = md5Round3(b, c, d, a, x[14], 23, 0xfde5380c); - a = md5Round3(a, b, c, d, x[1], 4, 0xa4beea44); - d = md5Round3(d, a, b, c, x[4], 11, 0x4bdecfa9); - c = md5Round3(c, d, a, b, x[7], 16, 0xf6bb4b60); - b = md5Round3(b, c, d, a, x[10], 23, 0xbebfbc70); - a = md5Round3(a, b, c, d, x[13], 4, 0x289b7ec6); - d = md5Round3(d, a, b, c, x[0], 11, 0xeaa127fa); - c = md5Round3(c, d, a, b, x[3], 16, 0xd4ef3085); - b = md5Round3(b, c, d, a, x[6], 23, 0x04881d05); - a = md5Round3(a, b, c, d, x[9], 4, 0xd9d4d039); - d = md5Round3(d, a, b, c, x[12], 11, 0xe6db99e5); - c = md5Round3(c, d, a, b, x[15], 16, 0x1fa27cf8); - b = md5Round3(b, c, d, a, x[2], 23, 0xc4ac5665); - - // round 4 - a = md5Round4(a, b, c, d, x[0], 6, 0xf4292244); - d = md5Round4(d, a, b, c, x[7], 10, 0x432aff97); - c = md5Round4(c, d, a, b, x[14], 15, 0xab9423a7); - b = md5Round4(b, c, d, a, x[5], 21, 0xfc93a039); - a = md5Round4(a, b, c, d, x[12], 6, 0x655b59c3); - d = md5Round4(d, a, b, c, x[3], 10, 0x8f0ccc92); - c = md5Round4(c, d, a, b, x[10], 15, 0xffeff47d); - b = md5Round4(b, c, d, a, x[1], 21, 0x85845dd1); - a = md5Round4(a, b, c, d, x[8], 6, 0x6fa87e4f); - d = md5Round4(d, a, b, c, x[15], 10, 0xfe2ce6e0); - c = md5Round4(c, d, a, b, x[6], 15, 0xa3014314); - b = md5Round4(b, c, d, a, x[13], 21, 0x4e0811a1); - a = md5Round4(a, b, c, d, x[4], 6, 0xf7537e82); - d = md5Round4(d, a, b, c, x[11], 10, 0xbd3af235); - c = md5Round4(c, d, a, b, x[2], 15, 0x2ad7d2bb); - b = md5Round4(b, c, d, a, x[9], 21, 0xeb86d391); - - // increment a, b, c, d - a += aa; - b += bb; - c += cc; - d += dd; - } + a = state->a; + b = state->b; + c = state->c; + d = state->d; + + // round 1 + a = md5Round1(a, b, c, d, x[0], 7, 0xd76aa478); + d = md5Round1(d, a, b, c, x[1], 12, 0xe8c7b756); + c = md5Round1(c, d, a, b, x[2], 17, 0x242070db); + b = md5Round1(b, c, d, a, x[3], 22, 0xc1bdceee); + a = md5Round1(a, b, c, d, x[4], 7, 0xf57c0faf); + d = md5Round1(d, a, b, c, x[5], 12, 0x4787c62a); + c = md5Round1(c, d, a, b, x[6], 17, 0xa8304613); + b = md5Round1(b, c, d, a, x[7], 22, 0xfd469501); + a = md5Round1(a, b, c, d, x[8], 7, 0x698098d8); + d = md5Round1(d, a, b, c, x[9], 12, 0x8b44f7af); + c = md5Round1(c, d, a, b, x[10], 17, 0xffff5bb1); + b = md5Round1(b, c, d, a, x[11], 22, 0x895cd7be); + a = md5Round1(a, b, c, d, x[12], 7, 0x6b901122); + d = md5Round1(d, a, b, c, x[13], 12, 0xfd987193); + c = md5Round1(c, d, a, b, x[14], 17, 0xa679438e); + b = md5Round1(b, c, d, a, x[15], 22, 0x49b40821); + + // round 2 + a = md5Round2(a, b, c, d, x[1], 5, 0xf61e2562); + d = md5Round2(d, a, b, c, x[6], 9, 0xc040b340); + c = md5Round2(c, d, a, b, x[11], 14, 0x265e5a51); + b = md5Round2(b, c, d, a, x[0], 20, 0xe9b6c7aa); + a = md5Round2(a, b, c, d, x[5], 5, 0xd62f105d); + d = md5Round2(d, a, b, c, x[10], 9, 0x02441453); + c = md5Round2(c, d, a, b, x[15], 14, 0xd8a1e681); + b = md5Round2(b, c, d, a, x[4], 20, 0xe7d3fbc8); + a = md5Round2(a, b, c, d, x[9], 5, 0x21e1cde6); + d = md5Round2(d, a, b, c, x[14], 9, 0xc33707d6); + c = md5Round2(c, d, a, b, x[3], 14, 0xf4d50d87); + b = md5Round2(b, c, d, a, x[8], 20, 0x455a14ed); + a = md5Round2(a, b, c, d, x[13], 5, 0xa9e3e905); + d = md5Round2(d, a, b, c, x[2], 9, 0xfcefa3f8); + c = md5Round2(c, d, a, b, x[7], 14, 0x676f02d9); + b = md5Round2(b, c, d, a, x[12], 20, 0x8d2a4c8a); + + // round 3 + a = md5Round3(a, b, c, d, x[5], 4, 0xfffa3942); + d = md5Round3(d, a, b, c, x[8], 11, 0x8771f681); + c = md5Round3(c, d, a, b, x[11], 16, 0x6d9d6122); + b = md5Round3(b, c, d, a, x[14], 23, 0xfde5380c); + a = md5Round3(a, b, c, d, x[1], 4, 0xa4beea44); + d = md5Round3(d, a, b, c, x[4], 11, 0x4bdecfa9); + c = md5Round3(c, d, a, b, x[7], 16, 0xf6bb4b60); + b = md5Round3(b, c, d, a, x[10], 23, 0xbebfbc70); + a = md5Round3(a, b, c, d, x[13], 4, 0x289b7ec6); + d = md5Round3(d, a, b, c, x[0], 11, 0xeaa127fa); + c = md5Round3(c, d, a, b, x[3], 16, 0xd4ef3085); + b = md5Round3(b, c, d, a, x[6], 23, 0x04881d05); + a = md5Round3(a, b, c, d, x[9], 4, 0xd9d4d039); + d = md5Round3(d, a, b, c, x[12], 11, 0xe6db99e5); + c = md5Round3(c, d, a, b, x[15], 16, 0x1fa27cf8); + b = md5Round3(b, c, d, a, x[2], 23, 0xc4ac5665); + + // round 4 + a = md5Round4(a, b, c, d, x[0], 6, 0xf4292244); + d = md5Round4(d, a, b, c, x[7], 10, 0x432aff97); + c = md5Round4(c, d, a, b, x[14], 15, 0xab9423a7); + b = md5Round4(b, c, d, a, x[5], 21, 0xfc93a039); + a = md5Round4(a, b, c, d, x[12], 6, 0x655b59c3); + d = md5Round4(d, a, b, c, x[3], 10, 0x8f0ccc92); + c = md5Round4(c, d, a, b, x[10], 15, 0xffeff47d); + b = md5Round4(b, c, d, a, x[1], 21, 0x85845dd1); + a = md5Round4(a, b, c, d, x[8], 6, 0x6fa87e4f); + d = md5Round4(d, a, b, c, x[15], 10, 0xfe2ce6e0); + c = md5Round4(c, d, a, b, x[6], 15, 0xa3014314); + b = md5Round4(b, c, d, a, x[13], 21, 0x4e0811a1); + a = md5Round4(a, b, c, d, x[4], 6, 0xf7537e82); + d = md5Round4(d, a, b, c, x[11], 10, 0xbd3af235); + c = md5Round4(c, d, a, b, x[2], 15, 0x2ad7d2bb); + b = md5Round4(b, c, d, a, x[9], 21, 0xeb86d391); + + // increment a, b, c, d + state->a += a; + state->b += b; + state->c += c; + state->d += d; + + state->bufLen = 0; +} + +void md5Append(MD5State *state, Guchar *data, int dataLen) { + Guchar *p; + int remain, k; + + p = data; + remain = dataLen; + while (state->bufLen + remain >= 64) { + k = 64 - state->bufLen; + memcpy(state->buf + state->bufLen, p, k); + state->bufLen = 64; + md5ProcessBlock(state); + p += k; + remain -= k; + } + if (remain > 0) { + memcpy(state->buf + state->bufLen, p, remain); + state->bufLen += remain; + } + state->msgLen += dataLen; +} + +void md5Finish(MD5State *state) { + // padding and length + state->buf[state->bufLen++] = 0x80; + if (state->bufLen > 56) { + while (state->bufLen < 64) { + state->buf[state->bufLen++] = 0x00; + } + md5ProcessBlock(state); + } + while (state->bufLen < 56) { + state->buf[state->bufLen++] = 0x00; + } + state->buf[56] = (Guchar)(state->msgLen << 3); + state->buf[57] = (Guchar)(state->msgLen >> 5); + state->buf[58] = (Guchar)(state->msgLen >> 13); + state->buf[59] = (Guchar)(state->msgLen >> 21); + state->buf[60] = (Guchar)(state->msgLen >> 29); + state->buf[61] = (Guchar)0; + state->buf[62] = (Guchar)0; + state->buf[63] = (Guchar)0; + state->bufLen = 64; + md5ProcessBlock(state); // break digest into bytes - digest[0] = (Guchar)(a & 0xff); - digest[1] = (Guchar)((a >>= 8) & 0xff); - digest[2] = (Guchar)((a >>= 8) & 0xff); - digest[3] = (Guchar)((a >>= 8) & 0xff); - digest[4] = (Guchar)(b & 0xff); - digest[5] = (Guchar)((b >>= 8) & 0xff); - digest[6] = (Guchar)((b >>= 8) & 0xff); - digest[7] = (Guchar)((b >>= 8) & 0xff); - digest[8] = (Guchar)(c & 0xff); - digest[9] = (Guchar)((c >>= 8) & 0xff); - digest[10] = (Guchar)((c >>= 8) & 0xff); - digest[11] = (Guchar)((c >>= 8) & 0xff); - digest[12] = (Guchar)(d & 0xff); - digest[13] = (Guchar)((d >>= 8) & 0xff); - digest[14] = (Guchar)((d >>= 8) & 0xff); - digest[15] = (Guchar)((d >>= 8) & 0xff); + state->digest[0] = (Guchar)state->a; + state->digest[1] = (Guchar)(state->a >> 8); + state->digest[2] = (Guchar)(state->a >> 16); + state->digest[3] = (Guchar)(state->a >> 24); + state->digest[4] = (Guchar)state->b; + state->digest[5] = (Guchar)(state->b >> 8); + state->digest[6] = (Guchar)(state->b >> 16); + state->digest[7] = (Guchar)(state->b >> 24); + state->digest[8] = (Guchar)state->c; + state->digest[9] = (Guchar)(state->c >> 8); + state->digest[10] = (Guchar)(state->c >> 16); + state->digest[11] = (Guchar)(state->c >> 24); + state->digest[12] = (Guchar)state->d; + state->digest[13] = (Guchar)(state->d >> 8); + state->digest[14] = (Guchar)(state->d >> 16); + state->digest[15] = (Guchar)(state->d >> 24); +} + +void md5(Guchar *msg, int msgLen, Guchar *digest) { + MD5State state; + int i; + + if (msgLen < 0) { + return; + } + md5Start(&state); + md5Append(&state, msg, msgLen); + md5Finish(&state); + for (i = 0; i < 16; ++i) { + digest[i] = state.digest[i]; + } } //------------------------------------------------------------------------ @@ -1042,7 +1236,7 @@ static inline Guint sha256sigma1(Guint x return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10); } -void sha256HashBlock(Guchar *blk, Guint *H) { +static void sha256HashBlock(Guchar *blk, Guint *H) { Guint W[64]; Guint a, b, c, d, e, f, g, h; Guint T1, T2; @@ -1147,3 +1341,270 @@ static void sha256(Guchar *msg, int msgL hash[i*4 + 3] = (Guchar)H[i]; } } + +//------------------------------------------------------------------------ +// SHA-384 and SHA-512 hashes +//------------------------------------------------------------------------ + +typedef unsigned long long SHA512Uint64; + +static SHA512Uint64 sha512K[80] = { + 0x428a2f98d728ae22LL, 0x7137449123ef65cdLL, + 0xb5c0fbcfec4d3b2fLL, 0xe9b5dba58189dbbcLL, + 0x3956c25bf348b538LL, 0x59f111f1b605d019LL, + 0x923f82a4af194f9bLL, 0xab1c5ed5da6d8118LL, + 0xd807aa98a3030242LL, 0x12835b0145706fbeLL, + 0x243185be4ee4b28cLL, 0x550c7dc3d5ffb4e2LL, + 0x72be5d74f27b896fLL, 0x80deb1fe3b1696b1LL, + 0x9bdc06a725c71235LL, 0xc19bf174cf692694LL, + 0xe49b69c19ef14ad2LL, 0xefbe4786384f25e3LL, + 0x0fc19dc68b8cd5b5LL, 0x240ca1cc77ac9c65LL, + 0x2de92c6f592b0275LL, 0x4a7484aa6ea6e483LL, + 0x5cb0a9dcbd41fbd4LL, 0x76f988da831153b5LL, + 0x983e5152ee66dfabLL, 0xa831c66d2db43210LL, + 0xb00327c898fb213fLL, 0xbf597fc7beef0ee4LL, + 0xc6e00bf33da88fc2LL, 0xd5a79147930aa725LL, + 0x06ca6351e003826fLL, 0x142929670a0e6e70LL, + 0x27b70a8546d22ffcLL, 0x2e1b21385c26c926LL, + 0x4d2c6dfc5ac42aedLL, 0x53380d139d95b3dfLL, + 0x650a73548baf63deLL, 0x766a0abb3c77b2a8LL, + 0x81c2c92e47edaee6LL, 0x92722c851482353bLL, + 0xa2bfe8a14cf10364LL, 0xa81a664bbc423001LL, + 0xc24b8b70d0f89791LL, 0xc76c51a30654be30LL, + 0xd192e819d6ef5218LL, 0xd69906245565a910LL, + 0xf40e35855771202aLL, 0x106aa07032bbd1b8LL, + 0x19a4c116b8d2d0c8LL, 0x1e376c085141ab53LL, + 0x2748774cdf8eeb99LL, 0x34b0bcb5e19b48a8LL, + 0x391c0cb3c5c95a63LL, 0x4ed8aa4ae3418acbLL, + 0x5b9cca4f7763e373LL, 0x682e6ff3d6b2b8a3LL, + 0x748f82ee5defb2fcLL, 0x78a5636f43172f60LL, + 0x84c87814a1f0ab72LL, 0x8cc702081a6439ecLL, + 0x90befffa23631e28LL, 0xa4506cebde82bde9LL, + 0xbef9a3f7b2c67915LL, 0xc67178f2e372532bLL, + 0xca273eceea26619cLL, 0xd186b8c721c0c207LL, + 0xeada7dd6cde0eb1eLL, 0xf57d4f7fee6ed178LL, + 0x06f067aa72176fbaLL, 0x0a637dc5a2c898a6LL, + 0x113f9804bef90daeLL, 0x1b710b35131c471bLL, + 0x28db77f523047d84LL, 0x32caab7b40c72493LL, + 0x3c9ebe0a15c9bebcLL, 0x431d67c49c100d4cLL, + 0x4cc5d4becb3e42b6LL, 0x597f299cfc657e2aLL, + 0x5fcb6fab3ad6faecLL, 0x6c44198c4a475817LL +}; + +static inline SHA512Uint64 rotr64(SHA512Uint64 x, Guint n) { + return (x >> n) | (x << (64 - n)); +} + +static inline SHA512Uint64 sha512Ch(SHA512Uint64 x, SHA512Uint64 y, + SHA512Uint64 z) { + return (x & y) ^ (~x & z); +} + +static inline SHA512Uint64 sha512Maj(SHA512Uint64 x, SHA512Uint64 y, + SHA512Uint64 z) { + return (x & y) ^ (x & z) ^ (y & z); +} + +static inline SHA512Uint64 sha512Sigma0(SHA512Uint64 x) { + return rotr64(x, 28) ^ rotr64(x, 34) ^ rotr64(x, 39); +} + +static inline SHA512Uint64 sha512Sigma1(SHA512Uint64 x) { + return rotr64(x, 14) ^ rotr64(x, 18) ^ rotr64(x, 41); +} + +static inline SHA512Uint64 sha512sigma0(SHA512Uint64 x) { + return rotr64(x, 1) ^ rotr64(x, 8) ^ (x >> 7); +} + +static inline SHA512Uint64 sha512sigma1(SHA512Uint64 x) { + return rotr64(x, 19) ^ rotr64(x, 61) ^ (x >> 6); +} + +static void sha512HashBlock(Guchar *blk, SHA512Uint64 *H) { + SHA512Uint64 W[80]; + SHA512Uint64 a, b, c, d, e, f, g, h; + SHA512Uint64 T1, T2; + Guint t; + + // 1. prepare the message schedule + for (t = 0; t < 16; ++t) { + W[t] = ((SHA512Uint64)blk[t*8] << 56) | + ((SHA512Uint64)blk[t*8 + 1] << 48) | + ((SHA512Uint64)blk[t*8 + 2] << 40) | + ((SHA512Uint64)blk[t*8 + 3] << 32) | + ((SHA512Uint64)blk[t*8 + 4] << 24) | + ((SHA512Uint64)blk[t*8 + 5] << 16) | + ((SHA512Uint64)blk[t*8 + 6] << 8) | + (SHA512Uint64)blk[t*8 + 7]; + } + for (t = 16; t < 80; ++t) { + W[t] = sha512sigma1(W[t-2]) + W[t-7] + sha512sigma0(W[t-15]) + W[t-16]; + } + + // 2. initialize the eight working variables + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + f = H[5]; + g = H[6]; + h = H[7]; + + // 3. + for (t = 0; t < 80; ++t) { + T1 = h + sha512Sigma1(e) + sha512Ch(e,f,g) + sha512K[t] + W[t]; + T2 = sha512Sigma0(a) + sha512Maj(a,b,c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + } + + // 4. compute the intermediate hash value + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + H[5] += f; + H[6] += g; + H[7] += h; +} + +static void sha512(Guchar *msg, int msgLen, Guchar *hash) { + Guchar blk[128]; + SHA512Uint64 H[8]; + int blkLen, i; + + H[0] = 0x6a09e667f3bcc908LL; + H[1] = 0xbb67ae8584caa73bLL; + H[2] = 0x3c6ef372fe94f82bLL; + H[3] = 0xa54ff53a5f1d36f1LL; + H[4] = 0x510e527fade682d1LL; + H[5] = 0x9b05688c2b3e6c1fLL; + H[6] = 0x1f83d9abfb41bd6bLL; + H[7] = 0x5be0cd19137e2179LL; + + blkLen = 0; + for (i = 0; i + 128 <= msgLen; i += 128) { + sha512HashBlock(msg + i, H); + } + blkLen = msgLen - i; + if (blkLen > 0) { + memcpy(blk, msg + i, blkLen); + } + + // pad the message + blk[blkLen++] = 0x80; + if (blkLen > 112) { + while (blkLen < 128) { + blk[blkLen++] = 0; + } + sha512HashBlock(blk, H); + blkLen = 0; + } + while (blkLen < 112) { + blk[blkLen++] = 0; + } + blk[112] = 0; + blk[113] = 0; + blk[114] = 0; + blk[115] = 0; + blk[116] = 0; + blk[117] = 0; + blk[118] = 0; + blk[119] = 0; + blk[120] = 0; + blk[121] = 0; + blk[122] = 0; + blk[123] = 0; + blk[124] = (Guchar)(msgLen >> 21); + blk[125] = (Guchar)(msgLen >> 13); + blk[126] = (Guchar)(msgLen >> 5); + blk[127] = (Guchar)(msgLen << 3); + sha512HashBlock(blk, H); + + // copy the output into the buffer (convert words to bytes) + for (i = 0; i < 8; ++i) { + hash[i*8] = (Guchar)(H[i] >> 56); + hash[i*8 + 1] = (Guchar)(H[i] >> 48); + hash[i*8 + 2] = (Guchar)(H[i] >> 40); + hash[i*8 + 3] = (Guchar)(H[i] >> 32); + hash[i*8 + 4] = (Guchar)(H[i] >> 24); + hash[i*8 + 5] = (Guchar)(H[i] >> 16); + hash[i*8 + 6] = (Guchar)(H[i] >> 8); + hash[i*8 + 7] = (Guchar)H[i]; + } +} + +static void sha384(Guchar *msg, int msgLen, Guchar *hash) { + Guchar blk[128]; + SHA512Uint64 H[8]; + int blkLen, i; + + H[0] = 0xcbbb9d5dc1059ed8LL; + H[1] = 0x629a292a367cd507LL; + H[2] = 0x9159015a3070dd17LL; + H[3] = 0x152fecd8f70e5939LL; + H[4] = 0x67332667ffc00b31LL; + H[5] = 0x8eb44a8768581511LL; + H[6] = 0xdb0c2e0d64f98fa7LL; + H[7] = 0x47b5481dbefa4fa4LL; + + blkLen = 0; + for (i = 0; i + 128 <= msgLen; i += 128) { + sha512HashBlock(msg + i, H); + } + blkLen = msgLen - i; + if (blkLen > 0) { + memcpy(blk, msg + i, blkLen); + } + + // pad the message + blk[blkLen++] = 0x80; + if (blkLen > 112) { + while (blkLen < 128) { + blk[blkLen++] = 0; + } + sha512HashBlock(blk, H); + blkLen = 0; + } + while (blkLen < 112) { + blk[blkLen++] = 0; + } + blk[112] = 0; + blk[113] = 0; + blk[114] = 0; + blk[115] = 0; + blk[116] = 0; + blk[117] = 0; + blk[118] = 0; + blk[119] = 0; + blk[120] = 0; + blk[121] = 0; + blk[122] = 0; + blk[123] = 0; + blk[124] = (Guchar)(msgLen >> 21); + blk[125] = (Guchar)(msgLen >> 13); + blk[126] = (Guchar)(msgLen >> 5); + blk[127] = (Guchar)(msgLen << 3); + sha512HashBlock(blk, H); + + // copy the output into the buffer (convert words to bytes) + for (i = 0; i < 6; ++i) { + hash[i*8] = (Guchar)(H[i] >> 56); + hash[i*8 + 1] = (Guchar)(H[i] >> 48); + hash[i*8 + 2] = (Guchar)(H[i] >> 40); + hash[i*8 + 3] = (Guchar)(H[i] >> 32); + hash[i*8 + 4] = (Guchar)(H[i] >> 24); + hash[i*8 + 5] = (Guchar)(H[i] >> 16); + hash[i*8 + 6] = (Guchar)(H[i] >> 8); + hash[i*8 + 7] = (Guchar)H[i]; + } +} diff -uNrp xpdf-3.03/xpdf/Decrypt.h xpdf-3.04/xpdf/Decrypt.h --- xpdf-3.03/xpdf/Decrypt.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Decrypt.h 2014-05-28 20:50:50.000000000 +0200 @@ -42,6 +42,8 @@ public: private: + static void r6Hash(Guchar *key, int keyLen, const char *pwd, int pwdLen, + char *userKey); static GBool makeFileKey2(int encVersion, int encRevision, int keyLength, GString *ownerKey, GString *userKey, int permissions, GString *fileID, @@ -104,8 +106,24 @@ private: //------------------------------------------------------------------------ +struct MD5State { + Gulong a, b, c, d; + Guchar buf[64]; + int bufLen; + int msgLen; + Guchar digest[16]; +}; + extern void rc4InitKey(Guchar *key, int keyLen, Guchar *state); extern Guchar rc4DecryptByte(Guchar *state, Guchar *x, Guchar *y, Guchar c); +void md5Start(MD5State *state); +void md5Append(MD5State *state, Guchar *data, int dataLen); +void md5Finish(MD5State *state); extern void md5(Guchar *msg, int msgLen, Guchar *digest); +extern void aesKeyExpansion(DecryptAESState *s, + Guchar *objKey, int objKeyLen, + GBool decrypt); +extern void aesEncryptBlock(DecryptAESState *s, Guchar *in); +extern void aesDecryptBlock(DecryptAESState *s, Guchar *in, GBool last); #endif diff -uNrp xpdf-3.03/xpdf/Dict.cc xpdf-3.04/xpdf/Dict.cc --- xpdf-3.03/xpdf/Dict.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Dict.cc 2014-05-28 20:50:50.000000000 +0200 @@ -20,13 +20,24 @@ #include "Dict.h" //------------------------------------------------------------------------ + +struct DictEntry { + char *key; + Object val; + DictEntry *next; +}; + +//------------------------------------------------------------------------ // Dict //------------------------------------------------------------------------ Dict::Dict(XRef *xrefA) { xref = xrefA; - entries = NULL; - size = length = 0; + size = 8; + length = 0; + entries = (DictEntry *)gmallocn(size, sizeof(DictEntry)); + hashTab = (DictEntry **)gmallocn(2 * size - 1, sizeof(DictEntry *)); + memset(hashTab, 0, (2 * size - 1) * sizeof(DictEntry *)); ref = 1; } @@ -38,32 +49,69 @@ Dict::~Dict() { entries[i].val.free(); } gfree(entries); + gfree(hashTab); } void Dict::add(char *key, Object *val) { - if (length == size) { - if (length == 0) { - size = 8; - } else { - size *= 2; + DictEntry *e; + int h; + + if ((e = find(key))) { + e->val.free(); + e->val = *val; + gfree(key); + } else { + if (length == size) { + expand(); } - entries = (DictEntry *)greallocn(entries, size, sizeof(DictEntry)); + h = hash(key); + entries[length].key = key; + entries[length].val = *val; + entries[length].next = hashTab[h]; + hashTab[h] = &entries[length]; + ++length; } - entries[length].key = key; - entries[length].val = *val; - ++length; } -inline DictEntry *Dict::find(const char *key) { - int i; +void Dict::expand() { + int h, i; + size *= 2; + entries = (DictEntry *)greallocn(entries, size, sizeof(DictEntry)); + hashTab = (DictEntry **)greallocn(hashTab, 2 * size - 1, + sizeof(DictEntry *)); + memset(hashTab, 0, (2 * size - 1) * sizeof(DictEntry *)); for (i = 0; i < length; ++i) { - if (!strcmp(key, entries[i].key)) - return &entries[i]; + h = hash(entries[i].key); + entries[i].next = hashTab[h]; + hashTab[h] = &entries[i]; + } +} + +inline DictEntry *Dict::find(const char *key) { + DictEntry *e; + int h; + + h = hash(key); + for (e = hashTab[h]; e; e = e->next) { + if (!strcmp(key, e->key)) { + return e; + } } return NULL; } +int Dict::hash(const char *key) { + const char *p; + unsigned int h; + + h = 0; + for (p = key; *p; ++p) { + h = 17 * h + (int)(*p & 0xff); + } + return (int)(h % (2 * size - 1)); +} + GBool Dict::is(const char *type) { DictEntry *e; diff -uNrp xpdf-3.03/xpdf/Dict.h xpdf-3.04/xpdf/Dict.h --- xpdf-3.03/xpdf/Dict.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Dict.h 2014-05-28 20:50:50.000000000 +0200 @@ -17,15 +17,12 @@ #include "Object.h" +struct DictEntry; + //------------------------------------------------------------------------ // Dict //------------------------------------------------------------------------ -struct DictEntry { - char *key; - Object val; -}; - class Dict { public: @@ -67,11 +64,14 @@ private: XRef *xref; // the xref table for this PDF file DictEntry *entries; // array of entries + DictEntry **hashTab; // hash table pointers int size; // size of array int length; // number of entries in dictionary int ref; // reference count DictEntry *find(const char *key); + void expand(); + int hash(const char *key); }; #endif diff -uNrp xpdf-3.03/xpdf/Error.cc xpdf-3.04/xpdf/Error.cc --- xpdf-3.03/xpdf/Error.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Error.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // Error.cc // -// Copyright 1996-2003 Glyph & Cog, LLC +// Copyright 1996-2013 Glyph & Cog, LLC // //======================================================================== @@ -41,9 +41,12 @@ void setErrorCallback(void (*cbk)(void * errorCbkData = data; } -void CDECL error(ErrorCategory category, int pos, const char *msg, ...) { +void CDECL error(ErrorCategory category, GFileOffset pos, + const char *msg, ...) { va_list args; diff -uNrp xpdf-3.03/xpdf/Error.h xpdf-3.04/xpdf/Error.h --- xpdf-3.03/xpdf/Error.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Error.h 2014-05-28 20:50:50.000000000 +0200 @@ -17,11 +17,12 @@ #include #include "config.h" +#include "gfile.h" enum ErrorCategory { errSyntaxWarning, // PDF syntax error which can be worked around; // output will probably be correct - errSyntaxError, // PDF syntax error which can be worked around; + errSyntaxError, // PDF syntax error which cannot be worked around; // output will probably be incorrect errConfig, // error in Xpdf config info (xpdfrc file, etc.) errCommandLine, // error in user-supplied parameters, action not @@ -37,6 +38,7 @@ extern void setErrorCallback(void (*cbk) int pos, char *msg), void *data); -extern void CDECL error(ErrorCategory category, int pos, const char *msg, ...); +extern void CDECL error(ErrorCategory category, GFileOffset pos, + const char *msg, ...); #endif diff -uNrp xpdf-3.03/xpdf/Form.cc xpdf-3.04/xpdf/Form.cc --- xpdf-3.03/xpdf/Form.cc 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/Form.cc 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,67 @@ +//======================================================================== +// +// Form.cc +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#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, catDict, needsRenderingObj; + + if (!acroFormObj->isDict()) { + error(errSyntaxError, -1, "AcroForm object is wrong type"); + return NULL; + } + //~ temporary: create an XFAForm only for XFAF, not for dynamic XFA + acroFormObj->dictLookup("XFA", &xfaObj); + docA->getXRef()->getCatalog(&catDict); + catDict.dictLookup("NeedsRendering", &needsRenderingObj); + catDict.free(); + if (globalParams->getEnableXFA() && + !xfaObj.isNull() && + !(needsRenderingObj.isBool() && needsRenderingObj.getBool())) { + form = XFAForm::load(docA, acroFormObj, &xfaObj); + } else { + form = AcroForm::load(docA, catalog, acroFormObj); + } + xfaObj.free(); + needsRenderingObj.free(); + return form; +} + +Form::Form(PDFDoc *docA) { + doc = docA; +} + +Form::~Form() { +} + +//------------------------------------------------------------------------ +// FormField +//------------------------------------------------------------------------ + +FormField::FormField() { +} + +FormField::~FormField() { +} diff -uNrp xpdf-3.03/xpdf/Form.h xpdf-3.04/xpdf/Form.h --- xpdf-3.03/xpdf/Form.h 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/Form.h 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,64 @@ +//======================================================================== +// +// Form.h +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef FORM_H +#define FORM_H + +#include + +#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; + +protected: + + Form(PDFDoc *docA); + + PDFDoc *doc; +}; + +//------------------------------------------------------------------------ + +class FormField { +public: + + FormField(); + virtual ~FormField(); + + virtual const char *getType() = 0; + virtual Unicode *getName(int *length) = 0; + virtual Unicode *getValue(int *length) = 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 diff -uNrp xpdf-3.03/xpdf/Function.cc xpdf-3.04/xpdf/Function.cc --- xpdf-3.03/xpdf/Function.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Function.cc 2014-05-28 20:50:50.000000000 +0200 @@ -17,6 +17,7 @@ #include #include #include "gmem.h" +#include "GList.h" #include "Object.h" #include "Dict.h" #include "Stream.h" @@ -350,7 +351,7 @@ SampledFunction::SampledFunction(Object samples = (double *)gmallocn(nSamples, sizeof(double)); buf = 0; bits = 0; - bitMask = (1 << sampleBits) - 1; + bitMask = (sampleBits < 32) ? ((1 << sampleBits) - 1) : 0xffffffffU; str->reset(); for (i = 0; i < nSamples; ++i) { if (sampleBits == 8) { @@ -778,55 +779,57 @@ void StitchingFunction::transform(double // PostScriptFunction //------------------------------------------------------------------------ -enum PSOp { - psOpAbs, - psOpAdd, - psOpAnd, - psOpAtan, - psOpBitshift, - psOpCeiling, - psOpCopy, - psOpCos, - psOpCvi, - psOpCvr, - psOpDiv, - psOpDup, - psOpEq, - psOpExch, - psOpExp, - psOpFalse, - psOpFloor, - psOpGe, - psOpGt, - psOpIdiv, - psOpIndex, - psOpLe, - psOpLn, - psOpLog, - psOpLt, - psOpMod, - psOpMul, - psOpNe, - psOpNeg, - psOpNot, - psOpOr, - psOpPop, - psOpRoll, - psOpRound, - psOpSin, - psOpSqrt, - psOpSub, - psOpTrue, - psOpTruncate, - psOpXor, - psOpIf, - psOpIfelse, - psOpReturn -}; +// This is not an enum, because we can't foreward-declare the enum +// type in Function.h +#define psOpAbs 0 +#define psOpAdd 1 +#define psOpAnd 2 +#define psOpAtan 3 +#define psOpBitshift 4 +#define psOpCeiling 5 +#define psOpCopy 6 +#define psOpCos 7 +#define psOpCvi 8 +#define psOpCvr 9 +#define psOpDiv 10 +#define psOpDup 11 +#define psOpEq 12 +#define psOpExch 13 +#define psOpExp 14 +#define psOpFalse 15 +#define psOpFloor 16 +#define psOpGe 17 +#define psOpGt 18 +#define psOpIdiv 19 +#define psOpIndex 20 +#define psOpLe 21 +#define psOpLn 22 +#define psOpLog 23 +#define psOpLt 24 +#define psOpMod 25 +#define psOpMul 26 +#define psOpNe 27 +#define psOpNeg 28 +#define psOpNot 29 +#define psOpOr 30 +#define psOpPop 31 +#define psOpRoll 32 +#define psOpRound 33 +#define psOpSin 34 +#define psOpSqrt 35 +#define psOpSub 36 +#define psOpTrue 37 +#define psOpTruncate 38 +#define psOpXor 39 +#define psOpPush 40 +#define psOpJ 41 +#define psOpJz 42 + +#define nPSOps 43 // Note: 'if' and 'ifelse' are parsed separately. // The rest are listed here in alphabetical order. -// The index in this table is equivalent to the entry in PSOp. +// The index in this table is equivalent to the psOpXXX defines. static const char *psOpNames[] = { "abs", "add", @@ -870,218 +873,22 @@ static const char *psOpNames[] = { "xor" }; -#define nPSOps (sizeof(psOpNames) / sizeof(char *)) - -enum PSObjectType { - psBool, - psInt, - psReal, - psOperator, - psBlock -}; - -// In the code array, 'if'/'ifelse' operators take up three slots -// plus space for the code in the subclause(s). -// -// +---------------------------------+ -// | psOperator: psOpIf / psOpIfelse | -// +---------------------------------+ -// | psBlock: ptr= | -// +---------------------------------+ -// | psBlock: ptr= | -// +---------------------------------+ -// | if clause | -// | ... | -// | psOperator: psOpReturn | -// +---------------------------------+ -// | else clause | -// | ... | -// | psOperator: psOpReturn | -// +---------------------------------+ -// | ... | -// -// For 'if', pointer is present in the code stream but unused. - -struct PSObject { - PSObjectType type; +struct PSCode { + int op; union { - GBool booln; // boolean (stack only) - int intg; // integer (stack and code) - double real; // real (stack and code) - PSOp op; // operator (code only) - int blk; // if/ifelse block pointer (code only) - }; + double d; + int i; + } val; }; #define psStackSize 100 -class PSStack { -public: - - PSStack() { sp = psStackSize; } - void pushBool(GBool booln); - void pushInt(int intg); - void pushReal(double real); - GBool popBool(); - int popInt(); - double popNum(); - GBool empty() { return sp == psStackSize; } - GBool topIsInt() { return sp < psStackSize && stack[sp].type == psInt; } - GBool topTwoAreInts() - { return sp < psStackSize - 1 && - stack[sp].type == psInt && - stack[sp+1].type == psInt; } - GBool topIsReal() { return sp < psStackSize && stack[sp].type == psReal; } - GBool topTwoAreNums() - { return sp < psStackSize - 1 && - (stack[sp].type == psInt || stack[sp].type == psReal) && - (stack[sp+1].type == psInt || stack[sp+1].type == psReal); } - void copy(int n); - void roll(int n, int j); - void index(int i); - void pop(); - -private: - - GBool checkOverflow(int n = 1); - GBool checkUnderflow(); - GBool checkType(PSObjectType t1, PSObjectType t2); - - PSObject stack[psStackSize]; - int sp; -}; - -GBool PSStack::checkOverflow(int n) { - if (sp - n < 0) { - error(errSyntaxError, -1, "Stack overflow in PostScript function"); - return gFalse; - } - return gTrue; -} - -GBool PSStack::checkUnderflow() { - if (sp == psStackSize) { - error(errSyntaxError, -1, "Stack underflow in PostScript function"); - return gFalse; - } - return gTrue; -} - -GBool PSStack::checkType(PSObjectType t1, PSObjectType t2) { - if (stack[sp].type != t1 && stack[sp].type != t2) { - error(errSyntaxError, -1, "Type mismatch in PostScript function"); - return gFalse; - } - return gTrue; -} - -void PSStack::pushBool(GBool booln) { - if (checkOverflow()) { - stack[--sp].type = psBool; - stack[sp].booln = booln; - } -} - -void PSStack::pushInt(int intg) { - if (checkOverflow()) { - stack[--sp].type = psInt; - stack[sp].intg = intg; - } -} - -void PSStack::pushReal(double real) { - if (checkOverflow()) { - stack[--sp].type = psReal; - stack[sp].real = real; - } -} - -GBool PSStack::popBool() { - if (checkUnderflow() && checkType(psBool, psBool)) { - return stack[sp++].booln; - } - return gFalse; -} - -int PSStack::popInt() { - if (checkUnderflow() && checkType(psInt, psInt)) { - return stack[sp++].intg; - } - return 0; -} - -double PSStack::popNum() { - double ret; - - if (checkUnderflow() && checkType(psInt, psReal)) { - ret = (stack[sp].type == psInt) ? (double)stack[sp].intg : stack[sp].real; - ++sp; - return ret; - } - return 0; -} - -void PSStack::copy(int n) { - int i; - - if (sp + n > psStackSize) { - error(errSyntaxError, -1, "Stack underflow in PostScript function"); - return; - } - if (!checkOverflow(n)) { - return; - } - for (i = sp + n - 1; i >= sp; --i) { - stack[i - n] = stack[i]; - } - sp -= n; -} - -void PSStack::roll(int n, int j) { - PSObject obj; - int i, k; - - if (j >= 0) { - j %= n; - } else { - j = -j % n; - if (j != 0) { - j = n - j; - } - } - if (n <= 0 || j == 0 || n > psStackSize || sp + n > psStackSize) { - return; - } - for (i = 0; i < j; ++i) { - obj = stack[sp]; - for (k = sp; k < sp + n - 1; ++k) { - stack[k] = stack[k+1]; - } - stack[sp + n - 1] = obj; - } -} - -void PSStack::index(int i) { - if (!checkOverflow()) { - return; - } - --sp; - stack[sp] = stack[sp + 1 + i]; -} - -void PSStack::pop() { - if (!checkUnderflow()) { - return; - } - ++sp; -} - PostScriptFunction::PostScriptFunction(Object *funcObj, Dict *dict) { Stream *str; - int codePtr; + GList *tokens; GString *tok; double in[funcMaxInputs]; - int i; + int tokPtr, codePtr, i; codeString = NULL; code = NULL; @@ -1104,22 +911,27 @@ PostScriptFunction::PostScriptFunction(O } str = funcObj->getStream(); - //----- parse the function + //----- tokenize the function codeString = new GString(); + tokens = new GList(); str->reset(); - if (!(tok = getToken(str)) || tok->cmp("{")) { + while ((tok = getToken(str))) { + tokens->append(tok); + } + str->close(); + + //----- parse the function + if (tokens->getLength() < 1 || + ((GString *)tokens->get(0))->cmp("{")) { error(errSyntaxError, -1, "Expected '{' at start of PostScript function"); - if (tok) { - delete tok; - } - goto err1; + goto err2; } - delete tok; + tokPtr = 1; codePtr = 0; - if (!parseCode(str, &codePtr)) { + if (!parseCode(tokens, &tokPtr, &codePtr)) { goto err2; } - str->close(); + codeLen = codePtr; //----- set up the cache for (i = 0; i < m; ++i) { @@ -1131,16 +943,16 @@ PostScriptFunction::PostScriptFunction(O ok = gTrue; err2: - str->close(); + deleteGList(tokens, GString); err1: return; } PostScriptFunction::PostScriptFunction(PostScriptFunction *func) { memcpy(this, func, sizeof(PostScriptFunction)); - code = (PSObject *)gmallocn(codeSize, sizeof(PSObject)); - memcpy(code, func->code, codeSize * sizeof(PSObject)); codeString = func->codeString->copy(); + code = (PSCode *)gmallocn(codeSize, sizeof(PSCode)); + memcpy(code, func->code, codeSize * sizeof(PSCode)); } PostScriptFunction::~PostScriptFunction() { @@ -1151,8 +963,9 @@ PostScriptFunction::~PostScriptFunction( } void PostScriptFunction::transform(double *in, double *out) { - PSStack *stack; - int i; + double stack[psStackSize]; + double x; + int sp, i; // check the cache for (i = 0; i < m; ++i) { @@ -1167,25 +980,28 @@ void PostScriptFunction::transform(doubl return; } - stack = new PSStack(); for (i = 0; i < m; ++i) { - //~ may need to check for integers here - stack->pushReal(in[i]); + stack[psStackSize - 1 - i] = in[i]; } - exec(stack, 0); - for (i = n - 1; i >= 0; --i) { - out[i] = stack->popNum(); - if (out[i] < range[i][0]) { + sp = exec(stack, psStackSize - m); + // if (sp < psStackSize - n) { + // error(errSyntaxWarning, -1, + // "Extra values on stack at end of PostScript function"); + // } + if (sp > psStackSize - n) { + error(errSyntaxError, -1, "Stack underflow in PostScript function"); + sp = psStackSize - n; + } + for (i = 0; i < n; ++i) { + x = stack[sp + n - 1 - i]; + if (x < range[i][0]) { out[i] = range[i][0]; - } else if (out[i] > range[i][1]) { + } else if (x > range[i][1]) { out[i] = range[i][1]; + } else { + out[i] = x; } } - // if (!stack->empty()) { - // error(errSyntaxWarning, -1, - // "Extra values on stack at end of PostScript function"); - // } - delete stack; // save current result in the cache for (i = 0; i < m; ++i) { @@ -1196,101 +1012,71 @@ void PostScriptFunction::transform(doubl } } -GBool PostScriptFunction::parseCode(Stream *str, int *codePtr) { +GBool PostScriptFunction::parseCode(GList *tokens, int *tokPtr, int *codePtr) { GString *tok; char *p; - GBool isReal; - int opPtr, elsePtr; int a, b, mid, cmp; + int codePtr0, codePtr1; while (1) { - if (!(tok = getToken(str))) { + if (*tokPtr >= tokens->getLength()) { error(errSyntaxError, -1, "Unexpected end of PostScript function stream"); return gFalse; } + tok = (GString *)tokens->get((*tokPtr)++); p = tok->getCString(); if (isdigit(*p) || *p == '.' || *p == '-') { - isReal = gFalse; - for (; *p; ++p) { - if (*p == '.') { - isReal = gTrue; - break; - } - } - resizeCode(*codePtr); - if (isReal) { - code[*codePtr].type = psReal; - code[*codePtr].real = atof(tok->getCString()); - } else { - code[*codePtr].type = psInt; - code[*codePtr].intg = atoi(tok->getCString()); - } - ++*codePtr; - delete tok; + addCodeD(codePtr, psOpPush, atof(tok->getCString())); } else if (!tok->cmp("{")) { - delete tok; - opPtr = *codePtr; - *codePtr += 3; - resizeCode(opPtr + 2); - if (!parseCode(str, codePtr)) { + codePtr0 = *codePtr; + addCodeI(codePtr, psOpJz, 0); + if (!parseCode(tokens, tokPtr, codePtr)) { return gFalse; } - if (!(tok = getToken(str))) { + if (*tokPtr >= tokens->getLength()) { error(errSyntaxError, -1, "Unexpected end of PostScript function stream"); return gFalse; } - if (!tok->cmp("{")) { - elsePtr = *codePtr; - if (!parseCode(str, codePtr)) { + tok = (GString *)tokens->get((*tokPtr)++); + if (!tok->cmp("if")) { + code[codePtr0].val.i = *codePtr; + } else if (!tok->cmp("{")) { + codePtr1 = *codePtr; + addCodeI(codePtr, psOpJ, 0); + code[codePtr0].val.i = *codePtr; + if (!parseCode(tokens, tokPtr, codePtr)) { return gFalse; } - delete tok; - if (!(tok = getToken(str))) { + if (*tokPtr >= tokens->getLength()) { error(errSyntaxError, -1, "Unexpected end of PostScript function stream"); return gFalse; } - } else { - elsePtr = -1; - } - if (!tok->cmp("if")) { - if (elsePtr >= 0) { - error(errSyntaxError, -1, - "Got 'if' operator with two blocks in PostScript function"); - return gFalse; - } - code[opPtr].type = psOperator; - code[opPtr].op = psOpIf; - code[opPtr+2].type = psBlock; - code[opPtr+2].blk = *codePtr; - } else if (!tok->cmp("ifelse")) { - if (elsePtr < 0) { + tok = (GString *)tokens->get((*tokPtr)++); + if (!tok->cmp("ifelse")) { + code[codePtr1].val.i = *codePtr; + } else { error(errSyntaxError, -1, - "Got 'ifelse' operator with one block in PostScript function"); + "Expected 'ifelse' in PostScript function stream"); return gFalse; } - code[opPtr].type = psOperator; - code[opPtr].op = psOpIfelse; - code[opPtr+1].type = psBlock; - code[opPtr+1].blk = elsePtr; - code[opPtr+2].type = psBlock; - code[opPtr+2].blk = *codePtr; } else { error(errSyntaxError, -1, - "Expected if/ifelse operator in PostScript function"); - delete tok; + "Expected 'if' in PostScript function stream"); return gFalse; } - delete tok; } else if (!tok->cmp("}")) { - delete tok; - resizeCode(*codePtr); - code[*codePtr].type = psOperator; - code[*codePtr].op = psOpReturn; - ++*codePtr; break; + } else if (!tok->cmp("if")) { + error(errSyntaxError, -1, + "Unexpected 'if' in PostScript function stream"); + return gFalse; + } else if (!tok->cmp("ifelse")) { + error(errSyntaxError, -1, + "Unexpected 'ifelse' in PostScript function stream"); + return gFalse; } else { a = -1; b = nPSOps; @@ -1311,19 +1097,55 @@ GBool PostScriptFunction::parseCode(Stre error(errSyntaxError, -1, "Unknown operator '{0:t}' in PostScript function", tok); - delete tok; return gFalse; } - delete tok; - resizeCode(*codePtr); - code[*codePtr].type = psOperator; - code[*codePtr].op = (PSOp)a; - ++*codePtr; + addCode(codePtr, a); } } return gTrue; } +void PostScriptFunction::addCode(int *codePtr, int op) { + if (*codePtr >= codeSize) { + if (codeSize) { + codeSize *= 2; + } else { + codeSize = 16; + } + code = (PSCode *)greallocn(code, codeSize, sizeof(PSCode)); + } + code[*codePtr].op = op; + ++(*codePtr); +} + +void PostScriptFunction::addCodeI(int *codePtr, int op, int x) { + if (*codePtr >= codeSize) { + if (codeSize) { + codeSize *= 2; + } else { + codeSize = 16; + } + code = (PSCode *)greallocn(code, codeSize, sizeof(PSCode)); + } + code[*codePtr].op = op; + code[*codePtr].val.i = x; + ++(*codePtr); +} + +void PostScriptFunction::addCodeD(int *codePtr, int op, double x) { + if (*codePtr >= codeSize) { + if (codeSize) { + codeSize *= 2; + } else { + codeSize = 16; + } + code = (PSCode *)greallocn(code, codeSize, sizeof(PSCode)); + } + code[*codePtr].op = op; + code[*codePtr].val.d = x; + ++(*codePtr); +} + GString *PostScriptFunction::getToken(Stream *str) { GString *s; int c; @@ -1333,7 +1155,8 @@ GString *PostScriptFunction::getToken(St comment = gFalse; while (1) { if ((c = str->getChar()) == EOF) { - break; + delete s; + return NULL; } codeString->append(c); if (comment) { @@ -1372,323 +1195,362 @@ GString *PostScriptFunction::getToken(St return s; } -void PostScriptFunction::resizeCode(int newSize) { - if (newSize >= codeSize) { - codeSize += 64; - code = (PSObject *)greallocn(code, codeSize, sizeof(PSObject)); - } -} - -void PostScriptFunction::exec(PSStack *stack, int codePtr) { - int i1, i2; - double r1, r2; - GBool b1, b2; - - while (1) { - switch (code[codePtr].type) { - case psInt: - stack->pushInt(code[codePtr++].intg); - break; - case psReal: - stack->pushReal(code[codePtr++].real); - break; - case psOperator: - switch (code[codePtr++].op) { - case psOpAbs: - if (stack->topIsInt()) { - stack->pushInt(abs(stack->popInt())); - } else { - stack->pushReal(fabs(stack->popNum())); - } - break; - case psOpAdd: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 + i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(r1 + r2); - } - break; - case psOpAnd: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 & i2); - } else { - b2 = stack->popBool(); - b1 = stack->popBool(); - stack->pushBool(b1 && b2); - } - break; - case psOpAtan: - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(atan2(r1, r2)); - break; - case psOpBitshift: - i2 = stack->popInt(); - i1 = stack->popInt(); - if (i2 > 0) { - stack->pushInt(i1 << i2); - } else if (i2 < 0) { - stack->pushInt((int)((Guint)i1 >> -i2)); - } else { - stack->pushInt(i1); - } - break; - case psOpCeiling: - if (!stack->topIsInt()) { - stack->pushReal(ceil(stack->popNum())); - } - break; - case psOpCopy: - stack->copy(stack->popInt()); - break; - case psOpCos: - stack->pushReal(cos(stack->popNum())); - break; - case psOpCvi: - if (!stack->topIsInt()) { - stack->pushInt((int)stack->popNum()); - } - break; - case psOpCvr: - if (!stack->topIsReal()) { - stack->pushReal(stack->popNum()); - } - break; - case psOpDiv: - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(r1 / r2); - break; - case psOpDup: - stack->copy(1); - break; - case psOpEq: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 == i2); - } else if (stack->topTwoAreNums()) { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 == r2); - } else { - b2 = stack->popBool(); - b1 = stack->popBool(); - stack->pushBool(b1 == b2); - } - break; - case psOpExch: - stack->roll(2, 1); - break; - case psOpExp: - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(pow(r1, r2)); - break; - case psOpFalse: - stack->pushBool(gFalse); - break; - case psOpFloor: - if (!stack->topIsInt()) { - stack->pushReal(floor(stack->popNum())); - } - break; - case psOpGe: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 >= i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 >= r2); - } - break; - case psOpGt: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 > i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 > r2); - } - break; - case psOpIdiv: - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 / i2); - break; - case psOpIndex: - stack->index(stack->popInt()); - break; - case psOpLe: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 <= i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 <= r2); - } - break; - case psOpLn: - stack->pushReal(log(stack->popNum())); - break; - case psOpLog: - stack->pushReal(log10(stack->popNum())); - break; - case psOpLt: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 < i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 < r2); - } - break; - case psOpMod: - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 % i2); - break; - case psOpMul: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - //~ should check for out-of-range, and push a real instead - stack->pushInt(i1 * i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(r1 * r2); - } - break; - case psOpNe: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushBool(i1 != i2); - } else if (stack->topTwoAreNums()) { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushBool(r1 != r2); - } else { - b2 = stack->popBool(); - b1 = stack->popBool(); - stack->pushBool(b1 != b2); - } - break; - case psOpNeg: - if (stack->topIsInt()) { - stack->pushInt(-stack->popInt()); - } else { - stack->pushReal(-stack->popNum()); - } - break; - case psOpNot: - if (stack->topIsInt()) { - stack->pushInt(~stack->popInt()); - } else { - stack->pushBool(!stack->popBool()); - } - break; - case psOpOr: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 | i2); - } else { - b2 = stack->popBool(); - b1 = stack->popBool(); - stack->pushBool(b1 || b2); - } - break; - case psOpPop: - stack->pop(); - break; - case psOpRoll: - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->roll(i1, i2); - break; - case psOpRound: - if (!stack->topIsInt()) { - r1 = stack->popNum(); - stack->pushReal((r1 >= 0) ? floor(r1 + 0.5) : ceil(r1 - 0.5)); - } - break; - case psOpSin: - stack->pushReal(sin(stack->popNum())); - break; - case psOpSqrt: - stack->pushReal(sqrt(stack->popNum())); - break; - case psOpSub: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 - i2); - } else { - r2 = stack->popNum(); - r1 = stack->popNum(); - stack->pushReal(r1 - r2); - } - break; - case psOpTrue: - stack->pushBool(gTrue); - break; - case psOpTruncate: - if (!stack->topIsInt()) { - r1 = stack->popNum(); - stack->pushReal((r1 >= 0) ? floor(r1) : ceil(r1)); - } - break; - case psOpXor: - if (stack->topTwoAreInts()) { - i2 = stack->popInt(); - i1 = stack->popInt(); - stack->pushInt(i1 ^ i2); - } else { - b2 = stack->popBool(); - b1 = stack->popBool(); - stack->pushBool(b1 ^ b2); - } - break; - case psOpIf: - b1 = stack->popBool(); - if (b1) { - exec(stack, codePtr + 2); - } - codePtr = code[codePtr + 1].blk; - break; - case psOpIfelse: - b1 = stack->popBool(); - if (b1) { - exec(stack, codePtr + 2); - } else { - exec(stack, code[codePtr].blk); +int PostScriptFunction::exec(double *stack, int sp0) { + PSCode *c; + double tmp[psStackSize]; + double t; + int sp, ip, nn, k, i; + + sp = sp0; + ip = 0; + while (ip < codeLen) { + c = &code[ip++]; + switch(c->op) { + case psOpAbs: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = fabs(stack[sp]); + break; + case psOpAdd: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] + stack[sp]; + ++sp; + break; + case psOpAnd: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = (int)stack[sp + 1] & (int)stack[sp]; + ++sp; + break; + case psOpAtan: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = atan2(stack[sp + 1], stack[sp]); + ++sp; + break; + case psOpBitshift: + if (sp + 1 >= psStackSize) { + goto underflow; + } + k = (int)stack[sp + 1]; + nn = (int)stack[sp]; + if (nn > 0) { + stack[sp + 1] = k << nn; + } else if (nn < 0) { + stack[sp + 1] = k >> -nn; + } else { + stack[sp + 1] = k; + } + ++sp; + break; + case psOpCeiling: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = ceil(stack[sp]); + break; + case psOpCopy: + if (sp >= psStackSize) { + goto underflow; + } + nn = (int)stack[sp++]; + if (nn < 0) { + goto invalidArg; + } + if (sp + nn > psStackSize) { + goto underflow; + } + if (sp - nn < 0) { + goto overflow; + } + for (i = 0; i < nn; ++i) { + stack[sp - nn + i] = stack[sp + i]; + } + sp -= nn; + break; + case psOpCos: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = cos(stack[sp]); + break; + case psOpCvi: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = (int)stack[sp]; + break; + case psOpCvr: + if (sp >= psStackSize) { + goto underflow; + } + break; + case psOpDiv: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] / stack[sp]; + ++sp; + break; + case psOpDup: + if (sp >= psStackSize) { + goto underflow; + } + if (sp < 1) { + goto overflow; + } + stack[sp - 1] = stack[sp]; + --sp; + break; + case psOpEq: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] == stack[sp] ? 1 : 0; + ++sp; + break; + case psOpExch: + if (sp + 1 >= psStackSize) { + goto underflow; + } + t = stack[sp]; + stack[sp] = stack[sp + 1]; + stack[sp + 1] = t; + break; + case psOpExp: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = pow(stack[sp + 1], stack[sp]); + ++sp; + break; + case psOpFalse: + if (sp < 1) { + goto overflow; + } + stack[sp - 1] = 0; + --sp; + break; + case psOpFloor: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = floor(stack[sp]); + break; + case psOpGe: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] >= stack[sp] ? 1 : 0; + ++sp; + break; + case psOpGt: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] > stack[sp] ? 1 : 0; + ++sp; + break; + case psOpIdiv: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = (int)stack[sp + 1] / (int)stack[sp]; + ++sp; + break; + case psOpIndex: + if (sp >= psStackSize) { + goto underflow; + } + k = (int)stack[sp]; + if (k < 0) { + goto invalidArg; + } + if (sp + 1 + k >= psStackSize) { + goto underflow; + } + stack[sp] = stack[sp + 1 + k]; + break; + case psOpLe: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] <= stack[sp] ? 1 : 0; + ++sp; + break; + case psOpLn: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = log(stack[sp]); + break; + case psOpLog: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = log10(stack[sp]); + break; + case psOpLt: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] < stack[sp] ? 1 : 0; + ++sp; + break; + case psOpMod: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = (int)stack[sp + 1] % (int)stack[sp]; + ++sp; + break; + case psOpMul: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] * stack[sp]; + ++sp; + break; + case psOpNe: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] != stack[sp] ? 1 : 0; + ++sp; + break; + case psOpNeg: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = -stack[sp]; + break; + case psOpNot: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = stack[sp] == 0 ? 1 : 0; + break; + case psOpOr: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = (int)stack[sp + 1] | (int)stack[sp]; + ++sp; + break; + case psOpPop: + if (sp >= psStackSize) { + goto underflow; + } + ++sp; + break; + case psOpRoll: + if (sp + 1 >= psStackSize) { + goto underflow; + } + k = (int)stack[sp++]; + nn = (int)stack[sp++]; + if (nn < 0) { + goto invalidArg; + } + if (sp + nn > psStackSize) { + goto underflow; + } + if (k >= 0) { + k %= nn; + } else { + k = -k % nn; + if (k) { + k = nn - k; } - codePtr = code[codePtr + 1].blk; - break; - case psOpReturn: - return; + } + for (i = 0; i < nn; ++i) { + tmp[i] = stack[sp + i]; + } + for (i = 0; i < nn; ++i) { + stack[sp + i] = tmp[(i + k) % nn]; } break; - default: - error(errSyntaxError, -1, - "Internal: bad object in PostScript function code"); + case psOpRound: + if (sp >= psStackSize) { + goto underflow; + } + t = stack[sp]; + stack[sp] = (t >= 0) ? floor(t + 0.5) : ceil(t - 0.5); + break; + case psOpSin: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = sin(stack[sp]); + break; + case psOpSqrt: + if (sp >= psStackSize) { + goto underflow; + } + stack[sp] = sqrt(stack[sp]); + break; + case psOpSub: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = stack[sp + 1] - stack[sp]; + ++sp; + break; + case psOpTrue: + if (sp < 1) { + goto overflow; + } + stack[sp - 1] = 1; + --sp; + break; + case psOpTruncate: + if (sp >= psStackSize) { + goto underflow; + } + t = stack[sp]; + stack[sp] = (t >= 0) ? floor(t) : ceil(t); + break; + case psOpXor: + if (sp + 1 >= psStackSize) { + goto underflow; + } + stack[sp + 1] = (int)stack[sp + 1] ^ (int)stack[sp]; + ++sp; + break; + case psOpPush: + if (sp < 1) { + goto overflow; + } + stack[--sp] = c->val.d; + break; + case psOpJ: + ip = c->val.i; + break; + case psOpJz: + if (sp >= psStackSize) { + goto underflow; + } + k = (int)stack[sp++]; + if (k == 0) { + ip = c->val.i; + } break; } } + return sp; + + underflow: + error(errSyntaxError, -1, "Stack underflow in PostScript function"); + return sp; + overflow: + error(errSyntaxError, -1, "Stack overflow in PostScript function"); + return sp; + invalidArg: + error(errSyntaxError, -1, "Invalid arg in PostScript function"); + return sp; } diff -uNrp xpdf-3.03/xpdf/Function.h xpdf-3.04/xpdf/Function.h --- xpdf-3.03/xpdf/Function.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Function.h 2014-05-28 20:50:50.000000000 +0200 @@ -18,10 +18,10 @@ #include "gtypes.h" #include "Object.h" +class GList; class Dict; class Stream; -struct PSObject; -class PSStack; +struct PSCode; //------------------------------------------------------------------------ // Function @@ -217,13 +217,16 @@ public: private: PostScriptFunction(PostScriptFunction *func); - GBool parseCode(Stream *str, int *codePtr); + GBool parseCode(GList *tokens, int *tokPtr, int *codePtr); + void addCode(int *codePtr, int op); + void addCodeI(int *codePtr, int op, int x); + void addCodeD(int *codePtr, int op, double x); GString *getToken(Stream *str); - void resizeCode(int newSize); - void exec(PSStack *stack, int codePtr); + int exec(double *stack, int sp0); GString *codeString; - PSObject *code; + PSCode *code; + int codeLen; int codeSize; double cacheIn[funcMaxInputs]; double cacheOut[funcMaxOutputs]; diff -uNrp xpdf-3.03/xpdf/Gfx.cc xpdf-3.04/xpdf/Gfx.cc --- xpdf-3.03/xpdf/Gfx.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Gfx.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // Gfx.cc // -// Copyright 1996-2003 Glyph & Cog, LLC +// Copyright 1996-2013 Glyph & Cog, LLC // //======================================================================== @@ -36,7 +36,7 @@ #include "Annot.h" #include "OptionalContent.h" #include "Error.h" -#include "PDFDocEncoding.h" +#include "TextString.h" #include "Gfx.h" // the MSVC math.h doesn't define this @@ -80,11 +80,15 @@ // fill. #define patchColorDelta (dblToCol(1 / 256.0)) +// Max errors (undefined operator, wrong number of args) allowed before +// giving up on a content stream. +#define contentStreamErrorLimit 500 + //------------------------------------------------------------------------ // Operator table //------------------------------------------------------------------------ @@ -337,15 +341,31 @@ GfxFont *GfxResources::lookupFont(char * for (resPtr = this; resPtr; resPtr = resPtr->next) { if (resPtr->fonts) { - if ((font = resPtr->fonts->lookup(name))) + if ((font = resPtr->fonts->lookup(name))) { return font; + } } } error(errSyntaxError, -1, "Unknown font tag '{0:s}'", name); return NULL; } -GBool GfxResources::lookupXObject(char *name, Object *obj) { +GfxFont *GfxResources::lookupFontByRef(Ref ref) { + GfxFont *font; + GfxResources *resPtr; + + for (resPtr = this; resPtr; resPtr = resPtr->next) { + if (resPtr->fonts) { + if ((font = resPtr->fonts->lookupByRef(ref))) { + return font; + } + } + } + error(errSyntaxError, -1, "Unknown font ref {0:d}.{1:d}", ref.num, ref.gen); + return NULL; +} + +GBool GfxResources::lookupXObject(const char *name, Object *obj) { GfxResources *resPtr; for (resPtr = this; resPtr; resPtr = resPtr->next) { @@ -359,7 +379,7 @@ GBool GfxResources::lookupXObject(char * return gFalse; } -GBool GfxResources::lookupXObjectNF(char *name, Object *obj) { +GBool GfxResources::lookupXObjectNF(const char *name, Object *obj) { GfxResources *resPtr; for (resPtr = this; resPtr; resPtr = resPtr->next) { @@ -373,9 +393,16 @@ GBool GfxResources::lookupXObjectNF(char return gFalse; } -void GfxResources::lookupColorSpace(char *name, Object *obj) { +void GfxResources::lookupColorSpace(const char *name, Object *obj) { GfxResources *resPtr; + //~ should also test for G, RGB, and CMYK - but only in inline images (?) + if (!strcmp(name, "DeviceGray") || + !strcmp(name, "DeviceRGB") || + !strcmp(name, "DeviceCMYK")) { + obj->initNull(); + return; + } for (resPtr = this; resPtr; resPtr = resPtr->next) { if (resPtr->colorSpaceDict.isDict()) { if (!resPtr->colorSpaceDict.dictLookup(name, obj)->isNull()) { @@ -387,15 +414,19 @@ void GfxResources::lookupColorSpace(char obj->initNull(); } -GfxPattern *GfxResources::lookupPattern(char *name) { +GfxPattern *GfxResources::lookupPattern(const char *name + ) { GfxResources *resPtr; GfxPattern *pattern; - Object obj; + Object objRef, obj; for (resPtr = this; resPtr; resPtr = resPtr->next) { if (resPtr->patternDict.isDict()) { if (!resPtr->patternDict.dictLookup(name, &obj)->isNull()) { - pattern = GfxPattern::parse(&obj); + resPtr->patternDict.dictLookupNF(name, &objRef); + pattern = GfxPattern::parse(&objRef, &obj + ); + objRef.free(); obj.free(); return pattern; } @@ -406,7 +437,8 @@ GfxPattern *GfxResources::lookupPattern( return NULL; } -GfxShading *GfxResources::lookupShading(char *name) { +GfxShading *GfxResources::lookupShading(const char *name + ) { GfxResources *resPtr; GfxShading *shading; Object obj; @@ -414,7 +446,8 @@ GfxShading *GfxResources::lookupShading( for (resPtr = this; resPtr; resPtr = resPtr->next) { if (resPtr->shadingDict.isDict()) { if (!resPtr->shadingDict.dictLookup(name, &obj)->isNull()) { - shading = GfxShading::parse(&obj); + shading = GfxShading::parse(&obj + ); obj.free(); return shading; } @@ -425,7 +458,7 @@ GfxShading *GfxResources::lookupShading( return NULL; } -GBool GfxResources::lookupGState(char *name, Object *obj) { +GBool GfxResources::lookupGState(const char *name, Object *obj) { GfxResources *resPtr; for (resPtr = this; resPtr; resPtr = resPtr->next) { @@ -440,7 +473,7 @@ GBool GfxResources::lookupGState(char *n return gFalse; } -GBool GfxResources::lookupPropertiesNF(char *name, Object *obj) { +GBool GfxResources::lookupPropertiesNF(const char *name, Object *obj) { GfxResources *resPtr; for (resPtr = this; resPtr; resPtr = resPtr->next) { @@ -491,6 +524,7 @@ Gfx::Gfx(PDFDoc *docA, OutputDev *outA, markedContentStack = new GList(); ocState = gTrue; parser = NULL; + contentStreamStack = new GList(); abortCheckCbk = abortCheckCbkA; abortCheckCbkData = abortCheckCbkDataA; @@ -535,6 +569,7 @@ Gfx::Gfx(PDFDoc *docA, OutputDev *outA, markedContentStack = new GList(); ocState = gTrue; parser = NULL; + contentStreamStack = new GList(); abortCheckCbk = abortCheckCbkA; abortCheckCbkData = abortCheckCbkDataA; @@ -563,41 +598,99 @@ Gfx::~Gfx() { popResources(); } deleteGList(markedContentStack, GfxMarkedContent); + delete contentStreamStack; } -void Gfx::display(Object *obj, GBool topLevel) { - Object obj2; +void Gfx::display(Object *objRef, GBool topLevel) { + Object obj1, obj2; int i; - if (obj->isArray()) { - for (i = 0; i < obj->arrayGetLength(); ++i) { - obj->arrayGet(i, &obj2); + objRef->fetch(xref, &obj1); + if (obj1.isArray()) { + for (i = 0; i < obj1.arrayGetLength(); ++i) { + obj1.arrayGetNF(i, &obj2); + if (checkForContentStreamLoop(&obj2)) { + obj2.free(); + obj1.free(); + return; + } + obj2.free(); + } + for (i = 0; i < obj1.arrayGetLength(); ++i) { + obj1.arrayGet(i, &obj2); if (!obj2.isStream()) { - error(errSyntaxError, -1, "Weird page contents"); + error(errSyntaxError, -1, "Invalid object type for content stream"); obj2.free(); + obj1.free(); return; } obj2.free(); } - } else if (!obj->isStream()) { - error(errSyntaxError, -1, "Weird page contents"); + contentStreamStack->append(&obj1); + } else if (obj1.isStream()) { + if (checkForContentStreamLoop(objRef)) { + obj1.free(); + return; + } + contentStreamStack->append(objRef); + } else { + error(errSyntaxError, -1, "Invalid object type for content stream"); + obj1.free(); return; } - parser = new Parser(xref, new Lexer(xref, obj), gFalse); + parser = new Parser(xref, new Lexer(xref, &obj1), gFalse); go(topLevel); delete parser; parser = NULL; + contentStreamStack->del(contentStreamStack->getLength() - 1); + obj1.free(); +} + +// If is already on contentStreamStack, i.e., if there is a loop +// in the content streams, report an error, and return true. +GBool Gfx::checkForContentStreamLoop(Object *ref) { + Object *objPtr; + Object obj1; + int i, j; + + if (ref->isRef()) { + for (i = 0; i < contentStreamStack->getLength(); ++i) { + objPtr = (Object *)contentStreamStack->get(i); + if (objPtr->isRef()) { + if (ref->getRefNum() == objPtr->getRefNum() && + ref->getRefGen() == objPtr->getRefGen()) { + error(errSyntaxError, -1, "Loop in content streams"); + return gTrue; + } + } else if (objPtr->isArray()) { + for (j = 0; j < objPtr->arrayGetLength(); ++j) { + objPtr->arrayGetNF(j, &obj1); + if (obj1.isRef()) { + if (ref->getRefNum() == obj1.getRefNum() && + ref->getRefGen() == obj1.getRefGen()) { + error(errSyntaxError, -1, "Loop in content streams"); + obj1.free(); + return gTrue; + } + } + obj1.free(); + } + } + } + } + return gFalse; } void Gfx::go(GBool topLevel) { Object obj; Object args[maxArgs]; int numArgs, i; - int lastAbortCheck; + int lastAbortCheck, errCount; // scan a sequence of objects updateLevel = 1; // make sure even empty pages trigger a call to dump() lastAbortCheck = 0; + errCount = 0; numArgs = 0; parser->getObj(&obj); while (!obj.isEOF()) { @@ -613,7 +706,9 @@ void Gfx::go(GBool topLevel) { printf("\n"); fflush(stdout); } - execOp(&obj, args, numArgs); + if (!execOp(&obj, args, numArgs)) { + ++errCount; + } obj.free(); for (i = 0; i < numArgs; ++i) args[i].free(); @@ -635,6 +730,13 @@ void Gfx::go(GBool topLevel) { } } + // check for too many errors + if (errCount > contentStreamErrorLimit) { + error(errSyntaxError, -1, + "Too many errors - giving up on this content stream"); + break; + } + // got an argument - save it } else if (numArgs < maxArgs) { args[numArgs++] = obj; @@ -678,7 +780,8 @@ void Gfx::go(GBool topLevel) { } } -void Gfx::execOp(Object *cmd, Object args[], int numArgs) { +// Returns true if successful, false on error. +GBool Gfx::execOp(Object *cmd, Object args[], int numArgs) { Operator *op; char *name; Object *argPtr; @@ -687,9 +790,11 @@ void Gfx::execOp(Object *cmd, Object arg // find operator name = cmd->getCmd(); if (!(op = findOp(name))) { - if (ignoreUndef == 0) - error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name); - return; + if (ignoreUndef > 0) { + return gTrue; + } + error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name); + return gFalse; } // type check args @@ -698,7 +803,7 @@ void Gfx::execOp(Object *cmd, Object arg if (numArgs < op->numArgs) { error(errSyntaxError, getPos(), "Too few ({0:d}) args to '{1:s}' operator", numArgs, name); - return; + return gFalse; } if (numArgs > op->numArgs) { #if 0 @@ -713,7 +818,7 @@ void Gfx::execOp(Object *cmd, Object arg error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", numArgs, name); - return; + return gFalse; } } for (i = 0; i < numArgs; ++i) { @@ -721,12 +826,14 @@ void Gfx::execOp(Object *cmd, Object arg error(errSyntaxError, getPos(), "Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})", i, name, argPtr[i].getTypeName()); - return; + return gFalse; } } // do it (this->*op->func)(argPtr, numArgs); + + return gTrue; } Operator *Gfx::findOp(char *name) { @@ -766,7 +873,7 @@ GBool Gfx::checkArg(Object *arg, TchkTyp return gFalse; } -int Gfx::getPos() { +GFileOffset Gfx::getPos() { return parser ? parser->getPos() : -1; } @@ -840,7 +947,7 @@ void Gfx::opSetLineWidth(Object args[], } void Gfx::opSetExtGState(Object args[], int numArgs) { - Object obj1, obj2, obj3, obj4, obj5; + Object obj1, obj2, obj3, objRef3, obj4, obj5; Object args2[2]; GfxBlendMode mode; GBool haveFillOP; @@ -895,22 +1002,21 @@ void Gfx::opSetExtGState(Object args[], args2[1].free(); } obj2.free(); -#if 0 //~ need to add a new version of GfxResources::lookupFont() that - //~ takes an indirect ref instead of a name + if (obj1.dictLookup("FL", &obj2)->isNum()) { + opSetFlat(&obj2, 1); + } + obj2.free(); + + // font if (obj1.dictLookup("Font", &obj2)->isArray() && obj2.arrayGetLength() == 2) { - obj2.arrayGet(0, &args2[0]); - obj2.arrayGet(1, &args2[1]); - if (args2[0].isDict() && args2[1].isNum()) { - opSetFont(args2, 2); + obj2.arrayGetNF(0, &obj3); + obj2.arrayGetNF(1, &obj4); + if (obj3.isRef() && obj4.isNum()) { + doSetFont(res->lookupFontByRef(obj3.getRef()), obj4.getNum()); } - args2[0].free(); - args2[1].free(); - } - obj2.free(); -#endif - if (obj1.dictLookup("FL", &obj2)->isNum()) { - opSetFlat(&obj2, 1); + obj3.free(); + obj4.free(); } obj2.free(); @@ -1045,7 +1151,8 @@ void Gfx::opSetExtGState(Object args[], blendingColorSpace = NULL; isolated = knockout = gFalse; if (!obj4.dictLookup("CS", &obj5)->isNull()) { - blendingColorSpace = GfxColorSpace::parse(&obj5); + blendingColorSpace = GfxColorSpace::parse(&obj5 + ); } obj5.free(); if (obj4.dictLookup("I", &obj5)->isBool()) { @@ -1066,8 +1173,10 @@ void Gfx::opSetExtGState(Object args[], } } } - doSoftMask(&obj3, alpha, blendingColorSpace, + obj2.dictLookupNF("G", &objRef3); + doSoftMask(&obj3, &objRef3, alpha, blendingColorSpace, isolated, knockout, funcs[0], &backdropColor); + objRef3.free(); if (funcs[0]) { delete funcs[0]; } @@ -1090,7 +1199,7 @@ void Gfx::opSetExtGState(Object args[], obj1.free(); } -void Gfx::doSoftMask(Object *str, GBool alpha, +void Gfx::doSoftMask(Object *str, Object *strRef, GBool alpha, GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, Function *transferFunc, GfxColor *backdropColor) { @@ -1149,7 +1258,7 @@ void Gfx::doSoftMask(Object *str, GBool // draw it ++formDepth; - drawForm(str, resDict, m, bbox, gTrue, gTrue, + drawForm(strRef, resDict, m, bbox, gTrue, gTrue, blendingColorSpace, isolated, knockout, alpha, transferFunc, backdropColor); --formDepth; @@ -1171,7 +1280,7 @@ void Gfx::opSetFillGray(Object args[], i GfxColor color; state->setFillPattern(NULL); - state->setFillColorSpace(new GfxDeviceGrayColorSpace()); + state->setFillColorSpace(GfxColorSpace::create(csDeviceGray)); out->updateFillColorSpace(state); color.c[0] = dblToCol(args[0].getNum()); state->setFillColor(&color); @@ -1182,7 +1291,7 @@ void Gfx::opSetStrokeGray(Object args[], GfxColor color; state->setStrokePattern(NULL); - state->setStrokeColorSpace(new GfxDeviceGrayColorSpace()); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray)); out->updateStrokeColorSpace(state); color.c[0] = dblToCol(args[0].getNum()); state->setStrokeColor(&color); @@ -1194,7 +1303,7 @@ void Gfx::opSetFillCMYKColor(Object args int i; state->setFillPattern(NULL); - state->setFillColorSpace(new GfxDeviceCMYKColorSpace()); + state->setFillColorSpace(GfxColorSpace::create(csDeviceCMYK)); out->updateFillColorSpace(state); for (i = 0; i < 4; ++i) { color.c[i] = dblToCol(args[i].getNum()); @@ -1208,7 +1317,7 @@ void Gfx::opSetStrokeCMYKColor(Object ar int i; state->setStrokePattern(NULL); - state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace()); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceCMYK)); out->updateStrokeColorSpace(state); for (i = 0; i < 4; ++i) { color.c[i] = dblToCol(args[i].getNum()); @@ -1222,7 +1331,7 @@ void Gfx::opSetFillRGBColor(Object args[ int i; state->setFillPattern(NULL); - state->setFillColorSpace(new GfxDeviceRGBColorSpace()); + state->setFillColorSpace(GfxColorSpace::create(csDeviceRGB)); out->updateFillColorSpace(state); for (i = 0; i < 3; ++i) { color.c[i] = dblToCol(args[i].getNum()); @@ -1236,7 +1345,7 @@ void Gfx::opSetStrokeRGBColor(Object arg int i; state->setStrokePattern(NULL); - state->setStrokeColorSpace(new GfxDeviceRGBColorSpace()); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceRGB)); out->updateStrokeColorSpace(state); for (i = 0; i < 3; ++i) { color.c[i] = dblToCol(args[i].getNum()); @@ -1253,9 +1362,11 @@ void Gfx::opSetFillColorSpace(Object arg state->setFillPattern(NULL); res->lookupColorSpace(args[0].getName(), &obj); if (obj.isNull()) { - colorSpace = GfxColorSpace::parse(&args[0]); + colorSpace = GfxColorSpace::parse(&args[0] + ); } else { - colorSpace = GfxColorSpace::parse(&obj); + colorSpace = GfxColorSpace::parse(&obj + ); } obj.free(); if (colorSpace) { @@ -1277,9 +1388,11 @@ void Gfx::opSetStrokeColorSpace(Object a state->setStrokePattern(NULL); res->lookupColorSpace(args[0].getName(), &obj); if (obj.isNull()) { - colorSpace = GfxColorSpace::parse(&args[0]); + colorSpace = GfxColorSpace::parse(&args[0] + ); } else { - colorSpace = GfxColorSpace::parse(&obj); + colorSpace = GfxColorSpace::parse(&obj + ); } obj.free(); if (colorSpace) { @@ -1350,7 +1463,8 @@ void Gfx::opSetFillColorN(Object args[], out->updateFillColor(state); } if (args[numArgs-1].isName() && - (pattern = res->lookupPattern(args[numArgs-1].getName()))) { + (pattern = res->lookupPattern(args[numArgs-1].getName() + ))) { state->setFillPattern(pattern); } @@ -1395,7 +1509,8 @@ void Gfx::opSetStrokeColorN(Object args[ out->updateStrokeColor(state); } if (args[numArgs-1].isName() && - (pattern = res->lookupPattern(args[numArgs-1].getName()))) { + (pattern = res->lookupPattern(args[numArgs-1].getName() + ))) { state->setStrokePattern(pattern); } @@ -1751,11 +1866,11 @@ void Gfx::doPatternText() { } void Gfx::doPatternImageMask(Object *ref, Stream *str, int width, int height, - GBool invert, GBool inlineImg) { + GBool invert, GBool inlineImg, GBool interpolate) { saveState(); out->setSoftMaskFromImageMask(state, ref, str, - width, height, invert, inlineImg); + width, height, invert, inlineImg, interpolate); state->clearPath(); state->moveTo(0, 0); @@ -1773,11 +1888,11 @@ void Gfx::doTilingPatternFill(GfxTilingP GfxPatternColorSpace *patCS; GfxColorSpace *cs; GfxState *savedState; - double xMin, yMin, xMax, yMax, x, y, x1, y1; + double xMin, yMin, xMax, yMax, x, y, x1, y1, t; double cxMin, cyMin, cxMax, cyMax; int xi0, yi0, xi1, yi1, xi, yi; double *ctm, *btm, *ptm; - double m[6], ictm[6], m1[6], imb[6]; + double bbox[4], m[6], ictm[6], m1[6], imb[6]; double det; double xstep, ystep; int i; @@ -1791,7 +1906,12 @@ void Gfx::doTilingPatternFill(GfxTilingP btm = baseMatrix; ptm = tPat->getMatrix(); // iCTM = invert CTM - det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); + det = ctm[0] * ctm[3] - ctm[1] * ctm[2]; + if (fabs(det) < 0.000001) { + error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill"); + return; + } + det = 1 / det; ictm[0] = ctm[3] * det; ictm[1] = -ctm[1] * det; ictm[2] = -ctm[2] * det; @@ -1814,7 +1934,12 @@ void Gfx::doTilingPatternFill(GfxTilingP m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5]; // construct a (device space) -> (pattern space) transform matrix - det = 1 / (m1[0] * m1[3] - m1[1] * m1[2]); + det = m1[0] * m1[3] - m1[1] * m1[2]; + if (fabs(det) < 0.000001) { + error(errSyntaxError, getPos(), "Singular matrix in tiling pattern fill"); + return; + } + det = 1 / det; imb[0] = m1[3] * det; imb[1] = -m1[1] * det; imb[2] = -m1[2] * det; @@ -1839,9 +1964,9 @@ void Gfx::doTilingPatternFill(GfxTilingP out->updateFillColor(state); out->updateStrokeColor(state); } else { - state->setFillColorSpace(new GfxDeviceGrayColorSpace()); + state->setFillColorSpace(GfxColorSpace::create(csDeviceGray)); out->updateFillColorSpace(state); - state->setStrokeColorSpace(new GfxDeviceGrayColorSpace()); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray)); out->updateStrokeColorSpace(state); } if (!stroke) { @@ -1912,21 +2037,31 @@ void Gfx::doTilingPatternFill(GfxTilingP // draw the pattern //~ this should treat negative steps differently -- start at right/top //~ edge instead of left/bottom (?) + bbox[0] = tPat->getBBox()[0]; + bbox[1] = tPat->getBBox()[1]; + bbox[2] = tPat->getBBox()[2]; + bbox[3] = tPat->getBBox()[3]; + if (bbox[0] > bbox[2]) { + t = bbox[0]; bbox[0] = bbox[2]; bbox[2] = t; + } + if (bbox[1] > bbox[3]) { + t = bbox[1]; bbox[1] = bbox[3]; bbox[3] = t; + } xstep = fabs(tPat->getXStep()); ystep = fabs(tPat->getYStep()); - xi0 = (int)ceil((xMin - tPat->getBBox()[2]) / xstep); - xi1 = (int)floor((xMax - tPat->getBBox()[0]) / xstep) + 1; - yi0 = (int)ceil((yMin - tPat->getBBox()[3]) / ystep); - yi1 = (int)floor((yMax - tPat->getBBox()[1]) / ystep) + 1; + xi0 = (int)ceil((xMin - bbox[2]) / xstep); + xi1 = (int)floor((xMax - bbox[0]) / xstep) + 1; + yi0 = (int)ceil((yMin - bbox[3]) / ystep); + yi1 = (int)floor((yMax - bbox[1]) / ystep) + 1; for (i = 0; i < 4; ++i) { m1[i] = m[i]; } if (out->useTilingPatternFill()) { m1[4] = m[4]; m1[5] = m[5]; - out->tilingPatternFill(state, this, tPat->getContentStream(), + out->tilingPatternFill(state, this, tPat->getContentStreamRef(), tPat->getPaintType(), tPat->getResDict(), - m1, tPat->getBBox(), + m1, bbox, xi0, yi0, xi1, yi1, xstep, ystep); } else { for (yi = yi0; yi < yi1; ++yi) { @@ -1935,8 +2070,8 @@ void Gfx::doTilingPatternFill(GfxTilingP y = yi * ystep; m1[4] = x * m[0] + y * m[2] + m[4]; m1[5] = x * m[1] + y * m[3] + m[5]; - drawForm(tPat->getContentStream(), tPat->getResDict(), - m1, tPat->getBBox()); + drawForm(tPat->getContentStreamRef(), tPat->getResDict(), + m1, bbox); } } } @@ -1979,7 +2114,12 @@ void Gfx::doShadingPatternFill(GfxShadin btm = baseMatrix; ptm = sPat->getMatrix(); // iCTM = invert CTM - det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); + det = ctm[0] * ctm[3] - ctm[1] * ctm[2]; + if (fabs(det) < 0.000001) { + error(errSyntaxError, getPos(), "Singular matrix in shading pattern fill"); + return; + } + det = 1 / det; ictm[0] = ctm[3] * det; ictm[1] = -ctm[1] * det; ictm[2] = -ctm[2] * det; @@ -2074,11 +2214,16 @@ void Gfx::opShFill(Object args[], int nu GfxState *savedState; double xMin, yMin, xMax, yMax; + if (!out->needNonText()) { + return; + } + if (!ocState) { return; } - if (!(shading = res->lookupShading(args[0].getName()))) { + if (!(shading = res->lookupShading(args[0].getName() + ))) { return; } @@ -2487,7 +2632,7 @@ void Gfx::doRadialShFill(GfxRadialShadin GfxColor colorA, colorB; double xa, ya, xb, yb, ra, rb; double ta, tb, sa, sb; - double sz, sMin, sMax, h; + double sMin, sMax, h; double sLeft, sRight, sTop, sBottom, sZero, sDiag; GBool haveSLeft, haveSRight, haveSTop, haveSBottom, haveSZero; GBool haveSMin, haveSMax; @@ -2513,18 +2658,14 @@ void Gfx::doRadialShFill(GfxRadialShadin if (h == 0) { enclosed = gTrue; theta = 0; // make gcc happy - sz = 0; // make gcc happy } else if (r1 - r0 == 0) { enclosed = gFalse; theta = 0; - sz = 0; // make gcc happy - } else if (fabs(r1 - r0) >= h) { + } else if (fabs(r1 - r0) >= h - 0.0001) { enclosed = gTrue; theta = 0; // make gcc happy - sz = 0; // make gcc happy } else { enclosed = gFalse; - sz = -r0 / (r1 - r0); theta = asin((r1 - r0) / h); } if (enclosed) { @@ -2598,7 +2739,7 @@ void Gfx::doRadialShFill(GfxRadialShadin haveSMin = gTrue; } } - if (haveSZero && sZero < 0) { + if (haveSZero && sZero <= 0) { if (!haveSMin || sZero > sMin) { sMin = sZero; } @@ -2865,34 +3006,56 @@ void Gfx::doRadialShFill(GfxRadialShadin void Gfx::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) { double x0, y0, x1, y1, x2, y2; - GfxColor color0, color1, color2; + double color0[gfxColorMaxComps]; + double color1[gfxColorMaxComps]; + double color2[gfxColorMaxComps]; int i; for (i = 0; i < shading->getNTriangles(); ++i) { - shading->getTriangle(i, &x0, &y0, &color0, - &x1, &y1, &color1, - &x2, &y2, &color2); - gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2, - shading->getColorSpace()->getNComps(), 0); + shading->getTriangle(i, &x0, &y0, color0, + &x1, &y1, color1, + &x2, &y2, color2); + gouraudFillTriangle(x0, y0, color0, x1, y1, color1, x2, y2, color2, + shading, 0); } } -void Gfx::gouraudFillTriangle(double x0, double y0, GfxColor *color0, - double x1, double y1, GfxColor *color1, - double x2, double y2, GfxColor *color2, - int nComps, int depth) { +void Gfx::gouraudFillTriangle(double x0, double y0, double *color0, + double x1, double y1, double *color1, + double x2, double y2, double *color2, + GfxGouraudTriangleShading *shading, int depth) { + double dx0, dy0, dx1, dy1, dx2, dy2; double x01, y01, x12, y12, x20, y20; - GfxColor color01, color12, color20; - int i; - + double color01[gfxColorMaxComps]; + double color12[gfxColorMaxComps]; + double color20[gfxColorMaxComps]; + GfxColor c0, c1, c2; + int nComps, i; + + // recursion ends when: + // (1) color difference is smaller than gouraudColorDelta; or + // (2) triangles are smaller than 0.5 pixel (note that "device + // space" is 72dpi when generating PostScript); or + // (3) max recursion depth (gouraudMaxDepth) is hit. + nComps = shading->getColorSpace()->getNComps(); + shading->getColor(color0, &c0); + shading->getColor(color1, &c1); + shading->getColor(color2, &c2); for (i = 0; i < nComps; ++i) { - if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta || - abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) { + if (abs(c0.c[i] - c1.c[i]) > gouraudColorDelta || + abs(c1.c[i] - c2.c[i]) > gouraudColorDelta) { break; } } - if (i == nComps || depth == gouraudMaxDepth) { - state->setFillColor(color0); + state->transformDelta(x1 - x0, y1 - y0, &dx0, &dy0); + state->transformDelta(x2 - x1, y2 - y1, &dx1, &dy1); + state->transformDelta(x0 - x2, y0 - y2, &dx2, &dy2); + if (i == nComps || + depth == gouraudMaxDepth || + (fabs(dx0) < 0.5 && fabs(dy0) < 0.5 && + fabs(dx1) < 0.5 && fabs(dy1) < 0.5 && + fabs(dx2) < 0.5 && fabs(dy2) < 0.5)) { + state->setFillColor(&c0); out->updateFillColor(state); state->moveTo(x0, y0); state->lineTo(x1, y1); @@ -2907,21 +3070,19 @@ void Gfx::gouraudFillTriangle(double x0, y12 = 0.5 * (y1 + y2); x20 = 0.5 * (x2 + x0); y20 = 0.5 * (y2 + y0); - //~ if the shading has a Function, this should interpolate on the - //~ function parameter, not on the color components - for (i = 0; i < nComps; ++i) { - color01.c[i] = (color0->c[i] + color1->c[i]) / 2; - color12.c[i] = (color1->c[i] + color2->c[i]) / 2; - color20.c[i] = (color2->c[i] + color0->c[i]) / 2; - } - gouraudFillTriangle(x0, y0, color0, x01, y01, &color01, - x20, y20, &color20, nComps, depth + 1); - gouraudFillTriangle(x01, y01, &color01, x1, y1, color1, - x12, y12, &color12, nComps, depth + 1); - gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12, - x20, y20, &color20, nComps, depth + 1); - gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12, - x2, y2, color2, nComps, depth + 1); + for (i = 0; i < shading->getNComps(); ++i) { + color01[i] = 0.5 * (color0[i] + color1[i]); + color12[i] = 0.5 * (color1[i] + color2[i]); + color20[i] = 0.5 * (color2[i] + color0[i]); + } + gouraudFillTriangle(x0, y0, color0, x01, y01, color01, + x20, y20, color20, shading, depth + 1); + gouraudFillTriangle(x01, y01, color01, x1, y1, color1, + x12, y12, color12, shading, depth + 1); + gouraudFillTriangle(x01, y01, color01, x12, y12, color12, + x20, y20, color20, shading, depth + 1); + gouraudFillTriangle(x20, y20, color20, x12, y12, color12, + x2, y2, color2, shading, depth + 1); } } @@ -2938,31 +3099,32 @@ void Gfx::doPatchMeshShFill(GfxPatchMesh start = 0; } for (i = 0; i < shading->getNPatches(); ++i) { - fillPatch(shading->getPatch(i), shading->getColorSpace()->getNComps(), - start); + fillPatch(shading->getPatch(i), shading, start); } } -void Gfx::fillPatch(GfxPatch *patch, int nComps, int depth) { +void Gfx::fillPatch(GfxPatch *patch, GfxPatchMeshShading *shading, int depth) { GfxPatch patch00, patch01, patch10, patch11; + GfxColor c00, c01, c10, c11; double xx[4][8], yy[4][8]; double xxm, yym; - int i; + int nComps, i; + nComps = shading->getColorSpace()->getNComps(); + shading->getColor(patch->color[0][0], &c00); + shading->getColor(patch->color[0][1], &c01); + shading->getColor(patch->color[1][0], &c10); + shading->getColor(patch->color[1][1], &c11); for (i = 0; i < nComps; ++i) { - if (abs(patch->color[0][0].c[i] - patch->color[0][1].c[i]) - > patchColorDelta || - abs(patch->color[0][1].c[i] - patch->color[1][1].c[i]) - > patchColorDelta || - abs(patch->color[1][1].c[i] - patch->color[1][0].c[i]) - > patchColorDelta || - abs(patch->color[1][0].c[i] - patch->color[0][0].c[i]) - > patchColorDelta) { + 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 || depth == patchMaxDepth) { - state->setFillColor(&patch->color[0][0]); + state->setFillColor(&c00); out->updateFillColor(state); state->moveTo(patch->x[0][0], patch->y[0][0]); state->curveTo(patch->x[0][1], patch->y[0][1], @@ -3039,35 +3201,33 @@ void Gfx::fillPatch(GfxPatch *patch, int patch11.x[3][i-4] = xx[3][i]; patch11.y[3][i-4] = yy[3][i]; } - //~ if the shading has a Function, this should interpolate on the - //~ function parameter, not on the color components - for (i = 0; i < nComps; ++i) { - patch00.color[0][0].c[i] = patch->color[0][0].c[i]; - patch00.color[0][1].c[i] = (patch->color[0][0].c[i] + - patch->color[0][1].c[i]) / 2; - patch01.color[0][0].c[i] = patch00.color[0][1].c[i]; - patch01.color[0][1].c[i] = patch->color[0][1].c[i]; - patch01.color[1][1].c[i] = (patch->color[0][1].c[i] + - patch->color[1][1].c[i]) / 2; - patch11.color[0][1].c[i] = patch01.color[1][1].c[i]; - patch11.color[1][1].c[i] = patch->color[1][1].c[i]; - patch11.color[1][0].c[i] = (patch->color[1][1].c[i] + - patch->color[1][0].c[i]) / 2; - patch10.color[1][1].c[i] = patch11.color[1][0].c[i]; - patch10.color[1][0].c[i] = patch->color[1][0].c[i]; - patch10.color[0][0].c[i] = (patch->color[1][0].c[i] + - patch->color[0][0].c[i]) / 2; - patch00.color[1][0].c[i] = patch10.color[0][0].c[i]; - patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] + - patch01.color[1][1].c[i]) / 2; - patch01.color[1][0].c[i] = patch00.color[1][1].c[i]; - patch11.color[0][0].c[i] = patch00.color[1][1].c[i]; - patch10.color[0][1].c[i] = patch00.color[1][1].c[i]; - } - fillPatch(&patch00, nComps, depth + 1); - fillPatch(&patch10, nComps, depth + 1); - fillPatch(&patch01, nComps, depth + 1); - fillPatch(&patch11, nComps, depth + 1); + 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]); + 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(&patch00, shading, depth + 1); + fillPatch(&patch10, shading, depth + 1); + fillPatch(&patch01, shading, depth + 1); + fillPatch(&patch11, shading, depth + 1); } } @@ -3123,19 +3283,22 @@ void Gfx::opSetCharSpacing(Object args[] } void Gfx::opSetFont(Object args[], int numArgs) { - GfxFont *font; + doSetFont(res->lookupFont(args[0].getName()), args[1].getNum()); +} - if (!(font = res->lookupFont(args[0].getName()))) { +void Gfx::doSetFont(GfxFont *font, double size) { + if (!font) { + state->setFont(NULL, 0); return; } if (printCommands) { printf(" font: tag=%s name='%s' %g\n", font->getTag()->getCString(), font->getName() ? font->getName()->getCString() : "???", - args[1].getNum()); + size); fflush(stdout); } - state->setFont(font, args[1].getNum()); + state->setFont(font, size); fontChanged = gTrue; } @@ -3343,7 +3506,7 @@ void Gfx::doShowText(GString *s) { double x0, y0, x1, y1; double oldCTM[6], newCTM[6]; double *mat; - Object charProc; + Object charProcRef, charProc; Dict *resDict; Parser *oldParser; GfxState *savedState; @@ -3427,12 +3590,13 @@ void Gfx::doShowText(GString *s) { state->transformDelta(dx, dy, &ddx, &ddy); if (!out->beginType3Char(state, curX + riseX, curY + riseY, ddx, ddy, code, u, uLen)) { - ((Gfx8BitFont *)font)->getCharProc(code, &charProc); + ((Gfx8BitFont *)font)->getCharProcNF(code, &charProcRef); + charProcRef.fetch(xref, &charProc); if ((resDict = ((Gfx8BitFont *)font)->getResources())) { pushResources(resDict); } if (charProc.isStream()) { - display(&charProc, gFalse); + display(&charProcRef, gFalse); } else { error(errSyntaxError, getPos(), "Missing or bad Type3 CharProc entry"); @@ -3442,6 +3606,7 @@ void Gfx::doShowText(GString *s) { popResources(); } charProc.free(); + charProcRef.free(); } restoreStateStack(savedState); curX += tdx; @@ -3592,44 +3757,53 @@ void Gfx::opXObject(Object args[], int n obj1.free(); return; } +#if USE_EXCEPTIONS + try { +#endif #if OPI_SUPPORT - obj1.streamGetDict()->lookup("OPI", &opiDict); - if (opiDict.isDict()) { - out->opiBegin(state, opiDict.getDict()); - } + obj1.streamGetDict()->lookup("OPI", &opiDict); + if (opiDict.isDict()) { + out->opiBegin(state, opiDict.getDict()); + } #endif - obj1.streamGetDict()->lookup("Subtype", &obj2); - if (obj2.isName("Image")) { - if (out->needNonText()) { + obj1.streamGetDict()->lookup("Subtype", &obj2); + if (obj2.isName("Image")) { + if (out->needNonText()) { + res->lookupXObjectNF(name, &refObj); + doImage(&refObj, obj1.getStream(), gFalse); + refObj.free(); + } + } else if (obj2.isName("Form")) { res->lookupXObjectNF(name, &refObj); - doImage(&refObj, obj1.getStream(), gFalse); + if (out->useDrawForm() && refObj.isRef()) { + out->drawForm(refObj.getRef()); + } else { + doForm(&refObj, &obj1); + } refObj.free(); - } - } else if (obj2.isName("Form")) { - res->lookupXObjectNF(name, &refObj); - if (out->useDrawForm() && refObj.isRef()) { - out->drawForm(refObj.getRef()); + } else if (obj2.isName("PS")) { + obj1.streamGetDict()->lookup("Level1", &obj3); + out->psXObject(obj1.getStream(), + obj3.isStream() ? obj3.getStream() : (Stream *)NULL); + } else if (obj2.isName()) { + error(errSyntaxError, getPos(), + "Unknown XObject subtype '{0:s}'", obj2.getName()); } else { - doForm(&obj1); + error(errSyntaxError, getPos(), + "XObject subtype is missing or wrong type"); } - refObj.free(); - } else if (obj2.isName("PS")) { - obj1.streamGetDict()->lookup("Level1", &obj3); - out->psXObject(obj1.getStream(), - obj3.isStream() ? obj3.getStream() : (Stream *)NULL); - } else if (obj2.isName()) { - error(errSyntaxError, getPos(), - "Unknown XObject subtype '{0:s}'", obj2.getName()); - } else { - error(errSyntaxError, getPos(), - "XObject subtype is missing or wrong type"); - } - obj2.free(); + obj2.free(); #if OPI_SUPPORT - if (opiDict.isDict()) { - out->opiEnd(state, opiDict.getDict()); + if (opiDict.isDict()) { + out->opiEnd(state, opiDict.getDict()); + } + opiDict.free(); +#endif +#if USE_EXCEPTIONS + } catch (GMemException e) { + obj1.free(); + throw; } - opiDict.free(); #endif obj1.free(); } @@ -3649,6 +3823,7 @@ void Gfx::doImage(Object *ref, Stream *s int maskWidth, maskHeight; GBool maskInvert; Stream *maskStr; + GBool interpolate; Object obj1, obj2; int i, n; @@ -3710,6 +3885,9 @@ void Gfx::doImage(Object *ref, Stream *s } if (obj1.isInt()) { bits = obj1.getInt(); + if (bits < 1 || bits > 16) { + goto err2; + } } else if (mask) { bits = 1; } else { @@ -3718,6 +3896,15 @@ void Gfx::doImage(Object *ref, Stream *s obj1.free(); } + // interpolate flag + dict->lookup("Interpolate", &obj1); + if (obj1.isNull()) { + obj1.free(); + dict->lookup("I", &obj1); + } + interpolate = obj1.isBool() && obj1.getBool(); + obj1.free(); + // display a mask if (mask) { @@ -3751,9 +3938,11 @@ void Gfx::doImage(Object *ref, Stream *s // draw it } else { if (state->getFillColorSpace()->getMode() == csPattern) { - doPatternImageMask(ref, str, width, height, invert, inlineImg); + doPatternImageMask(ref, str, width, height, invert, inlineImg, + interpolate); } else { - out->drawImageMask(state, ref, str, width, height, invert, inlineImg); + out->drawImageMask(state, ref, str, width, height, invert, inlineImg, + interpolate); } } @@ -3775,13 +3964,14 @@ void Gfx::doImage(Object *ref, Stream *s } } if (!obj1.isNull()) { - colorSpace = GfxColorSpace::parse(&obj1); + colorSpace = GfxColorSpace::parse(&obj1 + ); } else if (csMode == streamCSDeviceGray) { - colorSpace = new GfxDeviceGrayColorSpace(); + colorSpace = GfxColorSpace::create(csDeviceGray); } else if (csMode == streamCSDeviceRGB) { - colorSpace = new GfxDeviceRGBColorSpace(); + colorSpace = GfxColorSpace::create(csDeviceRGB); } else if (csMode == streamCSDeviceCMYK) { - colorSpace = new GfxDeviceCMYKColorSpace(); + colorSpace = GfxColorSpace::create(csDeviceCMYK); } else { colorSpace = NULL; } @@ -3860,7 +4050,8 @@ void Gfx::doImage(Object *ref, Stream *s obj2.free(); } } - maskColorSpace = GfxColorSpace::parse(&obj1); + maskColorSpace = GfxColorSpace::parse(&obj1 + ); obj1.free(); if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) { goto err1; @@ -3977,14 +4168,17 @@ void Gfx::doImage(Object *ref, Stream *s } else { if (haveSoftMask) { out->drawSoftMaskedImage(state, ref, str, width, height, colorMap, - maskStr, maskWidth, maskHeight, maskColorMap); + maskStr, maskWidth, maskHeight, maskColorMap, + interpolate); delete maskColorMap; } else if (haveExplicitMask) { out->drawMaskedImage(state, ref, str, width, height, colorMap, - maskStr, maskWidth, maskHeight, maskInvert); + maskStr, maskWidth, maskHeight, maskInvert, + interpolate); } else { out->drawImage(state, ref, str, width, height, colorMap, - haveColorKeyMask ? maskColors : (int *)NULL, inlineImg); + haveColorKeyMask ? maskColors : (int *)NULL, inlineImg, + interpolate); } } @@ -4006,7 +4200,7 @@ void Gfx::doImage(Object *ref, Stream *s error(errSyntaxError, getPos(), "Bad image parameters"); } -void Gfx::doForm(Object *str) { +void Gfx::doForm(Object *strRef, Object *str) { Dict *dict; GBool transpGroup, isolated, knockout; GfxColorSpace *blendingColorSpace; @@ -4087,7 +4281,8 @@ void Gfx::doForm(Object *str) { if (obj1.dictLookup("S", &obj2)->isName("Transparency")) { transpGroup = gTrue; if (!obj1.dictLookup("CS", &obj3)->isNull()) { - blendingColorSpace = GfxColorSpace::parse(&obj3); + blendingColorSpace = GfxColorSpace::parse(&obj3 + ); } obj3.free(); if (obj1.dictLookup("I", &obj3)->isBool()) { @@ -4105,7 +4300,7 @@ void Gfx::doForm(Object *str) { // draw it ++formDepth; - drawForm(str, resDict, m, bbox, + drawForm(strRef, resDict, m, bbox, transpGroup, gFalse, blendingColorSpace, isolated, knockout); --formDepth; @@ -4117,7 +4312,8 @@ void Gfx::doForm(Object *str) { ocState = ocSaved; } -void Gfx::drawForm(Object *str, Dict *resDict, double *matrix, double *bbox, +void Gfx::drawForm(Object *strRef, Dict *resDict, + double *matrix, double *bbox, GBool transpGroup, GBool softMask, GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, @@ -4181,7 +4377,7 @@ void Gfx::drawForm(Object *str, Dict *re } // draw the form - display(str, gFalse); + display(strRef, gFalse); if (softMask || transpGroup) { out->endTransparencyGroup(state); @@ -4210,13 +4406,17 @@ void Gfx::drawForm(Object *str, Dict *re return; } +void Gfx::takeContentStreamStack(Gfx *oldGfx) { + contentStreamStack->append(oldGfx->contentStreamStack); +} + //------------------------------------------------------------------------ // in-line image operators //------------------------------------------------------------------------ void Gfx::opBeginImage(Object args[], int numArgs) { Stream *str; - int c1, c2; + int c1, c2, c3; // NB: this function is run even if ocState is false -- doImage() is // responsible for skipping over the inline image data @@ -4231,9 +4431,11 @@ void Gfx::opBeginImage(Object args[], in // skip 'EI' tag c1 = str->getUndecodedStream()->getChar(); c2 = str->getUndecodedStream()->getChar(); - while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) { + c3 = str->getUndecodedStream()->lookChar(); + while (!(c1 == 'E' && c2 == 'I' && Lexer::isSpace(c3)) && c3 != EOF) { c1 = c2; c2 = str->getUndecodedStream()->getChar(); + c3 = str->getUndecodedStream()->lookChar(); } delete str; } @@ -4328,9 +4530,7 @@ void Gfx::opBeginMarkedContent(Object ar GfxMarkedContent *mc; Object obj; GBool ocStateNew; - GString *s; - Unicode *u; - int uLen, i; + TextString *s; GfxMarkedContentKind mcKind; if (printCommands) { @@ -4351,24 +4551,9 @@ void Gfx::opBeginMarkedContent(Object ar mcKind = gfxMCOptionalContent; } else if (args[0].isName("Span") && numArgs == 2 && args[1].isDict()) { if (args[1].dictLookup("ActualText", &obj)->isString()) { - s = obj.getString(); - if ((s->getChar(0) & 0xff) == 0xfe && - (s->getChar(1) & 0xff) == 0xff) { - uLen = (s->getLength() - 2) / 2; - u = (Unicode *)gmallocn(uLen, sizeof(Unicode)); - for (i = 0; i < uLen; ++i) { - u[i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | - (s->getChar(3 + 2*i) & 0xff); - } - } else { - uLen = s->getLength(); - u = (Unicode *)gmallocn(uLen, sizeof(Unicode)); - for (i = 0; i < uLen; ++i) { - u[i] = pdfDocEncoding[s->getChar(i) & 0xff]; - } - } - out->beginActualText(state, u, uLen); - gfree(u); + s = new TextString(obj.getString()); + out->beginActualText(state, s->getUnicode(), s->getLength()); + delete s; mcKind = gfxMCActualText; } obj.free(); @@ -4416,14 +4601,14 @@ void Gfx::opMarkPoint(Object args[], int // misc //------------------------------------------------------------------------ -void Gfx::drawAnnot(Object *str, AnnotBorderStyle *borderStyle, +void Gfx::drawAnnot(Object *strRef, AnnotBorderStyle *borderStyle, double xMin, double yMin, double xMax, double yMax) { Dict *dict, *resDict; - Object matrixObj, bboxObj, resObj, obj1; + Object str, matrixObj, bboxObj, resObj, obj1; double formXMin, formYMin, formXMax, formYMax; double x, y, sx, sy, tx, ty; double m[6], bbox[4]; - double r, g, b; + double *borderColor; GfxColor color; double *dash, *dash2; int dashLength; @@ -4439,16 +4624,18 @@ void Gfx::drawAnnot(Object *str, AnnotBo } // draw the appearance stream (if there is one) - if (str->isStream()) { + strRef->fetch(xref, &str); + if (str.isStream()) { // get stream dict - dict = str->streamGetDict(); + dict = str.streamGetDict(); // get the form bounding box dict->lookup("BBox", &bboxObj); if (!bboxObj.isArray()) { - bboxObj.free(); error(errSyntaxError, getPos(), "Bad form bounding box"); + bboxObj.free(); + str.free(); return; } for (i = 0; i < 4; ++i) { @@ -4548,22 +4735,43 @@ void Gfx::drawAnnot(Object *str, AnnotBo resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL; // draw it - drawForm(str, resDict, m, bbox); + drawForm(strRef, resDict, m, bbox); resObj.free(); } + str.free(); // draw the border - if (borderStyle && borderStyle->getWidth() > 0) { - if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) { - state->setStrokePattern(NULL); - state->setStrokeColorSpace(new GfxDeviceRGBColorSpace()); - out->updateStrokeColorSpace(state); - } - borderStyle->getColor(&r, &g, &b); - color.c[0] = dblToCol(r); - color.c[1] = dblToCol(g); - color.c[2] = dblToCol(b); + if (borderStyle && borderStyle->getWidth() > 0 && + borderStyle->getNumColorComps() > 0) { + borderColor = borderStyle->getColor(); + switch (borderStyle->getNumColorComps()) { + case 1: + if (state->getStrokeColorSpace()->getMode() != csDeviceGray) { + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceGray)); + out->updateStrokeColorSpace(state); + } + break; + case 3: + if (state->getStrokeColorSpace()->getMode() != csDeviceRGB) { + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceRGB)); + out->updateStrokeColorSpace(state); + } + break; + case 4: + if (state->getStrokeColorSpace()->getMode() != csDeviceCMYK) { + state->setStrokePattern(NULL); + state->setStrokeColorSpace(GfxColorSpace::create(csDeviceCMYK)); + out->updateStrokeColorSpace(state); + } + break; + } + color.c[0] = dblToCol(borderColor[0]); + color.c[1] = dblToCol(borderColor[1]); + color.c[2] = dblToCol(borderColor[2]); + color.c[3] = dblToCol(borderColor[3]); state->setStrokeColor(&color); out->updateStrokeColor(state); state->setLineWidth(borderStyle->getWidth()); diff -uNrp xpdf-3.03/xpdf/GfxFont.cc xpdf-3.04/xpdf/GfxFont.cc --- xpdf-3.03/xpdf/GfxFont.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/GfxFont.cc 2014-05-28 20:50:50.000000000 +0200 @@ -146,6 +146,7 @@ static int readFromStream(void *data) { GfxFontLoc::GfxFontLoc() { path = NULL; fontNum = 0; + oblique = 0; encoding = NULL; substIdx = -1; } @@ -175,6 +176,8 @@ GfxFont *GfxFont::makeFont(XRef *xref, c fontDict->lookup("BaseFont", &obj1); if (obj1.isName()) { nameA = new GString(obj1.getName()); + } else if (obj1.isString()) { + nameA = obj1.getString()->copy(); } obj1.free(); @@ -451,7 +454,7 @@ void GfxFont::readFontDescriptor(XRef *x } // some broken font descriptors set ascent and descent to 0; // others set it to ridiculous values (e.g., 32768) - if (t != 0 && t < 3) { + if (t != 0 && t < 1.9) { ascent = t; } } @@ -464,7 +467,7 @@ void GfxFont::readFontDescriptor(XRef *x t = -t; } // some broken font descriptors set ascent and descent to 0 - if (t != 0 && t > -3) { + if (t != 0 && t > -1.9) { descent = t; } } @@ -489,7 +492,8 @@ CharCodeToUnicode *GfxFont::readToUnicod CharCodeToUnicode *ctu) { GString *buf; Object obj1; - int c; + char buf2[4096]; + int n; if (!fontDict->lookup("ToUnicode", &obj1)->isStream()) { obj1.free(); @@ -497,8 +501,8 @@ CharCodeToUnicode *GfxFont::readToUnicod } buf = new GString(); obj1.streamReset(); - while ((c = obj1.streamGetChar()) != EOF) { - buf->append(c); + while ((n = obj1.streamGetBlock(buf2, sizeof(buf2))) > 0) { + buf->append(buf2, n); } obj1.streamClose(); obj1.free(); @@ -518,6 +522,7 @@ GfxFontLoc *GfxFont::locateFont(XRef *xr PSFontParam16 *psFont16; Object refObj, embFontObj; int substIdx, fontNum; + double oblique; GBool embed; if (type == fontType3) { @@ -570,7 +575,7 @@ GfxFontLoc *GfxFont::locateFont(XRef *xr } //----- PS passthrough - if (ps && !isCIDFont() && globalParams->getPSFontPassthrough()) { + if (ps && name && !isCIDFont() && globalParams->getPSFontPassthrough()) { fontLoc = new GfxFontLoc(); fontLoc->locType = gfxFontLocResident; fontLoc->fontType = fontType1; @@ -578,6 +583,13 @@ GfxFontLoc *GfxFont::locateFont(XRef *xr return fontLoc; } + //----- external font file (fontFile, fontDir) + if (name && (path = globalParams->findFontFile(name))) { + if ((fontLoc = getExternalFont(path, 0, 0, isCIDFont()))) { + return fontLoc; + } + } + //----- PS resident Base-14 font if (ps && !isCIDFont() && ((Gfx8BitFont *)this)->base14) { fontLoc = new GfxFontLoc(); @@ -587,28 +599,19 @@ GfxFontLoc *GfxFont::locateFont(XRef *xr return fontLoc; } - //----- external font file (fontFile, fontDir) - if ((path = globalParams->findFontFile(name))) { - if ((fontLoc = getExternalFont(path, isCIDFont()))) { - return fontLoc; - } - } - //----- external font file for Base-14 font if (!ps && !isCIDFont() && ((Gfx8BitFont *)this)->base14) { base14Name = new GString(((Gfx8BitFont *)this)->base14->base14Name); - if ((path = globalParams->findFontFile(base14Name))) { - if ((fontLoc = getExternalFont(path, gFalse))) { - delete base14Name; - return fontLoc; - } - } + path = globalParams->findBase14FontFile(base14Name, &fontNum, &oblique); delete base14Name; + if (path && (fontLoc = getExternalFont(path, fontNum, oblique, gFalse))) { + return fontLoc; + } } //----- system font - if ((path = globalParams->findSystemFontFile(name, &sysFontType, - &fontNum))) { + if (name && (path = globalParams->findSystemFontFile(name, &sysFontType, + &fontNum))) { if (isCIDFont()) { if (sysFontType == sysFontTTF || sysFontType == sysFontTTC) { fontLoc = new GfxFontLoc(); @@ -624,13 +627,13 @@ GfxFontLoc *GfxFont::locateFont(XRef *xr fontLoc->locType = gfxFontLocExternal; fontLoc->fontType = fontTrueType; fontLoc->path = path; + fontLoc->fontNum = fontNum; return fontLoc; } else if (sysFontType == sysFontPFA || sysFontType == sysFontPFB) { fontLoc = new GfxFontLoc(); fontLoc->locType = gfxFontLocExternal; fontLoc->fontType = fontType1; fontLoc->path = path; - fontLoc->fontNum = fontNum; return fontLoc; } } @@ -641,7 +644,7 @@ GfxFontLoc *GfxFont::locateFont(XRef *xr //----- 8-bit PS resident font if (ps) { - if ((path = globalParams->getPSResidentFont(name))) { + if (name && (path = globalParams->getPSResidentFont(name))) { fontLoc = new GfxFontLoc(); fontLoc->locType = gfxFontLocResident; fontLoc->fontType = fontType1; @@ -675,10 +678,10 @@ GfxFontLoc *GfxFont::locateFont(XRef *xr fontLoc->substIdx = substIdx; return fontLoc; } else { - path = globalParams->findFontFile(substName); + path = globalParams->findBase14FontFile(substName, &fontNum, &oblique); delete substName; if (path) { - if ((fontLoc = getExternalFont(path, gFalse))) { + if ((fontLoc = getExternalFont(path, fontNum, oblique, gFalse))) { error(errSyntaxWarning, -1, "Substituting font '{0:s}' for '{1:t}'", base14SubstFonts[substIdx], name); fontLoc->substIdx = substIdx; @@ -692,7 +695,7 @@ GfxFontLoc *GfxFont::locateFont(XRef *xr } //----- 16-bit PS resident font - if (ps && ((psFont16 = globalParams->getPSResidentFont16( + if (ps && name && ((psFont16 = globalParams->getPSResidentFont16( name, ((GfxCIDFont *)this)->getWMode())))) { fontLoc = new GfxFontLoc(); @@ -720,7 +723,7 @@ GfxFontLoc *GfxFont::locateFont(XRef *xr //----- CID font substitution if ((path = globalParams->findCCFontFile( ((GfxCIDFont *)this)->getCollection()))) { - if ((fontLoc = getExternalFont(path, gTrue))) { + if ((fontLoc = getExternalFont(path, 0, 0, gTrue))) { error(errSyntaxWarning, -1, "Substituting font '{0:t}' for '{1:t}'", fontLoc->path, name); return fontLoc; @@ -733,15 +736,18 @@ GfxFontLoc *GfxFont::locateFont(XRef *xr GfxFontLoc *GfxFont::locateBase14Font(GString *base14Name) { GString *path; + int fontNum; + double oblique; - path = globalParams->findFontFile(base14Name); + path = globalParams->findBase14FontFile(base14Name, &fontNum, &oblique); if (!path) { return NULL; } - return getExternalFont(path, gFalse); + return getExternalFont(path, fontNum, oblique, gFalse); } -GfxFontLoc *GfxFont::getExternalFont(GString *path, GBool cid) { +GfxFontLoc *GfxFont::getExternalFont(GString *path, int fontNum, + double oblique, GBool cid) { FoFiIdentifierType fft; GfxFontType fontType; GfxFontLoc *fontLoc; @@ -768,6 +774,9 @@ GfxFontLoc *GfxFont::getExternalFont(GSt case fofiIdOpenTypeCFFCID: fontType = fontCIDType0COT; break; + case fofiIdDfont: + fontType = cid ? fontCIDType2 : fontTrueType; + break; case fofiIdUnknown: case fofiIdError: default: @@ -784,6 +793,8 @@ GfxFontLoc *GfxFont::getExternalFont(GSt fontLoc->locType = gfxFontLocExternal; fontLoc->fontType = fontType; fontLoc->path = path; + fontLoc->fontNum = fontNum; + fontLoc->oblique = oblique; return fontLoc; } @@ -791,8 +802,7 @@ char *GfxFont::readEmbFontFile(XRef *xre char *buf; Object obj1, obj2; Stream *str; - int c; - int size, i; + int size, n; obj1.initRef(embFontID.num, embFontID.gen); obj1.fetch(xref, &obj2); @@ -805,21 +815,19 @@ char *GfxFont::readEmbFontFile(XRef *xre } str = obj2.getStream(); + size = 0; buf = NULL; - i = size = 0; str->reset(); - while ((c = str->getChar()) != EOF) { - if (i == size) { - if (size > INT_MAX - 4096) { - error(errSyntaxError, -1, "Embedded font file is too large"); - break; - } - size += 4096; - buf = (char *)grealloc(buf, size); - } - buf[i++] = c; - } - *len = i; + do { + if (size > INT_MAX - 4096) { + error(errSyntaxError, -1, "Embedded font file is too large"); + break; + } + buf = (char *)grealloc(buf, size + 4096); + n = str->getBlock(buf + size, 4096); + size += n; + } while (n == 4096); + *len = size; str->close(); obj2.free(); @@ -908,8 +916,8 @@ Gfx8BitFont::Gfx8BitFont(XRef *xref, cha fontBBox[2] = 0.001 * builtinFont->bbox[2]; fontBBox[3] = 0.001 * builtinFont->bbox[3]; } else { - ascent = 0.95; - descent = -0.35; + ascent = 0.75; + descent = -0.25; fontBBox[0] = fontBBox[1] = fontBBox[2] = fontBBox[3] = 0; } @@ -1491,6 +1499,15 @@ Object *Gfx8BitFont::getCharProc(int cod return proc; } +Object *Gfx8BitFont::getCharProcNF(int code, Object *proc) { + if (enc[code] && charProcs.isDict()) { + charProcs.dictLookupNF(enc[code], proc); + } else { + proc->initNull(); + } + return proc; +} + Dict *Gfx8BitFont::getResources() { return resources.isDict() ? resources.getDict() : (Dict *)NULL; } @@ -1565,7 +1582,6 @@ GfxCIDFont::GfxCIDFont(XRef *xref, char error(errSyntaxError, -1, "Missing or empty DescendantFonts entry in Type 0 font"); obj1.free(); - goto err1; } if (!obj1.arrayGet(0, &desFontDictObj)->isDict()) { @@ -1661,6 +1677,7 @@ GfxCIDFont::GfxCIDFont(XRef *xref, char } cidToGID[cidToGIDLen++] = (c1 << 8) + c2; } + obj1.streamClose(); } else if (!obj1.isName("Identity") && !obj1.isNull()) { error(errSyntaxError, -1, "Invalid CIDToGIDMap entry in CID font"); } @@ -1836,7 +1853,8 @@ GfxCIDFont::GfxCIDFont(XRef *xref, char err2: obj1.free(); desFontDictObj.free(); - err1:; + err1: + error(errSyntaxError, -1, "Failed to parse font object for '{0:t}'", name); } GfxCIDFont::~GfxCIDFont() { @@ -2014,5 +2032,18 @@ GfxFont *GfxFontDict::lookup(char *tag) return fonts[i]; } } + return NULL; +} + +GfxFont *GfxFontDict::lookupByRef(Ref ref) { + int i; + + for (i = 0; i < numFonts; ++i) { + if (fonts[i] && + fonts[i]->getID()->num == ref.num && + fonts[i]->getID()->gen == ref.gen) { + return fonts[i]; + } + } return NULL; } diff -uNrp xpdf-3.03/xpdf/GfxFont.h xpdf-3.04/xpdf/GfxFont.h --- xpdf-3.03/xpdf/GfxFont.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/GfxFont.h 2014-05-28 20:50:50.000000000 +0200 @@ -100,8 +100,11 @@ public: // (if locType == gfxFontLocExternal) // PS font name // (if locType == gfxFontLocResident) - int fontNum; // for TrueType collections + int fontNum; // for TrueType collections and Mac dfonts // (if locType == gfxFontLocExternal) + double oblique; // sheer factor to oblique this font + // (used when substituting a plain + // font for an oblique font) GString *encoding; // PS font encoding, only for 16-bit fonts // (if locType == gfxFontLocResident) int wMode; // writing mode, only for 16-bit fonts @@ -207,7 +210,8 @@ protected: void readFontDescriptor(XRef *xref, Dict *fontDict); CharCodeToUnicode *readToUnicodeCMap(Dict *fontDict, int nBits, CharCodeToUnicode *ctu); - static GfxFontLoc *getExternalFont(GString *path, GBool cid); + static GfxFontLoc *getExternalFont(GString *path, int fontNum, + double oblique, GBool cid); GString *tag; // PDF font tag Ref id; // reference (used as unique ID) @@ -267,6 +271,7 @@ public: // Return the Type 3 CharProc for the character associated with . Object *getCharProc(int code, Object *proc); + Object *getCharProcNF(int code, Object *proc); // Return the Type 3 Resources dictionary, or NULL if none. Dict *getResources(); @@ -347,6 +352,7 @@ public: // Get the specified font. GfxFont *lookup(char *tag); + GfxFont *lookupByRef(Ref ref); // Iterative access. int getNumFonts() { return numFonts; } diff -uNrp xpdf-3.03/xpdf/Gfx.h xpdf-3.04/xpdf/Gfx.h --- xpdf-3.03/xpdf/Gfx.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Gfx.h 2014-05-28 20:50:50.000000000 +0200 @@ -16,6 +16,7 @@ #endif #include "gtypes.h" +#include "gfile.h" class GString; class GList; @@ -84,13 +85,16 @@ public: ~GfxResources(); GfxFont *lookupFont(char *name); - GBool lookupXObject(char *name, Object *obj); - GBool lookupXObjectNF(char *name, Object *obj); - void lookupColorSpace(char *name, Object *obj); - GfxPattern *lookupPattern(char *name); - GfxShading *lookupShading(char *name); - GBool lookupGState(char *name, Object *obj); - GBool lookupPropertiesNF(char *name, Object *obj); + 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); + GfxPattern *lookupPattern(const char *name + ); + GfxShading *lookupShading(const char *name + ); + GBool lookupGState(const char *name, Object *obj); + GBool lookupPropertiesNF(const char *name, Object *obj); GfxResources *getNext() { return next; } @@ -152,12 +156,13 @@ public: ~Gfx(); - // Interpret a stream or array of streams. - void display(Object *obj, GBool topLevel = gTrue); + // Interpret a stream or array of streams. should be a + // reference wherever possible (for loop-checking). + void display(Object *objRef, GBool topLevel = gTrue); // Display an annotation, given its appearance (a Form XObject), // border style, and bounding box (in default user space). - void drawAnnot(Object *str, AnnotBorderStyle *borderStyle, + void drawAnnot(Object *strRef, AnnotBorderStyle *borderStyle, double xMin, double yMin, double xMax, double yMax); // Save graphics state. @@ -169,13 +174,21 @@ public: // Get the current graphics state object. GfxState *getState() { return state; } - void drawForm(Object *str, Dict *resDict, double *matrix, double *bbox, + 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); + // Take all of the content stream stack entries from . This + // is useful when creating a new Gfx object to handle a pattern, + // etc., where it's useful to check for loops that span both Gfx + // objects. This function should be called immediately after the + // Gfx constructor, i.e., before processing any content streams with + // the new Gfx object. + void takeContentStreamStack(Gfx *oldGfx); + private: PDFDoc *doc; @@ -201,6 +214,8 @@ private: GList *markedContentStack; // BMC/BDC/EMC stack [GfxMarkedContent] Parser *parser; // parser for page content stream(s) + GList *contentStreamStack; // stack of open content streams, used + // for loop-checking GBool // callback to check for an abort (*abortCheckCbk)(void *data); @@ -208,11 +223,12 @@ private: static Operator opTab[]; // table of operators + GBool checkForContentStreamLoop(Object *ref); void go(GBool topLevel); - void execOp(Object *cmd, Object args[], int numArgs); + GBool execOp(Object *cmd, Object args[], int numArgs); Operator *findOp(char *name); GBool checkArg(Object *arg, TchkType type); - int getPos(); + GFileOffset getPos(); // graphics state operators void opSave(Object args[], int numArgs); @@ -225,7 +241,7 @@ private: void opSetMiterLimit(Object args[], int numArgs); void opSetLineWidth(Object args[], int numArgs); void opSetExtGState(Object args[], int numArgs); - void doSoftMask(Object *str, GBool alpha, + void doSoftMask(Object *str, Object *strRef, GBool alpha, GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, Function *transferFunc, GfxColor *backdropColor); @@ -268,7 +284,7 @@ private: void doPatternStroke(); void doPatternText(); void doPatternImageMask(Object *ref, Stream *str, int width, int height, - GBool invert, GBool inlineImg); + GBool invert, GBool inlineImg, GBool interpolate); void doTilingPatternFill(GfxTilingPattern *tPat, GBool stroke, GBool eoFill, GBool text); void doShadingPatternFill(GfxShadingPattern *sPat, @@ -282,12 +298,12 @@ private: void doAxialShFill(GfxAxialShading *shading); void doRadialShFill(GfxRadialShading *shading); void doGouraudTriangleShFill(GfxGouraudTriangleShading *shading); - void gouraudFillTriangle(double x0, double y0, GfxColor *color0, - double x1, double y1, GfxColor *color1, - double x2, double y2, GfxColor *color2, - int nComps, int depth); + void gouraudFillTriangle(double x0, double y0, double *color0, + double x1, double y1, double *color1, + double x2, double y2, double *color2, + GfxGouraudTriangleShading *shading, int depth); void doPatchMeshShFill(GfxPatchMeshShading *shading); - void fillPatch(GfxPatch *patch, int nComps, int depth); + void fillPatch(GfxPatch *patch, GfxPatchMeshShading *shading, int depth); void doEndPath(); // path clipping operators @@ -301,6 +317,7 @@ private: // text state operators void opSetCharSpacing(Object args[], int numArgs); void opSetFont(Object args[], int numArgs); + void doSetFont(GfxFont *font, double size); void opSetTextLeading(Object args[], int numArgs); void opSetTextRender(Object args[], int numArgs); void opSetTextRise(Object args[], int numArgs); @@ -324,7 +341,7 @@ private: // XObject operators void opXObject(Object args[], int numArgs); void doImage(Object *ref, Stream *str, GBool inlineImg); - void doForm(Object *str); + void doForm(Object *strRef, Object *str); // in-line image operators void opBeginImage(Object args[], int numArgs); diff -uNrp xpdf-3.03/xpdf/GfxState.cc xpdf-3.04/xpdf/GfxState.cc --- xpdf-3.03/xpdf/GfxState.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/GfxState.cc 2014-05-28 20:50:50.000000000 +0200 @@ -20,6 +20,7 @@ #include "Object.h" #include "Array.h" #include "Page.h" +#include "XRef.h" #include "GfxState.h" //------------------------------------------------------------------------ @@ -28,7 +29,6 @@ // loops in the color space object structure. #define colorSpaceRecursionLimit 8 - //------------------------------------------------------------------------ static inline GfxColorComp clip01(GfxColorComp x) { @@ -101,7 +101,8 @@ GfxColorSpace::GfxColorSpace() { GfxColorSpace::~GfxColorSpace() { } -GfxColorSpace *GfxColorSpace::parse(Object *csObj, int recursion) { +GfxColorSpace *GfxColorSpace::parse(Object *csObj, + int recursion) { GfxColorSpace *cs; Object obj1; @@ -112,11 +113,11 @@ GfxColorSpace *GfxColorSpace::parse(Obje cs = NULL; if (csObj->isName()) { if (csObj->isName("DeviceGray") || csObj->isName("G")) { - cs = new GfxDeviceGrayColorSpace(); + cs = GfxColorSpace::create(csDeviceGray); } else if (csObj->isName("DeviceRGB") || csObj->isName("RGB")) { - cs = new GfxDeviceRGBColorSpace(); + cs = GfxColorSpace::create(csDeviceRGB); } else if (csObj->isName("DeviceCMYK") || csObj->isName("CMYK")) { - cs = new GfxDeviceCMYKColorSpace(); + cs = GfxColorSpace::create(csDeviceCMYK); } else if (csObj->isName("Pattern")) { cs = new GfxPatternColorSpace(NULL); } else { @@ -125,11 +126,11 @@ GfxColorSpace *GfxColorSpace::parse(Obje } else if (csObj->isArray() && csObj->arrayGetLength() > 0) { csObj->arrayGet(0, &obj1); if (obj1.isName("DeviceGray") || obj1.isName("G")) { - cs = new GfxDeviceGrayColorSpace(); + cs = GfxColorSpace::create(csDeviceGray); } else if (obj1.isName("DeviceRGB") || obj1.isName("RGB")) { - cs = new GfxDeviceRGBColorSpace(); + cs = GfxColorSpace::create(csDeviceRGB); } else if (obj1.isName("DeviceCMYK") || obj1.isName("CMYK")) { - cs = new GfxDeviceCMYKColorSpace(); + cs = GfxColorSpace::create(csDeviceCMYK); } else if (obj1.isName("CalGray")) { cs = GfxCalGrayColorSpace::parse(csObj->getArray(), recursion); } else if (obj1.isName("CalRGB")) { @@ -137,15 +138,20 @@ GfxColorSpace *GfxColorSpace::parse(Obje } else if (obj1.isName("Lab")) { cs = GfxLabColorSpace::parse(csObj->getArray(), recursion); } else if (obj1.isName("ICCBased")) { - cs = GfxICCBasedColorSpace::parse(csObj->getArray(), recursion); + cs = GfxICCBasedColorSpace::parse(csObj->getArray(), + recursion); } else if (obj1.isName("Indexed") || obj1.isName("I")) { - cs = GfxIndexedColorSpace::parse(csObj->getArray(), recursion); + cs = GfxIndexedColorSpace::parse(csObj->getArray(), + recursion); } else if (obj1.isName("Separation")) { - cs = GfxSeparationColorSpace::parse(csObj->getArray(), recursion); + cs = GfxSeparationColorSpace::parse(csObj->getArray(), + recursion); } else if (obj1.isName("DeviceN")) { - cs = GfxDeviceNColorSpace::parse(csObj->getArray(), recursion); + cs = GfxDeviceNColorSpace::parse(csObj->getArray(), + recursion); } else if (obj1.isName("Pattern")) { - cs = GfxPatternColorSpace::parse(csObj->getArray(), recursion); + cs = GfxPatternColorSpace::parse(csObj->getArray(), + recursion); } else { error(errSyntaxError, -1, "Bad color space"); } @@ -156,6 +162,20 @@ GfxColorSpace *GfxColorSpace::parse(Obje return cs; } +GfxColorSpace *GfxColorSpace::create(GfxColorSpaceMode mode) { + GfxColorSpace *cs; + + cs = NULL; + if (mode == csDeviceGray) { + cs = new GfxDeviceGrayColorSpace(); + } else if (mode == csDeviceRGB) { + cs = new GfxDeviceRGBColorSpace(); + } else if (mode == csDeviceCMYK) { + cs = new GfxDeviceCMYKColorSpace(); + } + return cs; +} + void GfxColorSpace::getDefaultRanges(double *decodeLow, double *decodeRange, int maxImgPixel) { int i; @@ -185,9 +205,13 @@ GfxDeviceGrayColorSpace::~GfxDeviceGrayC } GfxColorSpace *GfxDeviceGrayColorSpace::copy() { - return new GfxDeviceGrayColorSpace(); + GfxDeviceGrayColorSpace *cs; + + cs = new GfxDeviceGrayColorSpace(); + return cs; } + void GfxDeviceGrayColorSpace::getGray(GfxColor *color, GfxGray *gray) { *gray = clip01(color->c[0]); } @@ -233,6 +257,7 @@ GfxColorSpace *GfxCalGrayColorSpace::cop return cs; } + GfxColorSpace *GfxCalGrayColorSpace::parse(Array *arr, int recursion) { GfxCalGrayColorSpace *cs; Object obj1, obj2, obj3; @@ -311,9 +336,13 @@ GfxDeviceRGBColorSpace::~GfxDeviceRGBCol } GfxColorSpace *GfxDeviceRGBColorSpace::copy() { - return new GfxDeviceRGBColorSpace(); + GfxDeviceRGBColorSpace *cs; + + cs = new GfxDeviceRGBColorSpace(); + return cs; } + void GfxDeviceRGBColorSpace::getGray(GfxColor *color, GfxGray *gray) { *gray = clip01((GfxColorComp)(0.3 * color->c[0] + 0.59 * color->c[1] + @@ -456,6 +485,7 @@ GfxColorSpace *GfxCalRGBColorSpace::pars return cs; } + void GfxCalRGBColorSpace::getGray(GfxColor *color, GfxGray *gray) { *gray = clip01((GfxColorComp)(0.299 * color->c[0] + 0.587 * color->c[1] + @@ -505,9 +535,13 @@ GfxDeviceCMYKColorSpace::~GfxDeviceCMYKC } GfxColorSpace *GfxDeviceCMYKColorSpace::copy() { - return new GfxDeviceCMYKColorSpace(); + GfxDeviceCMYKColorSpace *cs; + + cs = new GfxDeviceCMYKColorSpace(); + return cs; } + void GfxDeviceCMYKColorSpace::getGray(GfxColor *color, GfxGray *gray) { *gray = clip01((GfxColorComp)(gfxColorComp1 - color->c[3] - 0.3 * color->c[0] @@ -706,6 +740,7 @@ GfxColorSpace *GfxLabColorSpace::parse(A return cs; } + void GfxLabColorSpace::getGray(GfxColor *color, GfxGray *gray) { GfxRGB rgb; @@ -720,6 +755,7 @@ void GfxLabColorSpace::getRGB(GfxColor * double t1, t2; double r, g, b; + // convert L*a*b* to CIE 1931 XYZ color space t1 = (colToDbl(color->c[0]) + 16) / 116; t2 = t1 + colToDbl(color->c[1]) / 500; @@ -756,6 +792,7 @@ void GfxLabColorSpace::getCMYK(GfxColor GfxRGB rgb; GfxColorComp c, m, y, k; + getRGB(color, &rgb); c = clip01(gfxColorComp1 - rgb.r); m = clip01(gfxColorComp1 - rgb.g); @@ -831,7 +868,8 @@ GfxColorSpace *GfxICCBasedColorSpace::co return cs; } -GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, int recursion) { +GfxColorSpace *GfxICCBasedColorSpace::parse(Array *arr, + int recursion) { GfxICCBasedColorSpace *cs; Ref iccProfileStreamA; int nCompsA; @@ -874,16 +912,17 @@ GfxColorSpace *GfxICCBasedColorSpace::pa nCompsA = 4; } if (dict->lookup("Alternate", &obj2)->isNull() || - !(altA = GfxColorSpace::parse(&obj2, recursion + 1))) { + !(altA = GfxColorSpace::parse(&obj2, + recursion + 1))) { switch (nCompsA) { case 1: - altA = new GfxDeviceGrayColorSpace(); + altA = GfxColorSpace::create(csDeviceGray); break; case 3: - altA = new GfxDeviceRGBColorSpace(); + altA = GfxColorSpace::create(csDeviceRGB); break; case 4: - altA = new GfxDeviceCMYKColorSpace(); + altA = GfxColorSpace::create(csDeviceCMYK); break; default: error(errSyntaxError, -1, "Bad ICCBased color space - invalid N"); @@ -910,6 +949,7 @@ GfxColorSpace *GfxICCBasedColorSpace::pa return cs; } + void GfxICCBasedColorSpace::getGray(GfxColor *color, GfxGray *gray) { alt->getGray(color, gray); } @@ -981,7 +1021,8 @@ GfxColorSpace *GfxIndexedColorSpace::cop return cs; } -GfxColorSpace *GfxIndexedColorSpace::parse(Array *arr, int recursion) { +GfxColorSpace *GfxIndexedColorSpace::parse(Array *arr, + int recursion) { GfxIndexedColorSpace *cs; GfxColorSpace *baseA; int indexHighA; @@ -995,7 +1036,8 @@ GfxColorSpace *GfxIndexedColorSpace::par goto err1; } arr->get(1, &obj1); - if (!(baseA = GfxColorSpace::parse(&obj1, recursion + 1))) { + if (!(baseA = GfxColorSpace::parse(&obj1, + recursion + 1))) { error(errSyntaxError, -1, "Bad Indexed color space (base color space)"); goto err2; } @@ -1060,6 +1102,7 @@ GfxColorSpace *GfxIndexedColorSpace::par return NULL; } + GfxColor *GfxIndexedColorSpace::mapColorToBase(GfxColor *color, GfxColor *baseColor) { Guchar *p; @@ -1146,12 +1189,16 @@ GfxSeparationColorSpace::~GfxSeparationC } GfxColorSpace *GfxSeparationColorSpace::copy() { - return new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy(), - nonMarking, overprintMask); + GfxSeparationColorSpace *cs; + + cs = new GfxSeparationColorSpace(name->copy(), alt->copy(), func->copy(), + nonMarking, overprintMask); + return cs; } //~ handle the 'All' and 'None' colorants -GfxColorSpace *GfxSeparationColorSpace::parse(Array *arr, int recursion) { +GfxColorSpace *GfxSeparationColorSpace::parse(Array *arr, + int recursion) { GfxSeparationColorSpace *cs; GString *nameA; GfxColorSpace *altA; @@ -1169,7 +1216,8 @@ GfxColorSpace *GfxSeparationColorSpace:: nameA = new GString(obj1.getName()); obj1.free(); arr->get(2, &obj1); - if (!(altA = GfxColorSpace::parse(&obj1, recursion + 1))) { + if (!(altA = GfxColorSpace::parse(&obj1, + recursion + 1))) { error(errSyntaxError, -1, "Bad Separation color space (alternate color space)"); goto err3; @@ -1193,6 +1241,7 @@ GfxColorSpace *GfxSeparationColorSpace:: return NULL; } + void GfxSeparationColorSpace::getGray(GfxColor *color, GfxGray *gray) { double x; double c[gfxColorMaxComps]; @@ -1303,12 +1352,16 @@ GfxDeviceNColorSpace::~GfxDeviceNColorSp } GfxColorSpace *GfxDeviceNColorSpace::copy() { - return new GfxDeviceNColorSpace(nComps, names, alt->copy(), func->copy(), - nonMarking, overprintMask); + GfxDeviceNColorSpace *cs; + + cs = new GfxDeviceNColorSpace(nComps, names, alt->copy(), func->copy(), + nonMarking, overprintMask); + return cs; } //~ handle the 'None' colorant -GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, int recursion) { +GfxColorSpace *GfxDeviceNColorSpace::parse(Array *arr, + int recursion) { GfxDeviceNColorSpace *cs; int nCompsA; GString *namesA[gfxColorMaxComps]; @@ -1343,7 +1396,8 @@ GfxColorSpace *GfxDeviceNColorSpace::par } obj1.free(); arr->get(2, &obj1); - if (!(altA = GfxColorSpace::parse(&obj1, recursion + 1))) { + if (!(altA = GfxColorSpace::parse(&obj1, + recursion + 1))) { error(errSyntaxError, -1, "Bad DeviceN color space (alternate color space)"); goto err3; @@ -1369,6 +1423,7 @@ GfxColorSpace *GfxDeviceNColorSpace::par return NULL; } + void GfxDeviceNColorSpace::getGray(GfxColor *color, GfxGray *gray) { double x[gfxColorMaxComps], c[gfxColorMaxComps]; GfxColor color2; @@ -1438,11 +1493,15 @@ GfxPatternColorSpace::~GfxPatternColorSp } GfxColorSpace *GfxPatternColorSpace::copy() { - return new GfxPatternColorSpace(under ? under->copy() : - (GfxColorSpace *)NULL); + GfxPatternColorSpace *cs; + + cs = new GfxPatternColorSpace(under ? under->copy() : + (GfxColorSpace *)NULL); + return cs; } -GfxColorSpace *GfxPatternColorSpace::parse(Array *arr, int recursion) { +GfxColorSpace *GfxPatternColorSpace::parse(Array *arr, + int recursion) { GfxPatternColorSpace *cs; GfxColorSpace *underA; Object obj1; @@ -1454,7 +1513,8 @@ GfxColorSpace *GfxPatternColorSpace::par underA = NULL; if (arr->getLength() == 2) { arr->get(1, &obj1); - if (!(underA = GfxColorSpace::parse(&obj1, recursion + 1))) { + if (!(underA = GfxColorSpace::parse(&obj1, + recursion + 1))) { error(errSyntaxError, -1, "Bad Pattern color space (underlying color space)"); obj1.free(); @@ -1466,6 +1526,7 @@ GfxColorSpace *GfxPatternColorSpace::par return cs; } + void GfxPatternColorSpace::getGray(GfxColor *color, GfxGray *gray) { *gray = 0; } @@ -1495,24 +1556,26 @@ GfxPattern::GfxPattern(int typeA) { GfxPattern::~GfxPattern() { } -GfxPattern *GfxPattern::parse(Object *obj) { +GfxPattern *GfxPattern::parse(Object *objRef, Object *obj + ) { GfxPattern *pattern; - Object obj1; + Object typeObj; if (obj->isDict()) { - obj->dictLookup("PatternType", &obj1); + obj->dictLookup("PatternType", &typeObj); } else if (obj->isStream()) { - obj->streamGetDict()->lookup("PatternType", &obj1); + obj->streamGetDict()->lookup("PatternType", &typeObj); } else { return NULL; } pattern = NULL; - if (obj1.isInt() && obj1.getInt() == 1) { - pattern = GfxTilingPattern::parse(obj); - } else if (obj1.isInt() && obj1.getInt() == 2) { - pattern = GfxShadingPattern::parse(obj); + if (typeObj.isInt() && typeObj.getInt() == 1) { + pattern = GfxTilingPattern::parse(objRef, obj); + } else if (typeObj.isInt() && typeObj.getInt() == 2) { + pattern = GfxShadingPattern::parse(obj + ); } - obj1.free(); + typeObj.free(); return pattern; } @@ -1520,7 +1583,7 @@ GfxPattern *GfxPattern::parse(Object *ob // GfxTilingPattern //------------------------------------------------------------------------ -GfxTilingPattern *GfxTilingPattern::parse(Object *patObj) { +GfxTilingPattern *GfxTilingPattern::parse(Object *patObjRef, Object *patObj) { GfxTilingPattern *pat; Dict *dict; int paintTypeA, tilingTypeA; @@ -1597,7 +1660,7 @@ GfxTilingPattern *GfxTilingPattern::pars obj1.free(); pat = new GfxTilingPattern(paintTypeA, tilingTypeA, bboxA, xStepA, yStepA, - &resDictA, matrixA, patObj); + &resDictA, matrixA, patObjRef); resDictA.free(); return pat; } @@ -1605,7 +1668,7 @@ GfxTilingPattern *GfxTilingPattern::pars GfxTilingPattern::GfxTilingPattern(int paintTypeA, int tilingTypeA, double *bboxA, double xStepA, double yStepA, Object *resDictA, double *matrixA, - Object *contentStreamA): + Object *contentStreamRefA): GfxPattern(1) { int i; @@ -1621,24 +1684,25 @@ GfxTilingPattern::GfxTilingPattern(int p for (i = 0; i < 6; ++i) { matrix[i] = matrixA[i]; } - contentStreamA->copy(&contentStream); + contentStreamRefA->copy(&contentStreamRef); } GfxTilingPattern::~GfxTilingPattern() { resDict.free(); - contentStream.free(); + contentStreamRef.free(); } GfxPattern *GfxTilingPattern::copy() { return new GfxTilingPattern(paintType, tilingType, bbox, xStep, yStep, - &resDict, matrix, &contentStream); + &resDict, matrix, &contentStreamRef); } //------------------------------------------------------------------------ // GfxShadingPattern //------------------------------------------------------------------------ -GfxShadingPattern *GfxShadingPattern::parse(Object *patObj) { +GfxShadingPattern *GfxShadingPattern::parse(Object *patObj + ) { Dict *dict; GfxShading *shadingA; double matrixA[6]; @@ -1651,7 +1715,8 @@ GfxShadingPattern *GfxShadingPattern::pa dict = patObj->getDict(); dict->lookup("Shading", &obj1); - shadingA = GfxShading::parse(&obj1); + shadingA = GfxShading::parse(&obj1 + ); obj1.free(); if (!shadingA) { return NULL; @@ -1724,7 +1789,8 @@ GfxShading::~GfxShading() { } } -GfxShading *GfxShading::parse(Object *obj) { +GfxShading *GfxShading::parse(Object *obj + ) { GfxShading *shading; Dict *dict; int typeA; @@ -1748,17 +1814,21 @@ GfxShading *GfxShading::parse(Object *ob switch (typeA) { case 1: - shading = GfxFunctionShading::parse(dict); + shading = GfxFunctionShading::parse(dict + ); break; case 2: - shading = GfxAxialShading::parse(dict); + shading = GfxAxialShading::parse(dict + ); break; case 3: - shading = GfxRadialShading::parse(dict); + shading = GfxRadialShading::parse(dict + ); break; case 4: if (obj->isStream()) { - shading = GfxGouraudTriangleShading::parse(4, dict, obj->getStream()); + shading = GfxGouraudTriangleShading::parse(4, dict, obj->getStream() + ); } else { error(errSyntaxError, -1, "Invalid Type 4 shading object"); goto err1; @@ -1766,7 +1836,8 @@ GfxShading *GfxShading::parse(Object *ob break; case 5: if (obj->isStream()) { - shading = GfxGouraudTriangleShading::parse(5, dict, obj->getStream()); + shading = GfxGouraudTriangleShading::parse(5, dict, obj->getStream() + ); } else { error(errSyntaxError, -1, "Invalid Type 5 shading object"); goto err1; @@ -1774,7 +1845,8 @@ GfxShading *GfxShading::parse(Object *ob break; case 6: if (obj->isStream()) { - shading = GfxPatchMeshShading::parse(6, dict, obj->getStream()); + shading = GfxPatchMeshShading::parse(6, dict, obj->getStream() + ); } else { error(errSyntaxError, -1, "Invalid Type 6 shading object"); goto err1; @@ -1782,7 +1854,8 @@ GfxShading *GfxShading::parse(Object *ob break; case 7: if (obj->isStream()) { - shading = GfxPatchMeshShading::parse(7, dict, obj->getStream()); + shading = GfxPatchMeshShading::parse(7, dict, obj->getStream() + ); } else { error(errSyntaxError, -1, "Invalid Type 7 shading object"); goto err1; @@ -1799,12 +1872,14 @@ GfxShading *GfxShading::parse(Object *ob return NULL; } -GBool GfxShading::init(Dict *dict) { +GBool GfxShading::init(Dict *dict + ) { Object obj1, obj2; int i; dict->lookup("ColorSpace", &obj1); - if (!(colorSpace = GfxColorSpace::parse(&obj1))) { + if (!(colorSpace = GfxColorSpace::parse(&obj1 + ))) { error(errSyntaxError, -1, "Bad color space in shading dictionary"); obj1.free(); return gFalse; @@ -1901,7 +1976,8 @@ GfxFunctionShading::~GfxFunctionShading( } } -GfxFunctionShading *GfxFunctionShading::parse(Dict *dict) { +GfxFunctionShading *GfxFunctionShading::parse(Dict *dict + ) { GfxFunctionShading *shading; double x0A, y0A, x1A, y1A; double matrixA[6]; @@ -1916,9 +1992,9 @@ GfxFunctionShading *GfxFunctionShading:: obj1.arrayGetLength() == 4) { x0A = obj1.arrayGet(0, &obj2)->getNum(); obj2.free(); - y0A = obj1.arrayGet(1, &obj2)->getNum(); + x1A = obj1.arrayGet(1, &obj2)->getNum(); obj2.free(); - x1A = obj1.arrayGet(2, &obj2)->getNum(); + y0A = obj1.arrayGet(2, &obj2)->getNum(); obj2.free(); y1A = obj1.arrayGet(3, &obj2)->getNum(); obj2.free(); @@ -1970,7 +2046,8 @@ GfxFunctionShading *GfxFunctionShading:: shading = new GfxFunctionShading(x0A, y0A, x1A, y1A, matrixA, funcsA, nFuncsA); - if (!shading->init(dict)) { + if (!shading->init(dict + )) { delete shading; return NULL; } @@ -2060,7 +2137,8 @@ GfxAxialShading::~GfxAxialShading() { } } -GfxAxialShading *GfxAxialShading::parse(Dict *dict) { +GfxAxialShading *GfxAxialShading::parse(Dict *dict + ) { GfxAxialShading *shading; double x0A, y0A, x1A, y1A; double t0A, t1A; @@ -2137,7 +2215,8 @@ GfxAxialShading *GfxAxialShading::parse( shading = new GfxAxialShading(x0A, y0A, x1A, y1A, t0A, t1A, funcsA, nFuncsA, extend0A, extend1A); - if (!shading->init(dict)) { + if (!shading->init(dict + )) { delete shading; return NULL; } @@ -2226,7 +2305,8 @@ GfxRadialShading::~GfxRadialShading() { } } -GfxRadialShading *GfxRadialShading::parse(Dict *dict) { +GfxRadialShading *GfxRadialShading::parse(Dict *dict + ) { GfxRadialShading *shading; double x0A, y0A, r0A, x1A, y1A, r1A; double t0A, t1A; @@ -2307,7 +2387,8 @@ GfxRadialShading *GfxRadialShading::pars shading = new GfxRadialShading(x0A, y0A, r0A, x1A, y1A, r1A, t0A, t1A, funcsA, nFuncsA, extend0A, extend1A); - if (!shading->init(dict)) { + if (!shading->init(dict + )) { delete shading; return NULL; } @@ -2413,7 +2494,7 @@ GfxGouraudTriangleShading::GfxGouraudTri int typeA, GfxGouraudVertex *verticesA, int nVerticesA, int (*trianglesA)[3], int nTrianglesA, - Function **funcsA, int nFuncsA): + int nCompsA, Function **funcsA, int nFuncsA): GfxShading(typeA) { int i; @@ -2422,6 +2503,7 @@ GfxGouraudTriangleShading::GfxGouraudTri nVertices = nVerticesA; triangles = trianglesA; nTriangles = nTrianglesA; + nComps = nCompsA; nFuncs = nFuncsA; for (i = 0; i < nFuncs; ++i) { funcs[i] = funcsA[i]; @@ -2440,6 +2522,7 @@ GfxGouraudTriangleShading::GfxGouraudTri nTriangles = shading->nTriangles; triangles = (int (*)[3])gmallocn(nTriangles * 3, sizeof(int)); memcpy(triangles, shading->triangles, nTriangles * 3 * sizeof(int)); + nComps = shading->nComps; nFuncs = shading->nFuncs; for (i = 0; i < nFuncs; ++i) { funcs[i] = shading->funcs[i]->copy(); @@ -2456,9 +2539,9 @@ GfxGouraudTriangleShading::~GfxGouraudTr } } -GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse(int typeA, - Dict *dict, - Stream *str) { +GfxGouraudTriangleShading *GfxGouraudTriangleShading::parse( + int typeA, Dict *dict, Stream *str + ) { GfxGouraudTriangleShading *shading; Function *funcsA[gfxColorMaxComps]; int nFuncsA; @@ -2469,7 +2552,7 @@ GfxGouraudTriangleShading *GfxGouraudTri double cMul[gfxColorMaxComps]; GfxGouraudVertex *verticesA; int (*trianglesA)[3]; - int nComps, nVerticesA, nTrianglesA, vertSize, triSize; + int nCompsA, nVerticesA, nTrianglesA, vertSize, triSize; Guint x, y, flag; Guint c[gfxColorMaxComps]; GfxShadingBitBuf *bitBuf; @@ -2531,7 +2614,7 @@ GfxGouraudTriangleShading *GfxGouraudTri obj2.free(); cMul[i] = (cMax[i] - cMin[i]) / (double)((1 << compBits) - 1); } - nComps = i; + nCompsA = i; } else { error(errSyntaxError, -1, "Missing or invalid Decode array in shading dictionary"); @@ -2585,12 +2668,12 @@ GfxGouraudTriangleShading *GfxGouraudTri !bitBuf->getBits(coordBits, &y)) { break; } - for (i = 0; i < nComps; ++i) { + for (i = 0; i < nCompsA; ++i) { if (!bitBuf->getBits(compBits, &c[i])) { break; } } - if (i < nComps) { + if (i < nCompsA) { break; } if (nVerticesA == vertSize) { @@ -2600,9 +2683,8 @@ GfxGouraudTriangleShading *GfxGouraudTri } verticesA[nVerticesA].x = xMin + xMul * (double)x; verticesA[nVerticesA].y = yMin + yMul * (double)y; - for (i = 0; i < nComps; ++i) { - verticesA[nVerticesA].color.c[i] = - dblToCol(cMin[i] + cMul[i] * (double)c[i]); + for (i = 0; i < nCompsA; ++i) { + verticesA[nVerticesA].color[i] = cMin[i] + cMul[i] * (double)c[i]; } ++nVerticesA; bitBuf->flushBits(); @@ -2657,8 +2739,9 @@ GfxGouraudTriangleShading *GfxGouraudTri shading = new GfxGouraudTriangleShading(typeA, verticesA, nVerticesA, trianglesA, nTrianglesA, - funcsA, nFuncsA); - if (!shading->init(dict)) { + nCompsA, funcsA, nFuncsA); + if (!shading->init(dict + )) { delete shading; return NULL; } @@ -2676,54 +2759,46 @@ GfxShading *GfxGouraudTriangleShading::c void GfxGouraudTriangleShading::getTriangle( int i, - double *x0, double *y0, GfxColor *color0, - double *x1, double *y1, GfxColor *color1, - double *x2, double *y2, GfxColor *color2) { - double in; - double out[gfxColorMaxComps]; + double *x0, double *y0, double *color0, + double *x1, double *y1, double *color1, + double *x2, double *y2, double *color2) { int v, j; v = triangles[i][0]; *x0 = vertices[v].x; *y0 = vertices[v].y; - if (nFuncs > 0) { - in = colToDbl(vertices[v].color.c[0]); - for (j = 0; j < nFuncs; ++j) { - funcs[j]->transform(&in, &out[j]); - } - for (j = 0; j < gfxColorMaxComps; ++j) { - color0->c[j] = dblToCol(out[j]); - } - } else { - *color0 = vertices[v].color; + for (j = 0; j < nComps; ++j) { + color0[j] = vertices[v].color[j]; } v = triangles[i][1]; *x1 = vertices[v].x; *y1 = vertices[v].y; - if (nFuncs > 0) { - in = colToDbl(vertices[v].color.c[0]); - for (j = 0; j < nFuncs; ++j) { - funcs[j]->transform(&in, &out[j]); - } - for (j = 0; j < gfxColorMaxComps; ++j) { - color1->c[j] = dblToCol(out[j]); - } - } else { - *color1 = vertices[v].color; + for (j = 0; j < nComps; ++j) { + color1[j] = vertices[v].color[j]; } v = triangles[i][2]; *x2 = vertices[v].x; *y2 = vertices[v].y; + for (j = 0; j < nComps; ++j) { + color2[j] = vertices[v].color[j]; + } +} + +void GfxGouraudTriangleShading::getColor(double *in, GfxColor *out) { + double c[gfxColorMaxComps]; + int i; + if (nFuncs > 0) { - in = colToDbl(vertices[v].color.c[0]); - for (j = 0; j < nFuncs; ++j) { - funcs[j]->transform(&in, &out[j]); + for (i = 0; i < nFuncs; ++i) { + funcs[i]->transform(in, &c[i]); } - for (j = 0; j < gfxColorMaxComps; ++j) { - color2->c[j] = dblToCol(out[j]); + for (i = 0; i < colorSpace->getNComps(); ++i) { + out->c[i] = dblToCol(c[i]); } } else { - *color2 = vertices[v].color; + for (i = 0; i < nComps; ++i) { + out->c[i] = dblToCol(in[i]); + } } } @@ -2733,6 +2808,7 @@ void GfxGouraudTriangleShading::getTrian GfxPatchMeshShading::GfxPatchMeshShading(int typeA, GfxPatch *patchesA, int nPatchesA, + int nCompsA, Function **funcsA, int nFuncsA): GfxShading(typeA) { @@ -2740,6 +2816,7 @@ GfxPatchMeshShading::GfxPatchMeshShading patches = patchesA; nPatches = nPatchesA; + nComps = nCompsA; nFuncs = nFuncsA; for (i = 0; i < nFuncs; ++i) { funcs[i] = funcsA[i]; @@ -2754,6 +2831,7 @@ GfxPatchMeshShading::GfxPatchMeshShading nPatches = shading->nPatches; patches = (GfxPatch *)gmallocn(nPatches, sizeof(GfxPatch)); memcpy(patches, shading->patches, nPatches * sizeof(GfxPatch)); + nComps = shading->nComps; nFuncs = shading->nFuncs; for (i = 0; i < nFuncs; ++i) { funcs[i] = shading->funcs[i]->copy(); @@ -2770,7 +2848,8 @@ GfxPatchMeshShading::~GfxPatchMeshShadin } GfxPatchMeshShading *GfxPatchMeshShading::parse(int typeA, Dict *dict, - Stream *str) { + Stream *str + ) { GfxPatchMeshShading *shading; Function *funcsA[gfxColorMaxComps]; int nFuncsA; @@ -2780,11 +2859,11 @@ GfxPatchMeshShading *GfxPatchMeshShading double xMul, yMul; double cMul[gfxColorMaxComps]; GfxPatch *patchesA, *p; - int nComps, nPatchesA, patchesSize, nPts, nColors; + int nCompsA, nPatchesA, patchesSize, nPts, nColors; Guint flag; double x[16], y[16]; Guint xi, yi; - GfxColorComp c[4][gfxColorMaxComps]; + double c[4][gfxColorMaxComps]; Guint ci; GfxShadingBitBuf *bitBuf; Object obj1, obj2; @@ -2833,7 +2912,7 @@ GfxPatchMeshShading *GfxPatchMeshShading obj2.free(); cMul[i] = (cMax[i] - cMin[i]) / (double)((1 << compBits) - 1); } - nComps = i; + nCompsA = i; } else { error(errSyntaxError, -1, "Missing or invalid Decode array in shading dictionary"); @@ -2907,13 +2986,13 @@ GfxPatchMeshShading *GfxPatchMeshShading break; } for (i = 0; i < nColors; ++i) { - for (j = 0; j < nComps; ++j) { + for (j = 0; j < nCompsA; ++j) { if (!bitBuf->getBits(compBits, &ci)) { break; } - c[i][j] = dblToCol(cMin[j] + cMul[j] * (double)ci); + c[i][j] = cMin[j] + cMul[j] * (double)ci; } - if (j < nComps) { + if (j < nCompsA) { break; } } @@ -2953,11 +3032,11 @@ GfxPatchMeshShading *GfxPatchMeshShading p->y[2][0] = y[10]; p->x[1][0] = x[11]; p->y[1][0] = y[11]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = c[0][j]; - p->color[0][1].c[j] = c[1][j]; - p->color[1][1].c[j] = c[2][j]; - p->color[1][0].c[j] = c[3][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = c[0][j]; + p->color[0][1][j] = c[1][j]; + p->color[1][1][j] = c[2][j]; + p->color[1][0][j] = c[3][j]; } break; case 1: @@ -2985,11 +3064,11 @@ GfxPatchMeshShading *GfxPatchMeshShading p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = patchesA[nPatchesA-1].color[0][1].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][1].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = patchesA[nPatchesA-1].color[0][1][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[1][1][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; case 2: @@ -3017,11 +3096,11 @@ GfxPatchMeshShading *GfxPatchMeshShading p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][1].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][0].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = patchesA[nPatchesA-1].color[1][1][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[1][0][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; case 3: @@ -3049,11 +3128,11 @@ GfxPatchMeshShading *GfxPatchMeshShading p->y[2][0] = y[6]; p->x[1][0] = x[7]; p->y[1][0] = y[7]; - for (j = 0; j < nComps; ++j) { - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][0].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[0][0].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][1][j] = patchesA[nPatchesA-1].color[1][0][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[0][0][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; } @@ -3092,11 +3171,11 @@ GfxPatchMeshShading *GfxPatchMeshShading p->y[2][2] = y[14]; p->x[2][1] = x[15]; p->y[2][1] = y[15]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = c[0][j]; - p->color[0][1].c[j] = c[1][j]; - p->color[1][1].c[j] = c[2][j]; - p->color[1][0].c[j] = c[3][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = c[0][j]; + p->color[0][1][j] = c[1][j]; + p->color[1][1][j] = c[2][j]; + p->color[1][0][j] = c[3][j]; } break; case 1: @@ -3132,11 +3211,11 @@ GfxPatchMeshShading *GfxPatchMeshShading p->y[2][2] = y[10]; p->x[2][1] = x[11]; p->y[2][1] = y[11]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = patchesA[nPatchesA-1].color[0][1].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][1].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = patchesA[nPatchesA-1].color[0][1][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[1][1][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; case 2: @@ -3172,11 +3251,11 @@ GfxPatchMeshShading *GfxPatchMeshShading p->y[2][2] = y[10]; p->x[2][1] = x[11]; p->y[2][1] = y[11]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][1].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[1][0].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = patchesA[nPatchesA-1].color[1][1][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[1][0][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; case 3: @@ -3212,11 +3291,11 @@ GfxPatchMeshShading *GfxPatchMeshShading p->y[2][2] = y[10]; p->x[2][1] = x[11]; p->y[2][1] = y[11]; - for (j = 0; j < nComps; ++j) { - p->color[0][0].c[j] = patchesA[nPatchesA-1].color[1][0].c[j]; - p->color[0][1].c[j] = patchesA[nPatchesA-1].color[0][0].c[j]; - p->color[1][1].c[j] = c[0][j]; - p->color[1][0].c[j] = c[1][j]; + for (j = 0; j < nCompsA; ++j) { + p->color[0][0][j] = patchesA[nPatchesA-1].color[1][0][j]; + p->color[0][1][j] = patchesA[nPatchesA-1].color[0][0][j]; + p->color[1][1][j] = c[0][j]; + p->color[1][0][j] = c[1][j]; } break; } @@ -3273,8 +3352,9 @@ GfxPatchMeshShading *GfxPatchMeshShading } shading = new GfxPatchMeshShading(typeA, patchesA, nPatchesA, - funcsA, nFuncsA); - if (!shading->init(dict)) { + nCompsA, funcsA, nFuncsA); + if (!shading->init(dict + )) { delete shading; return NULL; } @@ -3290,6 +3370,24 @@ GfxShading *GfxPatchMeshShading::copy() return new GfxPatchMeshShading(this); } +void GfxPatchMeshShading::getColor(double *in, GfxColor *out) { + double c[gfxColorMaxComps]; + int i; + + if (nFuncs > 0) { + for (i = 0; i < nFuncs; ++i) { + funcs[i]->transform(in, &c[i]); + } + for (i = 0; i < colorSpace->getNComps(); ++i) { + out->c[i] = dblToCol(c[i]); + } + } else { + for (i = 0; i < nComps; ++i) { + out->c[i] = dblToCol(in[i]); + } + } +} + //------------------------------------------------------------------------ // GfxImageColorMap //------------------------------------------------------------------------ @@ -3310,7 +3408,11 @@ GfxImageColorMap::GfxImageColorMap(int b // bits per component and color space bits = bitsA; - maxPixel = (1 << bits) - 1; + if (bits <= 8) { + maxPixel = (1 << bits) - 1; + } else { + maxPixel = 0xff; + } colorSpace = colorSpaceA; // initialize @@ -3431,7 +3533,11 @@ GfxImageColorMap::GfxImageColorMap(GfxIm lookup[k] = NULL; lookup2[k] = NULL; } - n = 1 << bits; + if (bits <= 8) { + n = 1 << bits; + } else { + n = 256; + } for (k = 0; k < nComps; ++k) { lookup[k] = (GfxColorComp *)gmallocn(n, sizeof(GfxColorComp)); memcpy(lookup[k], colorMap->lookup[k], n * sizeof(GfxColorComp)); @@ -3521,7 +3627,11 @@ void GfxImageColorMap::getCMYK(Guchar *x void GfxImageColorMap::getColor(Guchar *x, GfxColor *color) { int maxPixel, i; - maxPixel = (1 << bits) - 1; + if (bits <= 8) { + maxPixel = (1 << bits) - 1; + } else { + maxPixel = 0xff; + } for (i = 0; i < nComps; ++i) { color->c[i] = dblToCol(decodeLow[i] + (x[i] * decodeRange[i]) / maxPixel); } @@ -3811,7 +3921,8 @@ void GfxPath::offset(double dx, double d //------------------------------------------------------------------------ GfxState::GfxState(double hDPIA, double vDPIA, PDFRectangle *pageBox, - int rotateA, GBool upsideDown) { + int rotateA, GBool upsideDown + ) { double kx, ky; hDPI = hDPIA; @@ -3861,8 +3972,8 @@ GfxState::GfxState(double hDPIA, double pageHeight = ky * (py2 - py1); } - fillColorSpace = new GfxDeviceGrayColorSpace(); - strokeColorSpace = new GfxDeviceGrayColorSpace(); + fillColorSpace = GfxColorSpace::create(csDeviceGray); + strokeColorSpace = GfxColorSpace::create(csDeviceGray); fillColor.c[0] = 0; strokeColor.c[0] = 0; fillPattern = NULL; diff -uNrp xpdf-3.03/xpdf/GfxState.h xpdf-3.04/xpdf/GfxState.h --- xpdf-3.03/xpdf/GfxState.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/GfxState.h 2014-05-28 20:50:50.000000000 +0200 @@ -140,7 +140,13 @@ public: virtual GfxColorSpaceMode getMode() = 0; // Construct a color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Object *csObj, int recursion = 0); + static GfxColorSpace *parse(Object *csObj, + int recursion = 0); + + // Construct a simple color space. The argument can be + // csDeviceGray, csDeviceRGB, or csDeviceCMYK. + static GfxColorSpace *create(GfxColorSpaceMode mode); + // Convert to gray, RGB, or CMYK. virtual void getGray(GfxColor *color, GfxGray *gray) = 0; @@ -381,7 +387,8 @@ public: virtual GfxColorSpaceMode getMode() { return csICCBased; } // Construct an ICCBased color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Array *arr, int recursion); + static GfxColorSpace *parse(Array *arr, + int recursion); virtual void getGray(GfxColor *color, GfxGray *gray); virtual void getRGB(GfxColor *color, GfxRGB *rgb); @@ -418,7 +425,8 @@ public: virtual GfxColorSpaceMode getMode() { return csIndexed; } // Construct an Indexed color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Array *arr, int recursion); + static GfxColorSpace *parse(Array *arr, + int recursion); virtual void getGray(GfxColor *color, GfxGray *gray); virtual void getRGB(GfxColor *color, GfxRGB *rgb); @@ -457,7 +465,8 @@ public: virtual GfxColorSpaceMode getMode() { return csSeparation; } // Construct a Separation color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Array *arr, int recursion); + static GfxColorSpace *parse(Array *arr, + int recursion); virtual void getGray(GfxColor *color, GfxGray *gray); virtual void getRGB(GfxColor *color, GfxRGB *rgb); @@ -499,7 +508,8 @@ public: virtual GfxColorSpaceMode getMode() { return csDeviceN; } // Construct a DeviceN color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Array *arr, int recursion); + static GfxColorSpace *parse(Array *arr, + int recursion); virtual void getGray(GfxColor *color, GfxGray *gray); virtual void getRGB(GfxColor *color, GfxRGB *rgb); @@ -542,7 +552,8 @@ public: virtual GfxColorSpaceMode getMode() { return csPattern; } // Construct a Pattern color space. Returns NULL if unsuccessful. - static GfxColorSpace *parse(Array *arr, int recursion); + static GfxColorSpace *parse(Array *arr, + int recursion); virtual void getGray(GfxColor *color, GfxGray *gray); virtual void getRGB(GfxColor *color, GfxRGB *rgb); @@ -570,7 +581,8 @@ public: GfxPattern(int typeA); virtual ~GfxPattern(); - static GfxPattern *parse(Object *obj); + static GfxPattern *parse(Object *objRef, Object *obj + ); virtual GfxPattern *copy() = 0; @@ -588,7 +600,7 @@ private: class GfxTilingPattern: public GfxPattern { public: - static GfxTilingPattern *parse(Object *patObj); + static GfxTilingPattern *parse(Object *patObjRef, Object *patObj); virtual ~GfxTilingPattern(); virtual GfxPattern *copy(); @@ -601,7 +613,7 @@ public: Dict *getResDict() { return resDict.isDict() ? resDict.getDict() : (Dict *)NULL; } double *getMatrix() { return matrix; } - Object *getContentStream() { return &contentStream; } + Object *getContentStreamRef() { return &contentStreamRef; } private: @@ -616,7 +628,7 @@ private: double xStep, yStep; Object resDict; double matrix[6]; - Object contentStream; + Object contentStreamRef; }; //------------------------------------------------------------------------ @@ -626,7 +638,8 @@ private: class GfxShadingPattern: public GfxPattern { public: - static GfxShadingPattern *parse(Object *patObj); + static GfxShadingPattern *parse(Object *patObj + ); virtual ~GfxShadingPattern(); virtual GfxPattern *copy(); @@ -653,7 +666,8 @@ public: GfxShading(GfxShading *shading); virtual ~GfxShading(); - static GfxShading *parse(Object *obj); + static GfxShading *parse(Object *obj + ); virtual GfxShading *copy() = 0; @@ -667,7 +681,8 @@ public: protected: - GBool init(Dict *dict); + GBool init(Dict *dict + ); int type; GfxColorSpace *colorSpace; @@ -691,7 +706,8 @@ public: GfxFunctionShading(GfxFunctionShading *shading); virtual ~GfxFunctionShading(); - static GfxFunctionShading *parse(Dict *dict); + static GfxFunctionShading *parse(Dict *dict + ); virtual GfxShading *copy(); @@ -725,7 +741,8 @@ public: GfxAxialShading(GfxAxialShading *shading); virtual ~GfxAxialShading(); - static GfxAxialShading *parse(Dict *dict); + static GfxAxialShading *parse(Dict *dict + ); virtual GfxShading *copy(); @@ -763,7 +780,8 @@ public: GfxRadialShading(GfxRadialShading *shading); virtual ~GfxRadialShading(); - static GfxRadialShading *parse(Dict *dict); + static GfxRadialShading *parse(Dict *dict + ); virtual GfxShading *copy(); @@ -793,7 +811,7 @@ private: struct GfxGouraudVertex { double x, y; - GfxColor color; + double color[gfxColorMaxComps]; }; class GfxGouraudTriangleShading: public GfxShading { @@ -802,18 +820,21 @@ public: GfxGouraudTriangleShading(int typeA, GfxGouraudVertex *verticesA, int nVerticesA, int (*trianglesA)[3], int nTrianglesA, - Function **funcsA, int nFuncsA); + int nCompsA, Function **funcsA, int nFuncsA); GfxGouraudTriangleShading(GfxGouraudTriangleShading *shading); virtual ~GfxGouraudTriangleShading(); - static GfxGouraudTriangleShading *parse(int typeA, Dict *dict, Stream *str); + static GfxGouraudTriangleShading *parse(int typeA, Dict *dict, Stream *str + ); virtual GfxShading *copy(); + int getNComps() { return nComps; } int getNTriangles() { return nTriangles; } - void getTriangle(int i, double *x0, double *y0, GfxColor *color0, - double *x1, double *y1, GfxColor *color1, - double *x2, double *y2, GfxColor *color2); + void getTriangle(int i, double *x0, double *y0, double *color0, + double *x1, double *y1, double *color1, + double *x2, double *y2, double *color2); + void getColor(double *in, GfxColor *out); private: @@ -822,6 +843,7 @@ private: int (*triangles)[3]; int nTriangles; Function *funcs[gfxColorMaxComps]; + int nComps; // number of color components (1 if nFuncs > 0) int nFuncs; }; @@ -832,29 +854,33 @@ private: struct GfxPatch { double x[4][4]; double y[4][4]; - GfxColor color[2][2]; + double color[2][2][gfxColorMaxComps]; }; class GfxPatchMeshShading: public GfxShading { public: GfxPatchMeshShading(int typeA, GfxPatch *patchesA, int nPatchesA, - Function **funcsA, int nFuncsA); + int nCompsA, Function **funcsA, int nFuncsA); GfxPatchMeshShading(GfxPatchMeshShading *shading); virtual ~GfxPatchMeshShading(); - static GfxPatchMeshShading *parse(int typeA, Dict *dict, Stream *str); + static GfxPatchMeshShading *parse(int typeA, Dict *dict, Stream *str + ); virtual GfxShading *copy(); + int getNComps() { return nComps; } int getNPatches() { return nPatches; } GfxPatch *getPatch(int i) { return &patches[i]; } + void getColor(double *in, GfxColor *out); private: GfxPatch *patches; int nPatches; Function *funcs[gfxColorMaxComps]; + int nComps; // number of color components (1 if nFuncs > 0) int nFuncs; }; @@ -1040,7 +1066,8 @@ public: // x , page box , page rotation , and // coordinate system specified by . GfxState(double hDPIA, double vDPIA, PDFRectangle *pageBox, - int rotateA, GBool upsideDown); + int rotateA, GBool upsideDown + ); // Destructor. ~GfxState(); diff -uNrp xpdf-3.03/xpdf/GlobalParams.cc xpdf-3.04/xpdf/GlobalParams.cc --- xpdf-3.03/xpdf/GlobalParams.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/GlobalParams.cc 2014-05-28 20:50:50.000000000 +0200 @@ -12,15 +12,19 @@ #pragma implementation #endif +#ifdef _WIN32 +# define _WIN32_WINNT 0x0500 // for GetSystemWindowsDirectory +# include +#endif #include #include #include #ifdef ENABLE_PLUGINS @@ -31,6 +35,7 @@ #include "GList.h" #include "GHash.h" #include "gfile.h" +#include "FoFiIdentifier.h" #include "Error.h" #include "NameToCharCode.h" #include "CharCodeToUnicode.h" @@ -43,8 +48,9 @@ #endif #include "GlobalParams.h" #ifdef _WIN32 # define strcasecmp stricmp +# define strncasecmp strnicmp #endif #if MULTITHREADED @@ -84,25 +90,29 @@ static struct { const char *name; const char *t1FileName; const char *ttFileName; + const char *macFileName; // may be .dfont, .ttf, or .ttc + const char *macFontName; // font name inside .dfont or .ttc + const char *obliqueFont; // name of font to oblique + double obliqueFactor; // oblique sheer factor } displayFontTab[] = { - {"Courier", "n022003l.pfb", "cour.ttf"}, - {"Courier-Bold", "n022004l.pfb", "courbd.ttf"}, - {"Courier-BoldOblique", "n022024l.pfb", "courbi.ttf"}, - {"Courier-Oblique", "n022023l.pfb", "couri.ttf"}, - {"Helvetica", "n019003l.pfb", "arial.ttf"}, - {"Helvetica-Bold", "n019004l.pfb", "arialbd.ttf"}, - {"Helvetica-BoldOblique", "n019024l.pfb", "arialbi.ttf"}, - {"Helvetica-Oblique", "n019023l.pfb", "ariali.ttf"}, - {"Symbol", "s050000l.pfb", NULL}, - {"Times-Bold", "n021004l.pfb", "timesbd.ttf"}, - {"Times-BoldItalic", "n021024l.pfb", "timesbi.ttf"}, - {"Times-Italic", "n021023l.pfb", "timesi.ttf"}, - {"Times-Roman", "n021003l.pfb", "times.ttf"}, - {"ZapfDingbats", "d050000l.pfb", NULL}, + {"Courier", "n022003l.pfb", "cour.ttf", "Courier", "Courier", NULL, 0}, + {"Courier-Bold", "n022004l.pfb", "courbd.ttf", "Courier", "Courier Bold", NULL, 0}, + {"Courier-BoldOblique", "n022024l.pfb", "courbi.ttf", "Courier", "Courier Bold Oblique", "Courier-Bold", 0.212557}, + {"Courier-Oblique", "n022023l.pfb", "couri.ttf", "Courier", "Courier Oblique", "Courier", 0.212557}, + {"Helvetica", "n019003l.pfb", "arial.ttf", "Helvetica", "Helvetica", NULL, 0}, + {"Helvetica-Bold", "n019004l.pfb", "arialbd.ttf", "Helvetica", "Helvetica-Bold", NULL, 0}, + {"Helvetica-BoldOblique", "n019024l.pfb", "arialbi.ttf", "Helvetica", "Helvetica Bold Oblique", "Helvetica-Bold", 0.212557}, + {"Helvetica-Oblique", "n019023l.pfb", "ariali.ttf", "Helvetica", "Helvetica Oblique", "Helvetica", 0.212557}, + {"Symbol", "s050000l.pfb", NULL, "Symbol", "Symbol", NULL, 0}, + {"Times-Bold", "n021004l.pfb", "timesbd.ttf", "Times", "Times-Bold", NULL, 0}, + {"Times-BoldItalic", "n021024l.pfb", "timesbi.ttf", "Times", "Times-BoldItalic", NULL, 0}, + {"Times-Italic", "n021023l.pfb", "timesi.ttf", "Times", "Times-Italic", NULL, 0}, + {"Times-Roman", "n021003l.pfb", "times.ttf", "Times", "Times-Roman", NULL, 0}, + {"ZapfDingbats", "d050000l.pfb", NULL, "ZapfDingbats", "Zapf Dingbats", NULL, 0}, {NULL} }; -#ifdef WIN32 +#ifdef _WIN32 static const char *displayFontDirs[] = { "c:/windows/fonts", "c:/winnt/fonts", @@ -115,10 +125,31 @@ static const char *displayFontDirs[] = { "/usr/share/fonts/default/Type1", "/usr/share/fonts/default/ghostscript", "/usr/share/fonts/type1/gsfonts", +#if defined(__sun) && defined(__SVR4) + "/usr/sfw/share/ghostscript/fonts", +#endif NULL }; #endif +#ifdef __APPLE__ +static const char *macSystemFontPath = "/System/Library/Fonts"; +#endif + +struct Base14FontInfo { + Base14FontInfo(GString *fileNameA, int fontNumA, double obliqueA) { + fileName = fileNameA; + fontNum = fontNumA; + oblique = obliqueA; + } + ~Base14FontInfo() { + delete fileName; + } + GString *fileName; + int fontNum; + double oblique; +}; + //------------------------------------------------------------------------ GlobalParams *globalParams = NULL; @@ -241,40 +272,36 @@ SysFontInfo *SysFontList::find(GString * } n = name2->getLength(); - // remove trailing "MT" (Foo-MT, Foo-BoldMT, etc.) - if (n > 2 && !strcmp(name2->getCString() + n - 2, "MT")) { - name2->del(n - 2, 2); - n -= 2; - } + // font names like "Arial-BoldMT,Bold" are occasionally used, + // so run this loop twice + bold = italic = gFalse; + for (i = 0; i < 2; ++i) { - // look for "Regular" - if (n > 7 && !strcmp(name2->getCString() + n - 7, "Regular")) { - name2->del(n - 7, 7); - n -= 7; - } + // 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 "Italic" - if (n > 6 && !strcmp(name2->getCString() + n - 6, "Italic")) { - name2->del(n - 6, 6); - italic = gTrue; - n -= 6; - } else { - italic = gFalse; - } + // look for "Regular" + if (n > 7 && !strcmp(name2->getCString() + n - 7, "Regular")) { + name2->del(n - 7, 7); + n -= 7; + } - // look for "Bold" - if (n > 4 && !strcmp(name2->getCString() + n - 4, "Bold")) { - name2->del(n - 4, 4); - bold = gTrue; - n -= 4; - } else { - bold = gFalse; - } + // look for "Italic" + if (n > 6 && !strcmp(name2->getCString() + n - 6, "Italic")) { + name2->del(n - 6, 6); + italic = gTrue; + n -= 6; + } - // remove trailing "MT" (FooMT-Bold, etc.) - if (n > 2 && !strcmp(name2->getCString() + n - 2, "MT")) { - name2->del(n - 2, 2); - n -= 2; + // look for "Bold" + if (n > 4 && !strcmp(name2->getCString() + n - 4, "Bold")) { + name2->del(n - 4, 4); + bold = gTrue; + n -= 4; + } } // remove trailing "PS" @@ -341,15 +368,15 @@ void SysFontList::scanWindowsFonts(char } else { path = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Fonts\\"; } - if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, path, 0, - KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, - ®Key) == ERROR_SUCCESS) { + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, path, 0, + KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, + ®Key) == ERROR_SUCCESS) { idx = 0; while (1) { valNameLen = sizeof(valName) - 1; dataLen = sizeof(data) - 1; - if (RegEnumValue(regKey, idx, valName, &valNameLen, NULL, - &type, (LPBYTE)data, &dataLen) != ERROR_SUCCESS) { + if (RegEnumValueA(regKey, idx, valName, &valNameLen, NULL, + &type, (LPBYTE)data, &dataLen) != ERROR_SUCCESS) { break; } if (type == REG_SZ && @@ -357,7 +384,7 @@ void SysFontList::scanWindowsFonts(char dataLen > 0 && dataLen < sizeof(data)) { valName[valNameLen] = '\0'; data[dataLen] = '\0'; - n = strlen(data); + n = (int)strlen(data); if (!strcasecmp(data + n - 4, ".ttf") || !strcasecmp(data + n - 4, ".ttc")) { fontPath = new GString(data); @@ -398,7 +425,7 @@ SysFontInfo *SysFontList::makeWindowsFon int i; SysFontType type; - n = strlen(name); + n = (int)strlen(name); bold = italic = gFalse; // remove trailing ' (TrueType)' @@ -419,7 +446,7 @@ SysFontInfo *SysFontList::makeWindowsFon } // remove trailing ' Regular' - if (n > 5 && !strncmp(name + n - 8, " Regular", 8)) { + if (n > 8 && !strncmp(name + n - 8, " Regular", 8)) { n -= 8; } @@ -515,9 +542,9 @@ Plugin *Plugin::load(char *type, char *n appendToPath(path, type); appendToPath(path, name); #ifdef _WIN32 path->append(".dll"); - if (!(libA = LoadLibrary(path->getCString()))) { + if (!(libA = LoadLibraryA(path->getCString()))) { error(errIO, -1, "Failed to load plugin '{0:t}'", path); goto err1; } @@ -621,7 +648,7 @@ Plugin::~Plugin() { // parsing //------------------------------------------------------------------------ -GlobalParams::GlobalParams(char *cfgFileName) { +GlobalParams::GlobalParams(const char *cfgFileName) { UnicodeMap *map; GString *fileName; FILE *f; @@ -660,6 +687,7 @@ GlobalParams::GlobalParams(char *cfgFile fontFiles = new GHash(gTrue); fontDirs = new GList(); ccFontFiles = new GHash(gTrue); + base14SysFonts = new GHash(gTrue); sysFonts = new SysFontList(); #if HAVE_PAPER_H char *paperName; @@ -683,6 +711,7 @@ GlobalParams::GlobalParams(char *cfgFile psImageableURX = psPaperWidth; psImageableURY = psPaperHeight; psCrop = gTrue; + psUseCropBoxAsPage = gFalse; psExpandSmaller = gFalse; psShrinkLarger = gTrue; psCenter = gTrue; @@ -700,12 +729,15 @@ GlobalParams::GlobalParams(char *cfgFile psPreload = gFalse; psOPI = gFalse; psASCIIHex = gFalse; + psLZW = gTrue; psUncompressPreloadedImages = gFalse; + psMinLineWidth = 0; psRasterResolution = 300; psRasterMono = gFalse; + psRasterSliceSize = 20000000; psAlwaysRasterize = gFalse; textEncoding = new GString("Latin1"); @@ -713,10 +745,9 @@ GlobalParams::GlobalParams(char *cfgFile textEOL = eolUnix; #endif textPageBreaks = gTrue; - textKeepTinyChars = gFalse; + textKeepTinyChars = gTrue; initialZoom = new GString("125"); continuousView = gFalse; - enableT1lib = gTrue; enableFreeType = gTrue; disableFreeTypeHinting = gFalse; antialias = gTrue; @@ -737,6 +768,8 @@ GlobalParams::GlobalParams(char *cfgFile movieCommand = NULL; mapNumericCharNames = gTrue; mapUnknownCharNames = gFalse; + mapExtTrueTypeFontsViaUnicode = gTrue; + enableXFA = gTrue; createDefaultKeyBindings(); printCommands = gFalse; errQuiet = gFalse; @@ -791,9 +824,9 @@ GlobalParams::GlobalParams(char *cfgFile } } if (!f) { #ifdef _WIN32 char buf[512]; - i = GetModuleFileName(NULL, buf, sizeof(buf)); + i = GetModuleFileNameA(NULL, buf, sizeof(buf)); if (i <= 0 || i >= sizeof(buf)) { // error or path too long for buffer - just use the current dir buf[0] = '\0'; @@ -928,7 +961,7 @@ void GlobalParams::createDefaultKeyBindi keyBindings->append(new KeyBinding('l', xpdfKeyModCtrl, xpdfKeyContextAny, "redraw")); keyBindings->append(new KeyBinding('w', xpdfKeyModCtrl, - xpdfKeyContextAny, "closeWindow")); + xpdfKeyContextAny, "closeWindowOrQuit")); keyBindings->append(new KeyBinding('?', xpdfKeyModNone, xpdfKeyContextAny, "about")); keyBindings->append(new KeyBinding('q', xpdfKeyModNone, @@ -1017,6 +1050,9 @@ void GlobalParams::parseLine(char *buf, parsePSImageableArea(tokens, fileName, line); } else if (!cmd->cmp("psCrop")) { parseYesNo("psCrop", &psCrop, tokens, fileName, line); + } else if (!cmd->cmp("psUseCropBoxAsPage")) { + parseYesNo("psUseCropBoxAsPage", &psUseCropBoxAsPage, + tokens, fileName, line); } else if (!cmd->cmp("psExpandSmaller")) { parseYesNo("psExpandSmaller", &psExpandSmaller, tokens, fileName, line); @@ -1054,14 +1090,22 @@ void GlobalParams::parseLine(char *buf, parseYesNo("psOPI", &psOPI, tokens, fileName, line); } else if (!cmd->cmp("psASCIIHex")) { parseYesNo("psASCIIHex", &psASCIIHex, tokens, fileName, line); + } else if (!cmd->cmp("psLZW")) { + parseYesNo("psLZW", &psLZW, tokens, fileName, line); } else if (!cmd->cmp("psUncompressPreloadedImages")) { parseYesNo("psUncompressPreloadedImages", &psUncompressPreloadedImages, tokens, fileName, line); + } else if (!cmd->cmp("psMinLineWidth")) { + parseFloat("psMinLineWidth", &psMinLineWidth, + tokens, fileName, line); } else if (!cmd->cmp("psRasterResolution")) { parseFloat("psRasterResolution", &psRasterResolution, tokens, fileName, line); } else if (!cmd->cmp("psRasterMono")) { parseYesNo("psRasterMono", &psRasterMono, tokens, fileName, line); + } else if (!cmd->cmp("psRasterSliceSize")) { + parseInteger("psRasterSliceSize", &psRasterSliceSize, + tokens, fileName, line); } else if (!cmd->cmp("psAlwaysRasterize")) { parseYesNo("psAlwaysRasterize", &psAlwaysRasterize, tokens, fileName, line); @@ -1079,8 +1123,6 @@ void GlobalParams::parseLine(char *buf, parseInitialZoom(tokens, fileName, line); } else if (!cmd->cmp("continuousView")) { parseYesNo("continuousView", &continuousView, tokens, fileName, line); - } else if (!cmd->cmp("enableT1lib")) { - parseYesNo("enableT1lib", &enableT1lib, tokens, fileName, line); } else if (!cmd->cmp("enableFreeType")) { parseYesNo("enableFreeType", &enableFreeType, tokens, fileName, line); } else if (!cmd->cmp("disableFreeTypeHinting")) { @@ -1133,6 +1175,12 @@ void GlobalParams::parseLine(char *buf, } else if (!cmd->cmp("mapUnknownCharNames")) { parseYesNo("mapUnknownCharNames", &mapUnknownCharNames, tokens, fileName, line); + } else if (!cmd->cmp("mapExtTrueTypeFontsViaUnicode")) { + parseYesNo("mapExtTrueTypeFontsViaUnicode", + &mapExtTrueTypeFontsViaUnicode, + 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")) { @@ -1148,6 +1196,8 @@ void GlobalParams::parseLine(char *buf, !cmd->cmp("displayNamedCIDFontX") || !cmd->cmp("displayCIDFontX")) { error(errConfig, -1, "Xpdf no longer supports X fonts"); + } else if (!cmd->cmp("enableT1lib")) { + error(errConfig, -1, "Xpdf no longer uses t1lib"); } else if (!cmd->cmp("t1libControl") || !cmd->cmp("freetypeControl")) { error(errConfig, -1, "The t1libControl and freetypeControl options have been replaced by the enableT1lib, enableFreeType, and antialias options"); @@ -1520,6 +1570,7 @@ void GlobalParams::parseScreenType(GList } } + void GlobalParams::parseBind(GList *tokens, GString *fileName, int line) { KeyBinding *binding; GList *cmds; @@ -1836,6 +1887,7 @@ GlobalParams::~GlobalParams() { deleteGHash(fontFiles, GString); deleteGList(fontDirs, GString); deleteGHash(ccFontFiles, GString); + deleteGHash(base14SysFonts, Base14FontInfo); delete sysFonts; if (psFile) { delete psFile; @@ -1886,35 +1938,77 @@ void GlobalParams::setBaseDir(char *dir) baseDir = new GString(dir); } -void GlobalParams::setupBaseFonts(char *dir) { - GString *fontName; - GString *fileName; -#ifdef WIN32 +#ifdef _WIN32 +static void getWinFontDir(char *winFontDir) { HMODULE shell32Lib; BOOL (__stdcall *SHGetSpecialFolderPathFunc)(HWND hwndOwner, - LPTSTR lpszPath, + LPSTR lpszPath, int nFolder, BOOL fCreate); - char winFontDir[MAX_PATH]; -#endif - FILE *f; - int i, j; + char *p; + int i; -#ifdef WIN32 // SHGetSpecialFolderPath isn't available in older versions of // shell32.dll (Win95 and WinNT4), so do a dynamic load winFontDir[0] = '\0'; - if ((shell32Lib = LoadLibrary("shell32.dll"))) { + if ((shell32Lib = LoadLibraryA("shell32.dll"))) { if ((SHGetSpecialFolderPathFunc = - (BOOL (__stdcall *)(HWND hwndOwner, LPTSTR lpszPath, + (BOOL (__stdcall *)(HWND hwndOwner, LPSTR lpszPath, int nFolder, BOOL fCreate)) GetProcAddress(shell32Lib, "SHGetSpecialFolderPathA"))) { if (!(*SHGetSpecialFolderPathFunc)(NULL, winFontDir, CSIDL_FONTS, FALSE)) { winFontDir[0] = '\0'; } + // kludge: Terminal Server changes CSIDL_FONTS to something like + // "C:\Users\whatever\Windows\Fonts", which doesn't actually + // contain any fonts -- kill that, so we hit the fallback code + // below. + for (p = winFontDir; *p; ++p) { + if (!strncasecmp(p, "\\Users\\", 7)) { + winFontDir[0] = '\0'; + break; + } + } + } + } + // if something went wrong, or we're on a Terminal Server, try using + // %SYSTEMROOT%\Fonts + if (!winFontDir[0]) { + GetSystemWindowsDirectoryA(winFontDir, MAX_PATH - 6); + winFontDir[MAX_PATH - 7] = '\0'; + i = (int)strlen(winFontDir); + if (winFontDir[i-1] != '\\') { + winFontDir[i++] = '\\'; } + strcpy(winFontDir + i, "Fonts"); } +} +#endif + +void GlobalParams::setupBaseFonts(char *dir) { + GString *fontName; + GString *fileName; + int fontNum; + const char *s; + Base14FontInfo *base14; +#ifdef _WIN32 + char winFontDir[MAX_PATH]; +#endif +#ifdef __APPLE__ + static const char *macFontExts[3] = { "dfont", "ttc", "ttf" }; + GList *dfontFontNames; + GBool found; + int k; +#endif + FILE *f; + int i, j; + +#ifdef _WIN32 + getWinFontDir(winFontDir); +#endif +#ifdef __APPLE__ + dfontFontNames = NULL; #endif for (i = 0; displayFontTab[i].name; ++i) { if (fontFiles->lookup(displayFontTab[i].name)) { @@ -1922,6 +2016,7 @@ void GlobalParams::setupBaseFonts(char * } fontName = new GString(displayFontTab[i].name); fileName = NULL; + fontNum = 0; if (dir) { fileName = appendToPath(new GString(dir), displayFontTab[i].t1FileName); if ((f = fopen(fileName->getCString(), "rb"))) { @@ -1942,13 +2037,67 @@ void GlobalParams::setupBaseFonts(char * fileName = NULL; } } - // SHGetSpecialFolderPath(CSIDL_FONTS) doesn't work on Win 2k Server - // or Win2003 Server, or with older versions of shell32.dll, so check - // the "standard" directories - if (displayFontTab[i].ttFileName) { +#endif +#ifdef __APPLE__ + // Check for Mac OS X system fonts. + s = displayFontTab[i].macFileName; + if (dfontFontNames && i > 0 && + (!s || strcmp(s, displayFontTab[i-1].macFileName))) { + deleteGList(dfontFontNames, GString); + dfontFontNames = NULL; + } + if (!fileName && s) { + for (j = 0; j < 3; ++j) { + fileName = GString::format("{0:s}/{1:s}.{2:s}", + macSystemFontPath, s, macFontExts[j]); + if (!(f = fopen(fileName->getCString(), "rb"))) { + delete fileName; + fileName = NULL; + } else { + fclose(f); + found = gFalse; + // for .dfont or .ttc, we need to scan the font list + if (j < 2) { + if (!dfontFontNames) { + dfontFontNames = + FoFiIdentifier::getFontList(fileName->getCString()); + } + if (dfontFontNames) { + for (k = 0; k < dfontFontNames->getLength(); ++k) { + if (!((GString *)dfontFontNames->get(k)) + ->cmp(displayFontTab[i].macFontName)) { + fontNum = k; + found = gTrue; + break; + } + } + } + // for .ttf, we just use the font + } else { + found = gTrue; + } + if (!found) { + delete fileName; + fileName = NULL; + } + break; + } + } + } +#endif // __APPLE__ + // On Linux, this checks the "standard" ghostscript font + // directories. On Windows, it checks the "standard" system font + // directories (because SHGetSpecialFolderPath(CSIDL_FONTS) + // doesn't work on Win 2k Server or Win2003 Server, or with older + // versions of shell32.dll). +#ifdef _WIN32 + s = displayFontTab[i].ttFileName; +#else + s = displayFontTab[i].t1FileName; +#endif + if (!fileName && s) { for (j = 0; !fileName && displayFontDirs[j]; ++j) { - fileName = appendToPath(new GString(displayFontDirs[j]), - displayFontTab[i].ttFileName); + fileName = appendToPath(new GString(displayFontDirs[j]), s); if ((f = fopen(fileName->getCString(), "rb"))) { fclose(f); } else { @@ -1957,28 +2106,35 @@ void GlobalParams::setupBaseFonts(char * } } } -#else // WIN32 - for (j = 0; !fileName && displayFontDirs[j]; ++j) { - fileName = appendToPath(new GString(displayFontDirs[j]), - displayFontTab[i].t1FileName); - if ((f = fopen(fileName->getCString(), "rb"))) { - fclose(f); - } else { - delete fileName; - fileName = NULL; - } - } -#endif // WIN32 if (!fileName) { - error(errConfig, -1, "No display font for '{0:s}'", - displayFontTab[i].name); delete fontName; continue; } - addFontFile(fontName, fileName); + base14SysFonts->add(fontName, new Base14FontInfo(fileName, fontNum, 0)); } - -#ifdef WIN32 +#ifdef __APPLE__ + if (dfontFontNames) { + deleteGList(dfontFontNames, GString); + } +#endif + for (i = 0; displayFontTab[i].name; ++i) { + if (!base14SysFonts->lookup(displayFontTab[i].name) && + !fontFiles->lookup(displayFontTab[i].name)) { + if (displayFontTab[i].obliqueFont && + ((base14 = (Base14FontInfo *)base14SysFonts + ->lookup(displayFontTab[i].obliqueFont)))) { + base14SysFonts->add( + new GString(displayFontTab[i].name), + new Base14FontInfo(base14->fileName->copy(), + base14->fontNum, + displayFontTab[i].obliqueFactor)); + } else { + error(errConfig, -1, "No display font for '{0:s}'", + displayFontTab[i].name); + } + } + } +#ifdef _WIN32 if (winFontDir[0]) { sysFonts->scanWindowsFonts(winFontDir); } @@ -2120,6 +2276,25 @@ GString *GlobalParams::findFontFile(GStr return NULL; } +GString *GlobalParams::findBase14FontFile(GString *fontName, int *fontNum, + double *oblique) { + Base14FontInfo *fi; + GString *path; + + lockGlobalParams; + if ((fi = (Base14FontInfo *)base14SysFonts->lookup(fontName))) { + path = fi->fileName->copy(); + *fontNum = fi->fontNum; + *oblique = fi->oblique; + unlockGlobalParams; + return path; + } + unlockGlobalParams; + *fontNum = 0; + *oblique = 0; + return findFontFile(fontName); +} + GString *GlobalParams::findSystemFontFile(GString *fontName, SysFontType *type, int *fontNum) { @@ -2193,6 +2368,15 @@ GBool GlobalParams::getPSCrop() { return f; } +GBool GlobalParams::getPSUseCropBoxAsPage() { + GBool f; + + lockGlobalParams; + f = psUseCropBoxAsPage; + unlockGlobalParams; + return f; +} + GBool GlobalParams::getPSExpandSmaller() { GBool f; @@ -2242,7 +2426,9 @@ GString *GlobalParams::getPSResidentFont GString *psName; lockGlobalParams; - psName = (GString *)psResidentFonts->lookup(fontName); + if ((psName = (GString *)psResidentFonts->lookup(fontName))) { + psName = psName->copy(); + } unlockGlobalParams; return psName; } @@ -2371,6 +2557,15 @@ GBool GlobalParams::getPSASCIIHex() { return ah; } +GBool GlobalParams::getPSLZW() { + GBool ah; + + lockGlobalParams; + ah = psLZW; + unlockGlobalParams; + return ah; +} + GBool GlobalParams::getPSUncompressPreloadedImages() { GBool ah; @@ -2380,6 +2575,15 @@ GBool GlobalParams::getPSUncompressPrelo return ah; } +double GlobalParams::getPSMinLineWidth() { + double w; + + lockGlobalParams; + w = psMinLineWidth; + unlockGlobalParams; + return w; +} + double GlobalParams::getPSRasterResolution() { double res; @@ -2398,6 +2602,15 @@ GBool GlobalParams::getPSRasterMono() { return mono; } +int GlobalParams::getPSRasterSliceSize() { + int slice; + + lockGlobalParams; + slice = psRasterSliceSize; + unlockGlobalParams; + return slice; +} + GBool GlobalParams::getPSAlwaysRasterize() { GBool rast; @@ -2461,15 +2674,6 @@ GBool GlobalParams::getContinuousView() return f; } -GBool GlobalParams::getEnableT1lib() { - GBool f; - - lockGlobalParams; - f = enableT1lib; - unlockGlobalParams; - return f; -} - GBool GlobalParams::getEnableFreeType() { GBool f; @@ -2597,6 +2801,7 @@ GBool GlobalParams::getDrawAnnotations() return draw; } + GBool GlobalParams::getMapNumericCharNames() { GBool map; @@ -2615,6 +2820,24 @@ GBool GlobalParams::getMapUnknownCharNam return map; } +GBool GlobalParams::getMapExtTrueTypeFontsViaUnicode() { + GBool map; + + lockGlobalParams; + map = mapExtTrueTypeFontsViaUnicode; + unlockGlobalParams; + return map; +} + +GBool GlobalParams::getEnableXFA() { + GBool enable; + + lockGlobalParams; + enable = enableXFA; + unlockGlobalParams; + return enable; +} + GList *GlobalParams::getKeyBinding(int code, int mods, int context) { KeyBinding *binding; GList *cmds; @@ -2804,6 +3027,12 @@ void GlobalParams::setPSCrop(GBool crop) unlockGlobalParams; } +void GlobalParams::setPSUseCropBoxAsPage(GBool crop) { + lockGlobalParams; + psUseCropBoxAsPage = crop; + unlockGlobalParams; +} + void GlobalParams::setPSExpandSmaller(GBool expand) { lockGlobalParams; psExpandSmaller = expand; @@ -2882,7 +3111,7 @@ void GlobalParams::setPSASCIIHex(GBool h unlockGlobalParams; } -void GlobalParams::setTextEncoding(char *encodingName) { +void GlobalParams::setTextEncoding(const char *encodingName) { lockGlobalParams; delete textEncoding; textEncoding = new GString(encodingName); @@ -2930,15 +3159,6 @@ void GlobalParams::setContinuousView(GBo unlockGlobalParams; } -GBool GlobalParams::setEnableT1lib(char *s) { - GBool ok; - - lockGlobalParams; - ok = parseYesNo2(s, &enableT1lib); - unlockGlobalParams; - return ok; -} - GBool GlobalParams::setEnableFreeType(char *s) { GBool ok; @@ -3015,6 +3235,18 @@ void GlobalParams::setMapUnknownCharName unlockGlobalParams; } +void GlobalParams::setMapExtTrueTypeFontsViaUnicode(GBool map) { + lockGlobalParams; + mapExtTrueTypeFontsViaUnicode = map; + unlockGlobalParams; +} + +void GlobalParams::setEnableXFA(GBool enable) { + lockGlobalParams; + enableXFA = enable; + unlockGlobalParams; +} + void GlobalParams::setPrintCommands(GBool printCommandsA) { lockGlobalParams; printCommands = printCommandsA; diff -uNrp xpdf-3.03/xpdf/GlobalParams.h xpdf-3.04/xpdf/GlobalParams.h --- xpdf-3.03/xpdf/GlobalParams.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/GlobalParams.h 2014-05-28 20:50:50.000000000 +0200 @@ -173,7 +173,7 @@ public: // Initialize the global parameters by attempting to read a config // file. - GlobalParams(char *cfgFileName); + GlobalParams(const char *cfgFileName); ~GlobalParams(); @@ -193,6 +193,8 @@ public: FILE *findCMapFile(GString *collection, GString *cMapName); FILE *findToUnicodeFile(GString *name); GString *findFontFile(GString *fontName); + GString *findBase14FontFile(GString *fontName, int *fontNum, + double *oblique); GString *findSystemFontFile(GString *fontName, SysFontType *type, int *fontNum); GString *findCCFontFile(GString *collection); @@ -202,6 +204,7 @@ public: void getPSImageableArea(int *llx, int *lly, int *urx, int *ury); GBool getPSDuplex(); GBool getPSCrop(); + GBool getPSUseCropBoxAsPage(); GBool getPSExpandSmaller(); GBool getPSShrinkLarger(); GBool getPSCenter(); @@ -218,9 +221,12 @@ public: GBool getPSPreload(); GBool getPSOPI(); GBool getPSASCIIHex(); + GBool getPSLZW(); GBool getPSUncompressPreloadedImages(); + double getPSMinLineWidth(); double getPSRasterResolution(); GBool getPSRasterMono(); + int getPSRasterSliceSize(); GBool getPSAlwaysRasterize(); GString *getTextEncodingName(); EndOfLineKind getTextEOL(); @@ -228,7 +234,6 @@ public: GBool getTextKeepTinyChars(); GString *getInitialZoom(); GBool getContinuousView(); - GBool getEnableT1lib(); GBool getEnableFreeType(); GBool getDisableFreeTypeHinting(); GBool getAntialias(); @@ -249,6 +254,8 @@ public: GString *getMovieCommand() { return movieCommand; } GBool getMapNumericCharNames(); GBool getMapUnknownCharNames(); + GBool getMapExtTrueTypeFontsViaUnicode(); + GBool getEnableXFA(); GList *getKeyBinding(int code, int mods, int context); GBool getPrintCommands(); GBool getErrQuiet(); @@ -269,6 +276,7 @@ public: void setPSImageableArea(int llx, int lly, int urx, int ury); void setPSDuplex(GBool duplex); void setPSCrop(GBool crop); + void setPSUseCropBoxAsPage(GBool crop); void setPSExpandSmaller(GBool expand); void setPSShrinkLarger(GBool shrink); void setPSCenter(GBool center); @@ -281,13 +289,12 @@ public: void setPSPreload(GBool preload); void setPSOPI(GBool opi); void setPSASCIIHex(GBool hex); - void setTextEncoding(char *encodingName); + void setTextEncoding(const char *encodingName); GBool setTextEOL(char *s); void setTextPageBreaks(GBool pageBreaks); void setTextKeepTinyChars(GBool keep); void setInitialZoom(char *s); void setContinuousView(GBool cont); - GBool setEnableT1lib(char *s); GBool setEnableFreeType(char *s); GBool setAntialias(char *s); GBool setVectorAntialias(char *s); @@ -299,6 +306,8 @@ public: void setScreenWhiteThreshold(double thresh); void setMapNumericCharNames(GBool map); void setMapUnknownCharNames(GBool map); + void setMapExtTrueTypeFontsViaUnicode(GBool map); + void setEnableXFA(GBool enable); void setPrintCommands(GBool printCommandsA); void setErrQuiet(GBool errQuietA); @@ -379,6 +388,8 @@ private: GList *fontDirs; // list of font dirs [GString] GHash *ccFontFiles; // character collection font files: // collection name mapped to path [GString] + GHash *base14SysFonts; // Base-14 system font files: font name + // mapped to path [Base14FontInfo] SysFontList *sysFonts; // system fonts GString *psFile; // PostScript file or command (for xpdf) int psPaperWidth; // paper size, in PostScript points, for @@ -388,6 +399,7 @@ private: psImageableURX, psImageableURY; GBool psCrop; // crop PS output to CropBox + GBool psUseCropBoxAsPage; // use CropBox as page size GBool psExpandSmaller; // expand smaller pages to fill paper GBool psShrinkLarger; // shrink larger pages to fit paper GBool psCenter; // center pages on the paper @@ -411,11 +423,15 @@ private: // memory GBool psOPI; // generate PostScript OPI comments? GBool psASCIIHex; // use ASCIIHex instead of ASCII85? + GBool psLZW; // false to use RLE instead of LZW GBool psUncompressPreloadedImages; // uncompress all preloaded images + double psMinLineWidth; // minimum line width for PostScript output double psRasterResolution; // PostScript rasterization resolution (dpi) GBool psRasterMono; // true to do PostScript rasterization // in monochrome (gray); false to do it // in color (RGB/CMYK) + int psRasterSliceSize; // maximum size (pixels) of PostScript + // rasterization slice GBool psAlwaysRasterize; // force PostScript rasterization GString *textEncoding; // encoding (unicodeMap) to use for text // output @@ -425,7 +441,6 @@ private: GBool textKeepTinyChars; // keep all characters in text output GString *initialZoom; // initial zoom level GBool continuousView; // continuous view mode - GBool enableT1lib; // t1lib enable flag GBool enableFreeType; // FreeType enable flag GBool disableFreeTypeHinting; // FreeType hinting disable flag GBool antialias; // font anti-aliasing enable flag @@ -446,6 +461,9 @@ private: GString *movieCommand; // command executed for movie annotations GBool mapNumericCharNames; // map numeric char names (from font subsets)? GBool mapUnknownCharNames; // map unknown char names? + GBool mapExtTrueTypeFontsViaUnicode; // map char codes to GID via Unicode + // for external TrueType fonts? + GBool enableXFA; // enable XFA form rendering GList *keyBindings; // key & mouse button bindings [KeyBinding] GBool printCommands; // print the drawing commands GBool errQuiet; // suppress error messages? diff -uNrp xpdf-3.03/xpdf/HTMLGen.cc xpdf-3.04/xpdf/HTMLGen.cc --- xpdf-3.03/xpdf/HTMLGen.cc 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/HTMLGen.cc 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,583 @@ +//======================================================================== +// +// HTMLGen.cc +// +// Copyright 2010 Glyph & Cog, LLC +// +//======================================================================== + +//~ to do: +//~ - fonts +//~ - underlined? (underlines are present in the background image) +//~ - include the original font name in the CSS entry (before the +//~ generic serif/sans-serif/monospace name) +//~ - check that htmlDir exists and is a directory +//~ - links: +//~ - links to pages +//~ - links to named destinations +//~ - links to URLs +//~ - rotated text should go in the background image +//~ - metadata +//~ - PDF outline + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include +#include +#include "gmem.h" +#include "GString.h" +#include "GList.h" +#include "SplashBitmap.h" +#include "PDFDoc.h" +#include "TextOutputDev.h" +#include "SplashOutputDev.h" +#include "ErrorCodes.h" +#if EVAL_MODE +# include "SplashMath.h" +# include "Splash.h" +# include "BuiltinFontTables.h" +# include "FontEncodingTables.h" +#endif +#include "HTMLGen.h" + +#ifdef _WIN32 +# define strcasecmp stricmp +# define strncasecmp strnicmp +#endif + +//------------------------------------------------------------------------ + +struct FontStyleTagInfo { + const char *tag; + int tagLen; + GBool bold; + GBool italic; +}; + +// NB: these are compared, in order, against the tail of the font +// name, so "BoldItalic" must come before "Italic", etc. +static FontStyleTagInfo fontStyleTags[] = { + {"Roman", 5, gFalse, gFalse}, + {"Regular", 7, gFalse, gFalse}, + {"Condensed", 9, gFalse, gFalse}, + {"CondensedBold", 13, gTrue, gFalse}, + {"CondensedLight", 14, gFalse, gFalse}, + {"SemiBold", 8, gTrue, gFalse}, + {"BoldItalic", 10, gTrue, gTrue}, + {"Bold_Italic", 11, gTrue, gTrue}, + {"BoldOblique", 11, gTrue, gTrue}, + {"Bold_Oblique", 12, gTrue, gTrue}, + {"Bold", 4, gTrue, gFalse}, + {"Italic", 6, gFalse, gTrue}, + {"Oblique", 7, gFalse, gTrue}, + {NULL, 0, gFalse, gFalse} +}; + +struct StandardFontInfo { + const char *name; + GBool fixedWidth; + GBool serif; +}; + +static StandardFontInfo standardFonts[] = { + {"Arial", gFalse, gFalse}, + {"Courier", gTrue, gFalse}, + {"Futura", gFalse, gFalse}, + {"Helvetica", gFalse, gFalse}, + {"Minion", gFalse, gTrue}, + {"NewCenturySchlbk", gFalse, gTrue}, + {"Times", gFalse, gTrue}, + {"TimesNew", gFalse, gTrue}, + {"Times_New", gFalse, gTrue}, + {"Verdana", gFalse, gFalse}, + {"LucidaSans", gFalse, gFalse}, + {NULL, gFalse, gFalse} +}; + +struct SubstFontInfo { + double mWidth; +}; + +// index: {fixed:8, serif:4, sans-serif:0} + bold*2 + italic +static SubstFontInfo substFonts[16] = { + {0.833}, + {0.833}, + {0.889}, + {0.889}, + {0.788}, + {0.722}, + {0.833}, + {0.778}, + {0.600}, + {0.600}, + {0.600}, + {0.600} +}; + +// Map Unicode indexes from the private use area, following the Adobe +// Glyph list. +#define privateUnicodeMapStart 0xf6f9 +#define privateUnicodeMapEnd 0xf7ff +static int +privateUnicodeMap[privateUnicodeMapEnd - privateUnicodeMapStart + 1] = { + 0x0141, 0x0152, 0, 0, 0x0160, 0, 0x017d, // f6f9 + 0, 0, 0, 0, 0, 0, 0, 0, // f700 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f710 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x0021, 0, 0, 0x0024, 0, 0x0026, 0, // f720 + 0, 0, 0, 0, 0, 0, 0, 0, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, // f730 + 0x0038, 0x0039, 0, 0, 0, 0, 0, 0x003f, + 0, 0, 0, 0, 0, 0, 0, 0, // f740 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f750 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, // f760 + 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, // f770 + 0x0058, 0x0059, 0x005a, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f780 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f790 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x00a1, 0x00a2, 0, 0, 0, 0, 0, // f7a0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f7b0 + 0, 0, 0, 0, 0, 0, 0, 0x00bf, + 0, 0, 0, 0, 0, 0, 0, 0, // f7c0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, // f7d0 + 0, 0, 0, 0, 0, 0, 0, 0, + 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7, // f7e0 + 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf, + 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0, // f7f0 + 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x0178 +}; + +//------------------------------------------------------------------------ + +#if EVAL_MODE + +#define EVAL_MODE_MSG "XpdfHTML evaluation - www.glyphandcog.com" + +static void drawEvalModeMsg(SplashOutputDev *out) { + BuiltinFont *bf; + SplashFont *font; + GString *fontName; + char *msg; + SplashCoord mat[4], ident[6]; + SplashCoord diag, size, textW, x, y; + Gushort cw; + int w, h, n, i; + + // get the Helvetica font info + bf = builtinFontSubst[4]; + + msg = EVAL_MODE_MSG; + n = strlen(msg); + + w = out->getBitmap()->getWidth(); + h = out->getBitmap()->getHeight(); + + ident[0] = 1; ident[1] = 0; + ident[2] = 0; ident[3] = -1; + ident[4] = 0; ident[5] = h; + out->getSplash()->setMatrix(ident); + + diag = splashSqrt((SplashCoord)(w*w + h*h)); + size = diag / (0.67 * n); + if (size < 8) { + size = 8; + } + mat[0] = size * (SplashCoord)w / diag; + mat[3] = mat[0]; + mat[1] = size * (SplashCoord)h / diag; + mat[2] = -mat[1]; + fontName = new GString(bf->name); + font = out->getFont(fontName, mat); + delete fontName; + if (!font) { + return; + } + + textW = 0; + for (i = 0; i < n; ++i) { + bf->widths->getWidth(winAnsiEncoding[msg[i] & 0xff], &cw); + textW += size * cw * 0.001; + } + + out->setFillColor(255, 0, 0); + x = 0.5 * (diag - textW) * (SplashCoord)w / diag; + y = 0.5 * (diag - textW) * (SplashCoord)h / diag; + for (i = 0; i < n; ++i) { + out->getSplash()->fillChar(x, y, msg[i], font); + bf->widths->getWidth(winAnsiEncoding[msg[i] & 0xff], &cw); + x += mat[0] * cw * 0.001; + y += mat[1] * cw * 0.001; + } +} +#endif + +//------------------------------------------------------------------------ + +HTMLGen::HTMLGen(double backgroundResolutionA) { + TextOutputControl textOutControl; + SplashColor paperColor; + + ok = gTrue; + + backgroundResolution = backgroundResolutionA; + drawInvisibleText = gTrue; + + // set up the TextOutputDev + textOutControl.mode = textOutReadingOrder; + textOutControl.html = gTrue; + textOut = new TextOutputDev(NULL, &textOutControl, gFalse); + if (!textOut->isOk()) { + ok = gFalse; + } + + // set up the SplashOutputDev + paperColor[0] = paperColor[1] = paperColor[2] = 0xff; + splashOut = new SplashOutputDev(splashModeRGB8, 1, gFalse, paperColor); + splashOut->setSkipText(gTrue, gFalse); +} + +HTMLGen::~HTMLGen() { + delete textOut; + delete splashOut; +} + +void HTMLGen::startDoc(PDFDoc *docA) { + doc = docA; + splashOut->startDoc(doc->getXRef()); +} + +static inline int pr(int (*writeFunc)(void *stream, const char *data, int size), + void *stream, const char *data) { + return writeFunc(stream, data, (int)strlen(data)); +} + +static int pf(int (*writeFunc)(void *stream, const char *data, int size), + void *stream, const char *fmt, ...) { + va_list args; + GString *s; + int ret; + + va_start(args, fmt); + s = GString::formatv(fmt, args); + va_end(args); + ret = writeFunc(stream, s->getCString(), s->getLength()); + delete s; + return ret; +} + +struct PNGWriteInfo { + int (*writePNG)(void *stream, const char *data, int size); + void *pngStream; +}; + +static void pngWriteFunc(png_structp png, png_bytep data, png_size_t size) { + PNGWriteInfo *info; + + info = (PNGWriteInfo *)png_get_progressive_ptr(png); + info->writePNG(info->pngStream, (char *)data, (int)size); +} + +int HTMLGen::convertPage( + int pg, const char *pngURL, + int (*writeHTML)(void *stream, const char *data, int size), + void *htmlStream, + int (*writePNG)(void *stream, const char *data, int size), + void *pngStream) { + png_structp png; + png_infop pngInfo; + PNGWriteInfo writeInfo; + SplashBitmap *bitmap; + Guchar *p; + double pageW, pageH; + TextPage *text; + GList *fonts, *cols, *pars, *lines, *words; + double *fontScales; + TextFontInfo *font; + TextColumn *col; + TextParagraph *par; + TextLine *line; + TextWord *word0, *word1; + GString *s; + double base, base1; + int subSuper0, subSuper1; + double r0, g0, b0, r1, g1, b1; + int colIdx, parIdx, lineIdx, wordIdx; + int y, i, u; + + // generate the background bitmap + doc->displayPage(splashOut, pg, backgroundResolution, backgroundResolution, + 0, gFalse, gTrue, gFalse); +#if EVAL_MODE + drawEvalModeMsg(splashOut); +#endif + bitmap = splashOut->getBitmap(); + if (!(png = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL)) || + !(pngInfo = png_create_info_struct(png))) { + return errFileIO; + } + if (setjmp(png_jmpbuf(png))) { + return errFileIO; + } + writeInfo.writePNG = writePNG; + writeInfo.pngStream = pngStream; + png_set_write_fn(png, &writeInfo, pngWriteFunc, NULL); + png_set_IHDR(png, pngInfo, bitmap->getWidth(), bitmap->getHeight(), + 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(png, pngInfo); + p = bitmap->getDataPtr(); + for (y = 0; y < bitmap->getHeight(); ++y) { + png_write_row(png, (png_bytep)p); + p += bitmap->getRowSize(); + } + png_write_end(png, pngInfo); + png_destroy_write_struct(&png, &pngInfo); + + // page size + pageW = doc->getPageCropWidth(pg); + pageH = doc->getPageCropHeight(pg); + + // get the PDF text + doc->displayPage(textOut, pg, 72, 72, 0, gFalse, gTrue, gFalse); + doc->processLinks(textOut, pg); + text = textOut->takeText(); + + // HTML header + pr(writeHTML, htmlStream, "\n"); + pr(writeHTML, htmlStream, "\n"); + pr(writeHTML, htmlStream, "\n"); + pr(writeHTML, htmlStream, "\n"); + pr(writeHTML, htmlStream, "\n"); + pr(writeHTML, htmlStream, "\n"); + pf(writeHTML, htmlStream, "\n", + (int)pageW, (int)pageH, pngURL); + + // generate the HTML text + cols = text->makeColumns(); + for (colIdx = 0; colIdx < cols->getLength(); ++colIdx) { + col = (TextColumn *)cols->get(colIdx); + pars = col->getParagraphs(); + for (parIdx = 0; parIdx < pars->getLength(); ++parIdx) { + par = (TextParagraph *)pars->get(parIdx); + lines = par->getLines(); + for (lineIdx = 0; lineIdx < lines->getLength(); ++lineIdx) { + line = (TextLine *)lines->get(lineIdx); + if (line->getRotation() != 0) { + continue; + } + words = line->getWords(); + base = line->getBaseline(); + s = new GString(); + word0 = NULL; + subSuper0 = 0; // make gcc happy + r0 = g0 = b0 = 0; // make gcc happy + for (wordIdx = 0; wordIdx < words->getLength(); ++wordIdx) { + word1 = (TextWord *)words->get(wordIdx); + if (!drawInvisibleText && word1->isInvisible()) { + continue; + } + word1->getColor(&r1, &g1, &b1); + base1 = word1->getBaseline(); + if (base1 - base < -1) { + subSuper1 = -1; // superscript + } else if (base1 - base > 1) { + subSuper1 = 1; // subscript + } else { + subSuper1 = 0; + } + if (!word0 || + word1->getFontInfo() != word0->getFontInfo() || + word1->getFontSize() != word0->getFontSize() || + subSuper1 != subSuper0 || + r1 != r0 || g1 != g0 || b1 != b0) { + if (word0) { + s->append(""); + } + for (i = 0; i < fonts->getLength(); ++i) { + if (word1->getFontInfo() == (TextFontInfo *)fonts->get(i)) { + break; + } + } + s->appendf("", + i, (int)(fontScales[i] * word1->getFontSize()), + subSuper1 < 0 ? "super" + : subSuper1 > 0 ? "sub" + : "baseline", + (int)(r1 * 255), (int)(g1 * 255), (int)(b1 * 255)); + } + for (i = 0; i < word1->getLength(); ++i) { + u = word1->getChar(i); + if (u >= privateUnicodeMapStart && + u <= privateUnicodeMapEnd && + privateUnicodeMap[u - privateUnicodeMapStart]) { + u = privateUnicodeMap[u - privateUnicodeMapStart]; + } + if (u <= 0x7f) { + if (u == '&') { + s->append("&"); + } else if (u == '<') { + s->append("<"); + } else if (u == '>') { + s->append(">"); + } else { + s->append((char)u); + } + } else if (u <= 0x7ff) { + s->append((char)(0xc0 + (u >> 6))); + s->append((char)(0x80 + (u & 0x3f))); + } else if (u <= 0xffff) { + s->append((char)0xe0 + (u >> 12)); + s->append((char)0x80 + ((u >> 6) & 0x3f)); + s->append((char)0x80 + (u & 0x3f)); + } else if (u <= 0x1fffff) { + s->append((char)0xf0 + (u >> 18)); + s->append((char)0x80 + ((u >> 12) & 0x3f)); + s->append((char)0x80 + ((u >> 6) & 0x3f)); + s->append((char)0x80 + (u & 0x3f)); + } else if (u <= 0x3ffffff) { + s->append((char)0xf8 + (u >> 24)); + s->append((char)0x80 + ((u >> 18) & 0x3f)); + s->append((char)0x80 + ((u >> 12) & 0x3f)); + s->append((char)0x80 + ((u >> 6) & 0x3f)); + s->append((char)0x80 + (u & 0x3f)); + } else if (u <= 0x7fffffff) { + s->append((char)0xfc + (u >> 30)); + s->append((char)0x80 + ((u >> 24) & 0x3f)); + s->append((char)0x80 + ((u >> 18) & 0x3f)); + s->append((char)0x80 + ((u >> 12) & 0x3f)); + s->append((char)0x80 + ((u >> 6) & 0x3f)); + s->append((char)0x80 + (u & 0x3f)); + } + } + if (word1->getSpaceAfter()) { + s->append(' '); + } + word0 = word1; + subSuper0 = subSuper1; + r0 = r1; + g0 = g1; + b0 = b1; + } + s->append(""); + pf(writeHTML, htmlStream, "
{2:t}
\n", + (int)line->getXMin(), (int)line->getYMin(), s); + delete s; + } + } + } + gfree(fontScales); + delete text; + deleteGList(cols, TextColumn); + + // HTML trailer + pr(writeHTML, htmlStream, "\n"); + pr(writeHTML, htmlStream, "\n"); + + return errNone; +} + +GString *HTMLGen::getFontDefn(TextFontInfo *font, double *scale) { + GString *fontName; + char *fontName2; + FontStyleTagInfo *fst; + StandardFontInfo *sf; + GBool fixedWidth, serif, bold, italic; + double s; + int n, i; + + // get the font name, remove any subset tag + fontName = font->getFontName(); + if (fontName) { + fontName2 = fontName->getCString(); + n = fontName->getLength(); + for (i = 0; i < n && i < 7; ++i) { + if (fontName2[i] < 'A' || fontName2[i] > 'Z') { + break; + } + } + if (i == 6 && n > 7 && fontName2[6] == '+') { + fontName2 += 7; + n -= 7; + } + } else { + fontName2 = NULL; + n = 0; + } + + // get the style info from the font descriptor flags + fixedWidth = font->isFixedWidth(); + serif = font->isSerif(); + bold = font->isBold(); + italic = font->isItalic(); + + if (fontName2) { + + // look for a style tag at the end of the font name -- this + // overrides the font descriptor bold/italic flags + for (fst = fontStyleTags; fst->tag; ++fst) { + if (n > fst->tagLen && + !strcasecmp(fontName2 + n - fst->tagLen, fst->tag)) { + bold = fst->bold; + italic = fst->italic; + n -= fst->tagLen; + if (n > 1 && (fontName2[n-1] == '-' || + fontName2[n-1] == ',' || + fontName2[n-1] == '.' || + fontName2[n-1] == '_')) { + --n; + } + break; + } + } + + // look for a known font name -- this overrides the font descriptor + // fixedWidth/serif flags + for (sf = standardFonts; sf->name; ++sf) { + if (!strncasecmp(fontName2, sf->name, n)) { + fixedWidth = sf->fixedWidth; + serif = sf->serif; + break; + } + } + } + + // compute the scaling factor + *scale = 1; + if ((s = font->getMWidth())) { + i = (fixedWidth ? 8 : serif ? 4 : 0) + (bold ? 2 : 0) + (italic ? 1 : 0); + if (s < substFonts[i].mWidth) { + *scale = s / substFonts[i].mWidth; + } + } + + // generate the CSS markup + return GString::format("font-family:{0:s}; font-weight:{1:s}; font-style:{2:s};", + fixedWidth ? "monospace" + : serif ? "serif" + : "sans-serif", + bold ? "bold" : "normal", + italic ? "italic" : "normal"); +} diff -uNrp xpdf-3.03/xpdf/HTMLGen.h xpdf-3.04/xpdf/HTMLGen.h --- xpdf-3.03/xpdf/HTMLGen.h 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/HTMLGen.h 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,63 @@ +//======================================================================== +// +// HTMLGen.h +// +// Copyright 2010 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef HTMLGEN_H +#define HTMLGEN_H + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +class GString; +class PDFDoc; +class TextOutputDev; +class TextFontInfo; +class SplashOutputDev; + +//------------------------------------------------------------------------ + +class HTMLGen { +public: + + HTMLGen(double backgroundResolutionA); + ~HTMLGen(); + + GBool isOk() { return ok; } + + double getBackgroundResolution() { return backgroundResolution; } + void setBackgroundResolution(double backgroundResolutionA) + { backgroundResolution = backgroundResolutionA; } + + GBool getDrawInvisibleText() { return drawInvisibleText; } + void setDrawInvisibleText(GBool drawInvisibleTextA) + { drawInvisibleText = drawInvisibleTextA; } + + void startDoc(PDFDoc *docA); + int convertPage(int pg, const char *pngURL, + int (*writeHTML)(void *stream, const char *data, int size), + void *htmlStream, + int (*writePNG)(void *stream, const char *data, int size), + void *pngStream); + +private: + + GString *getFontDefn(TextFontInfo *font, double *scale); + + double backgroundResolution; + GBool drawInvisibleText; + + PDFDoc *doc; + TextOutputDev *textOut; + SplashOutputDev *splashOut; + + GBool ok; +}; + +#endif diff -uNrp xpdf-3.03/xpdf/ImageOutputDev.cc xpdf-3.04/xpdf/ImageOutputDev.cc --- xpdf-3.03/xpdf/ImageOutputDev.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/ImageOutputDev.cc 2014-05-28 20:50:50.000000000 +0200 @@ -37,7 +37,8 @@ ImageOutputDev::~ImageOutputDev() { gfree(fileRoot); } -void ImageOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, +void ImageOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, + Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -47,16 +48,16 @@ void ImageOutputDev::tilingPatternFill(G void ImageOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { + GBool inlineImg, GBool interpolate) { FILE *f; - int c; - int size, i; + char buf[4096]; + int size, n, i; // dump JPEG file if (dumpJPEG && str->getKind() == strDCT && !inlineImg) { // open the image file - sprintf(fileName, "%s-%03d.jpg", fileRoot, imgNum); + sprintf(fileName, "%s-%04d.jpg", fileRoot, imgNum); ++imgNum; if (!(f = fopen(fileName, "wb"))) { error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); @@ -68,8 +69,9 @@ void ImageOutputDev::drawImageMask(GfxSt str->reset(); // copy the stream - while ((c = str->getChar()) != EOF) - fputc(c, f); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + fwrite(buf, 1, n, f); + } str->close(); fclose(f); @@ -78,7 +80,7 @@ void ImageOutputDev::drawImageMask(GfxSt } else { // open the image file and write the PBM header - sprintf(fileName, "%s-%03d.pbm", fileRoot, imgNum); + sprintf(fileName, "%s-%04d.pbm", fileRoot, imgNum); ++imgNum; if (!(f = fopen(fileName, "wb"))) { error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); @@ -92,8 +94,14 @@ void ImageOutputDev::drawImageMask(GfxSt // copy the stream size = height * ((width + 7) / 8); - for (i = 0; i < size; ++i) { - fputc(str->getChar(), f); + while (size > 0) { + i = size < (int)sizeof(buf) ? size : (int)sizeof(buf); + n = str->getBlock(buf, i); + fwrite(buf, 1, n, f); + if (n < i) { + break; + } + size -= n; } str->close(); @@ -104,14 +112,15 @@ void ImageOutputDev::drawImageMask(GfxSt void ImageOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg) { + int *maskColors, GBool inlineImg, + GBool interpolate) { FILE *f; ImageStream *imgStr; Guchar *p; GfxRGB rgb; int x, y; - int c; - int size, i; + char buf[4096]; + int size, n, i; // dump JPEG file if (dumpJPEG && str->getKind() == strDCT && @@ -120,7 +129,7 @@ void ImageOutputDev::drawImage(GfxState !inlineImg) { // open the image file - sprintf(fileName, "%s-%03d.jpg", fileRoot, imgNum); + sprintf(fileName, "%s-%04d.jpg", fileRoot, imgNum); ++imgNum; if (!(f = fopen(fileName, "wb"))) { error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); @@ -132,8 +141,9 @@ void ImageOutputDev::drawImage(GfxState str->reset(); // copy the stream - while ((c = str->getChar()) != EOF) - fputc(c, f); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + fwrite(buf, 1, n, f); + } str->close(); fclose(f); @@ -143,7 +153,7 @@ void ImageOutputDev::drawImage(GfxState colorMap->getBits() == 1) { // open the image file and write the PBM header - sprintf(fileName, "%s-%03d.pbm", fileRoot, imgNum); + sprintf(fileName, "%s-%04d.pbm", fileRoot, imgNum); ++imgNum; if (!(f = fopen(fileName, "wb"))) { error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); @@ -157,8 +167,14 @@ void ImageOutputDev::drawImage(GfxState // copy the stream size = height * ((width + 7) / 8); - for (i = 0; i < size; ++i) { - fputc(str->getChar() ^ 0xff, f); + while (size > 0) { + i = size < (int)sizeof(buf) ? size : (int)sizeof(buf); + n = str->getBlock(buf, i); + fwrite(buf, 1, n, f); + if (n < i) { + break; + } + size -= n; } str->close(); @@ -168,7 +184,7 @@ void ImageOutputDev::drawImage(GfxState } else { // open the image file and write the PPM header - sprintf(fileName, "%s-%03d.ppm", fileRoot, imgNum); + sprintf(fileName, "%s-%04d.ppm", fileRoot, imgNum); ++imgNum; if (!(f = fopen(fileName, "wb"))) { error(errIO, -1, "Couldn't open image file '{0:s}'", fileName); @@ -203,8 +219,36 @@ void ImageOutputDev::drawImage(GfxState } } } + + imgStr->close(); delete imgStr; fclose(f); } } + +void ImageOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + 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, + gFalse, interpolate); +} + +void ImageOutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, + Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + Stream *maskStr, + int maskWidth, int maskHeight, + GfxImageColorMap *maskColorMap, + GBool interpolate) { + drawImage(state, ref, str, width, height, colorMap, + NULL, gFalse, interpolate); + drawImage(state, ref, maskStr, maskWidth, maskHeight, maskColorMap, + NULL, gFalse, interpolate); +} diff -uNrp xpdf-3.03/xpdf/ImageOutputDev.h xpdf-3.04/xpdf/ImageOutputDev.h --- xpdf-3.03/xpdf/ImageOutputDev.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/ImageOutputDev.h 2014-05-28 20:50:50.000000000 +0200 @@ -62,7 +62,7 @@ public: virtual GBool useDrawChar() { return gFalse; } //----- path painting - virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, + virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -71,10 +71,22 @@ public: //----- image drawing virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg); + int *maskColors, GBool inlineImg, GBool interpolate); + virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + 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, + int maskWidth, int maskHeight, + GfxImageColorMap *maskColorMap, + GBool interpolate); private: diff -uNrp xpdf-3.03/xpdf/JArithmeticDecoder.cc xpdf-3.04/xpdf/JArithmeticDecoder.cc --- xpdf-3.03/xpdf/JArithmeticDecoder.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/JArithmeticDecoder.cc 2014-05-28 20:50:50.000000000 +0200 @@ -92,10 +92,18 @@ JArithmeticDecoder::JArithmeticDecoder() dataLen = 0; limitStream = gFalse; nBytesRead = 0; + readBuf = -1; } inline Guint JArithmeticDecoder::readByte() { + Guint x; + if (limitStream) { + if (readBuf >= 0) { + x = (Guint)readBuf; + readBuf = -1; + return x; + } --dataLen; if (dataLen < 0) { return 0xff; @@ -162,9 +170,15 @@ void JArithmeticDecoder::restart(int dat void JArithmeticDecoder::cleanup() { if (limitStream) { + // This saves one extra byte of data from the end of packet i, to + // be used in packet i+1. It's not clear from the JPEG 2000 spec + // exactly how this should work, but this kludge does seem to fix + // decode of some problematic JPEG 2000 streams. It may actually + // be necessary to buffer an arbitrary number of bytes (not just + // one byte), but I haven't run into that case yet. while (dataLen > 0) { - buf0 = buf1; - buf1 = readByte(); + readBuf = -1; + readBuf = readByte(); } } } diff -uNrp xpdf-3.03/xpdf/JArithmeticDecoder.h xpdf-3.04/xpdf/JArithmeticDecoder.h --- xpdf-3.03/xpdf/JArithmeticDecoder.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/JArithmeticDecoder.h 2014-05-28 20:50:50.000000000 +0200 @@ -108,6 +108,7 @@ private: Guint nBytesRead; int dataLen; GBool limitStream; + int readBuf; }; #endif diff -uNrp xpdf-3.03/xpdf/JBIG2Stream.cc xpdf-3.04/xpdf/JBIG2Stream.cc --- xpdf-3.03/xpdf/JBIG2Stream.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/JBIG2Stream.cc 2014-05-28 20:50:50.000000000 +0200 @@ -623,11 +623,11 @@ Guint JBIG2MMRDecoder::get24Bits() { } void JBIG2MMRDecoder::skipTo(Guint length) { - while (nBytesRead < length) { - str->getChar(); - ++nBytesRead; - ++byteCounter; - } + int n; + + n = str->discardChars(length - nBytesRead); + nBytesRead += n; + byteCounter += n; } //------------------------------------------------------------------------ @@ -1310,10 +1310,9 @@ void JBIG2Stream::readSegments() { } refFlags = (refFlags << 24) | (c1 << 16) | (c2 << 8) | c3; nRefSegs = refFlags & 0x1fffffff; - for (i = 0; i < (nRefSegs + 9) >> 3; ++i) { - if ((c1 = curStr->getChar()) == EOF) { - goto eofError1; - } + i = (nRefSegs + 9) >> 3; + if (curStr->discardChars(i) != i) { + goto eofError1; } } @@ -1436,10 +1435,8 @@ void JBIG2Stream::readSegments() { break; default: error(errSyntaxError, getPos(), "Unknown segment type in JBIG2 stream"); - for (i = 0; i < segLength; ++i) { - if ((c1 = curStr->getChar()) == EOF) { - goto eofError2; - } + if (curStr->discardChars(segLength) != segLength) { + goto eofError2; } break; } @@ -1460,12 +1457,7 @@ void JBIG2Stream::readSegments() { gfree(refSegs); break; } - while (byteCounter < segLength) { - if (curStr->getChar() == EOF) { - break; - } - ++byteCounter; - } + byteCounter += curStr->discardChars(segLength - byteCounter); } gfree(refSegs); @@ -1502,9 +1494,8 @@ GBool JBIG2Stream::readSymbolDictSeg(Gui Guint symHeight, symWidth, totalWidth, x, symID; int dh, dw, refAggNum, refDX, refDY, bmSize; GBool ex; - int run, cnt, c; + int run, cnt; Guint i, j, k; - Guchar *p; symWidths = NULL; @@ -1814,14 +1805,8 @@ GBool JBIG2Stream::readSymbolDictSeg(Gui if (bmSize == 0) { collBitmap = new JBIG2Bitmap(0, totalWidth, symHeight); bmSize = symHeight * ((totalWidth + 7) >> 3); - p = collBitmap->getDataPtr(); - for (k = 0; k < (Guint)bmSize; ++k) { - if ((c = curStr->getChar()) == EOF) { - break; - } - *p++ = (Guchar)c; - ++byteCounter; - } + byteCounter += curStr->getBlock((char *)collBitmap->getDataPtr(), + bmSize); } else { collBitmap = readGenericBitmap(gTrue, totalWidth, symHeight, 0, gFalse, gFalse, NULL, NULL, NULL, @@ -2781,7 +2766,6 @@ JBIG2Bitmap *JBIG2Stream::readGenericBit Guchar mask; int x, y, x0, x1, a0i, b1i, blackPixels, pix, i; - bitmap = new JBIG2Bitmap(0, w, h); bitmap->clearToZero(); @@ -2994,7 +2978,7 @@ JBIG2Bitmap *JBIG2Stream::readGenericBit ltpCX = 0x0e3; // 001 1100 01 1 break; case 3: - ltpCX = 0x18a; // 01100 0101 1 + ltpCX = 0x18b; // 01100 0101 1 break; } } @@ -3821,27 +3805,13 @@ void JBIG2Stream::readPageInfoSeg(Guint } void JBIG2Stream::readEndOfStripeSeg(Guint length) { - Guint i; - // skip the segment - for (i = 0; i < length; ++i) { - if (curStr->getChar() == EOF) { - break; - } - ++byteCounter; - } + byteCounter += curStr->discardChars(length); } void JBIG2Stream::readProfilesSeg(Guint length) { - Guint i; - // skip the segment - for (i = 0; i < length; ++i) { - if (curStr->getChar() == EOF) { - break; - } - ++byteCounter; - } + byteCounter += curStr->discardChars(length); } void JBIG2Stream::readCodeTableSeg(Guint segNum, Guint length) { @@ -3909,15 +3879,8 @@ void JBIG2Stream::readCodeTableSeg(Guint } void JBIG2Stream::readExtensionSeg(Guint length) { - Guint i; - // skip the segment - for (i = 0; i < length; ++i) { - if (curStr->getChar() == EOF) { - break; - } - ++byteCounter; - } + byteCounter += curStr->discardChars(length); } JBIG2Segment *JBIG2Stream::findSegment(Guint segNum) { diff -uNrp xpdf-3.03/xpdf/JPXStream.cc xpdf-3.04/xpdf/JPXStream.cc --- xpdf-3.03/xpdf/JPXStream.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/JPXStream.cc 2014-05-28 20:50:50.000000000 +0200 @@ -42,7 +42,7 @@ #define jpxContextSigProp 0 // 0 - 8: significance prop and cleanup #define jpxContextSign 9 // 9 - 13: sign -#define jpxContextMagRef 14 // 14 -16: magnitude refinement +#define jpxContextMagRef 14 // 14 - 16: magnitude refinement #define jpxContextRunLength 17 // cleanup: run length #define jpxContextUniform 18 // cleanup: first signif coeff @@ -152,9 +152,10 @@ static Guint signContext[5][5][2] = { #define idwtKappa 1.230174104914001 #define idwtIKappa (1.0 / idwtKappa) -// number of bits to the right of the decimal point for the fixed -// point arithmetic used in the IDWT -#define fracBits 16 +// sum of the sample size (number of bits) and the number of bits to +// the right of the decimal point for the fixed point arithmetic used +// in the IDWT +#define fracBits 24 //------------------------------------------------------------------------ @@ -233,12 +234,25 @@ JPXStream::JPXStream(Stream *strA): nComps = 0; bpc = NULL; width = height = 0; + reduction = 0; haveCS = gFalse; + + palette.bpc = NULL; + palette.c = NULL; havePalette = gFalse; + + compMap.comp = NULL; + compMap.type = NULL; + compMap.pComp = NULL; haveCompMap = gFalse; + + channelDefn.idx = NULL; + channelDefn.type = NULL; + channelDefn.assoc = NULL; haveChannelDefn = gFalse; img.tiles = NULL; + bitBuf = 0; bitBufLen = 0; bitBufSkip = gFalse; @@ -252,13 +266,13 @@ JPXStream::~JPXStream() { void JPXStream::reset() { bufStr->reset(); - if (readBoxes()) { - curY = img.yOffset; - } else { + if (readBoxes() == jpxDecodeFatalError) { // readBoxes reported an error, so we go immediately to EOF - curY = img.ySize; + curY = img.ySizeR; + } else { + curY = img.yOffsetR; } - curX = img.xOffset; + curX = img.xOffsetR; curComp = 0; readBufLen = 0; } @@ -387,23 +401,25 @@ int JPXStream::lookChar() { void JPXStream::fillReadBuf() { JPXTileComp *tileComp; Guint tileIdx, tx, ty; - int pix, pixBits; + int pix, pixBits, k; + GBool eol; do { - if (curY >= img.ySize) { + if (curY >= img.ySizeR) { return; } - tileIdx = ((curY - img.yTileOffset) / img.yTileSize) * img.nXTiles - + (curX - img.xTileOffset) / img.xTileSize; + tileIdx = ((curY - img.yTileOffsetR) / img.yTileSizeR) * img.nXTiles + + (curX - img.xTileOffsetR) / img.xTileSizeR; #if 1 //~ ignore the palette, assume the PDF ColorSpace object is valid tileComp = &img.tiles[tileIdx].tileComps[curComp]; #else tileComp = &img.tiles[tileIdx].tileComps[havePalette ? 0 : curComp]; #endif - tx = jpxCeilDiv((curX - img.xTileOffset) % img.xTileSize, tileComp->hSep); - ty = jpxCeilDiv((curY - img.yTileOffset) % img.yTileSize, tileComp->vSep); - pix = (int)tileComp->data[ty * (tileComp->x1 - tileComp->x0) + tx]; + tx = jpxCeilDiv((curX - img.xTileOffsetR) % img.xTileSizeR, tileComp->hSep); + ty = jpxCeilDiv((curY - img.yTileOffsetR) % img.yTileSizeR, tileComp->vSep); + pix = (int)tileComp->data[ty * tileComp->w + tx]; pixBits = tileComp->prec; + eol = gFalse; #if 1 //~ ignore the palette, assume the PDF ColorSpace object is valid if (++curComp == img.nComps) { #else @@ -418,13 +434,10 @@ void JPXStream::fillReadBuf() { if (++curComp == (Guint)(havePalette ? palette.nComps : img.nComps)) { #endif curComp = 0; - if (++curX == img.xSize) { - curX = img.xOffset; + if (++curX == img.xSizeR) { + curX = img.xOffsetR; ++curY; - if (pixBits < 8) { - pix <<= 8 - pixBits; - pixBits = 8; - } + eol = gTrue; } } if (pixBits == 8) { @@ -433,6 +446,10 @@ void JPXStream::fillReadBuf() { readBuf = (readBuf << pixBits) | (pix & ((1 << pixBits) - 1)); } readBufLen += pixBits; + if (eol && (k = readBufLen & 7)) { + readBuf <<= 8 - k; + readBufLen += 8 - k; + } } while (readBufLen < 8); } @@ -447,7 +464,7 @@ GBool JPXStream::isBinary(GBool last) { void JPXStream::getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode) { Guint boxType, boxLen, dataLen, csEnum; - Guint bpc1, dummy, i; + Guint bpc1, dummy; int csMeth, csPrec, csPrec1, dummy2; StreamColorSpaceMode csMode1; GBool haveBPC, haveCSMode; @@ -498,13 +515,13 @@ void JPXStream::getImageParams(int *bits csPrec = csPrec1; haveCSMode = gTrue; } - for (i = 0; i < dataLen - 7; ++i) { - bufStr->getChar(); + if (dataLen > 7) { + bufStr->discardChars(dataLen - 7); } } } else { - for (i = 0; i < dataLen - 3; ++i) { - bufStr->getChar(); + if (dataLen > 3) { + bufStr->discardChars(dataLen - 3); } } } @@ -516,9 +533,7 @@ void JPXStream::getImageParams(int *bits break; } else { cover(4); - for (i = 0; i < dataLen; ++i) { - bufStr->getChar(); - } + bufStr->discardChars(dataLen); } } } @@ -529,7 +544,7 @@ void JPXStream::getImageParams(int *bits void JPXStream::getImageParams2(int *bitsPerComponent, StreamColorSpaceMode *csMode) { int segType; - Guint segLen, nComps1, bpc1, dummy, i; + Guint segLen, nComps1, bpc1, dummy; while (readMarkerHdr(&segType, &segLen)) { if (segType == 0x51) { // SIZ - image and tile size @@ -559,15 +574,14 @@ void JPXStream::getImageParams2(int *bit } else { cover(6); if (segLen > 2) { - for (i = 0; i < segLen - 2; ++i) { - bufStr->getChar(); - } + bufStr->discardChars(segLen - 2); } } } } -GBool JPXStream::readBoxes() { +JPXDecodeResult JPXStream::readBoxes() { + JPXDecodeResult result; Guint boxType, boxLen, dataLen; Guint bpc1, compression, unknownColorspace, ipr; Guint i, j; @@ -581,8 +595,8 @@ GBool JPXStream::readBoxes() { cover(7); error(errSyntaxWarning, getPos(), "Naked JPEG 2000 codestream, missing JP2/JPX wrapper"); - if (!readCodestream(0)) { - return gFalse; + if ((result = readCodestream(0)) == jpxDecodeFatalError) { + return result; } nComps = img.nComps; bpc = (Guint *)gmallocn(nComps, sizeof(Guint)); @@ -591,7 +605,7 @@ GBool JPXStream::readBoxes() { } width = img.xSize - img.xOffset; height = img.ySize - img.yOffset; - return gTrue; + return result; } while (readBoxHdr(&boxType, &boxLen, &dataLen)) { @@ -614,12 +628,12 @@ GBool JPXStream::readBoxes() { !readUByte(&unknownColorspace) || !readUByte(&ipr)) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } if (compression != 7) { error(errSyntaxError, getPos(), "Unknown compression type in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } bpc = (Guint *)gmallocn(nComps, sizeof(Guint)); for (i = 0; i < nComps; ++i) { @@ -632,24 +646,24 @@ GBool JPXStream::readBoxes() { if (!haveImgHdr) { error(errSyntaxError, getPos(), "Found bits per component box before image header box in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } if (dataLen != nComps) { error(errSyntaxError, getPos(), "Invalid bits per component box in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } for (i = 0; i < nComps; ++i) { if (!readUByte(&bpc[i])) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } } break; case 0x636F6C72: // color specification cover(11); if (!readColorSpecBox(dataLen)) { - return gFalse; + return jpxDecodeFatalError; } break; case 0x70636c72: // palette @@ -657,15 +671,16 @@ GBool JPXStream::readBoxes() { if (!readUWord(&palette.nEntries) || !readUByte(&palette.nComps)) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } + havePalette = gTrue; palette.bpc = (Guint *)gmallocn(palette.nComps, sizeof(Guint)); palette.c = (int *)gmallocn(palette.nEntries * palette.nComps, sizeof(int)); for (i = 0; i < palette.nComps; ++i) { if (!readUByte(&palette.bpc[i])) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } ++palette.bpc[i]; } @@ -675,14 +690,14 @@ GBool JPXStream::readBoxes() { (palette.bpc[j] & 0x80) ? gTrue : gFalse, &palette.c[i * palette.nComps + j])) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } } } - havePalette = gTrue; break; case 0x636d6170: // component mapping cover(13); + haveCompMap = gTrue; compMap.nChannels = dataLen / 4; compMap.comp = (Guint *)gmallocn(compMap.nChannels, sizeof(Guint)); compMap.type = (Guint *)gmallocn(compMap.nChannels, sizeof(Guint)); @@ -692,17 +707,17 @@ GBool JPXStream::readBoxes() { !readUByte(&compMap.type[i]) || !readUByte(&compMap.pComp[i])) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } } - haveCompMap = gTrue; break; case 0x63646566: // channel definition cover(14); if (!readUWord(&channelDefn.nChannels)) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } + haveChannelDefn = gTrue; channelDefn.idx = (Guint *)gmallocn(channelDefn.nChannels, sizeof(Guint)); channelDefn.type = @@ -714,10 +729,9 @@ GBool JPXStream::readBoxes() { !readUWord(&channelDefn.type[i]) || !readUWord(&channelDefn.assoc[i])) { error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } } - haveChannelDefn = gTrue; break; case 0x6A703263: // contiguous codestream cover(15); @@ -729,28 +743,25 @@ GBool JPXStream::readBoxes() { error(errSyntaxError, getPos(), "JPX stream has no supported color spec"); } - if (!readCodestream(dataLen)) { - return gFalse; + if ((result = readCodestream(dataLen)) != jpxDecodeOk) { + return result; } break; default: cover(16); - for (i = 0; i < dataLen; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); - return gFalse; - } + if (bufStr->discardChars(dataLen) != dataLen) { + error(errSyntaxError, getPos(), "Unexpected EOF in JPX stream"); + return jpxDecodeFatalError; } break; } } - return gTrue; + return jpxDecodeOk; } GBool JPXStream::readColorSpecBox(Guint dataLen) { JPXColorSpec newCS; Guint csApprox, csEnum; - Guint i; GBool ok; ok = gFalse; @@ -852,10 +863,9 @@ GBool JPXStream::readColorSpecBox(Guint case 3: // any ICC profile (JPX) case 4: // vendor color (JPX) cover(18); - for (i = 0; i < dataLen - 3; ++i) { - if (bufStr->getChar() == EOF) { - goto err; - } + if (dataLen > 3 && + bufStr->discardChars(dataLen - 3) != dataLen - 3) { + goto err; } break; } @@ -872,11 +882,11 @@ GBool JPXStream::readColorSpecBox(Guint return gFalse; } -GBool JPXStream::readCodestream(Guint len) { +JPXDecodeResult JPXStream::readCodestream(Guint len) { JPXTile *tile; JPXTileComp *tileComp; int segType; - GBool haveSIZ, haveCOD, haveQCD, haveSOT; + GBool haveSIZ, haveCOD, haveQCD, haveSOT, ok; Guint precinctSize, style; Guint segLen, capabilities, comp, i, j, r; @@ -885,7 +895,7 @@ GBool JPXStream::readCodestream(Guint le do { if (!readMarkerHdr(&segType, &segLen)) { error(errSyntaxError, getPos(), "Error in JPX codestream"); - return gFalse; + return jpxDecodeFatalError; } switch (segType) { case 0x4f: // SOC - start of codestream @@ -897,7 +907,7 @@ GBool JPXStream::readCodestream(Guint le if (haveSIZ) { error(errSyntaxError, getPos(), "Duplicate SIZ marker segment in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } if (!readUWord(&capabilities) || !readULong(&img.xSize) || @@ -910,12 +920,12 @@ GBool JPXStream::readCodestream(Guint le !readULong(&img.yTileOffset) || !readUWord(&img.nComps)) { error(errSyntaxError, getPos(), "Error in JPX SIZ marker segment"); - return gFalse; + return jpxDecodeFatalError; } if (haveImgHdr && img.nComps != nComps) { error(errSyntaxError, getPos(), "Different number of components in JPX SIZ marker segment"); - return gFalse; + return jpxDecodeFatalError; } if (img.xSize == 0 || img.ySize == 0 || img.xOffset >= img.xSize || img.yOffset >= img.ySize || @@ -925,8 +935,16 @@ GBool JPXStream::readCodestream(Guint le img.xTileSize + img.xTileOffset <= img.xOffset || img.yTileSize + img.yTileOffset <= img.yOffset) { error(errSyntaxError, getPos(), "Error in JPX SIZ marker segment"); - return gFalse; + return jpxDecodeFatalError; } + img.xSizeR = img.xSize >> reduction; + img.ySizeR = img.ySize >> reduction; + img.xOffsetR = img.xOffset >> reduction; + img.yOffsetR = img.yOffset >> reduction; + img.xTileSizeR = img.xTileSize >> reduction; + img.yTileSizeR = img.yTileSize >> reduction; + img.xTileOffsetR = img.xTileOffset >> reduction; + img.yTileOffsetR = img.yTileOffset >> reduction; img.nXTiles = (img.xSize - img.xTileOffset + img.xTileSize - 1) / img.xTileSize; img.nYTiles = (img.ySize - img.yTileOffset + img.yTileSize - 1) @@ -936,12 +954,16 @@ GBool JPXStream::readCodestream(Guint le img.nXTiles >= INT_MAX / img.nYTiles) { error(errSyntaxError, getPos(), "Bad tile count in JPX SIZ marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles = (JPXTile *)gmallocn(img.nXTiles * img.nYTiles, sizeof(JPXTile)); for (i = 0; i < img.nXTiles * img.nYTiles; ++i) { img.tiles[i].init = gFalse; + img.tiles[i].nextTilePart = 0; + img.tiles[i].tileComps = NULL; + } + for (i = 0; i < img.nXTiles * img.nYTiles; ++i) { img.tiles[i].tileComps = (JPXTileComp *)gmallocn(img.nComps, sizeof(JPXTileComp)); for (comp = 0; comp < img.nComps; ++comp) { @@ -956,12 +978,12 @@ GBool JPXStream::readCodestream(Guint le !readUByte(&img.tiles[0].tileComps[comp].hSep) || !readUByte(&img.tiles[0].tileComps[comp].vSep)) { error(errSyntaxError, getPos(), "Error in JPX SIZ marker segment"); - return gFalse; + return jpxDecodeFatalError; } if (img.tiles[0].tileComps[comp].hSep == 0 || img.tiles[0].tileComps[comp].vSep == 0) { error(errSyntaxError, getPos(), "Error in JPX SIZ marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[comp].sgned = (img.tiles[0].tileComps[comp].prec & 0x80) ? gTrue : gFalse; @@ -978,7 +1000,7 @@ GBool JPXStream::readCodestream(Guint le if (!haveSIZ) { error(errSyntaxError, getPos(), "JPX COD marker segment before SIZ segment"); - return gFalse; + return jpxDecodeFatalError; } if (!readUByte(&img.tiles[0].tileComps[0].style) || !readUByte(&img.tiles[0].progOrder) || @@ -990,14 +1012,21 @@ GBool JPXStream::readCodestream(Guint le !readUByte(&img.tiles[0].tileComps[0].codeBlockStyle) || !readUByte(&img.tiles[0].tileComps[0].transform)) { error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); - return gFalse; + return jpxDecodeFatalError; } if (img.tiles[0].tileComps[0].nDecompLevels > 32 || img.tiles[0].tileComps[0].codeBlockW > 8 || img.tiles[0].tileComps[0].codeBlockH > 8) { error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); - return gFalse; + return jpxDecodeFatalError; + } +#if 1 //~ progression orders 2-4 are unimplemented + if (img.tiles[0].progOrder >= 2) { + error(errUnimplemented, -1, + "JPX progression order {0:d} is unimplemented", + img.tiles[0].progOrder); } +#endif img.tiles[0].tileComps[0].codeBlockW += 2; img.tiles[0].tileComps[0].codeBlockH += 2; for (i = 0; i < img.nXTiles * img.nYTiles; ++i) { @@ -1035,7 +1064,7 @@ GBool JPXStream::readCodestream(Guint le cover(91); if (!readUByte(&precinctSize)) { error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[0].resLevels[r].precinctWidth = precinctSize & 0x0f; @@ -1065,7 +1094,7 @@ GBool JPXStream::readCodestream(Guint le if (!haveCOD) { error(errSyntaxError, getPos(), "JPX COC marker segment before COD segment"); - return gFalse; + return jpxDecodeFatalError; } if ((img.nComps > 256 && !readUWord(&comp)) || (img.nComps <= 256 && !readUByte(&comp)) || @@ -1077,13 +1106,13 @@ GBool JPXStream::readCodestream(Guint le !readUByte(&img.tiles[0].tileComps[comp].codeBlockStyle) || !readUByte(&img.tiles[0].tileComps[comp].transform)) { error(errSyntaxError, getPos(), "Error in JPX COC marker segment"); - return gFalse; + return jpxDecodeFatalError; } if (img.tiles[0].tileComps[comp].nDecompLevels > 32 || img.tiles[0].tileComps[comp].codeBlockW > 8 || img.tiles[0].tileComps[comp].codeBlockH > 8) { error(errSyntaxError, getPos(), "Error in JPX COC marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[comp].style = (img.tiles[0].tileComps[comp].style & ~1) | (style & 1); @@ -1117,7 +1146,7 @@ GBool JPXStream::readCodestream(Guint le if (img.tiles[0].tileComps[comp].style & 0x01) { if (!readUByte(&precinctSize)) { error(errSyntaxError, getPos(), "Error in JPX COD marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[comp].resLevels[r].precinctWidth = precinctSize & 0x0f; @@ -1142,16 +1171,16 @@ GBool JPXStream::readCodestream(Guint le if (!haveSIZ) { error(errSyntaxError, getPos(), "JPX QCD marker segment before SIZ segment"); - return gFalse; + return jpxDecodeFatalError; } if (!readUByte(&img.tiles[0].tileComps[0].quantStyle)) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } if ((img.tiles[0].tileComps[0].quantStyle & 0x1f) == 0x00) { if (segLen <= 3) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[0].nQuantSteps = segLen - 3; img.tiles[0].tileComps[0].quantSteps = @@ -1161,7 +1190,7 @@ GBool JPXStream::readCodestream(Guint le for (i = 0; i < img.tiles[0].tileComps[0].nQuantSteps; ++i) { if (!readUByte(&img.tiles[0].tileComps[0].quantSteps[i])) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } } } else if ((img.tiles[0].tileComps[0].quantStyle & 0x1f) == 0x01) { @@ -1172,12 +1201,12 @@ GBool JPXStream::readCodestream(Guint le sizeof(Guint)); if (!readUWord(&img.tiles[0].tileComps[0].quantSteps[0])) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } } else if ((img.tiles[0].tileComps[0].quantStyle & 0x1f) == 0x02) { if (segLen < 5) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[0].nQuantSteps = (segLen - 3) / 2; img.tiles[0].tileComps[0].quantSteps = @@ -1187,12 +1216,12 @@ GBool JPXStream::readCodestream(Guint le for (i = 0; i < img.tiles[0].tileComps[0].nQuantSteps; ++i) { if (!readUWord(&img.tiles[0].tileComps[0].quantSteps[i])) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } } } else { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } for (i = 0; i < img.nXTiles * img.nYTiles; ++i) { for (comp = 0; comp < img.nComps; ++comp) { @@ -1219,19 +1248,19 @@ GBool JPXStream::readCodestream(Guint le if (!haveQCD) { error(errSyntaxError, getPos(), "JPX QCC marker segment before QCD segment"); - return gFalse; + return jpxDecodeFatalError; } if ((img.nComps > 256 && !readUWord(&comp)) || (img.nComps <= 256 && !readUByte(&comp)) || comp >= img.nComps || !readUByte(&img.tiles[0].tileComps[comp].quantStyle)) { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } if ((img.tiles[0].tileComps[comp].quantStyle & 0x1f) == 0x00) { if (segLen <= (img.nComps > 256 ? 5U : 4U)) { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[comp].nQuantSteps = segLen - (img.nComps > 256 ? 5 : 4); @@ -1242,7 +1271,7 @@ GBool JPXStream::readCodestream(Guint le for (i = 0; i < img.tiles[0].tileComps[comp].nQuantSteps; ++i) { if (!readUByte(&img.tiles[0].tileComps[comp].quantSteps[i])) { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } } } else if ((img.tiles[0].tileComps[comp].quantStyle & 0x1f) == 0x01) { @@ -1253,12 +1282,12 @@ GBool JPXStream::readCodestream(Guint le sizeof(Guint)); if (!readUWord(&img.tiles[0].tileComps[comp].quantSteps[0])) { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } } else if ((img.tiles[0].tileComps[comp].quantStyle & 0x1f) == 0x02) { if (segLen < (img.nComps > 256 ? 5U : 4U) + 2) { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } img.tiles[0].tileComps[comp].nQuantSteps = (segLen - (img.nComps > 256 ? 5 : 4)) / 2; @@ -1269,12 +1298,12 @@ GBool JPXStream::readCodestream(Guint le for (i = 0; i < img.tiles[0].tileComps[comp].nQuantSteps; ++i) { if (!readUWord(&img.tiles[0].tileComps[comp].quantSteps[i])) { error(errSyntaxError, getPos(), "Error in JPX QCD marker segment"); - return gFalse; + return jpxDecodeFatalError; } } } else { error(errSyntaxError, getPos(), "Error in JPX QCC marker segment"); - return gFalse; + return jpxDecodeFatalError; } for (i = 1; i < img.nXTiles * img.nYTiles; ++i) { img.tiles[i].tileComps[comp].quantStyle = @@ -1295,11 +1324,10 @@ GBool JPXStream::readCodestream(Guint le cover(25); #if 1 //~ ROI is unimplemented error(errUnimplemented, -1, "got a JPX RGN segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX RGN marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX RGN marker segment"); + return jpxDecodeFatalError; } #else if ((img.nComps > 256 && !readUWord(&comp)) || @@ -1308,7 +1336,7 @@ GBool JPXStream::readCodestream(Guint le !readUByte(&compInfo[comp].defROI.style) || !readUByte(&compInfo[comp].defROI.shift)) { error(errSyntaxError, getPos(), "Error in JPX RGN marker segment"); - return gFalse; + return jpxDecodeFatalError; } #endif break; @@ -1316,11 +1344,10 @@ GBool JPXStream::readCodestream(Guint le cover(26); #if 1 //~ progression order changes are unimplemented error(errUnimplemented, -1, "got a JPX POC segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX POC marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX POC marker segment"); + return jpxDecodeFatalError; } #else nProgs = (segLen - 2) / (img.nComps > 256 ? 9 : 7); @@ -1335,7 +1362,7 @@ GBool JPXStream::readCodestream(Guint le !(img.nComps <= 256 && readUByte(&progs[i].endComp)) || !readUByte(&progs[i].progOrder)) { error(errSyntaxError, getPos(), "Error in JPX POC marker segment"); - return gFalse; + return jpxDecodeFatalError; } } #endif @@ -1344,52 +1371,47 @@ GBool JPXStream::readCodestream(Guint le cover(27); #if 1 //~ packed packet headers are unimplemented error(errUnimplemented, -1, "Got a JPX PPM segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX PPM marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX PPM marker segment"); + return jpxDecodeFatalError; } #endif break; case 0x55: // TLM - tile-part lengths // skipped cover(28); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX TLM marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX TLM marker segment"); + return jpxDecodeFatalError; } break; case 0x57: // PLM - packet length, main header // skipped cover(29); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX PLM marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX PLM marker segment"); + return jpxDecodeFatalError; } break; case 0x63: // CRG - component registration // skipped cover(30); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX CRG marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX CRG marker segment"); + return jpxDecodeFatalError; } break; case 0x64: // COM - comment // skipped cover(31); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX COM marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX COM marker segment"); + return jpxDecodeFatalError; } break; case 0x90: // SOT - start of tile @@ -1400,10 +1422,8 @@ GBool JPXStream::readCodestream(Guint le cover(33); error(errSyntaxError, getPos(), "Unknown marker segment {0:02x} in JPX stream", segType); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - break; - } + if (segLen > 2) { + bufStr->discardChars(segLen - 2); } break; } @@ -1412,27 +1432,30 @@ GBool JPXStream::readCodestream(Guint le if (!haveSIZ) { error(errSyntaxError, getPos(), "Missing SIZ marker segment in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } if (!haveCOD) { error(errSyntaxError, getPos(), "Missing COD marker segment in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } if (!haveQCD) { error(errSyntaxError, getPos(), "Missing QCD marker segment in JPX stream"); - return gFalse; + return jpxDecodeFatalError; } //----- read the tile-parts + ok = gTrue; while (1) { if (!readTilePart()) { - return gFalse; + ok = gFalse; + break; } if (!readMarkerHdr(&segType, &segLen)) { error(errSyntaxError, getPos(), "Error in JPX codestream"); - return gFalse; + ok = gFalse; + break; } if (segType != 0x90) { // SOT - start of tile break; @@ -1441,7 +1464,7 @@ GBool JPXStream::readCodestream(Guint le if (segType != 0xd9) { // EOC - end of codestream error(errSyntaxError, getPos(), "Missing EOC marker in JPX codestream"); - return gFalse; + ok = gFalse; } //----- finish decoding the image @@ -1449,20 +1472,20 @@ GBool JPXStream::readCodestream(Guint le tile = &img.tiles[i]; if (!tile->init) { error(errSyntaxError, getPos(), "Uninitialized tile in JPX codestream"); - return gFalse; + return jpxDecodeFatalError; } for (comp = 0; comp < img.nComps; ++comp) { tileComp = &tile->tileComps[comp]; inverseTransform(tileComp); } if (!inverseMultiCompAndDC(tile)) { - return gFalse; + return jpxDecodeFatalError; } } //~ can free memory below tileComps here, and also tileComp.buf - return gTrue; + return ok ? jpxDecodeOk : jpxDecodeNonFatalError; } GBool JPXStream::readTilePart() { @@ -1490,11 +1513,16 @@ GBool JPXStream::readTilePart() { return gFalse; } - if ((tilePartIdx > 0 && !img.tiles[tileIdx].init) || - tileIdx >= img.nXTiles * img.nYTiles) { - error(errSyntaxError, getPos(), "Weird tile index in JPX stream"); + // check tileIdx and tilePartIdx + // (this ignores nTileParts, because some encoders get it wrong) + if (tileIdx >= img.nXTiles * img.nYTiles || + tilePartIdx != img.tiles[tileIdx].nextTilePart || + (tilePartIdx > 0 && !img.tiles[tileIdx].init) || + (tilePartIdx == 0 && img.tiles[tileIdx].init)) { + error(errSyntaxError, getPos(), "Weird tile-part header in JPX stream"); return gFalse; } + ++img.tiles[tileIdx].nextTilePart; tilePartToEOC = tilePartLen == 0; tilePartLen -= 12; // subtract size of SOT segment @@ -1527,6 +1555,13 @@ GBool JPXStream::readTilePart() { 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 img.tiles[tileIdx].tileComps[0].codeBlockW += 2; img.tiles[tileIdx].tileComps[0].codeBlockH += 2; for (comp = 0; comp < img.nComps; ++comp) { @@ -1760,11 +1795,10 @@ GBool JPXStream::readTilePart() { cover(38); #if 1 //~ ROI is unimplemented error(errUnimplemented, -1, "Got a JPX RGN segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX RGN marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX RGN marker segment"); + return gFalse; } #else if ((img.nComps > 256 && !readUWord(&comp)) || @@ -1781,11 +1815,10 @@ GBool JPXStream::readTilePart() { cover(39); #if 1 //~ progression order changes are unimplemented error(errUnimplemented, -1, "Got a JPX POC segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX POC marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX POC marker segment"); + return gFalse; } #else nTileProgs = (segLen - 2) / (img.nComps > 256 ? 9 : 7); @@ -1809,31 +1842,28 @@ GBool JPXStream::readTilePart() { cover(40); #if 1 //~ packed packet headers are unimplemented error(errUnimplemented, -1, "Got a JPX PPT segment"); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX PPT marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX PPT marker segment"); + return gFalse; } #endif case 0x58: // PLT - packet length, tile-part header // skipped cover(41); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX PLT marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX PLT marker segment"); + return gFalse; } break; case 0x64: // COM - comment // skipped cover(42); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - error(errSyntaxError, getPos(), "Error in JPX COM marker segment"); - return gFalse; - } + if (segLen > 2 && + bufStr->discardChars(segLen - 2) != segLen - 2) { + error(errSyntaxError, getPos(), "Error in JPX COM marker segment"); + return gFalse; } break; case 0x93: // SOD - start of data @@ -1845,10 +1875,8 @@ GBool JPXStream::readTilePart() { error(errSyntaxError, getPos(), "Unknown marker segment {0:02x} in JPX tile-part stream", segType); - for (i = 0; i < segLen - 2; ++i) { - if (bufStr->getChar() == EOF) { - break; - } + if (segLen > 2) { + bufStr->discardChars(segLen - 2); } break; } @@ -1886,12 +1914,13 @@ GBool JPXStream::readTilePart() { tileComp->y0 = jpxCeilDiv(tile->y0, tileComp->vSep); tileComp->x1 = jpxCeilDiv(tile->x1, tileComp->hSep); tileComp->y1 = jpxCeilDiv(tile->y1, tileComp->vSep); - tileComp->w = tileComp->x1 - tileComp->x0; tileComp->cbW = 1 << tileComp->codeBlockW; tileComp->cbH = 1 << tileComp->codeBlockH; - tileComp->data = (int *)gmallocn((tileComp->x1 - tileComp->x0) * - (tileComp->y1 - tileComp->y0), - sizeof(int)); + tileComp->w = (tileComp->x1 - tileComp->x0 + (1 << reduction) - 1) + >> reduction; + tileComp->h = (tileComp->y1 - tileComp->y0 + (1 << reduction) - 1) + >> reduction; + tileComp->data = (int *)gmallocn(tileComp->w * tileComp->h, sizeof(int)); if (tileComp->x1 - tileComp->x0 > tileComp->y1 - tileComp->y0) { n = tileComp->x1 - tileComp->x0; } else { @@ -1927,6 +1956,9 @@ GBool JPXStream::readTilePart() { } resLevel->precincts = (JPXPrecinct *)gmallocn(1, sizeof(JPXPrecinct)); for (pre = 0; pre < 1; ++pre) { + resLevel->precincts[pre].subbands = NULL; + } + for (pre = 0; pre < 1; ++pre) { precinct = &resLevel->precincts[pre]; precinct->x0 = resLevel->x0; precinct->y0 = resLevel->y0; @@ -1936,6 +1968,11 @@ GBool JPXStream::readTilePart() { 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]; @@ -1973,6 +2010,12 @@ GBool JPXStream::readTilePart() { 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 @@ -2013,21 +2056,25 @@ GBool JPXStream::readTilePart() { cb->nZeroBitPlanes = 0; cb->dataLenSize = 1; cb->dataLen = (Guint *)gmalloc(sizeof(Guint)); - 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; + 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; + } } + memset(cb->touched, 0, + (1 << (tileComp->codeBlockW + tileComp->codeBlockH))); + } else { + cb->coeffs = NULL; + cb->touched = NULL; + cb->len = 0; } - memset(cb->touched, 0, - (1 << (tileComp->codeBlockW + tileComp->codeBlockH))); - cb->arithDecoder = NULL; - cb->stats = NULL; ++cb; } } @@ -2381,7 +2428,21 @@ GBool JPXStream::readCodeBlockData(JPXTi Guint horiz, vert, diag, all, cx, xorBit; int horizSign, vertSign, bit; int segSym; - Guint i, x, y0, y1; + Guint n, i, x, y0, y1; + + if (res > tileComp->nDecompLevels - reduction) { + // skip the codeblock data + if (tileComp->codeBlockStyle & 0x04) { + n = 0; + for (i = 0; i < cb->nCodingPasses; ++i) { + n += cb->dataLen[i]; + } + } else { + n = cb->dataLen[0]; + } + bufStr->discardChars(n); + return gTrue; + } if (cb->arithDecoder) { cover(63); @@ -2735,7 +2796,7 @@ void JPXStream::inverseTransform(JPXTile } if (tileComp->transform == 0) { cover(71); - shift += fracBits; + shift += fracBits - tileComp->prec; } // do fixed point adjustment and dequantization on (NL)LL @@ -2766,7 +2827,7 @@ void JPXStream::inverseTransform(JPXTile cover(96); if (tileComp->transform == 0) { cover(97); - val &= -1 << fracBits; + val &= -1 << (fracBits - tileComp->prec); } } else { cover(98); @@ -2782,7 +2843,7 @@ void JPXStream::inverseTransform(JPXTile //----- IDWT for each level - for (r = 1; r <= tileComp->nDecompLevels; ++r) { + for (r = 1; r <= tileComp->nDecompLevels - reduction; ++r) { resLevel = &tileComp->resLevels[r]; // (n)LL is already in the upper-left corner of the @@ -2837,7 +2898,7 @@ void JPXStream::inverseTransformLevel(JP } if (tileComp->transform == 0) { cover(103); - shift += fracBits; + shift += fracBits - tileComp->prec; } // fixed point adjustment and dequantization @@ -2868,7 +2929,7 @@ void JPXStream::inverseTransformLevel(JP if (qStyle == 0) { cover(76); if (tileComp->transform == 0) { - val &= -1 << fracBits; + val &= -1 << (fracBits - tileComp->prec); } } else { cover(77); @@ -3103,8 +3164,8 @@ GBool JPXStream::inverseMultiCompAndDC(J if (tile->tileComps[0].transform == 0) { cover(87); j = 0; - for (y = 0; y < tile->tileComps[0].y1 - tile->tileComps[0].y0; ++y) { - for (x = 0; x < tile->tileComps[0].x1 - tile->tileComps[0].x0; ++x) { + for (y = 0; y < tile->tileComps[0].h; ++y) { + for (x = 0; x < tile->tileComps[0].w; ++x) { d0 = tile->tileComps[0].data[j]; d1 = tile->tileComps[1].data[j]; d2 = tile->tileComps[2].data[j]; @@ -3120,8 +3181,8 @@ GBool JPXStream::inverseMultiCompAndDC(J } else { cover(88); j = 0; - for (y = 0; y < tile->tileComps[0].y1 - tile->tileComps[0].y0; ++y) { - for (x = 0; x < tile->tileComps[0].x1 - tile->tileComps[0].x0; ++x) { + for (y = 0; y < tile->tileComps[0].h; ++y) { + for (x = 0; x < tile->tileComps[0].w; ++x) { d0 = tile->tileComps[0].data[j]; d1 = tile->tileComps[1].data[j]; d2 = tile->tileComps[2].data[j]; @@ -3144,12 +3205,12 @@ GBool JPXStream::inverseMultiCompAndDC(J minVal = -(1 << (tileComp->prec - 1)); maxVal = (1 << (tileComp->prec - 1)) - 1; dataPtr = tileComp->data; - for (y = 0; y < tileComp->y1 - tileComp->y0; ++y) { - for (x = 0; x < tileComp->x1 - tileComp->x0; ++x) { + for (y = 0; y < tileComp->h; ++y) { + for (x = 0; x < tileComp->w; ++x) { coeff = *dataPtr; if (tileComp->transform == 0) { cover(109); - coeff >>= fracBits; + coeff >>= fracBits - tileComp->prec; } if (coeff < minVal) { cover(110); @@ -3168,12 +3229,12 @@ GBool JPXStream::inverseMultiCompAndDC(J maxVal = (1 << tileComp->prec) - 1; zeroVal = 1 << (tileComp->prec - 1); dataPtr = tileComp->data; - for (y = 0; y < tileComp->y1 - tileComp->y0; ++y) { - for (x = 0; x < tileComp->x1 - tileComp->x0; ++x) { + for (y = 0; y < tileComp->h; ++y) { + for (x = 0; x < tileComp->w; ++x) { coeff = *dataPtr; if (tileComp->transform == 0) { cover(112); - coeff >>= fracBits; + coeff >>= fracBits - tileComp->prec; } coeff += zeroVal; if (coeff < 0) { @@ -3339,16 +3400,12 @@ GBool JPXStream::readBits(int nBits, Gui } void JPXStream::skipSOP() { - int i; - // SOP occurs at the start of the packet header, so we don't need to // worry about bit-stuff prior to it if (byteCount >= 6 && bufStr->lookChar(0) == 0xff && bufStr->lookChar(1) == 0x91) { - for (i = 0; i < 6; ++i) { - bufStr->getChar(); - } + bufStr->discardChars(6); byteCount -= 6; bitBufLen = 0; bitBufSkip = gFalse; @@ -3356,15 +3413,13 @@ void JPXStream::skipSOP() { } void JPXStream::skipEPH() { - int i, k; + int k; k = bitBufSkip ? 1 : 0; if (byteCount >= (Guint)(k + 2) && bufStr->lookChar(k) == 0xff && bufStr->lookChar(k + 1) == 0x92) { - for (i = 0; i < k + 2; ++i) { - bufStr->getChar(); - } + bufStr->discardChars(k + 2); byteCount -= k + 2; bitBufLen = 0; bitBufSkip = gFalse; diff -uNrp xpdf-3.03/xpdf/JPXStream.h xpdf-3.04/xpdf/JPXStream.h --- xpdf-3.03/xpdf/JPXStream.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/JPXStream.h 2014-05-28 20:50:50.000000000 +0200 @@ -199,7 +199,7 @@ struct JPXTileComp { //----- computed Guint x0, y0, x1, y1; // bounds of the tile-comp, in ref coords - Guint w; // x1 - x0 + Guint w, h; // data size = {x1 - x0, y1 - y0} >> reduction Guint cbW; // code-block width Guint cbH; // code-block height @@ -234,6 +234,9 @@ struct JPXTile { Guint precinct; // precinct Guint layer; // layer + //----- tile part info + Guint nextTilePart; // next expected tile-part + //----- children JPXTileComp *tileComps; // the tile-components (len = JPXImage.nComps) }; @@ -247,6 +250,11 @@ struct JPXImage { Guint xTileSize, yTileSize; // size of tiles Guint xTileOffset, // offset of first tile yTileOffset; + Guint xSizeR, ySizeR; // size of reference grid >> reduction + Guint xOffsetR, yOffsetR; // image offset >> reduction + Guint xTileSizeR, yTileSizeR; // size of tiles >> reduction + Guint xTileOffsetR, // offset of first tile >> reduction + yTileOffsetR; Guint nComps; // number of components //----- computed @@ -259,6 +267,14 @@ struct JPXImage { //------------------------------------------------------------------------ +enum JPXDecodeResult { + jpxDecodeOk, + jpxDecodeNonFatalError, + jpxDecodeFatalError +}; + +//------------------------------------------------------------------------ + class JPXStream: public FilterStream { public: @@ -273,14 +289,15 @@ public: virtual GBool isBinary(GBool last = gTrue); virtual void getImageParams(int *bitsPerComponent, StreamColorSpaceMode *csMode); + void reduceResolution(int reductionA) { reduction = reductionA; } private: void fillReadBuf(); void getImageParams2(int *bitsPerComponent, StreamColorSpaceMode *csMode); - GBool readBoxes(); + JPXDecodeResult readBoxes(); GBool readColorSpecBox(Guint dataLen); - GBool readCodestream(Guint len); + JPXDecodeResult readCodestream(Guint len); GBool readTilePart(); GBool readTilePartData(Guint tileIdx, Guint tilePartLen, GBool tilePartToEOC); @@ -314,6 +331,7 @@ private: Guint nComps; // number of components Guint *bpc; // bits per component, for each component Guint width, height; // image size + int reduction; // log2(reduction in resolution) GBool haveImgHdr; // set if a JP2/JPX image header has been // found JPXColorSpec cs; // color specification diff -uNrp xpdf-3.03/xpdf/Lexer.h xpdf-3.04/xpdf/Lexer.h --- xpdf-3.03/xpdf/Lexer.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Lexer.h 2014-05-28 20:50:50.000000000 +0200 @@ -53,13 +53,12 @@ public: Stream *getStream() { return curStr.isNone() ? (Stream *)NULL : curStr.getStream(); } - // Get current position in file. This is only used for error - // messages, so it returns an int instead of a Guint. - int getPos() - { return curStr.isNone() ? -1 : (int)curStr.streamGetPos(); } + // Get current position in file. + GFileOffset getPos() + { return curStr.isNone() ? -1 : curStr.streamGetPos(); } // Set position in file. - void setPos(Guint pos, int dir = 0) + void setPos(GFileOffset pos, int dir = 0) { if (!curStr.isNone()) curStr.streamSetPos(pos, dir); } // Returns true if is a whitespace character. diff -uNrp xpdf-3.03/xpdf/Link.cc xpdf-3.04/xpdf/Link.cc --- xpdf-3.03/xpdf/Link.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Link.cc 2014-05-28 20:50:50.000000000 +0200 @@ -39,7 +39,7 @@ LinkAction *LinkAction::parseDest(Object LinkAction *LinkAction::parseAction(Object *obj, GString *baseURI) { LinkAction *action; - Object obj2, obj3, obj4; + Object obj2, obj3, obj4, obj5; if (!obj->isDict()) { error(errSyntaxWarning, -1, "Bad annotation action"); @@ -86,6 +86,30 @@ LinkAction *LinkAction::parseAction(Obje obj3.free(); obj4.free(); + // JavaScript action + } else if (obj2.isName("JavaScript")) { + obj->dictLookup("JS", &obj3); + action = new LinkJavaScript(&obj3); + obj3.free(); + + // SubmitForm action + } else if (obj2.isName("SubmitForm")) { + obj->dictLookup("F", &obj3); + obj->dictLookup("Fields", &obj4); + obj->dictLookup("Flags", &obj5); + action = new LinkSubmitForm(&obj3, &obj4, &obj5); + obj3.free(); + obj4.free(); + obj5.free(); + + // Hide action + } else if (obj2.isName("Hide")) { + obj->dictLookupNF("T", &obj3); + obj->dictLookup("H", &obj4); + action = new LinkHide(&obj3, &obj4); + obj3.free(); + obj4.free(); + // unknown action } else if (obj2.isName()) { action = new LinkUnknown(obj2.getName()); @@ -644,6 +668,98 @@ LinkMovie::~LinkMovie() { } //------------------------------------------------------------------------ +// LinkJavaScript +//------------------------------------------------------------------------ + +LinkJavaScript::LinkJavaScript(Object *jsObj) { + char buf[4096]; + int n; + + if (jsObj->isString()) { + js = jsObj->getString()->copy(); + } else if (jsObj->isStream()) { + js = new GString(); + jsObj->streamReset(); + while ((n = jsObj->getStream()->getBlock(buf, sizeof(buf))) > 0) { + js->append(buf, n); + } + jsObj->streamClose(); + } else { + error(errSyntaxError, -1, "JavaScript action JS key is wrong type"); + js = NULL; + } +} + +LinkJavaScript::~LinkJavaScript() { + if (js) { + delete js; + } +} + +//------------------------------------------------------------------------ +// LinkSubmitForm +//------------------------------------------------------------------------ + +LinkSubmitForm::LinkSubmitForm(Object *urlObj, Object *fieldsObj, + Object *flagsObj) { + if (urlObj->isString()) { + url = urlObj->getString()->copy(); + } else { + error(errSyntaxError, -1, "SubmitForm action URL is wrong type"); + url = NULL; + } + + if (fieldsObj->isArray()) { + fieldsObj->copy(&fields); + } else { + if (!fieldsObj->isNull()) { + error(errSyntaxError, -1, "SubmitForm action Fields value is wrong type"); + } + fields.initNull(); + } + + if (flagsObj->isInt()) { + flags = flagsObj->getInt(); + } else { + if (!flagsObj->isNull()) { + error(errSyntaxError, -1, "SubmitForm action Flags value is wrong type"); + } + flags = 0; + } +} + +LinkSubmitForm::~LinkSubmitForm() { + if (url) { + delete url; + } + fields.free(); +} + +//------------------------------------------------------------------------ +// LinkHide +//------------------------------------------------------------------------ + +LinkHide::LinkHide(Object *fieldsObj, Object *hideFlagObj) { + if (fieldsObj->isRef() || fieldsObj->isString() || fieldsObj->isArray()) { + fieldsObj->copy(&fields); + } else { + error(errSyntaxError, -1, "Hide action T value is wrong type"); + fields.initNull(); + } + + if (hideFlagObj->isBool()) { + hideFlag = hideFlagObj->getBool(); + } else { + error(errSyntaxError, -1, "Hide action H value is wrong type"); + hideFlag = gFalse; + } +} + +LinkHide::~LinkHide() { + fields.free(); +} + +//------------------------------------------------------------------------ // LinkUnknown //------------------------------------------------------------------------ @@ -745,7 +861,7 @@ Link::~Link() { Links::Links(Object *annots, GString *baseURI) { Link *link; - Object obj1, obj2; + Object obj1, obj2, obj3; int size; int i; @@ -756,7 +872,10 @@ Links::Links(Object *annots, GString *ba if (annots->isArray()) { for (i = 0; i < annots->arrayGetLength(); ++i) { if (annots->arrayGet(i, &obj1)->isDict()) { - if (obj1.dictLookup("Subtype", &obj2)->isName("Link")) { + obj1.dictLookup("Subtype", &obj2); + obj1.dictLookup("FT", &obj3); + if (obj2.isName("Link") || + (obj2.isName("Widget") && (obj3.isName("Btn") || obj3.isNull()))) { link = new Link(obj1.getDict(), baseURI); if (link->isOk()) { if (numLinks >= size) { @@ -768,6 +887,7 @@ Links::Links(Object *annots, GString *ba delete link; } } + obj3.free(); obj2.free(); } obj1.free(); diff -uNrp xpdf-3.03/xpdf/Link.h xpdf-3.04/xpdf/Link.h --- xpdf-3.03/xpdf/Link.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Link.h 2014-05-28 20:50:50.000000000 +0200 @@ -32,6 +32,9 @@ enum LinkActionKind { actionURI, // URI actionNamed, // named action actionMovie, // movie action + actionJavaScript, // run JavaScript + actionSubmitForm, // submit form + actionHide, // hide annotation actionUnknown // anything else }; @@ -244,8 +247,10 @@ public: virtual ~LinkNamed(); + // Was the LinkNamed created successfully? virtual GBool isOk() { return name != NULL; } + // Accessors. virtual LinkActionKind getKind() { return actionNamed; } GString *getName() { return name; } @@ -265,8 +270,10 @@ public: virtual ~LinkMovie(); + // Was the LinkMovie created successfully? virtual GBool isOk() { return annotRef.num >= 0 || title != NULL; } + // Accessors. virtual LinkActionKind getKind() { return actionMovie; } GBool hasAnnotRef() { return annotRef.num >= 0; } Ref *getAnnotRef() { return &annotRef; } @@ -279,6 +286,81 @@ private: }; //------------------------------------------------------------------------ +// LinkJavaScript +//------------------------------------------------------------------------ + +class LinkJavaScript: public LinkAction { +public: + + LinkJavaScript(Object *jsObj); + + virtual ~LinkJavaScript(); + + // Was the LinkJavaScript created successfully? + virtual GBool isOk() { return js != NULL; } + + // Accessors. + virtual LinkActionKind getKind() { return actionJavaScript; } + GString *getJS() { return js; } + +private: + + GString *js; +}; + +//------------------------------------------------------------------------ +// LinkSubmitForm +//------------------------------------------------------------------------ + +class LinkSubmitForm: public LinkAction { +public: + + LinkSubmitForm(Object *urlObj, Object *fieldsObj, Object *flagsObj); + + virtual ~LinkSubmitForm(); + + // Was the LinkSubmitForm created successfully? + virtual GBool isOk() { return url != NULL; } + + // Accessors. + virtual LinkActionKind getKind() { return actionSubmitForm; } + GString *getURL() { return url; } + Object *getFields() { return &fields; } + int getFlags() { return flags; } + +private: + + GString *url; + Object fields; + int flags; +}; + +//------------------------------------------------------------------------ +// LinkHide +//------------------------------------------------------------------------ + +class LinkHide: public LinkAction { +public: + + LinkHide(Object *fieldsObj, Object *hideFlagObj); + + virtual ~LinkHide(); + + // Was the LinkHide created successfully? + virtual GBool isOk() { return !fields.isNull(); } + + // Accessors. + virtual LinkActionKind getKind() { return actionHide; } + Object *getFields() { return &fields; } + GBool getHideFlag() { return hideFlag; } + +private: + + Object fields; + GBool hideFlag; +}; + +//------------------------------------------------------------------------ // LinkUnknown //------------------------------------------------------------------------ diff -uNrp xpdf-3.03/xpdf/Makefile.in xpdf-3.04/xpdf/Makefile.in --- xpdf-3.03/xpdf/Makefile.in 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Makefile.in 2014-05-28 20:50:50.000000000 +0200 @@ -19,18 +19,19 @@ FOFILIBDIR = ../fofi SPLASHSRCDIR = $(srcdir)/../splash SPLASHLIBDIR = ../splash -CXXFLAGS = @CXXFLAGS@ @DEFS@ -I.. -I$(GOOSRCDIR) -I$(FOFISRCDIR) -I$(SPLASHSRCDIR) -I$(srcdir) @freetype2_CFLAGS@ @Sgm_CFLAGS@ @Xm_CFLAGS@ @Xt_CFLAGS@ @Xp_CFLAGS@ @Xext_CFLAGS@ @Xpm_CFLAGS@ @t1_CFLAGS@ @libpaper_CFLAGS@ @X_CFLAGS@ +CXXFLAGS = @CXXFLAGS@ @DEFS@ -I.. -I$(srcdir)/.. -I$(GOOSRCDIR) -I$(FOFISRCDIR) -I$(SPLASHSRCDIR) -I$(srcdir) @freetype2_CFLAGS@ @Sgm_CFLAGS@ @Xm_CFLAGS@ @Xt_CFLAGS@ @Xp_CFLAGS@ @Xext_CFLAGS@ @Xpm_CFLAGS@ @libpng_CFLAGS@ @libpaper_CFLAGS@ @X_CFLAGS@ @EXTRA_CFLAGS@ LDFLAGS = @LDFLAGS@ -T1LIBS = @t1_LIBS@ -FTLIBS = @freetype2_LIBS@ +FTLIBS = @freetype2_LIBS@ -lz XLIBS = @Sgm_LIBS@ @Xm_LIBS@ @Xt_LIBS@ @Xp_LIBS@ @Xext_LIBS@ @Xpm_LIBS@ @X_PRE_LIBS@ @X_LIBS@ -lX11 @X_EXTRA_LIBS@ +PNGLIBS = @libpng_LIBS@ + SPLASHLIBS = -L$(SPLASHLIBDIR) -lsplash -OTHERLIBS = @LIBS@ @libpaper_LIBS@ \ +OTHERLIBS = @LIBS@ @libpaper_LIBS@ @EXTRA_LIBS@ \ -L$(FOFILIBDIR) -lfofi \ -L$(GOOLIBDIR) -lGoo @@ -49,6 +50,7 @@ EXE = @EXE@ #------------------------------------------------------------------------ CXX_SRC = \ + $(srcdir)/AcroForm.cc \ $(srcdir)/Annot.cc \ $(srcdir)/Array.cc \ $(srcdir)/BuiltinFont.cc \ @@ -61,11 +63,13 @@ CXX_SRC = \ $(srcdir)/Dict.cc \ $(srcdir)/Error.cc \ $(srcdir)/FontEncodingTables.cc \ + $(srcdir)/Form.cc \ $(srcdir)/Function.cc \ $(srcdir)/Gfx.cc \ $(srcdir)/GfxFont.cc \ $(srcdir)/GfxState.cc \ $(srcdir)/GlobalParams.cc \ + $(srcdir)/HTMLGen.cc \ $(srcdir)/ImageOutputDev.cc \ $(srcdir)/JArithmeticDecoder.cc \ $(srcdir)/JBIG2Stream.cc \ @@ -89,44 +93,94 @@ CXX_SRC = \ $(srcdir)/SplashOutputDev.cc \ $(srcdir)/Stream.cc \ $(srcdir)/TextOutputDev.cc \ + $(srcdir)/TextString.cc \ $(srcdir)/UnicodeMap.cc \ $(srcdir)/UnicodeTypeTable.cc \ + $(srcdir)/XFAForm.cc \ $(srcdir)/XPDFApp.cc \ $(srcdir)/XPDFCore.cc \ $(srcdir)/XPDFTree.cc \ $(srcdir)/XPDFViewer.cc \ $(srcdir)/XpdfPluginAPI.cc \ $(srcdir)/XRef.cc \ + $(srcdir)/Zoox.cc \ $(srcdir)/pdftops.cc \ $(srcdir)/pdftotext.cc \ + $(srcdir)/pdftohtml.cc \ $(srcdir)/pdfinfo.cc \ $(srcdir)/pdffonts.cc \ $(srcdir)/pdfdetach.cc \ $(srcdir)/pdftoppm.cc \ + $(srcdir)/pdftopng.cc \ $(srcdir)/pdfimages.cc \ $(srcdir)/xpdf.cc #------------------------------------------------------------------------ -all: xpdf$(EXE) pdftops$(EXE) pdftotext$(EXE) pdfinfo$(EXE) \ - pdffonts$(EXE) pdfdetach$(EXE) pdftoppm$(EXE) pdfimages$(EXE) - -all-no-x: pdftops$(EXE) pdftotext$(EXE) pdfinfo$(EXE) pdffonts$(EXE) \ - pdfdetach$(EXE) pdfimages$(EXE) - -#------------------------------------------------------------------------ - -XPDF_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o Catalog.o \ - CharCodeToUnicode.o CMap.o CoreOutputDev.o Decrypt.o Dict.o \ - Error.o FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFCore.o \ - PDFDoc.o PDFDocEncoding.o PreScanOutputDev.o PSOutputDev.o \ - PSTokenizer.o SecurityHandler.o SplashOutputDev.o Stream.o \ - TextOutputDev.o UnicodeMap.o UnicodeTypeTable.o XPDFApp.o \ - XPDFCore.o XPDFTree.o XPDFViewer.o XpdfPluginAPI.o XRef.o xpdf.o -XPDF_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(T1LIBS) $(FTLIBS) \ +all: xpdf$(EXE) pdftops$(EXE) pdftotext$(EXE) pdftohtml$(EXE) \ + pdfinfo$(EXE) pdffonts$(EXE) pdfdetach$(EXE) pdftoppm$(EXE) \ + pdftopng$(EXE) pdfimages$(EXE) + +all-no-x: pdftops$(EXE) pdftotext$(EXE) pdftohtml$(EXE) pdfinfo$(EXE) \ + pdffonts$(EXE) pdfdetach$(EXE) pdfimages$(EXE) + +#------------------------------------------------------------------------ + +XPDF_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + CoreOutputDev.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFCore.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PreScanOutputDev.o \ + PSOutputDev.o \ + PSTokenizer.o \ + SecurityHandler.o \ + SplashOutputDev.o \ + Stream.o \ + TextOutputDev.o \ + TextString.o \ + UnicodeMap.o \ + UnicodeTypeTable.o \ + XFAForm.o \ + XPDFApp.o \ + XPDFCore.o \ + XPDFTree.o \ + XPDFViewer.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + xpdf.o +XPDF_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(FTLIBS) \ $(XLIBS) $(OTHERLIBS) -lm xpdf$(EXE): $(XPDF_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -134,16 +188,53 @@ xpdf$(EXE): $(XPDF_OBJS) $(GOOLIBDIR)/$( #------------------------------------------------------------------------ -PDFTOPS_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o OptionalContent.o \ - Outline.o Object.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PreScanOutputDev.o PSOutputDev.o PSTokenizer.o \ - SecurityHandler.o SplashOutputDev.o Stream.o UnicodeMap.o \ - XpdfPluginAPI.o XRef.o pdftops.o -PDFTOPS_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(T1LIBS) $(FTLIBS) \ +PDFTOPS_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + OptionalContent.o \ + Outline.o \ + Object.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PreScanOutputDev.o \ + PSOutputDev.o \ + PSTokenizer.o \ + SecurityHandler.o \ + SplashOutputDev.o \ + Stream.o \ + TextString.o \ + UnicodeMap.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdftops.o +PDFTOPS_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(FTLIBS) \ $(OTHERLIBS) -lm pdftops$(EXE): $(PDFTOPS_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -152,15 +243,51 @@ pdftops$(EXE): $(PDFTOPS_OBJS) $(GOOLIBD #------------------------------------------------------------------------ -PDFTOTEXT_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PSTokenizer.o SecurityHandler.o Stream.o \ - TextOutputDev.o UnicodeMap.o UnicodeTypeTable.o XpdfPluginAPI.o \ - XRef.o pdftotext.o +PDFTOTEXT_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + Stream.o \ + TextOutputDev.o \ + TextString.o \ + UnicodeMap.o \ + UnicodeTypeTable.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdftotext.o PDFTOTEXT_LIBS = -L$(GOOLIBDIR) -lGoo $(OTHERLIBS) -lm pdftotext$(EXE): $(PDFTOTEXT_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -169,14 +296,105 @@ pdftotext$(EXE): $(PDFTOTEXT_OBJS) $(GOO #------------------------------------------------------------------------ -PDFINFO_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PSTokenizer.o SecurityHandler.o Stream.o \ - UnicodeMap.o XpdfPluginAPI.o XRef.o pdfinfo.o +PDFTOHTML_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + HTMLGen.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + SplashOutputDev.o \ + Stream.o \ + TextOutputDev.o \ + TextString.o \ + UnicodeMap.o \ + UnicodeTypeTable.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdftohtml.o +PDFTOHTML_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(FTLIBS) \ + $(OTHERLIBS) $(PNGLIBS) -lm + +pdftohtml$(EXE): $(PDFTOHTML_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o pdftohtml$(EXE) $(PDFTOHTML_OBJS) \ + $(PDFTOHTML_LIBS) + +#------------------------------------------------------------------------ + +PDFINFO_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + Stream.o \ + TextString.o \ + UnicodeMap.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdfinfo.o PDFINFO_LIBS = -L$(GOOLIBDIR) -lGoo $(OTHERLIBS) -lm pdfinfo$(EXE): $(PDFINFO_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -185,14 +403,49 @@ pdfinfo$(EXE): $(PDFINFO_OBJS) $(GOOLIBD #------------------------------------------------------------------------ -PDFFONTS_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PSTokenizer.o SecurityHandler.o Stream.o \ - UnicodeMap.o XpdfPluginAPI.o XRef.o pdffonts.o +PDFFONTS_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + Stream.o \ + TextString.o \ + UnicodeMap.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdffonts.o PDFFONTS_LIBS = -L$(GOOLIBDIR) -lGoo $(OTHERLIBS) -lm pdffonts$(EXE): $(PDFFONTS_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -201,14 +454,49 @@ pdffonts$(EXE): $(PDFFONTS_OBJS) $(GOOLI #------------------------------------------------------------------------ -PDFDETACH_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o \ - GfxState.o GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o \ - JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PSTokenizer.o SecurityHandler.o Stream.o \ - UnicodeMap.o XpdfPluginAPI.o XRef.o pdfdetach.o +PDFDETACH_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + Stream.o \ + TextString.o \ + UnicodeMap.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdfdetach.o PDFDETACH_LIBS = -L$(GOOLIBDIR) -lGoo $(OTHERLIBS) -lm pdfdetach$(EXE): $(PDFDETACH_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -217,16 +505,53 @@ pdfdetach$(EXE): $(PDFDETACH_OBJS) $(GOO #------------------------------------------------------------------------ -PDFTOPPM_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o GfxState.o \ - GlobalParams.o JArithmeticDecoder.o JBIG2Stream.o JPXStream.o \ - Lexer.o Link.o NameToCharCode.o Object.o OptionalContent.o \ - Outline.o OutputDev.o Page.o Parser.o PDFDoc.o PDFDocEncoding.o \ - PSTokenizer.o SecurityHandler.o SplashOutputDev.o Stream.o \ - TextOutputDev.o UnicodeMap.o UnicodeTypeTable.o XpdfPluginAPI.o \ - XRef.o pdftoppm.o -PDFTOPPM_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(T1LIBS) $(FTLIBS) \ +PDFTOPPM_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + SplashOutputDev.o \ + Stream.o \ + TextOutputDev.o \ + TextString.o \ + UnicodeMap.o \ + UnicodeTypeTable.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdftoppm.o +PDFTOPPM_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(FTLIBS) \ $(OTHERLIBS) -lm pdftoppm$(EXE): $(PDFTOPPM_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -235,14 +560,105 @@ pdftoppm$(EXE): $(PDFTOPPM_OBJS) $(GOOLI #------------------------------------------------------------------------ -PDFIMAGES_OBJS = Annot.o Array.o BuiltinFont.o BuiltinFontTables.o \ - Catalog.o CharCodeToUnicode.o CMap.o Decrypt.o Dict.o Error.o \ - FontEncodingTables.o Function.o Gfx.o GfxFont.o GfxState.o \ - GlobalParams.o ImageOutputDev.o JArithmeticDecoder.o \ - JBIG2Stream.o JPXStream.o Lexer.o Link.o NameToCharCode.o Object.o \ - OptionalContent.o Outline.o OutputDev.o Page.o Parser.o PDFDoc.o \ - PDFDocEncoding.o PSTokenizer.o SecurityHandler.o Stream.o \ - UnicodeMap.o XpdfPluginAPI.o XRef.o pdfimages.o +PDFTOPNG_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + SplashOutputDev.o \ + Stream.o \ + TextOutputDev.o \ + TextString.o \ + UnicodeMap.o \ + UnicodeTypeTable.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdftopng.o +PDFTOPNG_LIBS = -L$(GOOLIBDIR) -lGoo $(SPLASHLIBS) $(FTLIBS) \ + $(OTHERLIBS) $(PNGLIBS) -lm + +pdftopng$(EXE): $(PDFTOPNG_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a + $(CXX) $(CXXFLAGS) $(LDFLAGS) -o pdftopng$(EXE) $(PDFTOPNG_OBJS) \ + $(PDFTOPNG_LIBS) + +#------------------------------------------------------------------------ + +PDFIMAGES_OBJS = \ + AcroForm.o \ + Annot.o \ + Array.o \ + BuiltinFont.o \ + BuiltinFontTables.o \ + Catalog.o \ + CharCodeToUnicode.o \ + CMap.o \ + Decrypt.o \ + Dict.o \ + Error.o \ + FontEncodingTables.o \ + Form.o \ + Function.o \ + Gfx.o \ + GfxFont.o \ + GfxState.o \ + GlobalParams.o \ + ImageOutputDev.o \ + JArithmeticDecoder.o \ + JBIG2Stream.o \ + JPXStream.o \ + Lexer.o \ + Link.o \ + NameToCharCode.o \ + Object.o \ + OptionalContent.o \ + Outline.o \ + OutputDev.o \ + Page.o \ + Parser.o \ + PDFDoc.o \ + PDFDocEncoding.o \ + PSTokenizer.o \ + SecurityHandler.o \ + Stream.o \ + TextString.o \ + UnicodeMap.o \ + XFAForm.o \ + XpdfPluginAPI.o \ + XRef.o \ + Zoox.o \ + pdfimages.o PDFIMAGES_LIBS = -L$(GOOLIBDIR) -lGoo $(OTHERLIBS) -lm pdfimages$(EXE): $(PDFIMAGES_OBJS) $(GOOLIBDIR)/$(LIBPREFIX)Goo.a @@ -255,10 +671,12 @@ clean: rm -f $(XPDF_OBJS) xpdf$(EXE) rm -f $(PDFTOPS_OBJS) pdftops$(EXE) rm -f $(PDFTOTEXT_OBJS) pdftotext$(EXE) + rm -f $(PDFTOHTML_OBJS) pdftohtml$(EXE) rm -f $(PDFINFO_OBJS) pdfinfo$(EXE) rm -f $(PDFFONTS_OBJS) pdffonts$(EXE) rm -f $(PDFDETACH_OBJS) pdfdetach$(EXE) rm -f $(PDFTOPPM_OBJS) pdftoppm$(EXE) + rm -f $(PDFTOPNG_OBJS) pdftopng$(EXE) rm -f $(PDFIMAGES_OBJS) pdfimages$(EXE) #------------------------------------------------------------------------ @@ -266,4 +684,4 @@ clean: depend: $(CXX) $(CXXFLAGS) -MM $(CXX_SRC) >Makefile.dep -include Makefile.dep +-include Makefile.dep diff -uNrp xpdf-3.03/xpdf/Object.h xpdf-3.04/xpdf/Object.h --- xpdf-3.03/xpdf/Object.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Object.h 2014-05-28 20:50:50.000000000 +0200 @@ -19,6 +19,7 @@ #include #include "gtypes.h" #include "gmem.h" +#include "gfile.h" #include "GString.h" class XRef; @@ -179,9 +180,10 @@ public: void streamClose(); int streamGetChar(); int streamLookChar(); + int streamGetBlock(char *blk, int size); char *streamGetLine(char *buf, int size); - Guint streamGetPos(); - void streamSetPos(Guint pos, int dir = 0); + GFileOffset streamGetPos(); + void streamSetPos(GFileOffset pos, int dir = 0); Dict *streamGetDict(); // Output. @@ -288,13 +290,16 @@ inline int Object::streamGetChar() inline int Object::streamLookChar() { return stream->lookChar(); } +inline int Object::streamGetBlock(char *blk, int size) + { return stream->getBlock(blk, size); } + inline char *Object::streamGetLine(char *buf, int size) { return stream->getLine(buf, size); } -inline Guint Object::streamGetPos() +inline GFileOffset Object::streamGetPos() { return stream->getPos(); } -inline void Object::streamSetPos(Guint pos, int dir) +inline void Object::streamSetPos(GFileOffset pos, int dir) { stream->setPos(pos, dir); } inline Dict *Object::streamGetDict() diff -uNrp xpdf-3.03/xpdf/OptionalContent.cc xpdf-3.04/xpdf/OptionalContent.cc --- xpdf-3.03/xpdf/OptionalContent.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/OptionalContent.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // OptionalContent.cc // -// Copyright 2008 Glyph & Cog, LLC +// Copyright 2008-2013 Glyph & Cog, LLC // //======================================================================== @@ -17,7 +17,7 @@ #include "Error.h" #include "Object.h" #include "PDFDoc.h" -#include "PDFDocEncoding.h" +#include "TextString.h" #include "OptionalContent.h" //------------------------------------------------------------------------ @@ -150,70 +150,79 @@ GBool OptionalContent::evalOCObject(Obje } } obj->fetch(xref, &obj2); - if (obj2.isDict("OCMD")) { - if (obj2.dictLookup("VE", &obj3)->isArray()) { - *visible = evalOCVisibilityExpr(&obj3, 0); - obj3.free(); - } else { - obj3.free(); - policy = ocPolicyAnyOn; - if (obj2.dictLookup("P", &obj3)->isName()) { - if (obj3.isName("AllOn")) { - policy = ocPolicyAllOn; - } else if (obj3.isName("AnyOn")) { - policy = ocPolicyAnyOn; - } else if (obj3.isName("AnyOff")) { - policy = ocPolicyAnyOff; - } else if (obj3.isName("AllOff")) { - policy = ocPolicyAllOff; - } + if (!obj2.isDict("OCMD")) { + obj2.free(); + return gFalse; + } + if (obj2.dictLookup("VE", &obj3)->isArray()) { + *visible = evalOCVisibilityExpr(&obj3, 0); + obj3.free(); + } else { + obj3.free(); + policy = ocPolicyAnyOn; + if (obj2.dictLookup("P", &obj3)->isName()) { + if (obj3.isName("AllOn")) { + policy = ocPolicyAllOn; + } else if (obj3.isName("AnyOn")) { + policy = ocPolicyAnyOn; + } else if (obj3.isName("AnyOff")) { + policy = ocPolicyAnyOff; + } else if (obj3.isName("AllOff")) { + policy = ocPolicyAllOff; } - obj3.free(); - obj2.dictLookupNF("OCGs", &obj3); - ocg = NULL; - if (obj3.isRef()) { - ref = obj3.getRef(); - ocg = findOCG(&ref); + } + obj3.free(); + obj2.dictLookupNF("OCGs", &obj3); + ocg = NULL; + if (obj3.isRef()) { + ref = obj3.getRef(); + ocg = findOCG(&ref); + } + if (ocg) { + *visible = (policy == ocPolicyAllOn || policy == ocPolicyAnyOn) ? + ocg->getState() : !ocg->getState(); + } else { + *visible = policy == ocPolicyAllOn || policy == ocPolicyAllOff; + if (!obj3.fetch(xref, &obj4)->isArray()) { + obj4.free(); + obj3.free(); + obj2.free(); + return gFalse; } - if (ocg) { - *visible = (policy == ocPolicyAllOn || policy == ocPolicyAnyOn) ? - ocg->getState() : !ocg->getState(); - } else { - *visible = gTrue; - if (obj3.fetch(xref, &obj4)->isArray()) { - for (i = 0; i < obj4.arrayGetLength(); ++i) { - obj4.arrayGetNF(i, &obj5); - if (obj5.isRef()) { - ref = obj5.getRef(); - if ((ocg = findOCG(&ref))) { - 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; - } - } - } + 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; + } + 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; } } - obj4.free(); + obj5.free(); } - obj3.free(); + obj4.free(); } - obj2.free(); - return gTrue; + obj3.free(); } obj2.free(); - return gFalse; + return gTrue; } GBool OptionalContent::evalOCVisibilityExpr(Object *expr, int recursion) { @@ -279,12 +288,9 @@ GBool OptionalContent::evalOCVisibilityE //------------------------------------------------------------------------ OptionalContentGroup *OptionalContentGroup::parse(Ref *refA, Object *obj) { - Unicode *nameA; - int nameLenA; + TextString *nameA; Object obj1, obj2, obj3; - GString *s; OCUsageState viewStateA, printStateA; - int i; if (!obj->isDict()) { return NULL; @@ -294,22 +300,7 @@ OptionalContentGroup *OptionalContentGro obj1.free(); return NULL; } - s = obj1.getString(); - if ((s->getChar(0) & 0xff) == 0xfe && - (s->getChar(1) & 0xff) == 0xff) { - nameLenA = (s->getLength() - 2) / 2; - nameA = (Unicode *)gmallocn(nameLenA, sizeof(Unicode)); - for (i = 0; i < nameLenA; ++i) { - nameA[i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | - (s->getChar(3 + 2*i) & 0xff); - } - } else { - nameLenA = s->getLength(); - nameA = (Unicode *)gmallocn(nameLenA, sizeof(Unicode)); - for (i = 0; i < nameLenA; ++i) { - nameA[i] = pdfDocEncoding[s->getChar(i) & 0xff]; - } - } + nameA = new TextString(obj1.getString()); obj1.free(); viewStateA = printStateA = ocUsageUnset; @@ -339,30 +330,35 @@ OptionalContentGroup *OptionalContentGro } obj1.free(); - return new OptionalContentGroup(refA, nameA, nameLenA, - viewStateA, printStateA); + return new OptionalContentGroup(refA, nameA, viewStateA, printStateA); } -OptionalContentGroup::OptionalContentGroup(Ref *refA, Unicode *nameA, - int nameLenA, +OptionalContentGroup::OptionalContentGroup(Ref *refA, TextString *nameA, OCUsageState viewStateA, OCUsageState printStateA) { ref = *refA; name = nameA; - nameLen = nameLenA; viewState = viewStateA; printState = printStateA; state = gTrue; } OptionalContentGroup::~OptionalContentGroup() { - gfree(name); + delete name; } GBool OptionalContentGroup::matches(Ref *refA) { return refA->num == ref.num && refA->gen == ref.gen; } +Unicode *OptionalContentGroup::getName() { + return name->getUnicode(); +} + +int OptionalContentGroup::getNameLength() { + return name->getLength(); +} + //------------------------------------------------------------------------ OCDisplayNode *OCDisplayNode::parse(Object *obj, OptionalContent *oc, @@ -404,8 +400,10 @@ OCDisplayNode *OCDisplayNode::parse(Obje obj2.arrayGetNF(i, &obj3); if ((child = OCDisplayNode::parse(&obj3, oc, xref, recursion + 1))) { if (!child->ocg && !child->name && node->getNumChildren() > 0) { - node->getChild(node->getNumChildren() - 1)-> - addChildren(child->takeChildren()); + if (child->getNumChildren() > 0) { + node->getChild(node->getNumChildren() - 1)-> + addChildren(child->takeChildren()); + } delete child; } else { node->addChild(child); @@ -418,42 +416,19 @@ OCDisplayNode *OCDisplayNode::parse(Obje } OCDisplayNode::OCDisplayNode() { - name = NULL; - nameLen = 0; + name = new TextString(); ocg = NULL; children = NULL; } OCDisplayNode::OCDisplayNode(GString *nameA) { - int i; - - if ((nameA->getChar(0) & 0xff) == 0xfe && - (nameA->getChar(1) & 0xff) == 0xff) { - nameLen = (nameA->getLength() - 2) / 2; - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - for (i = 0; i < nameLen; ++i) { - name[i] = ((nameA->getChar(2 + 2*i) & 0xff) << 8) | - (nameA->getChar(3 + 2*i) & 0xff); - } - } else { - nameLen = nameA->getLength(); - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - for (i = 0; i < nameLen; ++i) { - name[i] = pdfDocEncoding[nameA->getChar(i) & 0xff]; - } - } + name = new TextString(nameA); ocg = NULL; children = NULL; } OCDisplayNode::OCDisplayNode(OptionalContentGroup *ocgA) { - nameLen = ocgA->getNameLength(); - if (nameLen) { - name = (Unicode *)gmallocn(nameLen, sizeof(Unicode)); - memcpy(name, ocgA->getName(), nameLen * sizeof(Unicode)); - } else { - name = NULL; - } + name = new TextString(ocgA->name); ocg = ocgA; children = NULL; } @@ -482,12 +457,20 @@ GList *OCDisplayNode::takeChildren() { } OCDisplayNode::~OCDisplayNode() { - gfree(name); + delete name; if (children) { deleteGList(children, OCDisplayNode); } } +Unicode *OCDisplayNode::getName() { + return name->getUnicode(); +} + +int OCDisplayNode::getNameLength() { + return name->getLength(); +} + int OCDisplayNode::getNumChildren() { if (!children) { return 0; diff -uNrp xpdf-3.03/xpdf/OptionalContent.h xpdf-3.04/xpdf/OptionalContent.h --- xpdf-3.03/xpdf/OptionalContent.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/OptionalContent.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // OptionalContent.h // -// Copyright 2008 Glyph & Cog, LLC +// Copyright 2008-2013 Glyph & Cog, LLC // //======================================================================== @@ -22,6 +22,7 @@ class GString; class GList; class PDFDoc; +class TextString; class XRef; class OptionalContentGroup; class OCDisplayNode; @@ -78,8 +79,8 @@ public: GBool matches(Ref *refA); - Unicode *getName() { return name; } - int getNameLength() { return nameLen; } + Unicode *getName(); + int getNameLength(); OCUsageState getViewState() { return viewState; } OCUsageState getPrintState() { return printState; } GBool getState() { return state; } @@ -87,15 +88,16 @@ public: private: - OptionalContentGroup(Ref *refA, Unicode *nameA, int nameLenA, + OptionalContentGroup(Ref *refA, TextString *nameA, OCUsageState viewStateA, OCUsageState printStateA); Ref ref; - Unicode *name; - int nameLen; + TextString *name; OCUsageState viewState, // suggested state when viewing printState; // suggested state when printing GBool state; // current state (on/off) + + friend class OCDisplayNode; }; //------------------------------------------------------------------------ @@ -108,8 +110,8 @@ public: OCDisplayNode(); ~OCDisplayNode(); - Unicode *getName() { return name; } - int getNameLength() { return nameLen; } + Unicode *getName(); + int getNameLength(); OptionalContentGroup *getOCG() { return ocg; } int getNumChildren(); OCDisplayNode *getChild(int idx); @@ -122,8 +124,7 @@ private: void addChildren(GList *childrenA); GList *takeChildren(); - Unicode *name; // display name (may be NULL) - int nameLen; + TextString *name; // display name OptionalContentGroup *ocg; // NULL for display labels GList *children; // NULL if there are no children // [OCDisplayNode] diff -uNrp xpdf-3.03/xpdf/Outline.cc xpdf-3.04/xpdf/Outline.cc --- xpdf-3.03/xpdf/Outline.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Outline.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // Outline.cc // -// Copyright 2002-2003 Glyph & Cog, LLC +// Copyright 2002-2013 Glyph & Cog, LLC // //======================================================================== @@ -17,7 +17,7 @@ #include "GList.h" #include "Error.h" #include "Link.h" -#include "PDFDocEncoding.h" +#include "TextString.h" #include "Outline.h" //------------------------------------------------------------------------ @@ -32,7 +32,7 @@ Outline::Outline(Object *outlineObj, XRe outlineObj->dictLookupNF("First", &first); outlineObj->dictLookupNF("Last", &last); if (first.isRef() && last.isRef()) { - items = OutlineItem::readItemList(&first, &last, xref); + items = OutlineItem::readItemList(&first, &last, NULL, xref); } first.free(); last.free(); @@ -46,35 +46,18 @@ Outline::~Outline() { //------------------------------------------------------------------------ -OutlineItem::OutlineItem(Dict *dict, XRef *xrefA) { +OutlineItem::OutlineItem(Object *itemRefA, Dict *dict, + OutlineItem *parentA, XRef *xrefA) { Object obj1; - GString *s; - int i; xref = xrefA; title = NULL; action = NULL; kids = NULL; + parent = parentA; if (dict->lookup("Title", &obj1)->isString()) { - s = obj1.getString(); - if ((s->getChar(0) & 0xff) == 0xfe && - (s->getChar(1) & 0xff) == 0xff) { - titleLen = (s->getLength() - 2) / 2; - title = (Unicode *)gmallocn(titleLen, sizeof(Unicode)); - for (i = 0; i < titleLen; ++i) { - title[i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | - (s->getChar(3 + 2*i) & 0xff); - } - } else { - titleLen = s->getLength(); - title = (Unicode *)gmallocn(titleLen, sizeof(Unicode)); - for (i = 0; i < titleLen; ++i) { - title[i] = pdfDocEncoding[s->getChar(i) & 0xff]; - } - } - } else { - titleLen = 0; + title = new TextString(obj1.getString()); } obj1.free(); @@ -88,6 +71,7 @@ OutlineItem::OutlineItem(Dict *dict, XRe } obj1.free(); + itemRefA->copy(&itemRef); dict->lookupNF("First", &firstRef); dict->lookupNF("Last", &lastRef); dict->lookupNF("Next", &nextRef); @@ -104,22 +88,24 @@ OutlineItem::OutlineItem(Dict *dict, XRe OutlineItem::~OutlineItem() { close(); if (title) { - gfree(title); + delete title; } if (action) { delete action; } + itemRef.free(); firstRef.free(); lastRef.free(); nextRef.free(); } GList *OutlineItem::readItemList(Object *firstItemRef, Object *lastItemRef, - XRef *xrefA) { + OutlineItem *parentA, XRef *xrefA) { GList *items; - OutlineItem *item; + OutlineItem *item, *sibling; Object obj; - Object *p, *refObj; + Object *p; + OutlineItem *ancestor; int i; items = new GList(); @@ -132,8 +118,36 @@ GList *OutlineItem::readItemList(Object obj.free(); break; } - item = new OutlineItem(obj.getDict(), xrefA); + item = new OutlineItem(p, obj.getDict(), parentA, xrefA); obj.free(); + + // check for loops with parents + for (ancestor = parentA; ancestor; ancestor = ancestor->parent) { + if (p->getRefNum() == ancestor->itemRef.getRefNum() && + p->getRefGen() == ancestor->itemRef.getRefGen()) { + error(errSyntaxError, -1, "Loop detected in outline"); + break; + } + } + if (ancestor) { + delete item; + break; + } + + // check for loops with siblings + for (i = 0; i < items->getLength(); ++i) { + sibling = (OutlineItem *)items->get(i); + if (p->getRefNum() == sibling->itemRef.getRefNum() && + p->getRefGen() == sibling->itemRef.getRefGen()) { + error(errSyntaxError, -1, "Loop detected in outline"); + break; + } + } + if (i < items->getLength()) { + delete item; + break; + } + items->append(item); if (p->getRefNum() == lastItemRef->getRef().num && p->getRefGen() == lastItemRef->getRef().gen) { @@ -143,23 +157,13 @@ GList *OutlineItem::readItemList(Object if (!p->isRef()) { break; } - for (i = 0; i < items->getLength(); ++i) { - refObj = (i == 0) ? firstItemRef - : &((OutlineItem *)items->get(i - 1))->nextRef; - if (refObj->getRefNum() == p->getRefNum() && - refObj->getRefGen() == p->getRefGen()) { - error(errSyntaxError, -1, "Loop detected in outline item list"); - p = NULL; - break; - } - } } while (p); return items; } void OutlineItem::open() { if (!kids) { - kids = readItemList(&firstRef, &lastRef, xref); + kids = readItemList(&firstRef, &lastRef, this, xref); } } @@ -169,3 +173,11 @@ void OutlineItem::close() { kids = NULL; } } + +Unicode *OutlineItem::getTitle() { + return title ? title->getUnicode() : (Unicode *)NULL; +} + +int OutlineItem::getTitleLength() { + return title ? title->getLength() : 0; +} diff -uNrp xpdf-3.03/xpdf/Outline.h xpdf-3.04/xpdf/Outline.h --- xpdf-3.03/xpdf/Outline.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Outline.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // Outline.h // -// Copyright 2002-2003 Glyph & Cog, LLC +// Copyright 2002-2013 Glyph & Cog, LLC // //======================================================================== @@ -22,6 +22,7 @@ class GString; class GList; class XRef; class LinkAction; +class TextString; //------------------------------------------------------------------------ @@ -44,17 +45,18 @@ private: class OutlineItem { public: - OutlineItem(Dict *dict, XRef *xrefA); + OutlineItem(Object *itemRefA, Dict *dict, OutlineItem *parentA, XRef *xrefA); ~OutlineItem(); static GList *readItemList(Object *firstItemRef, Object *lastItemRef, - XRef *xrefA); + OutlineItem *parentA, XRef *xrefA); void open(); void close(); - Unicode *getTitle() { return title; } - int getTitleLength() { return titleLen; } + Unicode *getTitle(); + int getTitleLength(); + TextString *getTitleTextString() { return title; } LinkAction *getAction() { return action; } GBool isOpen() { return startsOpen; } GBool hasKids() { return firstRef.isRef(); } @@ -63,14 +65,15 @@ public: private: XRef *xref; - Unicode *title; - int titleLen; + TextString *title; // may be NULL LinkAction *action; + Object itemRef; Object firstRef; Object lastRef; Object nextRef; GBool startsOpen; GList *kids; // NULL unless this item is open [OutlineItem] + OutlineItem *parent; }; #endif diff -uNrp xpdf-3.03/xpdf/OutputDev.cc xpdf-3.04/xpdf/OutputDev.cc --- xpdf-3.03/xpdf/OutputDev.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/OutputDev.cc 2014-05-28 20:50:50.000000000 +0200 @@ -43,6 +43,11 @@ void OutputDev::cvtDevToUser(double dx, *uy = defICTM[1] * dx + defICTM[3] * dy + defICTM[5]; } +void OutputDev::cvtUserToDev(double ux, double uy, double *dx, double *dy) { + *dx = defCTM[0] * ux + defCTM[2] * uy + defCTM[4]; + *dy = defCTM[1] * ux + defCTM[3] * uy + defCTM[5]; +} + void OutputDev::cvtUserToDev(double ux, double uy, int *dx, int *dy) { *dx = (int)(defCTM[0] * ux + defCTM[2] * uy + defCTM[4] + 0.5); *dy = (int)(defCTM[1] * ux + defCTM[3] * uy + defCTM[5] + 0.5); @@ -78,14 +83,10 @@ GBool OutputDev::beginType3Char(GfxState void OutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { - int i, j; - + GBool inlineImg, GBool interpolate) { if (inlineImg) { str->reset(); - j = height * ((width + 7) / 8); - for (i = 0; i < j; ++i) - str->getChar(); + str->discardChars(height * ((width + 7) / 8)); str->close(); } } @@ -93,21 +94,17 @@ void OutputDev::drawImageMask(GfxState * void OutputDev::setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { - drawImageMask(state, ref, str, width, height, invert, inlineImg); + GBool inlineImg, GBool interpolate) { + drawImageMask(state, ref, str, width, height, invert, inlineImg, interpolate); } void OutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg) { - int i, j; - + int *maskColors, GBool inlineImg, GBool interpolate) { if (inlineImg) { str->reset(); - j = height * ((width * colorMap->getNumPixelComps() * - colorMap->getBits() + 7) / 8); - for (i = 0; i < j; ++i) - str->getChar(); + str->discardChars(height * ((width * colorMap->getNumPixelComps() * + colorMap->getBits() + 7) / 8)); str->close(); } } @@ -117,8 +114,9 @@ void OutputDev::drawMaskedImage(GfxState GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert) { - drawImage(state, ref, str, width, height, colorMap, NULL, gFalse); + GBool maskInvert, GBool interpolate) { + drawImage(state, ref, str, width, height, colorMap, NULL, gFalse, + interpolate); } void OutputDev::drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, @@ -126,8 +124,10 @@ void OutputDev::drawSoftMaskedImage(GfxS GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap) { - drawImage(state, ref, str, width, height, colorMap, NULL, gFalse); + GfxImageColorMap *maskColorMap, + GBool interpolate) { + drawImage(state, ref, str, width, height, colorMap, NULL, gFalse, + interpolate); } #if OPI_SUPPORT diff -uNrp xpdf-3.03/xpdf/OutputDev.h xpdf-3.04/xpdf/OutputDev.h --- xpdf-3.03/xpdf/OutputDev.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/OutputDev.h 2014-05-28 20:50:50.000000000 +0200 @@ -112,6 +112,7 @@ public: // Convert between device and user coordinates. virtual void cvtDevToUser(double dx, double dy, double *ux, double *uy); + virtual void cvtUserToDev(double ux, double uy, double *dx, double *dy); virtual void cvtUserToDev(double ux, double uy, int *dx, int *dy); double *getDefCTM() { return defCTM; } @@ -161,7 +162,7 @@ public: virtual void stroke(GfxState *state) {} virtual void fill(GfxState *state) {} virtual void eoFill(GfxState *state) {} - virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, + virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -201,25 +202,26 @@ public: //----- image drawing virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg); + int *maskColors, GBool inlineImg, GBool interpolate); virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert); + GBool maskInvert, GBool interpolate); virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap); + GfxImageColorMap *maskColorMap, + GBool interpolate); #if OPI_SUPPORT //----- OPI functions diff -uNrp xpdf-3.03/xpdf/Page.cc xpdf-3.04/xpdf/Page.cc --- xpdf-3.03/xpdf/Page.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Page.cc 2014-05-28 20:50:50.000000000 +0200 @@ -25,6 +25,7 @@ #include "Gfx.h" #include "GfxState.h" #include "Annot.h" +#include "Form.h" #endif #include "Error.h" #include "Catalog.h" @@ -123,6 +124,15 @@ PageAttrs::PageAttrs(PageAttrs *attrs, D dict->lookup("Metadata", &metadata); dict->lookup("PieceInfo", &pieceInfo); dict->lookup("SeparationInfo", &separationInfo); + if (dict->lookup("UserUnit", &obj1)->isNum()) { + userUnit = obj1.getNum(); + if (userUnit < 1) { + userUnit = 1; + } + } else { + userUnit = 1; + } + obj1.free(); // resource dictionary dict->lookup("Resources", &obj1); @@ -312,7 +322,7 @@ void Page::displaySlice(OutputDev *out, Gfx *gfx; Object obj; Annots *annotList; - Dict *acroForm; + Form *form; int i; if (!out->checkPageSlice(this, hDPI, vDPI, rotate, useMediaBox, crop, @@ -347,8 +357,10 @@ void Page::displaySlice(OutputDev *out, contents.fetch(xref, &obj); if (!obj.isNull()) { gfx->saveState(); - gfx->display(&obj); - gfx->restoreState(); + gfx->display(&contents); + while (gfx->getState()->hasSaves()) { + gfx->restoreState(); + } } else { // empty pages need to call dump to do any setup required by the // OutputDev @@ -356,20 +368,11 @@ void Page::displaySlice(OutputDev *out, } obj.free(); - // draw annotations + // draw (non-form) annotations if (globalParams->getDrawAnnotations()) { annotList = new Annots(doc, getAnnots(&obj)); obj.free(); - acroForm = doc->getCatalog()->getAcroForm()->isDict() ? - doc->getCatalog()->getAcroForm()->getDict() : NULL; - if (acroForm) { - if (acroForm->lookup("NeedAppearances", &obj)) { - if (obj.isBool() && obj.getBool()) { - annotList->generateAppearances(); - } - } - obj.free(); - } + annotList->generateAnnotAppearances(); if (annotList->getNumAnnots() > 0) { if (globalParams->getPrintCommands()) { printf("***** Annotations\n"); @@ -382,6 +385,12 @@ void Page::displaySlice(OutputDev *out, delete annotList; } + // draw form fields + if ((form = doc->getCatalog()->getForm())) { + form->draw(num, gfx, printing); + out->dump(); + } + delete gfx; #endif } @@ -459,6 +468,7 @@ void Page::processLinks(OutputDev *out) delete links; } +#ifndef PDF_PARSER_ONLY void Page::getDefaultCTM(double *ctm, double hDPI, double vDPI, int rotate, GBool useMediaBox, GBool upsideDown) { GfxState *state; @@ -478,3 +488,4 @@ void Page::getDefaultCTM(double *ctm, do } delete state; } +#endif diff -uNrp xpdf-3.03/xpdf/Page.h xpdf-3.04/xpdf/Page.h --- xpdf-3.03/xpdf/Page.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Page.h 2014-05-28 20:50:50.000000000 +0200 @@ -77,6 +77,7 @@ public: Dict *getSeparationInfo() { return separationInfo.isDict() ? separationInfo.getDict() : (Dict *)NULL; } + double getUserUnit() { return userUnit; } Dict *getResourceDict() { return resources.isDict() ? resources.getDict() : (Dict *)NULL; } @@ -100,6 +101,7 @@ private: Object metadata; Object pieceInfo; Object separationInfo; + double userUnit; Object resources; }; @@ -146,6 +148,7 @@ public: Stream *getMetadata() { return attrs->getMetadata(); } Dict *getPieceInfo() { return attrs->getPieceInfo(); } Dict *getSeparationInfo() { return attrs->getSeparationInfo(); } + double getUserUnit() { return attrs->getUserUnit(); } // Get resource dictionary. Dict *getResourceDict() { return attrs->getResourceDict(); } diff -uNrp xpdf-3.03/xpdf/Parser.cc xpdf-3.04/xpdf/Parser.cc --- xpdf-3.03/xpdf/Parser.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Parser.cc 2014-05-28 20:50:50.000000000 +0200 @@ -153,7 +153,7 @@ Stream *Parser::makeStream(Object *dict, Object obj; BaseStream *baseStr; Stream *str; - Guint pos, endPos, length; + GFileOffset pos, endPos, length; // get stream start position lexer->skipToNextLine(); @@ -162,20 +162,21 @@ Stream *Parser::makeStream(Object *dict, } pos = str->getPos(); - // get length - dict->dictLookup("Length", &obj, recursion); - if (obj.isInt()) { - length = (Guint)obj.getInt(); - obj.free(); - } else { - error(errSyntaxError, getPos(), "Bad 'Length' attribute in stream"); - obj.free(); - return NULL; - } - // check for length in damaged file if (xref && xref->getStreamEnd(pos, &endPos)) { length = endPos - pos; + + // get length from the stream object + } else { + dict->dictLookup("Length", &obj, recursion); + if (obj.isInt()) { + length = (GFileOffset)(Guint)obj.getInt(); + obj.free(); + } else { + error(errSyntaxError, getPos(), "Bad 'Length' attribute in stream"); + obj.free(); + return NULL; + } } // in badly damaged PDF files, we can run off the end of the input @@ -210,7 +211,7 @@ Stream *Parser::makeStream(Object *dict, } // get filters - str = str->addFilters(dict); + str = str->addFilters(dict, recursion); return str; } diff -uNrp xpdf-3.03/xpdf/Parser.h xpdf-3.04/xpdf/Parser.h --- xpdf-3.03/xpdf/Parser.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Parser.h 2014-05-28 20:50:50.000000000 +0200 @@ -42,7 +42,7 @@ public: Stream *getStream() { return lexer->getStream(); } // Get current position in file. - int getPos() { return lexer->getPos(); } + GFileOffset getPos() { return lexer->getPos(); } private: diff -uNrp xpdf-3.03/xpdf/PDFCore.cc xpdf-3.04/xpdf/PDFCore.cc --- xpdf-3.03/xpdf/PDFCore.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/PDFCore.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // PDFCore.cc // -// Copyright 2004 Glyph & Cog, LLC +// Copyright 2004-2013 Glyph & Cog, LLC // //======================================================================== @@ -100,7 +100,8 @@ PDFCore::PDFCore(SplashColorMode colorMo selectULY = selectLRY = 0; dragging = gFalse; lastDragLeft = lastDragTop = gTrue; - selectXorColor[0] = selectXorColor[1] = selectXorColor[2] = 0; + selectXorColor[0] = selectXorColor[1] = selectXorColor[2] = + reverseVideoA ? 0xff : 0x00; splashColorXor(selectXorColor, paperColorA); historyCur = pdfHistorySize - 1; @@ -128,7 +129,11 @@ PDFCore::~PDFCore() { } for (i = 0; i < pdfHistorySize; ++i) { if (history[i].fileName) { +#ifdef _WIN32 + delete[] history[i].fileName; +#else delete history[i].fileName; +#endif } } gfree(pageY); @@ -423,6 +428,7 @@ void PDFCore::update(int topPageA, int s // check for changes to the PDF file if ((force || (!continuousMode && topPage != topPageA)) && + doc->getFileName() && checkForNewFile()) { if (loadFile(doc->getFileName()) == errNone) { if (topPageA > doc->getNumPages()) { @@ -758,13 +764,30 @@ void PDFCore::update(int topPageA, int s } hist = &history[historyCur]; if (hist->fileName) { +#ifdef _WIN32 + delete[] hist->fileName; +#else delete hist->fileName; +#endif + } +#ifdef _WIN32 + if (doc->getFileNameU()) { + hist->fileName = (wchar_t *)gmallocn(MAX_PATH + 1, sizeof(wchar_t)); + if (GetFullPathNameW(doc->getFileNameU(), MAX_PATH + 1, + hist->fileName, NULL) == 0) { + delete[] hist->fileName; + hist->fileName = NULL; + } + } else { + hist->fileName = NULL; } +#else if (doc->getFileName()) { hist->fileName = doc->getFileName()->copy(); } else { hist->fileName = NULL; } +#endif hist->page = topPage; if (historyBLen < pdfHistorySize) { ++historyBLen; @@ -807,6 +830,7 @@ void PDFCore::addPage(int pg, int rot) { void PDFCore::needTile(PDFCorePage *page, int x, int y) { PDFCoreTile *tile; + TextOutputControl textOutCtrl; TextOutputDev *textOut; int xDest, yDest, sliceW, sliceH; int i; @@ -893,7 +917,8 @@ void PDFCore::needTile(PDFCorePage *page page->links = doc->getLinks(page->page); } if (!page->text) { - if ((textOut = new TextOutputDev(NULL, gTrue, 0, gFalse, gFalse))) { + textOutCtrl.mode = textOutPhysLayout; + if ((textOut = new TextOutputDev(NULL, &textOutCtrl, gFalse))) { doc->displayPage(textOut, page->page, dpi, dpi, rotate, gFalse, gTrue, gFalse); page->text = textOut->takeText(); @@ -977,11 +1002,27 @@ GBool PDFCore::goForward() { } --historyFLen; ++historyBLen; - if (!doc || history[historyCur].fileName->cmp(doc->getFileName()) != 0) { + if (!history[historyCur].fileName) { + return gFalse; + } +#ifdef _WIN32 + if (!doc || + !doc->getFileNameU() || + wcscmp(history[historyCur].fileName, doc->getFileNameU()) != 0) { + if (loadFile(history[historyCur].fileName, + wcslen(history[historyCur].fileName)) != errNone) { + return gFalse; + } + } +#else + if (!doc || + !doc->getFileName() || + history[historyCur].fileName->cmp(doc->getFileName()) != 0) { if (loadFile(history[historyCur].fileName) != errNone) { return gFalse; } } +#endif pg = history[historyCur].page; update(pg, scrollX, continuousMode ? -1 : scrollY, zoom, rotate, gFalse, gFalse, gTrue); @@ -999,11 +1040,27 @@ GBool PDFCore::goBackward() { } --historyBLen; ++historyFLen; - if (!doc || history[historyCur].fileName->cmp(doc->getFileName()) != 0) { + if (!history[historyCur].fileName) { + return gFalse; + } +#ifdef _WIN32 + if (!doc || + !doc->getFileNameU() || + wcscmp(history[historyCur].fileName, doc->getFileNameU()) != 0) { + if (loadFile(history[historyCur].fileName, + wcslen(history[historyCur].fileName)) != errNone) { + return gFalse; + } + } +#else + if (!doc || + !doc->getFileName() || + history[historyCur].fileName->cmp(doc->getFileName()) != 0) { if (loadFile(history[historyCur].fileName) != errNone) { return gFalse; } } +#endif pg = history[historyCur].page; update(pg, scrollX, continuousMode ? -1 : scrollY, zoom, rotate, gFalse, gFalse, gTrue); @@ -1615,6 +1672,7 @@ GBool PDFCore::getSelection(int *pg, dou GString *PDFCore::extractText(int pg, double xMin, double yMin, double xMax, double yMax) { PDFCorePage *page; + TextOutputControl textOutCtrl; TextOutputDev *textOut; int x0, y0, x1, y1, t; GString *s; @@ -1633,7 +1691,8 @@ GString *PDFCore::extractText(int pg, do } s = page->text->getText(x0, y0, x1, y1); } else { - textOut = new TextOutputDev(NULL, gTrue, 0, gFalse, gFalse); + textOutCtrl.mode = textOutPhysLayout; + textOut = new TextOutputDev(NULL, &textOutCtrl, gFalse); if (textOut->isOk()) { doc->displayPage(textOut, pg, dpi, dpi, rotate, gFalse, gTrue, gFalse); textOut->cvtUserToDev(xMin, yMin, &x0, &y0); @@ -1675,10 +1734,10 @@ GBool PDFCore::find(char *s, GBool caseS GBool PDFCore::findU(Unicode *u, int len, GBool caseSensitive, GBool next, GBool backward, GBool wholeWord, GBool onePageOnly) { + TextOutputControl textOutCtrl; TextOutputDev *textOut; double xMin, yMin, xMax, yMax; PDFCorePage *page; - PDFCoreTile *tile; int pg; GBool startAtTop, startAtLast, stopAtLast; @@ -1721,7 +1780,8 @@ GBool PDFCore::findU(Unicode *u, int len if (!onePageOnly) { // search following/previous pages - textOut = new TextOutputDev(NULL, gTrue, 0, gFalse, gFalse); + textOutCtrl.mode = textOutPhysLayout; + textOut = new TextOutputDev(NULL, &textOutCtrl, gFalse); if (!textOut->isOk()) { delete textOut; goto notFound; @@ -1791,7 +1851,6 @@ GBool PDFCore::findU(Unicode *u, int len // found: change the selection found: - tile = (PDFCoreTile *)page->tiles->get(0); setSelection(pg, (int)floor(xMin), (int)floor(yMin), (int)ceil(xMax), (int)ceil(yMax)); diff -uNrp xpdf-3.03/xpdf/PDFCore.h xpdf-3.04/xpdf/PDFCore.h --- xpdf-3.03/xpdf/PDFCore.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/PDFCore.h 2014-05-28 20:50:50.000000000 +0200 @@ -100,7 +100,11 @@ public: //------------------------------------------------------------------------ struct PDFHistory { +#ifdef _WIN32 + wchar_t *fileName; +#else GString *fileName; +#endif int page; }; diff -uNrp xpdf-3.03/xpdf/pdfdetach.cc xpdf-3.04/xpdf/pdfdetach.cc --- xpdf-3.03/xpdf/pdfdetach.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/pdfdetach.cc 2014-05-28 20:50:50.000000000 +0200 @@ -147,7 +147,7 @@ int main(int argc, char *argv[]) { } else if (saveAll) { for (i = 0; i < nFiles; ++i) { if (savePath[0]) { - n = strlen(savePath); + n = (int)strlen(savePath); if (n > (int)sizeof(path) - 2) { n = sizeof(path) - 2; } diff -uNrp xpdf-3.03/xpdf/PDFDoc.cc xpdf-3.04/xpdf/PDFDoc.cc --- xpdf-3.03/xpdf/PDFDoc.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/PDFDoc.cc 2014-05-28 20:50:50.000000000 +0200 @@ -231,6 +231,7 @@ GBool PDFDoc::setup(GString *ownerPasswo // read the optional content info optContent = new OptionalContent(this); + // done return gTrue; } @@ -309,10 +310,8 @@ void PDFDoc::checkHeader() { int i; pdfVersion = 0; - for (i = 0; i < headerSearchSize; ++i) { - hdrBuf[i] = str->getChar(); - } - hdrBuf[headerSearchSize] = '\0'; + memset(hdrBuf, 0, headerSearchSize + 1); + str->getBlock(hdrBuf, headerSearchSize); for (i = 0; i < headerSearchSize - 5; ++i) { if (!strncmp(&hdrBuf[i], "%PDF-", 5)) { break; @@ -455,15 +454,16 @@ GBool PDFDoc::isLinearized() { GBool PDFDoc::saveAs(GString *name) { FILE *f; - int c; + char buf[4096]; + int n; if (!(f = fopen(name->getCString(), "wb"))) { error(errIO, -1, "Couldn't open file '{0:t}'", name); return gFalse; } str->reset(); - while ((c = str->getChar()) != EOF) { - fputc(c, f); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + fwrite(buf, 1, n, f); } str->close(); fclose(f); @@ -518,14 +518,15 @@ GBool PDFDoc::saveEmbeddedFile(int idx, GBool PDFDoc::saveEmbeddedFile2(int idx, FILE *f) { Object strObj; - int c; + char buf[4096]; + int n; if (!catalog->getEmbeddedFileStreamObj(idx, &strObj)) { return gFalse; } strObj.streamReset(); - while ((c = strObj.streamGetChar()) != EOF) { - fputc(c, f); + while ((n = strObj.streamGetBlock(buf, sizeof(buf))) > 0) { + fwrite(buf, 1, n, f); } strObj.streamClose(); strObj.free(); @@ -535,24 +536,28 @@ GBool PDFDoc::saveEmbeddedFile2(int idx, char *PDFDoc::getEmbeddedFileMem(int idx, int *size) { Object strObj; char *buf; - int bufSize, len, c; + int bufSize, sizeInc, n; if (!catalog->getEmbeddedFileStreamObj(idx, &strObj)) { return NULL; } strObj.streamReset(); - bufSize = 1024; - buf = (char *)gmalloc(bufSize); - len = 0; - while ((c = strObj.streamGetChar()) != EOF) { - if (len == bufSize) { - bufSize *= 2; - buf = (char *)grealloc(buf, bufSize); + bufSize = 0; + buf = NULL; + do { + sizeInc = bufSize ? bufSize : 1024; + if (bufSize > INT_MAX - sizeInc) { + error(errIO, -1, "embedded file is too large"); + *size = 0; + return NULL; } - buf[len++] = (char)c; - } + buf = (char *)grealloc(buf, bufSize + sizeInc); + n = strObj.streamGetBlock(buf + bufSize, sizeInc); + bufSize += n; + } while (n == sizeInc); strObj.streamClose(); strObj.free(); - *size = len; + *size = bufSize; return buf; } + diff -uNrp xpdf-3.03/xpdf/pdffonts.cc xpdf-3.04/xpdf/pdffonts.cc --- xpdf-3.03/xpdf/pdffonts.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/pdffonts.cc 2014-05-28 20:50:50.000000000 +0200 @@ -22,6 +22,7 @@ #include "Dict.h" #include "GfxFont.h" #include "Annot.h" +#include "Form.h" #include "PDFDoc.h" #include "config.h" @@ -41,11 +42,14 @@ static const char *fontTypeNames[] = { "CID TrueType (OT)" }; +static void scanFonts(Object *obj, PDFDoc *doc); static void scanFonts(Dict *resDict, PDFDoc *doc); static void scanFont(GfxFont *font, PDFDoc *doc); static int firstPage = 1; static int lastPage = 0; +static GBool showFontLoc = gFalse; +static GBool showFontLocPS = gFalse; static char ownerPassword[33] = "\001"; static char userPassword[33] = "\001"; static char cfgFileName[256] = ""; @@ -57,6 +61,10 @@ static ArgDesc argDesc[] = { "first page to examine"}, {"-l", argInt, &lastPage, 0, "last page to examine"}, + {"-loc", argFlag, &showFontLoc, 0, + "print extended info on font location"}, + {"-locPS", argFlag, &showFontLocPS, 0, + "print extended info on font location for PostScript conversion"}, {"-opw", argString, ownerPassword, sizeof(ownerPassword), "owner password (for encrypted files)"}, {"-upw", argString, userPassword, sizeof(userPassword), @@ -80,6 +88,10 @@ static Ref *fonts; static int fontsLen; static int fontsSize; +static Ref *seenObjs; +static int seenObjsLen; +static int seenObjsSize; + int main(int argc, char *argv[]) { PDFDoc *doc; GString *fileName; @@ -88,8 +100,9 @@ int main(int argc, char *argv[]) { Page *page; Dict *resDict; Annots *annots; + Form *form; Object obj1, obj2; - int pg, i; + int pg, i, j; int exitCode; exitCode = 99; @@ -108,6 +121,7 @@ int main(int argc, char *argv[]) { // read config file globalParams = new GlobalParams(cfgFileName); + globalParams->setupBaseFonts(NULL); // open PDF file if (ownerPassword[0] != '\001') { @@ -141,10 +155,17 @@ int main(int argc, char *argv[]) { } // scan the fonts - printf("name type emb sub uni object ID\n"); - printf("------------------------------------ ----------------- --- --- --- ---------\n"); + if (showFontLoc || showFontLocPS) { + printf("name type emb sub uni object ID location\n"); + printf("------------------------------------ ----------------- --- --- --- --------- --------\n"); + } else { + printf("name type emb sub uni object ID\n"); + printf("------------------------------------ ----------------- --- --- --- ---------\n"); + } fonts = NULL; fontsLen = fontsSize = 0; + seenObjs = NULL; + seenObjsLen = seenObjsSize = 0; for (pg = firstPage; pg <= lastPage; ++pg) { page = doc->getCatalog()->getPage(pg); if ((resDict = page->getResourceDict())) { @@ -154,21 +175,35 @@ int main(int argc, char *argv[]) { obj1.free(); for (i = 0; i < annots->getNumAnnots(); ++i) { if (annots->getAnnot(i)->getAppearance(&obj1)->isStream()) { - obj1.streamGetDict()->lookup("Resources", &obj2); - if (obj2.isDict()) { - scanFonts(obj2.getDict(), doc); - } + obj1.streamGetDict()->lookupNF("Resources", &obj2); + scanFonts(&obj2, doc); obj2.free(); } obj1.free(); } delete annots; } + if ((form = doc->getCatalog()->getForm())) { + for (i = 0; i < form->getNumFields(); ++i) { + form->getField(i)->getResources(&obj1); + if (obj1.isArray()) { + for (j = 0; j < obj1.arrayGetLength(); ++j) { + obj1.arrayGetNF(j, &obj2); + scanFonts(&obj2, doc); + obj2.free(); + } + } else if (obj1.isDict()) { + scanFonts(obj1.getDict(), doc); + } + obj1.free(); + } + } exitCode = 0; // clean up gfree(fonts); + gfree(seenObjs); err1: delete doc; delete globalParams; @@ -181,8 +216,37 @@ int main(int argc, char *argv[]) { return exitCode; } +static void scanFonts(Object *obj, PDFDoc *doc) { + Object obj2; + int i; + + if (obj->isRef()) { + for (i = 0; i < seenObjsLen; ++i) { + if (obj->getRefNum() == seenObjs[i].num && + obj->getRefGen() == seenObjs[i].gen) { + return; + } + } + if (seenObjsLen == seenObjsSize) { + if (seenObjsSize <= INT_MAX - 32) { + seenObjsSize += 32; + } else { + // let greallocn throw an exception + seenObjsSize = -1; + } + seenObjs = (Ref *)greallocn(seenObjs, seenObjsSize, sizeof(Ref)); + } + seenObjs[seenObjsLen++] = obj->getRef(); + } + if (obj->fetch(doc->getXRef(), &obj2)->isDict()) { + scanFonts(obj2.getDict(), doc); + } + obj2.free(); +} + static void scanFonts(Dict *resDict, PDFDoc *doc) { - Object obj1, obj2, xObjDict, xObj, resObj; + Object obj1, obj2, xObjDict, xObj; + Object patternDict, pattern, gsDict, gs, smask, smaskGroup, resObj; Ref r; GfxFontDict *gfxFontDict; GfxFont *font; @@ -211,23 +275,58 @@ static void scanFonts(Dict *resDict, PDF } obj1.free(); - // recursively scan any resource dictionaries in objects in this + // recursively scan any resource dictionaries in XObjects in this // resource dictionary resDict->lookup("XObject", &xObjDict); if (xObjDict.isDict()) { for (i = 0; i < xObjDict.dictGetLength(); ++i) { xObjDict.dictGetVal(i, &xObj); if (xObj.isStream()) { - xObj.streamGetDict()->lookup("Resources", &resObj); - if (resObj.isDict()) { - scanFonts(resObj.getDict(), doc); - } + xObj.streamGetDict()->lookupNF("Resources", &resObj); + scanFonts(&resObj, doc); resObj.free(); } xObj.free(); } } xObjDict.free(); + + // recursively scan any resource dictionaries in Patterns in this + // resource dictionary + resDict->lookup("Pattern", &patternDict); + if (patternDict.isDict()) { + for (i = 0; i < patternDict.dictGetLength(); ++i) { + patternDict.dictGetVal(i, &pattern); + if (pattern.isStream()) { + pattern.streamGetDict()->lookupNF("Resources", &resObj); + scanFonts(&resObj, doc); + resObj.free(); + } + pattern.free(); + } + } + patternDict.free(); + + // recursively scan any resource dictionaries in ExtGStates in this + // resource dictionary + resDict->lookup("ExtGState", &gsDict); + if (gsDict.isDict()) { + for (i = 0; i < gsDict.dictGetLength(); ++i) { + if (gsDict.dictGetVal(i, &gs)->isDict()) { + if (gs.dictLookup("SMask", &smask)->isDict()) { + if (smask.dictLookup("G", &smaskGroup)->isStream()) { + smaskGroup.streamGetDict()->lookupNF("Resources", &resObj); + scanFonts(&resObj, doc); + resObj.free(); + } + smaskGroup.free(); + } + smask.free(); + } + gs.free(); + } + } + gsDict.free(); } static void scanFont(GfxFont *font, PDFDoc *doc) { @@ -235,6 +334,7 @@ static void scanFont(GfxFont *font, PDFD Object fontObj, toUnicodeObj; GString *name; GBool emb, subset, hasToUnicode; + GfxFontLoc *loc; int i; fontRef = *font->getID(); @@ -284,10 +384,38 @@ static void scanFont(GfxFont *font, PDFD subset ? "yes" : "no", hasToUnicode ? "yes" : "no"); if (fontRef.gen >= 100000) { - printf(" [none]\n"); + printf(" [none]"); } else { - printf(" %6d %2d\n", fontRef.num, fontRef.gen); + printf(" %6d %2d", fontRef.num, fontRef.gen); + } + if (showFontLoc || showFontLocPS) { + if (font->getType() == fontType3) { + printf(" embedded"); + } else { + loc = font->locateFont(doc->getXRef(), showFontLocPS); + if (loc) { + if (loc->locType == gfxFontLocEmbedded) { + printf(" embedded"); + } else if (loc->locType == gfxFontLocExternal) { + if (loc->path) { + printf(" external: %s", loc->path->getCString()); + } else { + printf(" unavailable"); + } + } else if (loc->locType == gfxFontLocResident) { + if (loc->path) { + printf(" resident: %s", loc->path->getCString()); + } else { + printf(" unavailable"); + } + } + } else { + printf(" unknown"); + } + delete loc; + } } + printf("\n"); // add this font to the list if (fontsLen == fontsSize) { diff -uNrp xpdf-3.03/xpdf/pdfinfo.cc xpdf-3.04/xpdf/pdfinfo.cc --- xpdf-3.03/xpdf/pdfinfo.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/pdfinfo.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // pdfinfo.cc // -// Copyright 1998-2003 Glyph & Cog, LLC +// Copyright 1998-2013 Glyph & Cog, LLC // //======================================================================== @@ -16,6 +16,7 @@ #include "parseargs.h" #include "GString.h" #include "gmem.h" +#include "gfile.h" #include "GlobalParams.h" #include "Object.h" #include "Stream.h" @@ -27,7 +28,7 @@ #include "PDFDoc.h" #include "CharTypes.h" #include "UnicodeMap.h" -#include "PDFDocEncoding.h" +#include "TextString.h" #include "Error.h" #include "config.h" @@ -239,6 +240,7 @@ int main(int argc, char *argv[]) { wISO /= sqrt(2.0); } } + printf(" (rotated %d degrees)", doc->getPageRotate(pg)); printf("\n"); } @@ -275,16 +277,8 @@ int main(int argc, char *argv[]) { f = fopen(fileName->getCString(), "rb"); #endif if (f) { -#if HAVE_FSEEKO - fseeko(f, 0, SEEK_END); - printf("File size: %u bytes\n", (Guint)ftello(f)); -#elif HAVE_FSEEK64 - fseek64(f, 0, SEEK_END); - printf("File size: %u bytes\n", (Guint)ftell64(f)); -#else - fseek(f, 0, SEEK_END); - printf("File size: %d bytes\n", (int)ftell(f)); -#endif + gfseek(f, 0, SEEK_END); + printf("File size: %u bytes\n", (Guint)gftell(f)); fclose(f); } @@ -322,36 +316,21 @@ int main(int argc, char *argv[]) { static void printInfoString(Dict *infoDict, const char *key, const char *text, UnicodeMap *uMap) { Object obj; - GString *s1; - GBool isUnicode; - Unicode u; + TextString *s; + Unicode *u; char buf[8]; int i, n; if (infoDict->lookup(key, &obj)->isString()) { fputs(text, stdout); - s1 = obj.getString(); - if ((s1->getChar(0) & 0xff) == 0xfe && - (s1->getChar(1) & 0xff) == 0xff) { - isUnicode = gTrue; - i = 2; - } else { - isUnicode = gFalse; - i = 0; - } - while (i < obj.getString()->getLength()) { - if (isUnicode) { - u = ((s1->getChar(i) & 0xff) << 8) | - (s1->getChar(i+1) & 0xff); - i += 2; - } else { - u = pdfDocEncoding[s1->getChar(i) & 0xff]; - ++i; - } - n = uMap->mapUnicode(u, buf, sizeof(buf)); + s = new TextString(obj.getString()); + u = s->getUnicode(); + for (i = 0; i < s->getLength(); ++i) { + n = uMap->mapUnicode(u[i], buf, sizeof(buf)); fwrite(buf, 1, n, stdout); } fputc('\n', stdout); + delete s; } obj.free(); } diff -uNrp xpdf-3.03/xpdf/pdftohtml.cc xpdf-3.04/xpdf/pdftohtml.cc --- xpdf-3.03/xpdf/pdftohtml.cc 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/pdftohtml.cc 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,246 @@ +//======================================================================== +// +// pdftohtml.cc +// +// Copyright 2005 Glyph & Cog, LLC +// +//======================================================================== + +#include +#include +#include +#include "parseargs.h" +#include "gmem.h" +#include "gfile.h" +#include "GString.h" +#include "GlobalParams.h" +#include "PDFDoc.h" +#include "HTMLGen.h" +#include "Error.h" +#include "ErrorCodes.h" +#include "config.h" + +//------------------------------------------------------------------------ + +static GBool createIndex(char *htmlDir); + +//------------------------------------------------------------------------ + +static int firstPage = 1; +static int lastPage = 0; +static int resolution = 150; +static GBool skipInvisible = gFalse; +static char ownerPassword[33] = "\001"; +static char userPassword[33] = "\001"; +static GBool quiet = gFalse; +static char cfgFileName[256] = ""; +static GBool printVersion = gFalse; +static GBool printHelp = gFalse; + +static ArgDesc argDesc[] = { + {"-f", argInt, &firstPage, 0, + "first page to convert"}, + {"-l", argInt, &lastPage, 0, + "last page to convert"}, + {"-r", argInt, &resolution, 0, + "resolution, in DPI (default is 150)"}, + {"-skipinvisible", argFlag, &skipInvisible, 0, + "do not draw invisible text"}, + {"-opw", argString, ownerPassword, sizeof(ownerPassword), + "owner password (for encrypted files)"}, + {"-upw", argString, userPassword, sizeof(userPassword), + "user password (for encrypted files)"}, + {"-q", argFlag, &quiet, 0, + "don't print any messages or errors"}, + {"-cfg", argString, cfgFileName, sizeof(cfgFileName), + "configuration file to use in place of .xpdfrc"}, + {"-v", argFlag, &printVersion, 0, + "print copyright and version info"}, + {"-h", argFlag, &printHelp, 0, + "print usage information"}, + {"-help", argFlag, &printHelp, 0, + "print usage information"}, + {"--help", argFlag, &printHelp, 0, + "print usage information"}, + {"-?", argFlag, &printHelp, 0, + "print usage information"}, + {NULL} +}; + +//------------------------------------------------------------------------ + +static int writeToFile(void *file, const char *data, int size) { + return (int)fwrite(data, 1, size, (FILE *)file); +} + +int main(int argc, char *argv[]) { + PDFDoc *doc; + GString *fileName; + char *htmlDir; + GString *ownerPW, *userPW; + HTMLGen *htmlGen; + GString *htmlFileName, *pngFileName, *pngURL; + FILE *htmlFile, *pngFile; + int pg, err, exitCode; + GBool ok; + + exitCode = 99; + + // parse args + ok = parseArgs(argDesc, &argc, argv); + if (!ok || argc != 3 || printVersion || printHelp) { + fprintf(stderr, "pdftohtml version %s\n", xpdfVersion); + fprintf(stderr, "%s\n", xpdfCopyright); + if (!printVersion) { + printUsage("pdftohtml", " ", argDesc); + } + goto err0; + } + fileName = new GString(argv[1]); + htmlDir = argv[2]; + + // read config file + globalParams = new GlobalParams(cfgFileName); + if (quiet) { + globalParams->setErrQuiet(quiet); + } + globalParams->setupBaseFonts(NULL); + globalParams->setTextEncoding("UTF-8"); + + // open PDF file + if (ownerPassword[0] != '\001') { + ownerPW = new GString(ownerPassword); + } else { + ownerPW = NULL; + } + if (userPassword[0] != '\001') { + userPW = new GString(userPassword); + } else { + userPW = NULL; + } + doc = new PDFDoc(fileName, ownerPW, userPW); + if (userPW) { + delete userPW; + } + if (ownerPW) { + delete ownerPW; + } + if (!doc->isOk()) { + exitCode = 1; + goto err1; + } + + // check for copy permission + if (!doc->okToCopy()) { + error(errNotAllowed, -1, + "Copying of text from this document is not allowed."); + exitCode = 3; + goto err1; + } + + // get page range + if (firstPage < 1) { + firstPage = 1; + } + if (lastPage < 1 || lastPage > doc->getNumPages()) { + lastPage = doc->getNumPages(); + } + + // create HTML directory + if (!createDir(htmlDir, 0755)) { + error(errIO, -1, "Couldn't create HTML output directory '{0:s}'", + htmlDir); + exitCode = 2; + goto err1; + } + + // set up the HTMLGen object + htmlGen = new HTMLGen(resolution); + if (!htmlGen->isOk()) { + exitCode = 99; + goto err1; + } + htmlGen->setDrawInvisibleText(!skipInvisible); + htmlGen->startDoc(doc); + + // convert the pages + 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"))) { + error(errIO, -1, "Couldn't open HTML file '{0:t}'", htmlFileName); + delete htmlFileName; + delete pngFileName; + goto err2; + } + if (!(pngFile = fopen(pngFileName->getCString(), "wb"))) { + error(errIO, -1, "Couldn't open PNG file '{0:t}'", pngFileName); + fclose(htmlFile); + delete htmlFileName; + delete pngFileName; + goto err2; + } + pngURL = GString::format("page{0:d}.png", pg); + err = htmlGen->convertPage(pg, pngURL->getCString(), + &writeToFile, htmlFile, + &writeToFile, pngFile); + delete pngURL; + fclose(htmlFile); + fclose(pngFile); + delete htmlFileName; + delete pngFileName; + if (err != errNone) { + error(errIO, -1, "Error converting page {0:d}", pg); + exitCode = 2; + goto err2; + } + } + + // create the master index + if (!createIndex(htmlDir)) { + exitCode = 2; + goto err2; + } + + exitCode = 0; + + // clean up + err2: + delete htmlGen; + err1: + delete doc; + delete globalParams; + err0: + + // check for memory leaks + Object::memCheck(stderr); + gMemReport(stderr); + + return exitCode; +} + +static GBool createIndex(char *htmlDir) { + GString *htmlFileName; + FILE *html; + int pg; + + htmlFileName = GString::format("{0:s}/index.html", htmlDir); + html = fopen(htmlFileName->getCString(), "w"); + delete htmlFileName; + if (!html) { + error(errIO, -1, "Couldn't open HTML file '{0:t}'", htmlFileName); + return gFalse; + } + + fprintf(html, "\n"); + fprintf(html, "\n"); + for (pg = firstPage; pg <= lastPage; ++pg) { + fprintf(html, "
page %d
\n", pg, pg); + } + fprintf(html, "\n"); + fprintf(html, "\n"); + + fclose(html); + + return gTrue; +} diff -uNrp xpdf-3.03/xpdf/pdftopng.cc xpdf-3.04/xpdf/pdftopng.cc --- xpdf-3.03/xpdf/pdftopng.cc 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/pdftopng.cc 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,289 @@ +//======================================================================== +// +// pdftopng.cc +// +// Copyright 2009 Glyph & Cog, LLC +// +//======================================================================== + +#include +#include +#include +#include +#include "parseargs.h" +#include "gmem.h" +#include "GString.h" +#include "GlobalParams.h" +#include "Object.h" +#include "PDFDoc.h" +#include "SplashBitmap.h" +#include "Splash.h" +#include "SplashOutputDev.h" +#include "config.h" + +static int firstPage = 1; +static int lastPage = 0; +static int resolution = 150; +static GBool mono = gFalse; +static GBool gray = gFalse; +static char enableFreeTypeStr[16] = ""; +static char antialiasStr[16] = ""; +static char vectorAntialiasStr[16] = ""; +static char ownerPassword[33] = ""; +static char userPassword[33] = ""; +static GBool quiet = gFalse; +static char cfgFileName[256] = ""; +static GBool printVersion = gFalse; +static GBool printHelp = gFalse; + +static ArgDesc argDesc[] = { + {"-f", argInt, &firstPage, 0, + "first page to print"}, + {"-l", argInt, &lastPage, 0, + "last page to print"}, + {"-r", argInt, &resolution, 0, + "resolution, in DPI (default is 150)"}, + {"-mono", argFlag, &mono, 0, + "generate a monochrome PBM file"}, + {"-gray", argFlag, &gray, 0, + "generate a grayscale PGM file"}, +#if HAVE_FREETYPE_FREETYPE_H | HAVE_FREETYPE_H + {"-freetype", argString, enableFreeTypeStr, sizeof(enableFreeTypeStr), + "enable FreeType font rasterizer: yes, no"}, +#endif + {"-aa", argString, antialiasStr, sizeof(antialiasStr), + "enable font anti-aliasing: yes, no"}, + {"-aaVector", argString, vectorAntialiasStr, sizeof(vectorAntialiasStr), + "enable vector anti-aliasing: yes, no"}, + {"-opw", argString, ownerPassword, sizeof(ownerPassword), + "owner password (for encrypted files)"}, + {"-upw", argString, userPassword, sizeof(userPassword), + "user password (for encrypted files)"}, + {"-q", argFlag, &quiet, 0, + "don't print any messages or errors"}, + {"-cfg", argString, cfgFileName, sizeof(cfgFileName), + "configuration file to use in place of .xpdfrc"}, + {"-v", argFlag, &printVersion, 0, + "print copyright and version info"}, + {"-h", argFlag, &printHelp, 0, + "print usage information"}, + {"-help", argFlag, &printHelp, 0, + "print usage information"}, + {"--help", argFlag, &printHelp, 0, + "print usage information"}, + {"-?", argFlag, &printHelp, 0, + "print usage information"}, + {NULL} +}; + +static void setupPNG(png_structp *png, png_infop *pngInfo, FILE *f, + int bitDepth, int colorType, + SplashBitmap *bitmap); +static void writePNGData(png_structp png, SplashBitmap *bitmap); +static void finishPNG(png_structp *png, png_infop *pngInfo); + +int main(int argc, char *argv[]) { + PDFDoc *doc; + GString *fileName; + char *pngRoot; + GString *pngFile; + GString *ownerPW, *userPW; + SplashColor paperColor; + SplashOutputDev *splashOut; + GBool ok; + int exitCode; + int pg; + png_structp png; + png_infop pngInfo; + FILE *f; + + exitCode = 99; + + // parse args + ok = parseArgs(argDesc, &argc, argv); + if (mono && gray) { + ok = gFalse; + } + if (!ok || argc != 3 || printVersion || printHelp) { + fprintf(stderr, "pdftopng version %s\n", xpdfVersion); + fprintf(stderr, "%s\n", xpdfCopyright); + if (!printVersion) { + printUsage("pdftopng", " ", argDesc); + } + goto err0; + } + fileName = new GString(argv[1]); + pngRoot = argv[2]; + + // read config file + globalParams = new GlobalParams(cfgFileName); + globalParams->setupBaseFonts(NULL); + if (enableFreeTypeStr[0]) { + if (!globalParams->setEnableFreeType(enableFreeTypeStr)) { + fprintf(stderr, "Bad '-freetype' value on command line\n"); + } + } + if (antialiasStr[0]) { + if (!globalParams->setAntialias(antialiasStr)) { + fprintf(stderr, "Bad '-aa' value on command line\n"); + } + } + if (vectorAntialiasStr[0]) { + if (!globalParams->setVectorAntialias(vectorAntialiasStr)) { + fprintf(stderr, "Bad '-aaVector' value on command line\n"); + } + } + if (quiet) { + globalParams->setErrQuiet(quiet); + } + + // open PDF file + if (ownerPassword[0]) { + ownerPW = new GString(ownerPassword); + } else { + ownerPW = NULL; + } + if (userPassword[0]) { + userPW = new GString(userPassword); + } else { + userPW = NULL; + } + doc = new PDFDoc(fileName, ownerPW, userPW); + if (userPW) { + delete userPW; + } + if (ownerPW) { + delete ownerPW; + } + if (!doc->isOk()) { + exitCode = 1; + goto err1; + } + + // get page range + if (firstPage < 1) + firstPage = 1; + if (lastPage < 1 || lastPage > doc->getNumPages()) + lastPage = doc->getNumPages(); + + + // write PNG files + if (mono) { + paperColor[0] = 0xff; + splashOut = new SplashOutputDev(splashModeMono1, 1, gFalse, paperColor); + } else if (gray) { + paperColor[0] = 0xff; + splashOut = new SplashOutputDev(splashModeMono8, 1, gFalse, paperColor); + } else { + paperColor[0] = paperColor[1] = paperColor[2] = 0xff; + splashOut = new SplashOutputDev(splashModeRGB8, 1, gFalse, paperColor); + } + splashOut->startDoc(doc->getXRef()); + for (pg = firstPage; pg <= lastPage; ++pg) { + doc->displayPage(splashOut, pg, resolution, resolution, 0, + gFalse, gTrue, gFalse); + if (mono) { + if (!strcmp(pngRoot, "-")) { + f = stdout; + } else { + pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg); + if (!(f = fopen(pngFile->getCString(), "wb"))) { + exit(2); + } + delete pngFile; + } + setupPNG(&png, &pngInfo, f, + 1, PNG_COLOR_TYPE_GRAY, splashOut->getBitmap()); + writePNGData(png, splashOut->getBitmap()); + finishPNG(&png, &pngInfo); + fclose(f); + } else if (gray) { + if (!strcmp(pngRoot, "-")) { + f = stdout; + } else { + pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg); + if (!(f = fopen(pngFile->getCString(), "wb"))) { + exit(2); + } + delete pngFile; + } + setupPNG(&png, &pngInfo, f, + 8, PNG_COLOR_TYPE_GRAY, splashOut->getBitmap()); + writePNGData(png, splashOut->getBitmap()); + finishPNG(&png, &pngInfo); + fclose(f); + } else { // RGB + if (!strcmp(pngRoot, "-")) { + f = stdout; + } else { + pngFile = GString::format("{0:s}-{1:06d}.png", pngRoot, pg); + if (!(f = fopen(pngFile->getCString(), "wb"))) { + exit(2); + } + delete pngFile; + } + setupPNG(&png, &pngInfo, f, + 8, PNG_COLOR_TYPE_RGB, splashOut->getBitmap()); + writePNGData(png, splashOut->getBitmap()); + finishPNG(&png, &pngInfo); + fclose(f); + } + } + delete splashOut; + + exitCode = 0; + + // clean up + err1: + delete doc; + delete globalParams; + err0: + + // check for memory leaks + Object::memCheck(stderr); + gMemReport(stderr); + + return exitCode; +} + +static void setupPNG(png_structp *png, png_infop *pngInfo, FILE *f, + int bitDepth, int colorType, + SplashBitmap *bitmap) { + if (!(*png = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL)) || + !(*pngInfo = png_create_info_struct(*png))) { + exit(2); + } + if (setjmp(png_jmpbuf(*png))) { + exit(2); + } + png_init_io(*png, f); + png_set_IHDR(*png, *pngInfo, bitmap->getWidth(), bitmap->getHeight(), + bitDepth, colorType, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(*png, *pngInfo); +} + +static void writePNGData(png_structp png, SplashBitmap *bitmap) { + Guchar *p; + int y; + + if (setjmp(png_jmpbuf(png))) { + exit(2); + } + p = bitmap->getDataPtr(); + for (y = 0; y < bitmap->getHeight(); ++y) { + png_write_row(png, (png_bytep)p); + p += bitmap->getRowSize(); + } +} + + + +static void finishPNG(png_structp *png, png_infop *pngInfo) { + if (setjmp(png_jmpbuf(*png))) { + exit(2); + } + png_write_end(*png, *pngInfo); + png_destroy_write_struct(png, pngInfo); +} diff -uNrp xpdf-3.03/xpdf/pdftoppm.cc xpdf-3.04/xpdf/pdftoppm.cc --- xpdf-3.03/xpdf/pdftoppm.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/pdftoppm.cc 2014-05-28 20:50:50.000000000 +0200 @@ -8,6 +8,14 @@ #include #include +#ifdef _WIN32 +# include +# include +#endif +#ifdef DEBUG_FP_LINUX +# include +# include +#endif #include "parseargs.h" #include "gmem.h" #include "GString.h" @@ -24,7 +32,6 @@ static int lastPage = 0; static int resolution = 150; static GBool mono = gFalse; static GBool gray = gFalse; -static char enableT1libStr[16] = ""; static char enableFreeTypeStr[16] = ""; static char antialiasStr[16] = ""; static char vectorAntialiasStr[16] = ""; @@ -46,10 +53,6 @@ static ArgDesc argDesc[] = { "generate a monochrome PBM file"}, {"-gray", argFlag, &gray, 0, "generate a grayscale PGM file"}, -#if HAVE_T1LIB_H - {"-t1lib", argString, enableT1libStr, sizeof(enableT1libStr), - "enable t1lib font rasterizer: yes, no"}, -#endif #if HAVE_FREETYPE_FREETYPE_H | HAVE_FREETYPE_H {"-freetype", argString, enableFreeTypeStr, sizeof(enableFreeTypeStr), "enable FreeType font rasterizer: yes, no"}, @@ -91,6 +94,21 @@ int main(int argc, char *argv[]) { int exitCode; int pg; +#ifdef DEBUG_FP_LINUX + // enable exceptions on floating point div-by-zero + feenableexcept(FE_DIVBYZERO); + // force 64-bit rounding: this avoids changes in output when minor + // code changes result in spills of x87 registers; it also avoids + // differences in output with valgrind's 64-bit floating point + // emulation (yes, this is a kludge; but it's pretty much + // unavoidable given the x87 instruction set; see gcc bug 323 for + // more info) + fpu_control_t cw; + _FPU_GETCW(cw); + cw = (cw & ~_FPU_EXTENDED) | _FPU_DOUBLE; + _FPU_SETCW(cw); +#endif + exitCode = 99; // parse args @@ -112,11 +130,6 @@ int main(int argc, char *argv[]) { // read config file globalParams = new GlobalParams(cfgFileName); globalParams->setupBaseFonts(NULL); - if (enableT1libStr[0]) { - if (!globalParams->setEnableT1lib(enableT1libStr)) { - fprintf(stderr, "Bad '-t1lib' value on command line\n"); - } - } if (enableFreeTypeStr[0]) { if (!globalParams->setEnableFreeType(enableFreeTypeStr)) { fprintf(stderr, "Bad '-freetype' value on command line\n"); @@ -182,6 +195,9 @@ int main(int argc, char *argv[]) { doc->displayPage(splashOut, pg, resolution, resolution, 0, gFalse, gTrue, gFalse); if (!strcmp(ppmRoot, "-")) { +#ifdef _WIN32 + _setmode(_fileno(stdout), _O_BINARY); +#endif splashOut->getBitmap()->writePNMFile(stdout); } else { ppmFile = GString::format("{0:s}-{1:06d}.{2:s}", diff -uNrp xpdf-3.03/xpdf/pdftops.cc xpdf-3.04/xpdf/pdftops.cc --- xpdf-3.03/xpdf/pdftops.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/pdftops.cc 2014-05-28 20:50:50.000000000 +0200 @@ -11,6 +11,10 @@ #include #include #include +#ifdef DEBUG_FP_LINUX +# include +# include +#endif #include "parseargs.h" #include "GString.h" #include "gmem.h" @@ -147,6 +151,21 @@ int main(int argc, char *argv[]) { char *p; int exitCode; +#ifdef DEBUG_FP_LINUX + // enable exceptions on floating point div-by-zero + feenableexcept(FE_DIVBYZERO); + // force 64-bit rounding: this avoids changes in output when minor + // code changes result in spills of x87 registers; it also avoids + // differences in output with valgrind's 64-bit floating point + // emulation (yes, this is a kludge; but it's pretty much + // unavoidable given the x87 instruction set; see gcc bug 323 for + // more info) + fpu_control_t cw; + _FPU_GETCW(cw); + cw = (cw & ~_FPU_EXTENDED) | _FPU_DOUBLE; + _FPU_SETCW(cw); +#endif + exitCode = 99; // parse args @@ -216,6 +235,9 @@ int main(int argc, char *argv[]) { if (noCrop) { globalParams->setPSCrop(gFalse); } + if (pageCrop) { + globalParams->setPSUseCropBoxAsPage(gTrue); + } if (expand) { globalParams->setPSExpandSmaller(gTrue); } @@ -318,15 +340,18 @@ int main(int argc, char *argv[]) { firstPage, lastPage, mode); if (psOut->isOk()) { doc->displayPages(psOut, firstPage, lastPage, 72, 72, - 0, !pageCrop, globalParams->getPSCrop(), gTrue); + 0, !globalParams->getPSUseCropBoxAsPage(), + globalParams->getPSCrop(), gTrue); } else { delete psOut; exitCode = 2; goto err2; } - delete psOut; - exitCode = 0; + if (!psOut->checkIO()) { + exitCode = 2; + } + delete psOut; // clean up err2: diff -uNrp xpdf-3.03/xpdf/pdftotext.cc xpdf-3.04/xpdf/pdftotext.cc --- xpdf-3.03/xpdf/pdftotext.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/pdftotext.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // pdftotext.cc // -// Copyright 1997-2003 Glyph & Cog, LLC +// Copyright 1997-2013 Glyph & Cog, LLC // //======================================================================== @@ -11,6 +11,10 @@ #include #include #include +#ifdef DEBUG_FP_LINUX +# include +# include +#endif #include "parseargs.h" #include "GString.h" #include "gmem.h" @@ -26,21 +30,19 @@ #include "TextOutputDev.h" #include "CharTypes.h" #include "UnicodeMap.h" +#include "TextString.h" #include "Error.h" #include "config.h" -static void printInfoString(FILE *f, Dict *infoDict, const char *key, - const char *text1, const char *text2, - UnicodeMap *uMap); -static void printInfoDate(FILE *f, Dict *infoDict, const char *key, - const char *fmt); - static int firstPage = 1; static int lastPage = 0; static GBool physLayout = gFalse; -static double fixedPitch = 0; +static GBool tableLayout = gFalse; +static GBool linePrinter = gFalse; static GBool rawOrder = gFalse; -static GBool htmlMeta = gFalse; +static double fixedPitch = 0; +static double fixedLineSpacing = 0; +static GBool clipText = gFalse; static char textEncName[128] = ""; static char textEOL[16] = ""; static GBool noPageBreaks = gFalse; @@ -58,12 +60,18 @@ static ArgDesc argDesc[] = { "last page to convert"}, {"-layout", argFlag, &physLayout, 0, "maintain original physical layout"}, - {"-fixed", argFP, &fixedPitch, 0, - "assume fixed-pitch (or tabular) text"}, + {"-table", argFlag, &tableLayout, 0, + "similar to -layout, but optimized for tables"}, + {"-lineprinter", argFlag, &linePrinter, 0, + "use strict fixed-pitch/height layout"}, {"-raw", argFlag, &rawOrder, 0, "keep strings in content stream order"}, - {"-htmlmeta", argFlag, &htmlMeta, 0, - "generate a simple HTML file, including the meta information"}, + {"-fixed", argFP, &fixedPitch, 0, + "assume fixed-pitch (or tabular) text"}, + {"-linespacing", argFP, &fixedLineSpacing, 0, + "fixed line spacing for LinePrinter mode"}, + {"-clip", argFlag, &clipText, 0, + "separate clipped text"}, {"-enc", argString, textEncName, sizeof(textEncName), "output text encoding name"}, {"-eol", argString, textEOL, sizeof(textEOL), @@ -96,14 +104,29 @@ int main(int argc, char *argv[]) { GString *fileName; GString *textFileName; GString *ownerPW, *userPW; + TextOutputControl textOutControl; TextOutputDev *textOut; - FILE *f; UnicodeMap *uMap; Object info; GBool ok; char *p; int exitCode; +#ifdef DEBUG_FP_LINUX + // enable exceptions on floating point div-by-zero + feenableexcept(FE_DIVBYZERO); + // force 64-bit rounding: this avoids changes in output when minor + // code changes result in spills of x87 registers; it also avoids + // differences in output with valgrind's 64-bit floating point + // emulation (yes, this is a kludge; but it's pretty much + // unavoidable given the x87 instruction set; see gcc bug 323 for + // more info) + fpu_control_t cw; + _FPU_GETCW(cw); + cw = (cw & ~_FPU_EXTENDED) | _FPU_DOUBLE; + _FPU_SETCW(cw); +#endif + exitCode = 99; // parse args @@ -117,9 +140,6 @@ int main(int argc, char *argv[]) { goto err0; } fileName = new GString(argv[1]); - if (fixedPitch) { - physLayout = gTrue; - } // read config file globalParams = new GlobalParams(cfgFileName); @@ -187,7 +207,7 @@ int main(int argc, char *argv[]) { } else { textFileName = fileName->copy(); } - textFileName->append(htmlMeta ? ".html" : ".txt"); + textFileName->append(".txt"); } // get page range @@ -198,50 +218,25 @@ int main(int argc, char *argv[]) { lastPage = doc->getNumPages(); } - // write HTML header - if (htmlMeta) { - if (!textFileName->cmp("-")) { - f = stdout; - } else { - if (!(f = fopen(textFileName->getCString(), "wb"))) { - error(errIO, -1, "Couldn't open text file '{0:t}'", textFileName); - exitCode = 2; - goto err3; - } - } - fputs("\n", f); - fputs("\n", f); - doc->getDocInfo(&info); - if (info.isDict()) { - printInfoString(f, info.getDict(), "Title", "", "\n", - uMap); - printInfoString(f, info.getDict(), "Subject", - "\n", uMap); - printInfoString(f, info.getDict(), "Keywords", - "\n", uMap); - printInfoString(f, info.getDict(), "Author", - "\n", uMap); - printInfoString(f, info.getDict(), "Creator", - "\n", uMap); - printInfoString(f, info.getDict(), "Producer", - "\n", uMap); - printInfoDate(f, info.getDict(), "CreationDate", - "\n"); - printInfoDate(f, info.getDict(), "LastModifiedDate", - "\n"); - } - info.free(); - fputs("\n", f); - fputs("\n", f); - fputs("
\n", f);
-    if (f != stdout) {
-      fclose(f);
-    }
-  }
-
   // write text file
-  textOut = new TextOutputDev(textFileName->getCString(),
-			      physLayout, fixedPitch, rawOrder, htmlMeta);
+  if (tableLayout) {
+    textOutControl.mode = textOutTableLayout;
+    textOutControl.fixedPitch = fixedPitch;
+  } else if (physLayout) {
+    textOutControl.mode = textOutPhysLayout;
+    textOutControl.fixedPitch = fixedPitch;
+  } else if (linePrinter) {
+    textOutControl.mode = textOutLinePrinter;
+    textOutControl.fixedPitch = fixedPitch;
+    textOutControl.fixedLineSpacing = fixedLineSpacing;
+  } else if (rawOrder) {
+    textOutControl.mode = textOutRawOrder;
+  } else {
+    textOutControl.mode = textOutReadingOrder;
+  }
+  textOutControl.clipText = clipText;
+  textOut = new TextOutputDev(textFileName->getCString(), &textOutControl,
+			      gFalse);
   if (textOut->isOk()) {
     doc->displayPages(textOut, firstPage, lastPage, 72, 72, 0,
 		      gFalse, gTrue, gFalse);
@@ -252,25 +247,6 @@ int main(int argc, char *argv[]) {
   }
   delete textOut;
 
-  // write end of HTML file
-  if (htmlMeta) {
-    if (!textFileName->cmp("-")) {
-      f = stdout;
-    } else {
-      if (!(f = fopen(textFileName->getCString(), "ab"))) {
-	error(errIO, -1, "Couldn't open text file '{0:t}'", textFileName);
-	exitCode = 2;
-	goto err3;
-      }
-    }
-    fputs("
\n", f); - fputs("\n", f); - fputs("\n", f); - if (f != stdout) { - fclose(f); - } - } - exitCode = 0; // clean up @@ -289,56 +265,3 @@ int main(int argc, char *argv[]) { return exitCode; } - -static void printInfoString(FILE *f, Dict *infoDict, const char *key, - const char *text1, const char *text2, - UnicodeMap *uMap) { - Object obj; - GString *s1; - GBool isUnicode; - Unicode u; - char buf[8]; - int i, n; - - if (infoDict->lookup(key, &obj)->isString()) { - fputs(text1, f); - s1 = obj.getString(); - if ((s1->getChar(0) & 0xff) == 0xfe && - (s1->getChar(1) & 0xff) == 0xff) { - isUnicode = gTrue; - i = 2; - } else { - isUnicode = gFalse; - i = 0; - } - while (i < obj.getString()->getLength()) { - if (isUnicode) { - u = ((s1->getChar(i) & 0xff) << 8) | - (s1->getChar(i+1) & 0xff); - i += 2; - } else { - u = s1->getChar(i) & 0xff; - ++i; - } - n = uMap->mapUnicode(u, buf, sizeof(buf)); - fwrite(buf, 1, n, f); - } - fputs(text2, f); - } - obj.free(); -} - -static void printInfoDate(FILE *f, Dict *infoDict, const char *key, - const char *fmt) { - Object obj; - char *s; - - if (infoDict->lookup(key, &obj)->isString()) { - s = obj.getString()->getCString(); - if (s[0] == 'D' && s[1] == ':') { - s += 2; - } - fprintf(f, fmt, s); - } - obj.free(); -} diff -uNrp xpdf-3.03/xpdf/PreScanOutputDev.cc xpdf-3.04/xpdf/PreScanOutputDev.cc --- xpdf-3.03/xpdf/PreScanOutputDev.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/PreScanOutputDev.cc 2014-05-28 20:50:50.000000000 +0200 @@ -61,13 +61,13 @@ void PreScanOutputDev::eoFill(GfxState * } void PreScanOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, - Object *str, + Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, double xStep, double yStep) { if (paintType == 1) { - gfx->drawForm(str, resDict, mat, bbox); + gfx->drawForm(strRef, resDict, mat, bbox); } else { check(state->getFillColorSpace(), state->getFillColor(), state->getFillOpacity(), state->getBlendMode()); @@ -174,9 +174,7 @@ void PreScanOutputDev::endType3Char(GfxS void PreScanOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { - int i, j; - + GBool inlineImg, GBool interpolate) { check(state->getFillColorSpace(), state->getFillColor(), state->getFillOpacity(), state->getBlendMode()); if (state->getFillColorSpace()->getMode() == csPattern) { @@ -186,9 +184,7 @@ void PreScanOutputDev::drawImageMask(Gfx if (inlineImg) { str->reset(); - j = height * ((width + 7) / 8); - for (i = 0; i < j; ++i) - str->getChar(); + str->discardChars(height * ((width + 7) / 8)); str->close(); } } @@ -196,9 +192,9 @@ void PreScanOutputDev::drawImageMask(Gfx void PreScanOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg) { + int *maskColors, GBool inlineImg, + GBool interpolate) { GfxColorSpace *colorSpace; - int i, j; colorSpace = colorMap->getColorSpace(); if (colorSpace->getMode() == csIndexed) { @@ -221,10 +217,8 @@ void PreScanOutputDev::drawImage(GfxStat if (inlineImg) { str->reset(); - j = height * ((width * colorMap->getNumPixelComps() * - colorMap->getBits() + 7) / 8); - for (i = 0; i < j; ++i) - str->getChar(); + str->discardChars(height * ((width * colorMap->getNumPixelComps() * + colorMap->getBits() + 7) / 8)); str->close(); } } @@ -235,7 +229,7 @@ void PreScanOutputDev::drawMaskedImage(G GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert) { + GBool maskInvert, GBool interpolate) { GfxColorSpace *colorSpace; colorSpace = colorMap->getColorSpace(); @@ -264,7 +258,8 @@ void PreScanOutputDev::drawSoftMaskedIma GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap) { + GfxImageColorMap *maskColorMap, + GBool interpolate) { GfxColorSpace *colorSpace; colorSpace = colorMap->getColorSpace(); diff -uNrp xpdf-3.03/xpdf/PreScanOutputDev.h xpdf-3.04/xpdf/PreScanOutputDev.h --- xpdf-3.03/xpdf/PreScanOutputDev.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/PreScanOutputDev.h 2014-05-28 20:50:50.000000000 +0200 @@ -67,7 +67,7 @@ public: virtual void stroke(GfxState *state); virtual void fill(GfxState *state); virtual void eoFill(GfxState *state); - virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, + virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -92,21 +92,22 @@ public: //----- image drawing virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg); + int *maskColors, GBool inlineImg, GBool interpolate); virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert); + GBool maskInvert, GBool interpolate); virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap); + GfxImageColorMap *maskColorMap, + GBool interpolate); //----- transparency groups and soft masks virtual void beginTransparencyGroup(GfxState *state, double *bbox, diff -uNrp xpdf-3.03/xpdf/PSOutputDev.cc xpdf-3.04/xpdf/PSOutputDev.cc --- xpdf-3.03/xpdf/PSOutputDev.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/PSOutputDev.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // PSOutputDev.cc // -// Copyright 1996-2003 Glyph & Cog, LLC +// Copyright 1996-2013 Glyph & Cog, LLC // //======================================================================== @@ -39,6 +39,8 @@ #include "XRef.h" #include "PreScanOutputDev.h" #include "CharCodeToUnicode.h" +#include "Form.h" +#include "TextString.h" #if HAVE_SPLASH # include "Splash.h" # include "SplashBitmap.h" @@ -57,11 +59,6 @@ #endif //------------------------------------------------------------------------ - -// Max size of a slice when rasterizing pages, in pixels. -#define rasterizationSliceSize 20000000 - -//------------------------------------------------------------------------ // PostScript prolog and setup //------------------------------------------------------------------------ @@ -85,24 +82,29 @@ static const char *prolog[] = { " } for", "~123sn", "/pdfSetup {", + " /pdfDuplex exch def", " /setpagedevice where {", " pop 2 dict begin", " /Policies 1 dict dup begin /PageSize 6 def end def", - " { /Duplex true def } if", + " pdfDuplex { /Duplex true def } if", " currentdict end setpagedevice", - " } {", - " pop", - " } ifelse", + " } if", + " /pdfPageW 0 def", + " /pdfPageH 0 def", "} def", "/pdfSetupPaper {", - " 2 array astore", - " /setpagedevice where {", - " pop 2 dict begin", - " /PageSize exch def", - " /ImagingBBox null def", - " currentdict end setpagedevice", + " 2 copy pdfPageH ne exch pdfPageW ne or {", + " /pdfPageH exch def", + " /pdfPageW exch def", + " /setpagedevice where {", + " pop 3 dict begin", + " /PageSize [pdfPageW pdfPageH] def", + " pdfDuplex { /Duplex true def } if", + " /ImagingBBox null def", + " currentdict end setpagedevice", + " } if", " } {", - " pop", + " pop pop", " } ifelse", "} def", "~1sn", @@ -571,17 +573,14 @@ static const char *prolog[] = { "} def", "~23sn", "/pdfImM { fCol imagemask skipEOD } def", - "/pr { 2 index 2 index 3 2 roll putinterval 4 add } def", - "/pdfImClip {", - " gsave", - " 0 2 4 index length 1 sub {", - " dup 4 index exch 2 copy", - " get 5 index div put", - " 1 add 3 index exch 2 copy", - " get 3 index div put", - " } for", - " pop pop rectclip", + "/pr {", + " 4 2 roll exch 5 index div exch 4 index div moveto", + " exch 3 index div dup 0 rlineto", + " exch 2 index div 0 exch rlineto", + " neg 0 rlineto", + " closepath", "} def", + "/pdfImClip { gsave clip } def", "/pdfImClipEnd { grestore } def", "~23sn", "% shading operators", @@ -736,6 +735,19 @@ static const char *prolog[] = { NULL }; +static const char *minLineWidthProlog[] = { + "/pdfDist { dup dtransform dup mul exch dup mul add 0.5 mul sqrt } def", + "/pdfIDist { dup idtransform dup mul exch dup mul add 0.5 mul sqrt } def", + "/pdfMinLineDist pdfMinLineWidth pdfDist def", + "/setlinewidth {", + " dup pdfDist pdfMinLineDist lt {", + " pop pdfMinLineDist pdfIDist", + " } if", + " setlinewidth", + "} bind def", + NULL +}; + static const char *cmapProlog[] = { "/CIDInit /ProcSet findresource begin", "10 dict begin", @@ -809,25 +821,68 @@ static PSSubstFont psBase14SubstFonts[14 {"ZapfDingbats", 0} }; -// Mapping from Type 1/1C font file to PS font name. -struct PST1FontName { - Ref fontFileID; - GString *psName; // PostScript font name used for this - // embedded font file -}; +class PSFontInfo { +public: + + PSFontInfo(Ref fontIDA) + { fontID = fontIDA; ff = NULL; } -// Info for 8-bit fonts -struct PSFont8Info { Ref fontID; - int *codeToGID; // code-to-GID mapping for TrueType fonts + PSFontFileInfo *ff; // pointer to font file info; NULL indicates + // font mapping failed }; -// Encoding info for substitute 16-bit font -struct PSFont16Enc { - Ref fontID; - GString *enc; // NULL means font wasn't correctly substituted +enum PSFontFileLocation { + psFontFileResident, + psFontFileEmbedded, + psFontFileExternal }; +class PSFontFileInfo { +public: + + PSFontFileInfo(GString *psNameA, GfxFontType typeA, + PSFontFileLocation locA); + ~PSFontFileInfo(); + + GString *psName; // name under which font is defined + GfxFontType type; // font type + PSFontFileLocation loc; // font location + Ref embFontID; // object ID for the embedded font file + // (for all embedded fonts) + GString *extFileName; // external font file path + // (for all external fonts) + GString *encoding; // encoding name (for resident CID fonts) + int *codeToGID; // mapping from code/CID to GID + // (for TrueType, OpenType-TrueType, and + // CID OpenType-CFF fonts) + int codeToGIDLen; // length of codeToGID array +}; + +PSFontFileInfo::PSFontFileInfo(GString *psNameA, GfxFontType typeA, + PSFontFileLocation locA) { + psName = psNameA; + type = typeA; + loc = locA; + embFontID.num = embFontID.gen = -1; + extFileName = NULL; + encoding = NULL; + codeToGID = NULL; +} + +PSFontFileInfo::~PSFontFileInfo() { + delete psName; + if (extFileName) { + delete extFileName; + } + if (encoding) { + delete encoding; + } + if (codeToGID) { + gfree(codeToGID); + } +} + //------------------------------------------------------------------------ // process colors //------------------------------------------------------------------------ @@ -998,11 +1053,8 @@ PSOutputDev::PSOutputDev(char *fileName, customCodeCbk = customCodeCbkA; customCodeCbkData = customCodeCbkDataA; - fontIDs = NULL; - fontNames = new GHash(gTrue); - t1FontNames = NULL; - font8Info = NULL; - font16Enc = NULL; + fontInfo = new GList(); + fontFileInfo = new GHash(); imgIDs = NULL; formIDs = NULL; xobjStack = NULL; @@ -1060,11 +1112,8 @@ PSOutputDev::PSOutputDev(PSOutputFunc ou customCodeCbk = customCodeCbkA; customCodeCbkData = customCodeCbkDataA; - fontIDs = NULL; - fontNames = new GHash(gTrue); - t1FontNames = NULL; - font8Info = NULL; - font16Enc = NULL; + fontInfo = new GList(); + fontFileInfo = new GHash(); imgIDs = NULL; formIDs = NULL; xobjStack = NULL; @@ -1088,6 +1137,7 @@ void PSOutputDev::init(PSOutputFunc outp Page *page; PDFRectangle *box; PSOutPaperSize *size; + PSFontFileInfo *ff; GList *names; int pg, w, h, i; @@ -1118,8 +1168,13 @@ void PSOutputDev::init(PSOutputFunc outp pg <= lastPage && pg <= catalog->getNumPages(); ++pg) { page = catalog->getPage(pg); - w = (int)ceil(page->getMediaWidth()); - h = (int)ceil(page->getMediaHeight()); + if (globalParams->getPSUseCropBoxAsPage()) { + w = (int)ceil(page->getCropWidth()); + h = (int)ceil(page->getCropHeight()); + } else { + w = (int)ceil(page->getMediaWidth()); + h = (int)ceil(page->getMediaHeight()); + } for (i = 0; i < paperSizes->getLength(); ++i) { size = (PSOutPaperSize *)paperSizes->get(i); if (size->w == w && size->h == h) { @@ -1160,25 +1215,21 @@ void PSOutputDev::init(PSOutputFunc outp clipLLX0 = clipLLY0 = 0; clipURX0 = clipURY0 = -1; - // initialize fontIDs and fontNames lists - fontIDSize = 64; - fontIDLen = 0; - fontIDs = (Ref *)gmallocn(fontIDSize, sizeof(Ref)); + // initialize font lists, etc. for (i = 0; i < 14; ++i) { - fontNames->add(new GString(psBase14SubstFonts[i].psName), 1); + ff = new PSFontFileInfo(new GString(psBase14SubstFonts[i].psName), + fontType1, psFontFileResident); + fontFileInfo->add(ff->psName, ff); } names = globalParams->getPSResidentFonts(); for (i = 0; i < names->getLength(); ++i) { - fontNames->add((GString *)names->get(i), 1); + if (!fontFileInfo->lookup((GString *)names->get(i))) { + ff = new PSFontFileInfo((GString *)names->get(i), fontType1, + psFontFileResident); + fontFileInfo->add(ff->psName, ff); + } } delete names; - t1FontNameSize = 64; - t1FontNameLen = 0; - t1FontNames = (PST1FontName *)gmallocn(t1FontNameSize, sizeof(PST1FontName)); - font8InfoLen = 0; - font8InfoSize = 0; - font16EncLen = 0; - font16EncSize = 0; imgIDLen = 0; imgIDSize = 0; formIDLen = 0; @@ -1224,7 +1275,6 @@ void PSOutputDev::init(PSOutputFunc outp PSOutputDev::~PSOutputDev() { PSOutCustomColor *cc; - int i; if (ok) { if (!manualCtrl) { @@ -1255,30 +1305,8 @@ PSOutputDev::~PSOutputDev() { if (embFontList) { delete embFontList; } - if (fontIDs) { - gfree(fontIDs); - } - delete fontNames; - if (t1FontNames) { - for (i = 0; i < t1FontNameLen; ++i) { - delete t1FontNames[i].psName; - } - gfree(t1FontNames); - } - if (font8Info) { - for (i = 0; i < font8InfoLen; ++i) { - gfree(font8Info[i].codeToGID); - } - gfree(font8Info); - } - if (font16Enc) { - for (i = 0; i < font16EncLen; ++i) { - if (font16Enc[i].enc) { - delete font16Enc[i].enc; - } - } - gfree(font16Enc); - } + deleteGList(fontInfo, PSFontInfo); + deleteGHash(fontFileInfo, PSFontFileInfo); gfree(imgIDs); gfree(formIDs); if (xobjStack) { @@ -1291,6 +1319,16 @@ PSOutputDev::~PSOutputDev() { } } +GBool PSOutputDev::checkIO() { + if (fileType == psFile || fileType == psPipe || fileType == psStdout) { + if (ferror((FILE *)outputStream)) { + error(errIO, -1, "Error writing to PostScript file"); + return gFalse; + } + } + return gTrue; +} + void PSOutputDev::writeHeader(int firstPage, int lastPage, PDFRectangle *mediaBox, PDFRectangle *cropBox, int pageRotate) { @@ -1395,6 +1433,7 @@ void PSOutputDev::writeXpdfProcset() { GBool lev1, lev2, lev3, sep, nonSep; const char **p; const char *q; + double w; writePSFmt("%%BeginResource: procset xpdf {0:s} 0\n", xpdfVersion); writePSFmt("%%Copyright: {0:s}\n", xpdfCopyright); @@ -1420,6 +1459,12 @@ void PSOutputDev::writeXpdfProcset() { writePSFmt("{0:s}\n", *p); } } + if ((w = globalParams->getPSMinLineWidth()) > 0) { + writePSFmt("/pdfMinLineWidth {0:.4g} def\n", w); + for (p = minLineWidthProlog; *p; ++p) { + writePSFmt("{0:s}\n", *p); + } + } writePS("%%EndResource\n"); if (level >= psLevel3) { @@ -1434,10 +1479,10 @@ void PSOutputDev::writeDocSetup(Catalog Page *page; Dict *resDict; Annots *annots; - Object *acroForm; + Form *form; Object obj1, obj2, obj3; GString *s; - int pg, i; + int pg, i, j; if (mode == psModeForm) { // swap the form and xpdf dicts @@ -1464,23 +1509,22 @@ void PSOutputDev::writeDocSetup(Catalog } delete annots; } - if ((acroForm = catalog->getAcroForm()) && acroForm->isDict()) { - if (acroForm->dictLookup("DR", &obj1)->isDict()) { - setupResources(obj1.getDict()); - } - obj1.free(); - if (acroForm->dictLookup("Fields", &obj1)->isArray()) { - for (i = 0; i < obj1.arrayGetLength(); ++i) { - if (obj1.arrayGet(i, &obj2)->isDict()) { - if (obj2.dictLookup("DR", &obj3)->isDict()) { - setupResources(obj3.getDict()); + if ((form = catalog->getForm())) { + for (i = 0; i < form->getNumFields(); ++i) { + form->getField(i)->getResources(&obj1); + if (obj1.isArray()) { + for (j = 0; j < obj1.arrayGetLength(); ++j) { + obj1.arrayGet(j, &obj2); + if (obj2.isDict()) { + setupResources(obj2.getDict()); } - obj3.free(); + obj2.free(); } - obj2.free(); + } else if (obj1.isDict()) { + setupResources(obj1.getDict()); } + obj1.free(); } - obj1.free(); } if (mode != psModeForm) { if (mode != psModeEPS && !manualCtrl) { @@ -1503,6 +1547,9 @@ void PSOutputDev::writeDocSetup(Catalog delete s; } } + if (mode != psModeForm) { + writePS("end\n"); + } } void PSOutputDev::writePageTrailer() { @@ -1517,7 +1564,6 @@ void PSOutputDev::writeTrailer() { if (mode == psModeForm) { writePS("/Foo exch /Form defineresource pop\n"); } else { - writePS("end\n"); writePS("%%DocumentSuppliedResources:\n"); writePS(embFontList->getCString()); if (level == psLevel1Sep || level == psLevel2Sep || @@ -1554,14 +1600,14 @@ void PSOutputDev::writeTrailer() { } void PSOutputDev::setupResources(Dict *resDict) { - Object xObjDict, xObjRef, xObj, patDict, patRef, pat, resObj; + Object xObjDict, xObjRef, xObj, patDict, patRef, pat; + Object gsDict, gsRef, gs, smask, smaskGroup, resObj; Ref ref0, ref1; GBool skip; int i, j; setupFonts(resDict); setupImages(resDict); - setupForms(resDict); //----- recursively scan XObjects resDict->lookup("XObject", &xObjDict); @@ -1648,6 +1694,55 @@ void PSOutputDev::setupResources(Dict *r inType3Char = gFalse; } patDict.free(); + + //----- recursively scan SMask transparency groups in ExtGState dicts + resDict->lookup("ExtGState", &gsDict); + if (gsDict.isDict()) { + for (i = 0; i < gsDict.dictGetLength(); ++i) { + + // avoid infinite recursion on ExtGStates + skip = gFalse; + if ((gsDict.dictGetValNF(i, &gsRef)->isRef())) { + ref0 = gsRef.getRef(); + for (j = 0; j < xobjStack->getLength(); ++j) { + ref1 = *(Ref *)xobjStack->get(j); + if (ref1.num == ref0.num && ref1.gen == ref0.gen) { + skip = gTrue; + break; + } + } + if (!skip) { + xobjStack->append(&ref0); + } + } + if (!skip) { + + // process the ExtGState's SMask's transparency group's resource dict + if (gsDict.dictGetVal(i, &gs)->isDict()) { + if (gs.dictLookup("SMask", &smask)->isDict()) { + if (smask.dictLookup("G", &smaskGroup)->isStream()) { + smaskGroup.streamGetDict()->lookup("Resources", &resObj); + if (resObj.isDict()) { + setupResources(resObj.getDict()); + } + resObj.free(); + } + smaskGroup.free(); + } + smask.free(); + } + gs.free(); + } + + if (gsRef.isRef() && !skip) { + xobjStack->del(xobjStack->getLength() - 1); + } + gsRef.free(); + } + } + gsDict.free(); + + setupForms(resDict); } void PSOutputDev::setupFonts(Dict *resDict) { @@ -1681,8 +1776,8 @@ void PSOutputDev::setupFonts(Dict *resDi } void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) { + PSFontInfo *fi; GfxFontLoc *fontLoc; - GString *psName; GBool subst; char buf[16]; UnicodeMap *uMap; @@ -1693,123 +1788,101 @@ void PSOutputDev::setupFont(GfxFont *fon int i, j; // check if font is already set up - for (i = 0; i < fontIDLen; ++i) { - if (fontIDs[i].num == font->getID()->num && - fontIDs[i].gen == font->getID()->gen) { + for (i = 0; i < fontInfo->getLength(); ++i) { + fi = (PSFontInfo *)fontInfo->get(i); + if (fi->fontID.num == font->getID()->num && + fi->fontID.gen == font->getID()->gen) { return; } } - // add entry to fontIDs list - if (fontIDLen >= fontIDSize) { - fontIDSize += 64; - fontIDs = (Ref *)greallocn(fontIDs, fontIDSize, sizeof(Ref)); - } - fontIDs[fontIDLen++] = *font->getID(); + // add fontInfo entry + fi = new PSFontInfo(*font->getID()); + fontInfo->append(fi); - psName = NULL; xs = ys = 1; subst = gFalse; if (font->getType() == fontType3) { - psName = GString::format("T3_{0:d}_{1:d}", - font->getID()->num, font->getID()->gen); - setupType3Font(font, psName, parentResDict); - } else { - fontLoc = font->locateFont(xref, gTrue); - switch (fontLoc->locType) { - case gfxFontLocEmbedded: - switch (fontLoc->fontType) { - case fontType1: - // this assumes that the PS font name matches the PDF font name - psName = font->getEmbeddedFontName()->copy(); - setupEmbeddedType1Font(&fontLoc->embFontID, psName); - break; - case fontType1C: - psName = makePSFontName(font, &fontLoc->embFontID); - setupEmbeddedType1CFont(font, &fontLoc->embFontID, psName); - break; - case fontType1COT: - psName = makePSFontName(font, &fontLoc->embFontID); - setupEmbeddedOpenTypeT1CFont(font, &fontLoc->embFontID, psName); - break; - case fontTrueType: - case fontTrueTypeOT: - psName = makePSFontName(font, font->getID()); - setupEmbeddedTrueTypeFont(font, &fontLoc->embFontID, psName); - break; - case fontCIDType0C: - psName = makePSFontName(font, &fontLoc->embFontID); - setupEmbeddedCIDType0Font(font, &fontLoc->embFontID, psName); - break; - case fontCIDType2: - case fontCIDType2OT: - psName = makePSFontName(font, font->getID()); - //~ should check to see if font actually uses vertical mode - setupEmbeddedCIDTrueTypeFont(font, &fontLoc->embFontID, psName, gTrue); - break; - case fontCIDType0COT: - psName = makePSFontName(font, &fontLoc->embFontID); - setupEmbeddedOpenTypeCFFFont(font, &fontLoc->embFontID, psName); - break; - default: - break; - } - break; - case gfxFontLocExternal: - //~ add cases for external 16-bit fonts - switch (fontLoc->fontType) { - case fontType1: - if (font->getName()) { - // this assumes that the PS font name matches the PDF font name - psName = font->getName()->copy(); - } else { - //~ this won't work -- the PS font name won't match - psName = makePSFontName(font, font->getID()); + fi->ff = setupType3Font(font, parentResDict); + } else { + if ((fontLoc = font->locateFont(xref, gTrue))) { + switch (fontLoc->locType) { + case gfxFontLocEmbedded: + switch (fontLoc->fontType) { + case fontType1: + fi->ff = setupEmbeddedType1Font(font, &fontLoc->embFontID); + break; + case fontType1C: + fi->ff = setupEmbeddedType1CFont(font, &fontLoc->embFontID); + break; + case fontType1COT: + fi->ff = setupEmbeddedOpenTypeT1CFont(font, &fontLoc->embFontID); + break; + case fontTrueType: + case fontTrueTypeOT: + fi->ff = setupEmbeddedTrueTypeFont(font, &fontLoc->embFontID); + break; + case fontCIDType0C: + fi->ff = setupEmbeddedCIDType0Font(font, &fontLoc->embFontID); + break; + case fontCIDType2: + case fontCIDType2OT: + //~ should check to see if font actually uses vertical mode + fi->ff = setupEmbeddedCIDTrueTypeFont(font, &fontLoc->embFontID, + gTrue); + break; + case fontCIDType0COT: + fi->ff = setupEmbeddedOpenTypeCFFFont(font, &fontLoc->embFontID); + break; + default: + break; } - setupExternalType1Font(fontLoc->path, psName); break; - case fontTrueType: - case fontTrueTypeOT: - psName = makePSFontName(font, font->getID()); - setupExternalTrueTypeFont(font, fontLoc->path, psName); - break; - case fontCIDType2: - case fontCIDType2OT: - psName = makePSFontName(font, font->getID()); - //~ should check to see if font actually uses vertical mode - setupExternalCIDTrueTypeFont(font, fontLoc->path, psName, gTrue); + case gfxFontLocExternal: + //~ add cases for other external 16-bit fonts + switch (fontLoc->fontType) { + case fontType1: + fi->ff = setupExternalType1Font(font, fontLoc->path); + break; + case fontTrueType: + case fontTrueTypeOT: + fi->ff = setupExternalTrueTypeFont(font, fontLoc->path, + fontLoc->fontNum); + break; + case fontCIDType2: + case fontCIDType2OT: + //~ should check to see if font actually uses vertical mode + fi->ff = setupExternalCIDTrueTypeFont(font, fontLoc->path, + fontLoc->fontNum, gTrue); + break; + default: + break; + } break; - default: + case gfxFontLocResident: + if (!(fi->ff = (PSFontFileInfo *)fontFileInfo->lookup(fontLoc->path))) { + // handle psFontPassthrough + fi->ff = new PSFontFileInfo(fontLoc->path->copy(), fontLoc->fontType, + psFontFileResident); + fontFileInfo->add(fi->ff->psName, fi->ff); + } break; } - break; - case gfxFontLocResident: - psName = fontLoc->path->copy(); - break; } - if (!psName) { + if (!fi->ff) { if (font->isCIDFont()) { error(errSyntaxError, -1, - "Couldn't find a font to substitute for '{0:s}' ('{1:s}' character collection)", + "Couldn't find a font for '{0:s}' ('{1:s}' character collection)", font->getName() ? font->getName()->getCString() : "(unnamed)", ((GfxCIDFont *)font)->getCollection() ? ((GfxCIDFont *)font)->getCollection()->getCString() : "(unknown)"); - if (font16EncLen >= font16EncSize) { - font16EncSize += 16; - font16Enc = (PSFont16Enc *)greallocn(font16Enc, - font16EncSize, - sizeof(PSFont16Enc)); - } - font16Enc[font16EncLen].fontID = *font->getID(); - font16Enc[font16EncLen].enc = NULL; - ++font16EncLen; } else { error(errSyntaxError, -1, - "Couldn't find a font to substitute for '{0:s}'", + "Couldn't find a font for '{0:s}'", font->getName() ? font->getName()->getCString() : "(unnamed)"); } @@ -1843,23 +1916,14 @@ void PSOutputDev::setupFont(GfxFont *fon if (fontLoc->locType == gfxFontLocResident && fontLoc->fontType >= fontCIDType0) { subst = gTrue; - if (font16EncLen >= font16EncSize) { - font16EncSize += 16; - font16Enc = (PSFont16Enc *)greallocn(font16Enc, - font16EncSize, - sizeof(PSFont16Enc)); - } - font16Enc[font16EncLen].fontID = *font->getID(); if ((uMap = globalParams->getUnicodeMap(fontLoc->encoding))) { - font16Enc[font16EncLen].enc = fontLoc->encoding->copy(); + 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); - font16Enc[font16EncLen].enc = NULL; } - ++font16EncLen; } delete fontLoc; @@ -1869,16 +1933,16 @@ void PSOutputDev::setupFont(GfxFont *fon if (font->isCIDFont()) { if (level == psLevel3 || level == psLevel3Sep) { writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16L3\n", - font->getID()->num, font->getID()->gen, psName, + font->getID()->num, font->getID()->gen, fi->ff->psName, font->getWMode()); } else { writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16\n", - font->getID()->num, font->getID()->gen, psName, + font->getID()->num, font->getID()->gen, fi->ff->psName, font->getWMode()); } } else { writePSFmt("/F{0:d}_{1:d} /{2:t} {3:.6g} {4:.6g}\n", - font->getID()->num, font->getID()->gen, psName, xs, ys); + font->getID()->num, font->getID()->gen, fi->ff->psName, xs, ys); for (i = 0; i < 256; i += 8) { writePS((char *)((i == 0) ? "[ " : " ")); for (j = 0; j < 8; ++j) { @@ -1903,25 +1967,29 @@ void PSOutputDev::setupFont(GfxFont *fon } writePS("pdfMakeFont\n"); } - - delete psName; } -void PSOutputDev::setupEmbeddedType1Font(Ref *id, GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedType1Font(GfxFont *font, Ref *id) { static char hexChar[17] = "0123456789abcdef"; - Object refObj, strObj, obj1, obj2, obj3; + GString *psName; + PSFontFileInfo *ff; + Object refObj, strObj, obj1, obj2; Dict *dict; - int length1, length2, length3; + int length1, length2; int c; - int start[4]; + int start[6]; GBool binMode; - int i; + int n, i; // check if font is already embedded - if (fontNames->lookupInt(psName)) { - return; + if ((ff = (PSFontFileInfo *) + fontFileInfo->lookup(font->getEmbeddedFontName()))) { + return ff; } - fontNames->add(psName->copy(), 1); + + // generate name + // (this assumes that the PS font name matches the PDF font name) + psName = font->getEmbeddedFontName()->copy(); // get the font stream and info refObj.initRef(id->num, id->gen); @@ -1938,21 +2006,17 @@ void PSOutputDev::setupEmbeddedType1Font } dict->lookup("Length1", &obj1); dict->lookup("Length2", &obj2); - dict->lookup("Length3", &obj3); - if (!obj1.isInt() || !obj2.isInt() || !obj3.isInt()) { + if (!obj1.isInt() || !obj2.isInt()) { error(errSyntaxError, -1, "Missing length fields in embedded font stream dictionary"); obj1.free(); obj2.free(); - obj3.free(); goto err1; } length1 = obj1.getInt(); length2 = obj2.getInt(); - length3 = obj3.getInt(); obj1.free(); obj2.free(); - obj3.free(); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -1960,91 +2024,164 @@ void PSOutputDev::setupEmbeddedType1Font embFontList->append(psName->getCString()); embFontList->append("\n"); - // copy ASCII portion of font + // check for PFB format strObj.streamReset(); - for (i = 0; i < length1 && (c = strObj.streamGetChar()) != EOF; ++i) { - writePSChar(c); - } + start[0] = strObj.streamGetChar(); + start[1] = strObj.streamGetChar(); + if (start[0] == 0x80 && start[1] == 0x01) { + error(errSyntaxWarning, -1, "Embedded Type 1 font is in PFB format"); + while (1) { + for (i = 2; i < 6; ++i) { + start[i] = strObj.streamGetChar(); + } + if (start[2] == EOF || start[3] == EOF || + start[4] == EOF || start[5] == EOF) { + break; + } + n = start[2] + (start[3] << 8) + (start[4] << 16) + (start[5] << 24); + if (start[1] == 0x01) { + for (i = 0; i < n; ++i) { + if ((c = strObj.streamGetChar()) == EOF) { + break; + } + writePSChar(c); + } + } else { + for (i = 0; i < n; ++i) { + if ((c = strObj.streamGetChar()) == EOF) { + break; + } + writePSChar(hexChar[(c >> 4) & 0x0f]); + writePSChar(hexChar[c & 0x0f]); + if (i % 32 == 31) { + writePSChar('\n'); + } + } + } + start[0] = strObj.streamGetChar(); + start[1] = strObj.streamGetChar(); + if (start[0] == EOF || start[1] == EOF || + (start[0] == 0x80 && start[1] == 0x03)) { + break; + } else if (!(start[0] == 0x80 && + (start[1] == 0x01 || start[1] == 0x02))) { + error(errSyntaxError, -1, + "Invalid PFB header in embedded font stream"); + break; + } + } + writePSChar('\n'); - // figure out if encrypted portion is binary or ASCII - binMode = gFalse; - for (i = 0; i < 4; ++i) { - start[i] = strObj.streamGetChar(); - if (start[i] == EOF) { - error(errSyntaxError, -1, - "Unexpected end of file in embedded font stream"); - goto err1; - } - if (!((start[i] >= '0' && start[i] <= '9') || - (start[i] >= 'A' && start[i] <= 'F') || - (start[i] >= 'a' && start[i] <= 'f'))) - binMode = gTrue; - } + // plain text (PFA) format + } else { - // convert binary data to ASCII - if (binMode) { + // copy ASCII portion of font + writePSChar(start[0]); + writePSChar(start[1]); + for (i = 2; i < length1 && (c = strObj.streamGetChar()) != EOF; ++i) { + writePSChar(c); + } + + // figure out if encrypted portion is binary or ASCII + binMode = gFalse; for (i = 0; i < 4; ++i) { - writePSChar(hexChar[(start[i] >> 4) & 0x0f]); - writePSChar(hexChar[start[i] & 0x0f]); + start[i] = strObj.streamGetChar(); + if (start[i] == EOF) { + error(errSyntaxError, -1, + "Unexpected end of file in embedded font stream"); + goto err1; + } + if (!((start[i] >= '0' && start[i] <= '9') || + (start[i] >= 'A' && start[i] <= 'F') || + (start[i] >= 'a' && start[i] <= 'f'))) + binMode = gTrue; } + + // convert binary data to ASCII + if (binMode) { + for (i = 0; i < 4; ++i) { + writePSChar(hexChar[(start[i] >> 4) & 0x0f]); + writePSChar(hexChar[start[i] & 0x0f]); + } #if 0 // this causes trouble for various PostScript printers - // if Length2 is incorrect (too small), font data gets chopped, so - // we take a few extra characters from the trailer just in case - length2 += length3 >= 8 ? 8 : length3; + // if Length2 is incorrect (too small), font data gets chopped, so + // we take a few extra characters from the trailer just in case + length2 += length3 >= 8 ? 8 : length3; #endif - while (i < length2) { - if ((c = strObj.streamGetChar()) == EOF) { - break; + while (i < length2) { + if ((c = strObj.streamGetChar()) == EOF) { + break; + } + writePSChar(hexChar[(c >> 4) & 0x0f]); + writePSChar(hexChar[c & 0x0f]); + if (++i % 32 == 0) { + writePSChar('\n'); + } } - writePSChar(hexChar[(c >> 4) & 0x0f]); - writePSChar(hexChar[c & 0x0f]); - if (++i % 32 == 0) { + if (i % 32 > 0) { writePSChar('\n'); } - } - if (i % 32 > 0) { - writePSChar('\n'); - } - // already in ASCII format -- just copy it - } else { - for (i = 0; i < 4; ++i) { - writePSChar(start[i]); - } - for (i = 4; i < length2; ++i) { - if ((c = strObj.streamGetChar()) == EOF) { - break; + // already in ASCII format -- just copy it + } else { + for (i = 0; i < 4; ++i) { + writePSChar(start[i]); + } + for (i = 4; i < length2; ++i) { + if ((c = strObj.streamGetChar()) == EOF) { + break; + } + writePSChar(c); } - writePSChar(c); } - } - // write padding and "cleartomark" - for (i = 0; i < 8; ++i) { - writePS("00000000000000000000000000000000" - "00000000000000000000000000000000\n"); + // write padding and "cleartomark" + for (i = 0; i < 8; ++i) { + writePS("00000000000000000000000000000000" + "00000000000000000000000000000000\n"); + } + writePS("cleartomark\n"); } - writePS("cleartomark\n"); // ending comment writePS("%%EndResource\n"); + strObj.streamClose(); + strObj.free(); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; + err1: strObj.streamClose(); strObj.free(); + delete psName; + return NULL; } -//~ This doesn't handle .pfb files or binary eexec data (which only -//~ happens in pfb files?). -void PSOutputDev::setupExternalType1Font(GString *fileName, GString *psName) { +PSFontFileInfo *PSOutputDev::setupExternalType1Font(GfxFont *font, + GString *fileName) { + static char hexChar[17] = "0123456789abcdef"; + GString *psName; + PSFontFileInfo *ff; FILE *fontFile; - int c; + int buf[6]; + int c, n, i; - // check if font is already embedded - if (fontNames->lookupInt(psName)) { - return; + if (font->getName()) { + // check if font is already embedded + if ((ff = (PSFontFileInfo *)fontFileInfo->lookup(font->getName()))) { + return ff; + } + // this assumes that the PS font name matches the PDF font name + psName = font->getName()->copy(); + } else { + // generate name + //~ this won't work -- the PS font name won't match + psName = makePSFontName(font, font->getID()); } - fontNames->add(psName->copy(), 1); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2052,44 +2189,98 @@ void PSOutputDev::setupExternalType1Font embFontList->append(psName->getCString()); embFontList->append("\n"); - // copy the font file + // open the font file if (!(fontFile = fopen(fileName->getCString(), "rb"))) { error(errIO, -1, "Couldn't open external font file"); - return; + return NULL; } - while ((c = fgetc(fontFile)) != EOF) { - writePSChar(c); + + // check for PFB format + buf[0] = fgetc(fontFile); + buf[1] = fgetc(fontFile); + if (buf[0] == 0x80 && buf[1] == 0x01) { + while (1) { + for (i = 2; i < 6; ++i) { + buf[i] = fgetc(fontFile); + } + if (buf[2] == EOF || buf[3] == EOF || buf[4] == EOF || buf[5] == EOF) { + break; + } + n = buf[2] + (buf[3] << 8) + (buf[4] << 16) + (buf[5] << 24); + if (buf[1] == 0x01) { + for (i = 0; i < n; ++i) { + if ((c = fgetc(fontFile)) == EOF) { + break; + } + writePSChar(c); + } + } else { + for (i = 0; i < n; ++i) { + if ((c = fgetc(fontFile)) == EOF) { + break; + } + writePSChar(hexChar[(c >> 4) & 0x0f]); + writePSChar(hexChar[c & 0x0f]); + if (i % 32 == 31) { + writePSChar('\n'); + } + } + } + buf[0] = fgetc(fontFile); + buf[1] = fgetc(fontFile); + if (buf[0] == EOF || buf[1] == EOF || + (buf[0] == 0x80 && buf[1] == 0x03)) { + break; + } else if (!(buf[0] == 0x80 && + (buf[1] == 0x01 || buf[1] == 0x02))) { + error(errSyntaxError, -1, + "Invalid PFB header in external font file"); + break; + } + } + writePSChar('\n'); + + // plain text (PFA) format + } else { + writePSChar(buf[0]); + writePSChar(buf[1]); + while ((c = fgetc(fontFile)) != EOF) { + writePSChar(c); + } } + fclose(fontFile); // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileExternal); + ff->extFileName = fileName->copy(); + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiType1C *ffT1C; - int i; + GHashIter *iter; // check if font is already embedded - for (i = 0; i < t1FontNameLen; ++i) { - if (t1FontNames[i].fontFileID.num == id->num && - t1FontNames[i].fontFileID.gen == id->gen) { - psName->clear(); - psName->insert(0, t1FontNames[i].psName); - return; + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen) { + fontFileInfo->killIter(&iter); + return ff; } } - if (t1FontNameLen == t1FontNameSize) { - t1FontNameSize *= 2; - t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, - sizeof(PST1FontName)); - } - t1FontNames[t1FontNameLen].fontFileID = *id; - t1FontNames[t1FontNameLen].psName = psName->copy(); - ++t1FontNameLen; + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2109,32 +2300,35 @@ void PSOutputDev::setupEmbeddedType1CFon // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, + Ref *id) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiTrueType *ffTT; - int i; + GHashIter *iter; // check if font is already embedded - for (i = 0; i < t1FontNameLen; ++i) { - if (t1FontNames[i].fontFileID.num == id->num && - t1FontNames[i].fontFileID.gen == id->gen) { - psName->clear(); - psName->insert(0, t1FontNames[i].psName); - return; + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen) { + fontFileInfo->killIter(&iter); + return ff; } } - if (t1FontNameLen == t1FontNameSize) { - t1FontNameSize *= 2; - t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, - sizeof(PST1FontName)); - } - t1FontNames[t1FontNameLen].fontFileID = *id; - t1FontNames[t1FontNameLen].psName = psName->copy(); - ++t1FontNameLen; + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2144,7 +2338,7 @@ void PSOutputDev::setupEmbeddedOpenTypeT // convert it to a Type 1 font if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { - if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) { + if ((ffTT = FoFiTrueType::make(fontBuf, fontLen, 0))) { if (ffTT->isOpenTypeCFF()) { ffTT->convertToType1(psName->getCString(), NULL, gTrue, outputFunc, outputStream); @@ -2156,14 +2350,50 @@ void PSOutputDev::setupEmbeddedOpenTypeT // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiTrueType *ffTT; int *codeToGID; + GHashIter *iter; + + // get the code-to-GID mapping + if (!(fontBuf = font->readEmbFontFile(xref, &fontLen))) { + return NULL; + } + if (!(ffTT = FoFiTrueType::make(fontBuf, fontLen, 0))) { + gfree(fontBuf); + return NULL; + } + codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT); + + // check if font is already embedded + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen && + ff->codeToGIDLen == 256 && + !memcmp(ff->codeToGID, codeToGID, 256 * sizeof(int))) { + fontFileInfo->killIter(&iter); + gfree(codeToGID); + delete ffTT; + gfree(fontBuf); + return ff; + } + } + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2172,38 +2402,57 @@ void PSOutputDev::setupEmbeddedTrueTypeF embFontList->append("\n"); // convert it to a Type 42 font - if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { - if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) { - codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT); - ffTT->convertToType42(psName->getCString(), - ((Gfx8BitFont *)font)->getHasEncoding() - ? ((Gfx8BitFont *)font)->getEncoding() - : (char **)NULL, - codeToGID, outputFunc, outputStream); - if (codeToGID) { - if (font8InfoLen >= font8InfoSize) { - font8InfoSize += 16; - font8Info = (PSFont8Info *)greallocn(font8Info, - font8InfoSize, - sizeof(PSFont8Info)); - } - font8Info[font8InfoLen].fontID = *font->getID(); - font8Info[font8InfoLen].codeToGID = codeToGID; - ++font8InfoLen; - } - delete ffTT; - } - gfree(fontBuf); - } + ffTT->convertToType42(psName->getCString(), + ((Gfx8BitFont *)font)->getHasEncoding() + ? ((Gfx8BitFont *)font)->getEncoding() + : (char **)NULL, + codeToGID, outputFunc, outputStream); + delete ffTT; + gfree(fontBuf); // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + ff->codeToGID = codeToGID; + ff->codeToGIDLen = 256; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupExternalTrueTypeFont(GfxFont *font, GString *fileName, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupExternalTrueTypeFont(GfxFont *font, + GString *fileName, + int fontNum) { + GString *psName; + PSFontFileInfo *ff; FoFiTrueType *ffTT; int *codeToGID; + GHashIter *iter; + + // get the code-to-GID mapping + if (!(ffTT = FoFiTrueType::load(fileName->getCString(), fontNum))) { + return NULL; + } + codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT); + + // check if font is already embedded + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileExternal && + ff->type == font->getType() && + !ff->extFileName->cmp(fileName) && + ff->codeToGIDLen == 256 && + !memcmp(ff->codeToGID, codeToGID, 256 * sizeof(int))) { + fontFileInfo->killIter(&iter); + gfree(codeToGID); + delete ffTT; + return ff; + } + } + + // generate name + psName = makePSFontName(font, font->getID()); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2212,55 +2461,45 @@ void PSOutputDev::setupExternalTrueTypeF embFontList->append("\n"); // convert it to a Type 42 font - if ((ffTT = FoFiTrueType::load(fileName->getCString()))) { - codeToGID = ((Gfx8BitFont *)font)->getCodeToGIDMap(ffTT); - ffTT->convertToType42(psName->getCString(), - ((Gfx8BitFont *)font)->getHasEncoding() - ? ((Gfx8BitFont *)font)->getEncoding() - : (char **)NULL, - codeToGID, outputFunc, outputStream); - if (codeToGID) { - if (font8InfoLen >= font8InfoSize) { - font8InfoSize += 16; - font8Info = (PSFont8Info *)greallocn(font8Info, - font8InfoSize, - sizeof(PSFont8Info)); - } - font8Info[font8InfoLen].fontID = *font->getID(); - font8Info[font8InfoLen].codeToGID = codeToGID; - ++font8InfoLen; - } - delete ffTT; - } + ffTT->convertToType42(psName->getCString(), + ((Gfx8BitFont *)font)->getHasEncoding() + ? ((Gfx8BitFont *)font)->getEncoding() + : (char **)NULL, + codeToGID, outputFunc, outputStream); + delete ffTT; // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileExternal); + ff->extFileName = fileName->copy(); + ff->codeToGID = codeToGID; + ff->codeToGIDLen = 256; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiType1C *ffT1C; - int i; + GHashIter *iter; // check if font is already embedded - for (i = 0; i < t1FontNameLen; ++i) { - if (t1FontNames[i].fontFileID.num == id->num && - t1FontNames[i].fontFileID.gen == id->gen) { - psName->clear(); - psName->insert(0, t1FontNames[i].psName); - return; + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen) { + fontFileInfo->killIter(&iter); + return ff; } } - if (t1FontNameLen == t1FontNameSize) { - t1FontNameSize *= 2; - t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, - sizeof(PST1FontName)); - } - t1FontNames[t1FontNameLen].fontFileID = *id; - t1FontNames[t1FontNameLen].psName = psName->copy(); - ++t1FontNameLen; + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2287,14 +2526,46 @@ void PSOutputDev::setupEmbeddedCIDType0F // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id, - GString *psName, - GBool needVerticalMetrics) { +PSFontFileInfo *PSOutputDev::setupEmbeddedCIDTrueTypeFont( + GfxFont *font, Ref *id, + GBool needVerticalMetrics) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiTrueType *ffTT; + int *codeToGID; + int codeToGIDLen; + GHashIter *iter; + + // get the code-to-GID mapping + codeToGID = ((GfxCIDFont *)font)->getCIDToGID(); + codeToGIDLen = ((GfxCIDFont *)font)->getCIDToGIDLen(); + + // check if font is already embedded + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen && + ff->codeToGIDLen == codeToGIDLen && + ((!ff->codeToGID && !codeToGID) || + (ff->codeToGID && codeToGID && + !memcmp(ff->codeToGID, codeToGID, codeToGIDLen * sizeof(int))))) { + fontFileInfo->killIter(&iter); + return ff; + } + } + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2304,19 +2575,17 @@ void PSOutputDev::setupEmbeddedCIDTrueTy // convert it to a Type 0 font if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { - if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) { + if ((ffTT = FoFiTrueType::make(fontBuf, fontLen, 0))) { if (globalParams->getPSLevel() >= psLevel3) { // Level 3: use a CID font ffTT->convertToCIDType2(psName->getCString(), - ((GfxCIDFont *)font)->getCIDToGID(), - ((GfxCIDFont *)font)->getCIDToGIDLen(), + codeToGID, codeToGIDLen, needVerticalMetrics, outputFunc, outputStream); } else { // otherwise: use a non-CID composite font ffTT->convertToType0(psName->getCString(), - ((GfxCIDFont *)font)->getCIDToGID(), - ((GfxCIDFont *)font)->getCIDToGIDLen(), + codeToGID, codeToGIDLen, needVerticalMetrics, outputFunc, outputStream); } @@ -2327,18 +2596,104 @@ void PSOutputDev::setupEmbeddedCIDTrueTy // ending comment writePS("%%EndResource\n"); -} -void PSOutputDev::setupExternalCIDTrueTypeFont(GfxFont *font, - GString *fileName, - GString *psName, - GBool needVerticalMetrics) { + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + if (codeToGIDLen) { + ff->codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int)); + memcpy(ff->codeToGID, codeToGID, codeToGIDLen * sizeof(int)); + ff->codeToGIDLen = codeToGIDLen; + } + fontFileInfo->add(ff->psName, ff); + return ff; +} + +PSFontFileInfo *PSOutputDev::setupExternalCIDTrueTypeFont( + GfxFont *font, + GString *fileName, + int fontNum, + GBool needVerticalMetrics) { + GString *psName; + PSFontFileInfo *ff; FoFiTrueType *ffTT; int *codeToGID; int codeToGIDLen; CharCodeToUnicode *ctu; Unicode uBuf[8]; int cmap, code; + GHashIter *iter; + + // create a code-to-GID mapping, via Unicode + if (!(ffTT = FoFiTrueType::load(fileName->getCString(), fontNum))) { + return NULL; + } + if (!(ctu = ((GfxCIDFont *)font)->getToUnicode())) { + error(errSyntaxError, -1, + "Couldn't find a mapping to Unicode for font '{0:s}'", + font->getName() ? font->getName()->getCString() : "(unnamed)"); + delete ffTT; + return NULL; + } + // look for a Unicode cmap + for (cmap = 0; cmap < ffTT->getNumCmaps(); ++cmap) { + if ((ffTT->getCmapPlatform(cmap) == 3 && + ffTT->getCmapEncoding(cmap) == 1) || + ffTT->getCmapPlatform(cmap) == 0) { + break; + } + } + if (cmap >= ffTT->getNumCmaps()) { + error(errSyntaxError, -1, + "Couldn't find a Unicode cmap in font '{0:s}'", + font->getName() ? font->getName()->getCString() : "(unnamed)"); + ctu->decRefCnt(); + delete ffTT; + return NULL; + } + // map CID -> Unicode -> GID + if (ctu->isIdentity()) { + codeToGIDLen = 65536; + } else { + codeToGIDLen = ctu->getLength(); + } + codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int)); + for (code = 0; code < codeToGIDLen; ++code) { + if (ctu->mapToUnicode(code, uBuf, 8) > 0) { + codeToGID[code] = ffTT->mapCodeToGID(cmap, uBuf[0]); + } else { + codeToGID[code] = 0; + } + } + ctu->decRefCnt(); + + // check if font is already embedded + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileExternal && + ff->type == font->getType() && + !ff->extFileName->cmp(fileName) && + ff->codeToGIDLen == codeToGIDLen && + ff->codeToGID && + !memcmp(ff->codeToGID, codeToGID, codeToGIDLen * sizeof(int))) { + fontFileInfo->killIter(&iter); + gfree(codeToGID); + delete ffTT; + return ff; + } + } + + // check for embedding permission + if (ffTT->getEmbeddingRights() < 1) { + error(errSyntaxError, -1, + "TrueType font '{0:s}' does not allow embedding", + font->getName() ? font->getName()->getCString() : "(unnamed)"); + gfree(codeToGID); + delete ffTT; + return NULL; + } + + // generate name + psName = makePSFontName(font, font->getID()); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2348,90 +2703,55 @@ void PSOutputDev::setupExternalCIDTrueTy // convert it to a Type 0 font //~ this should use fontNum to load the correct font - if ((ffTT = FoFiTrueType::load(fileName->getCString()))) { - - // check for embedding permission - if (ffTT->getEmbeddingRights() >= 1) { - - // create a CID-to-GID mapping, via Unicode - if ((ctu = ((GfxCIDFont *)font)->getToUnicode())) { - // look for a Unicode cmap - for (cmap = 0; cmap < ffTT->getNumCmaps(); ++cmap) { - if ((ffTT->getCmapPlatform(cmap) == 3 && - ffTT->getCmapEncoding(cmap) == 1) || - ffTT->getCmapPlatform(cmap) == 0) { - break; - } - } - if (cmap < ffTT->getNumCmaps()) { - // map CID -> Unicode -> GID - codeToGIDLen = ctu->getLength(); - codeToGID = (int *)gmallocn(codeToGIDLen, sizeof(int)); - for (code = 0; code < codeToGIDLen; ++code) { - if (ctu->mapToUnicode(code, uBuf, 8) > 0) { - codeToGID[code] = ffTT->mapCodeToGID(cmap, uBuf[0]); - } else { - codeToGID[code] = 0; - } - } - if (globalParams->getPSLevel() >= psLevel3) { - // Level 3: use a CID font - ffTT->convertToCIDType2(psName->getCString(), - codeToGID, codeToGIDLen, - needVerticalMetrics, - outputFunc, outputStream); - } else { - // otherwise: use a non-CID composite font - ffTT->convertToType0(psName->getCString(), - codeToGID, codeToGIDLen, - needVerticalMetrics, - outputFunc, outputStream); - } - gfree(codeToGID); - } - ctu->decRefCnt(); - } else { - error(errSyntaxError, -1, - "Couldn't find a mapping to Unicode for font '{0:s}'", - font->getName() ? font->getName()->getCString() : "(unnamed)"); - } - } else { - error(errSyntaxError, -1, - "TrueType font '%s' does not allow embedding", - font->getName() ? font->getName()->getCString() : "(unnamed)"); - - } - delete ffTT; + if (globalParams->getPSLevel() >= psLevel3) { + // Level 3: use a CID font + ffTT->convertToCIDType2(psName->getCString(), + codeToGID, codeToGIDLen, + needVerticalMetrics, + outputFunc, outputStream); + } else { + // otherwise: use a non-CID composite font + ffTT->convertToType0(psName->getCString(), + codeToGID, codeToGIDLen, + needVerticalMetrics, + outputFunc, outputStream); } + delete ffTT; // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileExternal); + ff->extFileName = fileName->copy(); + ff->codeToGID = codeToGID; + ff->codeToGIDLen = codeToGIDLen; + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id, - GString *psName) { +PSFontFileInfo *PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, + Ref *id) { + GString *psName; + PSFontFileInfo *ff; char *fontBuf; int fontLen; FoFiTrueType *ffTT; - int i; + GHashIter *iter; + int n; // check if font is already embedded - for (i = 0; i < t1FontNameLen; ++i) { - if (t1FontNames[i].fontFileID.num == id->num && - t1FontNames[i].fontFileID.gen == id->gen) { - psName->clear(); - psName->insert(0, t1FontNames[i].psName); - return; + fontFileInfo->startIter(&iter); + while (fontFileInfo->getNext(&iter, &psName, (void **)&ff)) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen) { + fontFileInfo->killIter(&iter); + return ff; } } - if (t1FontNameLen == t1FontNameSize) { - t1FontNameSize *= 2; - t1FontNames = (PST1FontName *)greallocn(t1FontNames, t1FontNameSize, - sizeof(PST1FontName)); - } - t1FontNames[t1FontNameLen].fontFileID = *id; - t1FontNames[t1FontNameLen].psName = psName->copy(); - ++t1FontNameLen; + + // generate name + psName = makePSFontName(font, id); // beginning comment writePSFmt("%%BeginResource: font {0:t}\n", psName); @@ -2441,7 +2761,7 @@ void PSOutputDev::setupEmbeddedOpenTypeC // convert it to a Type 0 font if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { - if ((ffTT = FoFiTrueType::make(fontBuf, fontLen))) { + if ((ffTT = FoFiTrueType::make(fontBuf, fontLen, 0))) { if (ffTT->isOpenTypeCFF()) { if (globalParams->getPSLevel() >= psLevel3) { // Level 3: use a CID font @@ -2464,10 +2784,22 @@ void PSOutputDev::setupEmbeddedOpenTypeC // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + if ((n = ((GfxCIDFont *)font)->getCIDToGIDLen())) { + ff->codeToGID = (int *)gmallocn(n, sizeof(int)); + memcpy(ff->codeToGID, ((GfxCIDFont *)font)->getCIDToGID(), n * sizeof(int)); + ff->codeToGIDLen = n; + } + fontFileInfo->add(ff->psName, ff); + return ff; } -void PSOutputDev::setupType3Font(GfxFont *font, GString *psName, - Dict *parentResDict) { +PSFontFileInfo *PSOutputDev::setupType3Font(GfxFont *font, + Dict *parentResDict) { + PSFontFileInfo *ff; + GString *psName; Dict *resDict; Dict *charProcs; Object charProc; @@ -2477,6 +2809,10 @@ void PSOutputDev::setupType3Font(GfxFont GString *buf; int i; + // generate name + psName = GString::format("T3_{0:d}_{1:d}", + font->getID()->num, font->getID()->gen); + // set up resources used by font if ((resDict = ((Gfx8BitFont *)font)->getResources())) { inType3Char = gTrue; @@ -2528,7 +2864,7 @@ void PSOutputDev::setupType3Font(GfxFont writePS("/"); writePSName(charProcs->getKey(i)); writePS(" {\n"); - gfx->display(charProcs->getVal(i, &charProc)); + gfx->display(charProcs->getValNF(i, &charProc)); charProc.free(); if (t3String) { if (t3Cacheable) { @@ -2558,6 +2894,10 @@ void PSOutputDev::setupType3Font(GfxFont // ending comment writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + fontFileInfo->add(ff->psName, ff); + return ff; } // Make a unique PS font name, based on the names given in the PDF @@ -2567,16 +2907,14 @@ GString *PSOutputDev::makePSFontName(Gfx if ((s = font->getEmbeddedFontName())) { psName = filterPSName(s); - if (!fontNames->lookupInt(psName)) { - fontNames->add(psName->copy(), 1); + if (!fontFileInfo->lookup(psName)) { return psName; } delete psName; } if ((s = font->getName())) { psName = filterPSName(s); - if (!fontNames->lookupInt(psName)) { - fontNames->add(psName->copy(), 1); + if (!fontFileInfo->lookup(psName)) { return psName; } delete psName; @@ -2591,7 +2929,6 @@ GString *PSOutputDev::makePSFontName(Gfx psName->append('_')->append(s); delete s; } - fontNames->add(psName->copy(), 1); return psName; } @@ -2651,7 +2988,7 @@ void PSOutputDev::setupImages(Dict *resD } void PSOutputDev::setupImage(Ref id, Stream *str, GBool mask) { - GBool useRLE, useCompressed, useASCIIHex; + GBool useLZW, useRLE, useCompressed, useASCIIHex; GString *s; int c; int size, line, col, i; @@ -2660,21 +2997,27 @@ void PSOutputDev::setupImage(Ref id, Str //~ this does not correctly handle the DeviceN color space //~ -- need to use DeviceNRecoder if (level < psLevel2) { - useRLE = gFalse; + useLZW = useRLE = gFalse; useCompressed = gFalse; useASCIIHex = gTrue; } else { if (globalParams->getPSUncompressPreloadedImages()) { - useRLE = gFalse; + useLZW = useRLE = gFalse; useCompressed = gFalse; } else { s = str->getPSFilter(level < psLevel3 ? 2 : 3, ""); if (s) { - useRLE = gFalse; + useLZW = useRLE = gFalse; useCompressed = gTrue; delete s; } else { - useRLE = gTrue; + if (globalParams->getPSLZW()) { + useLZW = gTrue; + useRLE = gFalse; + } else { + useRLE = gTrue; + useLZW = gFalse; + } useCompressed = gFalse; } } @@ -2683,7 +3026,9 @@ void PSOutputDev::setupImage(Ref id, Str if (useCompressed) { str = str->getUndecodedStream(); } - if (useRLE) { + if (useLZW) { + str = new LZWEncoder(str); + } else if (useRLE) { str = new RunLengthEncoder(str); } if (useASCIIHex) { @@ -2722,9 +3067,9 @@ void PSOutputDev::setupImage(Ref id, Str } } while (c != (useASCIIHex ? '>' : '~') && c != EOF); // add one entry for the final line of data; add another entry - // because the RunLengthDecode filter may read past the end + // because the LZWDecode/RunLengthDecode filter may read past the end ++size; - if (useRLE) { + if (useLZW || useRLE) { ++size; } writePSFmt("{0:d} array dup /{1:s}Data_{2:d}_{3:d} exch def\n", @@ -2771,7 +3116,7 @@ void PSOutputDev::setupImage(Ref id, Str } } while (c != (useASCIIHex ? '>' : '~') && c != EOF); writePS((char *)(useASCIIHex ? "> put\n" : "~> put\n")); - if (useRLE) { + if (useLZW || useRLE) { ++line; writePSFmt("{0:d} <> put\n", line); } else { @@ -2799,7 +3144,7 @@ void PSOutputDev::setupForms(Dict *resDi xObj.streamGetDict()->lookup("Subtype", &subtypeObj); if (subtypeObj.isName("Form")) { if (xObjRef.isRef()) { - setupForm(xObjRef.getRef(), &xObj); + setupForm(&xObjRef, &xObj); } else { error(errSyntaxError, -1, "Form in resource dict is not an indirect reference"); @@ -2814,7 +3159,7 @@ void PSOutputDev::setupForms(Dict *resDi xObjDict.free(); } -void PSOutputDev::setupForm(Ref id, Object *strObj) { +void PSOutputDev::setupForm(Object *strRef, Object *strObj) { Dict *dict, *resDict; Object matrixObj, bboxObj, resObj, obj1; double m[6], bbox[4]; @@ -2824,7 +3169,8 @@ void PSOutputDev::setupForm(Ref id, Obje // check if form is already defined for (i = 0; i < formIDLen; ++i) { - if (formIDs[i].num == id.num && formIDs[i].gen == id.gen) { + if (formIDs[i].num == strRef->getRefNum() && + formIDs[i].gen == strRef->getRefGen()) { return; } } @@ -2838,7 +3184,7 @@ void PSOutputDev::setupForm(Ref id, Obje } formIDs = (Ref *)greallocn(formIDs, formIDSize, sizeof(Ref)); } - formIDs[formIDLen++] = id; + formIDs[formIDLen++] = strRef->getRef(); dict = strObj->streamGetDict(); @@ -2875,7 +3221,7 @@ void PSOutputDev::setupForm(Ref id, Obje dict->lookup("Resources", &resObj); resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL; - writePSFmt("/f_{0:d}_{1:d} {{\n", id.num, id.gen); + writePSFmt("/f_{0:d}_{1:d} {{\n", strRef->getRefNum(), strRef->getRefGen()); writePS("q\n"); writePSFmt("[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] cm\n", m[0], m[1], m[2], m[3], m[4], m[5]); @@ -2885,7 +3231,7 @@ void PSOutputDev::setupForm(Ref id, Obje box.x2 = bbox[2]; box.y2 = bbox[3]; gfx = new Gfx(doc, this, resDict, &box, &box); - gfx->display(strObj); + gfx->display(strRef); delete gfx; writePS("Q\n"); @@ -2905,6 +3251,7 @@ GBool PSOutputDev::checkPageSlice(Page * GBool rasterize; #if HAVE_SPLASH GBool mono; + GBool useLZW; double dpi; SplashOutputDev *splashOut; SplashColor paperColor; @@ -2915,10 +3262,11 @@ GBool PSOutputDev::checkPageSlice(Page * Object obj; Guchar *p; Guchar col[4]; + char buf[4096]; double hDPI2, vDPI2; double m0, m1, m2, m3, m4, m5; int nStripes, stripeH, stripeY; - int c, w, h, x, y, comp, i; + int w, h, x, y, comp, i, n; #endif if (globalParams->getPSAlwaysRasterize()) { @@ -2939,6 +3287,7 @@ GBool PSOutputDev::checkPageSlice(Page * // get the rasterization parameters dpi = globalParams->getPSRasterResolution(); mono = globalParams->getPSRasterMono(); + useLZW = globalParams->getPSLZW(); // start the PS page page->makeBox(dpi, dpi, rotateA, useMediaBox, gFalse, @@ -2987,8 +3336,8 @@ GBool PSOutputDev::checkPageSlice(Page * sliceW = (int)((box.x2 - box.x1) * hDPI2 / 72.0); sliceH = (int)((box.y2 - box.y1) * vDPI2 / 72.0); } - nStripes = (int)ceil((double)(sliceW * sliceH) / - (double)rasterizationSliceSize); + nStripes = (int)ceil(((double)sliceW * (double)sliceH) / + (double)globalParams->getPSRasterSliceSize()); stripeH = (sliceH + nStripes - 1) / nStripes; // render the stripes @@ -3094,21 +3443,29 @@ GBool PSOutputDev::checkPageSlice(Page * } else { writePS(" /ASCII85Decode filter\n"); } - writePS(" /RunLengthDecode filter\n"); + if (useLZW) { + writePS(" /LZWDecode filter\n"); + } else { + writePS(" /RunLengthDecode filter\n"); + } writePS(">>\n"); writePS("image\n"); obj.initNull(); p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize(); str0 = new MemStream((char *)p, 0, w * h * (mono ? 1 : 3), &obj); - str = new RunLengthEncoder(str0); + if (useLZW) { + str = new LZWEncoder(str0); + } else { + str = new RunLengthEncoder(str0); + } if (globalParams->getPSASCIIHex()) { str = new ASCIIHexEncoder(str); } else { str = new ASCII85Encoder(str); } str->reset(); - while ((c = str->getChar()) != EOF) { - writePSChar(c); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); } str->close(); delete str; @@ -3148,8 +3505,13 @@ void PSOutputDev::startPage(int pageNum, if (paperMatch) { page = doc->getCatalog()->getPage(pageNum); imgLLX = imgLLY = 0; - imgURX = (int)ceil(page->getMediaWidth()); - imgURY = (int)ceil(page->getMediaHeight()); + if (globalParams->getPSUseCropBoxAsPage()) { + imgURX = (int)ceil(page->getCropWidth()); + imgURY = (int)ceil(page->getCropHeight()); + } else { + imgURX = (int)ceil(page->getMediaWidth()); + imgURY = (int)ceil(page->getMediaHeight()); + } if (state->getRotate() == 90 || state->getRotate() == 270) { t = imgURX; imgURX = imgURY; @@ -3160,6 +3522,9 @@ void PSOutputDev::startPage(int pageNum, } writePS("%%BeginPageSetup\n"); } + if (mode != psModeForm) { + writePS("xpdf begin\n"); + } // underlays if (underlayCbk) { @@ -3356,6 +3721,7 @@ void PSOutputDev::endPage() { } writePS("%%PageTrailer\n"); writePageTrailer(); + writePS("end\n"); } } @@ -3371,8 +3737,13 @@ void PSOutputDev::restoreState(GfxState void PSOutputDev::updateCTM(GfxState *state, double m11, double m12, double m21, double m22, double m31, double m32) { - writePSFmt("[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] cm\n", - m11, m12, m21, m22, m31, m32); + if (fabs(m11 * m22 - m12 * m21) < 0.00001) { + // avoid a singular (or close-to-singular) matrix + writePSFmt("[0.00001 0 0 0.00001 {0:.6g} {1:.6g}] Tm\n", m31, m32); + } else { + writePSFmt("[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] cm\n", + m11, m12, m21, m22, m31, m32); + } } void PSOutputDev::updateLineDash(GfxState *state) { @@ -3737,7 +4108,7 @@ void PSOutputDev::eoFill(GfxState *state writePS("f*\n"); } -void PSOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, +void PSOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -3770,6 +4141,7 @@ void PSOutputDev::tilingPatternFill(GfxS box.x2 = bbox[2]; box.y2 = bbox[3]; gfx2 = new Gfx(doc, this, resDict, &box, NULL); + gfx2->takeContentStreamStack(gfx); writePS("/x {\n"); if (paintType == 2) { writePSFmt("{0:.6g} 0 {1:.6g} {2:.6g} {3:.6g} {4:.6g} setcachedevice\n", @@ -3785,7 +4157,7 @@ void PSOutputDev::tilingPatternFill(GfxS } inType3Char = gTrue; ++numTilingPatterns; - gfx2->display(str); + gfx2->display(strRef); --numTilingPatterns; inType3Char = gFalse; writePS("} def\n"); @@ -3944,7 +4316,7 @@ GBool PSOutputDev::radialShadedFill(GfxS double xMin, yMin, xMax, yMax; double x0, y0, r0, x1, y1, r1, t0, t1; double xa, ya, ra; - double sz, sMin, sMax, h, ta; + double sMin, sMax, h, ta; double sLeft, sRight, sTop, sBottom, sZero, sDiag; GBool haveSLeft, haveSRight, haveSTop, haveSBottom, haveSZero; GBool haveSMin, haveSMax; @@ -3970,18 +4342,14 @@ GBool PSOutputDev::radialShadedFill(GfxS if (h == 0) { enclosed = gTrue; theta = 0; // make gcc happy - sz = 0; // make gcc happy } else if (r1 - r0 == 0) { enclosed = gFalse; theta = 0; - sz = 0; // make gcc happy } else if (fabs(r1 - r0) >= h) { enclosed = gTrue; theta = 0; // make gcc happy - sz = 0; // make gcc happy } else { enclosed = gFalse; - sz = -r0 / (r1 - r0); theta = asin((r1 - r0) / h); } if (enclosed) { @@ -4267,8 +4635,9 @@ void PSOutputDev::drawString(GfxState *s int wMode; int *codeToGID; GString *s2; - double dx, dy, originX, originY; + double dx, dy, originX, originY, originX0, originY0, tOriginX0, tOriginY0; char *p; + PSFontInfo *fi; UnicodeMap *uMap; CharCode code; Unicode u[8]; @@ -4292,30 +4661,32 @@ void PSOutputDev::drawString(GfxState *s } wMode = font->getWMode(); + fi = NULL; + for (i = 0; i < fontInfo->getLength(); ++i) { + fi = (PSFontInfo *)fontInfo->get(i); + if (fi->fontID.num == font->getID()->num && + fi->fontID.gen == font->getID()->gen) { + break; + } + fi = NULL; + } + // check for a subtitute 16-bit font uMap = NULL; codeToGID = NULL; if (font->isCIDFont()) { - for (i = 0; i < font16EncLen; ++i) { - if (font->getID()->num == font16Enc[i].fontID.num && - font->getID()->gen == font16Enc[i].fontID.gen) { - if (!font16Enc[i].enc) { - // font substitution failed, so don't output any text - return; - } - uMap = globalParams->getUnicodeMap(font16Enc[i].enc); - break; - } + if (!(fi && fi->ff)) { + // font substitution failed, so don't output any text + return; + } + if (fi->ff->encoding) { + uMap = globalParams->getUnicodeMap(fi->ff->encoding); } - // check for a code-to-GID map + // check for an 8-bit code-to-GID map } else { - for (i = 0; i < font8InfoLen; ++i) { - if (font->getID()->num == font8Info[i].fontID.num && - font->getID()->gen == font8Info[i].fontID.gen) { - codeToGID = font8Info[i].codeToGID; - break; - } + if (fi && fi->ff) { + codeToGID = fi->ff->codeToGID; } } @@ -4326,10 +4697,18 @@ void PSOutputDev::drawString(GfxState *s s2 = new GString(); dxdySize = font->isCIDFont() ? 8 : s->getLength(); dxdy = (double *)gmallocn(2 * dxdySize, sizeof(double)); + originX0 = originY0 = 0; // make gcc happy while (len > 0) { n = font->getNextChar(p, len, &code, u, (int)(sizeof(u) / sizeof(Unicode)), &uLen, &dx, &dy, &originX, &originY); + //~ this doesn't handle the case where the origin offset changes + //~ within a string of characters -- which could be fixed by + //~ modifying dx,dy as needed for each character + if (p == s->getCString()) { + originX0 = originX; + originY0 = originY; + } dx *= state->getFontSize(); dy *= state->getFontSize(); if (wMode) { @@ -4389,8 +4768,14 @@ void PSOutputDev::drawString(GfxState *s if (uMap) { uMap->decRefCnt(); } + originX0 *= state->getFontSize(); + originY0 *= state->getFontSize(); + state->textTransformDelta(originX0, originY0, &tOriginX0, &tOriginY0); if (nChars > 0) { + if (wMode) { + writePSFmt("{0:.6g} {1:.6g} rmoveto\n", -tOriginX0, -tOriginY0); + } writePSString(s2); writePS("\n["); for (i = 0; i < 2 * nChars; ++i) { @@ -4400,6 +4785,9 @@ void PSOutputDev::drawString(GfxState *s writePSFmt("{0:.6g}", dxdy[i]); } writePS("] Tj\n"); + if (wMode) { + writePSFmt("{0:.6g} {1:.6g} rmoveto\n", tOriginX0, tOriginY0); + } } gfree(dxdy); delete s2; @@ -4418,7 +4806,7 @@ void PSOutputDev::endTextObject(GfxState void PSOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { + GBool inlineImg, GBool interpolate) { int len; len = height * ((width + 7) / 8); @@ -4442,7 +4830,8 @@ void PSOutputDev::drawImageMask(GfxState void PSOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg) { + int *maskColors, GBool inlineImg, + GBool interpolate) { int len; len = height * ((width * colorMap->getNumPixelComps() * @@ -4474,7 +4863,7 @@ void PSOutputDev::drawMaskedImage(GfxSta GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert) { + GBool maskInvert, GBool interpolate) { int len; len = height * ((width * colorMap->getNumPixelComps() * @@ -4684,10 +5073,11 @@ void PSOutputDev::doImageL2(Object *ref, GBool emitRect, addRect, extendRect; GString *s; int n, numComps; - GBool useRLE, useASCII, useASCIIHex, useCompressed; + GBool useLZW, useRLE, useASCII, useASCIIHex, useCompressed; GfxSeparationColorSpace *sepCS; GfxColor color; GfxCMYK cmyk; + char buf[4096]; int c; int col, i, j, x0, x1, y, maskXor; @@ -4821,14 +5211,14 @@ void PSOutputDev::doImageL2(Object *ref, rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1; ++rectsOutLen; } - writePSFmt("{0:d} array 0\n", rectsOutLen * 4); + writePSFmt("{0:d} {1:d}\n", maskWidth, maskHeight); for (i = 0; i < rectsOutLen; ++i) { - writePSFmt("[{0:d} {1:d} {2:d} {3:d}] pr\n", + writePSFmt("{0:d} {1:d} {2:d} {3:d} pr\n", rectsOut[i].x0, rectsOut[i].y0, rectsOut[i].x1 - rectsOut[i].x0, rectsOut[i].y1 - rectsOut[i].y0); } - writePSFmt("pop {0:d} {1:d} pdfImClip\n", width, height); + writePS("pop pop pdfImClip\n"); gfree(rectsOut); gfree(rects0); gfree(rects1); @@ -4923,14 +5313,14 @@ void PSOutputDev::doImageL2(Object *ref, rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1; ++rectsOutLen; } - writePSFmt("{0:d} array 0\n", rectsOutLen * 4); + writePSFmt("{0:d} {1:d}\n", maskWidth, maskHeight); for (i = 0; i < rectsOutLen; ++i) { - writePSFmt("[{0:d} {1:d} {2:d} {3:d}] pr\n", + writePSFmt("{0:d} {1:d} {2:d} {3:d} pr\n", rectsOut[i].x0, rectsOut[i].y0, rectsOut[i].x1 - rectsOut[i].x0, rectsOut[i].y1 - rectsOut[i].y0); } - writePSFmt("pop {0:d} {1:d} pdfImClip\n", maskWidth, maskHeight); + writePS("pop pop pdfImClip\n"); gfree(rectsOut); gfree(rects0); gfree(rects1); @@ -4951,7 +5341,11 @@ void PSOutputDev::doImageL2(Object *ref, if (inlineImg) { // create an array str2 = new FixedLengthEncoder(str, len); - str2 = new RunLengthEncoder(str2); + if (globalParams->getPSLZW()) { + str2 = new LZWEncoder(str2); + } else { + str2 = new RunLengthEncoder(str2); + } if (useASCIIHex) { str2 = new ASCIIHexEncoder(str2); } else { @@ -4994,8 +5388,8 @@ void PSOutputDev::doImageL2(Object *ref, } } while (c != (useASCIIHex ? '>' : '~') && c != EOF); writePS((char *)(useASCIIHex ? ">\n" : "~>\n")); - // add an extra entry because the RunLengthDecode filter may - // read past the end + // add an extra entry because the LZWDecode/RunLengthDecode + // filter may read past the end writePS("<>]\n"); writePS("0\n"); str2->close(); @@ -5065,7 +5459,7 @@ void PSOutputDev::doImageL2(Object *ref, if ((mode == psModeForm || inType3Char || preload) && globalParams->getPSUncompressPreloadedImages()) { s = NULL; - useRLE = gFalse; + useLZW = useRLE = gFalse; useCompressed = gFalse; useASCII = gFalse; } else { @@ -5073,11 +5467,17 @@ void PSOutputDev::doImageL2(Object *ref, " "); if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) || inlineImg || !s) { - useRLE = gTrue; + if (globalParams->getPSLZW()) { + useLZW = gTrue; + useRLE = gFalse; + } else { + useRLE = gTrue; + useLZW = gFalse; + } useASCII = !(mode == psModeForm || inType3Char || preload); useCompressed = gFalse; } else { - useRLE = gFalse; + useLZW = useRLE = gFalse; useASCII = str->isBinary() && !(mode == psModeForm || inType3Char || preload); useCompressed = gTrue; @@ -5087,7 +5487,9 @@ void PSOutputDev::doImageL2(Object *ref, writePSFmt(" /ASCII{0:s}Decode filter\n", useASCIIHex ? "Hex" : "85"); } - if (useRLE) { + if (useLZW) { + writePS(" /LZWDecode filter\n"); + } else if (useRLE) { writePS(" /RunLengthDecode filter\n"); } if (useCompressed) { @@ -5119,8 +5521,10 @@ void PSOutputDev::doImageL2(Object *ref, str = new DeviceNRecoder(str, width, height, colorMap); } - // add RunLengthEncode and ASCIIHex/85 encode filters - if (useRLE) { + // add LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters + if (useLZW) { + str = new LZWEncoder(str); + } else if (useRLE) { str = new RunLengthEncoder(str); } if (useASCII) { @@ -5141,12 +5545,14 @@ void PSOutputDev::doImageL2(Object *ref, n = 0; } else { // need to read the stream to count characters -- the length - // is data-dependent (because of ASCII and RLE filters) + // is data-dependent (because of ASCII and LZW/RunLength + // filters) str->reset(); n = 0; - while ((c = str->getChar()) != EOF) { - ++n; - } + do { + i = str->discardChars(4096); + n += i; + } while (i == 4096); str->close(); } // +6/7 for "pdfIm\n" / "pdfImM\n" @@ -5170,8 +5576,8 @@ void PSOutputDev::doImageL2(Object *ref, // copy the stream data str->reset(); - while ((c = str->getChar()) != EOF) { - writePSChar(c); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); } str->close(); @@ -5185,7 +5591,7 @@ void PSOutputDev::doImageL2(Object *ref, #endif // delete encoders - if (useRLE || useASCII || inlineImg) { + if (useLZW || useRLE || useASCII || inlineImg) { delete str; } } @@ -5204,18 +5610,20 @@ void PSOutputDev::doImageL3(Object *ref, Stream *str2; GString *s; int n, numComps; - GBool useRLE, useASCII, useASCIIHex, useCompressed; - GBool maskUseRLE, maskUseASCII, maskUseCompressed; + GBool useLZW, useRLE, useASCII, useASCIIHex, useCompressed; + GBool maskUseLZW, maskUseRLE, maskUseASCII, maskUseCompressed; GString *maskFilters; GfxSeparationColorSpace *sepCS; GfxColor color; GfxCMYK cmyk; + char buf[4096]; int c; int col, i; useASCIIHex = globalParams->getPSASCIIHex(); - useRLE = useASCII = useCompressed = gFalse; // make gcc happy - maskUseRLE = maskUseASCII = maskUseCompressed = gFalse; // make gcc happy + useLZW = useRLE = useASCII = useCompressed = gFalse; // make gcc happy + maskUseLZW = maskUseRLE = maskUseASCII = gFalse; // make gcc happy + maskUseCompressed = gFalse; // make gcc happy maskFilters = NULL; // make gcc happy // explicit masking @@ -5225,17 +5633,23 @@ void PSOutputDev::doImageL3(Object *ref, if ((mode == psModeForm || inType3Char || preload) && globalParams->getPSUncompressPreloadedImages()) { s = NULL; - maskUseRLE = gFalse; + maskUseLZW = maskUseRLE = gFalse; maskUseCompressed = gFalse; maskUseASCII = gFalse; } else { s = maskStr->getPSFilter(3, " "); if (!s) { - maskUseRLE = gTrue; + if (globalParams->getPSLZW()) { + maskUseLZW = gTrue; + maskUseRLE = gFalse; + } else { + maskUseRLE = gTrue; + maskUseLZW = gFalse; + } maskUseASCII = !(mode == psModeForm || inType3Char || preload); maskUseCompressed = gFalse; } else { - maskUseRLE = gFalse; + maskUseLZW = maskUseRLE = gFalse; maskUseASCII = maskStr->isBinary() && !(mode == psModeForm || inType3Char || preload); maskUseCompressed = gTrue; @@ -5246,7 +5660,9 @@ void PSOutputDev::doImageL3(Object *ref, maskFilters->appendf(" /ASCII{0:s}Decode filter\n", useASCIIHex ? "Hex" : "85"); } - if (maskUseRLE) { + if (maskUseLZW) { + maskFilters->append(" /LZWDecode filter\n"); + } else if (maskUseRLE) { maskFilters->append(" /RunLengthDecode filter\n"); } if (maskUseCompressed) { @@ -5263,11 +5679,13 @@ void PSOutputDev::doImageL3(Object *ref, writePS(maskFilters->getCString()); writePS("pdfMask\n"); - // add RunLengthEncode and ASCIIHex/85 encode filters + // add LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters if (maskUseCompressed) { maskStr = maskStr->getUndecodedStream(); } - if (maskUseRLE) { + if (maskUseLZW) { + maskStr = new LZWEncoder(maskStr); + } else if (maskUseRLE) { maskStr = new RunLengthEncoder(maskStr); } if (maskUseASCII) { @@ -5280,15 +5698,15 @@ void PSOutputDev::doImageL3(Object *ref, // copy the stream data maskStr->reset(); - while ((c = maskStr->getChar()) != EOF) { - writePSChar(c); + while ((n = maskStr->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); } maskStr->close(); writePSChar('\n'); writePS("%-EOD-\n"); // delete encoders - if (maskUseRLE || maskUseASCII) { + if (maskUseLZW || maskUseRLE || maskUseASCII) { delete maskStr; } } @@ -5305,7 +5723,11 @@ void PSOutputDev::doImageL3(Object *ref, if (inlineImg) { // create an array str2 = new FixedLengthEncoder(str, len); - str2 = new RunLengthEncoder(str2); + if (globalParams->getPSLZW()) { + str2 = new LZWEncoder(str2); + } else { + str2 = new RunLengthEncoder(str2); + } if (useASCIIHex) { str2 = new ASCIIHexEncoder(str2); } else { @@ -5348,8 +5770,8 @@ void PSOutputDev::doImageL3(Object *ref, } } while (c != (useASCIIHex ? '>' : '~') && c != EOF); writePS((char *)(useASCIIHex ? ">\n" : "~>\n")); - // add an extra entry because the RunLengthDecode filter may - // read past the end + // add an extra entry because the LZWDecode/RunLengthDecode + // filter may read past the end writePS("<>]\n"); writePS("0\n"); str2->close(); @@ -5436,7 +5858,7 @@ void PSOutputDev::doImageL3(Object *ref, if ((mode == psModeForm || inType3Char || preload) && globalParams->getPSUncompressPreloadedImages()) { s = NULL; - useRLE = gFalse; + useLZW = useRLE = gFalse; useCompressed = gFalse; useASCII = gFalse; } else { @@ -5444,11 +5866,17 @@ void PSOutputDev::doImageL3(Object *ref, " "); if ((colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) || inlineImg || !s) { - useRLE = gTrue; + if (globalParams->getPSLZW()) { + useLZW = gTrue; + useRLE = gFalse; + } else { + useRLE = gTrue; + useLZW = gFalse; + } useASCII = !(mode == psModeForm || inType3Char || preload); useCompressed = gFalse; } else { - useRLE = gFalse; + useLZW = useRLE = gFalse; useASCII = str->isBinary() && !(mode == psModeForm || inType3Char || preload); useCompressed = gTrue; @@ -5458,7 +5886,9 @@ void PSOutputDev::doImageL3(Object *ref, writePSFmt(" /ASCII{0:s}Decode filter\n", useASCIIHex ? "Hex" : "85"); } - if (useRLE) { + if (useLZW) { + writePS(" /LZWDecode filter\n"); + } else if (useRLE) { writePS(" /RunLengthDecode filter\n"); } if (useCompressed) { @@ -5538,8 +5968,10 @@ void PSOutputDev::doImageL3(Object *ref, str = new DeviceNRecoder(str, width, height, colorMap); } - // add RunLengthEncode and ASCIIHex/85 encode filters - if (useRLE) { + // add LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters + if (useLZW) { + str = new LZWEncoder(str); + } else if (useRLE) { str = new RunLengthEncoder(str); } if (useASCII) { @@ -5552,8 +5984,8 @@ void PSOutputDev::doImageL3(Object *ref, // copy the stream data str->reset(); - while ((c = str->getChar()) != EOF) { - writePSChar(c); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); } str->close(); @@ -5562,7 +5994,7 @@ void PSOutputDev::doImageL3(Object *ref, writePS("%-EOD-\n"); // delete encoders - if (useRLE || useASCII || inlineImg) { + if (useLZW || useRLE || useASCII || inlineImg) { delete str; } } @@ -6267,6 +6699,10 @@ void PSOutputDev::type3D0(GfxState *stat void PSOutputDev::type3D1(GfxState *state, double wx, double wy, double llx, double lly, double urx, double ury) { + if (t3String) { + error(errSyntaxError, -1, "Multiple 'd1' operators in Type 3 CharProc"); + return; + } t3WX = wx; t3WY = wy; t3LLX = llx; @@ -6286,7 +6722,8 @@ void PSOutputDev::drawForm(Ref id) { void PSOutputDev::psXObject(Stream *psStream, Stream *level1Stream) { Stream *str; - int c; + char buf[4096]; + int n; if ((level == psLevel1 || level == psLevel1Sep) && level1Stream) { str = level1Stream; @@ -6294,8 +6731,8 @@ void PSOutputDev::psXObject(Stream *psSt str = psStream; } str->reset(); - while ((c = str->getChar()) != EOF) { - writePSChar(c); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); } str->close(); } @@ -6462,6 +6899,14 @@ void PSOutputDev::writePSChar(char c) { } } +void PSOutputDev::writePSBlock(char *s, int len) { + if (t3String) { + t3String->append(s, len); + } else { + (*outputFunc)(outputStream, s, len); + } +} + void PSOutputDev::writePS(const char *s) { if (t3String) { t3String->append(s); @@ -6564,7 +7009,9 @@ GString *PSOutputDev::filterPSName(GStri // Write a DSC-compliant . void PSOutputDev::writePSTextLine(GString *s) { - int i, j, step; + TextString *ts; + Unicode *u; + int i, j; int c; // - DSC comments must be printable ASCII; control chars and @@ -6574,17 +7021,10 @@ void PSOutputDev::writePSTextLine(GStrin // for the keyword, which was emitted by the caller) // - lines that start with a left paren are treated as // instead of , so we escape a leading paren - if (s->getLength() >= 2 && - (s->getChar(0) & 0xff) == 0xfe && - (s->getChar(1) & 0xff) == 0xff) { - i = 3; - step = 2; - } else { - i = 0; - step = 1; - } - for (j = 0; i < s->getLength() && j < 200; i += step) { - c = s->getChar(i) & 0xff; + ts = new TextString(s); + u = ts->getUnicode(); + for (i = 0, j = 0; i < ts->getLength() && j < 200; ++i) { + c = u[i] & 0xff; if (c == '\\') { writePS("\\\\"); j += 2; @@ -6597,4 +7037,5 @@ void PSOutputDev::writePSTextLine(GStrin } } writePS("\n"); + delete ts; } diff -uNrp xpdf-3.03/xpdf/PSOutputDev.h xpdf-3.04/xpdf/PSOutputDev.h --- xpdf-3.03/xpdf/PSOutputDev.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/PSOutputDev.h 2014-05-28 20:50:50.000000000 +0200 @@ -30,11 +30,9 @@ class GfxFont; class GfxColorSpace; class GfxSeparationColorSpace; class PDFRectangle; -struct PST1FontName; -struct PSFont8Info; -struct PSFont16Enc; class PSOutCustomColor; class PSOutputDev; +class PSFontFileInfo; //------------------------------------------------------------------------ // PSOutputDev @@ -92,6 +90,9 @@ public: // Check if file was successfully created. virtual GBool isOk() { return ok; } + // Returns false if there have been any errors on the output stream. + GBool checkIO(); + //---- get info about output device // Does this device use upside-down coordinates? @@ -196,7 +197,7 @@ public: virtual void stroke(GfxState *state); virtual void fill(GfxState *state); virtual void eoFill(GfxState *state); - virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, + virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -218,15 +219,15 @@ public: //----- image drawing virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg); + int *maskColors, GBool inlineImg, GBool interpolate); virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert); + GBool maskInvert, GBool interpolate); #if OPI_SUPPORT //----- OPI functions @@ -262,6 +263,7 @@ public: { overlayCbk = cbk; overlayCbkData = data; } void writePSChar(char c); + void writePSBlock(char *s, int len); void writePS(const char *s); void writePSFmt(const char *fmt, ...); void writePSString(GString *s); @@ -277,27 +279,27 @@ private: void setupResources(Dict *resDict); void setupFonts(Dict *resDict); void setupFont(GfxFont *font, Dict *parentResDict); - void setupEmbeddedType1Font(Ref *id, GString *psName); - void setupExternalType1Font(GString *fileName, GString *psName); - void setupEmbeddedType1CFont(GfxFont *font, Ref *id, GString *psName); - void setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id, GString *psName); - void setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id, GString *psName); - void setupExternalTrueTypeFont(GfxFont *font, GString *fileName, - GString *psName); - void setupEmbeddedCIDType0Font(GfxFont *font, Ref *id, GString *psName); - void setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id, GString *psName, - GBool needVerticalMetrics); - void setupExternalCIDTrueTypeFont(GfxFont *font, - GString *fileName, - GString *psName, - GBool needVerticalMetrics); - void setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id, GString *psName); - void setupType3Font(GfxFont *font, GString *psName, Dict *parentResDict); + PSFontFileInfo *setupEmbeddedType1Font(GfxFont *font, Ref *id); + PSFontFileInfo *setupExternalType1Font(GfxFont *font, GString *fileName); + PSFontFileInfo *setupEmbeddedType1CFont(GfxFont *font, Ref *id); + PSFontFileInfo *setupEmbeddedOpenTypeT1CFont(GfxFont *font, Ref *id); + PSFontFileInfo *setupEmbeddedTrueTypeFont(GfxFont *font, Ref *id); + PSFontFileInfo *setupExternalTrueTypeFont(GfxFont *font, GString *fileName, + int fontNum); + PSFontFileInfo *setupEmbeddedCIDType0Font(GfxFont *font, Ref *id); + PSFontFileInfo *setupEmbeddedCIDTrueTypeFont(GfxFont *font, Ref *id, + GBool needVerticalMetrics); + PSFontFileInfo *setupExternalCIDTrueTypeFont(GfxFont *font, + GString *fileName, + int fontNum, + GBool needVerticalMetrics); + PSFontFileInfo *setupEmbeddedOpenTypeCFFFont(GfxFont *font, Ref *id); + PSFontFileInfo *setupType3Font(GfxFont *font, Dict *parentResDict); GString *makePSFontName(GfxFont *font, Ref *id); void setupImages(Dict *resDict); void setupImage(Ref id, Stream *str, GBool mask); void setupForms(Dict *resDict); - void setupForm(Ref id, Object *strObj); + void setupForm(Object *strRef, Object *strObj); void addProcessColor(double c, double m, double y, double k); void addCustomColor(GfxSeparationColorSpace *sepCS); void doPath(GfxPath *path); @@ -358,19 +360,8 @@ private: PDFDoc *doc; XRef *xref; // the xref table for this PDF file - Ref *fontIDs; // list of object IDs of all used fonts - int fontIDLen; // number of entries in fontIDs array - int fontIDSize; // size of fontIDs array - GHash *fontNames; // all used font names - PST1FontName *t1FontNames; // font names for Type 1/1C fonts - int t1FontNameLen; // number of entries in t1FontNames array - int t1FontNameSize; // size of t1FontNames array - PSFont8Info *font8Info; // info for 8-bit fonts - int font8InfoLen; // number of entries in font8Info array - int font8InfoSize; // size of font8Info array - PSFont16Enc *font16Enc; // encodings for substitute 16-bit fonts - int font16EncLen; // number of entries in font16Enc array - int font16EncSize; // size of font16Enc array + GList *fontInfo; // info for each font [PSFontInfo] + GHash *fontFileInfo; // info for each font file [PSFontFileInfo] Ref *imgIDs; // list of image IDs for in-memory images int imgIDLen; // number of entries in imgIDs array int imgIDSize; // size of imgIDs array diff -uNrp xpdf-3.03/xpdf/SecurityHandler.cc xpdf-3.04/xpdf/SecurityHandler.cc --- xpdf-3.03/xpdf/SecurityHandler.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/SecurityHandler.cc 2014-05-28 20:50:50.000000000 +0200 @@ -158,7 +158,7 @@ StandardSecurityHandler::StandardSecurit if ((encRevision <= 4 && ownerKeyObj.getString()->getLength() == 32 && userKeyObj.getString()->getLength() == 32) || - (encRevision == 5 && + ((encRevision == 5 || encRevision == 6) && // the spec says 48 bytes, but Acrobat pads them out longer ownerKeyObj.getString()->getLength() >= 48 && userKeyObj.getString()->getLength() >= 48 && @@ -180,7 +180,7 @@ StandardSecurityHandler::StandardSecurit //~ doesn't handle the case where StmF, StrF, and EFF are not all the //~ same) if ((encVersion == 4 || encVersion == 5) && - (encRevision == 4 || encRevision == 5)) { + (encRevision == 4 || encRevision == 5 || encRevision == 6)) { encryptDictA->dictLookup("CF", &cryptFiltersObj); encryptDictA->dictLookup("StmF", &streamFilterObj); encryptDictA->dictLookup("StrF", &stringFilterObj); @@ -216,7 +216,9 @@ StandardSecurityHandler::StandardSecurit cfLengthObj.free(); } else if (cfmObj.isName("AESV3")) { encVersion = 5; - encRevision = 5; + if (encRevision != 5 && encRevision != 6) { + encRevision = 6; + } encAlgorithm = cryptAES256; if (cryptFilterObj.dictLookup("Length", &cfLengthObj)->isInt()) { @@ -254,15 +256,15 @@ StandardSecurityHandler::StandardSecurit } else { fileID = new GString(); } - if (fileKeyLength > 16 || fileKeyLength < 0) { + if (fileKeyLength > 16 || fileKeyLength <= 0) { fileKeyLength = 16; } ok = gTrue; - } else if (encVersion == 5 && encRevision == 5) { + } else if (encVersion == 5 && (encRevision == 5 || encRevision == 6)) { fileID = new GString(); // unused for V=R=5 ownerEnc = ownerEncObj.getString()->copy(); userEnc = userEncObj.getString()->copy(); - if (fileKeyLength > 32 || fileKeyLength < 0) { + if (fileKeyLength > 32 || fileKeyLength <= 0) { fileKeyLength = 32; } ok = gTrue; diff -uNrp xpdf-3.03/xpdf/SplashOutputDev.cc xpdf-3.04/xpdf/SplashOutputDev.cc --- xpdf-3.03/xpdf/SplashOutputDev.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/SplashOutputDev.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // SplashOutputDev.cc // -// Copyright 2003 Glyph & Cog, LLC +// Copyright 2003-2013 Glyph & Cog, LLC // //======================================================================== @@ -27,6 +27,7 @@ #include "BuiltinFont.h" #include "BuiltinFontTables.h" #include "FoFiTrueType.h" +#include "JPXStream.h" #include "SplashBitmap.h" #include "SplashGlyphBitmap.h" #include "SplashPattern.h" @@ -118,7 +119,9 @@ static void splashOutBlendColorDodge(Spl int i, x; for (i = 0; i < splashColorModeNComps[cm]; ++i) { - if (src[i] == 255) { + if (dest[i] == 0) { + blend[i] = 0; + } else if (src[i] == 255) { blend[i] = 255; } else { x = (dest[i] * 255) / (255 - src[i]); @@ -132,7 +135,9 @@ static void splashOutBlendColorBurn(Spla int i, x; for (i = 0; i < splashColorModeNComps[cm]; ++i) { - if (src[i] == 0) { + if (dest[i] == 255) { + blend[i] = 255; + } else if (src[i] == 0) { blend[i] = 0; } else { x = ((255 - dest[i]) * 255) / src[i]; @@ -284,7 +289,10 @@ static void setSat(Guchar rIn, Guchar gI static void splashOutBlendHue(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { - Guchar r0, g0, b0, r1, g1, b1; + Guchar r0, g0, b0; +#if SPLASH_CMYK + Guchar r1, g1, b1; +#endif switch (cm) { case splashModeMono1: @@ -317,7 +325,10 @@ static void splashOutBlendHue(SplashColo static void splashOutBlendSaturation(SplashColorPtr src, SplashColorPtr dest, SplashColorPtr blend, SplashColorMode cm) { - Guchar r0, g0, b0, r1, g1, b1; + Guchar r0, g0, b0; +#if SPLASH_CMYK + Guchar r1, g1, b1; +#endif switch (cm) { case splashModeMono1: @@ -435,7 +446,11 @@ SplashBlendFunc splashOutBlendFuncs[] = class SplashOutFontFileID: public SplashFontFileID { public: - SplashOutFontFileID(Ref *rA) { r = *rA; substIdx = -1; } + SplashOutFontFileID(Ref *rA) { + r = *rA; + substIdx = -1; + oblique = 0; + } ~SplashOutFontFileID() {} @@ -444,12 +459,15 @@ public: ((SplashOutFontFileID *)id)->r.gen == r.gen; } + void setOblique(double obliqueA) { oblique = obliqueA; } + double getOblique() { return oblique; } void setSubstIdx(int substIdxA) { substIdx = substIdxA; } int getSubstIdx() { return substIdx; } private: Ref r; + double oblique; int substIdx; }; @@ -536,6 +554,10 @@ T3FontCache::~T3FontCache() { struct T3GlyphStack { Gushort code; // character code + GBool haveDx; // set after seeing a d0/d1 operator + GBool doNotCache; // set if we see a gsave/grestore before + // the d0/d1 + //----- cache info T3FontCache *cache; // font cache for the current font T3FontCacheTag *cacheTag; // pointer to cache tag for the glyph @@ -580,6 +602,7 @@ SplashOutputDev::SplashOutputDev(SplashC bitmapRowPad = bitmapRowPadA; bitmapTopDown = bitmapTopDownA; bitmapUpsideDown = gFalse; + noComposite = gFalse; allowAntialias = allowAntialiasA; vectorAntialias = allowAntialias && globalParams->getVectorAntialias() && @@ -596,6 +619,7 @@ SplashOutputDev::SplashOutputDev(SplashC colorMode != splashModeMono1, bitmapTopDown); splash = new Splash(bitmap, vectorAntialias, &screenParams); splash->setMinLineWidth(globalParams->getMinLineWidth()); + splash->setStrokeAdjust(globalParams->getStrokeAdjust()); splash->clear(paperColor, 0); fontEngine = NULL; @@ -688,9 +712,6 @@ void SplashOutputDev::startDoc(XRef *xre delete fontEngine; } fontEngine = new SplashFontEngine( -#if HAVE_T1LIB_H - globalParams->getEnableT1lib(), -#endif #if HAVE_FREETYPE_FREETYPE_H || HAVE_FREETYPE_H globalParams->getEnableFreeType(), globalParams->getDisableFreeTypeHinting() @@ -777,18 +798,28 @@ void SplashOutputDev::startPage(int page } void SplashOutputDev::endPage() { - if (colorMode != splashModeMono1) { + if (colorMode != splashModeMono1 && !noComposite) { splash->compositeBackground(paperColor); } } void SplashOutputDev::saveState(GfxState *state) { splash->saveState(); + if (t3GlyphStack && !t3GlyphStack->haveDx) { + t3GlyphStack->doNotCache = gTrue; + error(errSyntaxWarning, -1, + "Save (q) operator before d0/d1 in Type 3 glyph"); + } } void SplashOutputDev::restoreState(GfxState *state) { splash->restoreState(); needFontUpdate = gTrue; + if (t3GlyphStack && !t3GlyphStack->haveDx) { + t3GlyphStack->doNotCache = gTrue; + error(errSyntaxWarning, -1, + "Restore (Q) operator before d0/d1 in Type 3 glyph"); + } } void SplashOutputDev::updateAll(GfxState *state) { @@ -976,10 +1007,19 @@ void SplashOutputDev::setOverprintMask(G if (overprintFlag && globalParams->getOverprintPreview()) { mask = colorSpace->getOverprintMask(); + // The OPM (overprintMode) setting is only relevant when the color + // space is DeviceCMYK or is "implicitly converted to DeviceCMYK". + // Per the PDF spec, this happens with ICCBased color spaces only + // if the profile matches the output device -- Acrobat's output + // preview mode does NOT honor OPM=1 for ICCBased CMYK color + // spaces. To change the behavior here, use: + // if (singleColor && overprintMode && + // (colorSpace->getMode() == csDeviceCMYK || + // (colorSpace->getMode() == csICCBased && + // colorSpace->getNComps() == 4 && + // <...the profile matches...>))) if (singleColor && overprintMode && - (colorSpace->getMode() == csDeviceCMYK || - (colorSpace->getMode() == csICCBased && - colorSpace->getNComps() == 4))) { + colorSpace->getMode() == csDeviceCMYK) { colorSpace->getCMYK(singleColor, &cmyk); if (cmyk.c == 0) { mask &= ~1; @@ -1072,22 +1112,33 @@ void SplashOutputDev::doUpdateFont(GfxSt FoFiTrueType *ff; Ref embRef; Object refObj, strObj; +#if LOAD_FONTS_FROM_MEM + GString *fontBuf; + FILE *extFontFile; +#else GString *tmpFileName, *fileName; FILE *tmpFile; +#endif + char blk[4096]; int *codeToGID; CharCodeToUnicode *ctu; double *textMat; - double m11, m12, m21, m22, fontSize; - double w, fontScaleMin, fontScaleAvg, fontScale; + double m11, m12, m21, m22, fontSize, oblique; + double fsx, fsy, w, fontScaleMin, fontScaleAvg, fontScale; Gushort ww; SplashCoord mat[4]; char *name; Unicode uBuf[8]; - int c, substIdx, n, code, cmap, i; + int substIdx, n, code, cmap, i; needFontUpdate = gFalse; font = NULL; +#if LOAD_FONTS_FROM_MEM + fontBuf = NULL; +#else tmpFileName = NULL; + fileName = NULL; +#endif substIdx = -1; if (!(gfxFont = state->getFont())) { @@ -1098,10 +1149,13 @@ void SplashOutputDev::doUpdateFont(GfxSt goto err1; } - // sanity-check the font size - skip anything larger than 10 inches + // sanity-check the font size - skip anything larger than 20 inches // (this avoids problems allocating memory for the font cache) - if (state->getTransformedFontSize() - > 10 * (state->getHDPI() + state->getVDPI())) { + 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()) { goto err1; } @@ -1112,7 +1166,6 @@ void SplashOutputDev::doUpdateFont(GfxSt } else { - fileName = NULL; fontNum = 0; if (!(fontLoc = gfxFont->locateFont(xref, gFalse))) { @@ -1125,6 +1178,24 @@ void SplashOutputDev::doUpdateFont(GfxSt // embedded font if (fontLoc->locType == gfxFontLocEmbedded) { gfxFont->getEmbeddedFontID(&embRef); +#if LOAD_FONTS_FROM_MEM + fontBuf = new GString(); + refObj.initRef(embRef.num, embRef.gen); + refObj.fetch(xref, &strObj); + refObj.free(); + if (!strObj.isStream()) { + error(errSyntaxError, -1, "Embedded font object is wrong type"); + strObj.free(); + delete fontLoc; + goto err2; + } + strObj.streamReset(); + while ((n = strObj.streamGetBlock(blk, sizeof(blk))) > 0) { + fontBuf->append(blk, n); + } + strObj.streamClose(); + strObj.free(); +#else if (!openTempFile(&tmpFileName, &tmpFile, "wb", NULL)) { error(errIO, -1, "Couldn't create temporary font file"); delete fontLoc; @@ -1141,21 +1212,39 @@ void SplashOutputDev::doUpdateFont(GfxSt goto err2; } strObj.streamReset(); - while ((c = strObj.streamGetChar()) != EOF) { - fputc(c, tmpFile); + while ((n = strObj.streamGetBlock(blk, sizeof(blk))) > 0) { + fwrite(blk, 1, n, tmpFile); } strObj.streamClose(); strObj.free(); fclose(tmpFile); fileName = tmpFileName; +#endif // external font } else { // gfxFontLocExternal +#if LOAD_FONTS_FROM_MEM + if (!(extFontFile = fopen(fontLoc->path->getCString(), "rb"))) { + error(errSyntaxError, -1, "Couldn't open external font file '{0:t}'", + fontLoc->path); + delete fontLoc; + goto err2; + } + fontBuf = new GString(); + while ((n = fread(blk, 1, sizeof(blk), extFontFile)) > 0) { + fontBuf->append(blk, n); + } + fclose(extFontFile); +#else fileName = fontLoc->path; +#endif fontNum = fontLoc->fontNum; if (fontLoc->substIdx >= 0) { id->setSubstIdx(fontLoc->substIdx); } + if (fontLoc->oblique != 0) { + id->setOblique(fontLoc->oblique); + } } // load the font file @@ -1163,8 +1252,12 @@ void SplashOutputDev::doUpdateFont(GfxSt case fontType1: if (!(fontFile = fontEngine->loadType1Font( id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else fileName->getCString(), fileName == tmpFileName, +#endif (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->getCString() @@ -1176,8 +1269,12 @@ void SplashOutputDev::doUpdateFont(GfxSt case fontType1C: if (!(fontFile = fontEngine->loadType1CFont( id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else fileName->getCString(), fileName == tmpFileName, +#endif (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->getCString() @@ -1189,8 +1286,12 @@ void SplashOutputDev::doUpdateFont(GfxSt case fontType1COT: if (!(fontFile = fontEngine->loadOpenTypeT1CFont( id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else fileName->getCString(), fileName == tmpFileName, +#endif (const char **)((Gfx8BitFont *)gfxFont)->getEncoding()))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->getCString() @@ -1201,7 +1302,12 @@ void SplashOutputDev::doUpdateFont(GfxSt break; case fontTrueType: case fontTrueTypeOT: - if ((ff = FoFiTrueType::load(fileName->getCString()))) { +#if LOAD_FONTS_FROM_MEM + if ((ff = FoFiTrueType::make(fontBuf->getCString(), fontBuf->getLength(), + fontNum))) { +#else + if ((ff = FoFiTrueType::load(fileName->getCString(), fontNum))) { +#endif codeToGID = ((Gfx8BitFont *)gfxFont)->getCodeToGIDMap(ff); n = 256; delete ff; @@ -1222,9 +1328,13 @@ void SplashOutputDev::doUpdateFont(GfxSt } if (!(fontFile = fontEngine->loadTrueTypeFont( id, - fileName->getCString(), fontNum, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName->getCString(), fileName == tmpFileName, - codeToGID, n, +#endif + fontNum, codeToGID, n, gfxFont->getEmbeddedFontName() ? gfxFont->getEmbeddedFontName()->getCString() : (char *)NULL))) { @@ -1239,8 +1349,14 @@ void SplashOutputDev::doUpdateFont(GfxSt case fontCIDType0C: if (!(fontFile = fontEngine->loadCIDFont( id, +#if LOAD_FONTS_FROM_MEM + fontBuf +#else fileName->getCString(), - fileName == tmpFileName))) { + fileName == tmpFileName +#endif + ))) { + error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->getCString() : "(unnamed)"); @@ -1260,8 +1376,12 @@ void SplashOutputDev::doUpdateFont(GfxSt } if (!(fontFile = fontEngine->loadOpenTypeCFFFont( id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else fileName->getCString(), fileName == tmpFileName, +#endif codeToGID, n))) { error(errSyntaxError, -1, "Couldn't create a font for '{0:s}'", gfxFont->getName() ? gfxFont->getName()->getCString() @@ -1281,11 +1401,18 @@ void SplashOutputDev::doUpdateFont(GfxSt memcpy(codeToGID, ((GfxCIDFont *)gfxFont)->getCIDToGID(), n * sizeof(int)); } + } else if (!globalParams->getMapExtTrueTypeFontsViaUnicode()) { + codeToGID = NULL; + n = 0; } else { // create a CID-to-GID mapping, via Unicode if ((ctu = ((GfxCIDFont *)gfxFont)->getToUnicode())) { - //~ this should use fontNum to load the correct font - if ((ff = FoFiTrueType::load(fileName->getCString()))) { +#if LOAD_FONTS_FROM_MEM + if ((ff = FoFiTrueType::make(fontBuf->getCString(), + fontBuf->getLength(), fontNum))) { +#else + if ((ff = FoFiTrueType::load(fileName->getCString(), fontNum))) { +#endif // look for a Unicode cmap for (cmap = 0; cmap < ff->getNumCmaps(); ++cmap) { if ((ff->getCmapPlatform(cmap) == 3 && @@ -1296,7 +1423,11 @@ void SplashOutputDev::doUpdateFont(GfxSt } if (cmap < ff->getNumCmaps()) { // map CID -> Unicode -> GID - n = ctu->getLength(); + if (ctu->isIdentity()) { + n = 65536; + } else { + n = ctu->getLength(); + } codeToGID = (int *)gmallocn(n, sizeof(int)); for (code = 0; code < n; ++code) { if (ctu->mapToUnicode(code, uBuf, 8) > 0) { @@ -1318,9 +1449,13 @@ void SplashOutputDev::doUpdateFont(GfxSt } if (!(fontFile = fontEngine->loadTrueTypeFont( id, - fileName->getCString(), fontNum, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fileName->getCString(), fileName == tmpFileName, - codeToGID, n, +#endif + fontNum, codeToGID, n, gfxFont->getEmbeddedFontName() ? gfxFont->getEmbeddedFontName()->getCString() : (char *)NULL))) { @@ -1342,10 +1477,15 @@ void SplashOutputDev::doUpdateFont(GfxSt // get the font matrix textMat = state->getTextMat(); fontSize = state->getFontSize(); - m11 = textMat[0] * fontSize * state->getHorizScaling(); - m12 = textMat[1] * fontSize * state->getHorizScaling(); - m21 = textMat[2] * fontSize; - m22 = textMat[3] * fontSize; + oblique = ((SplashOutFontFileID *)fontFile->getID())->getOblique(); + m11 = state->getHorizScaling() * textMat[0]; + m12 = state->getHorizScaling() * textMat[1]; + m21 = oblique * m11 + textMat[2]; + m22 = oblique * m12 + textMat[3]; + m11 *= fontSize; + m12 *= fontSize; + m21 *= fontSize; + m22 *= fontSize; // for substituted fonts: adjust the font matrix -- compare the // widths of letters and digits (A-Z, a-z, 0-9) in the original font @@ -1393,18 +1533,26 @@ void SplashOutputDev::doUpdateFont(GfxSt mat[2] = m21; mat[3] = m22; font = fontEngine->getFont(fontFile, mat, splash->getMatrix()); +#if !LOAD_FONTS_FROM_MEM if (tmpFileName) { delete tmpFileName; } +#endif return; err2: delete id; err1: +#if LOAD_FONTS_FROM_MEM + if (fontBuf) { + delete fontBuf; + } +#else if (tmpFileName) { unlink(tmpFileName->getCString()); delete tmpFileName; } +#endif return; } @@ -1447,7 +1595,8 @@ void SplashOutputDev::eoFill(GfxState *s delete path; } -void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, +void SplashOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, + Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -1529,7 +1678,7 @@ void SplashOutputDev::tilingPatternFill( ya = y * yStep; mat1[4] = xa * mat[0] + ya * mat[2] + mat[4]; mat1[5] = xa * mat[1] + ya * mat[3] + mat[5]; - gfx->drawForm(str, resDict, mat1, bbox); + gfx->drawForm(strRef, resDict, mat1, bbox); } } return; @@ -1542,6 +1691,7 @@ void SplashOutputDev::tilingPatternFill( colorMode, gTrue, bitmapTopDown); splash = new Splash(bitmap, vectorAntialias, origSplash->getScreen()); splash->setMinLineWidth(globalParams->getMinLineWidth()); + splash->setStrokeAdjust(globalParams->getStrokeAdjust()); for (i = 0; i < splashMaxColorComps; ++i) { color[i] = 0; } @@ -1556,7 +1706,7 @@ void SplashOutputDev::tilingPatternFill( // render the tile state->shiftCTM(-tileX0, -tileY0); updateCTM(state, 0, 0, 0, 0, 0, 0); - gfx->drawForm(str, resDict, mat, bbox); + gfx->drawForm(strRef, resDict, mat, bbox); state->shiftCTM(tileX0, tileY0); updateCTM(state, 0, 0, 0, 0, 0, 0); @@ -1889,8 +2039,8 @@ GBool SplashOutputDev::beginType3Char(Gf t3GlyphStack->cache = t3Font; t3GlyphStack->cacheTag = NULL; t3GlyphStack->cacheData = NULL; - - haveT3Dx = gFalse; + t3GlyphStack->haveDx = gFalse; + t3GlyphStack->doNotCache = gFalse; return gFalse; } @@ -1920,7 +2070,7 @@ void SplashOutputDev::endType3Char(GfxSt } void SplashOutputDev::type3D0(GfxState *state, double wx, double wy) { - haveT3Dx = gTrue; + t3GlyphStack->haveDx = gTrue; } void SplashOutputDev::type3D1(GfxState *state, double wx, double wy, @@ -1932,10 +2082,14 @@ void SplashOutputDev::type3D1(GfxState * int i, j; // ignore multiple d0/d1 operators - if (haveT3Dx) { + if (t3GlyphStack->haveDx) { + return; + } + t3GlyphStack->haveDx = gTrue; + // don't cache if we got a gsave/grestore before the d1 + if (t3GlyphStack->doNotCache) { return; } - haveT3Dx = gTrue; t3Font = t3GlyphStack->cache; @@ -2026,6 +2180,7 @@ void SplashOutputDev::type3D1(GfxState * color[0] = 0xff; } splash->setMinLineWidth(globalParams->getMinLineWidth()); + splash->setStrokeAdjust(t3GlyphStack->origSplash->getStrokeAdjust()); splash->setFillPattern(new SplashSolidColor(color)); splash->setStrokePattern(new SplashSolidColor(color)); //~ this should copy other state from t3GlyphStack->origSplash? @@ -2072,10 +2227,9 @@ GBool SplashOutputDev::imageMaskSrc(void SplashColorPtr q; int x; - if (imgMaskData->y == imgMaskData->height) { - return gFalse; - } - if (!(p = imgMaskData->imgStr->getLine())) { + if (imgMaskData->y == imgMaskData->height || + !(p = imgMaskData->imgStr->getLine())) { + memset(line, 0, imgMaskData->width); return gFalse; } for (x = 0, q = line; x < imgMaskData->width; ++x) { @@ -2087,7 +2241,7 @@ GBool SplashOutputDev::imageMaskSrc(void void SplashOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { + GBool inlineImg, GBool interpolate) { double *ctm; SplashCoord mat[6]; SplashOutImageMaskData imgMaskData; @@ -2106,6 +2260,8 @@ void SplashOutputDev::drawImageMask(GfxS mat[4] = ctm[2] + ctm[4]; mat[5] = ctm[3] + ctm[5]; + reduceImageResolution(str, ctm, &width, &height); + imgMaskData.imgStr = new ImageStream(str, width, 1, 1); imgMaskData.imgStr->reset(); imgMaskData.invert = invert ? 0 : 1; @@ -2114,7 +2270,7 @@ void SplashOutputDev::drawImageMask(GfxS imgMaskData.y = 0; splash->fillImageMask(&imageMaskSrc, &imgMaskData, width, height, mat, - t3GlyphStack != NULL); + t3GlyphStack != NULL, interpolate); if (inlineImg) { while (imgMaskData.y < height) { imgMaskData.imgStr->getLine(); @@ -2130,7 +2286,8 @@ void SplashOutputDev::setSoftMaskFromIma Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg) { + GBool inlineImg, + GBool interpolate) { double *ctm; SplashCoord mat[6]; SplashOutImageMaskData imgMaskData; @@ -2145,6 +2302,7 @@ void SplashOutputDev::setSoftMaskFromIma mat[3] = -ctm[3]; mat[4] = ctm[2] + ctm[4]; mat[5] = ctm[3] + ctm[5]; + reduceImageResolution(str, ctm, &width, &height); imgMaskData.imgStr = new ImageStream(str, width, 1, 1); imgMaskData.imgStr->reset(); imgMaskData.invert = invert ? 0 : 1; @@ -2154,12 +2312,12 @@ void SplashOutputDev::setSoftMaskFromIma maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, gFalse); maskSplash = new Splash(maskBitmap, gTrue); - maskColor[0] = 0; - maskSplash->clear(maskColor); + maskSplash->setStrokeAdjust(globalParams->getStrokeAdjust()); + clearMaskRegion(state, maskSplash, 0, 0, 1, 1); maskColor[0] = 0xff; maskSplash->setFillPattern(new SplashSolidColor(maskColor)); maskSplash->fillImageMask(&imageMaskSrc, &imgMaskData, - width, height, mat, gFalse); + width, height, mat, gFalse, interpolate); delete imgMaskData.imgStr; str->close(); delete maskSplash; @@ -2180,17 +2338,12 @@ GBool SplashOutputDev::imageSrc(void *da SplashOutImageData *imgData = (SplashOutImageData *)data; Guchar *p; SplashColorPtr q, col; - GfxRGB rgb; - GfxGray gray; -#if SPLASH_CMYK - GfxCMYK cmyk; -#endif int nComps, x; - if (imgData->y == imgData->height) { - return gFalse; - } - if (!(p = imgData->imgStr->getLine())) { + if (imgData->y == imgData->height || + !(p = imgData->imgStr->getLine())) { + memset(colorLine, 0, + imgData->width * splashColorModeNComps[imgData->colorMode]); return gFalse; } @@ -2229,29 +2382,15 @@ GBool SplashOutputDev::imageSrc(void *da switch (imgData->colorMode) { case splashModeMono1: case splashModeMono8: - for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) { - imgData->colorMap->getGray(p, &gray); - *q++ = colToByte(gray); - } + imgData->colorMap->getGrayByteLine(p, colorLine, imgData->width); break; case splashModeRGB8: case splashModeBGR8: - for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) { - imgData->colorMap->getRGB(p, &rgb); - *q++ = colToByte(rgb.r); - *q++ = colToByte(rgb.g); - *q++ = colToByte(rgb.b); - } + imgData->colorMap->getRGBByteLine(p, colorLine, imgData->width); break; #if SPLASH_CMYK case splashModeCMYK8: - for (x = 0, q = colorLine; x < imgData->width; ++x, p += nComps) { - imgData->colorMap->getCMYK(p, &cmyk); - *q++ = colToByte(cmyk.c); - *q++ = colToByte(cmyk.m); - *q++ = colToByte(cmyk.y); - *q++ = colToByte(cmyk.k); - } + imgData->colorMap->getCMYKByteLine(p, colorLine, imgData->width); break; #endif } @@ -2274,10 +2413,11 @@ GBool SplashOutputDev::alphaImageSrc(voi Guchar alpha; int nComps, x, i; - if (imgData->y == imgData->height) { - return gFalse; - } - if (!(p = imgData->imgStr->getLine())) { + if (imgData->y == imgData->height || + !(p = imgData->imgStr->getLine())) { + memset(colorLine, 0, + imgData->width * splashColorModeNComps[imgData->colorMode]); + memset(alphaLine, 0, imgData->width); return gFalse; } @@ -2353,7 +2493,8 @@ GBool SplashOutputDev::alphaImageSrc(voi void SplashOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg) { + int *maskColors, GBool inlineImg, + GBool interpolate) { double *ctm; SplashCoord mat[6]; SplashOutImageData imgData; @@ -2378,6 +2519,8 @@ void SplashOutputDev::drawImage(GfxState mat[4] = ctm[2] + ctm[4]; mat[5] = ctm[3] + ctm[5]; + reduceImageResolution(str, ctm, &width, &height); + imgData.imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), colorMap->getBits()); @@ -2433,12 +2576,14 @@ void SplashOutputDev::drawImage(GfxState if (colorMode == splashModeMono1) { srcMode = splashModeMono8; + } else if (colorMode == splashModeBGR8) { + srcMode = splashModeRGB8; } else { srcMode = colorMode; } src = maskColors ? &alphaImageSrc : &imageSrc; splash->drawImage(src, &imgData, srcMode, maskColors ? gTrue : gFalse, - width, height, mat); + width, height, mat, interpolate); if (inlineImg) { while (imgData.y < height) { imgData.imgStr->getLine(); @@ -2470,15 +2615,17 @@ GBool SplashOutputDev::maskedImageSrc(vo #if SPLASH_CMYK GfxCMYK cmyk; #endif + static Guchar bitToByte[2] = {0x00, 0xff}; Guchar alpha; Guchar *maskPtr; - int maskBit; + int maskShift; int nComps, x; - if (imgData->y == imgData->height) { - return gFalse; - } - if (!(p = imgData->imgStr->getLine())) { + if (imgData->y == imgData->height || + !(p = imgData->imgStr->getLine())) { + memset(colorLine, 0, + imgData->width * splashColorModeNComps[imgData->colorMode]); + memset(alphaLine, 0, imgData->width); return gFalse; } @@ -2486,15 +2633,13 @@ GBool SplashOutputDev::maskedImageSrc(vo maskPtr = imgData->mask->getDataPtr() + imgData->y * imgData->mask->getRowSize(); - maskBit = 0x80; + maskShift = 7; for (x = 0, q = colorLine, aq = alphaLine; x < imgData->width; ++x, p += nComps) { - alpha = (*maskPtr & maskBit) ? 0xff : 0x00; - if (!(maskBit >>= 1)) { - ++maskPtr; - maskBit = 0x80; - } + alpha = bitToByte[(*maskPtr >> maskShift) & 1]; + maskPtr += (8 - maskShift) >> 3; + maskShift = (maskShift - 1) & 7; if (imgData->lookup) { switch (imgData->colorMode) { case splashModeMono1: @@ -2555,7 +2700,8 @@ void SplashOutputDev::drawMaskedImage(Gf Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, - int maskHeight, GBool maskInvert) { + int maskHeight, GBool maskInvert, + GBool interpolate) { GfxImageColorMap *maskColorMap; Object maskDecode, decodeLow, decodeHigh; double *ctm; @@ -2577,6 +2723,10 @@ void SplashOutputDev::drawMaskedImage(Gf setOverprintMask(colorMap->getColorSpace(), state->getFillOverprint(), state->getOverprintMode(), NULL); + ctm = state->getCTM(); + reduceImageResolution(str, ctm, &width, &height); + reduceImageResolution(maskStr, ctm, &maskWidth, &maskHeight); + // If the mask is higher resolution than the image, use // drawSoftMaskedImage() instead. if (maskWidth > width || maskHeight > height) { @@ -2589,7 +2739,8 @@ void SplashOutputDev::drawMaskedImage(Gf new GfxDeviceGrayColorSpace()); maskDecode.free(); drawSoftMaskedImage(state, ref, str, width, height, colorMap, - maskStr, maskWidth, maskHeight, maskColorMap); + maskStr, maskWidth, maskHeight, maskColorMap, + interpolate); delete maskColorMap; } else { @@ -2610,19 +2761,20 @@ void SplashOutputDev::drawMaskedImage(Gf imgMaskData.y = 0; maskBitmap = new SplashBitmap(width, height, 1, splashModeMono1, gFalse); maskSplash = new Splash(maskBitmap, gFalse); + maskSplash->setStrokeAdjust(globalParams->getStrokeAdjust()); maskColor[0] = 0; maskSplash->clear(maskColor); maskColor[0] = 0xff; maskSplash->setFillPattern(new SplashSolidColor(maskColor)); + // use "glyph mode" here to get the correct scaled size maskSplash->fillImageMask(&imageMaskSrc, &imgMaskData, - maskWidth, maskHeight, mat, gFalse); + maskWidth, maskHeight, mat, gTrue, interpolate); delete imgMaskData.imgStr; maskStr->close(); delete maskSplash; //----- draw the source image - ctm = state->getCTM(); mat[0] = ctm[0]; mat[1] = ctm[1]; mat[2] = -ctm[2]; @@ -2685,11 +2837,13 @@ void SplashOutputDev::drawMaskedImage(Gf if (colorMode == splashModeMono1) { srcMode = splashModeMono8; + } else if (colorMode == splashModeBGR8) { + srcMode = splashModeRGB8; } else { srcMode = colorMode; } splash->drawImage(&maskedImageSrc, &imgData, srcMode, gTrue, - width, height, mat); + width, height, mat, interpolate); delete maskBitmap; gfree(imgData.lookup); @@ -2703,7 +2857,8 @@ void SplashOutputDev::drawSoftMaskedImag GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap) { + GfxImageColorMap *maskColorMap, + GBool interpolate) { double *ctm; SplashCoord mat[6]; SplashOutImageData imgData; @@ -2711,7 +2866,6 @@ void SplashOutputDev::drawSoftMaskedImag SplashColorMode srcMode; SplashBitmap *maskBitmap; Splash *maskSplash; - SplashColor maskColor; GfxGray gray; GfxRGB rgb; #if SPLASH_CMYK @@ -2731,6 +2885,9 @@ void SplashOutputDev::drawSoftMaskedImag mat[4] = ctm[2] + ctm[4]; mat[5] = ctm[3] + ctm[5]; + reduceImageResolution(str, ctm, &width, &height); + reduceImageResolution(maskStr, ctm, &maskWidth, &maskHeight); + //----- set up the soft mask imgMaskData.imgStr = new ImageStream(maskStr, maskWidth, @@ -2753,10 +2910,10 @@ void SplashOutputDev::drawSoftMaskedImag maskBitmap = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, gFalse); maskSplash = new Splash(maskBitmap, vectorAntialias); - maskColor[0] = 0; - maskSplash->clear(maskColor); + maskSplash->setStrokeAdjust(globalParams->getStrokeAdjust()); + clearMaskRegion(state, maskSplash, 0, 0, 1, 1); maskSplash->drawImage(&imageSrc, &imgMaskData, splashModeMono8, gFalse, - maskWidth, maskHeight, mat); + maskWidth, maskHeight, mat, interpolate); delete imgMaskData.imgStr; maskStr->close(); gfree(imgMaskData.lookup); @@ -2820,10 +2977,13 @@ void SplashOutputDev::drawSoftMaskedImag if (colorMode == splashModeMono1) { srcMode = splashModeMono8; + } else if (colorMode == splashModeBGR8) { + srcMode = splashModeRGB8; } else { srcMode = colorMode; } - splash->drawImage(&imageSrc, &imgData, srcMode, gFalse, width, height, mat); + splash->drawImage(&imageSrc, &imgData, srcMode, gFalse, width, height, mat, + interpolate); splash->setSoftMask(NULL); gfree(imgData.lookup); @@ -2831,6 +2991,98 @@ void SplashOutputDev::drawSoftMaskedImag str->close(); } +void SplashOutputDev::reduceImageResolution(Stream *str, double *ctm, + int *width, int *height) { + double sw, sh; + int reduction; + + if (str->getKind() == strJPX && + *width * *height > 10000000) { + sw = (double)*width / (fabs(ctm[2]) + fabs(ctm[3])); + sh = (double)*height / (fabs(ctm[0]) + fabs(ctm[1])); + if (sw > 8 && sh > 8) { + reduction = 3; + } else if (sw > 4 && sh > 4) { + reduction = 2; + } else if (sw > 2 && sh > 2) { + reduction = 1; + } else { + reduction = 0; + } + if (reduction > 0) { + ((JPXStream *)str)->reduceResolution(reduction); + *width >>= reduction; + *height >>= reduction; + } + } +} + +void SplashOutputDev::clearMaskRegion(GfxState *state, + Splash *maskSplash, + double xMin, double yMin, + double xMax, double yMax) { + SplashBitmap *maskBitmap; + double xxMin, yyMin, xxMax, yyMax, xx, yy; + int xxMinI, yyMinI, xxMaxI, yyMaxI, y, n; + Guchar *p; + + maskBitmap = maskSplash->getBitmap(); + xxMin = maskBitmap->getWidth(); + xxMax = 0; + yyMin = maskBitmap->getHeight(); + yyMax = 0; + state->transform(xMin, yMin, &xx, &yy); + if (xx < xxMin) { xxMin = xx; } + if (xx > xxMax) { xxMax = xx; } + if (yy < yyMin) { yyMin = yy; } + if (yy > yyMax) { yyMax = yy; } + state->transform(xMin, yMax, &xx, &yy); + if (xx < xxMin) { xxMin = xx; } + if (xx > xxMax) { xxMax = xx; } + if (yy < yyMin) { yyMin = yy; } + if (yy > yyMax) { yyMax = yy; } + state->transform(xMax, yMin, &xx, &yy); + if (xx < xxMin) { xxMin = xx; } + if (xx > xxMax) { xxMax = xx; } + if (yy < yyMin) { yyMin = yy; } + if (yy > yyMax) { yyMax = yy; } + state->transform(xMax, yMax, &xx, &yy); + if (xx < xxMin) { xxMin = xx; } + if (xx > xxMax) { xxMax = xx; } + if (yy < yyMin) { yyMin = yy; } + if (yy > yyMax) { yyMax = yy; } + xxMinI = (int)floor(xxMin); + if (xxMinI < 0) { + xxMinI = 0; + } + xxMaxI = (int)ceil(xxMax); + if (xxMaxI > maskBitmap->getWidth()) { + xxMaxI = maskBitmap->getWidth(); + } + yyMinI = (int)floor(yyMin); + if (yyMinI < 0) { + yyMinI = 0; + } + yyMaxI = (int)ceil(yyMax); + if (yyMaxI > maskBitmap->getHeight()) { + yyMaxI = maskBitmap->getHeight(); + } + p = maskBitmap->getDataPtr() + yyMinI * maskBitmap->getRowSize(); + if (maskBitmap->getMode() == splashModeMono1) { + n = (xxMaxI + 7) / 8 - xxMinI / 8; + p += xxMinI / 8; + } else { + n = xxMaxI - xxMinI; + p += xxMinI; + } + if (xxMaxI > xxMinI) { + for (y = yyMinI; y < yyMaxI; ++y) { + memset(p, 0, n); + p += maskBitmap->getRowSize(); + } + } +} + void SplashOutputDev::beginTransparencyGroup(GfxState *state, double *bbox, GfxColorSpace *blendingColorSpace, GBool isolated, GBool knockout, @@ -2921,7 +3173,7 @@ void SplashOutputDev::beginTransparencyG //~ not yet for transparency groups // switch to the blending color space - if (forSoftMask && isolated && blendingColorSpace) { + if (forSoftMask && isolated && !knockout && blendingColorSpace) { if (blendingColorSpace->getMode() == csDeviceGray || blendingColorSpace->getMode() == csCalGray || (blendingColorSpace->getMode() == csICCBased && @@ -2948,6 +3200,7 @@ void SplashOutputDev::beginTransparencyG splash = new Splash(bitmap, vectorAntialias, transpGroup->origSplash->getScreen()); splash->setMinLineWidth(globalParams->getMinLineWidth()); + splash->setStrokeAdjust(globalParams->getStrokeAdjust()); //~ Acrobat apparently copies at least the fill and stroke colors, and //~ maybe other state(?) -- but not the clipping path (and not sure //~ what else) @@ -2962,8 +3215,9 @@ void SplashOutputDev::beginTransparencyG splash->clear(color, 0); } else { splash->blitTransparent(transpGroup->origBitmap, tx, ty, 0, 0, w, h); - splash->setInNonIsolatedGroup(transpGroup->origBitmap, tx, ty); } + splash->setInTransparencyGroup(transpGroup->origBitmap, tx, ty, + !isolated, knockout); transpGroup->tBitmap = bitmap; state->shiftCTM(-tx, -ty); updateCTM(state, 0, 0, 0, 0, 0, 0); @@ -3022,7 +3276,7 @@ void SplashOutputDev::setSoftMask(GfxSta #if SPLASH_CMYK GfxCMYK cmyk; #endif - double lum, lum2; + double backdrop, backdrop2, lum, lum2; int tx, ty, x, y; tx = transpGroupStack->tx; @@ -3030,11 +3284,13 @@ void SplashOutputDev::setSoftMask(GfxSta tBitmap = transpGroupStack->tBitmap; // composite with backdrop color + backdrop = 0; if (!alpha && tBitmap->getMode() != splashModeMono1) { //~ need to correctly handle the case where no blending color //~ space is given tSplash = new Splash(tBitmap, vectorAntialias, transpGroupStack->origSplash->getScreen()); + tSplash->setStrokeAdjust(globalParams->getStrokeAdjust()); if (transpGroupStack->blendingColorSpace) { switch (tBitmap->getMode()) { case splashModeMono1: @@ -3042,12 +3298,16 @@ void SplashOutputDev::setSoftMask(GfxSta break; case splashModeMono8: transpGroupStack->blendingColorSpace->getGray(backdropColor, &gray); + backdrop = colToDbl(gray); color[0] = colToByte(gray); tSplash->compositeBackground(color); break; case splashModeRGB8: case splashModeBGR8: transpGroupStack->blendingColorSpace->getRGB(backdropColor, &rgb); + backdrop = 0.3 * colToDbl(rgb.r) + + 0.59 * colToDbl(rgb.g) + + 0.11 * colToDbl(rgb.b); color[0] = colToByte(rgb.r); color[1] = colToByte(rgb.g); color[2] = colToByte(rgb.b); @@ -3056,6 +3316,13 @@ void SplashOutputDev::setSoftMask(GfxSta #if SPLASH_CMYK case splashModeCMYK8: transpGroupStack->blendingColorSpace->getCMYK(backdropColor, &cmyk); + backdrop = (1 - colToDbl(cmyk.k)) + - 0.3 * colToDbl(cmyk.c) + - 0.59 * colToDbl(cmyk.m) + - 0.11 * colToDbl(cmyk.y); + if (backdrop < 0) { + backdrop = 0; + } color[0] = colToByte(cmyk.c); color[1] = colToByte(cmyk.m); color[2] = colToByte(cmyk.y); @@ -3067,10 +3334,15 @@ void SplashOutputDev::setSoftMask(GfxSta delete tSplash; } } + if (transferFunc) { + transferFunc->transform(&backdrop, &backdrop2); + } else { + backdrop2 = backdrop; + } softMask = new SplashBitmap(bitmap->getWidth(), bitmap->getHeight(), 1, splashModeMono8, gFalse); - memset(softMask->getDataPtr(), 0, + memset(softMask->getDataPtr(), (int)(backdrop2 * 255.0 + 0.5), softMask->getRowSize() * softMask->getHeight()); if (tx < softMask->getWidth() && ty < softMask->getHeight()) { p = softMask->getDataPtr() + ty * softMask->getRowSize() + tx; @@ -3198,12 +3470,19 @@ SplashFont *SplashOutputDev::getFont(GSt Ref ref; SplashOutFontFileID *id; GfxFontLoc *fontLoc; +#if LOAD_FONTS_FROM_MEM + GString *fontBuf; + FILE *extFontFile; + char blk[4096]; + int n; +#endif SplashFontFile *fontFile; SplashFont *fontObj; FoFiTrueType *ff; int *codeToGID; Unicode u; SplashCoord textMat[4]; + SplashCoord oblique; int cmap, i; for (i = 0; i < nBuiltinFonts; ++i) { @@ -3227,11 +3506,40 @@ SplashFont *SplashOutputDev::getFont(GSt if (!(fontLoc = GfxFont::locateBase14Font(name))) { return NULL; } +#if LOAD_FONTS_FROM_MEM + fontBuf = NULL; + if (fontLoc->fontType == fontType1 || + fontLoc->fontType == fontTrueType) { + if (!(extFontFile = fopen(fontLoc->path->getCString(), "rb"))) { + delete fontLoc; + delete id; + return NULL; + } + fontBuf = new GString(); + while ((n = fread(blk, 1, sizeof(blk), extFontFile)) > 0) { + fontBuf->append(blk, n); + } + fclose(extFontFile); + } +#endif if (fontLoc->fontType == fontType1) { - fontFile = fontEngine->loadType1Font(id, fontLoc->path->getCString(), - gFalse, winAnsiEncoding); + fontFile = fontEngine->loadType1Font(id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else + fontLoc->path->getCString(), + gFalse, +#endif + winAnsiEncoding); } else if (fontLoc->fontType == fontTrueType) { - if (!(ff = FoFiTrueType::load(fontLoc->path->getCString()))) { +#if LOAD_FONTS_FROM_MEM + if (!(ff = FoFiTrueType::make(fontBuf->getCString(), + fontBuf->getLength(), + fontLoc->fontNum))) { +#else + if (!(ff = FoFiTrueType::load(fontLoc->path->getCString(), + fontLoc->fontNum))) { +#endif delete fontLoc; delete id; return NULL; @@ -3259,9 +3567,14 @@ SplashFont *SplashOutputDev::getFont(GSt } delete ff; fontFile = fontEngine->loadTrueTypeFont(id, +#if LOAD_FONTS_FROM_MEM + fontBuf, +#else fontLoc->path->getCString(), + gFalse, +#endif fontLoc->fontNum, - gFalse, codeToGID, 256, NULL); + codeToGID, 256, NULL); } else { delete fontLoc; delete id; @@ -3274,10 +3587,12 @@ SplashFont *SplashOutputDev::getFont(GSt } // create the scaled font + oblique = (SplashCoord) + ((SplashOutFontFileID *)fontFile->getID())->getOblique(); textMat[0] = (SplashCoord)textMatA[0]; textMat[1] = (SplashCoord)textMatA[1]; - textMat[2] = (SplashCoord)textMatA[2]; - textMat[3] = (SplashCoord)textMatA[3]; + textMat[2] = oblique * textMatA[0] + textMatA[2]; + textMat[3] = oblique * textMatA[1] + textMatA[3]; fontObj = fontEngine->getFont(fontFile, textMat, splash->getMatrix()); return fontObj; diff -uNrp xpdf-3.03/xpdf/SplashOutputDev.h xpdf-3.04/xpdf/SplashOutputDev.h --- xpdf-3.03/xpdf/SplashOutputDev.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/SplashOutputDev.h 2014-05-28 20:50:50.000000000 +0200 @@ -110,7 +110,7 @@ public: virtual void stroke(GfxState *state); virtual void fill(GfxState *state); virtual void eoFill(GfxState *state); - virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *str, + virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, int paintType, Dict *resDict, double *mat, double *bbox, int x0, int y0, int x1, int y1, @@ -135,25 +135,26 @@ public: //----- image drawing virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void setSoftMaskFromImageMask(GfxState *state, Object *ref, Stream *str, int width, int height, GBool invert, - GBool inlineImg); + GBool inlineImg, GBool interpolate); virtual void drawImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, - int *maskColors, GBool inlineImg); + int *maskColors, GBool inlineImg, GBool interpolate); virtual void drawMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GBool maskInvert); + GBool maskInvert, GBool interpolate); virtual void drawSoftMaskedImage(GfxState *state, Object *ref, Stream *str, int width, int height, GfxImageColorMap *colorMap, Stream *maskStr, int maskWidth, int maskHeight, - GfxImageColorMap *maskColorMap); + GfxImageColorMap *maskColorMap, + GBool interpolate); //----- Type 3 font operators virtual void type3D0(GfxState *state, double wx, double wy); @@ -194,6 +195,10 @@ public: // for Windows BMP files). void setBitmapUpsideDown(GBool f) { bitmapUpsideDown = f; } + // Setting this to true disables the final composite (with the + // opaque paper color), resulting in transparent output. + void setNoComposite(GBool f) { noComposite = f; } + // Get the Splash object. Splash *getSplash() { return splash; } @@ -245,11 +250,18 @@ private: Guchar *alphaLine); static GBool maskedImageSrc(void *data, SplashColorPtr line, Guchar *alphaLine); + void reduceImageResolution(Stream *str, double *mat, + int *width, int *height); + void clearMaskRegion(GfxState *state, + Splash *maskSplash, + double xMin, double yMin, + double xMax, double yMax); SplashColorMode colorMode; int bitmapRowPad; GBool bitmapTopDown; GBool bitmapUpsideDown; + GBool noComposite; GBool allowAntialias; GBool vectorAntialias; GBool reverseVideo; // reverse video mode @@ -268,7 +280,6 @@ private: t3FontCache[splashOutT3FontCacheSize]; int nT3Fonts; // number of valid entries in t3FontCache T3GlyphStack *t3GlyphStack; // Type 3 glyph context stack - GBool haveT3Dx; // set after seeing a d0/d1 operator SplashFont *font; // current font GBool needFontUpdate; // set when the font needs to be updated diff -uNrp xpdf-3.03/xpdf/Stream.cc xpdf-3.04/xpdf/Stream.cc --- xpdf-3.03/xpdf/Stream.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Stream.cc 2014-05-28 20:50:50.000000000 +0200 @@ -98,11 +98,29 @@ char *Stream::getLine(char *buf, int siz return buf; } +Guint Stream::discardChars(Guint n) { + char buf[4096]; + Guint count, i, j; + + count = 0; + while (count < n) { + if ((i = n - count) > sizeof(buf)) { + i = (Guint)sizeof(buf); + } + j = (Guint)getBlock(buf, (int)i); + count += j; + if (j != i) { + break; + } + } + return count; +} + GString *Stream::getPSFilter(int psLevel, const char *indent) { return new GString(); } -Stream *Stream::addFilters(Object *dict) { +Stream *Stream::addFilters(Object *dict, int recursion) { Object obj, obj2; Object params, params2; Stream *str; @@ -120,7 +138,7 @@ Stream *Stream::addFilters(Object *dict) dict->dictLookup("DP", ¶ms); } if (obj.isName()) { - str = makeFilter(obj.getName(), str, ¶ms); + str = makeFilter(obj.getName(), str, ¶ms, recursion); } else if (obj.isArray()) { for (i = 0; i < obj.arrayGetLength(); ++i) { obj.arrayGet(i, &obj2); @@ -129,7 +147,7 @@ Stream *Stream::addFilters(Object *dict) else params2.initNull(); if (obj2.isName()) { - str = makeFilter(obj2.getName(), str, ¶ms2); + str = makeFilter(obj2.getName(), str, ¶ms2, recursion); } else { error(errSyntaxError, getPos(), "Bad filter name"); str = new EOFStream(str); @@ -146,7 +164,8 @@ Stream *Stream::addFilters(Object *dict) return str; } -Stream *Stream::makeFilter(char *name, Stream *str, Object *params) { +Stream *Stream::makeFilter(char *name, Stream *str, Object *params, + int recursion) { int pred; // parameters int colors; int bits; @@ -168,23 +187,23 @@ Stream *Stream::makeFilter(char *name, S bits = 8; early = 1; if (params->isDict()) { - params->dictLookup("Predictor", &obj); + params->dictLookup("Predictor", &obj, recursion); if (obj.isInt()) pred = obj.getInt(); obj.free(); - params->dictLookup("Columns", &obj); + params->dictLookup("Columns", &obj, recursion); if (obj.isInt()) columns = obj.getInt(); obj.free(); - params->dictLookup("Colors", &obj); + params->dictLookup("Colors", &obj, recursion); if (obj.isInt()) colors = obj.getInt(); obj.free(); - params->dictLookup("BitsPerComponent", &obj); + params->dictLookup("BitsPerComponent", &obj, recursion); if (obj.isInt()) bits = obj.getInt(); obj.free(); - params->dictLookup("EarlyChange", &obj); + params->dictLookup("EarlyChange", &obj, recursion); if (obj.isInt()) early = obj.getInt(); obj.free(); @@ -201,37 +220,37 @@ Stream *Stream::makeFilter(char *name, S endOfBlock = gTrue; black = gFalse; if (params->isDict()) { - params->dictLookup("K", &obj); + params->dictLookup("K", &obj, recursion); if (obj.isInt()) { encoding = obj.getInt(); } obj.free(); - params->dictLookup("EndOfLine", &obj); + params->dictLookup("EndOfLine", &obj, recursion); if (obj.isBool()) { endOfLine = obj.getBool(); } obj.free(); - params->dictLookup("EncodedByteAlign", &obj); + params->dictLookup("EncodedByteAlign", &obj, recursion); if (obj.isBool()) { byteAlign = obj.getBool(); } obj.free(); - params->dictLookup("Columns", &obj); + params->dictLookup("Columns", &obj, recursion); if (obj.isInt()) { columns = obj.getInt(); } obj.free(); - params->dictLookup("Rows", &obj); + params->dictLookup("Rows", &obj, recursion); if (obj.isInt()) { rows = obj.getInt(); } obj.free(); - params->dictLookup("EndOfBlock", &obj); + params->dictLookup("EndOfBlock", &obj, recursion); if (obj.isBool()) { endOfBlock = obj.getBool(); } obj.free(); - params->dictLookup("BlackIs1", &obj); + params->dictLookup("BlackIs1", &obj, recursion); if (obj.isBool()) { black = obj.getBool(); } @@ -242,7 +261,7 @@ Stream *Stream::makeFilter(char *name, S } else if (!strcmp(name, "DCTDecode") || !strcmp(name, "DCT")) { colorXform = -1; if (params->isDict()) { - if (params->dictLookup("ColorTransform", &obj)->isInt()) { + if (params->dictLookup("ColorTransform", &obj, recursion)->isInt()) { colorXform = obj.getInt(); } obj.free(); @@ -254,19 +273,19 @@ Stream *Stream::makeFilter(char *name, S colors = 1; bits = 8; if (params->isDict()) { - params->dictLookup("Predictor", &obj); + params->dictLookup("Predictor", &obj, recursion); if (obj.isInt()) pred = obj.getInt(); obj.free(); - params->dictLookup("Columns", &obj); + params->dictLookup("Columns", &obj, recursion); if (obj.isInt()) columns = obj.getInt(); obj.free(); - params->dictLookup("Colors", &obj); + params->dictLookup("Colors", &obj, recursion); if (obj.isInt()) colors = obj.getInt(); obj.free(); - params->dictLookup("BitsPerComponent", &obj); + params->dictLookup("BitsPerComponent", &obj, recursion); if (obj.isInt()) bits = obj.getInt(); obj.free(); @@ -274,7 +293,7 @@ Stream *Stream::makeFilter(char *name, S str = new FlateStream(str, pred, columns, colors, bits); } else if (!strcmp(name, "JBIG2Decode")) { if (params->isDict()) { - params->dictLookup("JBIG2Globals", &globals); + params->dictLookup("JBIG2Globals", &globals, recursion); } str = new JBIG2Stream(str, &globals); globals.free(); @@ -314,7 +333,7 @@ void FilterStream::close() { str->close(); } -void FilterStream::setPos(Guint pos, int dir) { +void FilterStream::setPos(GFileOffset pos, int dir) { error(errInternal, -1, "Called setPos() on FilterStream"); } @@ -365,6 +384,10 @@ void ImageStream::reset() { str->reset(); } +void ImageStream::close() { + str->close(); +} + GBool ImageStream::getPixel(Guchar *pix) { int i; @@ -405,6 +428,10 @@ Guchar *ImageStream::getLine() { } } else if (nBits == 8) { // special case: imgLine == inputLine + } else if (nBits == 16) { + for (i = 0; i < nVals; ++i) { + imgLine[i] = (Guchar)inputLine[2*i]; + } } else { bitMask = (1 << nBits) - 1; buf = 0; @@ -451,8 +478,8 @@ StreamPredictor::StreamPredictor(Stream return; } predLine = (Guchar *)gmalloc(rowBytes); - memset(predLine, 0, rowBytes); - predIdx = rowBytes; + + reset(); ok = gTrue; } @@ -461,6 +488,11 @@ StreamPredictor::~StreamPredictor() { gfree(predLine); } +void StreamPredictor::reset() { + memset(predLine, 0, rowBytes); + predIdx = rowBytes; +} + int StreamPredictor::lookChar() { if (predIdx >= rowBytes) { if (!getNextLine()) { @@ -573,19 +605,19 @@ GBool StreamPredictor::getNextLine() { // apply TIFF (component) predictor if (predictor == 2) { - if (nBits == 1) { - inBuf = predLine[pixBytes - 1]; - for (i = pixBytes; i < rowBytes; i += 8) { - // 1-bit add is just xor - inBuf = (inBuf << 8) | predLine[i]; - predLine[i] ^= inBuf >> nComps; - } - } else if (nBits == 8) { + if (nBits == 8) { for (i = pixBytes; i < rowBytes; ++i) { predLine[i] += predLine[i - nComps]; } + } else if (nBits == 16) { + for (i = pixBytes; i < rowBytes; i += 2) { + c = ((predLine[i] + predLine[i - 2*nComps]) << 8) + + predLine[i + 1] + predLine[i + 1 - 2*nComps]; + predLine[i] = (Guchar)(c >> 8); + predLine[i+1] = (Guchar)(c & 0xff); + } } else { - memset(upLeftBuf, 0, nComps + 1); + memset(upLeftBuf, 0, nComps); bitMask = (1 << nBits) - 1; inBuf = outBuf = 0; inBits = outBits = 0; @@ -624,8 +656,8 @@ GBool StreamPredictor::getNextLine() { // FileStream //------------------------------------------------------------------------ -FileStream::FileStream(FILE *fA, Guint startA, GBool limitedA, - Guint lengthA, Object *dictA): +FileStream::FileStream(FILE *fA, GFileOffset startA, GBool limitedA, + GFileOffset lengthA, Object *dictA): BaseStream(dictA) { f = fA; start = startA; @@ -641,22 +673,14 @@ FileStream::~FileStream() { close(); } -Stream *FileStream::makeSubStream(Guint startA, GBool limitedA, - Guint lengthA, Object *dictA) { +Stream *FileStream::makeSubStream(GFileOffset startA, GBool limitedA, + GFileOffset lengthA, Object *dictA) { return new FileStream(f, startA, limitedA, lengthA, dictA); } void FileStream::reset() { -#if HAVE_FSEEKO - savePos = (Guint)ftello(f); - fseeko(f, start, SEEK_SET); -#elif HAVE_FSEEK64 - savePos = (Guint)ftell64(f); - fseek64(f, start, SEEK_SET); -#else - savePos = (Guint)ftell(f); - fseek(f, start, SEEK_SET); -#endif + savePos = gftell(f); + gfseek(f, start, SEEK_SET); saved = gTrue; bufPtr = bufEnd = buf; bufPos = start; @@ -664,13 +688,7 @@ void FileStream::reset() { void FileStream::close() { if (saved) { -#if HAVE_FSEEKO - fseeko(f, savePos, SEEK_SET); -#elif HAVE_FSEEK64 - fseek64(f, savePos, SEEK_SET); -#else - fseek(f, savePos, SEEK_SET); -#endif + gfseek(f, savePos, SEEK_SET); saved = gFalse; } } @@ -705,7 +723,7 @@ GBool FileStream::fillBuf() { return gFalse; } if (limited && bufPos + fileStreamBufSize > start + length) { - n = start + length - bufPos; + n = (int)(start + length - bufPos); } else { n = fileStreamBufSize; } @@ -717,41 +735,20 @@ GBool FileStream::fillBuf() { return gTrue; } -void FileStream::setPos(Guint pos, int dir) { - Guint size; +void FileStream::setPos(GFileOffset pos, int dir) { + GFileOffset size; if (dir >= 0) { -#if HAVE_FSEEKO - fseeko(f, pos, SEEK_SET); -#elif HAVE_FSEEK64 - fseek64(f, pos, SEEK_SET); -#else - fseek(f, pos, SEEK_SET); -#endif + gfseek(f, pos, SEEK_SET); bufPos = pos; } else { -#if HAVE_FSEEKO - fseeko(f, 0, SEEK_END); - size = (Guint)ftello(f); -#elif HAVE_FSEEK64 - fseek64(f, 0, SEEK_END); - size = (Guint)ftell64(f); -#else - fseek(f, 0, SEEK_END); - size = (Guint)ftell(f); -#endif - if (pos > size) - pos = (Guint)size; -#if HAVE_FSEEKO - fseeko(f, -(int)pos, SEEK_END); - bufPos = (Guint)ftello(f); -#elif HAVE_FSEEK64 - fseek64(f, -(int)pos, SEEK_END); - bufPos = (Guint)ftell64(f); -#else - fseek(f, -(int)pos, SEEK_END); - bufPos = (Guint)ftell(f); -#endif + gfseek(f, 0, SEEK_END); + size = gftell(f); + if (pos > size) { + pos = size; + } + gfseek(f, -pos, SEEK_END); + bufPos = gftell(f); } bufPtr = bufEnd = buf; } @@ -782,17 +779,24 @@ MemStream::~MemStream() { } } -Stream *MemStream::makeSubStream(Guint startA, GBool limited, - Guint lengthA, Object *dictA) { +Stream *MemStream::makeSubStream(GFileOffset startA, GBool limited, + GFileOffset lengthA, Object *dictA) { MemStream *subStr; - Guint newLength; + Guint newStart, newLength; - if (!limited || startA + lengthA > start + length) { - newLength = start + length - startA; + if (startA < start) { + newStart = start; + } else if (startA > start + length) { + newStart = start + (int)length; } else { - newLength = lengthA; + newStart = (int)startA; } - subStr = new MemStream(buf, startA, newLength, dictA); + if (!limited || newStart + lengthA > start + length) { + newLength = start + length - newStart; + } else { + newLength = (Guint)lengthA; + } + subStr = new MemStream(buf, newStart, newLength, dictA); return subStr; } @@ -819,13 +823,13 @@ int MemStream::getBlock(char *blk, int s return n; } -void MemStream::setPos(Guint pos, int dir) { +void MemStream::setPos(GFileOffset pos, int dir) { Guint i; if (dir >= 0) { - i = pos; + i = (Guint)pos; } else { - i = start + length - pos; + i = (Guint)(start + length - pos); } if (i < start) { i = start; @@ -846,7 +850,7 @@ void MemStream::moveStart(int delta) { //------------------------------------------------------------------------ EmbedStream::EmbedStream(Stream *strA, Object *dictA, - GBool limitedA, Guint lengthA): + GBool limitedA, GFileOffset lengthA): BaseStream(dictA) { str = strA; limited = limitedA; @@ -856,8 +860,8 @@ EmbedStream::EmbedStream(Stream *strA, O EmbedStream::~EmbedStream() { } -Stream *EmbedStream::makeSubStream(Guint start, GBool limitedA, - Guint lengthA, Object *dictA) { +Stream *EmbedStream::makeSubStream(GFileOffset start, GBool limitedA, + GFileOffset lengthA, Object *dictA) { error(errInternal, -1, "Called makeSubStream() on EmbedStream"); return NULL; } @@ -887,11 +891,11 @@ int EmbedStream::getBlock(char *blk, int return str->getBlock(blk, size); } -void EmbedStream::setPos(Guint pos, int dir) { +void EmbedStream::setPos(GFileOffset pos, int dir) { error(errInternal, -1, "Called setPos() on EmbedStream"); } -Guint EmbedStream::getStart() { +GFileOffset EmbedStream::getStart() { error(errInternal, -1, "Called getStart() on EmbedStream"); return 0; } @@ -1173,6 +1177,9 @@ int LZWStream::getBlock(char *blk, int s void LZWStream::reset() { str->reset(); + if (pred) { + pred->reset(); + } eof = gFalse; inputBits = 0; clearTable(); @@ -2068,14 +2075,16 @@ GBool CCITTFaxStream::isBinary(GBool las //------------------------------------------------------------------------ // IDCT constants (20.12 fixed point format) -#define dctCos1 4017 // cos(pi/16) -#define dctSin1 799 // sin(pi/16) -#define dctCos3 3406 // cos(3*pi/16) -#define dctSin3 2276 // sin(3*pi/16) -#define dctCos6 1567 // cos(6*pi/16) -#define dctSin6 3784 // sin(6*pi/16) -#define dctSqrt2 5793 // sqrt(2) -#define dctSqrt1d2 2896 // sqrt(2) / 2 +#define dctSqrt2 5793 // sqrt(2) +#define dctSqrt2Cos6 2217 // sqrt(2) * cos(6*pi/16) +#define dctSqrt2Cos6PSin6 7568 // sqrt(2) * (cos(6*pi/16) + sin(6*pi/16)) +#define dctSqrt2Sin6MCos6 3135 // sqrt(2) * (sin(6*pi/16) - cos(6*pi/16)) +#define dctCos3 3406 // cos(3*pi/16) +#define dctCos3PSin3 5681 // cos(3*pi/16) + sin(3*pi/16) +#define dctSin3MCos3 -1130 // sin(3*pi/16) - cos(3*pi/16) +#define dctCos1 4017 // cos(pi/16) +#define dctCos1PSin1 4816 // cos(pi/16) + sin(pi/16) +#define dctSin1MCos1 -3218 // sin(pi/16) - cos(pi/16) // color conversion parameters (16.16 fixed point format) #define dctCrToR 91881 // 1.4020 @@ -2083,10 +2092,47 @@ GBool CCITTFaxStream::isBinary(GBool las #define dctCrToG -46802 // -0.71413636 #define dctCbToB 116130 // 1.772 -// clip [-256,511] --> [0,255] -#define dctClipOffset 256 -static Guchar dctClip[768]; -static int dctClipInit = 0; +// The dctClip function clips signed integers to the [0,255] range. +// To handle valid DCT inputs, this must support an input range of at +// least [-256,511]. Invalid DCT inputs (e.g., from damaged PDF +// files) can result in arbitrary values, so we want to mask those +// out. We round the input range size up to a power of 2 (so we can +// use a bit mask), which gives us an input range of [-384,639]. The +// end result is: +// input output +// ---------- ------ +// <-384 X invalid inputs -> output is "don't care" +// -384..-257 0 invalid inputs, clipped +// -256..-1 0 valid inputs, need to be clipped +// 0..255 0..255 +// 256..511 255 valid inputs, need to be clipped +// 512..639 255 invalid inputs, clipped +// >=512 X invalid inputs -> output is "don't care" + +#define dctClipOffset 384 +#define dctClipMask 1023 +static Guchar dctClipData[1024]; + +static inline void dctClipInit() { + static int initDone = 0; + int i; + if (!initDone) { + for (i = -384; i < 0; ++i) { + dctClipData[dctClipOffset + i] = 0; + } + for (i = 0; i < 256; ++i) { + dctClipData[dctClipOffset + i] = i; + } + for (i = 256; i < 639; ++i) { + dctClipData[dctClipOffset + i] = 255; + } + initDone = 1; + } +} + +static inline int dctClip(int x) { + return dctClipData[(dctClipOffset + x) & dctClipMask]; +} // zig zag decode map static int dctZigZag[64] = { @@ -2109,7 +2155,7 @@ static int dctZigZag[64] = { DCTStream::DCTStream(Stream *strA, GBool colorXformA): FilterStream(strA) { - int i, j; + int i; colorXform = colorXformA; progressive = interleaved = gFalse; @@ -2117,23 +2163,15 @@ DCTStream::DCTStream(Stream *strA, GBool mcuWidth = mcuHeight = 0; numComps = 0; comp = 0; - x = y = dy = 0; + x = y = 0; for (i = 0; i < 4; ++i) { - for (j = 0; j < 32; ++j) { - rowBuf[i][j] = NULL; - } frameBuf[i] = NULL; } + rowBuf = NULL; + memset(dcHuffTables, 0, sizeof(dcHuffTables)); + memset(acHuffTables, 0, sizeof(acHuffTables)); - if (!dctClipInit) { - for (i = -256; i < 0; ++i) - dctClip[dctClipOffset + i] = 0; - for (i = 0; i < 256; ++i) - dctClip[dctClipOffset + i] = i; - for (i = 256; i < 512; ++i) - dctClip[dctClipOffset + i] = 255; - dctClipInit = 1; - } + dctClipInit(); } DCTStream::~DCTStream() { @@ -2142,7 +2180,7 @@ DCTStream::~DCTStream() { } void DCTStream::reset() { - int i, j; + int i; str->reset(); @@ -2157,6 +2195,8 @@ void DCTStream::reset() { restartInterval = 0; if (!readHeader()) { + // force an EOF condition + progressive = gTrue; y = height; return; } @@ -2229,17 +2269,11 @@ void DCTStream::reset() { // allocate a buffer for one row of MCUs bufWidth = ((width + mcuWidth - 1) / mcuWidth) * mcuWidth; - for (i = 0; i < numComps; ++i) { - for (j = 0; j < mcuHeight; ++j) { - rowBuf[i][j] = (Guchar *)gmallocn(bufWidth, sizeof(Guchar)); - } - } + rowBuf = (Guchar *)gmallocn(numComps * mcuHeight, bufWidth); + rowBufPtr = rowBufEnd = rowBuf; // initialize counters - comp = 0; - x = 0; - y = 0; - dy = mcuHeight; + y = -mcuHeight; restartMarker = 0xd0; restart(); @@ -2247,26 +2281,24 @@ void DCTStream::reset() { } void DCTStream::close() { - int i, j; + int i; for (i = 0; i < 4; ++i) { - for (j = 0; j < 32; ++j) { - gfree(rowBuf[i][j]); - rowBuf[i][j] = NULL; - } gfree(frameBuf[i]); frameBuf[i] = NULL; } + gfree(rowBuf); + rowBuf = NULL; FilterStream::close(); } int DCTStream::getChar() { int c; - if (y >= height) { - return EOF; - } if (progressive || !interleaved) { + if (y >= height) { + return EOF; + } c = frameBuf[comp][y * bufWidth + x]; if (++comp == numComps) { comp = 0; @@ -2276,48 +2308,38 @@ int DCTStream::getChar() { } } } else { - if (dy >= mcuHeight) { + if (rowBufPtr == rowBufEnd) { + if (y + mcuHeight >= height) { + return EOF; + } + y += mcuHeight; if (!readMCURow()) { y = height; return EOF; } - comp = 0; - x = 0; - dy = 0; - } - c = rowBuf[comp][dy][x]; - if (++comp == numComps) { - comp = 0; - if (++x == width) { - x = 0; - ++y; - ++dy; - if (y == height) { - readTrailer(); - } - } } + c = *rowBufPtr++; } return c; } int DCTStream::lookChar() { - if (y >= height) { - return EOF; - } if (progressive || !interleaved) { + if (y >= height) { + return EOF; + } return frameBuf[comp][y * bufWidth + x]; } else { - if (dy >= mcuHeight) { + if (rowBufPtr == rowBufEnd) { + if (y + mcuHeight >= height) { + return EOF; + } if (!readMCURow()) { y = height; return EOF; } - comp = 0; - x = 0; - dy = 0; } - return rowBuf[comp][dy][x]; + return *rowBufPtr; } } @@ -2375,38 +2397,49 @@ GBool DCTStream::readMCURow() { } transformDataUnit(quantTables[compInfo[cc].quantTable], data1, data2); - if (hSub == 1 && vSub == 1) { + if (hSub == 1 && vSub == 1 && x1+x2+8 <= width) { for (y3 = 0, i = 0; y3 < 8; ++y3, i += 8) { - p1 = &rowBuf[cc][y2+y3][x1+x2]; - p1[0] = data2[i]; - p1[1] = data2[i+1]; - p1[2] = data2[i+2]; - p1[3] = data2[i+3]; - p1[4] = data2[i+4]; - p1[5] = data2[i+5]; - p1[6] = data2[i+6]; - p1[7] = data2[i+7]; + p1 = &rowBuf[((y2+y3) * width + (x1+x2)) * numComps + cc]; + p1[0] = data2[i]; + p1[ numComps] = data2[i+1]; + p1[2*numComps] = data2[i+2]; + p1[3*numComps] = data2[i+3]; + p1[4*numComps] = data2[i+4]; + p1[5*numComps] = data2[i+5]; + p1[6*numComps] = data2[i+6]; + p1[7*numComps] = data2[i+7]; } - } else if (hSub == 2 && vSub == 2) { + } else if (hSub == 2 && vSub == 2 && x1+x2+16 <= width) { for (y3 = 0, i = 0; y3 < 16; y3 += 2, i += 8) { - p1 = &rowBuf[cc][y2+y3][x1+x2]; - p2 = &rowBuf[cc][y2+y3+1][x1+x2]; - p1[0] = p1[1] = p2[0] = p2[1] = data2[i]; - p1[2] = p1[3] = p2[2] = p2[3] = data2[i+1]; - p1[4] = p1[5] = p2[4] = p2[5] = data2[i+2]; - p1[6] = p1[7] = p2[6] = p2[7] = data2[i+3]; - p1[8] = p1[9] = p2[8] = p2[9] = data2[i+4]; - p1[10] = p1[11] = p2[10] = p2[11] = data2[i+5]; - p1[12] = p1[13] = p2[12] = p2[13] = data2[i+6]; - p1[14] = p1[15] = p2[14] = p2[15] = data2[i+7]; + p1 = &rowBuf[((y2+y3) * width + (x1+x2)) * numComps + cc]; + p2 = p1 + width * numComps; + p1[0] = p1[numComps] = + p2[0] = p2[numComps] = data2[i]; + p1[2*numComps] = p1[3*numComps] = + p2[2*numComps] = p2[3*numComps] = data2[i+1]; + p1[4*numComps] = p1[5*numComps] = + p2[4*numComps] = p2[5*numComps] = data2[i+2]; + p1[6*numComps] = p1[7*numComps] = + p2[6*numComps] = p2[7*numComps] = data2[i+3]; + p1[8*numComps] = p1[9*numComps] = + p2[8*numComps] = p2[9*numComps] = data2[i+4]; + p1[10*numComps] = p1[11*numComps] = + p2[10*numComps] = p2[11*numComps] = data2[i+5]; + p1[12*numComps] = p1[13*numComps] = + p2[12*numComps] = p2[13*numComps] = data2[i+6]; + p1[14*numComps] = p1[15*numComps] = + p2[14*numComps] = p2[15*numComps] = data2[i+7]; } } else { + p1 = &rowBuf[(y2 * width + (x1+x2)) * numComps + cc]; i = 0; for (y3 = 0, y4 = 0; y3 < 8; ++y3, y4 += vSub) { for (x3 = 0, x4 = 0; x3 < 8; ++x3, x4 += hSub) { - for (y5 = 0; y5 < vSub; ++y5) - for (x5 = 0; x5 < hSub; ++x5) - rowBuf[cc][y2+y4+y5][x1+x2+x4+x5] = data2[i]; + for (y5 = 0; y5 < vSub; ++y5) { + for (x5 = 0; x5 < hSub && x1+x2+x4+x5 < width; ++x5) { + p1[((y4+y5) * width + (x4+x5)) * numComps] = data2[i]; + } + } ++i; } } @@ -2415,42 +2448,46 @@ GBool DCTStream::readMCURow() { } } --restartCtr; + } - // color space conversion - if (colorXform) { - // convert YCbCr to RGB - if (numComps == 3) { - for (y2 = 0; y2 < mcuHeight; ++y2) { - for (x2 = 0; x2 < mcuWidth; ++x2) { - pY = rowBuf[0][y2][x1+x2]; - pCb = rowBuf[1][y2][x1+x2] - 128; - pCr = rowBuf[2][y2][x1+x2] - 128; - pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; - rowBuf[0][y2][x1+x2] = dctClip[dctClipOffset + pR]; - pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; - rowBuf[1][y2][x1+x2] = dctClip[dctClipOffset + pG]; - pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; - rowBuf[2][y2][x1+x2] = dctClip[dctClipOffset + pB]; - } - } - // convert YCbCrK to CMYK (K is passed through unchanged) - } else if (numComps == 4) { - for (y2 = 0; y2 < mcuHeight; ++y2) { - for (x2 = 0; x2 < mcuWidth; ++x2) { - pY = rowBuf[0][y2][x1+x2]; - pCb = rowBuf[1][y2][x1+x2] - 128; - pCr = rowBuf[2][y2][x1+x2] - 128; - pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; - rowBuf[0][y2][x1+x2] = 255 - dctClip[dctClipOffset + pR]; - pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; - rowBuf[1][y2][x1+x2] = 255 - dctClip[dctClipOffset + pG]; - pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; - rowBuf[2][y2][x1+x2] = 255 - dctClip[dctClipOffset + pB]; - } - } + // color space conversion + if (colorXform) { + // convert YCbCr to RGB + if (numComps == 3) { + for (i = 0, p1 = rowBuf; i < width * mcuHeight; ++i, p1 += 3) { + pY = p1[0]; + pCb = p1[1] - 128; + pCr = p1[2] - 128; + pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; + p1[0] = dctClip(pR); + pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; + p1[1] = dctClip(pG); + pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; + p1[2] = dctClip(pB); + } + // convert YCbCrK to CMYK (K is passed through unchanged) + } else if (numComps == 4) { + for (i = 0, p1 = rowBuf; i < width * mcuHeight; ++i, p1 += 4) { + pY = p1[0]; + pCb = p1[1] - 128; + pCr = p1[2] - 128; + pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; + p1[0] = 255 - dctClip(pR); + pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; + p1[1] = 255 - dctClip(pG); + pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; + p1[2] = 255 - dctClip(pB); } } } + + rowBufPtr = rowBuf; + if (y + mcuHeight <= height) { + rowBufEnd = rowBuf + numComps * width * mcuHeight; + } else { + rowBufEnd = rowBuf + numComps * width * (height - y); + } + return gTrue; } @@ -2609,7 +2646,7 @@ GBool DCTStream::readDataUnit(DCTHuffTab return gTrue; } -// Read one data unit from a sequential JPEG stream. +// Read one data unit from a progressive JPEG stream. GBool DCTStream::readProgressiveDataUnit(DCTHuffTable *dcHuffTable, DCTHuffTable *acHuffTable, int *prevDC, int data[64]) { @@ -2635,7 +2672,13 @@ GBool DCTStream::readProgressiveDataUnit if ((bit = readBit()) == 9999) { return gFalse; } - data[0] += bit << scanInfo.al; + if (bit) { + if (data[0] >= 0) { + data[0] += 1 << scanInfo.al; + } else { + data[0] -= 1 << scanInfo.al; + } + } } ++i; } @@ -2652,7 +2695,11 @@ GBool DCTStream::readProgressiveDataUnit return gFalse; } if (bit) { - data[j] += 1 << scanInfo.al; + if (data[j] >= 0) { + data[j] += 1 << scanInfo.al; + } else { + data[j] -= 1 << scanInfo.al; + } } } } @@ -2678,7 +2725,11 @@ GBool DCTStream::readProgressiveDataUnit return gFalse; } if (bit) { - data[j] += 1 << scanInfo.al; + if (data[j] >= 0) { + data[j] += 1 << scanInfo.al; + } else { + data[j] -= 1 << scanInfo.al; + } } } } @@ -2701,7 +2752,11 @@ GBool DCTStream::readProgressiveDataUnit return gFalse; } if (bit) { - data[j] += 1 << scanInfo.al; + if (data[j] >= 0) { + data[j] += 1 << scanInfo.al; + } else { + data[j] -= 1 << scanInfo.al; + } } } } @@ -2723,7 +2778,11 @@ GBool DCTStream::readProgressiveDataUnit return gFalse; } if (bit) { - data[j] += 1 << scanInfo.al; + if (data[j] >= 0) { + data[j] += 1 << scanInfo.al; + } else { + data[j] -= 1 << scanInfo.al; + } } j = dctZigZag[i++]; } @@ -2837,12 +2896,12 @@ void DCTStream::decodeImage() { pCb = *p1 - 128; pCr = *p2 - 128; pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; - *p0++ = dctClip[dctClipOffset + pR]; + *p0++ = dctClip(pR); pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; - *p1++ = dctClip[dctClipOffset + pG]; + *p1++ = dctClip(pG); pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; - *p2++ = dctClip[dctClipOffset + pB]; + *p2++ = dctClip(pB); } } // convert YCbCrK to CMYK (K is passed through unchanged) @@ -2856,12 +2915,12 @@ void DCTStream::decodeImage() { pCb = *p1 - 128; pCr = *p2 - 128; pR = ((pY << 16) + dctCrToR * pCr + 32768) >> 16; - *p0++ = 255 - dctClip[dctClipOffset + pR]; + *p0++ = 255 - dctClip(pR); pG = ((pY << 16) + dctCbToG * pCb + dctCrToG * pCr + 32768) >> 16; - *p1++ = 255 - dctClip[dctClipOffset + pG]; + *p1++ = 255 - dctClip(pG); pB = ((pY << 16) + dctCbToB * pCb + 32768) >> 16; - *p2++ = 255 - dctClip[dctClipOffset + pB]; + *p2++ = 255 - dctClip(pB); } } } @@ -2880,71 +2939,76 @@ void DCTStream::decodeImage() { // paper. void DCTStream::transformDataUnit(Gushort *quantTable, int dataIn[64], Guchar dataOut[64]) { - int v0, v1, v2, v3, v4, v5, v6, v7, t; + int v0, v1, v2, v3, v4, v5, v6, v7, t0, t1, t2; int *p; + Gushort *q; int i; - // dequant - for (i = 0; i < 64; ++i) { - dataIn[i] *= quantTable[i]; - } - - // inverse DCT on rows + // dequant; inverse DCT on rows for (i = 0; i < 64; i += 8) { p = dataIn + i; + q = quantTable + i; // check for all-zero AC coefficients if (p[1] == 0 && p[2] == 0 && p[3] == 0 && p[4] == 0 && p[5] == 0 && p[6] == 0 && p[7] == 0) { - t = (dctSqrt2 * p[0] + 512) >> 10; - p[0] = t; - p[1] = t; - p[2] = t; - p[3] = t; - p[4] = t; - p[5] = t; - p[6] = t; - p[7] = t; + t0 = p[0] * q[0]; + p[0] = t0; + p[1] = t0; + p[2] = t0; + p[3] = t0; + p[4] = t0; + p[5] = t0; + p[6] = t0; + p[7] = t0; continue; } // stage 4 - v0 = (dctSqrt2 * p[0] + 128) >> 8; - v1 = (dctSqrt2 * p[4] + 128) >> 8; - v2 = p[2]; - v3 = p[6]; - v4 = (dctSqrt1d2 * (p[1] - p[7]) + 128) >> 8; - v7 = (dctSqrt1d2 * (p[1] + p[7]) + 128) >> 8; - v5 = p[3] << 4; - v6 = p[5] << 4; + v0 = p[0] * q[0]; + v1 = p[4] * q[4]; + v2 = p[2] * q[2]; + v3 = p[6] * q[6]; + t0 = p[1] * q[1]; + t1 = p[7] * q[7]; + v4 = t0 - t1; + v7 = t0 + t1; + v5 = (dctSqrt2 * p[3] * q[3]) >> 12; + v6 = (dctSqrt2 * p[5] * q[5]) >> 12; // stage 3 - t = (v0 - v1+ 1) >> 1; - v0 = (v0 + v1 + 1) >> 1; - v1 = t; - t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; - v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; - v3 = t; - t = (v4 - v6 + 1) >> 1; - v4 = (v4 + v6 + 1) >> 1; - v6 = t; - t = (v7 + v5 + 1) >> 1; - v5 = (v7 - v5 + 1) >> 1; - v7 = t; + t0 = v0 - v1; + v0 = v0 + v1; + v1 = t0; + t0 = dctSqrt2Cos6 * (v2 + v3); + t1 = dctSqrt2Cos6PSin6 * v3; + t2 = dctSqrt2Sin6MCos6 * v2; + v2 = (t0 - t1) >> 12; + v3 = (t0 + t2) >> 12; + t0 = v4 - v6; + v4 = v4 + v6; + v6 = t0; + t0 = v7 + v5; + v5 = v7 - v5; + v7 = t0; // stage 2 - t = (v0 - v3 + 1) >> 1; - v0 = (v0 + v3 + 1) >> 1; - v3 = t; - t = (v1 - v2 + 1) >> 1; - v1 = (v1 + v2 + 1) >> 1; - v2 = t; - t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; - v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; - v7 = t; - t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; - v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; - v6 = t; + t0 = v0 - v3; + v0 = v0 + v3; + v3 = t0; + t0 = v1 - v2; + v1 = v1 + v2; + v2 = t0; + t0 = dctCos3 * (v4 + v7); + t1 = dctCos3PSin3 * v7; + t2 = dctSin3MCos3 * v4; + v4 = (t0 - t1) >> 12; + v7 = (t0 + t2) >> 12; + t0 = dctCos1 * (v5 + v6); + t1 = dctCos1PSin1 * v6; + t2 = dctSin1MCos1 * v5; + v5 = (t0 - t1) >> 12; + v6 = (t0 + t2) >> 12; // stage 1 p[0] = v0 + v7; @@ -2964,55 +3028,60 @@ void DCTStream::transformDataUnit(Gushor // check for all-zero AC coefficients if (p[1*8] == 0 && p[2*8] == 0 && p[3*8] == 0 && p[4*8] == 0 && p[5*8] == 0 && p[6*8] == 0 && p[7*8] == 0) { - t = (dctSqrt2 * dataIn[i+0] + 8192) >> 14; - p[0*8] = t; - p[1*8] = t; - p[2*8] = t; - p[3*8] = t; - p[4*8] = t; - p[5*8] = t; - p[6*8] = t; - p[7*8] = t; + t0 = p[0*8]; + p[1*8] = t0; + p[2*8] = t0; + p[3*8] = t0; + p[4*8] = t0; + p[5*8] = t0; + p[6*8] = t0; + p[7*8] = t0; continue; } // stage 4 - v0 = (dctSqrt2 * p[0*8] + 2048) >> 12; - v1 = (dctSqrt2 * p[4*8] + 2048) >> 12; + v0 = p[0*8]; + v1 = p[4*8]; v2 = p[2*8]; v3 = p[6*8]; - v4 = (dctSqrt1d2 * (p[1*8] - p[7*8]) + 2048) >> 12; - v7 = (dctSqrt1d2 * (p[1*8] + p[7*8]) + 2048) >> 12; - v5 = p[3*8]; - v6 = p[5*8]; + v4 = p[1*8] - p[7*8]; + v7 = p[1*8] + p[7*8]; + v5 = (dctSqrt2 * p[3*8]) >> 12; + v6 = (dctSqrt2 * p[5*8]) >> 12; // stage 3 - t = (v0 - v1 + 1) >> 1; - v0 = (v0 + v1 + 1) >> 1; - v1 = t; - t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; - v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; - v3 = t; - t = (v4 - v6 + 1) >> 1; - v4 = (v4 + v6 + 1) >> 1; - v6 = t; - t = (v7 + v5 + 1) >> 1; - v5 = (v7 - v5 + 1) >> 1; - v7 = t; + t0 = v0 - v1; + v0 = v0 + v1; + v1 = t0; + t0 = dctSqrt2Cos6 * (v2 + v3); + t1 = dctSqrt2Cos6PSin6 * v3; + t2 = dctSqrt2Sin6MCos6 * v2; + v2 = (t0 - t1) >> 12; + v3 = (t0 + t2) >> 12; + t0 = v4 - v6; + v4 = v4 + v6; + v6 = t0; + t0 = v7 + v5; + v5 = v7 - v5; + v7 = t0; // stage 2 - t = (v0 - v3 + 1) >> 1; - v0 = (v0 + v3 + 1) >> 1; - v3 = t; - t = (v1 - v2 + 1) >> 1; - v1 = (v1 + v2 + 1) >> 1; - v2 = t; - t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; - v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; - v7 = t; - t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; - v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; - v6 = t; + t0 = v0 - v3; + v0 = v0 + v3; + v3 = t0; + t0 = v1 - v2; + v1 = v1 + v2; + v2 = t0; + t0 = dctCos3 * (v4 + v7); + t1 = dctCos3PSin3 * v7; + t2 = dctSin3MCos3 * v4; + v4 = (t0 - t1) >> 12; + v7 = (t0 + t2) >> 12; + t0 = dctCos1 * (v5 + v6); + t1 = dctCos1PSin1 * v6; + t2 = dctSin1MCos1 * v5; + v5 = (t0 - t1) >> 12; + v6 = (t0 + t2) >> 12; // stage 1 p[0*8] = v0 + v7; @@ -3027,7 +3096,7 @@ void DCTStream::transformDataUnit(Gushor // convert to 8-bit integers for (i = 0; i < 64; ++i) { - dataOut[i] = dctClip[dctClipOffset + 128 + ((dataIn[i] + 8) >> 4)]; + dataOut[i] = dctClip(128 + (dataIn[i] >> 3)); } } @@ -3103,7 +3172,6 @@ GBool DCTStream::readHeader() { GBool doScan; int n; int c = 0; - int i; // read headers doScan = gFalse; @@ -3163,9 +3231,7 @@ GBool DCTStream::readHeader() { // skip APPn / COM / etc. if (c >= 0xe0) { n = read16() - 2; - for (i = 0; i < n; ++i) { - str->getChar(); - } + str->discardChars(n); } else { error(errSyntaxError, getPos(), "Unknown DCT marker <{0:02x}>", c); return gFalse; @@ -3178,12 +3244,11 @@ GBool DCTStream::readHeader() { } GBool DCTStream::readBaselineSOF() { - int length; int prec; int i; int c; - length = read16(); + read16(); // length prec = str->getChar(); height = read16(); width = read16(); @@ -3218,12 +3283,11 @@ GBool DCTStream::readBaselineSOF() { } GBool DCTStream::readProgressiveSOF() { - int length; int prec; int i; int c; - length = read16(); + read16(); // length prec = str->getChar(); height = read16(); width = read16(); @@ -4194,6 +4258,9 @@ void FlateStream::reset() { eof = gTrue; str->reset(); + if (pred) { + pred->reset(); + } // read header //~ need to look at window size? @@ -4274,10 +4341,10 @@ int FlateStream::getBlock(char *blk, int n = 0; while (n < size) { - if (endOfBlock && eof) { - break; - } if (remain == 0) { + if (endOfBlock && eof) { + break; + } readSome(); } while (remain && n < size) { @@ -4969,3 +5036,149 @@ GBool RunLengthEncoder::fillBuf() { bufPtr = buf; return gTrue; } + +//------------------------------------------------------------------------ +// LZWEncoder +//------------------------------------------------------------------------ + +LZWEncoder::LZWEncoder(Stream *strA): + FilterStream(strA) +{ + inBufLen = 0; + outBufLen = 0; +} + +LZWEncoder::~LZWEncoder() { + if (str->isEncoder()) { + delete str; + } +} + +void LZWEncoder::reset() { + int i; + + str->reset(); + + // initialize code table + for (i = 0; i < 256; ++i) { + table[i].byte = i; + table[i].next = NULL; + table[i].children = NULL; + } + nextSeq = 258; + codeLen = 9; + + // initialize input buffer + inBufLen = str->getBlock((char *)inBuf, sizeof(inBuf)); + + // initialize output buffer with a clear-table code + outBuf = 256; + outBufLen = 9; + needEOD = gFalse; +} + +int LZWEncoder::getChar() { + int ret; + + if (inBufLen == 0 && !needEOD && outBufLen == 0) { + return EOF; + } + if (outBufLen < 8 && (inBufLen > 0 || needEOD)) { + fillBuf(); + } + if (outBufLen >= 8) { + ret = (outBuf >> (outBufLen - 8)) & 0xff; + outBufLen -= 8; + } else { + ret = (outBuf << (8 - outBufLen)) & 0xff; + outBufLen = 0; + } + return ret; +} + +int LZWEncoder::lookChar() { + if (inBufLen == 0 && !needEOD && outBufLen == 0) { + return EOF; + } + if (outBufLen < 8 && (inBufLen > 0 || needEOD)) { + fillBuf(); + } + if (outBufLen >= 8) { + return (outBuf >> (outBufLen - 8)) & 0xff; + } else { + return (outBuf << (8 - outBufLen)) & 0xff; + } +} + +// On input, outBufLen < 8. +// This function generates, at most, 2 12-bit codes +// --> outBufLen < 8 + 12 + 12 = 32 +void LZWEncoder::fillBuf() { + LZWEncoderNode *p0, *p1; + int seqLen, code, i; + + if (needEOD) { + outBuf = (outBuf << codeLen) | 257; + outBufLen += codeLen; + needEOD = gFalse; + return; + } + + // find longest matching sequence (if any) + p0 = table + inBuf[0]; + seqLen = 1; + while (inBufLen > seqLen) { + for (p1 = p0->children; p1; p1 = p1->next) { + if (p1->byte == inBuf[seqLen]) { + break; + } + } + if (!p1) { + break; + } + p0 = p1; + ++seqLen; + } + code = (int)(p0 - table); + + // generate an output code + outBuf = (outBuf << codeLen) | code; + outBufLen += codeLen; + + // update the table + table[nextSeq].byte = seqLen < inBufLen ? inBuf[seqLen] : 0; + table[nextSeq].children = NULL; + if (table[code].children) { + table[nextSeq].next = table[code].children; + } else { + table[nextSeq].next = NULL; + } + table[code].children = table + nextSeq; + ++nextSeq; + + // update the input buffer + memmove(inBuf, inBuf + seqLen, inBufLen - seqLen); + inBufLen -= seqLen; + inBufLen += str->getBlock((char *)inBuf + inBufLen, + sizeof(inBuf) - inBufLen); + + // increment codeLen; generate clear-table code + if (nextSeq == (1 << codeLen)) { + ++codeLen; + if (codeLen == 13) { + outBuf = (outBuf << 12) | 256; + outBufLen += 12; + for (i = 0; i < 256; ++i) { + table[i].next = NULL; + table[i].children = NULL; + } + nextSeq = 258; + codeLen = 9; + } + } + + // generate EOD next time + if (inBufLen == 0) { + needEOD = gTrue; + } +} diff -uNrp xpdf-3.03/xpdf/Stream.h xpdf-3.04/xpdf/Stream.h --- xpdf-3.03/xpdf/Stream.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/Stream.h 2014-05-28 20:50:50.000000000 +0200 @@ -17,6 +17,7 @@ #include #include "gtypes.h" +#include "gfile.h" #include "Object.h" class BaseStream; @@ -97,13 +98,18 @@ public: // Get next line from stream. virtual char *getLine(char *buf, int size); + // Discard the next bytes from stream. Returns the number of + // bytes discarded, which will be less than only if EOF is + // reached. + virtual Guint discardChars(Guint n); + // Get current position in file. - virtual int getPos() = 0; + virtual GFileOffset getPos() = 0; // Go to a position in the stream. If is negative, the // position is from the end of the file; otherwise the position is // from the start of the file. - virtual void setPos(Guint pos, int dir = 0) = 0; + virtual void setPos(GFileOffset pos, int dir = 0) = 0; // Get PostScript command for the filter(s). virtual GString *getPSFilter(int psLevel, const char *indent); @@ -133,11 +139,11 @@ public: // Add filters to this stream according to the parameters in . // Returns the new stream. - Stream *addFilters(Object *dict); + Stream *addFilters(Object *dict, int recursion = 0); private: - Stream *makeFilter(char *name, Stream *str, Object *params); + Stream *makeFilter(char *name, Stream *str, Object *params, int recursion); int ref; // reference count }; @@ -153,9 +159,9 @@ public: BaseStream(Object *dictA); virtual ~BaseStream(); - virtual Stream *makeSubStream(Guint start, GBool limited, - Guint length, Object *dict) = 0; - virtual void setPos(Guint pos, int dir = 0) = 0; + virtual Stream *makeSubStream(GFileOffset start, GBool limited, + GFileOffset length, Object *dict) = 0; + virtual void setPos(GFileOffset pos, int dir = 0) = 0; virtual GBool isBinary(GBool last = gTrue) { return last; } virtual BaseStream *getBaseStream() { return this; } virtual Stream *getUndecodedStream() { return this; } @@ -163,7 +169,7 @@ public: virtual GString *getFileName() { return NULL; } // Get/set position of first byte of stream within the file. - virtual Guint getStart() = 0; + virtual GFileOffset getStart() = 0; virtual void moveStart(int delta) = 0; private: @@ -183,8 +189,8 @@ public: FilterStream(Stream *strA); virtual ~FilterStream(); virtual void close(); - virtual int getPos() { return str->getPos(); } - virtual void setPos(Guint pos, int dir = 0); + virtual GFileOffset getPos() { return str->getPos(); } + virtual void setPos(GFileOffset pos, int dir = 0); virtual BaseStream *getBaseStream() { return str->getBaseStream(); } virtual Stream *getUndecodedStream() { return str->getUndecodedStream(); } virtual Dict *getDict() { return str->getDict(); } @@ -212,6 +218,9 @@ public: // Reset the stream. void reset(); + // Close down the stream. + void close(); + // Gets the next pixel from the stream. should be able to hold // at least nComps elements. Returns false at end of file. GBool getPixel(Guchar *pix); @@ -252,6 +261,8 @@ public: GBool isOk() { return ok; } + void reset(); + int lookChar(); int getChar(); int getBlock(char *blk, int size); @@ -282,11 +293,11 @@ private: class FileStream: public BaseStream { public: - FileStream(FILE *fA, Guint startA, GBool limitedA, - Guint lengthA, Object *dictA); + FileStream(FILE *fA, GFileOffset startA, GBool limitedA, + GFileOffset lengthA, Object *dictA); virtual ~FileStream(); - virtual Stream *makeSubStream(Guint startA, GBool limitedA, - Guint lengthA, Object *dictA); + virtual Stream *makeSubStream(GFileOffset startA, GBool limitedA, + GFileOffset lengthA, Object *dictA); virtual StreamKind getKind() { return strFile; } virtual void reset(); virtual void close(); @@ -295,9 +306,9 @@ public: virtual int lookChar() { return (bufPtr >= bufEnd && !fillBuf()) ? EOF : (*bufPtr & 0xff); } virtual int getBlock(char *blk, int size); - virtual int getPos() { return bufPos + (int)(bufPtr - buf); } - virtual void setPos(Guint pos, int dir = 0); - virtual Guint getStart() { return start; } + virtual GFileOffset getPos() { return bufPos + (int)(bufPtr - buf); } + virtual void setPos(GFileOffset pos, int dir = 0); + virtual GFileOffset getStart() { return start; } virtual void moveStart(int delta); private: @@ -305,14 +316,14 @@ private: GBool fillBuf(); FILE *f; - Guint start; + GFileOffset start; GBool limited; - Guint length; + GFileOffset length; char buf[fileStreamBufSize]; char *bufPtr; char *bufEnd; - Guint bufPos; - int savePos; + GFileOffset bufPos; + GFileOffset savePos; GBool saved; }; @@ -325,8 +336,8 @@ public: MemStream(char *bufA, Guint startA, Guint lengthA, Object *dictA); virtual ~MemStream(); - virtual Stream *makeSubStream(Guint start, GBool limited, - Guint lengthA, Object *dictA); + virtual Stream *makeSubStream(GFileOffset start, GBool limited, + GFileOffset lengthA, Object *dictA); virtual StreamKind getKind() { return strWeird; } virtual void reset(); virtual void close(); @@ -335,9 +346,9 @@ public: virtual int lookChar() { return (bufPtr < bufEnd) ? (*bufPtr & 0xff) : EOF; } virtual int getBlock(char *blk, int size); - virtual int getPos() { return (int)(bufPtr - buf); } - virtual void setPos(Guint pos, int dir = 0); - virtual Guint getStart() { return start; } + virtual GFileOffset getPos() { return (GFileOffset)(bufPtr - buf); } + virtual void setPos(GFileOffset pos, int dir = 0); + virtual GFileOffset getStart() { return start; } virtual void moveStart(int delta); private: @@ -363,25 +374,25 @@ private: class EmbedStream: public BaseStream { public: - EmbedStream(Stream *strA, Object *dictA, GBool limitedA, Guint lengthA); + EmbedStream(Stream *strA, Object *dictA, GBool limitedA, GFileOffset lengthA); virtual ~EmbedStream(); - virtual Stream *makeSubStream(Guint start, GBool limitedA, - Guint lengthA, Object *dictA); + virtual Stream *makeSubStream(GFileOffset start, GBool limitedA, + GFileOffset lengthA, Object *dictA); virtual StreamKind getKind() { return str->getKind(); } virtual void reset() {} virtual int getChar(); virtual int lookChar(); virtual int getBlock(char *blk, int size); - virtual int getPos() { return str->getPos(); } - virtual void setPos(Guint pos, int dir = 0); - virtual Guint getStart(); + virtual GFileOffset getPos() { return str->getPos(); } + virtual void setPos(GFileOffset pos, int dir = 0); + virtual GFileOffset getStart(); virtual void moveStart(int delta); private: Stream *str; GBool limited; - Guint length; + GFileOffset length; }; //------------------------------------------------------------------------ @@ -623,9 +634,11 @@ private: DCTHuffTable acHuffTables[4]; // AC Huffman tables int numDCHuffTables; // number of DC Huffman tables int numACHuffTables; // number of AC Huffman tables - Guchar *rowBuf[4][32]; // buffer for one MCU (non-progressive mode) + Guchar *rowBuf; + Guchar *rowBufPtr; // current position within rowBuf + Guchar *rowBufEnd; // end of valid data in rowBuf int *frameBuf[4]; // buffer for frame (progressive mode) - int comp, x, y, dy; // current position within image/MCU + int comp, x, y; // current position within image/MCU int restartCtr; // MCUs left until restart int restartMarker; // next restart marker int eobRun; // number of EOBs left in the current run @@ -902,4 +915,42 @@ private: GBool fillBuf(); }; +//------------------------------------------------------------------------ +// LZWEncoder +//------------------------------------------------------------------------ + +struct LZWEncoderNode { + int byte; + LZWEncoderNode *next; // next sibling + LZWEncoderNode *children; // first child +}; + +class LZWEncoder: public FilterStream { +public: + + LZWEncoder(Stream *strA); + virtual ~LZWEncoder(); + virtual StreamKind getKind() { return strWeird; } + virtual void reset(); + virtual int getChar(); + virtual int lookChar(); + virtual GString *getPSFilter(int psLevel, const char *indent) + { return NULL; } + virtual GBool isBinary(GBool last = gTrue) { return gTrue; } + virtual GBool isEncoder() { return gTrue; } + +private: + + LZWEncoderNode table[4096]; + int nextSeq; + int codeLen; + Guchar inBuf[4096]; + int inBufLen; + int outBuf; + int outBufLen; + GBool needEOD; + + void fillBuf(); +}; + #endif diff -uNrp xpdf-3.03/xpdf/TextOutputDev.cc xpdf-3.04/xpdf/TextOutputDev.cc --- xpdf-3.03/xpdf/TextOutputDev.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/TextOutputDev.cc 2014-05-28 20:50:50.000000000 +0200 @@ -33,82 +33,48 @@ #include "Link.h" #include "TextOutputDev.h" -#ifdef MACOS -// needed for setting type/creator of MacOS files -#include "ICSupport.h" -#endif - //------------------------------------------------------------------------ // parameters //------------------------------------------------------------------------ -// Each bucket in a text pool includes baselines within a range of -// this many points. -#define textPoolStep 4 - -// Inter-character space width which will cause addChar to start a new -// word. -#define minWordBreakSpace 0.1 - -// Negative inter-character space width, i.e., overlap, which will -// cause addChar to start a new word. -#define minDupBreakOverlap 0.2 - -// Max distance between baselines of two lines within a block, as a -// fraction of the font size. -#define maxLineSpacingDelta 1.5 - -// Max difference in primary font sizes on two lines in the same -// block. Delta1 is used when examining new lines above and below the -// current block; delta2 is used when examining text that overlaps the -// current block; delta3 is used when examining text to the left and -// right of the current block. -#define maxBlockFontSizeDelta1 0.05 -#define maxBlockFontSizeDelta2 0.6 -#define maxBlockFontSizeDelta3 0.2 - -// Max difference in font sizes inside a word. -#define maxWordFontSizeDelta 0.05 - -// Maximum distance between baselines of two words on the same line, -// e.g., distance between subscript or superscript and the primary -// baseline, as a fraction of the font size. -#define maxIntraLineDelta 0.5 - -// Minimum inter-word spacing, as a fraction of the font size. (Only -// used for raw ordering.) -#define minWordSpacing 0.15 - -// Maximum inter-word spacing, as a fraction of the font size. -#define maxWordSpacing 1.5 - -// Maximum horizontal spacing which will allow a word to be pulled -// into a block. -#define minColSpacing1 0.3 - -// Minimum spacing between columns, as a fraction of the font size. -#define minColSpacing2 1.0 - -// Maximum vertical spacing between blocks within a flow, as a -// multiple of the font size. -#define maxBlockSpacing 2.5 - -// Minimum spacing between characters within a word, as a fraction of -// the font size. -#define minCharSpacing -0.2 - -// Maximum spacing between characters within a word, as a fraction of -// the font size, when there is no obvious extra-wide character -// spacing. -#define maxCharSpacing 0.03 - -// When extra-wide character spacing is detected, the inter-character -// space threshold is set to the minimum inter-character space -// multiplied by this constant. -#define maxWideCharSpacingMul 1.3 - -// Upper limit on spacing between characters in a word. -#define maxWideCharSpacing 0.4 +// Size of bins used for horizontal and vertical profiles is +// splitPrecisionMul * minFontSize. +#define splitPrecisionMul 0.05 + +// Minimum allowed split precision. +#define minSplitPrecision 0.01 + +// yMin and yMax (or xMin and xMax for rot=1,3) are adjusted by this +// fraction of the text height, to allow for slightly overlapping +// lines (or large ascent/descent values). +#define ascentAdjustFactor 0 +#define descentAdjustFactor 0.35 + +// Gaps larger than max{gap} - splitGapSlack * avgFontSize are +// considered to be equivalent. +#define splitGapSlack 0.2 + +// The vertical gap threshold (minimum gap required to split +// vertically) depends on the (approximate) number of lines in the +// block: +// threshold = (max + slope * nLines) * avgFontSize +// with a min value of vertGapThresholdMin * avgFontSize. +#define vertGapThresholdMin 0.8 +#define vertGapThresholdMax 3 +#define vertGapThresholdSlope -0.5 + +// Vertical gap threshold for table mode. +#define vertGapThresholdTableMin 0.2 +#define vertGapThresholdTableMax 0.5 +#define vertGapThresholdTableSlope -0.02 + +// A large character has a font size larger than +// largeCharThreshold * avgFontSize. +#define largeCharThreshold 1.5 + +// A block will be split vertically only if the resulting chunk +// widths are greater than vertSplitChunkThreshold * avgFontSize. +#define vertSplitChunkThreshold 2 // Max difference in primary,secondary coordinates (as a fraction of // the font size) allowed for duplicated text (fake boldface, drop @@ -116,24 +82,293 @@ #define dupMaxPriDelta 0.1 #define dupMaxSecDelta 0.2 +// Inter-character spacing that varies by less than this multiple of +// font size is assumed to be equivalent. +#define uniformSpacing 0.07 + +// 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 + +// Minimum paragraph indent from left margin, as a fraction of font +// size. +#define minParagraphIndent 0.5 + +// If the space between two lines is greater than +// paragraphSpacingThreshold * avgLineSpacing, start a new paragraph. +#define paragraphSpacingThreshold 1.2 + +// If font size changes by at least this much (measured in points) +// between lines, start a new paragraph. +#define paragraphFontSizeDelta 1 + +// Spaces at the start of a line in physical layout mode are this wide +// (as a multiple of font size). +#define physLayoutSpaceWidth 0.33 + +// 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 + +// Primary axis delta which will cause a line break in raw mode +// (as a fraction of font size). +#define rawModeLineDelta 0.5 + +// Secondary axis delta which will cause a word break in raw mode +// (as a fraction of font size). +#define rawModeWordSpacing 0.15 + +// Secondary axis overlap which will cause a line break in raw mode +// (as a fraction of font size). +#define rawModeCharOverlap 0.2 + +// Max spacing (as a multiple of font size) allowed between the end of +// a line and a clipped character to be included in that line. +#define clippedTextMaxWordSpace 0.5 + // Max width of underlines (in points). #define maxUnderlineWidth 3 -// Min distance between baseline and underline (in points). -//~ this should be font-size-dependent -#define minUnderlineGap -2 - -// Max distance between baseline and underline (in points). -//~ this should be font-size-dependent -#define maxUnderlineGap 4 - // Max horizontal distance between edge of word and start of underline -// (in points). -//~ this should be font-size-dependent -#define underlineSlack 1 +// (as a fraction of font size). +#define underlineSlack 0.2 + +// Max vertical distance between baseline of word and start of +// underline (as a fraction of font size). +#define underlineBaselineSlack 0.2 + +// Max distance between edge of text and edge of link border (as a +// fraction of font size). +#define hyperlinkSlack 0.2 + +//------------------------------------------------------------------------ +// TextChar +//------------------------------------------------------------------------ + +class TextChar { +public: + + TextChar(Unicode cA, int charPosA, int charLenA, + double xMinA, double yMinA, double xMaxA, double yMaxA, + int rotA, 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); + + Unicode c; + int charPos; + int charLen; + double xMin, yMin, xMax, yMax; + Guchar rot; + char clipped; + char invisible; + TextFontInfo *font; + double fontSize; + double colorR, + colorG, + colorB; +}; + +TextChar::TextChar(Unicode cA, int charPosA, int charLenA, + double xMinA, double yMinA, double xMaxA, double yMaxA, + int rotA, GBool clippedA, GBool invisibleA, + TextFontInfo *fontA, double fontSizeA, + double colorRA, double colorGA, double colorBA) { + double t; + + c = cA; + charPos = charPosA; + charLen = charLenA; + xMin = xMinA; + yMin = yMinA; + xMax = xMaxA; + yMax = yMaxA; + // this can happen with vertical writing mode, or with odd values + // for the char/word spacing parameters + if (xMin > xMax) { + t = xMin; xMin = xMax; xMax = t; + } + if (yMin > yMax) { + t = yMin; yMin = yMax; yMax = t; + } + rot = (Guchar)rotA; + clipped = (char)clippedA; + invisible = (char)invisibleA; + font = fontA; + fontSize = fontSizeA; + colorR = colorRA; + colorG = colorGA; + colorB = colorBA; +} + +int TextChar::cmpX(const void *p1, const void *p2) { + const TextChar *ch1 = *(const TextChar **)p1; + const TextChar *ch2 = *(const TextChar **)p2; + + if (ch1->xMin < ch2->xMin) { + return -1; + } else if (ch1->xMin > ch2->xMin) { + return 1; + } else { + return 0; + } +} + +int TextChar::cmpY(const void *p1, const void *p2) { + const TextChar *ch1 = *(const TextChar **)p1; + const TextChar *ch2 = *(const TextChar **)p2; + + if (ch1->yMin < ch2->yMin) { + return -1; + } else if (ch1->yMin > ch2->yMin) { + return 1; + } else { + return 0; + } +} + +//------------------------------------------------------------------------ +// TextBlock +//------------------------------------------------------------------------ + +enum TextBlockType { + blkVertSplit, + blkHorizSplit, + blkLeaf +}; + +enum TextBlockTag { + blkTagMulticolumn, + blkTagColumn, + blkTagLine +}; + +class TextBlock { +public: + + TextBlock(TextBlockType typeA, int rotA); + ~TextBlock(); + void addChild(TextBlock *child); + void addChild(TextChar *child); + void prependChild(TextChar *child); + void updateBounds(int childIdx); + + TextBlockType type; + TextBlockTag tag; + int rot; + double xMin, yMin, xMax, yMax; + GBool smallSplit; // true for blkVertSplit/blkHorizSplit + // where the gap size is small + GList *children; // for blkLeaf, children are TextWord; + // for others, children are TextBlock +}; + +TextBlock::TextBlock(TextBlockType typeA, int rotA) { + type = typeA; + tag = blkTagMulticolumn; + rot = rotA; + xMin = yMin = xMax = yMax = 0; + smallSplit = gFalse; + children = new GList(); +} + +TextBlock::~TextBlock() { + if (type == blkLeaf) { + delete children; + } else { + deleteGList(children, TextBlock); + } +} + +void TextBlock::addChild(TextBlock *child) { + if (children->getLength() == 0) { + xMin = child->xMin; + yMin = child->yMin; + xMax = child->xMax; + yMax = child->yMax; + } else { + if (child->xMin < xMin) { + xMin = child->xMin; + } + if (child->yMin < yMin) { + yMin = child->yMin; + } + if (child->xMax > xMax) { + xMax = child->xMax; + } + if (child->yMax > yMax) { + yMax = child->yMax; + } + } + children->append(child); +} + +void TextBlock::addChild(TextChar *child) { + if (children->getLength() == 0) { + xMin = child->xMin; + yMin = child->yMin; + xMax = child->xMax; + yMax = child->yMax; + } else { + if (child->xMin < xMin) { + xMin = child->xMin; + } + if (child->yMin < yMin) { + yMin = child->yMin; + } + if (child->xMax > xMax) { + xMax = child->xMax; + } + if (child->yMax > yMax) { + yMax = child->yMax; + } + } + children->append(child); +} + +void TextBlock::prependChild(TextChar *child) { + if (children->getLength() == 0) { + xMin = child->xMin; + yMin = child->yMin; + xMax = child->xMax; + yMax = child->yMax; + } else { + if (child->xMin < xMin) { + xMin = child->xMin; + } + if (child->yMin < yMin) { + yMin = child->yMin; + } + if (child->xMax > xMax) { + xMax = child->xMax; + } + if (child->yMax > yMax) { + yMax = child->yMax; + } + } + children->insert(0, child); +} -// Max distance between edge of text and edge of link border -#define hyperlinkSlack 2 +void TextBlock::updateBounds(int childIdx) { + TextBlock *child; + + child = (TextBlock *)children->get(childIdx); + if (child->xMin < xMin) { + xMin = child->xMin; + } + if (child->yMin < yMin) { + yMin = child->yMin; + } + if (child->xMax > xMax) { + xMax = child->xMax; + } + if (child->yMax > yMax) { + yMax = child->yMax; + } +} //------------------------------------------------------------------------ // TextUnderline @@ -157,159 +392,202 @@ public: class TextLink { public: - TextLink(int xMinA, int yMinA, int xMaxA, int yMaxA, Link *linkA) - { xMin = xMinA; yMin = yMinA; xMax = xMaxA; yMax = yMaxA; link = linkA; } - ~TextLink() {} + TextLink(double xMinA, double yMinA, double xMaxA, double yMaxA, + GString *uriA) + { xMin = xMinA; yMin = yMinA; xMax = xMaxA; yMax = yMaxA; uri = uriA; } + ~TextLink(); - int xMin, yMin, xMax, yMax; - Link *link; + double xMin, yMin, xMax, yMax; + GString *uri; }; +TextLink::~TextLink() { + if (uri) { + delete uri; + } +} + +//------------------------------------------------------------------------ +// TextOutputControl +//------------------------------------------------------------------------ + +TextOutputControl::TextOutputControl() { + mode = textOutReadingOrder; + fixedPitch = 0; + fixedLineSpacing = 0; + html = gFalse; + clipText = gFalse; +} + + //------------------------------------------------------------------------ // TextFontInfo //------------------------------------------------------------------------ TextFontInfo::TextFontInfo(GfxState *state) { + GfxFont *gfxFont; + gfxFont = state->getFont(); -#if TEXTOUT_WORD_LIST + if (gfxFont) { + fontID = *gfxFont->getID(); + ascent = gfxFont->getAscent(); + descent = gfxFont->getDescent(); + // "odd" ascent/descent values cause trouble more often than not + // (in theory these could be legitimate values for oddly designed + // fonts -- but they are more often due to buggy PDF generators) + // (values that are too small are a different issue -- those seem + // to be more commonly legitimate) + if (ascent > 1) { + ascent = 0.75; + } + if (descent < -0.5) { + descent = -0.25; + } + } else { + fontID.num = -1; + fontID.gen = -1; + ascent = 0.75; + descent = -0.25; + } fontName = (gfxFont && gfxFont->getName()) ? gfxFont->getName()->copy() : (GString *)NULL; flags = gfxFont ? gfxFont->getFlags() : 0; -#endif + mWidth = 0; + if (gfxFont && !gfxFont->isCIDFont()) { + char *name; + int code; + for (code = 0; code < 256; ++code) { + if ((name = ((Gfx8BitFont *)gfxFont)->getCharName(code)) && + name[0] == 'm' && name[1] == '\0') { + mWidth = ((Gfx8BitFont *)gfxFont)->getWidth(code); + break; + } + } + } } TextFontInfo::~TextFontInfo() { -#if TEXTOUT_WORD_LIST if (fontName) { delete fontName; } -#endif } GBool TextFontInfo::matches(GfxState *state) { - return state->getFont() == gfxFont; + Ref *id; + + if (!state->getFont()) { + return gFalse; + } + id = state->getFont()->getID(); + return id->num == fontID.num && id->gen == fontID.gen; } //------------------------------------------------------------------------ // TextWord //------------------------------------------------------------------------ -TextWord::TextWord(GfxState *state, int rotA, double x0, double y0, - TextFontInfo *fontA, double fontSizeA) { - GfxFont *gfxFont; - double x, y, ascent, descent; - int wMode; +// 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, GBool spaceAfterA) { + TextChar *ch; + int i; rot = rotA; - font = fontA; - fontSize = fontSizeA; - state->transform(x0, y0, &x, &y); - if ((gfxFont = font->gfxFont)) { - ascent = gfxFont->getAscent() * fontSize; - descent = gfxFont->getDescent() * fontSize; - wMode = gfxFont->getWMode(); - } else { - // this means that the PDF file draws text without a current font, - // which should never happen - ascent = 0.95 * fontSize; - descent = -0.35 * fontSize; - wMode = 0; - } - if (wMode) { // vertical writing mode - // NB: the rotation value has been incremented by 1 (in - // TextPage::beginWord()) for vertical writing mode - switch (rot) { - case 0: - yMin = y - fontSize; - yMax = y; - base = y; - break; - case 1: - xMin = x; - xMax = x + fontSize; - base = x; - break; - case 2: - yMin = y; - yMax = y + fontSize; - base = y; - break; - case 3: - xMin = x - fontSize; - xMax = x; - base = x; - break; + len = lenA; + text = (Unicode *)gmallocn(len, sizeof(Unicode)); + edge = (double *)gmallocn(len + 1, sizeof(double)); + charPos = (int *)gmallocn(len + 1, sizeof(int)); + switch (rot) { + case 0: + default: + ch = (TextChar *)chars->get(start); + xMin = ch->xMin; + yMin = ch->yMin; + yMax = ch->yMax; + ch = (TextChar *)chars->get(start + len - 1); + xMax = ch->xMax; + break; + case 1: + ch = (TextChar *)chars->get(start); + xMin = ch->xMin; + xMax = ch->xMax; + yMin = ch->yMin; + ch = (TextChar *)chars->get(start + len - 1); + yMax = ch->yMax; + break; + case 2: + ch = (TextChar *)chars->get(start); + xMax = ch->xMax; + yMin = ch->yMin; + yMax = ch->yMax; + ch = (TextChar *)chars->get(start + len - 1); + xMin = ch->xMin; + break; + case 3: + ch = (TextChar *)chars->get(start); + xMin = ch->xMin; + xMax = ch->xMax; + yMax = ch->yMax; + ch = (TextChar *)chars->get(start + len - 1); + yMin = ch->yMin; + break; + } + for (i = 0; i < len; ++i) { + ch = (TextChar *)chars->get(rot >= 2 ? start + len - 1 - i : start + i); + text[i] = ch->c; + charPos[i] = ch->charPos; + if (i == len - 1) { + charPos[len] = ch->charPos + ch->charLen; } - } else { // horizontal writing mode switch (rot) { case 0: - yMin = y - ascent; - yMax = y - descent; - if (yMin == yMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - yMin = y; - yMax = y + 1; + default: + edge[i] = ch->xMin; + if (i == len - 1) { + edge[len] = ch->xMax; } - base = y; break; case 1: - xMin = x + descent; - xMax = x + ascent; - if (xMin == xMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - xMin = x; - xMax = x + 1; + edge[i] = ch->yMin; + if (i == len - 1) { + edge[len] = ch->yMax; } - base = x; break; case 2: - yMin = y + descent; - yMax = y + ascent; - if (yMin == yMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - yMin = y; - yMax = y + 1; + edge[i] = ch->xMax; + if (i == len - 1) { + edge[len] = ch->xMin; } - base = y; break; case 3: - xMin = x - ascent; - xMax = x - descent; - if (xMin == xMax) { - // this is a sanity check for a case that shouldn't happen -- but - // if it does happen, we want to avoid dividing by zero later - xMin = x; - xMax = x + 1; + edge[i] = ch->yMax; + if (i == len - 1) { + edge[len] = ch->yMin; } - base = x; break; } } - text = NULL; - edge = NULL; - charPos = NULL; - len = size = 0; - spaceAfter = gFalse; - next = NULL; - -#if TEXTOUT_WORD_LIST - GfxRGB rgb; - - if ((state->getRender() & 3) == 1) { - state->getStrokeRGB(&rgb); - } else { - state->getFillRGB(&rgb); - } - colorR = colToDbl(rgb.r); - colorG = colToDbl(rgb.g); - colorB = colToDbl(rgb.b); -#endif - + ch = (TextChar *)chars->get(start); + font = ch->font; + fontSize = ch->fontSize; + spaceAfter = spaceAfterA; underlined = gFalse; link = NULL; + colorR = ch->colorR; + colorG = ch->colorG; + colorB = ch->colorB; + invisible = ch->invisible; +} + +TextWord::TextWord(TextWord *word) { + *this = *word; + text = (Unicode *)gmallocn(len, sizeof(Unicode)); + memcpy(text, word->text, len * sizeof(Unicode)); + edge = (double *)gmallocn(len + 1, sizeof(double)); + memcpy(edge, word->edge, (len + 1) * sizeof(double)); + charPos = (int *)gmallocn(len + 1, sizeof(int)); + memcpy(charPos, word->charPos, (len + 1) * sizeof(int)); } TextWord::~TextWord() { @@ -318,175 +596,65 @@ TextWord::~TextWord() { gfree(charPos); } -void TextWord::addChar(GfxState *state, double x, double y, - double dx, double dy, int charPosA, int charLen, - Unicode u) { - int wMode; - - if (len == size) { - size += 16; - text = (Unicode *)greallocn(text, size, sizeof(Unicode)); - edge = (double *)greallocn(edge, size + 1, sizeof(double)); - charPos = (int *)greallocn(charPos, size + 1, sizeof(int)); - } - text[len] = u; - charPos[len] = charPosA; - charPos[len + 1] = charPosA + charLen; - wMode = font->gfxFont ? font->gfxFont->getWMode() : 0; - if (wMode) { // vertical writing mode - // NB: the rotation value has been incremented by 1 (in - // TextPage::beginWord()) for vertical writing mode - switch (rot) { - case 0: - if (len == 0) { - xMin = x - fontSize; - } - edge[len] = x - fontSize; - xMax = edge[len+1] = x; - break; - case 1: - if (len == 0) { - yMin = y - fontSize; - } - edge[len] = y - fontSize; - yMax = edge[len+1] = y; - break; - case 2: - if (len == 0) { - xMax = x + fontSize; - } - edge[len] = x + fontSize; - xMin = edge[len+1] = x; - break; - case 3: - if (len == 0) { - yMax = y + fontSize; - } - edge[len] = y + fontSize; - yMin = edge[len+1] = y; - break; - } - } else { // horizontal writing mode - switch (rot) { - case 0: - if (len == 0) { - xMin = x; - } - edge[len] = x; - xMax = edge[len+1] = x + dx; - break; - case 1: - if (len == 0) { - yMin = y; - } - edge[len] = y; - yMax = edge[len+1] = y + dy; - break; - case 2: - if (len == 0) { - xMax = x; - } - edge[len] = x; - xMin = edge[len+1] = x + dx; - break; - case 3: - if (len == 0) { - yMax = y; - } - edge[len] = y; - yMin = edge[len+1] = y + dy; - break; - } - } - ++len; -} - -void TextWord::merge(TextWord *word) { - int i; - - if (word->xMin < xMin) { - xMin = word->xMin; - } - if (word->yMin < yMin) { - yMin = word->yMin; - } - if (word->xMax > xMax) { - xMax = word->xMax; - } - if (word->yMax > yMax) { - yMax = word->yMax; - } - if (len + word->len > size) { - size = len + word->len; - text = (Unicode *)greallocn(text, size, sizeof(Unicode)); - edge = (double *)greallocn(edge, size + 1, sizeof(double)); - charPos = (int *)greallocn(charPos, size + 1, sizeof(int)); - } - for (i = 0; i < word->len; ++i) { - text[len + i] = word->text[i]; - edge[len + i] = word->edge[i]; - charPos[len + i] = word->charPos[i]; - } - edge[len + word->len] = word->edge[word->len]; - charPos[len + word->len] = word->charPos[word->len]; - len += word->len; -} - -inline int TextWord::primaryCmp(TextWord *word) { - double cmp; - - cmp = 0; // make gcc happy - switch (rot) { - case 0: - cmp = xMin - word->xMin; - break; - case 1: - cmp = yMin - word->yMin; - break; - case 2: - cmp = word->xMax - xMax; - break; - case 3: - cmp = word->yMax - yMax; - break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} - -double TextWord::primaryDelta(TextWord *word) { - double delta; - - delta = 0; // make gcc happy +// This is used to append a clipped character to a word. +void TextWord::appendChar(TextChar *ch) { + if (ch->xMin < xMin) { + xMin = ch->xMin; + } + if (ch->xMax > xMax) { + xMax = ch->xMax; + } + if (ch->yMin < yMin) { + yMin = ch->yMin; + } + if (ch->yMax > yMax) { + yMax = ch->yMax; + } + text = (Unicode *)greallocn(text, len + 1, sizeof(Unicode)); + edge = (double *)greallocn(edge, len + 2, sizeof(double)); + charPos = (int *)greallocn(charPos, len + 2, sizeof(int)); + text[len] = ch->c; + charPos[len] = ch->charPos; + charPos[len+1] = ch->charPos + ch->charLen; switch (rot) { case 0: - delta = word->xMin - xMax; + default: + edge[len] = ch->xMin; + edge[len+1] = ch->xMax; break; case 1: - delta = word->yMin - yMax; + edge[len] = ch->yMin; + edge[len+1] = ch->yMax; break; case 2: - delta = xMin - word->xMax; + edge[len] = ch->xMax; + edge[len+1] = ch->xMin; break; case 3: - delta = yMin - word->yMax; + edge[len] = ch->yMax; + edge[len+1] = ch->yMin; break; } - return delta; + ++len; } int TextWord::cmpYX(const void *p1, const void *p2) { - TextWord *word1 = *(TextWord **)p1; - TextWord *word2 = *(TextWord **)p2; + const TextWord *word1 = *(const TextWord **)p1; + const TextWord *word2 = *(const TextWord **)p2; double cmp; - cmp = word1->yMin - word2->yMin; - if (cmp == 0) { + if ((cmp = word1->yMin - word2->yMin) == 0) { cmp = word1->xMin - word2->xMin; } return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; } -#if TEXTOUT_WORD_LIST +int TextWord::cmpCharPos(const void *p1, const void *p2) { + const TextWord *word1 = *(const TextWord **)p1; + const TextWord *word2 = *(const TextWord **)p2; + + return word1->charPos[0] - word2->charPos[0]; +} GString *TextWord::getText() { GString *s; @@ -539,2591 +707,2931 @@ void TextWord::getCharBBox(int charIdx, } } -#endif // TEXTOUT_WORD_LIST - -//------------------------------------------------------------------------ -// TextPool -//------------------------------------------------------------------------ - -TextPool::TextPool() { - minBaseIdx = 0; - maxBaseIdx = -1; - pool = NULL; - cursor = NULL; - cursorBaseIdx = -1; -} - -TextPool::~TextPool() { - int baseIdx; - TextWord *word, *word2; - - for (baseIdx = minBaseIdx; baseIdx <= maxBaseIdx; ++baseIdx) { - for (word = pool[baseIdx - minBaseIdx]; word; word = word2) { - word2 = word->next; - delete word; - } - } - gfree(pool); -} - -int TextPool::getBaseIdx(double base) { - int baseIdx; - - baseIdx = (int)(base / textPoolStep); - if (baseIdx < minBaseIdx) { - return minBaseIdx; - } - if (baseIdx > maxBaseIdx) { - return maxBaseIdx; - } - return baseIdx; -} - -void TextPool::addWord(TextWord *word) { - TextWord **newPool; - int wordBaseIdx, newMinBaseIdx, newMaxBaseIdx, baseIdx; - TextWord *w0, *w1; - - // expand the array if needed - wordBaseIdx = (int)(word->base / textPoolStep); - if (minBaseIdx > maxBaseIdx) { - minBaseIdx = wordBaseIdx - 128; - maxBaseIdx = wordBaseIdx + 128; - pool = (TextWord **)gmallocn(maxBaseIdx - minBaseIdx + 1, - sizeof(TextWord *)); - for (baseIdx = minBaseIdx; baseIdx <= maxBaseIdx; ++baseIdx) { - pool[baseIdx - minBaseIdx] = NULL; - } - } else if (wordBaseIdx < minBaseIdx) { - newMinBaseIdx = wordBaseIdx - 128; - newPool = (TextWord **)gmallocn(maxBaseIdx - newMinBaseIdx + 1, - sizeof(TextWord *)); - for (baseIdx = newMinBaseIdx; baseIdx < minBaseIdx; ++baseIdx) { - newPool[baseIdx - newMinBaseIdx] = NULL; - } - memcpy(&newPool[minBaseIdx - newMinBaseIdx], pool, - (maxBaseIdx - minBaseIdx + 1) * sizeof(TextWord *)); - gfree(pool); - pool = newPool; - minBaseIdx = newMinBaseIdx; - } else if (wordBaseIdx > maxBaseIdx) { - newMaxBaseIdx = wordBaseIdx + 128; - pool = (TextWord **)greallocn(pool, newMaxBaseIdx - minBaseIdx + 1, - sizeof(TextWord *)); - for (baseIdx = maxBaseIdx + 1; baseIdx <= newMaxBaseIdx; ++baseIdx) { - pool[baseIdx - minBaseIdx] = NULL; - } - maxBaseIdx = newMaxBaseIdx; - } - - // insert the new word - if (cursor && wordBaseIdx == cursorBaseIdx && - word->primaryCmp(cursor) >= 0) { - w0 = cursor; - w1 = cursor->next; - } else { - w0 = NULL; - w1 = pool[wordBaseIdx - minBaseIdx]; - } - for (; w1 && word->primaryCmp(w1) > 0; w0 = w1, w1 = w1->next) ; - word->next = w1; - if (w0) { - w0->next = word; - } else { - pool[wordBaseIdx - minBaseIdx] = word; +double TextWord::getBaseline() { + switch (rot) { + case 0: + default: + return yMax + fontSize * font->descent; + case 1: + return xMin - fontSize * font->descent; + case 2: + return yMin - fontSize * font->descent; + case 3: + return xMax + fontSize * font->descent; } - cursor = word; - cursorBaseIdx = wordBaseIdx; +} + +GString *TextWord::getLinkURI() { + return link ? link->uri : (GString *)NULL; } //------------------------------------------------------------------------ // TextLine //------------------------------------------------------------------------ -TextLine::TextLine(TextBlock *blkA, int rotA, double baseA) { - blk = blkA; - rot = rotA; - xMin = yMin = 0; - xMax = yMax = -1; - base = baseA; - words = lastWord = NULL; - text = NULL; - edge = NULL; - col = NULL; - len = 0; - convertedLen = 0; - hyphenated = gFalse; - next = NULL; -} - -TextLine::~TextLine() { +TextLine::TextLine(GList *wordsA, double xMinA, double yMinA, + double xMaxA, double yMaxA, double fontSizeA) { TextWord *word; + int i, j, k; - while (words) { - word = words; - words = words->next; - delete word; - } - gfree(text); - gfree(edge); - gfree(col); -} - -void TextLine::addWord(TextWord *word) { - if (lastWord) { - lastWord->next = word; - } else { - words = word; - } - lastWord = word; + words = wordsA; + rot = 0; + xMin = xMinA; + yMin = yMinA; + xMax = xMaxA; + yMax = yMaxA; + fontSize = fontSizeA; + px = 0; + pw = 0; - if (xMin > xMax) { - xMin = word->xMin; - xMax = word->xMax; - yMin = word->yMin; - yMax = word->yMax; - } else { - if (word->xMin < xMin) { - xMin = word->xMin; - } - if (word->xMax > xMax) { - xMax = word->xMax; - } - if (word->yMin < yMin) { - yMin = word->yMin; + // build the text + len = 0; + for (i = 0; i < words->getLength(); ++i) { + word = (TextWord *)words->get(i); + len += word->len; + if (word->spaceAfter) { + ++len; } - if (word->yMax > yMax) { - yMax = word->yMax; + } + text = (Unicode *)gmallocn(len, sizeof(Unicode)); + edge = (double *)gmallocn(len + 1, sizeof(double)); + j = 0; + for (i = 0; i < words->getLength(); ++i) { + word = (TextWord *)words->get(i); + if (i == 0) { + rot = word->rot; + } + for (k = 0; k < word->len; ++k) { + text[j] = word->text[k]; + edge[j] = word->edge[k]; + ++j; + } + edge[j] = word->edge[word->len]; + if (word->spaceAfter) { + text[j] = (Unicode)0x0020; + ++j; + edge[j] = edge[j - 1]; } } + //~ need to check for other Unicode chars used as hyphens + hyphenated = text[len - 1] == (Unicode)'-'; } -double TextLine::primaryDelta(TextLine *line) { - double delta; +TextLine::~TextLine() { + deleteGList(words, TextWord); + gfree(text); + gfree(edge); +} - delta = 0; // make gcc happy +double TextLine::getBaseline() { + TextWord *word0; + + word0 = (TextWord *)words->get(0); switch (rot) { case 0: - delta = line->xMin - xMax; - break; + default: + return yMax + fontSize * word0->font->descent; case 1: - delta = line->yMin - yMax; - break; + return xMin - fontSize * word0->font->descent; case 2: - delta = xMin - line->xMax; - break; + return yMin - fontSize * word0->font->descent; case 3: - delta = yMin - line->yMax; - break; + return xMax + fontSize * word0->font->descent; } - return delta; } -int TextLine::primaryCmp(TextLine *line) { - double cmp; +//------------------------------------------------------------------------ +// TextParagraph +//------------------------------------------------------------------------ - cmp = 0; // make gcc happy - switch (rot) { - case 0: - cmp = xMin - line->xMin; - break; - case 1: - cmp = yMin - line->yMin; - break; - case 2: - cmp = line->xMax - xMax; - break; - case 3: - cmp = line->yMax - yMax; - break; +TextParagraph::TextParagraph(GList *linesA) { + TextLine *line; + int i; + + lines = linesA; + xMin = yMin = xMax = yMax = 0; + for (i = 0; i < lines->getLength(); ++i) { + line = (TextLine *)lines->get(i); + if (i == 0 || line->xMin < xMin) { + xMin = line->xMin; + } + if (i == 0 || line->yMin < yMin) { + yMin = line->yMin; + } + if (i == 0 || line->xMax > xMax) { + xMax = line->xMax; + } + if (i == 0 || line->yMax > yMax) { + yMax = line->yMax; + } } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; } -int TextLine::secondaryCmp(TextLine *line) { - double cmp; - - cmp = (rot == 0 || rot == 3) ? base - line->base : line->base - base; - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; +TextParagraph::~TextParagraph() { + deleteGList(lines, TextLine); } -int TextLine::cmpYX(TextLine *line) { - int cmp; +//------------------------------------------------------------------------ +// TextColumn +//------------------------------------------------------------------------ - if ((cmp = secondaryCmp(line))) { - return cmp; +TextColumn::TextColumn(GList *paragraphsA, double xMinA, double yMinA, + double xMaxA, double yMaxA) { + paragraphs = paragraphsA; + xMin = xMinA; + yMin = yMinA; + xMax = xMaxA; + yMax = yMaxA; + px = py = 0; + pw = ph = 0; +} + +TextColumn::~TextColumn() { + deleteGList(paragraphs, TextParagraph); +} + +int TextColumn::cmpX(const void *p1, const void *p2) { + const TextColumn *col1 = *(const TextColumn **)p1; + const TextColumn *col2 = *(const TextColumn **)p2; + + if (col1->xMin < col2->xMin) { + return -1; + } else if (col1->xMin > col2->xMin) { + return 1; + } else { + return 0; } - return primaryCmp(line); } -int TextLine::cmpXY(const void *p1, const void *p2) { - TextLine *line1 = *(TextLine **)p1; - TextLine *line2 = *(TextLine **)p2; - int cmp; +int TextColumn::cmpY(const void *p1, const void *p2) { + const TextColumn *col1 = *(const TextColumn **)p1; + const TextColumn *col2 = *(const TextColumn **)p2; - if ((cmp = line1->primaryCmp(line2))) { - return cmp; + if (col1->yMin < col2->yMin) { + return -1; + } else if (col1->yMin > col2->yMin) { + return 1; + } else { + return 0; } - return line1->secondaryCmp(line2); } -void TextLine::coalesce(UnicodeMap *uMap) { - TextWord *word0, *word1; - double space, delta, minSpace; - GBool isUnicode; - char buf[8]; - int i, j; +int TextColumn::cmpPX(const void *p1, const void *p2) { + const TextColumn *col1 = *(const TextColumn **)p1; + const TextColumn *col2 = *(const TextColumn **)p2; - if (words->next) { + if (col1->px < col2->px) { + return -1; + } else if (col1->px > col2->px) { + return 1; + } else { + return 0; + } +} - // compute the inter-word space threshold - if (words->len > 1 || words->next->len > 1) { - minSpace = 0; - } else { - minSpace = words->primaryDelta(words->next); - for (word0 = words->next, word1 = word0->next; - word1 && minSpace > 0; - word0 = word1, word1 = word0->next) { - if (word1->len > 1) { - minSpace = 0; - } - delta = word0->primaryDelta(word1); - if (delta < minSpace) { - minSpace = delta; - } - } - } - if (minSpace <= 0) { - space = maxCharSpacing * words->fontSize; - } else { - space = maxWideCharSpacingMul * minSpace; - if (space > maxWideCharSpacing * words->fontSize) { - space = maxWideCharSpacing * words->fontSize; - } - } +//------------------------------------------------------------------------ +// TextWordList +//------------------------------------------------------------------------ - // merge words - word0 = words; - word1 = words->next; - while (word1) { - if (word0->primaryDelta(word1) >= space) { - word0->spaceAfter = gTrue; - word0 = word1; - word1 = word1->next; - } else if (word0->font == word1->font && - word0->underlined == word1->underlined && - fabs(word0->fontSize - word1->fontSize) < - maxWordFontSizeDelta * words->fontSize && - word1->charPos[0] == word0->charPos[word0->len]) { - word0->merge(word1); - word0->next = word1->next; - delete word1; - word1 = word0->next; - } else { - word0 = word1; - word1 = word1->next; - } - } - } +TextWordList::TextWordList(GList *wordsA) { + words = wordsA; +} - // build the line text - isUnicode = uMap ? uMap->isUnicode() : gFalse; - len = 0; - for (word1 = words; word1; word1 = word1->next) { - len += word1->len; - if (word1->spaceAfter) { - ++len; - } - } - text = (Unicode *)gmallocn(len, sizeof(Unicode)); - edge = (double *)gmallocn(len + 1, sizeof(double)); - i = 0; - for (word1 = words; word1; word1 = word1->next) { - for (j = 0; j < word1->len; ++j) { - text[i] = word1->text[j]; - edge[i] = word1->edge[j]; - ++i; - } - edge[i] = word1->edge[word1->len]; - if (word1->spaceAfter) { - text[i] = (Unicode)0x0020; - ++i; - } - } +TextWordList::~TextWordList() { + deleteGList(words, TextWord); +} - // compute convertedLen and set up the col array - col = (int *)gmallocn(len + 1, sizeof(int)); - convertedLen = 0; - for (i = 0; i < len; ++i) { - col[i] = convertedLen; - if (isUnicode) { - ++convertedLen; - } else if (uMap) { - convertedLen += uMap->mapUnicode(text[i], buf, sizeof(buf)); - } - } - col[len] = convertedLen; +int TextWordList::getLength() { + return words->getLength(); +} - // check for hyphen at end of line - //~ need to check for other chars used as hyphens - hyphenated = text[len - 1] == (Unicode)'-'; +TextWord *TextWordList::get(int idx) { + if (idx < 0 || idx >= words->getLength()) { + return NULL; + } + return (TextWord *)words->get(idx); } //------------------------------------------------------------------------ -// TextLineFrag +// TextPage //------------------------------------------------------------------------ -class TextLineFrag { -public: +TextPage::TextPage(TextOutputControl *controlA) { + control = *controlA; + pageWidth = pageHeight = 0; + charPos = 0; + curFont = NULL; + curFontSize = 0; + curRot = 0; + nTinyChars = 0; + actualText = NULL; + actualTextLen = 0; + actualTextX0 = 0; + actualTextY0 = 0; + actualTextX1 = 0; + actualTextY1 = 0; + actualTextNBytes = 0; - TextLine *line; // the line object - int start, len; // offset and length of this fragment - // (in Unicode chars) - double xMin, xMax; // bounding box coordinates - double yMin, yMax; - double base; // baseline virtual coordinate - int col; // first column - - void init(TextLine *lineA, int startA, int lenA); - void computeCoords(GBool oneRot); - - static int cmpYXPrimaryRot(const void *p1, const void *p2); - static int cmpYXLineRot(const void *p1, const void *p2); - static int cmpXYLineRot(const void *p1, const void *p2); - static int cmpXYColumnPrimaryRot(const void *p1, const void *p2); - static int cmpXYColumnLineRot(const void *p1, const void *p2); -}; + chars = new GList(); + fonts = new GList(); -void TextLineFrag::init(TextLine *lineA, int startA, int lenA) { - line = lineA; - start = startA; - len = lenA; - col = line->col[start]; + underlines = new GList(); + links = new GList(); + + findCols = NULL; + findLR = gTrue; + lastFindXMin = lastFindYMin = 0; + haveLastFind = gFalse; } -void TextLineFrag::computeCoords(GBool oneRot) { - TextBlock *blk; - double d0, d1, d2, d3, d4; +TextPage::~TextPage() { + clear(); + deleteGList(chars, TextChar); + deleteGList(fonts, TextFontInfo); + deleteGList(underlines, TextUnderline); + deleteGList(links, TextLink); + if (findCols) { + deleteGList(findCols, TextColumn); + } +} - if (oneRot) { +void TextPage::startPage(GfxState *state) { + clear(); + if (state) { + pageWidth = state->getPageWidth(); + pageHeight = state->getPageHeight(); + } else { + pageWidth = pageHeight = 0; + } +} - switch (line->rot) { - case 0: - xMin = line->edge[start]; - xMax = line->edge[start + len]; - yMin = line->yMin; - yMax = line->yMax; - break; - case 1: - xMin = line->xMin; - xMax = line->xMax; - yMin = line->edge[start]; - yMax = line->edge[start + len]; - break; - case 2: - xMin = line->edge[start + len]; - xMax = line->edge[start]; - yMin = line->yMin; - yMax = line->yMax; - break; - case 3: - xMin = line->xMin; - xMax = line->xMax; - yMin = line->edge[start + len]; - yMax = line->edge[start]; +void TextPage::clear() { + pageWidth = pageHeight = 0; + charPos = 0; + curFont = NULL; + curFontSize = 0; + curRot = 0; + nTinyChars = 0; + gfree(actualText); + actualText = NULL; + actualTextLen = 0; + actualTextNBytes = 0; + deleteGList(chars, TextChar); + chars = new GList(); + deleteGList(fonts, TextFontInfo); + fonts = new GList(); + deleteGList(underlines, TextUnderline); + underlines = new GList(); + deleteGList(links, TextLink); + links = new GList(); + + if (findCols) { + deleteGList(findCols, TextColumn); + findCols = NULL; + } + findLR = gTrue; + lastFindXMin = lastFindYMin = 0; + haveLastFind = gFalse; +} + +void TextPage::updateFont(GfxState *state) { + GfxFont *gfxFont; + double *fm; + char *name; + int code, mCode, letterCode, anyCode; + double w; + double m[4], m2[4]; + int i; + + // get the font info object + curFont = NULL; + for (i = 0; i < fonts->getLength(); ++i) { + curFont = (TextFontInfo *)fonts->get(i); + if (curFont->matches(state)) { break; } - base = line->base; + curFont = NULL; + } + if (!curFont) { + curFont = new TextFontInfo(state); + fonts->append(curFont); + } + + // adjust the font size + gfxFont = state->getFont(); + curFontSize = state->getTransformedFontSize(); + if (gfxFont && gfxFont->getType() == fontType3) { + // This is a hack which makes it possible to deal with some Type 3 + // fonts. The problem is that it's impossible to know what the + // base coordinate system used in the font is without actually + // rendering the font. This code tries to guess by looking at the + // width of the character 'm' (which breaks if the font is a + // subset that doesn't contain 'm'). + mCode = letterCode = anyCode = -1; + for (code = 0; code < 256; ++code) { + name = ((Gfx8BitFont *)gfxFont)->getCharName(code); + if (name && name[0] == 'm' && name[1] == '\0') { + mCode = code; + } + if (letterCode < 0 && name && name[1] == '\0' && + ((name[0] >= 'A' && name[0] <= 'Z') || + (name[0] >= 'a' && name[0] <= 'z'))) { + letterCode = code; + } + if (anyCode < 0 && name && + ((Gfx8BitFont *)gfxFont)->getWidth(code) > 0) { + anyCode = code; + } + } + if (mCode >= 0 && + (w = ((Gfx8BitFont *)gfxFont)->getWidth(mCode)) > 0) { + // 0.6 is a generic average 'm' width -- yes, this is a hack + curFontSize *= w / 0.6; + } else if (letterCode >= 0 && + (w = ((Gfx8BitFont *)gfxFont)->getWidth(letterCode)) > 0) { + // even more of a hack: 0.5 is a generic letter width + curFontSize *= w / 0.5; + } else if (anyCode >= 0 && + (w = ((Gfx8BitFont *)gfxFont)->getWidth(anyCode)) > 0) { + // better than nothing: 0.5 is a generic character width + curFontSize *= w / 0.5; + } + fm = gfxFont->getFontMatrix(); + if (fm[0] != 0) { + curFontSize *= fabs(fm[3] / fm[0]); + } + } + // compute the rotation + state->getFontTransMat(&m[0], &m[1], &m[2], &m[3]); + if (gfxFont && gfxFont->getType() == fontType3) { + fm = gfxFont->getFontMatrix(); + m2[0] = fm[0] * m[0] + fm[1] * m[2]; + m2[1] = fm[0] * m[1] + fm[1] * m[3]; + m2[2] = fm[2] * m[0] + fm[3] * m[2]; + m2[3] = fm[2] * m[1] + fm[3] * m[3]; + m[0] = m2[0]; + m[1] = m2[1]; + m[2] = m2[2]; + m[3] = m2[3]; + } + if (fabs(m[0] * m[3]) > fabs(m[1] * m[2])) { + curRot = (m[0] > 0 || m[3] < 0) ? 0 : 2; } else { + curRot = (m[2] > 0) ? 1 : 3; + } +} - if (line->rot == 0 && line->blk->page->primaryRot == 0) { +void TextPage::addChar(GfxState *state, double x, double y, + double dx, double dy, + CharCode c, int nBytes, Unicode *u, int uLen) { + double x1, y1, x2, y2, w1, h1, dx2, dy2, ascent, descent, sp; + double xMin, yMin, xMax, yMax; + double clipXMin, clipYMin, clipXMax, clipYMax; + GfxRGB rgb; + GBool clipped, rtl; + int i, j; - xMin = line->edge[start]; - xMax = line->edge[start + len]; - yMin = line->yMin; - yMax = line->yMax; - base = line->base; + // if we're in an ActualText span, save the position info (the + // ActualText chars will be added by TextPage::endActualText()). + if (actualText) { + if (!actualTextNBytes) { + actualTextX0 = x; + actualTextY0 = y; + } + actualTextX1 = x + dx; + actualTextY1 = y + dy; + actualTextNBytes += nBytes; + return; + } - } else { + // subtract char and word spacing from the dx,dy values + sp = state->getCharSpace(); + if (c == (CharCode)0x20) { + sp += state->getWordSpace(); + } + state->textTransformDelta(sp * state->getHorizScaling(), 0, &dx2, &dy2); + dx -= dx2; + dy -= dy2; + state->transformDelta(dx, dy, &w1, &h1); - blk = line->blk; - d0 = line->edge[start]; - d1 = line->edge[start + len]; - d2 = d3 = d4 = 0; // make gcc happy + // throw away chars that aren't inside the page bounds + // (and also do a sanity check on the character size) + state->transform(x, y, &x1, &y1); + if (x1 + w1 < 0 || x1 > pageWidth || + y1 + h1 < 0 || y1 > pageHeight || + w1 > pageWidth || h1 > pageHeight) { + charPos += nBytes; + return; + } - switch (line->rot) { - case 0: - d2 = line->yMin; - d3 = line->yMax; - d4 = line->base; - d0 = (d0 - blk->xMin) / (blk->xMax - blk->xMin); - d1 = (d1 - blk->xMin) / (blk->xMax - blk->xMin); - d2 = (d2 - blk->yMin) / (blk->yMax - blk->yMin); - d3 = (d3 - blk->yMin) / (blk->yMax - blk->yMin); - d4 = (d4 - blk->yMin) / (blk->yMax - blk->yMin); - break; - case 1: - d2 = line->xMax; - d3 = line->xMin; - d4 = line->base; - d0 = (d0 - blk->yMin) / (blk->yMax - blk->yMin); - d1 = (d1 - blk->yMin) / (blk->yMax - blk->yMin); - d2 = (blk->xMax - d2) / (blk->xMax - blk->xMin); - d3 = (blk->xMax - d3) / (blk->xMax - blk->xMin); - d4 = (blk->xMax - d4) / (blk->xMax - blk->xMin); - break; - case 2: - d2 = line->yMax; - d3 = line->yMin; - d4 = line->base; - d0 = (blk->xMax - d0) / (blk->xMax - blk->xMin); - d1 = (blk->xMax - d1) / (blk->xMax - blk->xMin); - d2 = (blk->yMax - d2) / (blk->yMax - blk->yMin); - d3 = (blk->yMax - d3) / (blk->yMax - blk->yMin); - d4 = (blk->yMax - d4) / (blk->yMax - blk->yMin); - break; - case 3: - d2 = line->xMin; - d3 = line->xMax; - d4 = line->base; - d0 = (blk->yMax - d0) / (blk->yMax - blk->yMin); - d1 = (blk->yMax - d1) / (blk->yMax - blk->yMin); - d2 = (d2 - blk->xMin) / (blk->xMax - blk->xMin); - d3 = (d3 - blk->xMin) / (blk->xMax - blk->xMin); - d4 = (d4 - blk->xMin) / (blk->xMax - blk->xMin); - break; + // check the tiny chars limit + if (!globalParams->getTextKeepTinyChars() && + fabs(w1) < 3 && fabs(h1) < 3) { + if (++nTinyChars > 50000) { + charPos += nBytes; + return; + } + } + + // skip space characters + if (uLen == 1 && u[0] == (Unicode)0x20) { + charPos += nBytes; + return; + } + + // check for clipping + clipped = gFalse; + if (control.clipText) { + state->getClipBBox(&clipXMin, &clipYMin, &clipXMax, &clipYMax); + if (x1 + 0.1 * w1 < clipXMin || x1 + 0.9 * w1 > clipXMax || + y1 + 0.1 * h1 < clipYMin || y1 + 0.9 * h1 > clipYMax) { + clipped = gTrue; + } + } + + // add the characters + if (uLen > 0) { + + // handle right-to-left ligatures: if there are multiple Unicode + // characters, and they're all right-to-left, insert them in + // right-to-left order + if (uLen > 1) { + rtl = gTrue; + for (i = 0; i < uLen; ++i) { + if (!unicodeTypeR(u[i])) { + rtl = gFalse; + break; + } } + } else { + rtl = gFalse; + } - switch (line->blk->page->primaryRot) { + w1 /= uLen; + h1 /= uLen; + ascent = curFont->ascent * curFontSize; + descent = curFont->descent * curFontSize; + for (i = 0; i < uLen; ++i) { + x2 = x1 + i * w1; + y2 = y1 + i * h1; + switch (curRot) { case 0: - xMin = blk->xMin + d0 * (blk->xMax - blk->xMin); - xMax = blk->xMin + d1 * (blk->xMax - blk->xMin); - yMin = blk->yMin + d2 * (blk->yMax - blk->yMin); - yMax = blk->yMin + d3 * (blk->yMax - blk->yMin); - base = blk->yMin + d4 * (blk->yMax - blk->yMin); + default: + xMin = x2; + xMax = x2 + w1; + yMin = y2 - ascent; + yMax = y2 - descent; break; case 1: - xMin = blk->xMax - d3 * (blk->xMax - blk->xMin); - xMax = blk->xMax - d2 * (blk->xMax - blk->xMin); - yMin = blk->yMin + d0 * (blk->yMax - blk->yMin); - yMax = blk->yMin + d1 * (blk->yMax - blk->yMin); - base = blk->xMax - d4 * (blk->xMax - blk->xMin); + xMin = x2 + descent; + xMax = x2 + ascent; + yMin = y2; + yMax = y2 + h1; break; case 2: - xMin = blk->xMax - d1 * (blk->xMax - blk->xMin); - xMax = blk->xMax - d0 * (blk->xMax - blk->xMin); - yMin = blk->yMax - d3 * (blk->yMax - blk->yMin); - yMax = blk->yMax - d2 * (blk->yMax - blk->yMin); - base = blk->yMax - d4 * (blk->yMax - blk->yMin); + xMin = x2 + w1; + xMax = x2; + yMin = y2 + descent; + yMax = y2 + ascent; break; case 3: - xMin = blk->xMin + d2 * (blk->xMax - blk->xMin); - xMax = blk->xMin + d3 * (blk->xMax - blk->xMin); - yMin = blk->yMax - d1 * (blk->yMax - blk->yMin); - yMax = blk->yMax - d0 * (blk->yMax - blk->yMin); - base = blk->xMin + d4 * (blk->xMax - blk->xMin); + xMin = x2 - ascent; + xMax = x2 - descent; + yMin = y2 + h1; + yMax = y2; break; } - + if ((state->getRender() & 3) == 1) { + state->getStrokeRGB(&rgb); + } else { + state->getFillRGB(&rgb); + } + if (rtl) { + j = uLen - 1 - i; + } else { + j = i; + } + chars->append(new TextChar(u[j], charPos, nBytes, xMin, yMin, xMax, yMax, + curRot, clipped, + state->getRender() == 3, + curFont, curFontSize, + colToDbl(rgb.r), colToDbl(rgb.g), + colToDbl(rgb.b))); } } + + charPos += nBytes; } -int TextLineFrag::cmpYXPrimaryRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; +void TextPage::incCharCount(int nChars) { + charPos += nChars; +} - cmp = 0; // make gcc happy - switch (frag1->line->blk->page->primaryRot) { - case 0: - if (fabs(cmp = frag1->yMin - frag2->yMin) < 0.01) { - cmp = frag1->xMin - frag2->xMin; - } - break; - case 1: - if (fabs(cmp = frag2->xMax - frag1->xMax) < 0.01) { - cmp = frag1->yMin - frag2->yMin; - } - break; - case 2: - if (fabs(cmp = frag2->yMin - frag1->yMin) < 0.01) { - cmp = frag2->xMax - frag1->xMax; - } - break; - case 3: - if (fabs(cmp = frag1->xMax - frag2->xMax) < 0.01) { - cmp = frag2->yMax - frag1->yMax; - } - break; +void TextPage::beginActualText(GfxState *state, Unicode *u, int uLen) { + if (actualText) { + gfree(actualText); } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; + actualText = (Unicode *)gmallocn(uLen, sizeof(Unicode)); + memcpy(actualText, u, uLen * sizeof(Unicode)); + actualTextLen = uLen; + actualTextNBytes = 0; } -int TextLineFrag::cmpYXLineRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; +void TextPage::endActualText(GfxState *state) { + Unicode *u; - cmp = 0; // make gcc happy - switch (frag1->line->rot) { - case 0: - if ((cmp = frag1->yMin - frag2->yMin) == 0) { - cmp = frag1->xMin - frag2->xMin; - } - break; - case 1: - if ((cmp = frag2->xMax - frag1->xMax) == 0) { - cmp = frag1->yMin - frag2->yMin; - } - break; - case 2: - if ((cmp = frag2->yMin - frag1->yMin) == 0) { - cmp = frag2->xMax - frag1->xMax; - } - break; - case 3: - if ((cmp = frag1->xMax - frag2->xMax) == 0) { - cmp = frag2->yMax - frag1->yMax; - } - break; + u = actualText; + actualText = NULL; // so we can call TextPage::addChar() + if (actualTextNBytes) { + // now that we have the position info for all of the text inside + // the marked content span, we feed the "ActualText" back through + // addChar() + addChar(state, actualTextX0, actualTextY0, + actualTextX1 - actualTextX0, actualTextY1 - actualTextY0, + 0, actualTextNBytes, u, actualTextLen); } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; + gfree(u); + actualText = NULL; + actualTextLen = 0; + actualTextNBytes = gFalse; } -int TextLineFrag::cmpXYLineRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; +void TextPage::addUnderline(double x0, double y0, double x1, double y1) { + underlines->append(new TextUnderline(x0, y0, x1, y1)); +} - cmp = 0; // make gcc happy - switch (frag1->line->rot) { - case 0: - if ((cmp = frag1->xMin - frag2->xMin) == 0) { - cmp = frag1->yMin - frag2->yMin; - } +void TextPage::addLink(double xMin, double yMin, double xMax, double yMax, + Link *link) { + GString *uri; + + if (link && link->getAction() && link->getAction()->getKind() == actionURI) { + uri = ((LinkURI *)link->getAction())->getURI()->copy(); + links->append(new TextLink(xMin, yMin, xMax, yMax, uri)); + } +} + +//------------------------------------------------------------------------ +// TextPage: output +//------------------------------------------------------------------------ + +void TextPage::write(void *outputStream, TextOutputFunc outputFunc) { + UnicodeMap *uMap; + char space[8], eol[16], eop[8]; + int spaceLen, eolLen, eopLen; + GBool pageBreaks; + + // get the output encoding + if (!(uMap = globalParams->getTextEncoding())) { + return; + } + spaceLen = uMap->mapUnicode(0x20, space, sizeof(space)); + eolLen = 0; // make gcc happy + switch (globalParams->getTextEOL()) { + case eolUnix: + eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol)); break; - case 1: - if ((cmp = frag1->yMin - frag2->yMin) == 0) { - cmp = frag2->xMax - frag1->xMax; - } + case eolDOS: + eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); + eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen); break; - case 2: - if ((cmp = frag2->xMax - frag1->xMax) == 0) { - cmp = frag2->yMin - frag1->yMin; - } + case eolMac: + eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); break; - case 3: - if ((cmp = frag2->yMax - frag1->yMax) == 0) { - cmp = frag1->xMax - frag2->xMax; - } + } + eopLen = uMap->mapUnicode(0x0c, eop, sizeof(eop)); + pageBreaks = globalParams->getTextPageBreaks(); + + switch (control.mode) { + case textOutReadingOrder: + writeReadingOrder(outputStream, outputFunc, uMap, space, spaceLen, + eol, eolLen); + break; + case textOutPhysLayout: + case textOutTableLayout: + writePhysLayout(outputStream, outputFunc, uMap, space, spaceLen, + eol, eolLen); + break; + case textOutLinePrinter: + writeLinePrinter(outputStream, outputFunc, uMap, space, spaceLen, + eol, eolLen); + break; + case textOutRawOrder: + writeRaw(outputStream, outputFunc, uMap, space, spaceLen, + eol, eolLen); break; } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; + + // end of page + if (pageBreaks) { + (*outputFunc)(outputStream, eop, eopLen); + } + + uMap->decRefCnt(); } -int TextLineFrag::cmpXYColumnPrimaryRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; +void TextPage::writeReadingOrder(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen) { + TextBlock *tree; + TextColumn *col; + TextParagraph *par; + TextLine *line; + GList *columns; + GBool primaryLR; + GString *s; + int colIdx, parIdx, lineIdx, rot, n; - // if columns overlap, compare y values - if (frag1->col < frag2->col + (frag2->line->col[frag2->start + frag2->len] - - frag2->line->col[frag2->start]) && - frag2->col < frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start])) { - cmp = 0; // make gcc happy - switch (frag1->line->blk->page->primaryRot) { - case 0: cmp = frag1->yMin - frag2->yMin; break; - case 1: cmp = frag2->xMax - frag1->xMax; break; - case 2: cmp = frag2->yMin - frag1->yMin; break; - case 3: cmp = frag1->xMax - frag2->xMax; break; - } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; - } - - // otherwise, compare starting column - return frag1->col - frag2->col; -} - -int TextLineFrag::cmpXYColumnLineRot(const void *p1, const void *p2) { - TextLineFrag *frag1 = (TextLineFrag *)p1; - TextLineFrag *frag2 = (TextLineFrag *)p2; - double cmp; + rot = rotateChars(chars); + primaryLR = checkPrimaryLR(chars); + tree = splitChars(chars); +#if 0 //~debug + dumpTree(tree); +#endif + if (!tree) { + // no text + unrotateChars(chars, rot); + return; + } + columns = buildColumns(tree); + delete tree; + unrotateChars(chars, rot); + if (control.html) { + rotateUnderlinesAndLinks(rot); + generateUnderlinesAndLinks(columns); + } +#if 0 //~debug + dumpColumns(columns); +#endif - // if columns overlap, compare y values - if (frag1->col < frag2->col + (frag2->line->col[frag2->start + frag2->len] - - frag2->line->col[frag2->start]) && - frag2->col < frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start])) { - cmp = 0; // make gcc happy - switch (frag1->line->rot) { - case 0: cmp = frag1->yMin - frag2->yMin; break; - case 1: cmp = frag2->xMax - frag1->xMax; break; - case 2: cmp = frag2->yMin - frag1->yMin; break; - case 3: cmp = frag1->xMax - frag2->xMax; break; + 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); + n = line->len; + if (line->hyphenated && lineIdx + 1 < par->lines->getLength()) { + --n; + } + s = new GString(); + encodeFragment(line->text, n, uMap, primaryLR, s); + if (lineIdx + 1 < par->lines->getLength() && !line->hyphenated) { + s->append(space, spaceLen); + } + (*outputFunc)(outputStream, s->getCString(), s->getLength()); + delete s; + } + (*outputFunc)(outputStream, eol, eolLen); } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; + (*outputFunc)(outputStream, eol, eolLen); } - // otherwise, compare starting column - return frag1->col - frag2->col; + deleteGList(columns, TextColumn); } -//------------------------------------------------------------------------ -// TextBlock -//------------------------------------------------------------------------ +GList *TextPage::makeColumns() { + TextBlock *tree; + GList *columns; -TextBlock::TextBlock(TextPage *pageA, int rotA) { - page = pageA; - rot = rotA; - xMin = yMin = 0; - xMax = yMax = -1; - priMin = 0; - priMax = page->pageWidth; - pool = new TextPool(); - lines = NULL; - curLine = NULL; - next = NULL; - stackNext = NULL; + tree = splitChars(chars); + if (!tree) { + // no text + return new GList(); + } + columns = buildColumns(tree); + delete tree; + if (control.html) { + generateUnderlinesAndLinks(columns); + } + return columns; } -TextBlock::~TextBlock() { +// This handles both physical layout and table layout modes. +void TextPage::writePhysLayout(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen) { + TextBlock *tree; + GString **out; + int *outLen; + TextColumn *col; + TextParagraph *par; TextLine *line; + GList *columns; + GBool primaryLR; + int ph, colIdx, parIdx, lineIdx, rot, y, i; - delete pool; - while (lines) { - line = lines; - lines = lines->next; - delete line; +#if 0 //~debug + dumpChars(chars); +#endif + rot = rotateChars(chars); + primaryLR = checkPrimaryLR(chars); + tree = splitChars(chars); +#if 0 //~debug + dumpTree(tree); +#endif + if (!tree) { + // no text + unrotateChars(chars, rot); + return; + } + columns = buildColumns(tree); + delete tree; + unrotateChars(chars, rot); + if (control.html) { + rotateUnderlinesAndLinks(rot); + generateUnderlinesAndLinks(columns); + } + ph = assignPhysLayoutPositions(columns); +#if 0 //~debug + dumpColumns(columns); +#endif + + out = (GString **)gmallocn(ph, sizeof(GString *)); + outLen = (int *)gmallocn(ph, sizeof(int)); + for (i = 0; i < ph; ++i) { + out[i] = NULL; + outLen[i] = 0; + } + + columns->sort(&TextColumn::cmpPX); + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + y = col->py; + for (parIdx = 0; + parIdx < col->paragraphs->getLength() && y < ph; + ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + for (lineIdx = 0; + lineIdx < par->lines->getLength() && y < ph; + ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + if (!out[y]) { + out[y] = new GString(); + } + while (outLen[y] < col->px + line->px) { + out[y]->append(space, spaceLen); + ++outLen[y]; + } + encodeFragment(line->text, line->len, uMap, primaryLR, out[y]); + outLen[y] += line->pw; + ++y; + } + if (parIdx + 1 < col->paragraphs->getLength()) { + ++y; + } + } } + + for (i = 0; i < ph; ++i) { + if (out[i]) { + (*outputFunc)(outputStream, out[i]->getCString(), out[i]->getLength()); + delete out[i]; + } + (*outputFunc)(outputStream, eol, eolLen); + } + + gfree(out); + gfree(outLen); + + deleteGList(columns, TextColumn); } -void TextBlock::addWord(TextWord *word) { - pool->addWord(word); - if (xMin > xMax) { - xMin = word->xMin; - xMax = word->xMax; - yMin = word->yMin; - yMax = word->yMax; +void TextPage::writeLinePrinter(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen) { + TextChar *ch, *ch2; + GList *line; + GString *s; + char buf[8]; + double pitch, lineSpacing, delta; + double yMin0, yShift, xMin0, xShift; + double y, x; + int rot, n, i, j, k; + + rot = rotateChars(chars); + chars->sort(&TextChar::cmpX); + removeDuplicates(chars, 0); + chars->sort(&TextChar::cmpY); + + // get character pitch + if (control.fixedPitch > 0) { + pitch = control.fixedPitch; } else { - if (word->xMin < xMin) { - xMin = word->xMin; + // compute (approximate) character pitch + pitch = pageWidth; + for (i = 0; i < chars->getLength(); ++i) { + ch = (TextChar *)chars->get(i); + for (j = i + 1; j < chars->getLength(); ++j) { + ch2 = (TextChar *)chars->get(j); + if (ch2->yMin + ascentAdjustFactor * (ch2->yMax - ch2->yMin) < + ch->yMax - descentAdjustFactor * (ch->yMax - ch->yMin) && + 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) { + pitch = delta; + } + } + } } - if (word->xMax > xMax) { - xMax = word->xMax; + } + + // get line spacing + if (control.fixedLineSpacing > 0) { + lineSpacing = control.fixedLineSpacing; + } else { + // compute (approximate) line spacing + lineSpacing = pageHeight; + i = 0; + while (i < chars->getLength()) { + ch = (TextChar *)chars->get(i); + // look for the first char that does not (substantially) + // vertically overlap this one + delta = 0; + for (++i; delta == 0 && i < chars->getLength(); ++i) { + ch2 = (TextChar *)chars->get(i); + if (ch2->yMin + ascentAdjustFactor * (ch2->yMax - ch2->yMin) > + ch->yMax - descentAdjustFactor * (ch->yMax - ch->yMin)) { + delta = ch2->yMin - ch->yMin; + } + } + if (delta > 0 && delta < lineSpacing) { + lineSpacing = delta; + } } - if (word->yMin < yMin) { - yMin = word->yMin; + } + + // shift the grid to avoid problems with floating point accuracy -- + // for fixed line spacing, this avoids problems with + // dropping/inserting blank lines + if (chars->getLength()) { + yMin0 = ((TextChar *)chars->get(0))->yMin; + yShift = yMin0 - (int)(yMin0 / lineSpacing + 0.5) * lineSpacing + - 0.5 * lineSpacing; + } else { + yShift = 0; + } + + // for each line... + i = 0; + j = chars->getLength() - 1; + for (y = yShift; y < pageHeight; y += lineSpacing) { + + // get the characters in this line + line = new GList; + while (i < chars->getLength() && + ((TextChar *)chars->get(i))->yMin < y + lineSpacing) { + line->append(chars->get(i++)); + } + line->sort(&TextChar::cmpX); + + // shift the grid to avoid problems with floating point accuracy + // -- for fixed char spacing, this avoids problems with + // dropping/inserting spaces + if (line->getLength()) { + xMin0 = ((TextChar *)line->get(0))->xMin; + xShift = xMin0 - (int)(xMin0 / pitch + 0.5) * pitch - 0.5 * pitch; + } else { + xShift = 0; } - if (word->yMax > yMax) { - yMax = word->yMax; + + // write the line + s = new GString(); + x = xShift; + k = 0; + while (k < line->getLength()) { + ch = (TextChar *)line->get(k); + if (ch->xMin < x + pitch) { + n = uMap->mapUnicode(ch->c, buf, sizeof(buf)); + s->append(buf, n); + ++k; + } else { + s->append(space, spaceLen); + n = spaceLen; + } + x += (uMap->isUnicode() ? 1 : n) * pitch; } + s->append(eol, eolLen); + (*outputFunc)(outputStream, s->getCString(), s->getLength()); + delete s; + delete line; } + + unrotateChars(chars, rot); } -void TextBlock::coalesce(UnicodeMap *uMap, double fixedPitch) { - TextWord *word0, *word1, *word2, *bestWord0, *bestWord1, *lastWord; - TextLine *line, *line0, *line1; - int poolMinBaseIdx, startBaseIdx, minBaseIdx, maxBaseIdx; - int baseIdx, bestWordBaseIdx, idx0, idx1; - double minBase, maxBase; - double fontSize, wordSpacing, delta, priDelta, secDelta; - TextLine **lineArray; - GBool found, overlap; - int col1, col2; - int i, j, k; +void TextPage::writeRaw(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen) { + TextChar *ch, *ch2; + GString *s; + char buf[8]; + int n, i; - // discard duplicated text (fake boldface, drop shadows) - for (idx0 = pool->minBaseIdx; idx0 <= pool->maxBaseIdx; ++idx0) { - word0 = pool->getPool(idx0); - while (word0) { - priDelta = dupMaxPriDelta * word0->fontSize; - secDelta = dupMaxSecDelta * word0->fontSize; - maxBaseIdx = pool->getBaseIdx(word0->base + secDelta); - found = gFalse; - word1 = word2 = NULL; // make gcc happy - for (idx1 = idx0; idx1 <= maxBaseIdx; ++idx1) { - if (idx1 == idx0) { - word1 = word0; - word2 = word0->next; - } else { - word1 = NULL; - word2 = pool->getPool(idx1); - } - for (; word2; word1 = word2, word2 = word2->next) { - if (word2->len == word0->len && - !memcmp(word2->text, word0->text, - word0->len * sizeof(Unicode))) { - switch (rot) { - case 0: - case 2: - found = fabs(word0->xMin - word2->xMin) < priDelta && - fabs(word0->xMax - word2->xMax) < priDelta && - fabs(word0->yMin - word2->yMin) < secDelta && - fabs(word0->yMax - word2->yMax) < secDelta; - break; - case 1: - case 3: - found = fabs(word0->xMin - word2->xMin) < secDelta && - fabs(word0->xMax - word2->xMax) < secDelta && - fabs(word0->yMin - word2->yMin) < priDelta && - fabs(word0->yMax - word2->yMax) < priDelta; - break; - } + s = new GString(); + + for (i = 0; i < chars->getLength(); ++i) { + + // process one char + ch = (TextChar *)chars->get(i); + n = uMap->mapUnicode(ch->c, buf, sizeof(buf)); + s->append(buf, n); + + // check for space or eol + if (i+1 < chars->getLength()) { + ch2 = (TextChar *)chars->get(i+1); + if (ch2->rot != ch->rot) { + s->append(eol, eolLen); + } else { + switch (ch->rot) { + case 0: + default: + if (fabs(ch2->yMin - ch->yMin) > rawModeLineDelta * ch->fontSize || + ch2->xMin - ch->xMax < -rawModeCharOverlap * ch->fontSize) { + s->append(eol, eolLen); + } else if (ch2->xMin - ch->xMax > + rawModeWordSpacing * ch->fontSize) { + s->append(space, spaceLen); } - if (found) { - break; + break; + case 1: + if (fabs(ch->xMax - ch2->xMax) > rawModeLineDelta * ch->fontSize || + ch2->yMin - ch->yMax < -rawModeCharOverlap * ch->fontSize) { + s->append(eol, eolLen); + } else if (ch2->yMin - ch->yMax > + rawModeWordSpacing * ch->fontSize) { + s->append(space, spaceLen); + } + break; + case 2: + if (fabs(ch->yMax - ch2->yMax) > rawModeLineDelta * ch->fontSize || + ch->xMin - ch2->xMax < -rawModeCharOverlap * ch->fontSize) { + s->append(eol, eolLen); + } else if (ch->xMin - ch2->xMax > + rawModeWordSpacing * ch->fontSize) { + s->append(space, spaceLen); + } + break; + case 3: + if (fabs(ch2->xMin - ch->xMin) > rawModeLineDelta * ch->fontSize || + ch->yMin - ch2->yMax < -rawModeCharOverlap * ch->fontSize) { + s->append(eol, eolLen); + } else if (ch->yMin - ch2->yMax > + rawModeWordSpacing * ch->fontSize) { + s->append(space, spaceLen); } - } - if (found) { break; } } - if (found) { - if (word1) { - word1->next = word2->next; - } else { - pool->setPool(idx1, word2->next); - } - delete word2; - } else { - word0 = word0->next; - } + } else { + s->append(eol, eolLen); + } + + if (s->getLength() > 1000) { + (*outputFunc)(outputStream, s->getCString(), s->getLength()); + s->clear(); } } - // build the lines - curLine = NULL; - poolMinBaseIdx = pool->minBaseIdx; - charCount = 0; - nLines = 0; - while (1) { + if (s->getLength() > 0) { + (*outputFunc)(outputStream, s->getCString(), s->getLength()); + } + delete s; +} - // find the first non-empty line in the pool - for (; - poolMinBaseIdx <= pool->maxBaseIdx && !pool->getPool(poolMinBaseIdx); - ++poolMinBaseIdx) ; - if (poolMinBaseIdx > pool->maxBaseIdx) { - break; - } +void TextPage::encodeFragment(Unicode *text, int len, UnicodeMap *uMap, + GBool primaryLR, GString *s) { + char lre[8], rle[8], popdf[8], buf[8]; + int lreLen, rleLen, popdfLen, n; + int i, j, k; - // look for the left-most word in the first four lines of the - // pool -- this avoids starting with a superscript word - startBaseIdx = poolMinBaseIdx; - for (baseIdx = poolMinBaseIdx + 1; - baseIdx < poolMinBaseIdx + 4 && baseIdx <= pool->maxBaseIdx; - ++baseIdx) { - if (!pool->getPool(baseIdx)) { - continue; - } - if (pool->getPool(baseIdx)->primaryCmp(pool->getPool(startBaseIdx)) - < 0) { - startBaseIdx = baseIdx; - } - } + if (uMap->isUnicode()) { - // create a new line - word0 = pool->getPool(startBaseIdx); - pool->setPool(startBaseIdx, word0->next); - word0->next = NULL; - line = new TextLine(this, word0->rot, word0->base); - line->addWord(word0); - lastWord = word0; - - // compute the search range - fontSize = word0->fontSize; - minBase = word0->base - maxIntraLineDelta * fontSize; - maxBase = word0->base + maxIntraLineDelta * fontSize; - minBaseIdx = pool->getBaseIdx(minBase); - maxBaseIdx = pool->getBaseIdx(maxBase); - wordSpacing = fixedPitch ? fixedPitch : maxWordSpacing * fontSize; - - // find the rest of the words in this line - while (1) { - - // find the left-most word whose baseline is in the range for - // this line - bestWordBaseIdx = 0; - bestWord0 = bestWord1 = NULL; - overlap = gFalse; - for (baseIdx = minBaseIdx; - !overlap && baseIdx <= maxBaseIdx; - ++baseIdx) { - for (word0 = NULL, word1 = pool->getPool(baseIdx); - word1; - word0 = word1, word1 = word1->next) { - if (word1->base >= minBase && - word1->base <= maxBase) { - delta = lastWord->primaryDelta(word1); - if (delta < minCharSpacing * fontSize) { - overlap = gTrue; - break; - } else { - if (delta < wordSpacing && - (!bestWord1 || word1->primaryCmp(bestWord1) < 0)) { - bestWordBaseIdx = baseIdx; - bestWord0 = word0; - bestWord1 = word1; - } - break; - } + lreLen = uMap->mapUnicode(0x202a, lre, sizeof(lre)); + rleLen = uMap->mapUnicode(0x202b, rle, sizeof(rle)); + popdfLen = uMap->mapUnicode(0x202c, popdf, sizeof(popdf)); + + if (primaryLR) { + + i = 0; + while (i < len) { + // output a left-to-right section + for (j = i; j < len && !unicodeTypeR(text[j]); ++j) ; + for (k = i; k < j; ++k) { + n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + s->append(buf, n); + } + i = j; + // output a right-to-left section + for (j = i; + j < len && !(unicodeTypeL(text[j]) || unicodeTypeNum(text[j])); + ++j) ; + if (j > i) { + s->append(rle, rleLen); + for (k = j - 1; k >= i; --k) { + n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + s->append(buf, n); } + s->append(popdf, popdfLen); + i = j; } } - if (overlap || !bestWord1) { - break; - } - - // remove it from the pool, and add it to the line - if (bestWord0) { - bestWord0->next = bestWord1->next; - } else { - pool->setPool(bestWordBaseIdx, bestWord1->next); - } - bestWord1->next = NULL; - line->addWord(bestWord1); - lastWord = bestWord1; - } - // add the line - if (curLine && line->cmpYX(curLine) > 0) { - line0 = curLine; - line1 = curLine->next; - } else { - line0 = NULL; - line1 = lines; - } - for (; - line1 && line->cmpYX(line1) > 0; - line0 = line1, line1 = line1->next) ; - if (line0) { - line0->next = line; } else { - lines = line; - } - line->next = line1; - curLine = line; - line->coalesce(uMap); - charCount += line->len; - ++nLines; - } - - // sort lines into xy order for column assignment - lineArray = (TextLine **)gmallocn(nLines, sizeof(TextLine *)); - for (line = lines, i = 0; line; line = line->next, ++i) { - lineArray[i] = line; - } - qsort(lineArray, nLines, sizeof(TextLine *), &TextLine::cmpXY); - - // column assignment - nColumns = 0; - if (fixedPitch) { - for (i = 0; i < nLines; ++i) { - line0 = lineArray[i]; - col1 = 0; // make gcc happy - switch (rot) { - case 0: - col1 = (int)((line0->xMin - xMin) / fixedPitch + 0.5); - break; - case 1: - col1 = (int)((line0->yMin - yMin) / fixedPitch + 0.5); - break; - case 2: - col1 = (int)((xMax - line0->xMax) / fixedPitch + 0.5); - break; - case 3: - col1 = (int)((yMax - line0->yMax) / fixedPitch + 0.5); - break; - } - for (k = 0; k <= line0->len; ++k) { - line0->col[k] += col1; - } - if (line0->col[line0->len] > nColumns) { - nColumns = line0->col[line0->len]; - } - } - } else { - for (i = 0; i < nLines; ++i) { - line0 = lineArray[i]; - col1 = 0; - for (j = 0; j < i; ++j) { - line1 = lineArray[j]; - if (line1->primaryDelta(line0) >= 0) { - col2 = line1->col[line1->len] + 1; - } else { - k = 0; // make gcc happy - switch (rot) { - case 0: - for (k = 0; - k < line1->len && - line0->xMin >= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - case 1: - for (k = 0; - k < line1->len && - line0->yMin >= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - case 2: - for (k = 0; - k < line1->len && - line0->xMax <= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - case 3: - for (k = 0; - k < line1->len && - line0->yMax <= 0.5 * (line1->edge[k] + line1->edge[k+1]); - ++k) ; - break; - } - col2 = line1->col[k]; + + // Note: This code treats numeric characters (European and + // Arabic/Indic) as left-to-right, which isn't strictly correct + // (incurs extra LRE/POPDF pairs), but does produce correct + // visual formatting. + s->append(rle, rleLen); + i = len - 1; + while (i >= 0) { + // output a right-to-left section + for (j = i; + j >= 0 && !(unicodeTypeL(text[j]) || unicodeTypeNum(text[j])); + --j) ; + for (k = i; k > j; --k) { + n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + s->append(buf, n); } - if (col2 > col1) { - col1 = col2; + i = j; + // output a left-to-right section + for (j = i; j >= 0 && !unicodeTypeR(text[j]); --j) ; + if (j < i) { + s->append(lre, lreLen); + for (k = j + 1; k <= i; ++k) { + n = uMap->mapUnicode(text[k], buf, sizeof(buf)); + s->append(buf, n); + } + s->append(popdf, popdfLen); + i = j; } } - for (k = 0; k <= line0->len; ++k) { - line0->col[k] += col1; - } - if (line0->col[line0->len] > nColumns) { - nColumns = line0->col[line0->len]; - } + s->append(popdf, popdfLen); + } + + } else { + for (i = 0; i < len; ++i) { + n = uMap->mapUnicode(text[i], buf, sizeof(buf)); + s->append(buf, n); } } - gfree(lineArray); } -void TextBlock::updatePriMinMax(TextBlock *blk) { - double newPriMin, newPriMax; - GBool gotPriMin, gotPriMax; +//------------------------------------------------------------------------ +// TextPage: layout analysis +//------------------------------------------------------------------------ - gotPriMin = gotPriMax = gFalse; - newPriMin = newPriMax = 0; // make gcc happy - switch (page->primaryRot) { - case 0: - case 2: - if (blk->yMin < yMax && blk->yMax > yMin) { - if (blk->xMin < xMin) { - newPriMin = blk->xMax; - gotPriMin = gTrue; - } - if (blk->xMax > xMax) { - newPriMax = blk->xMin; - gotPriMax = gTrue; - } - } - break; - case 1: - case 3: - if (blk->xMin < xMax && blk->xMax > xMin) { - if (blk->yMin < yMin) { - newPriMin = blk->yMax; - gotPriMin = gTrue; - } - if (blk->yMax > yMax) { - newPriMax = blk->yMin; - gotPriMax = gTrue; - } - } - break; - } - if (gotPriMin) { - if (newPriMin > xMin) { - newPriMin = xMin; - } - if (newPriMin > priMin) { - priMin = newPriMin; - } - } - if (gotPriMax) { - if (newPriMax < xMax) { - newPriMax = xMax; - } - if (newPriMax < priMax) { - priMax = newPriMax; +// Determine primary (most common) rotation value. Rotate all chars +// to that primary rotation. +int TextPage::rotateChars(GList *charsA) { + TextChar *ch; + int nChars[4]; + double xMin, yMin, xMax, yMax, t; + int rot, i; + + // determine primary rotation + nChars[0] = nChars[1] = nChars[2] = nChars[3] = 0; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + ++nChars[ch->rot]; + } + rot = 0; + for (i = 1; i < 4; ++i) { + if (nChars[i] > nChars[rot]) { + rot = i; } } -} - -int TextBlock::cmpXYPrimaryRot(const void *p1, const void *p2) { - TextBlock *blk1 = *(TextBlock **)p1; - TextBlock *blk2 = *(TextBlock **)p2; - double cmp; - cmp = 0; // make gcc happy - switch (blk1->page->primaryRot) { + // rotate + switch (rot) { case 0: - if ((cmp = blk1->xMin - blk2->xMin) == 0) { - cmp = blk1->yMin - blk2->yMin; - } + default: break; case 1: - if ((cmp = blk1->yMin - blk2->yMin) == 0) { - cmp = blk2->xMax - blk1->xMax; - } + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + 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; + ch->rot = (ch->rot + 3) & 3; + } + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; break; case 2: - if ((cmp = blk2->xMax - blk1->xMax) == 0) { - cmp = blk2->yMin - blk1->yMin; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + 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; + ch->rot = (ch->rot + 2) & 3; } break; case 3: - if ((cmp = blk2->yMax - blk1->yMax) == 0) { - cmp = blk1->xMax - blk2->xMax; - } + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + 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; + ch->rot = (ch->rot + 1) & 3; + } + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; break; } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; -} -int TextBlock::cmpYXPrimaryRot(const void *p1, const void *p2) { - TextBlock *blk1 = *(TextBlock **)p1; - TextBlock *blk2 = *(TextBlock **)p2; - double cmp; + return rot; +} + +// Rotate the TextUnderlines and TextLinks to match the transform +// performed by rotateChars(). +void TextPage::rotateUnderlinesAndLinks(int rot) { + TextUnderline *underline; + TextLink *link; + double xMin, yMin, xMax, yMax; + int i; - cmp = 0; // make gcc happy - switch (blk1->page->primaryRot) { + switch (rot) { case 0: - if ((cmp = blk1->yMin - blk2->yMin) == 0) { - cmp = blk1->xMin - blk2->xMin; - } + default: break; case 1: - if ((cmp = blk2->xMax - blk1->xMax) == 0) { - cmp = blk1->yMin - blk2->yMin; + for (i = 0; i < underlines->getLength(); ++i) { + underline = (TextUnderline *)underlines->get(i); + xMin = underline->y0; + xMax = underline->y1; + yMin = pageWidth - underline->x1; + yMax = pageWidth - underline->x0; + underline->x0 = xMin; + underline->x1 = xMax; + underline->y0 = yMin; + underline->y1 = yMax; + underline->horiz = !underline->horiz; + } + for (i = 0; i < links->getLength(); ++i) { + link = (TextLink *)links->get(i); + xMin = link->yMin; + xMax = link->yMax; + yMin = pageWidth - link->xMax; + yMax = pageWidth - link->xMin; + link->xMin = xMin; + link->xMax = xMax; + link->yMin = yMin; + link->yMax = yMax; } break; case 2: - if ((cmp = blk2->yMin - blk1->yMin) == 0) { - cmp = blk2->xMax - blk1->xMax; + for (i = 0; i < underlines->getLength(); ++i) { + underline = (TextUnderline *)underlines->get(i); + xMin = pageWidth - underline->x1; + xMax = pageWidth - underline->x0; + yMin = pageHeight - underline->y1; + yMax = pageHeight - underline->y0; + underline->x0 = xMin; + underline->x1 = xMax; + underline->y0 = yMin; + underline->y1 = yMax; + } + for (i = 0; i < links->getLength(); ++i) { + link = (TextLink *)links->get(i); + xMin = pageWidth - link->xMax; + xMax = pageWidth - link->xMin; + yMin = pageHeight - link->yMax; + yMax = pageHeight - link->yMin; + link->xMin = xMin; + link->xMax = xMax; + link->yMin = yMin; + link->yMax = yMax; } break; case 3: - if ((cmp = blk1->xMax - blk2->xMax) == 0) { - cmp = blk2->yMax - blk1->yMax; + for (i = 0; i < underlines->getLength(); ++i) { + underline = (TextUnderline *)underlines->get(i); + xMin = pageHeight - underline->y1; + xMax = pageHeight - underline->y0; + yMin = underline->x0; + yMax = underline->x1; + underline->x0 = xMin; + underline->x1 = xMax; + underline->y0 = yMin; + underline->y1 = yMax; + underline->horiz = !underline->horiz; + } + for (i = 0; i < links->getLength(); ++i) { + link = (TextLink *)links->get(i); + xMin = pageHeight - link->yMax; + xMax = pageHeight - link->yMin; + yMin = link->xMin; + yMax = link->xMax; + link->xMin = xMin; + link->xMax = xMax; + link->yMin = yMin; + link->yMax = yMax; } break; } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; } -int TextBlock::primaryCmp(TextBlock *blk) { - double cmp; +// Undo the coordinate transform performed by rotateChars(). +void TextPage::unrotateChars(GList *charsA, int rot) { + TextChar *ch; + double xMin, yMin, xMax, yMax, t; + int i; - cmp = 0; // make gcc happy switch (rot) { case 0: - cmp = xMin - blk->xMin; + default: + // no transform break; case 1: - cmp = yMin - blk->yMin; + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + 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; + ch->rot = (ch->rot + 1) & 3; + } break; case 2: - cmp = blk->xMax - xMax; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + 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; + ch->rot = (ch->rot + 2) & 3; + } break; case 3: - cmp = blk->yMax - yMax; + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + 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; + ch->rot = (ch->rot + 3) & 3; + } break; } - return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; } -double TextBlock::secondaryDelta(TextBlock *blk) { - double delta; +// Undo the coordinate transform performed by rotateChars(). +void TextPage::unrotateColumns(GList *columns, int rot) { + TextColumn *col; + TextParagraph *par; + TextLine *line; + TextWord *word; + double xMin, yMin, xMax, yMax, t; + int colIdx, parIdx, lineIdx, wordIdx, i; - delta = 0; // make gcc happy switch (rot) { case 0: - delta = blk->yMin - yMax; - break; - case 1: - delta = xMin - blk->xMax; - break; - case 2: - delta = yMin - blk->yMax; - break; - case 3: - delta = blk->xMin - xMax; - break; - } - return delta; -} - -GBool TextBlock::isBelow(TextBlock *blk) { - GBool below; - - below = gFalse; // make gcc happy - switch (page->primaryRot) { - case 0: - below = xMin >= blk->priMin && xMax <= blk->priMax && - yMin > blk->yMin; + default: + // no transform break; case 1: - below = yMin >= blk->priMin && yMax <= blk->priMax && - xMax < blk->xMax; + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + 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; + line->rot = (line->rot + 1) & 3; + 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; + word->rot = (word->rot + 1) & 3; + } + } + } + } break; case 2: - below = xMin >= blk->priMin && xMax <= blk->priMax && - yMax < blk->yMax; + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + 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; + line->rot = (line->rot + 2) & 3; + 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; + word->rot = (word->rot + 2) & 3; + for (i = 0; i <= word->len; ++i) { + word->edge[i] = pageWidth - word->edge[i]; + } + } + } + } + } break; case 3: - below = yMin >= blk->priMin && yMax <= blk->priMax && - xMin > blk->xMin; + t = pageWidth; + pageWidth = pageHeight; + pageHeight = t; + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + 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; + line->rot = (line->rot + 3) & 3; + 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; + word->rot = (word->rot + 3) & 3; + for (i = 0; i <= word->len; ++i) { + word->edge[i] = pageHeight - word->edge[i]; + } + } + } + } + } break; } - - return below; -} - -//------------------------------------------------------------------------ -// TextFlow -//------------------------------------------------------------------------ - -TextFlow::TextFlow(TextPage *pageA, TextBlock *blk) { - page = pageA; - xMin = blk->xMin; - xMax = blk->xMax; - yMin = blk->yMin; - yMax = blk->yMax; - priMin = blk->priMin; - priMax = blk->priMax; - blocks = lastBlk = blk; - next = NULL; -} - -TextFlow::~TextFlow() { - TextBlock *blk; - - while (blocks) { - blk = blocks; - blocks = blocks->next; - delete blk; - } -} - -void TextFlow::addBlock(TextBlock *blk) { - if (lastBlk) { - lastBlk->next = blk; - } else { - blocks = blk; - } - lastBlk = blk; - if (blk->xMin < xMin) { - xMin = blk->xMin; - } - if (blk->xMax > xMax) { - xMax = blk->xMax; - } - if (blk->yMin < yMin) { - yMin = blk->yMin; - } - if (blk->yMax > yMax) { - yMax = blk->yMax; - } } -GBool TextFlow::blockFits(TextBlock *blk, TextBlock *prevBlk) { - GBool fits; - - // lower blocks must use smaller fonts - if (blk->lines->words->fontSize > lastBlk->lines->words->fontSize) { - return gFalse; - } +void TextPage::unrotateWords(GList *words, int rot) { + TextWord *word; + double xMin, yMin, xMax, yMax; + int i, j; - fits = gFalse; // make gcc happy - switch (page->primaryRot) { + switch (rot) { case 0: - fits = blk->xMin >= priMin && blk->xMax <= priMax; + default: + // no transform break; case 1: - fits = blk->yMin >= priMin && blk->yMax <= priMax; + for (i = 0; i < words->getLength(); ++i) { + word = (TextWord *)words->get(i); + 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; + word->rot = (word->rot + 1) & 3; + } break; case 2: - fits = blk->xMin >= priMin && blk->xMax <= priMax; + for (i = 0; i < words->getLength(); ++i) { + word = (TextWord *)words->get(i); + 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; + word->rot = (word->rot + 2) & 3; + for (j = 0; j <= word->len; ++j) { + word->edge[j] = pageWidth - word->edge[j]; + } + } break; case 3: - fits = blk->yMin >= priMin && blk->yMax <= priMax; + for (i = 0; i < words->getLength(); ++i) { + word = (TextWord *)words->get(i); + 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; + word->rot = (word->rot + 3) & 3; + for (j = 0; j <= word->len; ++j) { + word->edge[j] = pageHeight - word->edge[j]; + } + } break; } - return fits; } -#if TEXTOUT_WORD_LIST - -//------------------------------------------------------------------------ -// TextWordList -//------------------------------------------------------------------------ - -TextWordList::TextWordList(TextPage *text, GBool physLayout) { - TextFlow *flow; - TextBlock *blk; - TextLine *line; - TextWord *word; - TextWord **wordArray; - int nWords, i; - - words = new GList(); +// Determine the primary text direction (LR vs RL). Returns true for +// LR, false for RL. +GBool TextPage::checkPrimaryLR(GList *charsA) { + TextChar *ch; + int i, lrCount; - if (text->rawOrder) { - for (word = text->rawWords; word; word = word->next) { - words->append(word); - } + lrCount = 0; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + if (unicodeTypeL(ch->c)) { + ++lrCount; + } else if (unicodeTypeR(ch->c)) { + --lrCount; + } + } + return lrCount >= 0; +} + +// Remove duplicate characters. The list of chars has been sorted -- +// by x for rot=0,2; by y for rot=1,3. +void TextPage::removeDuplicates(GList *charsA, int rot) { + TextChar *ch, *ch2; + double xDelta, yDelta; + int i, j; - } else if (physLayout) { - // this is inefficient, but it's also the least useful of these - // three cases - nWords = 0; - for (flow = text->flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - ++nWords; - } + if (rot & 1) { + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + xDelta = dupMaxSecDelta * ch->fontSize; + yDelta = dupMaxPriDelta * ch->fontSize; + j = i + 1; + while (j < charsA->getLength()) { + ch2 = (TextChar *)charsA->get(j); + if (ch2->yMin - ch->yMin >= yDelta) { + break; + } + if (ch2->c == ch->c && + fabs(ch2->xMin - ch->xMin) < xDelta && + fabs(ch2->xMax - ch->xMax) < xDelta && + fabs(ch2->yMax - ch->yMax) < yDelta) { + charsA->del(j); + } else { + ++j; } } } - wordArray = (TextWord **)gmallocn(nWords, sizeof(TextWord *)); - i = 0; - for (flow = text->flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - wordArray[i++] = word; - } + } else { + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + xDelta = dupMaxPriDelta * ch->fontSize; + yDelta = dupMaxSecDelta * ch->fontSize; + j = i + 1; + while (j < charsA->getLength()) { + ch2 = (TextChar *)charsA->get(j); + if (ch2->xMin - ch->xMin >= xDelta) { + break; + } + if (ch2->c == ch->c && + fabs(ch2->xMax - ch->xMax) < xDelta && + fabs(ch2->yMin - ch->yMin) < yDelta && + fabs(ch2->yMax - ch->yMax) < yDelta) { + charsA->del(j); + } else { + ++j; } } } - qsort(wordArray, nWords, sizeof(TextWord *), &TextWord::cmpYX); - for (i = 0; i < nWords; ++i) { - words->append(wordArray[i]); - } - gfree(wordArray); + } +} - } else { - for (flow = text->flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - words->append(word); +// 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) { + TextBlock *tree[4]; + TextBlock *blk; + GList *chars2, *clippedChars; + TextChar *ch; + int rot, i; + + // split: build a tree of TextBlocks for each rotation + clippedChars = new GList(); + for (rot = 0; rot < 4; ++rot) { + chars2 = new GList(); + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + if (ch->rot == rot) { + chars2->append(ch); + } + } + tree[rot] = NULL; + if (chars2->getLength() > 0) { + chars2->sort((rot & 1) ? &TextChar::cmpY : &TextChar::cmpX); + removeDuplicates(chars2, rot); + if (control.clipText) { + i = 0; + while (i < chars2->getLength()) { + ch = (TextChar *)chars2->get(i); + if (ch->clipped) { + ch = (TextChar *)chars2->del(i); + clippedChars->append(ch); + } else { + ++i; } } } + if (chars2->getLength() > 0) { + tree[rot] = split(chars2, rot); + } } + delete chars2; } -} - -TextWordList::~TextWordList() { - delete words; -} - -int TextWordList::getLength() { - return words->getLength(); -} -TextWord *TextWordList::get(int idx) { - if (idx < 0 || idx >= words->getLength()) { + // if the page contains no (unclipped) text, just leave an empty + // column list + if (!tree[0]) { + delete clippedChars; return NULL; } - return (TextWord *)words->get(idx); -} - -#endif // TEXTOUT_WORD_LIST - -//------------------------------------------------------------------------ -// TextPage -//------------------------------------------------------------------------ - -TextPage::TextPage(GBool rawOrderA) { - int rot; - rawOrder = rawOrderA; - curWord = NULL; - charPos = 0; - curFont = NULL; - curFontSize = 0; - nest = 0; - nTinyChars = 0; - lastCharOverlap = gFalse; - actualText = NULL; - actualTextLen = 0; - actualTextNBytes = 0; - if (!rawOrder) { - for (rot = 0; rot < 4; ++rot) { - pools[rot] = new TextPool(); - } + // if the main tree is not a multicolumn node, insert one so that + // rotated text has somewhere to go + if (tree[0]->tag != blkTagMulticolumn) { + blk = new TextBlock(blkHorizSplit, 0); + blk->addChild(tree[0]); + blk->tag = blkTagMulticolumn; + tree[0] = blk; } - flows = NULL; - blocks = NULL; - rawWords = NULL; - rawLastWord = NULL; - fonts = new GList(); - lastFindXMin = lastFindYMin = 0; - haveLastFind = gFalse; - underlines = new GList(); - links = new GList(); -} - -TextPage::~TextPage() { - int rot; - clear(); - if (!rawOrder) { - for (rot = 0; rot < 4; ++rot) { - delete pools[rot]; + // merge non-primary-rotation text into the primary-rotation tree + for (rot = 1; rot < 4; ++rot) { + if (tree[rot]) { + insertIntoTree(tree[rot], tree[0]); + tree[rot] = NULL; } } - delete fonts; - deleteGList(underlines, TextUnderline); - deleteGList(links, TextLink); -} -void TextPage::startPage(GfxState *state) { - clear(); - if (state) { - pageWidth = state->getPageWidth(); - pageHeight = state->getPageHeight(); - } else { - pageWidth = pageHeight = 0; + if (clippedChars->getLength()) { + insertClippedChars(clippedChars, tree[0]); } -} + delete clippedChars; -void TextPage::endPage() { - if (curWord) { - endWord(); - } -} +#if 0 //~debug + dumpTree(tree[0]); +#endif -void TextPage::clear() { - int rot; - TextFlow *flow; - TextWord *word; + return tree[0]; +} - if (curWord) { - delete curWord; - curWord = NULL; - } - gfree(actualText); - if (rawOrder) { - while (rawWords) { - word = rawWords; - rawWords = rawWords->next; - delete word; +// Generate a tree of TextBlocks, marked as columns, lines, and words. +TextBlock *TextPage::split(GList *charsA, int rot) { + TextBlock *blk; + GList *chars2, *chars3; + int *horizProfile, *vertProfile; + double xMin, yMin, xMax, yMax; + int xMinI, yMinI, xMaxI, yMaxI; + int xMinI2, yMinI2, xMaxI2, yMaxI2; + TextChar *ch; + double minFontSize, avgFontSize, splitPrecision; + double nLines, vertGapThreshold, ascentAdjust, descentAdjust, minChunk; + int horizGapSize, vertGapSize; + double horizGapSize2, vertGapSize2; + int minHorizChunkWidth, minVertChunkWidth, nHorizGaps, nVertGaps; + double largeCharSize; + int nLargeChars; + GBool doHorizSplit, doVertSplit, smallSplit; + int i, x, y, prev, start; + + //----- compute bbox, min font size, average font size, and + // split precision for this block + + xMin = yMin = xMax = yMax = 0; // make gcc happy + minFontSize = avgFontSize = 0; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + if (i == 0 || ch->xMin < xMin) { + xMin = ch->xMin; + } + if (i == 0 || ch->yMin < yMin) { + yMin = ch->yMin; + } + if (i == 0 || ch->xMax > xMax) { + xMax = ch->xMax; + } + if (i == 0 || ch->yMax > yMax) { + yMax = ch->yMax; + } + avgFontSize += ch->fontSize; + if (i == 0 || ch->fontSize < minFontSize) { + minFontSize = ch->fontSize; + } + } + avgFontSize /= charsA->getLength(); + splitPrecision = splitPrecisionMul * minFontSize; + if (splitPrecision < minSplitPrecision) { + splitPrecision = minSplitPrecision; + } + + //----- compute the horizontal and vertical profiles + + // add some slack to the array bounds to avoid floating point + // precision problems + xMinI = (int)floor(xMin / splitPrecision) - 1; + yMinI = (int)floor(yMin / splitPrecision) - 1; + xMaxI = (int)floor(xMax / splitPrecision) + 1; + yMaxI = (int)floor(yMax / splitPrecision) + 1; + horizProfile = (int *)gmallocn(yMaxI - yMinI + 1, sizeof(int)); + vertProfile = (int *)gmallocn(xMaxI - xMinI + 1, sizeof(int)); + memset(horizProfile, 0, (yMaxI - yMinI + 1) * sizeof(int)); + memset(vertProfile, 0, (xMaxI - xMinI + 1) * sizeof(int)); + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + // yMinI2 and yMaxI2 are adjusted to allow for slightly overlapping lines + switch (rot) { + case 0: + default: + xMinI2 = (int)floor(ch->xMin / splitPrecision); + xMaxI2 = (int)floor(ch->xMax / splitPrecision); + ascentAdjust = ascentAdjustFactor * (ch->yMax - ch->yMin); + yMinI2 = (int)floor((ch->yMin + ascentAdjust) / splitPrecision); + descentAdjust = descentAdjustFactor * (ch->yMax - ch->yMin); + yMaxI2 = (int)floor((ch->yMax - descentAdjust) / splitPrecision); + break; + case 1: + descentAdjust = descentAdjustFactor * (ch->xMax - ch->xMin); + xMinI2 = (int)floor((ch->xMin + descentAdjust) / splitPrecision); + ascentAdjust = ascentAdjustFactor * (ch->xMax - ch->xMin); + xMaxI2 = (int)floor((ch->xMax - ascentAdjust) / splitPrecision); + yMinI2 = (int)floor(ch->yMin / splitPrecision); + yMaxI2 = (int)floor(ch->yMax / splitPrecision); + break; + case 2: + xMinI2 = (int)floor(ch->xMin / splitPrecision); + xMaxI2 = (int)floor(ch->xMax / splitPrecision); + descentAdjust = descentAdjustFactor * (ch->yMax - ch->yMin); + yMinI2 = (int)floor((ch->yMin + descentAdjust) / splitPrecision); + ascentAdjust = ascentAdjustFactor * (ch->yMax - ch->yMin); + yMaxI2 = (int)floor((ch->yMax - ascentAdjust) / splitPrecision); + break; + case 3: + ascentAdjust = ascentAdjustFactor * (ch->xMax - ch->xMin); + xMinI2 = (int)floor((ch->xMin + ascentAdjust) / splitPrecision); + descentAdjust = descentAdjustFactor * (ch->xMax - ch->xMin); + xMaxI2 = (int)floor((ch->xMax - descentAdjust) / splitPrecision); + yMinI2 = (int)floor(ch->yMin / splitPrecision); + yMaxI2 = (int)floor(ch->yMax / splitPrecision); + break; } - } else { - for (rot = 0; rot < 4; ++rot) { - delete pools[rot]; + for (y = yMinI2; y <= yMaxI2; ++y) { + ++horizProfile[y - yMinI]; } - while (flows) { - flow = flows; - flows = flows->next; - delete flow; + for (x = xMinI2; x <= xMaxI2; ++x) { + ++vertProfile[x - xMinI]; } - gfree(blocks); } - deleteGList(fonts, TextFontInfo); - deleteGList(underlines, TextUnderline); - deleteGList(links, TextLink); - curWord = NULL; - charPos = 0; - curFont = NULL; - curFontSize = 0; - nest = 0; - nTinyChars = 0; - actualText = NULL; - actualTextLen = 0; - actualTextNBytes = 0; - if (!rawOrder) { - for (rot = 0; rot < 4; ++rot) { - pools[rot] = new TextPool(); + //----- find the largest gaps in the horizontal and vertical profiles + + horizGapSize = 0; + for (start = yMinI; start < yMaxI && !horizProfile[start - yMinI]; ++start) ; + for (y = start; y < yMaxI; ++y) { + if (horizProfile[y - yMinI] && !horizProfile[y + 1 - yMinI]) { + start = y; + } else if (!horizProfile[y - yMinI] && horizProfile[y + 1 - yMinI]) { + if (y - start > horizGapSize) { + horizGapSize = y - start; + } } } - flows = NULL; - blocks = NULL; - rawWords = NULL; - rawLastWord = NULL; - fonts = new GList(); - underlines = new GList(); - links = new GList(); -} - -void TextPage::updateFont(GfxState *state) { - GfxFont *gfxFont; - double *fm; - char *name; - int code, mCode, letterCode, anyCode; - double w; - int i; - - // get the font info object - curFont = NULL; - for (i = 0; i < fonts->getLength(); ++i) { - curFont = (TextFontInfo *)fonts->get(i); - if (curFont->matches(state)) { - break; + vertGapSize = 0; + for (start = xMinI; start < xMaxI && !vertProfile[start - xMinI]; ++start) ; + for (x = start; x < xMaxI; ++x) { + if (vertProfile[x - xMinI] && !vertProfile[x + 1 - xMinI]) { + start = x; + } else if (!vertProfile[x - xMinI] && vertProfile[x + 1 - xMinI]) { + if (x - start > vertGapSize) { + vertGapSize = x - start; + } } - curFont = NULL; } - if (!curFont) { - curFont = new TextFontInfo(state); - fonts->append(curFont); + horizGapSize2 = horizGapSize - splitGapSlack * avgFontSize / splitPrecision; + if (horizGapSize2 < 0.99) { + horizGapSize2 = 0.99; + } + vertGapSize2 = vertGapSize - splitGapSlack * avgFontSize / splitPrecision; + if (vertGapSize2 < 0.99) { + vertGapSize2 = 0.99; } - // adjust the font size - gfxFont = state->getFont(); - curFontSize = state->getTransformedFontSize(); - if (gfxFont && gfxFont->getType() == fontType3) { - // This is a hack which makes it possible to deal with some Type 3 - // fonts. The problem is that it's impossible to know what the - // base coordinate system used in the font is without actually - // rendering the font. This code tries to guess by looking at the - // width of the character 'm' (which breaks if the font is a - // subset that doesn't contain 'm'). - mCode = letterCode = anyCode = -1; - for (code = 0; code < 256; ++code) { - name = ((Gfx8BitFont *)gfxFont)->getCharName(code); - if (name && name[0] == 'm' && name[1] == '\0') { - mCode = code; - } - if (letterCode < 0 && name && name[1] == '\0' && - ((name[0] >= 'A' && name[0] <= 'Z') || - (name[0] >= 'a' && name[0] <= 'z'))) { - letterCode = code; - } - if (anyCode < 0 && name && - ((Gfx8BitFont *)gfxFont)->getWidth(code) > 0) { - anyCode = code; + //----- count horiz/vert gaps equivalent to largest gaps + + minHorizChunkWidth = yMaxI - yMinI; + nHorizGaps = 0; + for (start = yMinI; start < yMaxI && !horizProfile[start - yMinI]; ++start) ; + prev = start - 1; + for (y = start; y < yMaxI; ++y) { + if (horizProfile[y - yMinI] && !horizProfile[y + 1 - yMinI]) { + start = y; + } else if (!horizProfile[y - yMinI] && horizProfile[y + 1 - yMinI]) { + if (y - start > horizGapSize2) { + ++nHorizGaps; + if (start - prev < minHorizChunkWidth) { + minHorizChunkWidth = start - prev; + } + prev = y; } } - if (mCode >= 0 && - (w = ((Gfx8BitFont *)gfxFont)->getWidth(mCode)) > 0) { - // 0.6 is a generic average 'm' width -- yes, this is a hack - curFontSize *= w / 0.6; - } else if (letterCode >= 0 && - (w = ((Gfx8BitFont *)gfxFont)->getWidth(letterCode)) > 0) { - // even more of a hack: 0.5 is a generic letter width - curFontSize *= w / 0.5; - } else if (anyCode >= 0 && - (w = ((Gfx8BitFont *)gfxFont)->getWidth(anyCode)) > 0) { - // better than nothing: 0.5 is a generic character width - curFontSize *= w / 0.5; - } - fm = gfxFont->getFontMatrix(); - if (fm[0] != 0) { - curFontSize *= fabs(fm[3] / fm[0]); + } + minVertChunkWidth = xMaxI - xMinI; + nVertGaps = 0; + for (start = xMinI; start < xMaxI && !vertProfile[start - xMinI]; ++start) ; + prev = start - 1; + for (x = start; x < xMaxI; ++x) { + if (vertProfile[x - xMinI] && !vertProfile[x + 1 - xMinI]) { + start = x; + } else if (!vertProfile[x - xMinI] && vertProfile[x + 1 - xMinI]) { + if (x - start > vertGapSize2) { + ++nVertGaps; + if (start - prev < minVertChunkWidth) { + minVertChunkWidth = start - prev; + } + prev = x; + } } } -} -void TextPage::beginWord(GfxState *state, double x0, double y0) { - double *fontm; - double m[4], m2[4]; - int rot; + //----- compute splitting parameters - // This check is needed because Type 3 characters can contain - // text-drawing operations (when TextPage is being used via - // {X,Win}SplashOutputDev rather than TextOutputDev). - if (curWord) { - ++nest; - return; + // approximation of number of lines in block + if (fabs(avgFontSize) < 0.001) { + nLines = 1; + } else if (rot & 1) { + nLines = (xMax - xMin) / avgFontSize; + } else { + nLines = (yMax - yMin) / avgFontSize; } - // compute the rotation - state->getFontTransMat(&m[0], &m[1], &m[2], &m[3]); - if (state->getFont()->getType() == fontType3) { - fontm = state->getFont()->getFontMatrix(); - m2[0] = fontm[0] * m[0] + fontm[1] * m[2]; - m2[1] = fontm[0] * m[1] + fontm[1] * m[3]; - m2[2] = fontm[2] * m[0] + fontm[3] * m[2]; - m2[3] = fontm[2] * m[1] + fontm[3] * m[3]; - m[0] = m2[0]; - m[1] = m2[1]; - m[2] = m2[2]; - m[3] = m2[3]; + // compute the minimum allowed vertical gap size + // (this is a horizontal gap threshold for rot=1,3 + if (control.mode == textOutTableLayout) { + vertGapThreshold = vertGapThresholdTableMax + + vertGapThresholdTableSlope * nLines; + if (vertGapThreshold < vertGapThresholdTableMin) { + vertGapThreshold = vertGapThresholdTableMin; + } + } else { + vertGapThreshold = vertGapThresholdMax + vertGapThresholdSlope * nLines; + if (vertGapThreshold < vertGapThresholdMin) { + vertGapThreshold = vertGapThresholdMin; + } } - if (fabs(m[0] * m[3]) > fabs(m[1] * m[2])) { - rot = (m[0] > 0 || m[3] < 0) ? 0 : 2; + vertGapThreshold = vertGapThreshold * avgFontSize / splitPrecision; + + // compute the minimum allowed chunk width + if (control.mode == textOutTableLayout) { + minChunk = 0; } else { - rot = (m[2] > 0) ? 1 : 3; + minChunk = vertSplitChunkThreshold * avgFontSize / splitPrecision; + } + + // look for large chars + // -- this kludge (multiply by 256, convert to int, divide by 256.0) + // prevents floating point stability issues on x86 with gcc, where + // 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; + } } - // for vertical writing mode, the lines are effectively rotated 90 - // degrees - if (state->getFont()->getWMode()) { - rot = (rot + 1) & 3; + // figure out which type of split to do + doHorizSplit = doVertSplit = gFalse; + smallSplit = gFalse; + if (rot & 1) { + if (nHorizGaps > 0 && + (horizGapSize > vertGapSize || control.mode == textOutTableLayout) && + horizGapSize > vertGapThreshold && + minHorizChunkWidth > minChunk) { + doHorizSplit = gTrue; + } else if (nVertGaps > 0) { + doVertSplit = gTrue; + } else if (nLargeChars == 0 && nHorizGaps > 0) { + doHorizSplit = gTrue; + smallSplit = gTrue; + } + } else { + if (nVertGaps > 0 && + (vertGapSize > horizGapSize || control.mode == textOutTableLayout) && + vertGapSize > vertGapThreshold && + minVertChunkWidth > minChunk) { + doVertSplit = gTrue; + } else if (nHorizGaps > 0) { + doHorizSplit = gTrue; + } else if (nLargeChars == 0 && nVertGaps > 0) { + doVertSplit = gTrue; + smallSplit = gTrue; + } + } + + //----- split the block + + //~ this could use "other content" (vector graphics, rotated text) -- + //~ presence of other content in a gap means we should definitely split + + // split vertically + if (doVertSplit) { + blk = new TextBlock(blkVertSplit, rot); + blk->smallSplit = smallSplit; + for (start = xMinI; start < xMaxI && !vertProfile[start - xMinI]; ++start) ; + prev = start - 1; + for (x = start; x < xMaxI; ++x) { + if (vertProfile[x - xMinI] && !vertProfile[x + 1 - xMinI]) { + start = x; + } else if (!vertProfile[x - xMinI] && vertProfile[x + 1 - xMinI]) { + if (x - start > vertGapSize2) { + chars2 = getChars(charsA, (prev + 0.5) * splitPrecision, yMin - 1, + (start + 1.5) * splitPrecision, yMax + 1); + blk->addChild(split(chars2, rot)); + delete chars2; + prev = x; + } + } + } + chars2 = getChars(charsA, (prev + 0.5) * splitPrecision, yMin - 1, + xMax + 1, yMax + 1); + blk->addChild(split(chars2, rot)); + delete chars2; + + // split horizontally + } else if (doHorizSplit) { + blk = new TextBlock(blkHorizSplit, rot); + blk->smallSplit = smallSplit; + for (start = yMinI; + start < yMaxI && !horizProfile[start - yMinI]; + ++start) ; + prev = start - 1; + for (y = start; y < yMaxI; ++y) { + if (horizProfile[y - yMinI] && !horizProfile[y + 1 - yMinI]) { + start = y; + } else if (!horizProfile[y - yMinI] && horizProfile[y + 1 - yMinI]) { + if (y - start > horizGapSize2) { + chars2 = getChars(charsA, xMin - 1, (prev + 0.5) * splitPrecision, + xMax + 1, (start + 1.5) * splitPrecision); + blk->addChild(split(chars2, rot)); + delete chars2; + prev = y; + } + } + } + chars2 = getChars(charsA, xMin - 1, (prev + 0.5) * splitPrecision, + xMax + 1, yMax + 1); + blk->addChild(split(chars2, rot)); + delete chars2; + + // split into larger and smaller chars + } else if (nLargeChars > 0) { + chars2 = new GList(); + chars3 = new GList(); + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + if (ch->fontSize > largeCharSize) { + chars2->append(ch); + } else { + chars3->append(ch); + } + } + blk = split(chars3, rot); + insertLargeChars(chars2, blk); + delete chars2; + delete chars3; + + // create a leaf node + } else { + blk = new TextBlock(blkLeaf, rot); + for (i = 0; i < charsA->getLength(); ++i) { + blk->addChild((TextChar *)charsA->get(i)); + } } - curWord = new TextWord(state, rot, x0, y0, curFont, curFontSize); + gfree(horizProfile); + gfree(vertProfile); + + tagBlock(blk); + + return blk; } -void TextPage::addChar(GfxState *state, double x, double y, - double dx, double dy, - CharCode c, int nBytes, Unicode *u, int uLen) { - double x1, y1, w1, h1, dx2, dy2, base, sp, delta; - GBool overlap; +// Return the subset of chars inside a rectangle. +GList *TextPage::getChars(GList *charsA, double xMin, double yMin, + double xMax, double yMax) { + GList *ret; + TextChar *ch; + double x, y; int i; - // if we're in an ActualText span, save the position info (the - // ActualText chars will be added by TextPage::endActualText()). - if (actualText) { - if (!actualTextNBytes) { - actualTextX0 = x; - actualTextY0 = y; + ret = new GList(); + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + // because of {ascent,descent}AdjustFactor, the y coords (or x + // coords for rot 1,3) for the gaps will be a little bit tight -- + // so we use the center of the character here + x = 0.5 * (ch->xMin + ch->xMax); + y = 0.5 * (ch->yMin + ch->yMax); + if (x > xMin && x < xMax && y > yMin && y < yMax) { + ret->append(ch); } - actualTextX1 = x + dx; - actualTextY1 = y + dy; - actualTextNBytes += nBytes; - return; } + return ret; +} - // subtract char and word spacing from the dx,dy values - sp = state->getCharSpace(); - if (c == (CharCode)0x20) { - sp += state->getWordSpace(); - } - state->textTransformDelta(sp * state->getHorizScaling(), 0, &dx2, &dy2); - dx -= dx2; - dy -= dy2; - state->transformDelta(dx, dy, &w1, &h1); +// Decide whether this block is a line, column, or multiple columns: +// - all leaf nodes are lines +// - horiz split nodes whose children are lines or columns are columns +// - other horiz split nodes are multiple columns +// - vert split nodes, with small gaps, whose children are lines are lines +// - other vert split nodes are multiple columns +// (for rot=1,3: the horiz and vert splits are swapped) +// In table layout mode: +// - all leaf nodes are lines +// - vert split nodes, with small gaps, whose children are lines are lines +// - everything else is multiple columns +void TextPage::tagBlock(TextBlock *blk) { + TextBlock *child; + int i; - // throw away chars that aren't inside the page bounds - // (and also do a sanity check on the character size) - state->transform(x, y, &x1, &y1); - if (x1 + w1 < 0 || x1 > pageWidth || - y1 + h1 < 0 || y1 > pageHeight || - w1 > pageWidth || h1 > pageHeight) { - charPos += nBytes; + if (control.mode == textOutTableLayout) { + if (blk->type == blkLeaf) { + blk->tag = blkTagLine; + } else if (blk->type == ((blk->rot & 1) ? blkHorizSplit : blkVertSplit) && + blk->smallSplit) { + blk->tag = blkTagLine; + for (i = 0; i < blk->children->getLength(); ++i) { + child = (TextBlock *)blk->children->get(i); + if (child->tag != blkTagLine) { + blk->tag = blkTagMulticolumn; + break; + } + } + } else { + blk->tag = blkTagMulticolumn; + } return; } - // check the tiny chars limit - if (!globalParams->getTextKeepTinyChars() && - fabs(w1) < 3 && fabs(h1) < 3) { - if (++nTinyChars > 50000) { - charPos += nBytes; - return; - } - } + if (blk->type == blkLeaf) { + blk->tag = blkTagLine; - // break words at space character - if (uLen == 1 && u[0] == (Unicode)0x20) { - charPos += nBytes; - endWord(); - return; + } else { + if (blk->type == ((blk->rot & 1) ? blkVertSplit : blkHorizSplit)) { + blk->tag = blkTagColumn; + for (i = 0; i < blk->children->getLength(); ++i) { + child = (TextBlock *)blk->children->get(i); + if (child->tag != blkTagColumn && child->tag != blkTagLine) { + blk->tag = blkTagMulticolumn; + break; + } + } + } else { + if (blk->smallSplit) { + blk->tag = blkTagLine; + for (i = 0; i < blk->children->getLength(); ++i) { + child = (TextBlock *)blk->children->get(i); + if (child->tag != blkTagLine) { + blk->tag = blkTagMulticolumn; + break; + } + } + } else { + blk->tag = blkTagMulticolumn; + } + } } +} - // start a new word if: - // (1) this character doesn't fall in the right place relative to - // the end of the previous word (this places upper and lower - // constraints on the position deltas along both the primary - // and secondary axes), or - // (2) this character overlaps the previous one (duplicated text), or - // (3) the previous character was an overlap (we want each duplicated - // character to be in a word by itself at this stage), - // (4) the font or font size has changed - if (curWord && curWord->len > 0) { - base = sp = delta = 0; // make gcc happy - switch (curWord->rot) { - case 0: - base = y1; - sp = x1 - curWord->xMax; - delta = x1 - curWord->edge[curWord->len - 1]; - break; - case 1: - base = x1; - sp = y1 - curWord->yMax; - delta = y1 - curWord->edge[curWord->len - 1]; - break; - case 2: - base = y1; - sp = curWord->xMin - x1; - delta = curWord->edge[curWord->len - 1] - x1; - break; - case 3: - base = x1; - sp = curWord->yMin - y1; - delta = curWord->edge[curWord->len - 1] - y1; +// Insert a list of large characters into a tree. +void TextPage::insertLargeChars(GList *largeChars, TextBlock *blk) { + TextChar *ch, *ch2; + GBool singleLine; + double xLimit, yLimit, minOverlap; + int i; + + //~ this currently works only for characters in the primary rotation + + // check to see if the large chars are a single line, in the + // upper-left corner of blk (this is just a rough estimate) + xLimit = blk->xMin + 0.5 * (blk->xMin + blk->xMax); + yLimit = blk->yMin + 0.5 * (blk->yMin + blk->yMax); + singleLine = gTrue; + // note: largeChars are already sorted by x + for (i = 0; i < largeChars->getLength(); ++i) { + ch2 = (TextChar *)largeChars->get(i); + if (ch2->xMax > xLimit || ch2->yMax > yLimit) { + singleLine = gFalse; break; } - overlap = fabs(delta) < dupMaxPriDelta * curWord->fontSize && - fabs(base - curWord->base) < dupMaxSecDelta * curWord->fontSize; - if (overlap || lastCharOverlap || - sp < -minDupBreakOverlap * curWord->fontSize || - sp > minWordBreakSpace * curWord->fontSize || - fabs(base - curWord->base) > 0.5 || - curFont != curWord->font || - curFontSize != curWord->fontSize) { - endWord(); + if (i > 0) { + ch = (TextChar *)largeChars->get(i-1); + minOverlap = 0.5 * (ch->fontSize < ch2->fontSize ? ch->fontSize + : ch2->fontSize); + if (ch->yMax - ch2->yMin < minOverlap || + ch2->yMax - ch->yMin < minOverlap) { + singleLine = gFalse; + break; + } } - lastCharOverlap = overlap; - } else { - lastCharOverlap = gFalse; } - if (uLen != 0) { - // start a new word if needed - if (!curWord) { - beginWord(state, x, y); - } - - // page rotation and/or transform matrices can cause text to be - // drawn in reverse order -- in this case, swap the begin/end - // coordinates and break text into individual chars - if ((curWord->rot == 0 && w1 < 0) || - (curWord->rot == 1 && h1 < 0) || - (curWord->rot == 2 && w1 > 0) || - (curWord->rot == 3 && h1 > 0)) { - endWord(); - beginWord(state, x + dx, y + dy); - x1 += w1; - y1 += h1; - w1 = -w1; - h1 = -h1; - } - - // add the characters to the current word - w1 /= uLen; - h1 /= uLen; - for (i = 0; i < uLen; ++i) { - curWord->addChar(state, x1 + i*w1, y1 + i*h1, w1, h1, - charPos, nBytes, u[i]); + if (singleLine) { + // if the large chars are a single line, prepend them to the first + // leaf node in blk + insertLargeCharsInFirstLeaf(largeChars, blk); + } else { + // if the large chars are not a single line, prepend each one to + // the appropriate leaf node -- this handles cases like bullets + // drawn in a large font, on the left edge of a column + for (i = largeChars->getLength() - 1; i >= 0; --i) { + ch = (TextChar *)largeChars->get(i); + insertLargeCharInLeaf(ch, blk); } } - charPos += nBytes; } -void TextPage::incCharCount(int nChars) { - charPos += nChars; -} +// Find the first leaf (in depth-first order) in blk, and prepend a +// list of large chars. +void TextPage::insertLargeCharsInFirstLeaf(GList *largeChars, TextBlock *blk) { + TextChar *ch; + int i; -void TextPage::beginActualText(GfxState *state, Unicode *u, int uLen) { - if (actualText) { - gfree(actualText); + if (blk->type == blkLeaf) { + for (i = largeChars->getLength() - 1; i >= 0; --i) { + ch = (TextChar *)largeChars->get(i); + blk->prependChild(ch); + } + } else { + insertLargeCharsInFirstLeaf(largeChars, (TextBlock *)blk->children->get(0)); + blk->updateBounds(0); } - actualText = (Unicode *)gmallocn(uLen, sizeof(Unicode)); - memcpy(actualText, u, uLen * sizeof(Unicode)); - actualTextLen = uLen; - actualTextNBytes = 0; } -void TextPage::endActualText(GfxState *state) { - Unicode *u; +// Find the leaf in where large char belongs, and prepend +// it. +void TextPage::insertLargeCharInLeaf(TextChar *ch, TextBlock *blk) { + TextBlock *child; + double y; + int i; - u = actualText; - actualText = NULL; // so we can call TextPage::addChar() - if (actualTextNBytes) { - // now that we have the position info for all of the text inside - // the marked content span, we feed the "ActualText" back through - // addChar() - addChar(state, actualTextX0, actualTextY0, - actualTextX1 - actualTextX0, actualTextY1 - actualTextY0, - 0, actualTextNBytes, u, actualTextLen); - } - gfree(u); - actualText = NULL; - actualTextLen = 0; - actualTextNBytes = gFalse; -} + //~ this currently works only for characters in the primary rotation -void TextPage::endWord() { - // This check is needed because Type 3 characters can contain - // text-drawing operations (when TextPage is being used via - // {X,Win}SplashOutputDev rather than TextOutputDev). - if (nest > 0) { - --nest; - return; - } + //~ this currently just looks down the left edge of blk + //~ -- it could be extended to do more + + // estimate the baseline of ch + y = ch->yMin + 0.75 * (ch->yMax - ch->yMin); - if (curWord) { - addWord(curWord); - curWord = NULL; + if (blk->type == blkLeaf) { + blk->prependChild(ch); + } else if (blk->type == blkHorizSplit) { + for (i = 0; i < blk->children->getLength(); ++i) { + child = (TextBlock *)blk->children->get(i); + if (y < child->yMax || i == blk->children->getLength() - 1) { + insertLargeCharInLeaf(ch, child); + blk->updateBounds(i); + break; + } + } + } else { + insertLargeCharInLeaf(ch, (TextBlock *)blk->children->get(0)); + blk->updateBounds(0); } } -void TextPage::addWord(TextWord *word) { - // throw away zero-length words -- they don't have valid xMin/xMax - // values, and they're useless anyway - if (word->len == 0) { - delete word; - return; - } +// Merge blk (rot != 0) into primaryTree (rot == 0). +void TextPage::insertIntoTree(TextBlock *blk, TextBlock *primaryTree) { + TextBlock *child; - if (rawOrder) { - if (rawLastWord) { - rawLastWord->next = word; - } else { - rawWords = word; + // we insert a whole column at a time - so call insertIntoTree + // recursively until we get to a column (or line) + + if (blk->tag == blkTagMulticolumn) { + while (blk->children->getLength()) { + child = (TextBlock *)blk->children->del(0); + insertIntoTree(child, primaryTree); } - rawLastWord = word; + delete blk; } else { - pools[word->rot]->addWord(word); + insertColumnIntoTree(blk, primaryTree); } } -void TextPage::addUnderline(double x0, double y0, double x1, double y1) { - underlines->append(new TextUnderline(x0, y0, x1, y1)); -} - -void TextPage::addLink(int xMin, int yMin, int xMax, int yMax, Link *link) { - links->append(new TextLink(xMin, yMin, xMax, yMax, link)); -} +// Insert a column (as an atomic subtree) into tree. +// Requirement: tree is not a leaf node. +void TextPage::insertColumnIntoTree(TextBlock *column, TextBlock *tree) { + TextBlock *child; + int i; -void TextPage::coalesce(GBool physLayout, double fixedPitch, GBool doHTML) { - UnicodeMap *uMap; - TextPool *pool; - TextWord *word0, *word1, *word2; - TextLine *line; - TextBlock *blkList, *blkStack, *blk, *lastBlk, *blk0, *blk1; - TextBlock **blkArray; - TextFlow *flow, *lastFlow; - TextUnderline *underline; - TextLink *link; - int rot, poolMinBaseIdx, baseIdx, startBaseIdx, endBaseIdx; - double minBase, maxBase, newMinBase, newMaxBase; - double fontSize, colSpace1, colSpace2, lineSpace, intraLineSpace, blkSpace; - GBool found; - int count[4]; - int lrCount; - int firstBlkIdx, nBlocksLeft; - int col1, col2; - int i, j, n; - - if (rawOrder) { - primaryRot = 0; - primaryLR = gTrue; - return; + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if (child->tag == blkTagMulticolumn && + column->xMin >= child->xMin && + column->yMin >= child->yMin && + column->xMax <= child->xMax && + column->yMax <= child->yMax) { + insertColumnIntoTree(column, child); + tree->tag = blkTagMulticolumn; + return; + } } - uMap = globalParams->getTextEncoding(); - blkList = NULL; - lastBlk = NULL; - nBlocks = 0; - primaryRot = 0; - -#if 0 // for debugging - printf("*** initial words ***\n"); - for (rot = 0; rot < 4; ++rot) { - pool = pools[rot]; - for (baseIdx = pool->minBaseIdx; baseIdx <= pool->maxBaseIdx; ++baseIdx) { - for (word0 = pool->getPool(baseIdx); word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f rot=%d link=%p '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, rot*90, word0->link); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); + if (tree->type == blkVertSplit) { + if (tree->rot == 1 || tree->rot == 2) { + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if (column->xMax > 0.5 * (child->xMin + child->xMax)) { + break; + } + } + } else { + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if (column->xMin < 0.5 * (child->xMin + child->xMax)) { + break; } - printf("'\n"); } } + } else if (tree->type == blkHorizSplit) { + if (tree->rot >= 2) { + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if (column->yMax > 0.5 * (child->yMin + child->yMax)) { + break; + } + } + } else { + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if (column->yMin < 0.5 * (child->yMin + child->yMax)) { + break; + } + } + } + } else { + // this should never happen + return; } - printf("\n"); -#endif - -#if 0 //~ for debugging - for (i = 0; i < underlines->getLength(); ++i) { - underline = (TextUnderline *)underlines->get(i); - printf("underline: x=%g..%g y=%g..%g horiz=%d\n", - underline->x0, underline->x1, underline->y0, underline->y1, - underline->horiz); - } -#endif + tree->children->insert(i, column); + tree->tag = blkTagMulticolumn; +} - if (doHTML) { +// Insert clipped characters back into the TextBlock tree. +void TextPage::insertClippedChars(GList *clippedChars, TextBlock *tree) { + TextChar *ch, *ch2; + TextBlock *leaf; + double y; + int i; - //----- handle underlining - for (i = 0; i < underlines->getLength(); ++i) { - underline = (TextUnderline *)underlines->get(i); - if (underline->horiz) { - // rot = 0 - if (pools[0]->minBaseIdx <= pools[0]->maxBaseIdx) { - startBaseIdx = pools[0]->getBaseIdx(underline->y0 + minUnderlineGap); - endBaseIdx = pools[0]->getBaseIdx(underline->y0 + maxUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[0]->getPool(j); word0; word0 = word0->next) { - //~ need to check the y value against the word baseline - if (underline->x0 < word0->xMin + underlineSlack && - word0->xMax - underlineSlack < underline->x1) { - word0->underlined = gTrue; - } - } - } - } + //~ this currently works only for characters in the primary rotation - // rot = 2 - if (pools[2]->minBaseIdx <= pools[2]->maxBaseIdx) { - startBaseIdx = pools[2]->getBaseIdx(underline->y0 - maxUnderlineGap); - endBaseIdx = pools[2]->getBaseIdx(underline->y0 - minUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[2]->getPool(j); word0; word0 = word0->next) { - if (underline->x0 < word0->xMin + underlineSlack && - word0->xMax - underlineSlack < underline->x1) { - word0->underlined = gTrue; - } - } - } - } + clippedChars->sort(TextChar::cmpX); + while (clippedChars->getLength()) { + ch = (TextChar *)clippedChars->del(0); + if (ch->rot != 0) { + continue; + } + if (!(leaf = findClippedCharLeaf(ch, tree))) { + continue; + } + leaf->addChild(ch); + i = 0; + while (i < clippedChars->getLength()) { + ch2 = (TextChar *)clippedChars->get(i); + if (ch2->xMin > ch->xMax + clippedTextMaxWordSpace * ch->fontSize) { + break; + } + y = 0.5 * (ch2->yMin + ch2->yMax); + if (y > leaf->yMin && y < leaf->yMax) { + ch2 = (TextChar *)clippedChars->del(i); + leaf->addChild(ch2); + ch = ch2; } else { - // rot = 1 - if (pools[1]->minBaseIdx <= pools[1]->maxBaseIdx) { - startBaseIdx = pools[1]->getBaseIdx(underline->x0 - maxUnderlineGap); - endBaseIdx = pools[1]->getBaseIdx(underline->x0 - minUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[1]->getPool(j); word0; word0 = word0->next) { - if (underline->y0 < word0->yMin + underlineSlack && - word0->yMax - underlineSlack < underline->y1) { - word0->underlined = gTrue; - } - } - } - } - - // rot = 3 - if (pools[3]->minBaseIdx <= pools[3]->maxBaseIdx) { - startBaseIdx = pools[3]->getBaseIdx(underline->x0 + minUnderlineGap); - endBaseIdx = pools[3]->getBaseIdx(underline->x0 + maxUnderlineGap); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[3]->getPool(j); word0; word0 = word0->next) { - if (underline->y0 < word0->yMin + underlineSlack && - word0->yMax - underlineSlack < underline->y1) { - word0->underlined = gTrue; - } - } - } - } + ++i; } } + } +} - //----- handle links - for (i = 0; i < links->getLength(); ++i) { - link = (TextLink *)links->get(i); +// Find the leaf in to which clipped char can be appended. +// Returns NULL if there is no appropriate append point. +TextBlock *TextPage::findClippedCharLeaf(TextChar *ch, TextBlock *tree) { + TextBlock *ret, *child; + double y; + int i; - // rot = 0 - if (pools[0]->minBaseIdx <= pools[0]->maxBaseIdx) { - startBaseIdx = pools[0]->getBaseIdx(link->yMin); - endBaseIdx = pools[0]->getBaseIdx(link->yMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[0]->getPool(j); word0; word0 = word0->next) { - if (link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax && - link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax) { - word0->link = link->link; - } - } - } - } + //~ this currently works only for characters in the primary rotation - // rot = 2 - if (pools[2]->minBaseIdx <= pools[2]->maxBaseIdx) { - startBaseIdx = pools[2]->getBaseIdx(link->yMin); - endBaseIdx = pools[2]->getBaseIdx(link->yMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[2]->getPool(j); word0; word0 = word0->next) { - if (link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax && - link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax) { - word0->link = link->link; - } - } - } + y = 0.5 * (ch->yMin + ch->yMax); + if (tree->type == blkLeaf) { + if (tree->rot == 0) { + if (y > tree->yMin && y < tree->yMax && + ch->xMin <= tree->xMax + clippedTextMaxWordSpace * ch->fontSize) { + return tree; } - - // rot = 1 - if (pools[1]->minBaseIdx <= pools[1]->maxBaseIdx) { - startBaseIdx = pools[1]->getBaseIdx(link->xMin); - endBaseIdx = pools[1]->getBaseIdx(link->xMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[1]->getPool(j); word0; word0 = word0->next) { - if (link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax && - link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax) { - word0->link = link->link; - } - } - } + } + } else { + for (i = 0; i < tree->children->getLength(); ++i) { + child = (TextBlock *)tree->children->get(i); + if ((ret = findClippedCharLeaf(ch, child))) { + return ret; } + } + } + return NULL; +} - // rot = 3 - if (pools[3]->minBaseIdx <= pools[3]->maxBaseIdx) { - startBaseIdx = pools[3]->getBaseIdx(link->xMin); - endBaseIdx = pools[3]->getBaseIdx(link->xMax); - for (j = startBaseIdx; j <= endBaseIdx; ++j) { - for (word0 = pools[3]->getPool(j); word0; word0 = word0->next) { - if (link->yMin < word0->yMin + hyperlinkSlack && - word0->yMax - hyperlinkSlack < link->yMax && - link->xMin < word0->xMin + hyperlinkSlack && - word0->xMax - hyperlinkSlack < link->xMax) { - word0->link = link->link; - } - } - } - } +// Convert the tree of TextBlocks into a list of TextColumns. +GList *TextPage::buildColumns(TextBlock *tree) { + GList *columns; + + columns = new GList(); + buildColumns2(tree, columns); + return columns; +} + +void TextPage::buildColumns2(TextBlock *blk, GList *columns) { + TextColumn *col; + int i; + + switch (blk->tag) { + case blkTagLine: + case blkTagColumn: + col = buildColumn(blk); + columns->append(col); + break; + case blkTagMulticolumn: + for (i = 0; i < blk->children->getLength(); ++i) { + buildColumns2((TextBlock *)blk->children->get(i), columns); } + break; } +} - //----- assemble the blocks +TextColumn *TextPage::buildColumn(TextBlock *blk) { + GList *lines, *parLines; + GList *paragraphs; + TextLine *line0, *line1; + double spaceThresh, indent0, indent1, fontSize0, fontSize1; + int i; - //~ add an outer loop for writing mode (vertical text) + lines = new GList(); + buildLines(blk, lines); - // build blocks for each rotation value - for (rot = 0; rot < 4; ++rot) { - pool = pools[rot]; - poolMinBaseIdx = pool->minBaseIdx; - count[rot] = 0; - - // add blocks until no more words are left - while (1) { - - // find the first non-empty line in the pool - for (; - poolMinBaseIdx <= pool->maxBaseIdx && - !pool->getPool(poolMinBaseIdx); - ++poolMinBaseIdx) ; - if (poolMinBaseIdx > pool->maxBaseIdx) { - break; - } + spaceThresh = paragraphSpacingThreshold * getAverageLineSpacing(lines); - // look for the left-most word in the first four lines of the - // pool -- this avoids starting with a superscript word - startBaseIdx = poolMinBaseIdx; - for (baseIdx = poolMinBaseIdx + 1; - baseIdx < poolMinBaseIdx + 4 && baseIdx <= pool->maxBaseIdx; - ++baseIdx) { - if (!pool->getPool(baseIdx)) { - continue; - } - if (pool->getPool(baseIdx)->primaryCmp(pool->getPool(startBaseIdx)) - < 0) { - startBaseIdx = baseIdx; - } - } + //~ could look for bulleted lists here: look for the case where + //~ all out-dented lines start with the same char - // create a new block - word0 = pool->getPool(startBaseIdx); - pool->setPool(startBaseIdx, word0->next); - word0->next = NULL; - blk = new TextBlock(this, rot); - blk->addWord(word0); - - fontSize = word0->fontSize; - minBase = maxBase = word0->base; - colSpace1 = minColSpacing1 * fontSize; - colSpace2 = minColSpacing2 * fontSize; - lineSpace = maxLineSpacingDelta * fontSize; - intraLineSpace = maxIntraLineDelta * fontSize; - - // add words to the block - do { - found = gFalse; - - // look for words on the line above the current top edge of - // the block - newMinBase = minBase; - for (baseIdx = pool->getBaseIdx(minBase); - baseIdx >= pool->getBaseIdx(minBase - lineSpace); - --baseIdx) { - word0 = NULL; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base < minBase && - word1->base >= minBase - lineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin < blk->xMax && word1->xMax > blk->xMin) - : (word1->yMin < blk->yMax && word1->yMax > blk->yMin)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta1 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = NULL; - blk->addWord(word2); - found = gTrue; - newMinBase = word2->base; - } else { - word0 = word1; - word1 = word1->next; - } - } - } - minBase = newMinBase; + // build the paragraphs + paragraphs = new GList(); + i = 0; + while (i < lines->getLength()) { - // look for words on the line below the current bottom edge of - // the block - newMaxBase = maxBase; - for (baseIdx = pool->getBaseIdx(maxBase); - baseIdx <= pool->getBaseIdx(maxBase + lineSpace); - ++baseIdx) { - word0 = NULL; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base > maxBase && - word1->base <= maxBase + lineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin < blk->xMax && word1->xMax > blk->xMin) - : (word1->yMin < blk->yMax && word1->yMax > blk->yMin)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta1 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = NULL; - blk->addWord(word2); - found = gTrue; - newMaxBase = word2->base; - } else { - word0 = word1; - word1 = word1->next; - } + // get the first line of the paragraph + parLines = new GList(); + line0 = (TextLine *)lines->get(i); + parLines->append(line0); + ++i; + + if (i < lines->getLength()) { + line1 = (TextLine *)lines->get(i); + indent0 = getLineIndent(line0, blk); + indent1 = getLineIndent(line1, blk); + fontSize0 = line0->fontSize; + fontSize1 = line1->fontSize; + + // inverted indent + if (indent1 - indent0 > minParagraphIndent * fontSize0 && + fabs(fontSize0 - fontSize1) <= paragraphFontSizeDelta && + getLineSpacing(line0, line1) <= spaceThresh) { + parLines->append(line1); + indent0 = indent1; + for (++i; i < lines->getLength(); ++i) { + line1 = (TextLine *)lines->get(i); + indent1 = getLineIndent(line1, blk); + fontSize1 = line1->fontSize; + if (indent0 - indent1 > minParagraphIndent * fontSize0) { + break; } - } - maxBase = newMaxBase; - - // look for words that are on lines already in the block, and - // that overlap the block horizontally - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word0 = NULL; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin < blk->xMax + colSpace1 && - word1->xMax > blk->xMin - colSpace1) - : (word1->yMin < blk->yMax + colSpace1 && - word1->yMax > blk->yMin - colSpace1)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta2 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = NULL; - blk->addWord(word2); - found = gTrue; - } else { - word0 = word1; - word1 = word1->next; - } + if (fabs(fontSize0 - fontSize1) > paragraphFontSizeDelta) { + break; } + if (getLineSpacing((TextLine *)lines->get(i - 1), line1) + > spaceThresh) { + break; + } + parLines->append(line1); } - // only check for outlying words (the next two chunks of code) - // if we didn't find anything else - if (found) { - continue; - } - - // scan down the left side of the block, looking for words - // that are near (but not overlapping) the block; if there are - // three or fewer, add them to the block - n = 0; - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMax <= blk->xMin && - word1->xMax > blk->xMin - colSpace2) - : (word1->yMax <= blk->yMin && - word1->yMax > blk->yMin - colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - ++n; - break; - } - word1 = word1->next; + // drop cap + } else if (fontSize0 > largeCharThreshold * fontSize1 && + indent1 - indent0 > minParagraphIndent * fontSize1 && + getLineSpacing(line0, line1) < 0) { + parLines->append(line1); + fontSize0 = fontSize1; + for (++i; i < lines->getLength(); ++i) { + line1 = (TextLine *)lines->get(i); + indent1 = getLineIndent(line1, blk); + if (indent1 - indent0 <= minParagraphIndent * fontSize0) { + break; + } + if (getLineSpacing((TextLine *)lines->get(i - 1), line1) + > spaceThresh) { + break; } + parLines->append(line1); } - if (n > 0 && n <= 3) { - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word0 = NULL; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMax <= blk->xMin && - word1->xMax > blk->xMin - colSpace2) - : (word1->yMax <= blk->yMin && - word1->yMax > blk->yMin - colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = NULL; - blk->addWord(word2); - if (word2->base < minBase) { - minBase = word2->base; - } else if (word2->base > maxBase) { - maxBase = word2->base; - } - found = gTrue; - break; - } else { - word0 = word1; - word1 = word1->next; - } - } + for (; i < lines->getLength(); ++i) { + line1 = (TextLine *)lines->get(i); + indent1 = getLineIndent(line1, blk); + fontSize1 = line1->fontSize; + if (indent1 - indent0 > minParagraphIndent * fontSize0) { + break; + } + if (fabs(fontSize0 - fontSize1) > paragraphFontSizeDelta) { + break; } + if (getLineSpacing((TextLine *)lines->get(i - 1), line1) + > spaceThresh) { + break; + } + parLines->append(line1); } - // scan down the right side of the block, looking for words - // that are near (but not overlapping) the block; if there are - // three or fewer, add them to the block - n = 0; - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin >= blk->xMax && - word1->xMin < blk->xMax + colSpace2) - : (word1->yMin >= blk->yMax && - word1->yMin < blk->yMax + colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - ++n; - break; - } - word1 = word1->next; + // regular indent or no indent + } else if (fabs(fontSize0 - fontSize1) <= paragraphFontSizeDelta && + getLineSpacing(line0, line1) <= spaceThresh) { + parLines->append(line1); + indent0 = indent1; + for (++i; i < lines->getLength(); ++i) { + line1 = (TextLine *)lines->get(i); + indent1 = getLineIndent(line1, blk); + fontSize1 = line1->fontSize; + if (indent1 - indent0 > minParagraphIndent * fontSize0) { + break; } - } - if (n > 0 && n <= 3) { - for (baseIdx = pool->getBaseIdx(minBase - intraLineSpace); - baseIdx <= pool->getBaseIdx(maxBase + intraLineSpace); - ++baseIdx) { - word0 = NULL; - word1 = pool->getPool(baseIdx); - while (word1) { - if (word1->base >= minBase - intraLineSpace && - word1->base <= maxBase + intraLineSpace && - ((rot == 0 || rot == 2) - ? (word1->xMin >= blk->xMax && - word1->xMin < blk->xMax + colSpace2) - : (word1->yMin >= blk->yMax && - word1->yMin < blk->yMax + colSpace2)) && - fabs(word1->fontSize - fontSize) < - maxBlockFontSizeDelta3 * fontSize) { - word2 = word1; - if (word0) { - word0->next = word1->next; - } else { - pool->setPool(baseIdx, word1->next); - } - word1 = word1->next; - word2->next = NULL; - blk->addWord(word2); - if (word2->base < minBase) { - minBase = word2->base; - } else if (word2->base > maxBase) { - maxBase = word2->base; - } - found = gTrue; - break; - } else { - word0 = word1; - word1 = word1->next; - } - } + if (fabs(fontSize0 - fontSize1) > paragraphFontSizeDelta) { + break; } + if (getLineSpacing((TextLine *)lines->get(i - 1), line1) + > spaceThresh) { + break; + } + parLines->append(line1); } + } + } - } while (found); + paragraphs->append(new TextParagraph(parLines)); + } - //~ need to compute the primary writing mode (horiz/vert) in - //~ addition to primary rotation + delete lines; - // coalesce the block, and add it to the list - blk->coalesce(uMap, fixedPitch); - if (lastBlk) { - lastBlk->next = blk; - } else { - blkList = blk; - } - lastBlk = blk; - count[rot] += blk->charCount; - ++nBlocks; - } + return new TextColumn(paragraphs, blk->xMin, blk->yMin, + blk->xMax, blk->yMax); +} + +double TextPage::getLineIndent(TextLine *line, TextBlock *blk) { + double indent; + + switch (line->rot) { + case 0: + default: indent = line->xMin - blk->xMin; break; + case 1: indent = line->yMin - blk->yMin; break; + case 2: indent = blk->xMax - line->xMax; break; + case 3: indent = blk->yMax - line->yMax; break; + } + return indent; +} + +// Compute average line spacing in column. +double TextPage::getAverageLineSpacing(GList *lines) { + double avg, sp; + int n, i; - if (count[rot] > count[primaryRot]) { - primaryRot = rot; + avg = 0; + n = 0; + for (i = 1; i < lines->getLength(); ++i) { + sp = getLineSpacing((TextLine *)lines->get(i - 1), + (TextLine *)lines->get(i)); + if (sp > 0) { + avg += sp; + ++n; } } + if (n > 0) { + avg /= n; + } + return avg; +} -#if 0 // for debugging - printf("*** rotation ***\n"); - for (rot = 0; rot < 4; ++rot) { - printf(" %d: %6d\n", rot, count[rot]); +// Compute the space between two lines. +double TextPage::getLineSpacing(TextLine *line0, TextLine *line1) { + double sp; + + switch (line0->rot) { + case 0: + default: sp = line1->yMin - line0->yMax; break; + case 1: sp = line0->xMin - line1->xMax; break; + case 2: sp = line0->yMin - line1->yMin; break; + case 3: sp = line1->xMin - line1->xMax; break; } - printf(" primary rot = %d\n", primaryRot); - printf("\n"); -#endif + return sp; +} -#if 0 // for debugging - printf("*** blocks ***\n"); - for (blk = blkList; blk; blk = blk->next) { - printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f\n", - blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax); - for (line = blk->lines; line; line = line->next) { - printf(" line: x=%.2f..%.2f y=%.2f..%.2f base=%.2f\n", - line->xMin, line->xMax, line->yMin, line->yMax, line->base); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); - } - printf("'\n"); - } +void TextPage::buildLines(TextBlock *blk, GList *lines) { + TextLine *line; + int i; + + switch (blk->tag) { + case blkTagLine: + line = buildLine(blk); + if (blk->rot == 1 || blk->rot == 2) { + lines->insert(0, line); + } else { + lines->append(line); + } + break; + case blkTagColumn: + case blkTagMulticolumn: // multicolumn should never happen here + for (i = 0; i < blk->children->getLength(); ++i) { + buildLines((TextBlock *)blk->children->get(i), lines); } + break; } - printf("\n"); -#endif +} - // determine the primary direction - lrCount = 0; - for (blk = blkList; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - for (word0 = line->words; word0; word0 = word0->next) { - for (i = 0; i < word0->len; ++i) { - if (unicodeTypeL(word0->text[i])) { - ++lrCount; - } else if (unicodeTypeR(word0->text[i])) { - --lrCount; - } - } +TextLine *TextPage::buildLine(TextBlock *blk) { + GList *charsA; + GList *words; + TextChar *ch, *ch2; + TextWord *word; + double wordSp, lineFontSize, sp; + GBool spaceAfter, spaceAfter2; + int i, j; + + charsA = new GList(); + getLineChars(blk, charsA); + + wordSp = computeWordSpacingThreshold(charsA, blk->rot); + + words = new GList(); + lineFontSize = 0; + spaceAfter = gFalse; + i = 0; + while (i < charsA->getLength()) { + sp = wordSp - 1; + 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); + if (sp > wordSp || + ch->font != ch2->font || + fabs(ch->fontSize - ch2->fontSize) > 0.01 || + (control.mode == textOutRawOrder && + ch2->charPos != ch->charPos + ch->charLen)) { + break; } + sp = wordSp - 1; + } + spaceAfter2 = spaceAfter; + spaceAfter = sp > wordSp; + word = new TextWord(charsA, i, j - i, blk->rot, + (blk->rot >= 2) ? spaceAfter2 : spaceAfter); + i = j; + if (blk->rot >= 2) { + words->insert(0, word); + } else { + words->append(word); + } + if (i == 0 || word->fontSize > lineFontSize) { + lineFontSize = word->fontSize; } } - primaryLR = lrCount >= 0; -#if 0 // for debugging - printf("*** direction ***\n"); - printf("lrCount = %d\n", lrCount); - printf("primaryLR = %d\n", primaryLR); -#endif + delete charsA; - //----- column assignment + return new TextLine(words, blk->xMin, blk->yMin, blk->xMax, blk->yMax, + lineFontSize); +} - if (physLayout && fixedPitch) { +void TextPage::getLineChars(TextBlock *blk, GList *charsA) { + int i; - blocks = (TextBlock **)gmallocn(nBlocks, sizeof(TextBlock *)); - for (blk = blkList, i = 0; blk; blk = blk->next, ++i) { - blocks[i] = blk; - col1 = 0; // make gcc happy - switch (primaryRot) { - case 0: - col1 = (int)(blk->xMin / fixedPitch + 0.5); - break; - case 1: - col1 = (int)(blk->yMin / fixedPitch + 0.5); - break; - case 2: - col1 = (int)((pageWidth - blk->xMax) / fixedPitch + 0.5); - break; - case 3: - col1 = (int)((pageHeight - blk->yMax) / fixedPitch + 0.5); - break; - } - blk->col = col1; - for (line = blk->lines; line; line = line->next) { - for (j = 0; j <= line->len; ++j) { - line->col[j] += col1; - } - } + if (blk->type == blkLeaf) { + charsA->append(blk->children); + } else { + for (i = 0; i < blk->children->getLength(); ++i) { + getLineChars((TextBlock *)blk->children->get(i), charsA); } + } +} - } else { +// Compute the inter-word spacing threshold for a line of chars. +// Spaces greater than this threshold will be considered inter-word +// spaces. +double TextPage::computeWordSpacingThreshold(GList *charsA, int rot) { + TextChar *ch, *ch2; + double avgFontSize, minSp, maxSp, sp; + int i; - // sort blocks into xy order for column assignment - blocks = (TextBlock **)gmallocn(nBlocks, sizeof(TextBlock *)); - for (blk = blkList, i = 0; blk; blk = blk->next, ++i) { - blocks[i] = blk; - } - qsort(blocks, nBlocks, sizeof(TextBlock *), &TextBlock::cmpXYPrimaryRot); - - // column assignment - for (i = 0; i < nBlocks; ++i) { - blk0 = blocks[i]; - col1 = 0; - for (j = 0; j < i; ++j) { - blk1 = blocks[j]; - col2 = 0; // make gcc happy - switch (primaryRot) { - case 0: - if (blk0->xMin > blk1->xMax) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->xMax == blk1->xMin) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->xMin - blk1->xMin) / - (blk1->xMax - blk1->xMin)) * - blk1->nColumns); - } - break; - case 1: - if (blk0->yMin > blk1->yMax) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->yMax == blk1->yMin) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->yMin - blk1->yMin) / - (blk1->yMax - blk1->yMin)) * - blk1->nColumns); - } - break; - case 2: - if (blk0->xMax < blk1->xMin) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->xMin == blk1->xMax) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->xMax - blk1->xMax) / - (blk1->xMin - blk1->xMax)) * - blk1->nColumns); - } - break; - case 3: - if (blk0->yMax < blk1->yMin) { - col2 = blk1->col + blk1->nColumns + 3; - } else if (blk1->yMin == blk1->yMax) { - col2 = blk1->col; - } else { - col2 = blk1->col + (int)(((blk0->yMax - blk1->yMax) / - (blk1->yMin - blk1->yMax)) * - blk1->nColumns); - } - break; - } - if (col2 > col1) { - col1 = col2; - } - } - blk0->col = col1; - for (line = blk0->lines; line; line = line->next) { - for (j = 0; j <= line->len; ++j) { - line->col[j] += col1; - } + avgFontSize = 0; + minSp = maxSp = 0; + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + avgFontSize += ch->fontSize; + if (i < charsA->getLength() - 1) { + ch2 = (TextChar *)charsA->get(i+1); + sp = (rot & 1) ? (ch2->yMin - ch->yMax) : (ch2->xMin - ch->xMax); + if (i == 0 || sp < minSp) { + minSp = sp; + } + if (sp > maxSp) { + maxSp = sp; } } + } + avgFontSize /= charsA->getLength(); + if (minSp < 0) { + minSp = 0; + } + + // if spacing is completely uniform, assume it's a single word + // (technically it could be either "ABC" or "A B C", but it's + // essentially impossible to tell) + if (maxSp - minSp < uniformSpacing * avgFontSize) { + return maxSp + 1; + + // if there is some variation in spacing, but it's small, assume + // there are some inter-word spaces + } else if (maxSp - minSp < wordSpacing * avgFontSize) { + return 0.5 * (minSp + maxSp); + + // otherwise, assume a reasonable threshold for inter-word spacing + // (we can't use something like 0.5*(minSp+maxSp) here because there + // can be outliers at the high end) + } else { + return minSp + wordSpacing * avgFontSize; + } +} + +int TextPage::assignPhysLayoutPositions(GList *columns) { + assignLinePhysPositions(columns); + return assignColumnPhysPositions(columns); +} +// Assign a physical x coordinate for each TextLine (relative to the +// containing TextColumn). This also computes TextColumn width and +// height. +void TextPage::assignLinePhysPositions(GList *columns) { + TextColumn *col; + TextParagraph *par; + TextLine *line; + UnicodeMap *uMap; + int colIdx, parIdx, lineIdx; + + if (!(uMap = globalParams->getTextEncoding())) { + return; } -#if 0 // for debugging - printf("*** blocks, after column assignment ***\n"); - for (blk = blkList; blk; blk = blk->next) { - printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f col=%d nCols=%d\n", - blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax, blk->col, - blk->nColumns); - for (line = blk->lines; line; line = line->next) { - printf(" line: col[0]=%d\n", line->col[0]); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + col->pw = col->ph = 0; + 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); + computeLinePhysWidth(line, uMap); + if (control.fixedPitch > 0) { + line->px = (int)((line->xMin - col->xMin) / control.fixedPitch); + } else if (fabs(line->fontSize) < 0.001) { + line->px = 0; + } else { + line->px = (int)((line->xMin - col->xMin) / + (physLayoutSpaceWidth * line->fontSize)); + } + if (line->px + line->pw > col->pw) { + col->pw = line->px + line->pw; } - printf("'\n"); } + col->ph += par->lines->getLength(); } + col->ph += col->paragraphs->getLength() - 1; } - printf("\n"); -#endif - //----- reading order sort + uMap->decRefCnt(); +} - // sort blocks into yx order (in preparation for reading order sort) - qsort(blocks, nBlocks, sizeof(TextBlock *), &TextBlock::cmpYXPrimaryRot); +void TextPage::computeLinePhysWidth(TextLine *line, UnicodeMap *uMap) { + char buf[8]; + int n, i; - // compute space on left and right sides of each block - for (i = 0; i < nBlocks; ++i) { - blk0 = blocks[i]; - for (j = 0; j < nBlocks; ++j) { - blk1 = blocks[j]; - if (blk1 != blk0) { - blk0->updatePriMinMax(blk1); - } + if (uMap->isUnicode()) { + line->pw = line->len; + } else { + line->pw = 0; + for (i = 0; i < line->len; ++i) { + n = uMap->mapUnicode(line->text[i], buf, sizeof(buf)); + line->pw += n; } } +} + +// Assign physical x and y coordinates for each TextColumn. Returns +// the text height (max physical y + 1). +int TextPage::assignColumnPhysPositions(GList *columns) { + TextColumn *col, *col2; + double slack, xOverlap, yOverlap; + int ph, i, j; + + if (control.mode == textOutTableLayout) { + slack = tableCellOverlapSlack; + } else { + slack = 0; + } -#if 0 // for debugging - printf("*** blocks, after yx sort ***\n"); - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - printf("block: rot=%d x=%.2f..%.2f y=%.2f..%.2f space=%.2f..%.2f\n", - blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax, - blk->priMin, blk->priMax); - for (line = blk->lines; line; line = line->next) { - printf(" line:\n"); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (j = 0; j < word0->len; ++j) { - fputc(word0->text[j] & 0xff, stdout); + // assign x positions + columns->sort(&TextColumn::cmpX); + for (i = 0; i < columns->getLength(); ++i) { + col = (TextColumn *)columns->get(i); + if (control.fixedPitch) { + col->px = (int)(col->xMin / control.fixedPitch); + } else { + col->px = 0; + for (j = 0; j < i; ++j) { + col2 = (TextColumn *)columns->get(j); + xOverlap = col2->xMax - col->xMin; + if (xOverlap < slack * (col2->xMax - col2->xMin)) { + if (col2->px + col2->pw + 2 > col->px) { + col->px = col2->px + col2->pw + 2; + } + } else { + yOverlap = (col->yMax < col2->yMax ? col->yMax : col2->yMax) - + (col->yMin > col2->yMin ? col->yMin : col2->yMin); + if (yOverlap > 0 && xOverlap < yOverlap) { + if (col2->px + col2->pw > col->px) { + col->px = col2->px + col2->pw; + } + } else { + if (col2->px > col->px) { + col->px = col2->px; + } + } } - printf("'\n"); } } } - printf("\n"); -#endif - // build the flows - //~ this needs to be adjusted for writing mode (vertical text) - //~ this also needs to account for right-to-left column ordering - blkArray = (TextBlock **)gmallocn(nBlocks, sizeof(TextBlock *)); - memcpy(blkArray, blocks, nBlocks * sizeof(TextBlock *)); - flows = lastFlow = NULL; - firstBlkIdx = 0; - nBlocksLeft = nBlocks; - while (nBlocksLeft > 0) { - - // find the upper-left-most block - for (; !blkArray[firstBlkIdx]; ++firstBlkIdx) ; - i = firstBlkIdx; - blk = blkArray[i]; - for (j = firstBlkIdx + 1; j < nBlocks; ++j) { - blk1 = blkArray[j]; - if (blk1) { - if (blk && blk->secondaryDelta(blk1) > 0) { - break; + // assign y positions + ph = 0; + columns->sort(&TextColumn::cmpY); + for (i = 0; i < columns->getLength(); ++i) { + col = (TextColumn *)columns->get(i); + col->py = 0; + for (j = 0; j < i; ++j) { + col2 = (TextColumn *)columns->get(j); + yOverlap = col2->yMax - col->yMin; + if (yOverlap < slack * (col2->yMax - col2->yMin)) { + if (col2->py + col2->ph + 1 > col->py) { + col->py = col2->py + col2->ph + 1; } - if (blk1->primaryCmp(blk) < 0) { - i = j; - blk = blk1; + } else { + xOverlap = (col->xMax < col2->xMax ? col->xMax : col2->xMax) - + (col->xMin > col2->xMin ? col->xMin : col2->xMin); + if (xOverlap > 0 && yOverlap < xOverlap) { + if (col2->py + col2->ph > col->py) { + col->py = col2->py + col2->ph; + } + } else { + if (col2->py > col->py) { + col->py = col2->py; + } } } } - blkArray[i] = NULL; - --nBlocksLeft; - blk->next = NULL; - - // create a new flow, starting with the upper-left-most block - flow = new TextFlow(this, blk); - if (lastFlow) { - lastFlow->next = flow; - } else { - flows = flow; + if (col->py + col->ph > ph) { + ph = col->py + col->ph; } - lastFlow = flow; - fontSize = blk->lines->words->fontSize; + } - // push the upper-left-most block on the stack - blk->stackNext = NULL; - blkStack = blk; - - // find the other blocks in this flow - while (blkStack) { - - // find the upper-left-most block under (but within - // maxBlockSpacing of) the top block on the stack - blkSpace = maxBlockSpacing * blkStack->lines->words->fontSize; - blk = NULL; - i = -1; - for (j = firstBlkIdx; j < nBlocks; ++j) { - blk1 = blkArray[j]; - if (blk1) { - if (blkStack->secondaryDelta(blk1) > blkSpace) { - break; - } - if (blk && blk->secondaryDelta(blk1) > 0) { - break; - } - if (blk1->isBelow(blkStack) && - (!blk || blk1->primaryCmp(blk) < 0)) { - i = j; - blk = blk1; - } - } - } + return ph; +} - // if a suitable block was found, add it to the flow and push it - // onto the stack - if (blk && flow->blockFits(blk, blkStack)) { - blkArray[i] = NULL; - --nBlocksLeft; - blk->next = NULL; - flow->addBlock(blk); - fontSize = blk->lines->words->fontSize; - blk->stackNext = blkStack; - blkStack = blk; +void TextPage::generateUnderlinesAndLinks(GList *columns) { + TextColumn *col; + TextParagraph *par; + TextLine *line; + TextWord *word; + TextUnderline *underline; + TextLink *link; + double base, uSlack, ubSlack, hSlack; + int colIdx, parIdx, lineIdx, wordIdx, i; - // otherwise (if there is no block under the top block or the - // block is not suitable), pop the stack - } else { - blkStack = blkStack->stackNext; - } - } - } - gfree(blkArray); + 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); + for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) { + word = (TextWord *)line->words->get(wordIdx); + base = word->getBaseline(); + uSlack = underlineSlack * word->fontSize; + ubSlack = underlineBaselineSlack * word->fontSize; + hSlack = hyperlinkSlack * word->fontSize; + + //----- handle underlining + for (i = 0; i < underlines->getLength(); ++i) { + underline = (TextUnderline *)underlines->get(i); + if (underline->horiz) { + if (word->rot == 0 || word->rot == 2) { + if (fabs(underline->y0 - base) < ubSlack && + underline->x0 < word->xMin + uSlack && + word->xMax - uSlack < underline->x1) { + word->underlined = gTrue; + } + } + } else { + if (word->rot == 1 || word->rot == 3) { + if (fabs(underline->x0 - base) < ubSlack && + underline->y0 < word->yMin + uSlack && + word->yMax - uSlack < underline->y1) { + word->underlined = gTrue; + } + } + } + } -#if 0 // for debugging - printf("*** flows ***\n"); - for (flow = flows; flow; flow = flow->next) { - printf("flow: x=%.2f..%.2f y=%.2f..%.2f pri:%.2f..%.2f\n", - flow->xMin, flow->xMax, flow->yMin, flow->yMax, - flow->priMin, flow->priMax); - for (blk = flow->blocks; blk; blk = blk->next) { - printf(" block: rot=%d x=%.2f..%.2f y=%.2f..%.2f pri=%.2f..%.2f\n", - blk->rot, blk->xMin, blk->xMax, blk->yMin, blk->yMax, - blk->priMin, blk->priMax); - for (line = blk->lines; line; line = line->next) { - printf(" line:\n"); - for (word0 = line->words; word0; word0 = word0->next) { - printf(" word: x=%.2f..%.2f y=%.2f..%.2f base=%.2f fontSize=%.2f space=%d: '", - word0->xMin, word0->xMax, word0->yMin, word0->yMax, - word0->base, word0->fontSize, word0->spaceAfter); - for (i = 0; i < word0->len; ++i) { - fputc(word0->text[i] & 0xff, stdout); + //----- handle links + for (i = 0; i < links->getLength(); ++i) { + link = (TextLink *)links->get(i); + if (link->xMin < word->xMin + hSlack && + word->xMax - hSlack < link->xMax && + link->yMin < word->yMin + hSlack && + word->yMax - hSlack < link->yMax) { + word->link = link; + } } - printf("'\n"); } } } } - printf("\n"); -#endif - - if (uMap) { - uMap->decRefCnt(); - } } +//------------------------------------------------------------------------ +// TextPage: access +//------------------------------------------------------------------------ + GBool TextPage::findText(Unicode *s, int len, GBool startAtTop, GBool stopAtBottom, GBool startAtLast, GBool stopAtLast, @@ -3131,20 +3639,31 @@ GBool TextPage::findText(Unicode *s, int GBool wholeWord, double *xMin, double *yMin, double *xMax, double *yMax) { - TextBlock *blk; + TextBlock *tree; + TextColumn *column; + TextParagraph *par; TextLine *line; Unicode *s2, *txt; Unicode *p; - int txtSize, m, i, j, k; double xStart, yStart, xStop, yStop; double xMin0, yMin0, xMax0, yMax0; double xMin1, yMin1, xMax1, yMax1; GBool found; + int txtSize, m, rot, colIdx, parIdx, lineIdx, i, j, k; - //~ needs to handle right-to-left text + //~ need to handle right-to-left text - if (rawOrder) { - return gFalse; + if (!findCols) { + rot = rotateChars(chars); + if ((tree = splitChars(chars))) { + findCols = buildColumns(tree); + delete tree; + } else { + // no text + findCols = new GList(); + } + unrotateChars(chars, rot); + unrotateColumns(findCols, rot); } // convert the search string to uppercase @@ -3176,499 +3695,202 @@ GBool TextPage::findText(Unicode *s, int yStop = *yMax; } - found = gFalse; - xMin0 = xMax0 = yMin0 = yMax0 = 0; // make gcc happy - xMin1 = xMax1 = yMin1 = yMax1 = 0; // make gcc happy - - for (i = backward ? nBlocks - 1 : 0; - backward ? i >= 0 : i < nBlocks; - i += backward ? -1 : 1) { - blk = blocks[i]; - - // check: is the block above the top limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the blocks won't be sorted in the useful order) - if (!startAtTop && primaryRot == 0 && - (backward ? blk->yMin > yStart : blk->yMax < yStart)) { - continue; - } - - // check: is the block below the bottom limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the blocks won't be sorted in the useful order) - if (!stopAtBottom && primaryRot == 0 && - (backward ? blk->yMax < yStop : blk->yMin > yStop)) { - break; - } - - for (line = blk->lines; line; line = line->next) { - - // check: is the line above the top limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the lines won't be sorted in the useful order) - if (!startAtTop && primaryRot == 0 && - (backward ? line->yMin > yStart : line->yMin < yStart)) { - continue; - } - - // check: is the line below the bottom limit? - // (this only works if the page's primary rotation is zero -- - // otherwise the lines won't be sorted in the useful order) - if (!stopAtBottom && primaryRot == 0 && - (backward ? line->yMin < yStop : line->yMin > yStop)) { - continue; - } - - // convert the line to uppercase - m = line->len; - if (!caseSensitive) { - if (m > txtSize) { - txt = (Unicode *)greallocn(txt, m, sizeof(Unicode)); - txtSize = m; - } - for (k = 0; k < m; ++k) { - txt[k] = unicodeToUpper(line->text[k]); - } - } else { - txt = line->text; - } - - // search each position in this line - j = backward ? m - len : 0; - p = txt + j; - while (backward ? j >= 0 : j <= m - len) { - if (!wholeWord || - ((j == 0 || !unicodeTypeAlphaNum(txt[j - 1])) && - (j + len == m || !unicodeTypeAlphaNum(txt[j + len])))) { - - // compare the strings - for (k = 0; k < len; ++k) { - if (p[k] != s2[k]) { - break; - } - } - - // found it - if (k == len) { - switch (line->rot) { - case 0: - xMin1 = line->edge[j]; - xMax1 = line->edge[j + len]; - yMin1 = line->yMin; - yMax1 = line->yMax; - break; - case 1: - xMin1 = line->xMin; - xMax1 = line->xMax; - yMin1 = line->edge[j]; - yMax1 = line->edge[j + len]; - break; - case 2: - xMin1 = line->edge[j + len]; - xMax1 = line->edge[j]; - yMin1 = line->yMin; - yMax1 = line->yMax; - break; - case 3: - xMin1 = line->xMin; - xMax1 = line->xMax; - yMin1 = line->edge[j + len]; - yMax1 = line->edge[j]; - break; - } - if (backward) { - if ((startAtTop || - yMin1 < yStart || (yMin1 == yStart && xMin1 < xStart)) && - (stopAtBottom || - yMin1 > yStop || (yMin1 == yStop && xMin1 > xStop))) { - if (!found || - yMin1 > yMin0 || (yMin1 == yMin0 && xMin1 > xMin0)) { - xMin0 = xMin1; - xMax0 = xMax1; - yMin0 = yMin1; - yMax0 = yMax1; - found = gTrue; - } - } - } else { - if ((startAtTop || - yMin1 > yStart || (yMin1 == yStart && xMin1 > xStart)) && - (stopAtBottom || - yMin1 < yStop || (yMin1 == yStop && xMin1 < xStop))) { - if (!found || - yMin1 < yMin0 || (yMin1 == yMin0 && xMin1 < xMin0)) { - xMin0 = xMin1; - xMax0 = xMax1; - yMin0 = yMin1; - yMax0 = yMax1; - found = gTrue; - } - } - } - } - } - if (backward) { - --j; - --p; - } else { - ++j; - ++p; - } - } - } - } - - if (!caseSensitive) { - gfree(s2); - gfree(txt); - } - - if (found) { - *xMin = xMin0; - *xMax = xMax0; - *yMin = yMin0; - *yMax = yMax0; - lastFindXMin = xMin0; - lastFindYMin = yMin0; - haveLastFind = gTrue; - return gTrue; - } - - return gFalse; -} - -GString *TextPage::getText(double xMin, double yMin, - double xMax, double yMax) { - GString *s; - UnicodeMap *uMap; - GBool isUnicode; - TextBlock *blk; - TextLine *line; - TextLineFrag *frags; - int nFrags, fragsSize; - TextLineFrag *frag; - char space[8], eol[16]; - int spaceLen, eolLen; - int lastRot; - double x, y, delta; - int col, idx0, idx1, i, j; - GBool multiLine, oneRot; - - s = new GString(); - - if (rawOrder) { - return s; - } - - // get the output encoding - if (!(uMap = globalParams->getTextEncoding())) { - return s; - } - isUnicode = uMap->isUnicode(); - spaceLen = uMap->mapUnicode(0x20, space, sizeof(space)); - eolLen = 0; // make gcc happy - switch (globalParams->getTextEOL()) { - case eolUnix: - eolLen = uMap->mapUnicode(0x0a, eol, sizeof(eol)); - break; - case eolDOS: - eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); - eolLen += uMap->mapUnicode(0x0a, eol + eolLen, sizeof(eol) - eolLen); - break; - case eolMac: - eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); - break; - } - - //~ writing mode (horiz/vert) - - // collect the line fragments that are in the rectangle - fragsSize = 256; - frags = (TextLineFrag *)gmallocn(fragsSize, sizeof(TextLineFrag)); - nFrags = 0; - lastRot = -1; - oneRot = gTrue; - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - if (xMin < blk->xMax && blk->xMin < xMax && - yMin < blk->yMax && blk->yMin < yMax) { - for (line = blk->lines; line; line = line->next) { - if (xMin < line->xMax && line->xMin < xMax && - yMin < line->yMax && line->yMin < yMax) { - idx0 = idx1 = -1; - switch (line->rot) { - case 0: - y = 0.5 * (line->yMin + line->yMax); - if (yMin < y && y < yMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > xMin) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < xMax) { - idx1 = j; - break; - } - --j; - } - } - break; - case 1: - x = 0.5 * (line->xMin + line->xMax); - if (xMin < x && x < xMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > yMin) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < yMax) { - idx1 = j; - break; - } - --j; - } - } - break; - case 2: - y = 0.5 * (line->yMin + line->yMax); - if (yMin < y && y < yMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < xMax) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > xMin) { - idx1 = j; - break; - } - --j; - } - } - break; - case 3: - x = 0.5 * (line->xMin + line->xMax); - if (xMin < x && x < xMax) { - j = 0; - while (j < line->len) { - if (0.5 * (line->edge[j] + line->edge[j+1]) < yMax) { - idx0 = j; - break; - } - ++j; - } - j = line->len - 1; - while (j >= 0) { - if (0.5 * (line->edge[j] + line->edge[j+1]) > yMin) { - idx1 = j; - break; - } - --j; - } - } - break; - } - if (idx0 >= 0 && idx1 >= 0) { - if (nFrags == fragsSize) { - fragsSize *= 2; - frags = (TextLineFrag *) - greallocn(frags, fragsSize, sizeof(TextLineFrag)); - } - frags[nFrags].init(line, idx0, idx1 - idx0 + 1); - ++nFrags; - if (lastRot >= 0 && line->rot != lastRot) { - oneRot = gFalse; - } - lastRot = line->rot; - } - } - } - } - } - - // sort the fragments and generate the string - if (nFrags > 0) { - - for (i = 0; i < nFrags; ++i) { - frags[i].computeCoords(oneRot); - } - assignColumns(frags, nFrags, oneRot); - - // if all lines in the region have the same rotation, use it; - // otherwise, use the page's primary rotation - if (oneRot) { - qsort(frags, nFrags, sizeof(TextLineFrag), - &TextLineFrag::cmpYXLineRot); - } else { - qsort(frags, nFrags, sizeof(TextLineFrag), - &TextLineFrag::cmpYXPrimaryRot); - } - i = 0; - while (i < nFrags) { - delta = maxIntraLineDelta * frags[i].line->words->fontSize; - for (j = i+1; - j < nFrags && fabs(frags[j].base - frags[i].base) < delta; - ++j) ; - qsort(frags + i, j - i, sizeof(TextLineFrag), - oneRot ? &TextLineFrag::cmpXYColumnLineRot - : &TextLineFrag::cmpXYColumnPrimaryRot); - i = j; - } - - col = 0; - multiLine = gFalse; - for (i = 0; i < nFrags; ++i) { - frag = &frags[i]; - - // insert a return - if (frag->col < col || - (i > 0 && fabs(frag->base - frags[i-1].base) > - maxIntraLineDelta * frags[i-1].line->words->fontSize)) { - s->append(eol, eolLen); - col = 0; - multiLine = gTrue; - } - - // column alignment - for (; col < frag->col; ++col) { - s->append(space, spaceLen); - } + found = gFalse; + xMin0 = xMax0 = yMin0 = yMax0 = 0; // make gcc happy + xMin1 = xMax1 = yMin1 = yMax1 = 0; // make gcc happy - // get the fragment text - col += dumpFragment(frag->line->text + frag->start, frag->len, uMap, s); + for (colIdx = backward ? findCols->getLength() - 1 : 0; + backward ? colIdx >= 0 : colIdx < findCols->getLength(); + colIdx += backward ? -1 : 1) { + column = (TextColumn *)findCols->get(colIdx); + + // check: is the column above the top limit? + if (!startAtTop && (backward ? column->yMin > yStart + : column->yMax < yStart)) { + continue; } - if (multiLine) { - s->append(eol, eolLen); + // check: is the column below the bottom limit? + if (!stopAtBottom && (backward ? column->yMax < yStop + : column->yMin > yStop)) { + continue; } - } - gfree(frags); - uMap->decRefCnt(); + for (parIdx = backward ? column->paragraphs->getLength() - 1 : 0; + backward ? parIdx >= 0 : parIdx < column->paragraphs->getLength(); + parIdx += backward ? -1 : 1) { + par = (TextParagraph *)column->paragraphs->get(parIdx); + + // check: is the paragraph above the top limit? + if (!startAtTop && (backward ? par->yMin > yStart + : par->yMax < yStart)) { + continue; + } - return s; -} + // check: is the paragraph below the bottom limit? + if (!stopAtBottom && (backward ? par->yMax < yStop + : par->yMin > yStop)) { + continue; + } -GBool TextPage::findCharRange(int pos, int length, - double *xMin, double *yMin, - double *xMax, double *yMax) { - TextBlock *blk; - TextLine *line; - TextWord *word; - double xMin0, xMax0, yMin0, yMax0; - double xMin1, xMax1, yMin1, yMax1; - GBool first; - int i, j0, j1; + for (lineIdx = backward ? par->lines->getLength() - 1 : 0; + backward ? lineIdx >= 0 : lineIdx < par->lines->getLength(); + lineIdx += backward ? -1 : 1) { + line = (TextLine *)par->lines->get(lineIdx); + + // check: is the line above the top limit? + if (!startAtTop && (backward ? line->yMin > yStart + : line->yMax < yStart)) { + continue; + } - if (rawOrder) { - return gFalse; - } + // check: is the line below the bottom limit? + if (!stopAtBottom && (backward ? line->yMax < yStop + : line->yMin > yStop)) { + continue; + } - //~ this doesn't correctly handle ranges split across multiple lines - //~ (the highlighted region is the bounding box of all the parts of - //~ the range) - first = gTrue; - xMin0 = xMax0 = yMin0 = yMax0 = 0; // make gcc happy - xMin1 = xMax1 = yMin1 = yMax1 = 0; // make gcc happy - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - for (line = blk->lines; line; line = line->next) { - for (word = line->words; word; word = word->next) { - if (pos < word->charPos[word->len] && - pos + length > word->charPos[0]) { - for (j0 = 0; - j0 < word->len && pos >= word->charPos[j0 + 1]; - ++j0) ; - for (j1 = word->len - 1; - j1 > j0 && pos + length <= word->charPos[j1]; - --j1) ; - switch (line->rot) { - case 0: - xMin1 = word->edge[j0]; - xMax1 = word->edge[j1 + 1]; - yMin1 = word->yMin; - yMax1 = word->yMax; - break; - case 1: - xMin1 = word->xMin; - xMax1 = word->xMax; - yMin1 = word->edge[j0]; - yMax1 = word->edge[j1 + 1]; - break; - case 2: - xMin1 = word->edge[j1 + 1]; - xMax1 = word->edge[j0]; - yMin1 = word->yMin; - yMax1 = word->yMax; - break; - case 3: - xMin1 = word->xMin; - xMax1 = word->xMax; - yMin1 = word->edge[j1 + 1]; - yMax1 = word->edge[j0]; - break; - } - if (first || xMin1 < xMin0) { - xMin0 = xMin1; + // convert the line to uppercase + m = line->len; + if (!caseSensitive) { + if (m > txtSize) { + txt = (Unicode *)greallocn(txt, m, sizeof(Unicode)); + txtSize = m; } - if (first || xMax1 > xMax0) { - xMax0 = xMax1; + for (k = 0; k < m; ++k) { + txt[k] = unicodeToUpper(line->text[k]); } - if (first || yMin1 < yMin0) { - yMin0 = yMin1; + } else { + txt = line->text; + } + + // search each position in this line + j = backward ? m - len : 0; + p = txt + j; + while (backward ? j >= 0 : j <= m - len) { + if (!wholeWord || + ((j == 0 || !unicodeTypeWord(txt[j - 1])) && + (j + len == m || !unicodeTypeWord(txt[j + len])))) { + + // compare the strings + for (k = 0; k < len; ++k) { + if (p[k] != s2[k]) { + break; + } + } + + // found it + if (k == len) { + switch (line->rot) { + case 0: + xMin1 = line->edge[j]; + xMax1 = line->edge[j + len]; + yMin1 = line->yMin; + yMax1 = line->yMax; + break; + case 1: + xMin1 = line->xMin; + xMax1 = line->xMax; + yMin1 = line->edge[j]; + yMax1 = line->edge[j + len]; + break; + case 2: + xMin1 = line->edge[j + len]; + xMax1 = line->edge[j]; + yMin1 = line->yMin; + yMax1 = line->yMax; + break; + case 3: + xMin1 = line->xMin; + xMax1 = line->xMax; + yMin1 = line->edge[j + len]; + yMax1 = line->edge[j]; + break; + } + if (backward) { + if ((startAtTop || + yMin1 < yStart || (yMin1 == yStart && xMin1 < xStart)) && + (stopAtBottom || + yMin1 > yStop || (yMin1 == yStop && xMin1 > xStop))) { + if (!found || + yMin1 > yMin0 || (yMin1 == yMin0 && xMin1 > xMin0)) { + xMin0 = xMin1; + xMax0 = xMax1; + yMin0 = yMin1; + yMax0 = yMax1; + found = gTrue; + } + } + } else { + if ((startAtTop || + yMin1 > yStart || (yMin1 == yStart && xMin1 > xStart)) && + (stopAtBottom || + yMin1 < yStop || (yMin1 == yStop && xMin1 < xStop))) { + if (!found || + yMin1 < yMin0 || (yMin1 == yMin0 && xMin1 < xMin0)) { + xMin0 = xMin1; + xMax0 = xMax1; + yMin0 = yMin1; + yMax0 = yMax1; + found = gTrue; + } + } + } + } } - if (first || yMax1 > yMax0) { - yMax0 = yMax1; + if (backward) { + --j; + --p; + } else { + ++j; + ++p; } - first = gFalse; } } } } - if (!first) { + + if (!caseSensitive) { + gfree(s2); + gfree(txt); + } + + if (found) { *xMin = xMin0; *xMax = xMax0; *yMin = yMin0; *yMax = yMax0; + lastFindXMin = xMin0; + lastFindYMin = yMin0; + haveLastFind = gTrue; return gTrue; } + return gFalse; } -void TextPage::dump(void *outputStream, TextOutputFunc outputFunc, - GBool physLayout) { +GString *TextPage::getText(double xMin, double yMin, + double xMax, double yMax) { UnicodeMap *uMap; - TextFlow *flow; - TextBlock *blk; + char space[8], eol[16]; + int spaceLen, eolLen; + GList *chars2; + GString **out; + int *outLen; + TextColumn *col; + TextParagraph *par; TextLine *line; - TextLineFrag *frags; - TextWord *word; - int nFrags, fragsSize; - TextLineFrag *frag; - char space[8], eol[16], eop[8]; - int spaceLen, eolLen, eopLen; - GBool pageBreaks; - GString *s; - double delta; - int col, i, j, d, n; + TextChar *ch; + GBool primaryLR; + TextBlock *tree; + GList *columns; + GString *ret; + double xx, yy; + int rot, colIdx, parIdx, lineIdx, ph, y, i; // get the output encoding if (!(uMap = globalParams->getTextEncoding())) { - return; + return NULL; } spaceLen = uMap->mapUnicode(0x20, space, sizeof(space)); eolLen = 0; // make gcc happy @@ -3684,351 +3906,277 @@ void TextPage::dump(void *outputStream, eolLen = uMap->mapUnicode(0x0d, eol, sizeof(eol)); break; } - eopLen = uMap->mapUnicode(0x0c, eop, sizeof(eop)); - pageBreaks = globalParams->getTextPageBreaks(); - - //~ writing mode (horiz/vert) - // output the page in raw (content stream) order - if (rawOrder) { - - for (word = rawWords; word; word = word->next) { - s = new GString(); - dumpFragment(word->text, word->len, uMap, s); - (*outputFunc)(outputStream, s->getCString(), s->getLength()); - delete s; - if (word->next && - fabs(word->next->base - word->base) < - maxIntraLineDelta * word->fontSize && - word->next->xMin > - word->xMax - minDupBreakOverlap * word->fontSize) { - if (word->next->xMin > word->xMax + minWordSpacing * word->fontSize) { - (*outputFunc)(outputStream, space, spaceLen); - } - } else { - (*outputFunc)(outputStream, eol, eolLen); - } + // get all chars in the rectangle + // (i.e., all chars whose center lies inside the rectangle) + chars2 = new GList(); + for (i = 0; i < chars->getLength(); ++i) { + ch = (TextChar *)chars->get(i); + xx = 0.5 * (ch->xMin + ch->xMax); + yy = 0.5 * (ch->yMin + ch->yMax); + if (xx > xMin && xx < xMax && yy > yMin && yy < yMax) { + chars2->append(ch); } + } +#if 0 //~debug + dumpChars(chars2); +#endif - // output the page, maintaining the original physical layout - } else if (physLayout) { - - // collect the line fragments for the page and sort them - fragsSize = 256; - frags = (TextLineFrag *)gmallocn(fragsSize, sizeof(TextLineFrag)); - nFrags = 0; - for (i = 0; i < nBlocks; ++i) { - blk = blocks[i]; - for (line = blk->lines; line; line = line->next) { - if (nFrags == fragsSize) { - fragsSize *= 2; - frags = (TextLineFrag *)greallocn(frags, - fragsSize, sizeof(TextLineFrag)); - } - frags[nFrags].init(line, 0, line->len); - frags[nFrags].computeCoords(gTrue); - ++nFrags; + rot = rotateChars(chars2); + primaryLR = checkPrimaryLR(chars2); + tree = splitChars(chars2); + if (!tree) { + unrotateChars(chars2, rot); + delete chars2; + return new GString(); + } +#if 0 //~debug + dumpTree(tree); +#endif + columns = buildColumns(tree); + delete tree; + ph = assignPhysLayoutPositions(columns); +#if 0 //~debug + dumpColumns(columns); +#endif + unrotateChars(chars2, rot); + delete chars2; + + out = (GString **)gmallocn(ph, sizeof(GString *)); + outLen = (int *)gmallocn(ph, sizeof(int)); + for (i = 0; i < ph; ++i) { + out[i] = NULL; + outLen[i] = 0; + } + + columns->sort(&TextColumn::cmpPX); + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + y = col->py; + for (parIdx = 0; + parIdx < col->paragraphs->getLength() && y < ph; + ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + for (lineIdx = 0; + lineIdx < par->lines->getLength() && y < ph; + ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + if (!out[y]) { + out[y] = new GString(); + } + while (outLen[y] < col->px + line->px) { + out[y]->append(space, spaceLen); + ++outLen[y]; + } + encodeFragment(line->text, line->len, uMap, primaryLR, out[y]); + outLen[y] += line->pw; + ++y; + } + if (parIdx + 1 < col->paragraphs->getLength()) { + ++y; } } - qsort(frags, nFrags, sizeof(TextLineFrag), &TextLineFrag::cmpYXPrimaryRot); - i = 0; - while (i < nFrags) { - delta = maxIntraLineDelta * frags[i].line->words->fontSize; - for (j = i+1; - j < nFrags && fabs(frags[j].base - frags[i].base) < delta; - ++j) ; - qsort(frags + i, j - i, sizeof(TextLineFrag), - &TextLineFrag::cmpXYColumnPrimaryRot); - i = j; - } - -#if 0 // for debugging - printf("*** line fragments ***\n"); - for (i = 0; i < nFrags; ++i) { - frag = &frags[i]; - printf("frag: x=%.2f..%.2f y=%.2f..%.2f base=%.2f '", - frag->xMin, frag->xMax, frag->yMin, frag->yMax, frag->base); - for (n = 0; n < frag->len; ++n) { - fputc(frag->line->text[frag->start + n] & 0xff, stdout); - } - printf("'\n"); + } + + ret = new GString(); + for (i = 0; i < ph; ++i) { + if (out[i]) { + ret->append(out[i]); + delete out[i]; + } + if (ph > 1) { + ret->append(eol, eolLen); } - printf("\n"); -#endif + } - // generate output - col = 0; - for (i = 0; i < nFrags; ++i) { - frag = &frags[i]; - - // column alignment - for (; col < frag->col; ++col) { - (*outputFunc)(outputStream, space, spaceLen); - } + gfree(out); + gfree(outLen); + deleteGList(columns, TextColumn); + uMap->decRefCnt(); - // print the line - s = new GString(); - col += dumpFragment(frag->line->text + frag->start, frag->len, uMap, s); - (*outputFunc)(outputStream, s->getCString(), s->getLength()); - delete s; + return ret; +} - // print one or more returns if necessary - if (i == nFrags - 1 || - frags[i+1].col < col || - fabs(frags[i+1].base - frag->base) > - maxIntraLineDelta * frag->line->words->fontSize) { - if (i < nFrags - 1) { - d = (int)((frags[i+1].base - frag->base) / - frag->line->words->fontSize); - if (d < 1) { - d = 1; - } else if (d > 5) { - d = 5; - } - } else { - d = 1; - } - for (; d > 0; --d) { - (*outputFunc)(outputStream, eol, eolLen); - } - col = 0; - } - } +GBool TextPage::findCharRange(int pos, int length, + double *xMin, double *yMin, + double *xMax, double *yMax) { + TextChar *ch; + double xMin2, yMin2, xMax2, yMax2; + GBool first; + int i; - gfree(frags); + //~ this doesn't correctly handle ranges split across multiple lines + //~ (the highlighted region is the bounding box of all the parts of + //~ the range) - // output the page, "undoing" the layout - } else { - for (flow = flows; flow; flow = flow->next) { - for (blk = flow->blocks; blk; blk = blk->next) { - for (line = blk->lines; line; line = line->next) { - n = line->len; - if (line->hyphenated && (line->next || blk->next)) { - --n; - } - s = new GString(); - dumpFragment(line->text, n, uMap, s); - (*outputFunc)(outputStream, s->getCString(), s->getLength()); - delete s; - if (!line->hyphenated) { - if (line->next) { - (*outputFunc)(outputStream, space, spaceLen); - } else if (blk->next) { - //~ this is a bit of a kludge - we should really do a more - //~ intelligent determination of paragraphs - if (blk->next->lines->words->fontSize == - blk->lines->words->fontSize) { - (*outputFunc)(outputStream, space, spaceLen); - } else { - (*outputFunc)(outputStream, eol, eolLen); - } - } - } - } + xMin2 = yMin2 = xMax2 = yMax2 = 0; + first = gTrue; + for (i = 0; i < chars->getLength(); ++i) { + ch = (TextChar *)chars->get(i); + if (ch->charPos >= pos && ch->charPos < pos + length) { + if (first || ch->xMin < xMin2) { + xMin2 = ch->xMin; } - (*outputFunc)(outputStream, eol, eolLen); - (*outputFunc)(outputStream, eol, eolLen); + if (first || ch->yMin < yMin2) { + yMin2 = ch->yMin; + } + if (first || ch->xMax > xMax2) { + xMax2 = ch->xMax; + } + if (first || ch->yMax > yMax2) { + yMax2 = ch->yMax; + } + first = gFalse; } } - - // end of page - if (pageBreaks) { - (*outputFunc)(outputStream, eop, eopLen); + if (first) { + return gFalse; } - - uMap->decRefCnt(); + *xMin = xMin2; + *yMin = yMin2; + *xMax = xMax2; + *yMax = yMax2; + return gTrue; } -void TextPage::assignColumns(TextLineFrag *frags, int nFrags, GBool oneRot) { - TextLineFrag *frag0, *frag1; - int rot, col1, col2, i, j, k; - - // all text in the region has the same rotation -- recompute the - // column numbers based only on the text in the region - if (oneRot) { - qsort(frags, nFrags, sizeof(TextLineFrag), &TextLineFrag::cmpXYLineRot); - rot = frags[0].line->rot; - for (i = 0; i < nFrags; ++i) { - frag0 = &frags[i]; - col1 = 0; - for (j = 0; j < i; ++j) { - frag1 = &frags[j]; - col2 = 0; // make gcc happy - switch (rot) { - case 0: - if (frag0->xMin >= frag1->xMax) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->xMin >= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - case 1: - if (frag0->yMin >= frag1->yMax) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->yMin >= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - case 2: - if (frag0->xMax <= frag1->xMin) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->xMax <= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - case 3: - if (frag0->yMax <= frag1->yMin) { - col2 = frag1->col + (frag1->line->col[frag1->start + frag1->len] - - frag1->line->col[frag1->start]) + 1; - } else { - for (k = frag1->start; - k < frag1->start + frag1->len && - frag0->yMax <= 0.5 * (frag1->line->edge[k] + - frag1->line->edge[k+1]); - ++k) ; - col2 = frag1->col + - frag1->line->col[k] - frag1->line->col[frag1->start]; - } - break; - } - if (col2 > col1) { - col1 = col2; +TextWordList *TextPage::makeWordList() { + TextBlock *tree; + GList *columns; + TextColumn *col; + TextParagraph *par; + TextLine *line; + TextWord *word; + GList *words; + int rot, colIdx, parIdx, lineIdx, wordIdx; + + rot = rotateChars(chars); + tree = splitChars(chars); + if (!tree) { + // no text + unrotateChars(chars, rot); + return new TextWordList(new GList()); + } + columns = buildColumns(tree); + delete tree; + unrotateChars(chars, rot); + if (control.html) { + rotateUnderlinesAndLinks(rot); + generateUnderlinesAndLinks(columns); + } + + words = new GList(); + 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); + for (wordIdx = 0; wordIdx < line->words->getLength(); ++wordIdx) { + word = (TextWord *)line->words->get(wordIdx); + words->append(word->copy()); } } - frag0->col = col1; } + } - // the region includes text at different rotations -- use the - // globally assigned column numbers, offset by the minimum column - // number (i.e., shift everything over to column 0) - } else { - col1 = frags[0].col; - for (i = 1; i < nFrags; ++i) { - if (frags[i].col < col1) { - col1 = frags[i].col; - } - } - for (i = 0; i < nFrags; ++i) { - frags[i].col -= col1; - } + switch (control.mode) { + case textOutReadingOrder: + // already in reading order + break; + case textOutPhysLayout: + case textOutTableLayout: + case textOutLinePrinter: + words->sort(&TextWord::cmpYX); + break; + case textOutRawOrder: + words->sort(&TextWord::cmpCharPos); + break; } -} -int TextPage::dumpFragment(Unicode *text, int len, UnicodeMap *uMap, - GString *s) { - char lre[8], rle[8], popdf[8], buf[8]; - int lreLen, rleLen, popdfLen, n; - int nCols, i, j, k; + // this has to be done after sorting with cmpYX + unrotateColumns(columns, rot); + unrotateWords(words, rot); - nCols = 0; + deleteGList(columns, TextColumn); - if (uMap->isUnicode()) { + return new TextWordList(words); +} - lreLen = uMap->mapUnicode(0x202a, lre, sizeof(lre)); - rleLen = uMap->mapUnicode(0x202b, rle, sizeof(rle)); - popdfLen = uMap->mapUnicode(0x202c, popdf, sizeof(popdf)); +//------------------------------------------------------------------------ +// TextPage: debug +//------------------------------------------------------------------------ - if (primaryLR) { +#if 0 //~debug - i = 0; - while (i < len) { - // output a left-to-right section - for (j = i; j < len && !unicodeTypeR(text[j]); ++j) ; - for (k = i; k < j; ++k) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - ++nCols; - } - i = j; - // output a right-to-left section - for (j = i; - j < len && !(unicodeTypeL(text[j]) || unicodeTypeNum(text[j])); - ++j) ; - if (j > i) { - s->append(rle, rleLen); - for (k = j - 1; k >= i; --k) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - ++nCols; - } - s->append(popdf, popdfLen); - i = j; - } - } +void TextPage::dumpChars(GList *charsA) { + TextChar *ch; + int i; - } else { + for (i = 0; i < charsA->getLength(); ++i) { + ch = (TextChar *)charsA->get(i); + printf("char: U+%04x '%c' xMin=%g yMin=%g xMax=%g yMax=%g fontSize=%g rot=%d\n", + ch->c, ch->c & 0xff, ch->xMin, ch->yMin, ch->xMax, ch->yMax, + ch->fontSize, ch->rot); + } +} - // Note: This code treats numeric characters (European and - // Arabic/Indic) as left-to-right, which isn't strictly correct - // (incurs extra LRE/POPDF pairs), but does produce correct - // visual formatting. - s->append(rle, rleLen); - i = len - 1; - while (i >= 0) { - // output a right-to-left section - for (j = i; - j >= 0 && !(unicodeTypeL(text[j]) || unicodeTypeNum(text[j])); - --j) ; - for (k = i; k > j; --k) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - ++nCols; - } - i = j; - // output a left-to-right section - for (j = i; j >= 0 && !unicodeTypeR(text[j]); --j) ; - if (j < i) { - s->append(lre, lreLen); - for (k = j + 1; k <= i; ++k) { - n = uMap->mapUnicode(text[k], buf, sizeof(buf)); - s->append(buf, n); - ++nCols; - } - s->append(popdf, popdfLen); - i = j; - } - } - s->append(popdf, popdfLen); +void TextPage::dumpTree(TextBlock *tree, int indent) { + TextChar *ch; + int i; + printf("%*sblock: type=%s tag=%s small=%d rot=%d xMin=%g yMin=%g xMax=%g yMax=%g\n", + indent, "", + tree->type == blkLeaf ? "leaf" : + tree->type == blkHorizSplit ? "horiz" : "vert", + tree->tag == blkTagMulticolumn ? "multicolumn" : + tree->tag == blkTagColumn ? "column" : "line", + tree->smallSplit, + tree->rot, tree->xMin, tree->yMin, tree->xMax, tree->yMax); + if (tree->type == blkLeaf) { + for (i = 0; i < tree->children->getLength(); ++i) { + ch = (TextChar *)tree->children->get(i); + printf("%*schar: '%c' xMin=%g yMin=%g xMax=%g yMax=%g font=%d.%d\n", + indent + 2, "", ch->c & 0xff, + ch->xMin, ch->yMin, ch->xMax, ch->yMax, + ch->font->fontID.num, ch->font->fontID.gen); } - } else { - for (i = 0; i < len; ++i) { - n = uMap->mapUnicode(text[i], buf, sizeof(buf)); - s->append(buf, n); - nCols += n; + for (i = 0; i < tree->children->getLength(); ++i) { + dumpTree((TextBlock *)tree->children->get(i), indent + 2); } } - - return nCols; } -#if TEXTOUT_WORD_LIST -TextWordList *TextPage::makeWordList(GBool physLayout) { - return new TextWordList(this, physLayout); +void TextPage::dumpColumns(GList *columns) { + TextColumn *col; + TextParagraph *par; + TextLine *line; + int colIdx, parIdx, lineIdx, i; + + for (colIdx = 0; colIdx < columns->getLength(); ++colIdx) { + col = (TextColumn *)columns->get(colIdx); + printf("column: xMin=%g yMin=%g xMax=%g yMax=%g px=%d py=%d pw=%d ph=%d\n", + col->xMin, col->yMin, col->xMax, col->yMax, + col->px, col->py, col->pw, col->ph); + for (parIdx = 0; parIdx < col->paragraphs->getLength(); ++parIdx) { + par = (TextParagraph *)col->paragraphs->get(parIdx); + printf(" paragraph:\n"); + for (lineIdx = 0; lineIdx < par->lines->getLength(); ++lineIdx) { + line = (TextLine *)par->lines->get(lineIdx); + printf(" line: xMin=%g yMin=%g xMax=%g yMax=%g px=%d pw=%d rot=%d\n", + line->xMin, line->yMin, line->xMax, line->yMax, + line->px, line->pw, line->rot); + printf(" "); + for (i = 0; i < line->len; ++i) { + printf("%c", line->text[i] & 0xff); + } + printf("\n"); + } + } + } } -#endif + +#endif //~debug //------------------------------------------------------------------------ // TextOutputDev @@ -4038,14 +4186,10 @@ static void outputToFile(void *stream, c fwrite(text, 1, len, (FILE *)stream); } -TextOutputDev::TextOutputDev(char *fileName, GBool physLayoutA, - double fixedPitchA, GBool rawOrderA, +TextOutputDev::TextOutputDev(char *fileName, TextOutputControl *controlA, GBool append) { text = NULL; - physLayout = physLayoutA; - fixedPitch = physLayout ? fixedPitchA : 0; - rawOrder = rawOrderA; - doHTML = gFalse; + control = *controlA; ok = gTrue; // open file @@ -4070,28 +4214,21 @@ TextOutputDev::TextOutputDev(char *fileN } // set up text object - text = new TextPage(rawOrderA); + text = new TextPage(&control); } TextOutputDev::TextOutputDev(TextOutputFunc func, void *stream, - GBool physLayoutA, double fixedPitchA, - GBool rawOrderA) { + TextOutputControl *controlA) { outputFunc = func; outputStream = stream; needClose = gFalse; - physLayout = physLayoutA; - fixedPitch = physLayout ? fixedPitchA : 0; - rawOrder = rawOrderA; - doHTML = gFalse; - text = new TextPage(rawOrderA); + control = *controlA; + text = new TextPage(&control); ok = gTrue; } TextOutputDev::~TextOutputDev() { if (needClose) { -#ifdef MACOS - ICS_MapRefNumAndAssign((short)((FILE *)outputStream)->handle); -#endif fclose((FILE *)outputStream); } if (text) { @@ -4104,10 +4241,8 @@ void TextOutputDev::startPage(int pageNu } void TextOutputDev::endPage() { - text->endPage(); - text->coalesce(physLayout, fixedPitch, doHTML); if (outputStream) { - text->dump(outputStream, outputFunc, physLayout); + text->write(outputStream, outputFunc); } } @@ -4129,7 +4264,7 @@ void TextOutputDev::drawChar(GfxState *s double dx, double dy, double originX, double originY, CharCode c, int nBytes, Unicode *u, int uLen) { - text->addChar(state, x - originX, y - originY, dx, dy, c, nBytes, u, uLen); + text->addChar(state, x, y, dx, dy, c, nBytes, u, uLen); } void TextOutputDev::incCharCount(int nChars) { @@ -4149,7 +4284,7 @@ void TextOutputDev::stroke(GfxState *sta GfxSubpath *subpath; double x[2], y[2]; - if (!doHTML) { + if (!control.html) { return; } path = state->getPath(); @@ -4176,7 +4311,7 @@ void TextOutputDev::fill(GfxState *state double rx0, ry0, rx1, ry1, t; int i; - if (!doHTML) { + if (!control.html) { return; } path = state->getPath(); @@ -4238,7 +4373,7 @@ void TextOutputDev::fill(GfxState *state } void TextOutputDev::eoFill(GfxState *state) { - if (!doHTML) { + if (!control.html) { return; } fill(state); @@ -4248,7 +4383,7 @@ void TextOutputDev::processLink(Link *li double x1, y1, x2, y2; int xMin, yMin, xMax, yMax, x, y; - if (!doHTML) { + if (!control.html) { return; } link->getRect(&x1, &y1, &x2, &y2); @@ -4315,16 +4450,14 @@ GBool TextOutputDev::findCharRange(int p return text->findCharRange(pos, length, xMin, yMin, xMax, yMax); } -#if TEXTOUT_WORD_LIST TextWordList *TextOutputDev::makeWordList() { - return text->makeWordList(physLayout); + return text->makeWordList(); } -#endif TextPage *TextOutputDev::takeText() { TextPage *ret; ret = text; - text = new TextPage(rawOrder); + text = new TextPage(&control); return ret; } diff -uNrp xpdf-3.03/xpdf/TextOutputDev.h xpdf-3.04/xpdf/TextOutputDev.h --- xpdf-3.03/xpdf/TextOutputDev.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/TextOutputDev.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // TextOutputDev.h // -// Copyright 1997-2003 Glyph & Cog, LLC +// Copyright 1997-2012 Glyph & Cog, LLC // //======================================================================== @@ -20,20 +20,12 @@ #include "GfxFont.h" #include "OutputDev.h" -class GString; class GList; -class GfxFont; -class GfxState; class UnicodeMap; -class Link; -class TextWord; -class TextPool; -class TextLine; -class TextLineFrag; class TextBlock; -class TextFlow; -class TextWordList; +class TextChar; +class TextLink; class TextPage; //------------------------------------------------------------------------ @@ -41,6 +33,37 @@ class TextPage; typedef void (*TextOutputFunc)(void *stream, const char *text, int len); //------------------------------------------------------------------------ +// TextOutputControl +//------------------------------------------------------------------------ + +enum TextOutputMode { + textOutReadingOrder, // format into reading order + textOutPhysLayout, // maintain original physical layout + textOutTableLayout, // similar to PhysLayout, but optimized + // for tables + textOutLinePrinter, // strict fixed-pitch/height layout + textOutRawOrder // keep text in content stream order +}; + +class TextOutputControl { +public: + + TextOutputControl(); + ~TextOutputControl() {} + + TextOutputMode mode; // formatting mode + double fixedPitch; // if this is non-zero, assume fixed-pitch + // characters with this width + // (only relevant for PhysLayout, Table, + // and LinePrinter modes) + double fixedLineSpacing; // fixed line spacing (only relevant for + // LinePrinter mode) + GBool html; // enable extra processing for HTML + GBool clipText; // separate clipped text and add it back + // in after forming columns +}; + +//------------------------------------------------------------------------ // TextFontInfo //------------------------------------------------------------------------ @@ -52,7 +75,6 @@ public: GBool matches(GfxState *state); -#if TEXTOUT_WORD_LIST // Get the font name (which may be NULL). GString *getFontName() { return fontName; } @@ -62,18 +84,21 @@ public: GBool isSymbolic() { return flags & fontSymbolic; } GBool isItalic() { return flags & fontItalic; } GBool isBold() { return flags & fontBold; } -#endif + + // Get the width of the 'm' character, if available. + double getMWidth() { return mWidth; } private: - GfxFont *gfxFont; -#if TEXTOUT_WORD_LIST + Ref fontID; GString *fontName; int flags; -#endif + double mWidth; + double ascent, descent; - friend class TextWord; + friend class TextLine; friend class TextPage; + friend class TextWord; }; //------------------------------------------------------------------------ @@ -83,44 +108,21 @@ private: class TextWord { public: - // Constructor. - TextWord(GfxState *state, int rotA, double x0, double y0, - TextFontInfo *fontA, double fontSize); - - // Destructor. + TextWord(GList *chars, int start, int lenA, + int rotA, GBool spaceAfterA); ~TextWord(); - - // Add a character to the word. - void addChar(GfxState *state, double x, double y, - double dx, double dy, int charPosA, int charLen, - Unicode u); - - // Merge onto the end of . - void merge(TextWord *word); - - // Compares to , returning -1 (<), 0 (=), or +1 (>), - // based on a primary-axis comparison, e.g., x ordering if rot=0. - int primaryCmp(TextWord *word); - - // Return the distance along the primary axis between and - // . - double primaryDelta(TextWord *word); - - static int cmpYX(const void *p1, const void *p2); + TextWord *copy() { return new TextWord(this); } // Get the TextFontInfo object associated with this word. TextFontInfo *getFontInfo() { return font; } - // Get the next TextWord on the linked list. - TextWord *getNext() { return next; } - -#if TEXTOUT_WORD_LIST int getLength() { return len; } Unicode getChar(int idx) { return text[idx]; } GString *getText(); GString *getFontName() { return font->fontName; } void getColor(double *r, double *g, double *b) { *r = colorR; *g = colorG; *b = colorB; } + GBool isInvisible() { return invisible; } void getBBox(double *xMinA, double *yMinA, double *xMaxA, double *yMaxA) { *xMinA = xMin; *yMinA = yMin; *xMaxA = xMax; *yMaxA = yMax; } void getCharBBox(int charIdx, double *xMinA, double *yMinA, @@ -130,76 +132,43 @@ public: int getCharPos() { return charPos[0]; } int getCharLen() { return charPos[len] - charPos[0]; } GBool getSpaceAfter() { return spaceAfter; } -#endif - + double getBaseline(); GBool isUnderlined() { return underlined; } - Link *getLink() { return link; } + GString *getLinkURI(); private: + TextWord(TextWord *word); + void appendChar(TextChar *ch); + 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 - double base; // baseline x or y coordinate Unicode *text; // the text - double *edge; // "near" edge x or y coord of each char - // (plus one extra entry for the last char) int *charPos; // character position (within content stream) // of each char (plus one extra entry for // the last char) - int len; // length of text/edge/charPos arrays - int size; // size of text/edge/charPos arrays + double *edge; // "near" edge x or y coord of each char + // (plus one extra entry for the last char) + int len; // number of characters TextFontInfo *font; // font information double fontSize; // font size GBool spaceAfter; // set if there is a space between this // word and the next word on the line - TextWord *next; // next word in line -#if TEXTOUT_WORD_LIST + GBool underlined; + TextLink *link; + double colorR, // word color colorG, colorB; -#endif - - GBool underlined; - Link *link; - - friend class TextPool; - friend class TextLine; - friend class TextBlock; - friend class TextFlow; - friend class TextWordList; - friend class TextPage; -}; - -//------------------------------------------------------------------------ -// TextPool -//------------------------------------------------------------------------ - -class TextPool { -public: - - TextPool(); - ~TextPool(); - - TextWord *getPool(int baseIdx) { return pool[baseIdx - minBaseIdx]; } - void setPool(int baseIdx, TextWord *p) { pool[baseIdx - minBaseIdx] = p; } - - int getBaseIdx(double base); - - void addWord(TextWord *word); - -private: - - int minBaseIdx; // min baseline bucket index - int maxBaseIdx; // max baseline bucket index - TextWord **pool; // array of linked lists, one for each - // baseline value (multiple of 4 pts) - TextWord *cursor; // pointer to last-accessed word - int cursorBaseIdx; // baseline bucket index of last-accessed word + GBool invisible; // set for invisible text (render mode 3) friend class TextBlock; + friend class TextLine; friend class TextPage; }; @@ -210,168 +179,92 @@ private: class TextLine { public: - TextLine(TextBlock *blkA, int rotA, double baseA); + TextLine(GList *wordsA, double xMinA, double yMinA, + double xMaxA, double yMaxA, double fontSizeA); ~TextLine(); - void addWord(TextWord *word); - - // Return the distance along the primary axis between and - // . - double primaryDelta(TextLine *line); - - // Compares to , returning -1 (<), 0 (=), or +1 (>), - // based on a primary-axis comparison, e.g., x ordering if rot=0. - int primaryCmp(TextLine *line); - - // Compares to , returning -1 (<), 0 (=), or +1 (>), - // based on a secondary-axis comparison of the baselines, e.g., y - // ordering if rot=0. - int secondaryCmp(TextLine *line); - - int cmpYX(TextLine *line); - - static int cmpXY(const void *p1, const void *p2); - - void coalesce(UnicodeMap *uMap); - - // Get the head of the linked list of TextWords. - TextWord *getWords() { return words; } - - // Get the next TextLine on the linked list. - TextLine *getNext() { return next; } - - // Returns true if the last char of the line is a hyphen. - GBool isHyphenated() { return hyphenated; } + double getXMin() { return xMin; } + double getYMin() { return yMin; } + double getBaseline(); + int getRotation() { return rot; } + GList *getWords() { return words; } private: - TextBlock *blk; // parent block - int rot; // text rotation + GList *words; // [TextWord] + int rot; // rotation, multiple of 90 degrees + // (0, 1, 2, or 3) double xMin, xMax; // bounding box x coordinates double yMin, yMax; // bounding box y coordinates - double base; // baseline x or y coordinate - TextWord *words; // words in this line - TextWord *lastWord; // last word in this line + double fontSize; // main (max) font size for this line Unicode *text; // Unicode text of the line, including // spaces between words double *edge; // "near" edge x or y coord of each char // (plus one extra entry for the last char) - int *col; // starting column number of each Unicode char int len; // number of Unicode chars - int convertedLen; // total number of converted characters GBool hyphenated; // set if last char is a hyphen - TextLine *next; // next line in block + int px; // x offset (in characters, relative to + // containing column) in physical layout mode + int pw; // line width (in characters) in physical + // layout mode - friend class TextLineFrag; - friend class TextBlock; - friend class TextFlow; - friend class TextWordList; friend class TextPage; + friend class TextParagraph; }; //------------------------------------------------------------------------ -// TextBlock +// TextParagraph //------------------------------------------------------------------------ -class TextBlock { +class TextParagraph { public: - TextBlock(TextPage *pageA, int rotA); - ~TextBlock(); - - void addWord(TextWord *word); - - void coalesce(UnicodeMap *uMap, double fixedPitch); - - // Update this block's priMin and priMax values, looking at . - void updatePriMinMax(TextBlock *blk); - - static int cmpXYPrimaryRot(const void *p1, const void *p2); - - static int cmpYXPrimaryRot(const void *p1, const void *p2); - - int primaryCmp(TextBlock *blk); - - double secondaryDelta(TextBlock *blk); + TextParagraph(GList *linesA); + ~TextParagraph(); - // Returns true if is below , relative to the page's - // primary rotation. - GBool isBelow(TextBlock *blk); - - // Get the head of the linked list of TextLines. - TextLine *getLines() { return lines; } - - // Get the next TextBlock on the linked list. - TextBlock *getNext() { return next; } + // Get the list of TextLine objects. + GList *getLines() { return lines; } private: - TextPage *page; // the parent page - int rot; // text rotation + GList *lines; // [TextLine] double xMin, xMax; // bounding box x coordinates double yMin, yMax; // bounding box y coordinates - double priMin, priMax; // whitespace bounding box along primary axis - - TextPool *pool; // pool of words (used only until lines - // are built) - TextLine *lines; // linked list of lines - TextLine *curLine; // most recently added line - int nLines; // number of lines - int charCount; // number of characters in the block - int col; // starting column - int nColumns; // number of columns in the block - - TextBlock *next; - TextBlock *stackNext; - friend class TextLine; - friend class TextLineFrag; - friend class TextFlow; - friend class TextWordList; friend class TextPage; }; //------------------------------------------------------------------------ -// TextFlow +// TextColumn //------------------------------------------------------------------------ -class TextFlow { +class TextColumn { public: - TextFlow(TextPage *pageA, TextBlock *blk); - ~TextFlow(); - - // Add a block to the end of this flow. - void addBlock(TextBlock *blk); - - // Returns true if fits below in the flow, i.e., (1) - // it uses a font no larger than the last block added to the flow, - // and (2) it fits within the flow's [priMin, priMax] along the - // primary axis. - GBool blockFits(TextBlock *blk, TextBlock *prevBlk); - - // Get the head of the linked list of TextBlocks. - TextBlock *getBlocks() { return blocks; } + TextColumn(GList *paragraphsA, double xMinA, double yMinA, + double xMaxA, double yMaxA); + ~TextColumn(); - // Get the next TextFlow on the linked list. - TextFlow *getNext() { return next; } + // Get the list of TextParagraph objects. + GList *getParagraphs() { return paragraphs; } private: - TextPage *page; // the parent page + static int cmpX(const void *p1, const void *p2); + static int cmpY(const void *p1, const void *p2); + static int cmpPX(const void *p1, const void *p2); + + GList *paragraphs; // [TextParagraph] double xMin, xMax; // bounding box x coordinates double yMin, yMax; // bounding box y coordinates - double priMin, priMax; // whitespace bounding box along primary axis - TextBlock *blocks; // blocks in flow - TextBlock *lastBlk; // last block in this flow - TextFlow *next; + int px, py; // x, y position (in characters) in physical + // layout mode + int pw, ph; // column width, height (in characters) in + // physical layout mode - friend class TextWordList; friend class TextPage; }; -#if TEXTOUT_WORD_LIST - //------------------------------------------------------------------------ // TextWordList //------------------------------------------------------------------------ @@ -379,11 +272,7 @@ private: class TextWordList { public: - // Build a flat word list, in content stream order (if - // text->rawOrder is true), physical layout order (if - // is true and text->rawOrder is false), or reading order (if both - // flags are false). - TextWordList(TextPage *text, GBool physLayout); + TextWordList(GList *wordsA); ~TextWordList(); @@ -398,8 +287,6 @@ private: GList *words; // [TextWord] }; -#endif // TEXTOUT_WORD_LIST - //------------------------------------------------------------------------ // TextPage //------------------------------------------------------------------------ @@ -407,52 +294,11 @@ private: class TextPage { public: - // Constructor. - TextPage(GBool rawOrderA); - - // Destructor. + TextPage(TextOutputControl *controlA); ~TextPage(); - // Start a new page. - void startPage(GfxState *state); - - // End the current page. - void endPage(); - - // Update the current font. - void updateFont(GfxState *state); - - // Begin a new word. - void beginWord(GfxState *state, double x0, double y0); - - // Add a character to the current word. - void addChar(GfxState *state, double x, double y, - double dx, double dy, - CharCode c, int nBytes, Unicode *u, int uLen); - - // Add invisible characters. - void incCharCount(int nChars); - - // Begin/end an "ActualText" span, where the char indexes are - // supplied by a marked content operator rather than the text - // drawing operators. - void beginActualText(GfxState *state, Unicode *u, int uLen); - void endActualText(GfxState *state); - - // End the current word, sorting it into the list of words. - void endWord(); - - // Add a word, sorting it into the list of words. - void addWord(TextWord *word); - - // Add a (potential) underline. - void addUnderline(double x0, double y0, double x1, double y1); - - // Add a hyperlink. - void addLink(int xMin, int yMin, int xMax, int yMax, Link *link); - - // Coalesce strings that look like parts of the same line. - void coalesce(GBool physLayout, double fixedPitch, GBool doHTML); + // Write contents of page to a stream. + void write(void *outputStream, TextOutputFunc outputFunc); // Find a string. If is true, starts looking at the // top of the page; else if is true, starts looking @@ -480,39 +326,106 @@ public: double *xMin, double *yMin, double *xMax, double *yMax); - // Dump contents of page to a file. - void dump(void *outputStream, TextOutputFunc outputFunc, - GBool physLayout); + // Create and return a list of TextColumn objects. + GList *makeColumns(); - // Get the head of the linked list of TextFlows. - TextFlow *getFlows() { return flows; } + // Get the list of all TextFontInfo objects used on this page. + GList *getFonts() { return fonts; } -#if TEXTOUT_WORD_LIST - // Build a flat word list, in content stream order (if - // this->rawOrder is true), physical layout order (if - // is true and this->rawOrder is false), or reading order (if both - // flags are false). - TextWordList *makeWordList(GBool physLayout); -#endif + // Build a flat word list, in the specified ordering. + TextWordList *makeWordList(); private: + void startPage(GfxState *state); void clear(); - void assignColumns(TextLineFrag *frags, int nFrags, int rot); - int dumpFragment(Unicode *text, int len, UnicodeMap *uMap, GString *s); + void updateFont(GfxState *state); + void addChar(GfxState *state, double x, double y, + double dx, double dy, + CharCode c, int nBytes, Unicode *u, int uLen); + void incCharCount(int nChars); + void beginActualText(GfxState *state, Unicode *u, int uLen); + void endActualText(GfxState *state); + void addUnderline(double x0, double y0, double x1, double y1); + void addLink(double xMin, double yMin, double xMax, double yMax, + Link *link); - GBool rawOrder; // keep text in content stream order + // output + void writeReadingOrder(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen); + void writePhysLayout(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen); + void writeLinePrinter(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen); + void writeRaw(void *outputStream, + TextOutputFunc outputFunc, + UnicodeMap *uMap, + char *space, int spaceLen, + char *eol, int eolLen); + void encodeFragment(Unicode *text, int len, UnicodeMap *uMap, + GBool primaryLR, GString *s); + + // analysis + int rotateChars(GList *charsA); + void rotateUnderlinesAndLinks(int rot); + void unrotateChars(GList *charsA, int rot); + void unrotateColumns(GList *columns, int rot); + void unrotateWords(GList *words, int rot); + GBool checkPrimaryLR(GList *charsA); + void removeDuplicates(GList *charsA, int rot); + TextBlock *splitChars(GList *charsA); + TextBlock *split(GList *charsA, int rot); + GList *getChars(GList *charsA, double xMin, double yMin, + double xMax, double yMax); + void tagBlock(TextBlock *blk); + void insertLargeChars(GList *largeChars, TextBlock *blk); + void insertLargeCharsInFirstLeaf(GList *largeChars, TextBlock *blk); + void insertLargeCharInLeaf(TextChar *ch, TextBlock *blk); + void insertIntoTree(TextBlock *subtree, TextBlock *primaryTree); + void insertColumnIntoTree(TextBlock *column, TextBlock *tree); + void insertClippedChars(GList *clippedChars, TextBlock *tree); + TextBlock *findClippedCharLeaf(TextChar *ch, TextBlock *tree); + GList *buildColumns(TextBlock *tree); + void buildColumns2(TextBlock *blk, GList *columns); + TextColumn *buildColumn(TextBlock *tree); + double getLineIndent(TextLine *line, TextBlock *blk); + double getAverageLineSpacing(GList *lines); + double getLineSpacing(TextLine *line0, TextLine *line1); + void buildLines(TextBlock *blk, GList *lines); + TextLine *buildLine(TextBlock *blk); + void getLineChars(TextBlock *blk, GList *charsA); + double computeWordSpacingThreshold(GList *charsA, int rot); + int assignPhysLayoutPositions(GList *columns); + void assignLinePhysPositions(GList *columns); + void computeLinePhysWidth(TextLine *line, UnicodeMap *uMap); + int assignColumnPhysPositions(GList *columns); + void generateUnderlinesAndLinks(GList *columns); + + // debug +#if 0 //~debug + void dumpChars(GList *charsA); + void dumpTree(TextBlock *tree, int indent = 0); + void dumpColumns(GList *columns); +#endif + + TextOutputControl control; // formatting parameters double pageWidth, pageHeight; // width and height of current page - TextWord *curWord; // currently active string int charPos; // next character position (within content // stream) TextFontInfo *curFont; // current font double curFontSize; // current font size - int nest; // current nesting level (for Type 3 fonts) + int curRot; // current rotation int nTinyChars; // number of "tiny" chars seen so far - GBool lastCharOverlap; // set if the last added char overlapped the - // previous char Unicode *actualText; // current "ActualText" span int actualTextLen; double actualTextX0, @@ -521,32 +434,22 @@ private: actualTextY1; int actualTextNBytes; - TextPool *pools[4]; // a "pool" of TextWords for each rotation - TextFlow *flows; // linked list of flows - TextBlock **blocks; // array of blocks, in yx order - int nBlocks; // number of blocks - int primaryRot; // primary rotation - GBool primaryLR; // primary direction (true means L-to-R, - // false means R-to-L) - TextWord *rawWords; // list of words, in raw order (only if - // rawOrder is set) - TextWord *rawLastWord; // last word on rawWords list - + GList *chars; // [TextChar] GList *fonts; // all font info objects used on this // page [TextFontInfo] + GList *underlines; // [TextUnderline] + GList *links; // [TextLink] + + GList *findCols; // text used by the findText function + // [TextColumn] + GBool findLR; // primary text direction, used by the + // findText function double lastFindXMin, // coordinates of the last "find" result lastFindYMin; GBool haveLastFind; - GList *underlines; // [TextUnderline] - GList *links; // [TextLink] - - friend class TextLine; - friend class TextLineFrag; - friend class TextBlock; - friend class TextFlow; - friend class TextWordList; + friend class TextOutputDev; }; //------------------------------------------------------------------------ @@ -561,8 +464,7 @@ public: // is true, the original physical layout of the text // is maintained. If is true, the text is kept in // content stream order. - TextOutputDev(char *fileName, GBool physLayoutA, - double fixedPitchA, GBool rawOrderA, + TextOutputDev(char *fileName, TextOutputControl *controlA, GBool append); // Create a TextOutputDev which will write to a generic stream. If @@ -570,8 +472,7 @@ public: // is maintained. If is true, the text is kept in // content stream order. TextOutputDev(TextOutputFunc func, void *stream, - GBool physLayoutA, double fixedPitchA, - GBool rawOrderA); + TextOutputControl *controlA); // Destructor. virtual ~TextOutputDev(); @@ -660,20 +561,18 @@ public: double *xMin, double *yMin, double *xMax, double *yMax); -#if TEXTOUT_WORD_LIST // Build a flat word list, in content stream order (if // this->rawOrder is true), physical layout order (if // this->physLayout is true and this->rawOrder is false), or reading // order (if both flags are false). TextWordList *makeWordList(); -#endif // Returns the TextPage object for the last rasterized page, // transferring ownership to the caller. TextPage *takeText(); // Turn extra processing for HTML conversion on or off. - void enableHTMLExtras(GBool doHTMLA) { doHTML = doHTMLA; } + void enableHTMLExtras(GBool html) { control.html = html; } private: @@ -682,13 +581,7 @@ private: GBool needClose; // need to close the output file? // (only if outputStream is a FILE*) TextPage *text; // text for the current page - GBool physLayout; // maintain original physical layout when - // dumping text - double fixedPitch; // if physLayout is true and this is non-zero, - // assume fixed-pitch characters with this - // width - GBool rawOrder; // keep text in content stream order - GBool doHTML; // extra processing for HTML conversion + TextOutputControl control; // formatting parameters GBool ok; // set up ok? }; diff -uNrp xpdf-3.03/xpdf/TextString.cc xpdf-3.04/xpdf/TextString.cc --- xpdf-3.03/xpdf/TextString.cc 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/TextString.cc 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,164 @@ +//======================================================================== +// +// TextString.cc +// +// Copyright 2011-2013 Glyph & Cog, LLC +// +//======================================================================== + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include +#include "gmem.h" +#include "GString.h" +#include "PDFDocEncoding.h" +#include "TextString.h" + +//------------------------------------------------------------------------ + +TextString::TextString() { + u = NULL; + len = size = 0; +} + +TextString::TextString(GString *s) { + u = NULL; + len = size = 0; + append(s); +} + +TextString::TextString(TextString *s) { + len = size = s->len; + if (len) { + u = (Unicode *)gmallocn(size, sizeof(Unicode)); + memcpy(u, s->u, len * sizeof(Unicode)); + } else { + u = NULL; + } +} + +TextString::~TextString() { + gfree(u); +} + +TextString *TextString::append(Unicode c) { + expand(1); + u[len] = c; + ++len; + return this; +} + +TextString *TextString::append(GString *s) { + int n, i; + + if ((s->getChar(0) & 0xff) == 0xfe && + (s->getChar(1) & 0xff) == 0xff) { + n = (s->getLength() - 2) / 2; + expand(n); + for (i = 0; i < n; ++i) { + u[len + i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | + (s->getChar(3 + 2*i) & 0xff); + } + len += n; + } else { + n = s->getLength(); + expand(n); + for (i = 0; i < n; ++i) { + u[len + i] = pdfDocEncoding[s->getChar(i) & 0xff]; + } + len += n; + } + return this; +} + +TextString *TextString::insert(int idx, Unicode c) { + if (idx >= 0 && idx <= len) { + expand(1); + if (idx < len) { + memmove(u + idx + 1, u + idx, (len - idx) * sizeof(Unicode)); + } + u[idx] = c; + ++len; + } + return this; +} + +TextString *TextString::insert(int idx, GString *s) { + int n, i; + + if (idx >= 0 && idx <= len) { + if ((s->getChar(0) & 0xff) == 0xfe && + (s->getChar(1) & 0xff) == 0xff) { + n = (s->getLength() - 2) / 2; + expand(n); + if (idx < len) { + memmove(u + idx + n, u + idx, (len - idx) * sizeof(Unicode)); + } + for (i = 0; i < n; ++i) { + u[idx + i] = ((s->getChar(2 + 2*i) & 0xff) << 8) | + (s->getChar(3 + 2*i) & 0xff); + } + len += n; + } else { + n = s->getLength(); + expand(n); + if (idx < len) { + memmove(u + idx + n, u + idx, (len - idx) * sizeof(Unicode)); + } + for (i = 0; i < n; ++i) { + u[idx + i] = pdfDocEncoding[s->getChar(i) & 0xff]; + } + len += n; + } + } + return this; +} + +void TextString::expand(int delta) { + int newLen; + + newLen = len + delta; + if (delta > INT_MAX - len) { + // trigger an out-of-memory error + size = -1; + } else if (newLen <= size) { + return; + } else if (size > 0 && size <= INT_MAX / 2 && size*2 >= newLen) { + size *= 2; + } else { + size = newLen; + } + u = (Unicode *)greallocn(u, size, sizeof(Unicode)); +} + +GString *TextString::toPDFTextString() { + GString *s; + GBool useUnicode; + int i; + + useUnicode = gFalse; + for (i = 0; i < len; ++i) { + if (u[i] >= 0x80) { + useUnicode = gTrue; + break; + } + } + s = new GString(); + if (useUnicode) { + s->append((char)0xfe); + s->append((char)0xff); + for (i = 0; i < len; ++i) { + s->append((char)(u[i] >> 8)); + s->append((char)u[i]); + } + } else { + for (i = 0; i < len; ++i) { + s->append((char)u[i]); + } + } + return s; +} diff -uNrp xpdf-3.03/xpdf/TextString.h xpdf-3.04/xpdf/TextString.h --- xpdf-3.03/xpdf/TextString.h 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/TextString.h 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,66 @@ +//======================================================================== +// +// TextString.h +// +// Copyright 2011-2013 Glyph & Cog, LLC +// +// Represents a PDF "text string", which can either be a UTF-16BE +// string (with a leading byte order marker), or an 8-bit string in +// PDFDocEncoding. +// +//======================================================================== + +#ifndef TEXTSTRING_H +#define TEXTSTRING_H + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include "CharTypes.h" + +class GString; + +//------------------------------------------------------------------------ + +class TextString { +public: + + // Create an empty TextString. + TextString(); + + // Create a TextString from a PDF text string. + TextString(GString *s); + + // Copy a TextString. + TextString(TextString *s); + + ~TextString(); + + // Append a Unicode character or PDF text string to this TextString. + TextString *append(Unicode c); + TextString *append(GString *s); + + // Insert a Unicode character or PDF text string in this TextString. + TextString *insert(int idx, Unicode c); + TextString *insert(int idx, GString *s); + + // Get the Unicode characters in the TextString. + int getLength() { return len; } + Unicode *getUnicode() { return u; } + + // Create a PDF text string from a TextString. + GString *toPDFTextString(); + +private: + + void expand(int delta); + + Unicode *u; // NB: not null-terminated + int len; + int size; +}; + +#endif diff -uNrp xpdf-3.03/xpdf/UnicodeTypeTable.cc xpdf-3.04/xpdf/UnicodeTypeTable.cc --- xpdf-3.03/xpdf/UnicodeTypeTable.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/UnicodeTypeTable.cc 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // UnicodeTypeTable.cc // -// Copyright 2004 Glyph & Cog, LLC +// Copyright 2004-2013 Glyph & Cog, LLC // //======================================================================== @@ -20,21 +20,21 @@ struct UnicodeCaseTableVector { }; static UnicodeMapTableEntry typeTable[256] = { - { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN###NNNNN################NNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN#N####NNNNLNNNNN####NLNNN#LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL", 'X' }, + { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN...NNNNN.....##########.NNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN.N....NNNNLNNNNN..##NLNNN#LNNNNNLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLL", 'X' }, { NULL, 'L' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLNNNNNNNNNNNNNNLLNNNNNNNNNNNNNNLLLLLNNNNNNNNNLNNNNNNNNNNNNNNNNN", 'X' }, { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLNNNNNNNNNNNLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNRNRNNRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR", 'X' }, - { "RRRR#########RNNNNNNNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNNNNNNNN####################RRRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNRNNNNNNNRRNNNNNNNRR##########RRRRRR", 'X' }, + { "RRRR.........RNNNNNNNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNNNNNNNN#################.##RRRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNRNNNNNNNRRNNNNNNNRR##########RRRRRR", 'X' }, { "RRRRRRRRRRRRRRNNRNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNNNNNNNNNNNNNNNNNNNNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNNNNNNNNNNRNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { NULL, 'N' }, - { "NNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNLLLLNLLLNNNNLLLLLLLLLLLLLNNLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNLLLLLLLLNLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLL##LLLLLLLNNNNN", 'X' }, - { "NNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNLLLLNLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLL##NNNNNNNNNNNNNN", 'X' }, - { "NNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLNLNNNLLLLLLLLLNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNN#NLLLLL", 'X' }, + { "NNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNLLLLNLLLNNNNLLLLLLLLLLLLLNNLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNLLLLLLLLNLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLL..LLLLLLLNNNNN", 'X' }, + { "NNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLNNNNNNNNLLLLNLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLL..NNNNNNNNNNNNNN", 'X' }, + { "NNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLNLNNNLLLLLLLLLNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNN.NLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNLLLLNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNLLLLLLLNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, - { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLNNNNNNN#####LLLLLLLNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLNNNNNNNNNLLLLLLLLLLNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, + { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLNNNNNNN.....LLLLLLLNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLNNNNNNNNNLLLLLLLLLLNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLNLNLNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNLNNNNNLNNLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNLNNNNNNLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { NULL, 'L' }, @@ -43,7 +43,7 @@ static UnicodeMapTableEntry typeTable[25 { NULL, 'L' }, { NULL, 'L' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, - { "LLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNLLLLLLLLNLLNNNNNNNNNNNLLLLLLL#LNLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNN", 'X' }, + { "LLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNLLLLLLLLNLLNNNNNNNNNNNLLLLLLL.LNLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { "NNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL", 'X' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLNNNNNLLLLLLNLLLLLLNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { NULL, 'L' }, @@ -52,9 +52,9 @@ static UnicodeMapTableEntry typeTable[25 { NULL, 'L' }, { NULL, 'L' }, { "LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNLNNNLLLLLLLLLLLNNNLLLLLLLLLLLLNNNNLLLLLLLLLLLLLNNNLLLLLLLLLLLLLNNN", 'X' }, - { "NNNNNNNNNNNNNNLRNNNNNNNNNNNNNNNNNNNNNNNNNNLRNLRN#####NNNNNNNNNNNNNNN#NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN#L##########NNNL############NNN###################################NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, - { "NNLNNNNLNNLLLLLLLLLLNLNNNLLLLLNNNNNNLNLNLNLLLL#LLLNLLLLLLLNNLLLLNNNNNLLLLLNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, - { "NNNNNNNNNNNNNNNNNN##NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, + { "NNNNNNNNNNNNNNLRNNNNNNNNNNNNNNNNNNNNNNNNNNLRNLRN.....NNNNNNNNNNNNNNN.NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN#L########..NNNL##########..NNN...................................NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, + { "NNLNNNNLNNLLLLLLLLLLNLNNNLLLLLNNNNNNLNLNLNLLLL.LLLNLLLLLLLNNLLLLNNNNNLLLLLNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, + { "NNNNNNNNNNNNNNNNNN..NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN####################LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNN", 'X' }, { NULL, 'N' }, @@ -271,11 +271,11 @@ static UnicodeMapTableEntry typeTable[25 { NULL, 'L' }, { NULL, 'L' }, { NULL, 'L' }, - { "LLLLLLLLLLLLLLLLLLLLLLLLRRRRRRNRRRRRRRRRR#RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR", 'X' }, + { "LLLLLLLLLLLLLLLLLLLLLLLLRRRRRRNRRRRRRRRRR.RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR", 'X' }, { NULL, 'R' }, { "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNN", 'X' }, - { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN#N#NN#NNNNNNNNN#NN##NNNNN##NRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNN", 'X' }, - { "NNN###NNNNN################NNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL#####NNN##NNNNNNNNNNNNNNNNNNNNNNNLL", 'X' } + { "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN.N.NN.NNNNNNNNN.NN..NNNNN..NRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRNNN", 'X' }, + { "NNN...NNNNN.....##########.NNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL.....NNN..NNNNNNNNNNNNNNNNNNNNNNNLL", 'X' } }; static UnicodeCaseTableVector caseTable00 = {{ @@ -935,13 +935,23 @@ GBool unicodeTypeR(Unicode c) { } GBool unicodeTypeNum(Unicode c) { - return getType(c) == '#'; + char t; + + t = getType(c); + return t == '#' || t == '.'; } GBool unicodeTypeAlphaNum(Unicode c) { char t; t = getType(c); + return t == 'L' || t == 'R' || t == '#' || t == '.'; +} + +GBool unicodeTypeWord(Unicode c) { + char t; + + t = getType(c); return t == 'L' || t == 'R' || t == '#'; } diff -uNrp xpdf-3.03/xpdf/UnicodeTypeTable.h xpdf-3.04/xpdf/UnicodeTypeTable.h --- xpdf-3.03/xpdf/UnicodeTypeTable.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/UnicodeTypeTable.h 2014-05-28 20:50:50.000000000 +0200 @@ -2,7 +2,7 @@ // // UnicodeTypeTable.h // -// Copyright 2003 Glyph & Cog, LLC +// Copyright 2003-2013 Glyph & Cog, LLC // //======================================================================== @@ -19,6 +19,8 @@ extern GBool unicodeTypeNum(Unicode c); extern GBool unicodeTypeAlphaNum(Unicode c); +extern GBool unicodeTypeWord(Unicode c); + extern Unicode unicodeToUpper(Unicode c); #endif diff -uNrp xpdf-3.03/xpdf/XFAForm.cc xpdf-3.04/xpdf/XFAForm.cc --- xpdf-3.03/xpdf/XFAForm.cc 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/XFAForm.cc 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,1458 @@ +//======================================================================== +// +// XFAForm.cc +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include +#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 "XFAForm.h" + +#ifdef _WIN32 +# 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 } +}; + +//------------------------------------------------------------------------ +// XFAForm +//------------------------------------------------------------------------ + +XFAForm *XFAForm::load(PDFDoc *docA, Object *acroFormObj, Object *xfaObj) { + XFAForm *xfaForm; + ZxDoc *xmlA; + ZxElement *tmpl; + Object catDict, resourceDictA, obj1; + GString *data; + GBool fullXFAA; + GString *name; + char buf[4096]; + int n, i; + + 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, xmlA, &resourceDictA, fullXFAA); + + resourceDictA.free(); + + if (xfaForm->xml->getRoot()) { + if ((tmpl = xfaForm->xml->getRoot()->findFirstChildElement("template"))) { + name = new GString("form"); + xfaForm->curPageNum = 1; + xfaForm->curXOffset = xfaForm->curYOffset = 0; + xfaForm->scanFields(tmpl, name, name); + delete name; + } + } + + return xfaForm; +} + +XFAForm::XFAForm(PDFDoc *docA, ZxDoc *xmlA, Object *resourceDictA, + GBool fullXFAA): Form(docA) { + xml = xmlA; + fields = new GList(); + resourceDictA->copy(&resourceDict); + fullXFA = fullXFAA; +} + +XFAForm::~XFAForm() { + delete xml; + deleteGList(fields, XFAFormField); + resourceDict.free(); +} + +void XFAForm::scanFields(ZxElement *elem, GString *name, GString *dataName) { + ZxAttr *attr; + ZxNode *child; + ZxElement *bindElem; + GHash *names1, *names2; + GString *childName, *fullName, *fullDataName; + int i; + + //~ need to handle subform + + //~ need to handle exclGroup + //~ - fields in an exclGroup may/must(?) not have names + //~ - each field has an items element with the the value when that + //~ field is selected + + if (elem->isElement("field")) { + fields->append(new XFAFormField(this, elem, name->copy(), + dataName->copy(), curPageNum, + curXOffset, curYOffset)); + } else if (elem->isElement("breakBefore")) { + if ((attr = elem->findAttr("targetType")) && + !attr->getValue()->cmp("pageArea") && + (attr = elem->findAttr("startNew")) && + !attr->getValue()->cmp("1")) { + ++curPageNum; + } + } else if (elem->isElement("break")) { + if ((attr = elem->findAttr("before")) && + !attr->getValue()->cmp("pageArea") && + (attr = elem->findAttr("startNew")) && + !attr->getValue()->cmp("1")) { + ++curPageNum; + } + } else if (elem->isElement("contentArea")) { + curXOffset = XFAFormField::getMeasurement(elem->findAttr("x"), 0); + curYOffset = XFAFormField::getMeasurement(elem->findAttr("y"), 0); + } else { + names1 = new GHash(); + for (child = elem->getFirstChild(); child; child = child->getNextChild()) { + if (child->isElement() && + (attr = ((ZxElement *)child)->findAttr("name"))) { + childName = attr->getValue(); + names1->replace(childName, names1->lookupInt(childName) + 1); + } + } + names2 = new GHash(); + for (child = elem->getFirstChild(); child; child = child->getNextChild()) { + if (child->isElement()) { + if (!((bindElem = child->findFirstChildElement("bind")) && + (attr = bindElem->findAttr("match")) && + !attr->getValue()->cmp("none")) && + (attr = ((ZxElement *)child)->findAttr("name"))) { + childName = attr->getValue(); + if (names1->lookupInt(childName) > 1) { + i = names2->lookupInt(childName); + fullName = GString::format("{0:t}.{1:t}[{2:d}]", + name, childName, i); + fullDataName = GString::format("{0:t}.{1:t}[{2:d}]", + dataName, childName, i); + names2->replace(childName, i + 1); + } else { + fullName = GString::format("{0:t}.{1:t}", name, childName); + fullDataName = GString::format("{0:t}.{1:t}", dataName, childName); + } + } else { + fullName = name->copy(); + fullDataName = dataName->copy(); + } + scanFields((ZxElement *)child, fullName, fullDataName); + delete fullName; + delete fullDataName; + } + } + delete names1; + delete names2; + } +} + +void XFAForm::draw(int pageNum, Gfx *gfx, GBool printing) { + GfxFontDict *fontDict; + Object obj1; + int i; + + // build the font dictionary + if (resourceDict.isDict() && + resourceDict.dictLookup("Font", &obj1)->isDict()) { + fontDict = new GfxFontDict(doc->getXRef(), NULL, obj1.getDict()); + } else { + fontDict = NULL; + } + 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 *dataNameA, int pageNumA, + double xOffsetA, double yOffsetA) { + xfaForm = xfaFormA; + xml = xmlA; + name = nameA; + dataName = dataNameA; + pageNum = pageNumA; + xOffset = xOffsetA; + yOffset = yOffsetA; +} + +XFAFormField::~XFAFormField() { + delete name; + delete dataName; +} + +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("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"); + } else if (node->isElement("barcode")) { + s = getFieldValue("text"); + } + //~ 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::draw(int pageNumA, Gfx *gfx, GBool printing, + GfxFontDict *fontDict) { + Page *page; + PDFRectangle *pageRect; + ZxElement *uiElem; + ZxNode *node; + ZxAttr *attr; + GString *appearBuf; + MemStream *appearStream; + Object appearDict, appearance, obj1, obj2; + double mat[6]; + double x, y, w, h, x2, y2, w2, h2, x3, y3, w3, h3; + double anchorX, anchorY; + int pageRot, rot, rot3; + + if (pageNumA != pageNum) { + return; + } + + 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; + } + } + x = getMeasurement(xml->findAttr("x"), 0) + xOffset; + y = getMeasurement(xml->findAttr("y"), 0) + yOffset; + w = getMeasurement(xml->findAttr("w"), 0); + h = getMeasurement(xml->findAttr("h"), 0); + if ((attr = xml->findAttr("rotate"))) { + rot = atoi(attr->getValue()->getCString()); + if ((rot %= 360) < 0) { + rot += 360; + } + } else { + rot = 0; + } + + // 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 = x - anchorX * w; + y2 = y - anchorY * h; + w2 = w; + h2 = h; + break; + case 90: + x2 = x - anchorY * h; + y2 = y - (1 - anchorX) * w; + w2 = h; + h2 = w; + break; + case 180: + x2 = x - (1 - anchorX) * w; + y2 = y - (1 - anchorY) * h; + w2 = w; + h2 = h; + break; + case 270: + x2 = x - (1 - anchorY) * h; + y2 = y - anchorX * w; + w2 = h; + h2 = w; + break; + } + + // convert annot rect to PDF coords (LL corner, width, height), + // taking page rotation into account + switch (pageRot) { + case 0: + default: + x3 = pageRect->x1 + x2; + y3 = pageRect->y2 - (y2 + h2); + w3 = w2; + h3 = h2; + break; + case 90: + x3 = pageRect->x1 + y2; + y3 = pageRect->y1 + x2; + w3 = h2; + h3 = w2; + break; + case 180: + x3 = pageRect->x2 - (x2 + w2); + y3 = pageRect->y1 + y2; + w3 = w2; + h3 = h2; + break; + case 270: + x3 = pageRect->x2 - (y2 + h2); + y3 = pageRect->y1 + (x2 + w2); + w3 = h2; + h3 = w2; + break; + } + rot3 = (rot + pageRot) % 360; + + // 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] = h; mat[5] = 0; + break; + case 180: + mat[0] = -1; mat[1] = 0; + mat[2] = 0; mat[3] = -1; + mat[4] = w; mat[5] = h; + break; + case 270: + mat[0] = 0; mat[1] = -1; + mat[2] = 1; mat[3] = 0; + mat[4] = 0; mat[5] = w; + break; + } + + // get the appearance stream data + appearBuf = new GString(); +#if 0 //~ for debugging + appearBuf->appendf("q 1 1 0 rg 0 0 {0:.4f} {1:.4f} re f Q\n", w, h); +#endif + if ((uiElem = xml->findFirstChildElement("ui"))) { + for (node = uiElem->getFirstChild(); node; node = node->getNextChild()) { + if (node->isElement("textEdit")) { + drawTextEdit(fontDict, w, h, rot3, appearBuf); + break; + } else if (node->isElement("barcode")) { + drawBarCode(fontDict, w, h, rot3, appearBuf); + break; + } + //~ other field types go here + } + } + + // 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(w)); + obj1.arrayAdd(obj2.initReal(h)); + 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); + if (xfaForm->resourceDict.isDict()) { + appearDict.dictAdd(copyString("Resources"), + xfaForm->resourceDict.copy(&obj1)); + } + appearStream = new MemStream(appearBuf->getCString(), 0, + appearBuf->getLength(), &appearDict); + appearance.initStream(appearStream); + gfx->drawAnnot(&appearance, NULL, x3, y3, x3 + w3, y3 + h3); + appearance.free(); + delete appearBuf; +} + +void XFAFormField::drawTextEdit(GfxFontDict *fontDict, + double w, double h, int rot, + GString *appearBuf) { + ZxElement *valueElem, *textElem, *uiElem, *textEditElem, *combElem; + ZxElement *fontElem, *paraElem; + ZxAttr *attr; + GString *value, *fontName; + double fontSize; + int maxChars, combCells; + GBool multiLine, bold, italic; + XFAHorizAlign hAlign; + XFAVertAlign vAlign; + + if (!(value = getFieldValue("text"))) { + return; + } + + 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 = xml->findFirstChildElement("ui")) && + (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(value, multiLine, combCells, + fontName, bold, italic, fontSize, + hAlign, vAlign, 0, 0, w, h, gFalse, fontDict, appearBuf); + delete fontName; +} + +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, fontSize; + double yText, wText, yBarcode, hBarcode, wNarrow, xx; + GBool doText; + int dataLength; + GBool bold, italic; + char *p; + int i, j, c; + + //--- get field value + if (!(value = getFieldValue("text"))) { + return; + } + + //--- get field attributes + barcodeType = NULL; + wideNarrowRatio = 3; + dataLength = 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, 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("dataLength"))) { + dataLength = atoi(attr->getValue()->getCString()); + } + if ((attr = barcodeElem->findAttr("textLocation"))) { + textLocation = attr->getValue(); + } + } + if (!barcodeType) { + error(errSyntaxError, -1, "Missing 'type' attribute in XFA barcode field"); + return; + } + if (!dataLength) { + error(errSyntaxError, -1, + "Missing 'dataLength' attribute in XFA barcode field"); + 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"); + } + + //--- 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 + //~ this may depend on barcode type + value2 = value->copy(); + 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")) { + 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 { + error(errSyntaxError, -1, + "Unimplemented barcode type in XFA barcode field"); + } + //~ 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); + } + delete fontName; + delete value2; +} + +Object *XFAFormField::getResources(Object *res) { + return xfaForm->resourceDict.copy(res); +} + +double XFAFormField::getMeasurement(ZxAttr *attr, double defaultVal) { + GString *s; + double val, mul; + GBool neg; + int i; + + if (!attr) { + return defaultVal; + } + s = attr->getValue(); + i = 0; + 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, *elem; + char *p; + + // check the 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(); + } + } + + // check the packet + if (!xfaForm->xml->getRoot() || + !(datasets = + xfaForm->xml->getRoot()->findFirstChildElement("xfa:datasets")) || + !(data = datasets->findFirstChildElement("xfa:data"))) { + return NULL; + } + p = name->getCString(); + if (!strncmp(p, "form.", 5)) { + p += 5; + } else { + return NULL; + } + elem = findFieldData(data, p); + if (elem && + elem->getFirstChild() && + elem->getFirstChild()->isCharData() && + ((ZxCharData *)elem->getFirstChild())->getData()->getLength() > 0) { + return ((ZxCharData *)elem->getFirstChild())->getData(); + } + + return NULL; +} + +ZxElement *XFAFormField::findFieldData(ZxElement *elem, char *partName) { + ZxNode *node; + GString *nodeName; + 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] == '.') { + return findFieldData((ZxElement *)node, partName + n + 1); + } + } + } + } + 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) { + GfxFont *font; + GString *s; + double xx, yy, tw, charWidth, lineHeight; + double rectX, rectY, rectW, rectH; + int line, i, j, k, c, rectI; + + //~ deal with Unicode text (is it UTF-8?) + + // find the font + if (!(font = findFont(fontDict, fontName, bold, italic))) { + 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"); + return; + } + + // setup + rectW = rectH = 0; + rectI = appearBuf->getLength(); + appearBuf->append("BT\n"); + appearBuf->appendf("/{0:t} {1:.2f} Tf\n", font->getTag(), fontSize); + + // multi-line text + if (multiLine) { + + // figure out how many lines will fit + lineHeight = 1.2 * fontSize; + + // write a series of lines of text + line = 0; + i = 0; + while (i < text->getLength()) { + + getNextLine(text, 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 = y + h - fontSize * font->getAscent() - line * lineHeight; + + // draw the line + appearBuf->appendf("1 0 0 1 {0:.4f} {1:.4f} Tm\n", xx, yy); + appearBuf->append('('); + for (; i < j; ++i) { + c = text->getChar(i) & 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append(c); + } + } + appearBuf->append(") Tj\n"); + + // next line + i = k; + ++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 - text->getLength())) * tw; + break; + case xfaHAlignRight: + xx = x + w - text->getLength() * tw; + break; + } + rectW = text->getLength() * tw; + switch (vAlign) { + case xfaVAlignTop: + default: + yy = y + h - fontSize * font->getAscent(); + break; + case xfaVAlignMiddle: + yy = y + 0.5 * (h - fontSize * (font->getAscent() + + font->getDescent())); + break; + case xfaVAlignBottom: + yy = y - fontSize * font->getDescent(); + break; + } + rectY = yy + fontSize * font->getDescent(); + rectH = fontSize * (font->getAscent() - font->getDescent()); + + // write the text string + for (i = 0; i < text->getLength(); ++i) { + c = text->getChar(i) & 0xff; + if (!font->isCIDFont()) { + charWidth = fontSize * ((Gfx8BitFont *)font)->getWidth(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(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("{0:.4f} 0 Td\n", w); + } else { + appearBuf->append(c); + } + appearBuf->append(") Tj\n"); + } + + // regular (non-comb) formatting + } else { + + // compute string width + if (!font->isCIDFont()) { + tw = 0; + for (i = 0; i < text->getLength(); ++i) { + tw += ((Gfx8BitFont *)font)->getWidth(text->getChar(i)); + } + } else { + // otherwise, make a crude estimate + tw = text->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 - fontSize * font->getAscent(); + break; + case xfaVAlignMiddle: + yy = y + 0.5 * (h - fontSize * (font->getAscent() + + font->getDescent())); + break; + case xfaVAlignBottom: + yy = y - fontSize * font->getDescent(); + break; + } + rectY = yy + fontSize * font->getDescent(); + rectH = fontSize * (font->getAscent() - font->getDescent()); + appearBuf->appendf("{0:.4f} {1:.4f} Td\n", xx, yy); + + // write the text string + appearBuf->append('('); + for (i = 0; i < text->getLength(); ++i) { + c = text->getChar(i) & 0xff; + if (c == '(' || c == ')' || c == '\\') { + appearBuf->append('\\'); + appearBuf->append(c); + } else if (c < 0x20 || c >= 0x80) { + appearBuf->appendf("\\{0:03o}", c); + } else { + appearBuf->append(c); + } + } + appearBuf->append(") Tj\n"); + } + + // cleanup + appearBuf->append("ET\n"); + + // 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; + 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; + } +} + +// Searches for a font matching(, , +// ). +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; +} + +// 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(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; +} diff -uNrp xpdf-3.03/xpdf/XFAForm.h xpdf-3.04/xpdf/XFAForm.h --- xpdf-3.03/xpdf/XFAForm.h 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/XFAForm.h 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,126 @@ +//======================================================================== +// +// XFAForm.h +// +// Copyright 2012 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef XFAFORM_H +#define XFAFORM_H + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include "Form.h" + +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, 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, ZxDoc *xmlA, Object *resourceDictA, GBool fullXFAA); + void scanFields(ZxElement *elem, GString *name, GString *dataName); + + ZxDoc *xml; + GList *fields; // [XFAFormField] + Object resourceDict; + GBool fullXFA; // true for "Full XFA", false for + // "XFA Foreground" + 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 *dataNameA, int pageNumA, + double xOffsetA, double yOffsetA); + + virtual ~XFAFormField(); + + virtual const char *getType(); + virtual Unicode *getName(int *length); + virtual Unicode *getValue(int *length); + + virtual Object *getResources(Object *res); + +private: + + Unicode *utf8ToUnicode(GString *s, int *length); + void draw(int pageNumA, Gfx *gfx, GBool printing, GfxFontDict *fontDict); + void drawTextEdit(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); + GString *getFieldValue(const char *valueChildType); + ZxElement *findFieldData(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); + void getNextLine(GString *text, int start, + GfxFont *font, double fontSize, double wMax, + int *end, double *width, int *next); + + XFAForm *xfaForm; + ZxElement *xml; + GString *name; + GString *dataName; + int pageNum; + double xOffset, yOffset; + + friend class XFAForm; +}; + +#endif diff -uNrp xpdf-3.03/xpdf/xpdf.cc xpdf-3.04/xpdf/xpdf.cc --- xpdf-3.03/xpdf/xpdf.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/xpdf.cc 2014-05-28 20:50:50.000000000 +0200 @@ -22,7 +22,6 @@ //------------------------------------------------------------------------ static GBool contView = gFalse; -static char enableT1libStr[16] = ""; static char enableFreeTypeStr[16] = ""; static char antialiasStr[16] = ""; static char vectorAntialiasStr[16] = ""; @@ -66,10 +65,6 @@ static ArgDesc argDesc[] = { "initial zoom level (percent, 'page', 'width')"}, {"-cont", argFlag, &contView, 0, "start in continuous view mode" }, -#if HAVE_T1LIB_H - {"-t1lib", argString, enableT1libStr, sizeof(enableT1libStr), - "enable t1lib font rasterizer: yes, no"}, -#endif #if HAVE_FREETYPE_FREETYPE_H | HAVE_FREETYPE_H {"-freetype", argString, enableFreeTypeStr, sizeof(enableFreeTypeStr), "enable FreeType font rasterizer: yes, no"}, @@ -185,11 +180,6 @@ int main(int argc, char *argv[]) { fprintf(stderr, "Bad '-eol' value on command line\n"); } } - if (enableT1libStr[0]) { - if (!globalParams->setEnableT1lib(enableT1libStr)) { - fprintf(stderr, "Bad '-t1lib' value on command line\n"); - } - } if (enableFreeTypeStr[0]) { if (!globalParams->setEnableFreeType(enableFreeTypeStr)) { fprintf(stderr, "Bad '-freetype' value on command line\n"); diff -uNrp xpdf-3.03/xpdf/XPDFCore.cc xpdf-3.04/xpdf/XPDFCore.cc --- xpdf-3.03/xpdf/XPDFCore.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/XPDFCore.cc 2014-05-28 20:50:50.000000000 +0200 @@ -493,7 +493,7 @@ void XPDFCore::doAction(LinkAction *acti } s = ((LinkGoToR *)action)->getFileName()->getCString(); //~ translate path name for VMS (deal with '/') - if (isAbsolutePath(s)) { + if (isAbsolutePath(s) || !doc->getFileName()) { fileName = new GString(s); } else { fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s); @@ -531,7 +531,7 @@ void XPDFCore::doAction(LinkAction *acti if (!strcmp(s + fileName->getLength() - 4, ".pdf") || !strcmp(s + fileName->getLength() - 4, ".PDF")) { //~ translate path name for VMS (deal with '/') - if (isAbsolutePath(s)) { + if (isAbsolutePath(s) || !doc->getFileName()) { fileName = fileName->copy(); } else { fileName = appendToPath(grabPath(doc->getFileName()->getCString()), s); @@ -640,7 +640,8 @@ void XPDFCore::doAction(LinkAction *acti if (movieAnnot.dictLookup("Movie", &obj1)->isDict()) { if (obj1.dictLookup("F", &obj2)) { if ((fileName = LinkAction::getFileSpecName(&obj2))) { - if (!isAbsolutePath(fileName->getCString())) { + if (!isAbsolutePath(fileName->getCString()) && + doc->getFileName()) { fileName2 = appendToPath( grabPath(doc->getFileName()->getCString()), fileName->getCString()); @@ -658,6 +659,13 @@ void XPDFCore::doAction(LinkAction *acti movieAnnot.free(); break; + // unsupported action types + case actionJavaScript: + case actionSubmitForm: + case actionHide: + error(errSyntaxError, -1, "Unsupported link action type"); + break; + // unknown action type case actionUnknown: error(errSyntaxError, -1, "Unknown link action type: '{0:t}'", @@ -1124,6 +1132,9 @@ void XPDFCore::inputCbk(Widget widget, X case actionMovie: s = "[movie]"; break; + case actionJavaScript: + case actionSubmitForm: + case actionHide: case actionUnknown: s = "[unknown link]"; break; @@ -1356,6 +1367,8 @@ void XPDFCore::redrawRect(PDFCoreTile *t XFillRectangle(display, drawAreaWin, drawAreaGC, xDest, yDest, width, height); } + + XFlush(display); } void XPDFCore::updateScrollbars() { diff -uNrp xpdf-3.03/xpdf/XPDFViewer.cc xpdf-3.04/xpdf/XPDFViewer.cc --- xpdf-3.03/xpdf/XPDFViewer.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/XPDFViewer.cc 2014-05-28 20:50:50.000000000 +0200 @@ -165,6 +165,7 @@ XPDFViewerCmd XPDFViewer::cmdTab[] = { { "about", 0, gFalse, gFalse, &XPDFViewer::cmdAbout }, { "closeOutline", 0, gFalse, gFalse, &XPDFViewer::cmdCloseOutline }, { "closeWindow", 0, gFalse, gFalse, &XPDFViewer::cmdCloseWindow }, + { "closeWindowOrQuit", 0, gFalse, gFalse, &XPDFViewer::cmdCloseWindowOrQuit }, { "continuousMode", 0, gFalse, gFalse, &XPDFViewer::cmdContinuousMode }, { "endPan", 0, gTrue, gTrue, &XPDFViewer::cmdEndPan }, { "endSelection", 0, gTrue, gTrue, &XPDFViewer::cmdEndSelection }, @@ -384,7 +385,9 @@ void XPDFViewer::open(GString *fileName, int pg; double z; - if (!core->getDoc() || fileName->cmp(core->getDoc()->getFileName())) { + if (!core->getDoc() || + !core->getDoc()->getFileName() || + fileName->cmp(core->getDoc()->getFileName())) { if (!loadFile(fileName, NULL, NULL)) { return; } @@ -445,7 +448,7 @@ GBool XPDFViewer::loadFile(GString *file void XPDFViewer::reloadFile() { int pg; - if (!core->getDoc()) { + if (!core->getDoc() || !core->getDoc()->getFileName()) { return; } pg = core->getPageNum(); @@ -808,6 +811,11 @@ void XPDFViewer::cmdCloseWindow(GString app->close(this, gFalse); } +void XPDFViewer::cmdCloseWindowOrQuit(GString *args[], int nArgs, + XEvent *event) { + app->close(this, gTrue); +} + void XPDFViewer::cmdContinuousMode(GString *args[], int nArgs, XEvent *event) { Widget btn; @@ -1803,7 +1811,7 @@ void XPDFViewer::initToolbar(Widget pare menuPane = XmCreatePulldownMenu(toolBar, "zoomMenuPane", args, n); for (i = 0; i < nZoomMenuItems; ++i) { n = 0; - s = XmStringCreateLocalized(zoomMenuInfo[i].label); + s = XmStringCreateLocalized((char *)zoomMenuInfo[i].label); XtSetArg(args[n], XmNlabelString, s); ++n; XtSetArg(args[n], XmNuserData, (XtPointer)i); ++n; sprintf(buf, "zoom%d", i); @@ -3422,17 +3430,18 @@ void XPDFViewer::setupPrintDialog() { doc = core->getDoc(); psFileName = globalParams->getPSFile(); if (!psFileName || psFileName->getChar(0) == '|') { - pdfFileName = doc->getFileName(); - p = pdfFileName->getCString() + pdfFileName->getLength() - 4; - if (!strcmp(p, ".pdf") || !strcmp(p, ".PDF")) { - psFileName2 = new GString(pdfFileName->getCString(), - pdfFileName->getLength() - 4); - } else { - psFileName2 = pdfFileName->copy(); + if ((pdfFileName = doc->getFileName())) { + p = pdfFileName->getCString() + pdfFileName->getLength() - 4; + if (!strcmp(p, ".pdf") || !strcmp(p, ".PDF")) { + psFileName2 = new GString(pdfFileName->getCString(), + pdfFileName->getLength() - 4); + } else { + psFileName2 = pdfFileName->copy(); + } + psFileName2->append(".ps"); + XmTextFieldSetString(printFileText, psFileName2->getCString()); + delete psFileName2; } - psFileName2->append(".ps"); - XmTextFieldSetString(printFileText, psFileName2->getCString()); - delete psFileName2; } if (psFileName && psFileName->getChar(0) == '|') { XmToggleButtonSetState(printWithCmdBtn, True, False); diff -uNrp xpdf-3.03/xpdf/XPDFViewer.h xpdf-3.04/xpdf/XPDFViewer.h --- xpdf-3.03/xpdf/XPDFViewer.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/XPDFViewer.h 2014-05-28 20:50:50.000000000 +0200 @@ -103,6 +103,7 @@ private: void cmdAbout(GString *args[], int nArgs, XEvent *event); void cmdCloseOutline(GString *args[], int nArgs, XEvent *event); void cmdCloseWindow(GString *args[], int nArgs, XEvent *event); + void cmdCloseWindowOrQuit(GString *args[], int nArgs, XEvent *event); void cmdContinuousMode(GString *args[], int nArgs, XEvent *event); void cmdEndPan(GString *args[], int nArgs, XEvent *event); void cmdEndSelection(GString *args[], int nArgs, XEvent *event); diff -uNrp xpdf-3.03/xpdf/XRef.cc xpdf-3.04/xpdf/XRef.cc --- xpdf-3.03/xpdf/XRef.cc 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/XRef.cc 2014-05-28 20:50:50.000000000 +0200 @@ -18,6 +18,7 @@ #include #include #include "gmem.h" +#include "gfile.h" #include "Object.h" #include "Stream.h" #include "Lexer.h" @@ -43,6 +44,84 @@ #define defPermFlags 0xfffc //------------------------------------------------------------------------ +// XRefPosSet +//------------------------------------------------------------------------ + +class XRefPosSet { +public: + + XRefPosSet(); + ~XRefPosSet(); + void add(GFileOffset pos); + GBool check(GFileOffset pos); + +private: + + int find(GFileOffset pos); + + GFileOffset *tab; + int size; + int len; +}; + +XRefPosSet::XRefPosSet() { + size = 16; + len = 0; + tab = (GFileOffset *)gmallocn(size, sizeof(GFileOffset)); +} + +XRefPosSet::~XRefPosSet() { + gfree(tab); +} + +void XRefPosSet::add(GFileOffset pos) { + int i; + + i = find(pos); + if (i < len && tab[i] == pos) { + return; + } + if (len == size) { + if (size > INT_MAX / 2) { + gMemError("Integer overflow in XRefPosSet::add()"); + } + size *= 2; + tab = (GFileOffset *)greallocn(tab, size, sizeof(GFileOffset)); + } + if (i < len) { + memmove(&tab[i + 1], &tab[i], (len - i) * sizeof(GFileOffset)); + } + tab[i] = pos; + ++len; +} + +GBool XRefPosSet::check(GFileOffset pos) { + int i; + + i = find(pos); + return i < len && tab[i] == pos; +} + +int XRefPosSet::find(GFileOffset pos) { + int a, b, m; + + a = - 1; + b = len; + // invariant: tab[a] < pos < tab[b] + while (b - a > 1) { + m = (a + b) / 2; + if (tab[m] < pos) { + a = m; + } else if (tab[m] > pos) { + b = m; + } else { + return m; + } + } + return b; +} + +//------------------------------------------------------------------------ // ObjectStream //------------------------------------------------------------------------ @@ -134,7 +213,7 @@ ObjectStream::ObjectStream(XRef *xref, i obj2.free(); delete parser; gfree(offsets); - goto err1; + goto err2; } objNums[i] = obj1.getInt(); offsets[i] = obj2.getInt(); @@ -144,7 +223,7 @@ ObjectStream::ObjectStream(XRef *xref, i (i > 0 && offsets[i] < offsets[i-1])) { delete parser; gfree(offsets); - goto err1; + goto err2; } } while (str->getChar() != EOF) ; @@ -153,8 +232,8 @@ ObjectStream::ObjectStream(XRef *xref, i // skip to the first object - this shouldn't be necessary because // the First key is supposed to be equal to offsets[0], but just in // case... - for (i = first; i < offsets[0]; ++i) { - objStr.getStream()->getChar(); + if (i < offsets[0]) { + objStr.getStream()->discardChars(offsets[0] - i); } // parse the objects @@ -175,6 +254,8 @@ ObjectStream::ObjectStream(XRef *xref, i gfree(offsets); ok = gTrue; + err2: + objStr.streamClose(); err1: objStr.free(); } @@ -203,8 +284,10 @@ Object *ObjectStream::getObject(int objI //------------------------------------------------------------------------ XRef::XRef(BaseStream *strA, GBool repair) { - Guint pos; + GFileOffset pos; Object obj; + XRefPosSet *posSet; + int i; ok = gTrue; errCode = errNone; @@ -213,12 +296,18 @@ XRef::XRef(BaseStream *strA, GBool repai entries = NULL; streamEnds = NULL; streamEndsLen = 0; - objStr = NULL; + for (i = 0; i < objStrCacheSize; ++i) { + objStrs[i] = NULL; + } encrypted = gFalse; permFlags = defPermFlags; ownerPasswordOk = gFalse; + for (i = 0; i < xrefCacheSize; ++i) { + cache[i].num = -1; + } + str = strA; start = str->getStart(); @@ -241,7 +330,9 @@ XRef::XRef(BaseStream *strA, GBool repai } // read the xref table - while (readXRef(&pos)) ; + posSet = new XRefPosSet(); + while (readXRef(&pos, posSet)) ; + delete posSet; if (!ok) { errCode = errDamaged; return; @@ -268,30 +359,34 @@ XRef::XRef(BaseStream *strA, GBool repai } XRef::~XRef() { + int i; + + for (i = 0; i < xrefCacheSize; ++i) { + if (cache[i].num >= 0) { + cache[i].obj.free(); + } + } gfree(entries); trailerDict.free(); if (streamEnds) { gfree(streamEnds); } - if (objStr) { - delete objStr; + for (i = 0; i < objStrCacheSize; ++i) { + if (objStrs[i]) { + delete objStrs[i]; + } } } // Read the 'startxref' position. -Guint XRef::getStartXref() { +GFileOffset XRef::getStartXref() { char buf[xrefSearchSize+1]; char *p; - int c, n, i; + int n, i; // read last xrefSearchSize bytes str->setPos(xrefSearchSize, -1); - for (n = 0; n < xrefSearchSize; ++n) { - if ((c = str->getChar()) == EOF) { - break; - } - buf[n] = c; - } + n = str->getBlock(buf, xrefSearchSize); buf[n] = '\0'; // find startxref @@ -304,86 +399,123 @@ Guint XRef::getStartXref() { return 0; } for (p = &buf[i+9]; isspace(*p & 0xff); ++p) ; - lastXRefPos = strToUnsigned(p); + lastXRefPos = strToFileOffset(p); return lastXRefPos; } // Read one xref table section. Also reads the associated trailer // dictionary, and returns the prev pointer (if any). -GBool XRef::readXRef(Guint *pos) { +GBool XRef::readXRef(GFileOffset *pos, XRefPosSet *posSet) { Parser *parser; Object obj; GBool more; + char buf[100]; + int n, i; - // start up a parser, parse one token - obj.initNull(); - parser = new Parser(NULL, - new Lexer(NULL, - str->makeSubStream(start + *pos, gFalse, 0, &obj)), - gTrue); - parser->getObj(&obj, gTrue); + // the xref data should either be "xref ..." (for an xref table) or + // "nn gg obj << ... >> stream ..." (for an xref stream); possibly + // preceded by whitespace + str->setPos(start + *pos); + n = str->getBlock(buf, 100); + for (i = 0; i < n && Lexer::isSpace(buf[i]); ++i) ; // parse an old-style xref table - if (obj.isCmd("xref")) { - obj.free(); - more = readXRefTable(parser, pos); + if (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 (obj.isInt()) { + } else if (i < n && buf[i] >= '0' && buf[i] <= '9') { + obj.initNull(); + parser = new Parser(NULL, + new Lexer(NULL, + str->makeSubStream(start + *pos, gFalse, 0, &obj)), + gTrue); + if (!parser->getObj(&obj, gTrue)->isInt()) { + goto err2; + } obj.free(); if (!parser->getObj(&obj, gTrue)->isInt()) { - goto err1; + goto err2; } obj.free(); if (!parser->getObj(&obj, gTrue)->isCmd("obj")) { - goto err1; + goto err2; } obj.free(); if (!parser->getObj(&obj)->isStream()) { - goto err1; + goto err2; } more = readXRefStream(obj.getStream(), pos); obj.free(); + delete parser; } else { goto err1; } - delete parser; return more; - err1: + err2: obj.free(); delete parser; + err1: ok = gFalse; return gFalse; } -GBool XRef::readXRefTable(Parser *parser, Guint *pos) { +GBool XRef::readXRefTable(GFileOffset *pos, int offset, XRefPosSet *posSet) { XRefEntry entry; - GBool more; + Parser *parser; Object obj, obj2; - Guint pos2; - int first, n, newSize, i; + char buf[6]; + GFileOffset off, pos2; + GBool more; + int first, n, newSize, gen, i, c; + + if (posSet->check(*pos)) { + error(errSyntaxWarning, -1, "Infinite loop in xref table"); + return gFalse; + } + posSet->add(*pos); + + str->setPos(start + *pos + offset); while (1) { - parser->getObj(&obj, gTrue); - if (obj.isCmd("trailer")) { - obj.free(); + do { + c = str->getChar(); + } while (Lexer::isSpace(c)); + if (c == 't') { + if (str->getBlock(buf, 6) != 6 || memcmp(buf, "railer", 6)) { + goto err1; + } break; } - if (!obj.isInt()) { + if (c < '0' || c > '9') { goto err1; } - first = obj.getInt(); - obj.free(); - if (!parser->getObj(&obj, gTrue)->isInt()) { + first = 0; + do { + first = (first * 10) + (c - '0'); + c = str->getChar(); + } while (c >= '0' && c <= '9'); + if (!Lexer::isSpace(c)) { goto err1; } - n = obj.getInt(); - obj.free(); - if (first < 0 || n < 0 || first + n < 0) { + do { + c = str->getChar(); + } while (Lexer::isSpace(c)); + n = 0; + do { + n = (n * 10) + (c - '0'); + c = str->getChar(); + } while (c >= '0' && c <= '9'); + if (!Lexer::isSpace(c)) { + goto err1; + } + if (first < 0 || n < 0 || first > INT_MAX - n) { goto err1; } if (first + n > size) { @@ -395,32 +527,51 @@ GBool XRef::readXRefTable(Parser *parser } entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); for (i = size; i < newSize; ++i) { - entries[i].offset = 0xffffffff; + entries[i].offset = (GFileOffset)-1; entries[i].type = xrefEntryFree; } size = newSize; } for (i = first; i < first + n; ++i) { - if (!parser->getObj(&obj, gTrue)->isInt()) { + do { + c = str->getChar(); + } while (Lexer::isSpace(c)); + off = 0; + do { + off = (off * 10) + (c - '0'); + c = str->getChar(); + } while (c >= '0' && c <= '9'); + if (!Lexer::isSpace(c)) { goto err1; } - entry.offset = (Guint)obj.getInt(); - obj.free(); - if (!parser->getObj(&obj, gTrue)->isInt()) { + entry.offset = off; + do { + c = str->getChar(); + } while (Lexer::isSpace(c)); + gen = 0; + do { + gen = (gen * 10) + (c - '0'); + c = str->getChar(); + } while (c >= '0' && c <= '9'); + if (!Lexer::isSpace(c)) { goto err1; } - entry.gen = obj.getInt(); - obj.free(); - parser->getObj(&obj, gTrue); - if (obj.isCmd("n")) { + entry.gen = gen; + do { + c = str->getChar(); + } while (Lexer::isSpace(c)); + if (c == 'n') { entry.type = xrefEntryUncompressed; - } else if (obj.isCmd("f")) { + } else if (c == 'f') { entry.type = xrefEntryFree; } else { goto err1; } - obj.free(); - if (entries[i].offset == 0xffffffff) { + c = str->getChar(); + if (!Lexer::isSpace(c)) { + goto err1; + } + if (entries[i].offset == (GFileOffset)-1) { entries[i] = entry; // PDF files of patents from the IBM Intellectual Property // Network have a bug: the xref table claims to start at 1 @@ -430,7 +581,7 @@ GBool XRef::readXRefTable(Parser *parser entries[1].type == xrefEntryFree) { i = first = 0; entries[0] = entries[1]; - entries[1].offset = 0xffffffff; + entries[1].offset = (GFileOffset)-1; } if (i > last) { last = i; @@ -440,32 +591,29 @@ GBool XRef::readXRefTable(Parser *parser } // read the trailer dictionary - if (!parser->getObj(&obj)->isDict()) { + obj.initNull(); + parser = new Parser(NULL, + new Lexer(NULL, + str->makeSubStream(str->getPos(), gFalse, 0, &obj)), + gTrue); + parser->getObj(&obj); + delete parser; + if (!obj.isDict()) { + obj.free(); goto err1; } // get the 'Prev' pointer + //~ this can be a 64-bit int (?) obj.getDict()->lookupNF("Prev", &obj2); if (obj2.isInt()) { - pos2 = (Guint)obj2.getInt(); - if (pos2 != *pos) { - *pos = pos2; - more = gTrue; - } else { - error(errSyntaxWarning, -1, "Infinite loop in xref table"); - more = gFalse; - } + *pos = (GFileOffset)(Guint)obj2.getInt(); + more = gTrue; } else if (obj2.isRef()) { // certain buggy PDF generators generate "/Prev NNN 0 R" instead // of "/Prev NNN" - pos2 = (Guint)obj2.getRefNum(); - if (pos2 != *pos) { - *pos = pos2; - more = gTrue; - } else { - error(errSyntaxWarning, -1, "Infinite loop in xref table"); - more = gFalse; - } + *pos = (GFileOffset)(Guint)obj2.getRefNum(); + more = gTrue; } else { more = gFalse; } @@ -477,9 +625,10 @@ GBool XRef::readXRefTable(Parser *parser } // check for an 'XRefStm' key + //~ this can be a 64-bit int (?) if (obj.getDict()->lookup("XRefStm", &obj2)->isInt()) { - pos2 = (Guint)obj2.getInt(); - readXRef(&pos2); + pos2 = (GFileOffset)(Guint)obj2.getInt(); + readXRef(&pos2, posSet); if (!ok) { obj2.free(); goto err1; @@ -491,12 +640,11 @@ GBool XRef::readXRefTable(Parser *parser return more; err1: - obj.free(); ok = gFalse; return gFalse; } -GBool XRef::readXRefStream(Stream *xrefStr, Guint *pos) { +GBool XRef::readXRefStream(Stream *xrefStr, GFileOffset *pos) { Dict *dict; int w[3]; GBool more; @@ -516,7 +664,7 @@ GBool XRef::readXRefStream(Stream *xrefS if (newSize > size) { entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); for (i = size; i < newSize; ++i) { - entries[i].offset = 0xffffffff; + entries[i].offset = (GFileOffset)-1; entries[i].type = xrefEntryFree; } size = newSize; @@ -533,11 +681,13 @@ GBool XRef::readXRefStream(Stream *xrefS } w[i] = obj2.getInt(); obj2.free(); - if (w[i] < 0 || w[i] > 4) { - goto err1; - } } obj.free(); + if (w[0] < 0 || w[0] > 4 || + w[1] < 0 || w[1] > (int)sizeof(GFileOffset) || + w[2] < 0 || w[2] > 4) { + goto err0; + } xrefStr->reset(); dict->lookupNF("Index", &idx); @@ -569,9 +719,10 @@ GBool XRef::readXRefStream(Stream *xrefS } idx.free(); + //~ this can be a 64-bit int (?) dict->lookupNF("Prev", &obj); if (obj.isInt()) { - *pos = (Guint)obj.getInt(); + *pos = (GFileOffset)(Guint)obj.getInt(); more = gTrue; } else { more = gFalse; @@ -591,7 +742,7 @@ GBool XRef::readXRefStream(Stream *xrefS } GBool XRef::readXRefStreamSection(Stream *xrefStr, int *w, int first, int n) { - Guint offset; + GFileOffset offset; int type, gen, c, newSize, i, j; if (first + n < 0) { @@ -606,7 +757,7 @@ GBool XRef::readXRefStreamSection(Stream } entries = (XRefEntry *)greallocn(entries, newSize, sizeof(XRefEntry)); for (i = size; i < newSize; ++i) { - entries[i].offset = 0xffffffff; + entries[i].offset = (GFileOffset)-1; entries[i].type = xrefEntryFree; } size = newSize; @@ -634,7 +785,7 @@ GBool XRef::readXRefStreamSection(Stream } gen = (gen << 8) + c; } - if (entries[i].offset == 0xffffffff) { + if (entries[i].offset == (GFileOffset)-1) { switch (type) { case 0: entries[i].offset = offset; @@ -668,7 +819,7 @@ GBool XRef::constructXRef() { Parser *parser; Object newTrailerDict, obj; char buf[256]; - Guint pos; + GFileOffset pos; int num, gen; int newSize; int streamEndsSize; @@ -748,7 +899,7 @@ GBool XRef::constructXRef() { entries = (XRefEntry *) greallocn(entries, newSize, sizeof(XRefEntry)); for (i = size; i < newSize; ++i) { - entries[i].offset = 0xffffffff; + entries[i].offset = (GFileOffset)-1; entries[i].type = xrefEntryFree; } size = newSize; @@ -771,15 +922,16 @@ GBool XRef::constructXRef() { } else if (!strncmp(p, "endstream", 9)) { if (streamEndsLen == streamEndsSize) { streamEndsSize += 64; - streamEnds = (Guint *)greallocn(streamEnds, - streamEndsSize, sizeof(Guint)); + streamEnds = (GFileOffset *)greallocn(streamEnds, streamEndsSize, + sizeof(GFileOffset)); } streamEnds[streamEndsLen++] = pos; } } - if (gotRoot) + if (gotRoot) { return gTrue; + } error(errSyntaxError, -1, "Couldn't find trailer dictionary"); return gFalse; @@ -824,13 +976,31 @@ GBool XRef::okToAddNotes(GBool ignoreOwn Object *XRef::fetch(int num, int gen, Object *obj, int recursion) { XRefEntry *e; Parser *parser; + ObjectStream *objStr; Object obj1, obj2, obj3; + XRefCacheEntry tmp; + int i, j; // check for bogus ref - this can happen in corrupted PDF files if (num < 0 || num >= size) { goto err; } + // check the cache + if (cache[0].num == num && cache[0].gen == gen) { + return cache[0].obj.copy(obj); + } + for (i = 1; i < xrefCacheSize; ++i) { + if (cache[i].num == num && cache[i].gen == gen) { + tmp = cache[i]; + for (j = i; j > 0; --j) { + cache[j] = cache[j - 1]; + } + cache[0] = tmp; + return cache[0].obj.copy(obj); + } + } + e = &entries[num]; switch (e->type) { @@ -869,21 +1039,13 @@ Object *XRef::fetch(int num, int gen, Ob goto err; } #endif - if (e->offset >= (Guint)size || + if (e->offset >= (GFileOffset)size || entries[e->offset].type != xrefEntryUncompressed) { error(errSyntaxError, -1, "Invalid object stream"); goto err; } - if (!objStr || objStr->getObjStrNum() != (int)e->offset) { - if (objStr) { - delete objStr; - } - objStr = new ObjectStream(this, e->offset); - if (!objStr->isOk()) { - delete objStr; - objStr = NULL; - goto err; - } + if (!(objStr = getObjectStream((int)e->offset))) { + goto err; } objStr->getObject(e->gen, num, obj); break; @@ -892,12 +1054,61 @@ Object *XRef::fetch(int num, int gen, Ob goto err; } + // put the new object in the cache, throwing away the oldest object + // currently in the cache + if (cache[xrefCacheSize - 1].num >= 0) { + cache[xrefCacheSize - 1].obj.free(); + } + for (i = xrefCacheSize - 1; i > 0; --i) { + cache[i] = cache[i - 1]; + } + cache[0].num = num; + cache[0].gen = gen; + obj->copy(&cache[0].obj); + return obj; err: return obj->initNull(); } +ObjectStream *XRef::getObjectStream(int objStrNum) { + ObjectStream *objStr; + int i, j; + + // check the MRU entry in the cache + if (objStrs[0] && objStrs[0]->getObjStrNum() == objStrNum) { + return objStrs[0]; + } + + // check the rest of the cache + for (i = 1; i < objStrCacheSize; ++i) { + if (objStrs[i] && objStrs[i]->getObjStrNum() == objStrNum) { + objStr = objStrs[i]; + for (j = i; j > 0; --j) { + objStrs[j] = objStrs[j - 1]; + } + objStrs[0] = objStr; + return objStr; + } + } + + // load a new ObjectStream + objStr = new ObjectStream(this, objStrNum); + if (!objStr->isOk()) { + delete objStr; + return NULL; + } + if (objStrs[objStrCacheSize - 1]) { + delete objStrs[objStrCacheSize - 1]; + } + for (j = objStrCacheSize - 1; j > 0; --j) { + objStrs[j] = objStrs[j - 1]; + } + objStrs[0] = objStr; + return objStr; +} + Object *XRef::getDocInfo(Object *obj) { return trailerDict.dictLookup("Info", obj); } @@ -907,7 +1118,7 @@ Object *XRef::getDocInfoNF(Object *obj) return trailerDict.dictLookupNF("Info", obj); } -GBool XRef::getStreamEnd(Guint streamStart, Guint *streamEnd) { +GBool XRef::getStreamEnd(GFileOffset streamStart, GFileOffset *streamEnd) { int a, b, m; if (streamEndsLen == 0 || @@ -930,14 +1141,14 @@ GBool XRef::getStreamEnd(Guint streamSta return gTrue; } -Guint XRef::strToUnsigned(char *s) { - Guint x, d; +GFileOffset XRef::strToFileOffset(char *s) { + GFileOffset x, d; char *p; x = 0; for (p = s; *p && isdigit(*p & 0xff); ++p) { d = *p - '0'; - if (x > (UINT_MAX - d) / 10) { + if (x > (GFILEOFFSET_MAX - d) / 10) { break; } x = 10 * x + d; diff -uNrp xpdf-3.03/xpdf/XRef.h xpdf-3.04/xpdf/XRef.h --- xpdf-3.03/xpdf/XRef.h 2011-08-15 23:08:53.000000000 +0200 +++ xpdf-3.04/xpdf/XRef.h 2014-05-28 20:50:50.000000000 +0200 @@ -16,12 +16,14 @@ #endif #include "gtypes.h" +#include "gfile.h" #include "Object.h" class Dict; class Stream; class Parser; class ObjectStream; +class XRefPosSet; //------------------------------------------------------------------------ // XRef @@ -34,11 +36,21 @@ enum XRefEntryType { }; struct XRefEntry { - Guint offset; + GFileOffset offset; int gen; XRefEntryType type; }; +struct XRefCacheEntry { + int num; + int gen; + Object obj; +}; + +#define xrefCacheSize 16 + +#define objStrCacheSize 4 + class XRef { public: @@ -83,7 +95,7 @@ public: int getNumObjects() { return last + 1; } // Return the offset of the last xref table. - Guint getLastXRefPos() { return lastXRefPos; } + GFileOffset getLastXRefPos() { return lastXRefPos; } // Return the catalog object reference. int getRootNum() { return rootNum; } @@ -91,7 +103,7 @@ public: // Get end position for a stream in a damaged file. // Returns false if unknown or file is not damaged. - GBool getStreamEnd(Guint streamStart, Guint *streamEnd); + GBool getStreamEnd(GFileOffset streamStart, GFileOffset *streamEnd); // Direct access. int getSize() { return size; } @@ -101,7 +113,7 @@ public: private: BaseStream *str; // input stream - Guint start; // offset in file (to allow for garbage + GFileOffset start; // offset in file (to allow for garbage // at beginning of file) XRefEntry *entries; // xref entries int size; // size of array @@ -110,11 +122,12 @@ private: GBool ok; // true if xref table is valid int errCode; // error code (if is false) Object trailerDict; // trailer dictionary - Guint lastXRefPos; // offset of last xref table - Guint *streamEnds; // 'endstream' positions - only used in + GFileOffset lastXRefPos; // offset of last xref table + GFileOffset *streamEnds; // 'endstream' positions - only used in // damaged files int streamEndsLen; // number of valid entries in streamEnds - ObjectStream *objStr; // cached object stream + ObjectStream * // cached object streams + objStrs[objStrCacheSize]; GBool encrypted; // true if file is encrypted int permFlags; // permission bits GBool ownerPasswordOk; // true if owner password is correct @@ -122,14 +135,17 @@ private: int keyLength; // length of key, in bytes int encVersion; // encryption version CryptAlgorithm encAlgorithm; // encryption algorithm + XRefCacheEntry // cache of recently accessed objects + cache[xrefCacheSize]; - Guint getStartXref(); - GBool readXRef(Guint *pos); - GBool readXRefTable(Parser *parser, Guint *pos); + GFileOffset getStartXref(); + GBool readXRef(GFileOffset *pos, XRefPosSet *posSet); + GBool readXRefTable(GFileOffset *pos, int offset, XRefPosSet *posSet); GBool readXRefStreamSection(Stream *xrefStr, int *w, int first, int n); - GBool readXRefStream(Stream *xrefStr, Guint *pos); + GBool readXRefStream(Stream *xrefStr, GFileOffset *pos); GBool constructXRef(); - Guint strToUnsigned(char *s); + ObjectStream *getObjectStream(int objStrNum); + GFileOffset strToFileOffset(char *s); }; #endif diff -uNrp xpdf-3.03/xpdf/Zoox.cc xpdf-3.04/xpdf/Zoox.cc --- xpdf-3.03/xpdf/Zoox.cc 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/Zoox.cc 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,839 @@ +//======================================================================== +// +// Zoox.cc +// +//======================================================================== + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include +#include +#include +#include "gmem.h" +#include "GString.h" +#include "GList.h" +#include "GHash.h" +#include "Zoox.h" + +//~ all of this code assumes the encoding is UTF-8 or ASCII or something +//~ similar (ISO-8859-*) + +//------------------------------------------------------------------------ + +static char nameStartChar[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, // 30 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 50 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 70 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 90 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // a0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // b0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // c0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // d0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // e0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // f0 +}; + +static char nameChar[256] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, // 20 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 30 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 50 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 70 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 90 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // a0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // b0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // c0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // d0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // e0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // f0 +}; + +//------------------------------------------------------------------------ + +ZxNode::ZxNode() { + next = NULL; + parent = NULL; + firstChild = lastChild = NULL; +} + +ZxNode::~ZxNode() { + ZxNode *child; + + while (firstChild) { + child = firstChild; + firstChild = firstChild->next; + delete child; + } +} + +ZxElement *ZxNode::findFirstElement(const char *type) { + ZxNode *child; + ZxElement *result; + + if (isElement(type)) { + return (ZxElement *)this; + } + for (child = firstChild; child; child = child->next) { + if ((result = child->findFirstElement(type))) { + return result; + } + } + return NULL; +} + +ZxElement *ZxNode::findFirstChildElement(const char *type) { + ZxNode *child; + + for (child = firstChild; child; child = child->next) { + if (child->isElement(type)) { + return (ZxElement *)child; + } + } + return NULL; +} + +GList *ZxNode::findAllElements(const char *type) { + GList *results; + + results = new GList(); + findAllElements(type, results); + return results; +} + +void ZxNode::findAllElements(const char *type, GList *results) { + ZxNode *child; + + if (isElement(type)) { + results->append(this); + } + for (child = firstChild; child; child = child->next) { + child->findAllElements(type, results); + } +} + +GList *ZxNode::findAllChildElements(const char *type) { + GList *results; + ZxNode *child; + + results = new GList(); + for (child = firstChild; child; child = child->next) { + if (child->isElement(type)) { + results->append(child); + } + } + return results; +} + +void ZxNode::addChild(ZxNode *child) { + if (lastChild) { + lastChild->next = child; + lastChild = child; + } else { + firstChild = lastChild = child; + } + child->parent = this; + child->next = NULL; +} + +//------------------------------------------------------------------------ + +ZxDoc::ZxDoc() { + xmlDecl = NULL; + docTypeDecl = NULL; + root = NULL; +} + +ZxDoc *ZxDoc::loadMem(const char *data, Guint dataLen) { + ZxDoc *doc; + + doc = new ZxDoc(); + if (!doc->parse(data, dataLen)) { + delete doc; + return NULL; + } + return doc; +} + +ZxDoc *ZxDoc::loadFile(const char *fileName) { + ZxDoc *doc; + FILE *f; + char *data; + Guint dataLen; + + if (!(f = fopen(fileName, "rb"))) { + return NULL; + } + fseek(f, 0, SEEK_END); + dataLen = (Guint)ftell(f); + if (!dataLen) { + fclose(f); + return NULL; + } + fseek(f, 0, SEEK_SET); + data = (char *)gmalloc(dataLen); + if (fread(data, 1, dataLen, f) != dataLen) { + fclose(f); + gfree(data); + return NULL; + } + fclose(f); + doc = loadMem(data, dataLen); + gfree(data); + return doc; +} + +ZxDoc::~ZxDoc() { +} + +void ZxDoc::addChild(ZxNode *node) { + if (node->isXMLDecl() && !xmlDecl) { + xmlDecl = (ZxXMLDecl *)node; + } else if (node->isDocTypeDecl() && !docTypeDecl) { + docTypeDecl = (ZxDocTypeDecl *)node; + } else if (node->isElement() && !root) { + root = (ZxElement *)node; + } + ZxNode::addChild(node); +} + +bool ZxDoc::parse(const char *data, Guint dataLen) { + parsePtr = data; + parseEnd = data + dataLen; + + parseSpace(); + parseXMLDecl(this); + parseMisc(this); + parseDocTypeDecl(this); + parseMisc(this); + if (match("<")) { + parseElement(this); + } + parseMisc(this); + return root != NULL; +} + +void ZxDoc::parseXMLDecl(ZxNode *par) { + GString *version, *encoding, *s; + bool standalone; + + if (!match("cmp("yes"); + delete s; + } + } + parseSpace(); + + if (match("?>")) { + parsePtr += 2; + } + + par->addChild(new ZxXMLDecl(version, encoding, standalone)); +} + +//~ this just skips everything after the name +void ZxDoc::parseDocTypeDecl(ZxNode *par) { + GString *name; + int state; + char c, quote; + + if (!match("') { + state = 4; + } else if (c == '"' || c == '\'') { + state = 1; + } else if (c == '[') { + state = 2; + } + break; + case 1: // not in square brackets; in quotes + if (c == quote) { + state = 0; + } + break; + case 2: // in square brackets; not in quotes + if (c == ']') { + state = 0; + } else if (c == '"' || c == '\'') { + state = 3; + } + break; + case 3: // in square brackets; in quotes + if (c == quote) { + state = 2; + } + break; + } + } + + par->addChild(new ZxDocTypeDecl(name)); +} + +// assumes match("<") +void ZxDoc::parseElement(ZxNode *par) { + GString *type; + ZxElement *elem; + ZxAttr *attr; + + ++parsePtr; + type = parseName(); + elem = new ZxElement(type); + parseSpace(); + while ((attr = parseAttr())) { + elem->addAttr(attr); + parseSpace(); + } + if (match("/>")) { + parsePtr += 2; + } else if (match(">")) { + ++parsePtr; + parseContent(elem); + } + par->addChild(elem); +} + +ZxAttr *ZxDoc::parseAttr() { + GString *name, *value; + const char *start; + char quote, c; + int x, n; + + name = parseName(); + parseSpace(); + if (!match("=")) { + delete name; + return NULL; + } + ++parsePtr; + parseSpace(); + if (!(parsePtr < parseEnd && (*parsePtr == '"' || *parsePtr == '\''))) { + delete name; + return NULL; + } + quote = *parsePtr++; + value = new GString(); + while (parsePtr < parseEnd && *parsePtr != quote) { + if (*parsePtr == '&') { + ++parsePtr; + if (parsePtr < parseEnd && *parsePtr == '#') { + ++parsePtr; + if (parsePtr < parseEnd && *parsePtr == 'x') { + ++parsePtr; + x = 0; + while (parsePtr < parseEnd) { + c = *parsePtr; + if (c >= '0' && c <= '9') { + x = (x << 4) + (c - '0'); + } else if (c >= 'a' && c <= 'f') { + x = (x << 4) + (10 + c - 'a'); + } else if (c >= 'A' && c <= 'F') { + x = (x << 4) + (10 + c - 'A'); + } else { + break; + } + ++parsePtr; + } + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + appendUTF8(value, x); + } else { + x = 0; + while (parsePtr < parseEnd) { + c = *parsePtr; + if (c >= '0' && c <= '9') { + x = x * 10 + (c - '0'); + } else { + break; + } + ++parsePtr; + } + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + appendUTF8(value, x); + } + } else { + start = parsePtr; + for (++parsePtr; + parsePtr < parseEnd && *parsePtr != ';' && + *parsePtr != quote && *parsePtr != '&'; + ++parsePtr) ; + n = (int)(parsePtr - start); + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + if (n == 2 && !strncmp(start, "lt", 2)) { + value->append('<'); + } else if (n == 2 && !strncmp(start, "gt", 2)) { + value->append('>'); + } else if (n == 3 && !strncmp(start, "amp", 3)) { + value->append('&'); + } else if (n == 4 && !strncmp(start, "apos", 4)) { + value->append('\''); + } else if (n == 4 && !strncmp(start, "quot", 4)) { + value->append('"'); + } else { + value->append(start - 1, (int)(parsePtr - start) + 1); + } + } + } else { + start = parsePtr; + for (++parsePtr; + parsePtr < parseEnd && *parsePtr != quote && *parsePtr != '&'; + ++parsePtr) ; + value->append(start, (int)(parsePtr - start)); + } + } + if (parsePtr < parseEnd && *parsePtr == quote) { + ++parsePtr; + } + return new ZxAttr(name, value); +} + +// this consumes the end tag +void ZxDoc::parseContent(ZxElement *par) { + GString *endType; + + endType = (new GString("append(par->getType()); + + while (parsePtr < parseEnd) { + if (match(endType->getCString())) { + parsePtr += endType->getLength(); + parseSpace(); + if (match(">")) { + ++parsePtr; + } + break; + } else if (match("= '0' && c <= '9') { + x = (x << 4) + (c - '0'); + } else if (c >= 'a' && c <= 'f') { + x = (x << 4) + (10 + c - 'a'); + } else if (c >= 'A' && c <= 'F') { + x = (x << 4) + (10 + c - 'A'); + } else { + break; + } + ++parsePtr; + } + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + appendUTF8(data, x); + } else { + x = 0; + while (parsePtr < parseEnd) { + c = *parsePtr; + if (c >= '0' && c <= '9') { + x = x * 10 + (c - '0'); + } else { + break; + } + ++parsePtr; + } + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + appendUTF8(data, x); + } + } else { + start = parsePtr; + for (++parsePtr; + parsePtr < parseEnd && *parsePtr != ';' && + *parsePtr != '<' && *parsePtr != '&'; + ++parsePtr) ; + n = (int)(parsePtr - start); + if (parsePtr < parseEnd && *parsePtr == ';') { + ++parsePtr; + } + if (n == 2 && !strncmp(start, "lt", 2)) { + data->append('<'); + } else if (n == 2 && !strncmp(start, "gt", 2)) { + data->append('>'); + } else if (n == 3 && !strncmp(start, "amp", 3)) { + data->append('&'); + } else if (n == 4 && !strncmp(start, "apos", 4)) { + data->append('\''); + } else if (n == 4 && !strncmp(start, "quot", 4)) { + data->append('"'); + } else { + data->append(start - 1, (int)(parsePtr - start) + 1); + } + } + } else { + start = parsePtr; + for (++parsePtr; + parsePtr < parseEnd && *parsePtr != '<' && *parsePtr != '&'; + ++parsePtr) ; + data->append(start, (int)(parsePtr - start)); + } + } + par->addChild(new ZxCharData(data, true)); +} + +void ZxDoc::appendUTF8(GString *s, int c) { + if (c <= 0x7f) { + s->append((char)c); + } else if (c <= 0x7ff) { + s->append((char)(0xc0 + (c >> 6))); + s->append((char)(0x80 + (c & 0x3f))); + } else if (c <= 0xffff) { + s->append((char)(0xe0 + (c >> 12))); + s->append((char)(0x80 + ((c >> 6) & 0x3f))); + s->append((char)(0x80 + (c & 0x3f))); + } else if (c <= 0x1fffff) { + s->append((char)(0xf0 + (c >> 18))); + s->append((char)(0x80 + ((c >> 12) & 0x3f))); + s->append((char)(0x80 + ((c >> 6) & 0x3f))); + s->append((char)(0x80 + (c & 0x3f))); + } else if (c <= 0x3ffffff) { + s->append((char)(0xf8 + (c >> 24))); + s->append((char)(0x80 + ((c >> 18) & 0x3f))); + s->append((char)(0x80 + ((c >> 12) & 0x3f))); + s->append((char)(0x80 + ((c >> 6) & 0x3f))); + s->append((char)(0x80 + (c & 0x3f))); + } else if (c <= 0x7fffffff) { + s->append((char)(0xfc + (c >> 30))); + s->append((char)(0x80 + ((c >> 24) & 0x3f))); + s->append((char)(0x80 + ((c >> 18) & 0x3f))); + s->append((char)(0x80 + ((c >> 12) & 0x3f))); + s->append((char)(0x80 + ((c >> 6) & 0x3f))); + s->append((char)(0x80 + (c & 0x3f))); + } +} + +// assumes match("", 3)) { + par->addChild(new ZxCharData(new GString(start, (int)(parsePtr - start)), + false)); + parsePtr += 3; + return; + } + ++parsePtr; + } + parsePtr = parseEnd; + par->addChild(new ZxCharData(new GString(start, (int)(parsePtr - start)), + false)); +} + +void ZxDoc::parseMisc(ZxNode *par) { + while (1) { + if (match("", 3)) { + par->addChild(new ZxComment(new GString(start, (int)(parsePtr - start)))); + parsePtr += 3; + return; + } + ++parsePtr; + } + parsePtr = parseEnd; +} + +// assumes match("", 2)) { + par->addChild(new ZxPI(target, new GString(start, + (int)(parsePtr - start)))); + parsePtr += 2; + return; + } + ++parsePtr; + } + parsePtr = parseEnd; + par->addChild(new ZxPI(target, new GString(start, (int)(parsePtr - start)))); +} + +//~ this accepts all chars >= 0x80 +//~ this doesn't check for properly-formed UTF-8 +GString *ZxDoc::parseName() { + GString *name; + + name = new GString(); + if (parsePtr < parseEnd && nameStartChar[*parsePtr & 0xff]) { + name->append(*parsePtr++); + while (parsePtr < parseEnd && nameChar[*parsePtr & 0xff]) { + name->append(*parsePtr++); + } + } + return name; +} + +GString *ZxDoc::parseQuotedString() { + GString *s; + const char *start; + char quote; + + if (parsePtr < parseEnd && (*parsePtr == '"' || *parsePtr == '\'')) { + quote = *parsePtr++; + start = parsePtr; + while (parsePtr < parseEnd && *parsePtr != quote) { + ++parsePtr; + } + s = new GString(start, (int)(parsePtr - start)); + if (parsePtr < parseEnd && *parsePtr == quote) { + ++parsePtr; + } + } else { + s = new GString(); + } + return s; +} + +void ZxDoc::parseSpace() { + while (parsePtr < parseEnd && (*parsePtr == '\x20' || + *parsePtr == '\x09' || + *parsePtr == '\x0d' || + *parsePtr == '\x0a')) { + ++parsePtr; + } +} + +bool ZxDoc::match(const char *s) { + int n; + + n = (int)strlen(s); + return parseEnd - parsePtr >= n && !strncmp(parsePtr, s, n); +} + +//------------------------------------------------------------------------ + +ZxXMLDecl::ZxXMLDecl(GString *versionA, GString *encodingA, bool standaloneA) { + version = versionA; + encoding = encodingA; + standalone = standaloneA; +} + +ZxXMLDecl::~ZxXMLDecl() { + delete version; + if (encoding) { + delete encoding; + } +} + +//------------------------------------------------------------------------ + +ZxDocTypeDecl::ZxDocTypeDecl(GString *nameA) { + name = nameA; +} + +ZxDocTypeDecl::~ZxDocTypeDecl() { + delete name; +} + +//------------------------------------------------------------------------ + +ZxComment::ZxComment(GString *textA) { + text = textA; +} + +ZxComment::~ZxComment() { + delete text; +} + +//------------------------------------------------------------------------ + +ZxPI::ZxPI(GString *targetA, GString *textA) { + target = targetA; + text = textA; +} + +ZxPI::~ZxPI() { + delete target; + delete text; +} + +//------------------------------------------------------------------------ + +ZxElement::ZxElement(GString *typeA) { + type = typeA; + attrs = new GHash(); + firstAttr = lastAttr = NULL; +} + +ZxElement::~ZxElement() { + delete type; + deleteGHash(attrs, ZxAttr); +} + +bool ZxElement::isElement(const char *typeA) { + return !type->cmp(typeA); +} + +ZxAttr *ZxElement::findAttr(const char *attrName) { + return (ZxAttr *)attrs->lookup(attrName); +} + +void ZxElement::addAttr(ZxAttr *attr) { + attrs->add(attr->getName(), attr); + if (lastAttr) { + lastAttr->next = attr; + lastAttr= attr; + } else { + firstAttr = lastAttr = attr; + } + attr->parent = this; + attr->next = NULL; +} + +//------------------------------------------------------------------------ + +ZxAttr::ZxAttr(GString *nameA, GString *valueA) { + name = nameA; + value = valueA; + parent = NULL; + next = NULL; +} + +ZxAttr::~ZxAttr() { + delete name; + delete value; +} + +//------------------------------------------------------------------------ + +ZxCharData::ZxCharData(GString *dataA, bool parsedA) { + data = dataA; + parsed = parsedA; +} + +ZxCharData::~ZxCharData() { + delete data; +} diff -uNrp xpdf-3.03/xpdf/Zoox.h xpdf-3.04/xpdf/Zoox.h --- xpdf-3.03/xpdf/Zoox.h 1970-01-01 01:00:00.000000000 +0100 +++ xpdf-3.04/xpdf/Zoox.h 2014-05-28 20:50:50.000000000 +0200 @@ -0,0 +1,241 @@ +//======================================================================== +// +// Zoox.h +// +//======================================================================== + +#ifndef ZOOX_H +#define ZOOX_H + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include "gtypes.h" + +class GString; +class GList; +class GHash; + +class ZxAttr; +class ZxDocTypeDecl; +class ZxElement; +class ZxXMLDecl; + +//------------------------------------------------------------------------ + +class ZxNode { +public: + + ZxNode(); + virtual ~ZxNode(); + + virtual bool isDoc() { return false; } + virtual bool isXMLDecl() { return false; } + virtual bool isDocTypeDecl() { return false; } + virtual bool isComment() { return false; } + virtual bool isPI() { return false; } + virtual bool isElement() { return false; } + virtual bool isElement(const char *type) { return false; } + virtual bool isCharData() { return false; } + virtual ZxNode *getFirstChild() { return firstChild; } + virtual ZxNode *getNextChild() { return next; } + ZxElement *findFirstElement(const char *type); + ZxElement *findFirstChildElement(const char *type); + GList *findAllElements(const char *type); + GList *findAllChildElements(const char *type); + virtual void addChild(ZxNode *child); + +protected: + + void findAllElements(const char *type, GList *results); + + ZxNode *next; + ZxNode *parent; + ZxNode *firstChild, + *lastChild; +}; + +//------------------------------------------------------------------------ + +class ZxDoc: public ZxNode { +public: + + ZxDoc(); + + // Parse from memory. Returns NULL on error. + static ZxDoc *loadMem(const char *data, Guint dataLen); + + // Parse from disk. Returns NULL on error. + static ZxDoc *loadFile(const char *fileName); + + virtual ~ZxDoc(); + + virtual bool isDoc() { return true; } + ZxXMLDecl *getXMLDecl() { return xmlDecl; } + ZxDocTypeDecl *getDocTypeDecl() { return docTypeDecl; } + ZxElement *getRoot() { return root; } + virtual void addChild(ZxNode *node); + +private: + + bool parse(const char *data, Guint dataLen); + void parseXMLDecl(ZxNode *par); + void parseDocTypeDecl(ZxNode *par); + void parseElement(ZxNode *par); + ZxAttr *parseAttr(); + void parseContent(ZxElement *par); + void parseCharData(ZxElement *par); + void appendUTF8(GString *s, int c); + void parseCDSect(ZxNode *par); + void parseMisc(ZxNode *par); + void parseComment(ZxNode *par); + void parsePI(ZxNode *par); + GString *parseName(); + GString *parseQuotedString(); + void parseSpace(); + bool match(const char *s); + + ZxXMLDecl *xmlDecl; // may be NULL + ZxDocTypeDecl *docTypeDecl; // may be NULL + ZxElement *root; // may be NULL + + const char *parsePtr; + const char *parseEnd; +}; + +//------------------------------------------------------------------------ + +class ZxXMLDecl: public ZxNode { +public: + + ZxXMLDecl(GString *versionA, GString *encodingA, bool standaloneA); + virtual ~ZxXMLDecl(); + + virtual bool isXMLDecl() { return true; } + GString *getVersion() { return version; } + GString *getEncoding() { return encoding; } + bool getStandalone() { return standalone; } + +private: + + GString *version; + GString *encoding; // may be NULL + bool standalone; +}; + +//------------------------------------------------------------------------ + +class ZxDocTypeDecl: public ZxNode { +public: + + ZxDocTypeDecl(GString *nameA); + virtual ~ZxDocTypeDecl(); + + virtual bool isDocTypeDecl() { return true; } + GString *getName() { return name; } + +private: + + GString *name; +}; + +//------------------------------------------------------------------------ + +class ZxComment: public ZxNode { +public: + + ZxComment(GString *textA); + virtual ~ZxComment(); + + virtual bool isComment() { return true; } + GString *getText() { return text; } + +private: + + GString *text; +}; + +//------------------------------------------------------------------------ + +class ZxPI: public ZxNode { +public: + + ZxPI(GString *targetA, GString *textA); + virtual ~ZxPI(); + + virtual bool isPI() { return true; } + GString *getTarget() { return target; } + GString *getText() { return text; } + +private: + + GString *target; + GString *text; +}; + +//------------------------------------------------------------------------ + +class ZxElement: public ZxNode { +public: + + ZxElement(GString *typeA); + virtual ~ZxElement(); + + virtual bool isElement() { return true; } + virtual bool isElement(const char *typeA); + GString *getType() { return type; } + ZxAttr *findAttr(const char *attrName); + ZxAttr *getFirstAttr() { return firstAttr; } + void addAttr(ZxAttr *attr); + +private: + + GString *type; + GHash *attrs; // [ZxAttr] + ZxAttr *firstAttr, *lastAttr; +}; + +//------------------------------------------------------------------------ + +class ZxAttr { +public: + + ZxAttr(GString *nameA, GString *valueA); + ~ZxAttr(); + + GString *getName() { return name; } + GString *getValue() { return value; } + ZxAttr *getNextAttr() { return next; } + +private: + + GString *name; + GString *value; + ZxElement *parent; + ZxAttr *next; + + friend class ZxElement; +}; + +//------------------------------------------------------------------------ + +class ZxCharData: public ZxNode { +public: + + ZxCharData(GString *dataA, bool parsedA); + virtual ~ZxCharData(); + + virtual bool isCharData() { return true; } + GString *getData() { return data; } + bool isParsed() { return parsed; } + +private: + + GString *data; // in UTF-8 format + bool parsed; +}; + +#endif