From 0fcc0c3f6e6999ef90162d0ed2bdf62e9cfc3a33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Marjam=C3=A4ki?= Date: Sat, 2 May 2020 19:51:59 +0200 Subject: [PATCH] Add bug hunting test case for CVE-2019-10019 --- .../cve/CVE-2019-10019/PSOutputDev.cc | 8388 +++++++++++++++++ .../cve/CVE-2019-10019/PSOutputDev.h | 511 + 2 files changed, 8899 insertions(+) create mode 100644 test/bug-hunting/cve/CVE-2019-10019/PSOutputDev.cc create mode 100644 test/bug-hunting/cve/CVE-2019-10019/PSOutputDev.h diff --git a/test/bug-hunting/cve/CVE-2019-10019/PSOutputDev.cc b/test/bug-hunting/cve/CVE-2019-10019/PSOutputDev.cc new file mode 100644 index 000000000..f09f41d4d --- /dev/null +++ b/test/bug-hunting/cve/CVE-2019-10019/PSOutputDev.cc @@ -0,0 +1,8388 @@ +// Cppcheck bug hunting define: +#define HAVE_SPLASH 1 + +//======================================================================== +// +// PSOutputDev.cc +// +// Copyright 1996-2013 Glyph & Cog, LLC +// +//======================================================================== + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include +#include +#include +#include +#include +#include "gmempp.h" +#include "GString.h" +#include "GList.h" +#include "GHash.h" +#include "config.h" +#include "GlobalParams.h" +#include "Object.h" +#include "Error.h" +#include "Function.h" +#include "Gfx.h" +#include "GfxState.h" +#include "GfxFont.h" +#include "UnicodeMap.h" +#include "FoFiType1C.h" +#include "FoFiTrueType.h" +#include "Catalog.h" +#include "Page.h" +#include "Stream.h" +#include "Annot.h" +#include "PDFDoc.h" +#include "XRef.h" +#include "PreScanOutputDev.h" +#include "CharCodeToUnicode.h" +#include "Form.h" +#include "TextString.h" +#if HAVE_SPLASH +# include "Splash.h" +# include "SplashBitmap.h" +# include "SplashOutputDev.h" +#endif +#include "PSOutputDev.h" + +// the MSVC math.h doesn't define this +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +//------------------------------------------------------------------------ +// PostScript prolog and setup +//------------------------------------------------------------------------ + +// The '~' escapes mark prolog code that is emitted only in certain +// levels: +// +// ~[123][ngs] +// ^ ^----- n=psLevel_, g=psLevel_Gray, s=psLevel_Sep +// +----- 1=psLevel1__, 2=psLevel2__, 3=psLevel3__ + +static const char *prolog[] = { + "/xpdf 75 dict def xpdf begin", + "% PDF special state", + "/pdfDictSize 15 def", + "~1ns", + "/pdfStates 64 array def", + " 0 1 63 {", + " pdfStates exch pdfDictSize dict", + " dup /pdfStateIdx 3 index put", + " put", + " } for", + "~123ngs", + "/pdfSetup {", + " /pdfDuplex exch def", + " /setpagedevice where {", + " pop 2 dict begin", + " /Policies 1 dict dup begin /PageSize 6 def end def", + " pdfDuplex { /Duplex true def } if", + " currentdict end setpagedevice", + " } if", + " /pdfPageW 0 def", + " /pdfPageH 0 def", + "} def", + "/pdfSetupPaper {", + " 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", + " } ifelse", + "} def", + "~1ns", + "/pdfOpNames [", + " /pdfFill /pdfStroke /pdfLastFill /pdfLastStroke", + " /pdfTextMat /pdfFontSize /pdfCharSpacing /pdfTextRender", + " /pdfTextRise /pdfWordSpacing /pdfHorizScaling /pdfTextClipPath", + "] def", + "~123ngs", + "/pdfStartPage {", + "~1ns", + " pdfStates 0 get begin", + "~23ngs", + " pdfDictSize dict begin", + "~23n", + " /pdfFillCS [] def", + " /pdfFillXform {} def", + " /pdfStrokeCS [] def", + " /pdfStrokeXform {} def", + "~1n", + " /pdfFill 0 def", + " /pdfStroke 0 def", + "~1s", + " /pdfFill [0 0 0 1] def", + " /pdfStroke [0 0 0 1] def", + "~23g", + " /pdfFill 0 def", + " /pdfStroke 0 def", + "~23ns", + " /pdfFill [0] def", + " /pdfStroke [0] def", + " /pdfFillOP false def", + " /pdfStrokeOP false def", + "~123ngs", + " /pdfLastFill false def", + " /pdfLastStroke false def", + " /pdfTextMat [1 0 0 1 0 0] def", + " /pdfFontSize 0 def", + " /pdfCharSpacing 0 def", + " /pdfTextRender 0 def", + " /pdfTextRise 0 def", + " /pdfWordSpacing 0 def", + " /pdfHorizScaling 1 def", + " /pdfTextClipPath [] def", + "} def", + "/pdfEndPage { end } def", + "~23s", + "% separation convention operators", + "/findcmykcustomcolor where {", + " pop", + "}{", + " /findcmykcustomcolor { 5 array astore } def", + "} ifelse", + "/setcustomcolor where {", + " pop", + "}{", + " /setcustomcolor {", + " exch", + " [ exch /Separation exch dup 4 get exch /DeviceCMYK exch", + " 0 4 getinterval cvx", + " [ exch /dup load exch { mul exch dup } /forall load", + " /pop load dup ] cvx", + " ] setcolorspace setcolor", + " } def", + "} ifelse", + "/customcolorimage where {", + " pop", + "}{", + " /customcolorimage {", + " gsave", + " [ exch /Separation exch dup 4 get exch /DeviceCMYK exch", + " 0 4 getinterval", + " [ exch /dup load exch { mul exch dup } /forall load", + " /pop load dup ] cvx", + " ] setcolorspace", + " 10 dict begin", + " /ImageType 1 def", + " /DataSource exch def", + " /ImageMatrix exch def", + " /BitsPerComponent exch def", + " /Height exch def", + " /Width exch def", + " /Decode [1 0] def", + " currentdict end", + " image", + " grestore", + " } def", + "} ifelse", + "~123ngs", + "% PDF color state", + "~1n", + "/g { dup /pdfFill exch def setgray", + " /pdfLastFill true def /pdfLastStroke false def } def", + "/G { dup /pdfStroke exch def setgray", + " /pdfLastStroke true def /pdfLastFill false def } def", + "/fCol {", + " pdfLastFill not {", + " pdfFill setgray", + " /pdfLastFill true def /pdfLastStroke false def", + " } if", + "} def", + "/sCol {", + " pdfLastStroke not {", + " pdfStroke setgray", + " /pdfLastStroke true def /pdfLastFill false def", + " } if", + "} def", + "~1s", + "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor", + " /pdfLastFill true def /pdfLastStroke false def } def", + "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor", + " /pdfLastStroke true def /pdfLastFill false def } def", + "/fCol {", + " pdfLastFill not {", + " pdfFill aload pop setcmykcolor", + " /pdfLastFill true def /pdfLastStroke false def", + " } if", + "} def", + "/sCol {", + " pdfLastStroke not {", + " pdfStroke aload pop setcmykcolor", + " /pdfLastStroke true def /pdfLastFill false def", + " } if", + "} def", + "~23n", + "/cs { /pdfFillXform exch def dup /pdfFillCS exch def", + " setcolorspace } def", + "/CS { /pdfStrokeXform exch def dup /pdfStrokeCS exch def", + " setcolorspace } def", + "/sc { pdfLastFill not {", + " pdfFillCS setcolorspace pdfFillOP setoverprint", + " } if", + " dup /pdfFill exch def aload pop pdfFillXform setcolor", + " /pdfLastFill true def /pdfLastStroke false def } def", + "/SC { pdfLastStroke not {", + " pdfStrokeCS setcolorspace pdfStrokeOP setoverprint", + " } if", + " dup /pdfStroke exch def aload pop pdfStrokeXform setcolor", + " /pdfLastStroke true def /pdfLastFill false def } def", + "/op { /pdfFillOP exch def", + " pdfLastFill { pdfFillOP setoverprint } if } def", + "/OP { /pdfStrokeOP exch def", + " pdfLastStroke { pdfStrokeOP setoverprint } if } def", + "/fCol {", + " pdfLastFill not {", + " pdfFillCS setcolorspace", + " pdfFill aload pop pdfFillXform setcolor", + " pdfFillOP setoverprint", + " /pdfLastFill true def /pdfLastStroke false def", + " } if", + "} def", + "/sCol {", + " pdfLastStroke not {", + " pdfStrokeCS setcolorspace", + " pdfStroke aload pop pdfStrokeXform setcolor", + " pdfStrokeOP setoverprint", + " /pdfLastStroke true def /pdfLastFill false def", + " } if", + "} def", + "~23g", + "/g { dup /pdfFill exch def setgray", + " /pdfLastFill true def /pdfLastStroke false def } def", + "/G { dup /pdfStroke exch def setgray", + " /pdfLastStroke true def /pdfLastFill false def } def", + "/fCol {", + " pdfLastFill not {", + " pdfFill setgray", + " /pdfLastFill true def /pdfLastStroke false def", + " } if", + "} def", + "/sCol {", + " pdfLastStroke not {", + " pdfStroke setgray", + " /pdfLastStroke true def /pdfLastFill false def", + " } if", + "} def", + "~23s", + "/k { 4 copy 4 array astore /pdfFill exch def setcmykcolor", + " pdfFillOP setoverprint", + " /pdfLastFill true def /pdfLastStroke false def } def", + "/K { 4 copy 4 array astore /pdfStroke exch def setcmykcolor", + " pdfStrokeOP setoverprint", + " /pdfLastStroke true def /pdfLastFill false def } def", + "/ck { 6 copy 6 array astore /pdfFill exch def", + " findcmykcustomcolor exch setcustomcolor", + " pdfFillOP setoverprint", + " /pdfLastFill true def /pdfLastStroke false def } def", + "/CK { 6 copy 6 array astore /pdfStroke exch def", + " findcmykcustomcolor exch setcustomcolor", + " pdfStrokeOP setoverprint", + " /pdfLastStroke true def /pdfLastFill false def } def", + "/op { /pdfFillOP exch def", + " pdfLastFill { pdfFillOP setoverprint } if } def", + "/OP { /pdfStrokeOP exch def", + " pdfLastStroke { pdfStrokeOP setoverprint } if } def", + "/fCol {", + " pdfLastFill not {", + " pdfFill aload length 4 eq {", + " setcmykcolor", + " }{", + " findcmykcustomcolor exch setcustomcolor", + " } ifelse", + " pdfFillOP setoverprint", + " /pdfLastFill true def /pdfLastStroke false def", + " } if", + "} def", + "/sCol {", + " pdfLastStroke not {", + " pdfStroke aload length 4 eq {", + " setcmykcolor", + " }{", + " findcmykcustomcolor exch setcustomcolor", + " } ifelse", + " pdfStrokeOP setoverprint", + " /pdfLastStroke true def /pdfLastFill false def", + " } if", + "} def", + "~3ns", + "/opm {", + " /setoverprintmode where { pop setoverprintmode } { pop } ifelse", + "} def", + "~123ngs", + "% build a font", + "/pdfMakeFont {", + " 4 3 roll findfont", + " 4 2 roll matrix scale makefont", + " dup length dict begin", + " { 1 index /FID ne { def } { pop pop } ifelse } forall", + " /Encoding exch def", + " currentdict", + " end", + " definefont pop", + "} def", + "/pdfMakeFont16 {", + " exch findfont", + " dup length dict begin", + " { 1 index /FID ne { def } { pop pop } ifelse } forall", + " /WMode exch def", + " currentdict", + " end", + " definefont pop", + "} def", + "~3ngs", + "/pdfMakeFont16L3 {", + " 1 index /CIDFont resourcestatus {", + " pop pop 1 index /CIDFont findresource /CIDFontType known", + " } {", + " false", + " } ifelse", + " {", + " 0 eq { /Identity-H } { /Identity-V } ifelse", + " exch 1 array astore composefont pop", + " } {", + " pdfMakeFont16", + " } ifelse", + "} def", + "~123ngs", + "% graphics state operators", + "~1ns", + "/q {", + " gsave", + " pdfOpNames length 1 sub -1 0 { pdfOpNames exch get load } for", + " pdfStates pdfStateIdx 1 add get begin", + " pdfOpNames { exch def } forall", + "} def", + "/Q { end grestore } def", + "~23ngs", + "/q { gsave pdfDictSize dict begin } def", + "/Q {", + " end grestore", + "} def", + "~123ngs", + "/cm { concat } def", + "/d { setdash } def", + "/i { setflat } def", + "/j { setlinejoin } def", + "/J { setlinecap } def", + "/M { setmiterlimit } def", + "/w { setlinewidth } def", + "% path segment operators", + "/m { moveto } def", + "/l { lineto } def", + "/c { curveto } def", + "/re { 4 2 roll moveto 1 index 0 rlineto 0 exch rlineto", + " neg 0 rlineto closepath } def", + "/h { closepath } def", + "% path painting operators", + "/S { sCol stroke } def", + "/Sf { fCol stroke } def", + "/f { fCol fill } def", + "/f* { fCol eofill } def", + "% clipping operators", + "/W { clip newpath } def", + "/W* { eoclip newpath } def", + "/Ws { strokepath clip newpath } def", + "% text state operators", + "/Tc { /pdfCharSpacing exch def } def", + "/Tf { dup /pdfFontSize exch def", + " dup pdfHorizScaling mul exch matrix scale", + " pdfTextMat matrix concatmatrix dup 4 0 put dup 5 0 put", + " exch findfont exch makefont setfont } def", + "/Tr { /pdfTextRender exch def } def", + "/Ts { /pdfTextRise exch def } def", + "/Tw { /pdfWordSpacing exch def } def", + "/Tz { /pdfHorizScaling exch def } def", + "% text positioning operators", + "/Td { pdfTextMat transform moveto } def", + "/Tm { /pdfTextMat exch def } def", + "% text string operators", + "/xyshow where {", + " pop", + " /xyshow2 {", + " dup length array", + " 0 2 2 index length 1 sub {", + " 2 index 1 index 2 copy get 3 1 roll 1 add get", + " pdfTextMat dtransform", + " 4 2 roll 2 copy 6 5 roll put 1 add 3 1 roll dup 4 2 roll put", + " } for", + " exch pop", + " xyshow", + " } def", + "}{", + " /xyshow2 {", + " currentfont /FontType get 0 eq {", + " 0 2 3 index length 1 sub {", + " currentpoint 4 index 3 index 2 getinterval show moveto", + " 2 copy get 2 index 3 2 roll 1 add get", + " pdfTextMat dtransform rmoveto", + " } for", + " } {", + " 0 1 3 index length 1 sub {", + " currentpoint 4 index 3 index 1 getinterval show moveto", + " 2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get", + " pdfTextMat dtransform rmoveto", + " } for", + " } ifelse", + " pop pop", + " } def", + "} ifelse", + "/cshow where {", + " pop", + " /xycp {", // xycharpath + " 0 3 2 roll", + " {", + " pop pop currentpoint 3 2 roll", + " 1 string dup 0 4 3 roll put false charpath moveto", + " 2 copy get 2 index 2 index 1 add get", + " pdfTextMat dtransform rmoveto", + " 2 add", + " } exch cshow", + " pop pop", + " } def", + "}{", + " /xycp {", // xycharpath + " currentfont /FontType get 0 eq {", + " 0 2 3 index length 1 sub {", + " currentpoint 4 index 3 index 2 getinterval false charpath moveto", + " 2 copy get 2 index 3 2 roll 1 add get", + " pdfTextMat dtransform rmoveto", + " } for", + " } {", + " 0 1 3 index length 1 sub {", + " currentpoint 4 index 3 index 1 getinterval false charpath moveto", + " 2 copy 2 mul get 2 index 3 2 roll 2 mul 1 add get", + " pdfTextMat dtransform rmoveto", + " } for", + " } ifelse", + " pop pop", + " } def", + "} ifelse", + "/Tj {", + " fCol", // because stringwidth has to draw Type 3 chars + " 0 pdfTextRise pdfTextMat dtransform rmoveto", + " currentpoint 4 2 roll", + " pdfTextRender 1 and 0 eq {", + " 2 copy xyshow2", + " } if", + " pdfTextRender 3 and dup 1 eq exch 2 eq or {", + " 3 index 3 index moveto", + " 2 copy", + " currentfont /FontType get 3 eq { fCol } { sCol } ifelse", + " xycp currentpoint stroke moveto", + " } if", + " pdfTextRender 4 and 0 ne {", + " 4 2 roll moveto xycp", + " /pdfTextClipPath [ pdfTextClipPath aload pop", + " {/moveto cvx}", + " {/lineto cvx}", + " {/curveto cvx}", + " {/closepath cvx}", + " pathforall ] def", + " currentpoint newpath moveto", + " } {", + " pop pop pop pop", + " } ifelse", + " 0 pdfTextRise neg pdfTextMat dtransform rmoveto", + "} def", + "/Tj3 {", + " pdfTextRender 3 and 3 ne {" + " fCol", // because stringwidth has to draw Type 3 chars + " 0 pdfTextRise pdfTextMat dtransform rmoveto", + " xyshow2", + " 0 pdfTextRise neg pdfTextMat dtransform rmoveto", + " } {", + " pop pop", + " } ifelse", + "} def", + "/TJm { 0.001 mul pdfFontSize mul pdfHorizScaling mul neg 0", + " pdfTextMat dtransform rmoveto } def", + "/TJmV { 0.001 mul pdfFontSize mul neg 0 exch", + " pdfTextMat dtransform rmoveto } def", + "/Tclip { pdfTextClipPath cvx exec clip newpath", + " /pdfTextClipPath [] def } def", + "~1ns", + "% Level 1 image operators", + "~1n", + "/pdfIm1 {", + " /pdfImBuf1 4 index string def", + " { currentfile pdfImBuf1 readhexstring pop } image", + "} def", + "~1s", + "/pdfIm1Sep {", + " /pdfImBuf1 4 index string def", + " /pdfImBuf2 4 index string def", + " /pdfImBuf3 4 index string def", + " /pdfImBuf4 4 index string def", + " { currentfile pdfImBuf1 readhexstring pop }", + " { currentfile pdfImBuf2 readhexstring pop }", + " { currentfile pdfImBuf3 readhexstring pop }", + " { currentfile pdfImBuf4 readhexstring pop }", + " true 4 colorimage", + "} def", + "~1ns", + "/pdfImM1 {", + " fCol /pdfImBuf1 4 index 7 add 8 idiv string def", + " { currentfile pdfImBuf1 readhexstring pop } imagemask", + "} def", + "/pdfImStr {", + " 2 copy exch length lt {", + " 2 copy get exch 1 add exch", + " } {", + " ()", + " } ifelse", + "} def", + "/pdfImM1a {", + " { pdfImStr } imagemask", + " pop pop", + "} def", + "~23ngs", + "% Level 2/3 image operators", + "/pdfImBuf 100 string def", + "/pdfImStr {", + " 2 copy exch length lt {", + " 2 copy get exch 1 add exch", + " } {", + " ()", + " } ifelse", + "} def", + "/skipEOD {", + " { currentfile pdfImBuf readline", + " not { pop exit } if", + " (%-EOD-) eq { exit } if } loop", + "} def", + "/pdfIm { image skipEOD } def", + "~3ngs", + "/pdfMask {", + " /ReusableStreamDecode filter", + " skipEOD", + " /maskStream exch def", + "} def", + "/pdfMaskEnd { maskStream closefile } def", + "/pdfMaskInit {", + " /maskArray exch def", + " /maskIdx 0 def", + "} def", + "/pdfMaskSrc {", + " maskIdx maskArray length lt {", + " maskArray maskIdx get", + " /maskIdx maskIdx 1 add def", + " } {", + " ()", + " } ifelse", + "} def", + "~23s", + "/pdfImSep {", + " findcmykcustomcolor exch", + " dup /Width get /pdfImBuf1 exch string def", + " dup /Decode get aload pop 1 index sub /pdfImDecodeRange exch def", + " /pdfImDecodeLow exch def", + " begin Width Height BitsPerComponent ImageMatrix DataSource end", + " /pdfImData exch def", + " { pdfImData pdfImBuf1 readstring pop", + " 0 1 2 index length 1 sub {", + " 1 index exch 2 copy get", + " pdfImDecodeRange mul 255 div pdfImDecodeLow add round cvi", + " 255 exch sub put", + " } for }", + " 6 5 roll customcolorimage", + " skipEOD", + "} def", + "~23ngs", + "/pdfImM { fCol imagemask skipEOD } def", + "/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", + "~23ns", + "% shading operators", + "/colordelta {", + " false 0 1 3 index length 1 sub {", + " dup 4 index exch get 3 index 3 2 roll get sub abs 0.004 gt {", + " pop true", + " } if", + " } for", + " exch pop exch pop", + "} def", + "/funcCol { func n array astore } def", + "/funcSH {", + " dup 0 eq {", + " true", + " } {", + " dup 6 eq {", + " false", + " } {", + " 4 index 4 index funcCol dup", + " 6 index 4 index funcCol dup", + " 3 1 roll colordelta 3 1 roll", + " 5 index 5 index funcCol dup", + " 3 1 roll colordelta 3 1 roll", + " 6 index 8 index funcCol dup", + " 3 1 roll colordelta 3 1 roll", + " colordelta or or or", + " } ifelse", + " } ifelse", + " {", + " 1 add", + " 4 index 3 index add 0.5 mul exch 4 index 3 index add 0.5 mul exch", + " 6 index 6 index 4 index 4 index 4 index funcSH", + " 2 index 6 index 6 index 4 index 4 index funcSH", + " 6 index 2 index 4 index 6 index 4 index funcSH", + " 5 3 roll 3 2 roll funcSH pop pop", + " } {", + " pop 3 index 2 index add 0.5 mul 3 index 2 index add 0.5 mul", + "~23n", + " funcCol sc", + "~23s", + " funcCol aload pop k", + "~23ns", + " dup 4 index exch mat transform m", + " 3 index 3 index mat transform l", + " 1 index 3 index mat transform l", + " mat transform l pop pop h f*", + " } ifelse", + "} def", + "/axialCol {", + " dup 0 lt {", + " pop t0", + " } {", + " dup 1 gt {", + " pop t1", + " } {", + " dt mul t0 add", + " } ifelse", + " } ifelse", + " func n array astore", + "} def", + "/axialSH {", + " dup 2 lt {", + " true", + " } {", + " dup 8 eq {", + " false", + " } {", + " 2 index axialCol 2 index axialCol colordelta", + " } ifelse", + " } ifelse", + " {", + " 1 add 3 1 roll 2 copy add 0.5 mul", + " dup 4 3 roll exch 4 index axialSH", + " exch 3 2 roll axialSH", + " } {", + " pop 2 copy add 0.5 mul", + "~23n", + " axialCol sc", + "~23s", + " axialCol aload pop k", + "~23ns", + " exch dup dx mul x0 add exch dy mul y0 add", + " 3 2 roll dup dx mul x0 add exch dy mul y0 add", + " dx abs dy abs ge {", + " 2 copy yMin sub dy mul dx div add yMin m", + " yMax sub dy mul dx div add yMax l", + " 2 copy yMax sub dy mul dx div add yMax l", + " yMin sub dy mul dx div add yMin l", + " h f*", + " } {", + " exch 2 copy xMin sub dx mul dy div add xMin exch m", + " xMax sub dx mul dy div add xMax exch l", + " exch 2 copy xMax sub dx mul dy div add xMax exch l", + " xMin sub dx mul dy div add xMin exch l", + " h f*", + " } ifelse", + " } ifelse", + "} def", + "/radialCol {", + " dup t0 lt {", + " pop t0", + " } {", + " dup t1 gt {", + " pop t1", + " } if", + " } ifelse", + " func n array astore", + "} def", + "/radialSH {", + " dup 0 eq {", + " true", + " } {", + " dup 8 eq {", + " false", + " } {", + " 2 index dt mul t0 add radialCol", + " 2 index dt mul t0 add radialCol colordelta", + " } ifelse", + " } ifelse", + " {", + " 1 add 3 1 roll 2 copy add 0.5 mul", + " dup 4 3 roll exch 4 index radialSH", + " exch 3 2 roll radialSH", + " } {", + " pop 2 copy add 0.5 mul dt mul t0 add", + "~23n", + " radialCol sc", + "~23s", + " radialCol aload pop k", + "~23ns", + " encl {", + " exch dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", + " 0 360 arc h", + " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", + " 360 0 arcn h f", + " } {", + " 2 copy", + " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", + " a1 a2 arcn", + " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", + " a2 a1 arcn h", + " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", + " a1 a2 arc", + " dup dx mul x0 add exch dup dy mul y0 add exch dr mul r0 add", + " a2 a1 arc h f", + " } ifelse", + " } ifelse", + "} def", + "~123ngs", + "end", + 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", + " begincmap", + " /CMapType 1 def", + " /CMapName /Identity-H def", + " /CIDSystemInfo 3 dict dup begin", + " /Registry (Adobe) def", + " /Ordering (Identity) def", + " /Supplement 0 def", + " end def", + " 1 begincodespacerange", + " <0000> ", + " endcodespacerange", + " 0 usefont", + " 1 begincidrange", + " <0000> 0", + " endcidrange", + " endcmap", + " currentdict CMapName exch /CMap defineresource pop", + "end", + "10 dict begin", + " begincmap", + " /CMapType 1 def", + " /CMapName /Identity-V def", + " /CIDSystemInfo 3 dict dup begin", + " /Registry (Adobe) def", + " /Ordering (Identity) def", + " /Supplement 0 def", + " end def", + " /WMode 1 def", + " 1 begincodespacerange", + " <0000> ", + " endcodespacerange", + " 0 usefont", + " 1 begincidrange", + " <0000> 0", + " endcidrange", + " endcmap", + " currentdict CMapName exch /CMap defineresource pop", + "end", + "end", + NULL +}; + +//------------------------------------------------------------------------ +// Fonts +//------------------------------------------------------------------------ + +struct PSSubstFont { + const char *psName; // PostScript name + double mWidth; // width of 'm' character +}; + +// NB: must be in same order as base14SubstFonts in GfxFont.cc +static PSSubstFont psBase14SubstFonts[14] = { + {"Courier", 0.600}, + {"Courier-Oblique", 0.600}, + {"Courier-Bold", 0.600}, + {"Courier-BoldOblique", 0.600}, + {"Helvetica", 0.833}, + {"Helvetica-Oblique", 0.833}, + {"Helvetica-Bold", 0.889}, + {"Helvetica-BoldOblique", 0.889}, + {"Times-Roman", 0.788}, + {"Times-Italic", 0.722}, + {"Times-Bold", 0.833}, + {"Times-BoldItalic", 0.778}, + // the last two are never used for substitution + {"Symbol", 0}, + {"ZapfDingbats", 0} +}; + +class PSFontInfo { +public: + + PSFontInfo(Ref fontIDA) + { fontID = fontIDA; ff = NULL; } + + Ref fontID; + PSFontFileInfo *ff; // pointer to font file info; NULL indicates + // font mapping failed +}; + +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; + codeToGIDLen = 0; +} + +PSFontFileInfo::~PSFontFileInfo() { + delete psName; + if (extFileName) { + delete extFileName; + } + if (encoding) { + delete encoding; + } + if (codeToGID) { + gfree(codeToGID); + } +} + +//------------------------------------------------------------------------ +// process colors +//------------------------------------------------------------------------ + +#define psProcessCyan 1 +#define psProcessMagenta 2 +#define psProcessYellow 4 +#define psProcessBlack 8 +#define psProcessCMYK 15 + +//------------------------------------------------------------------------ +// PSOutCustomColor +//------------------------------------------------------------------------ + +class PSOutCustomColor { +public: + + PSOutCustomColor(double cA, double mA, + double yA, double kA, GString *nameA); + ~PSOutCustomColor(); + + double c, m, y, k; + GString *name; + PSOutCustomColor *next; +}; + +PSOutCustomColor::PSOutCustomColor(double cA, double mA, + double yA, double kA, GString *nameA) { + c = cA; + m = mA; + y = yA; + k = kA; + name = nameA; + next = NULL; +} + +PSOutCustomColor::~PSOutCustomColor() { + delete name; +} + +//------------------------------------------------------------------------ + +struct PSOutImgClipRect { + int x0, x1, y0, y1; +}; + +//------------------------------------------------------------------------ + +struct PSOutPaperSize { + PSOutPaperSize(int wA, int hA) { w = wA; h = hA; } + int w, h; +}; + +//------------------------------------------------------------------------ +// DeviceNRecoder +//------------------------------------------------------------------------ + +class DeviceNRecoder: public FilterStream { +public: + + DeviceNRecoder(Stream *strA, int widthA, int heightA, + GfxImageColorMap *colorMapA); + virtual ~DeviceNRecoder(); + virtual Stream *copy(); + virtual StreamKind getKind() { return strWeird; } + virtual void reset(); + virtual void close(); + virtual int getChar() + { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx++]; } + virtual int lookChar() + { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx]; } + virtual GString *getPSFilter(int psLevel, const char *indent) { return NULL; } + virtual GBool isBinary(GBool last = gTrue) { return gTrue; } + virtual GBool isEncoder() { return gTrue; } + +private: + + GBool fillBuf(); + + int width, height; + GfxImageColorMap *colorMap; + Function *func; + ImageStream *imgStr; + int buf[gfxColorMaxComps]; + int pixelIdx; + int bufIdx; + int bufSize; +}; + +DeviceNRecoder::DeviceNRecoder(Stream *strA, int widthA, int heightA, + GfxImageColorMap *colorMapA): + FilterStream(strA) { + width = widthA; + height = heightA; + colorMap = colorMapA; + imgStr = NULL; + pixelIdx = 0; + bufIdx = gfxColorMaxComps; + bufSize = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())-> + getAlt()->getNComps(); + func = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())-> + getTintTransformFunc(); +} + +DeviceNRecoder::~DeviceNRecoder() { + if (str->isEncoder()) { + delete str; + } +} + +Stream *DeviceNRecoder::copy() { + error(errInternal, -1, "Called copy() on DeviceNRecoder"); + return NULL; +} + +void DeviceNRecoder::reset() { + imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), + colorMap->getBits()); + imgStr->reset(); +} + +void DeviceNRecoder::close() { + delete imgStr; + imgStr = NULL; + str->close(); +} + +GBool DeviceNRecoder::fillBuf() { + Guchar pixBuf[gfxColorMaxComps]; + GfxColor color; + double x[gfxColorMaxComps], y[gfxColorMaxComps]; + int i; + + if (pixelIdx >= width * height) { + return gFalse; + } + imgStr->getPixel(pixBuf); + colorMap->getColor(pixBuf, &color); + for (i = 0; + i < ((GfxDeviceNColorSpace *)colorMap->getColorSpace())->getNComps(); + ++i) { + x[i] = colToDbl(color.c[i]); + } + func->transform(x, y); + for (i = 0; i < bufSize; ++i) { + buf[i] = (int)(y[i] * 255 + 0.5); + } + bufIdx = 0; + ++pixelIdx; + return gTrue; +} + +//------------------------------------------------------------------------ +// GrayRecoder +//------------------------------------------------------------------------ + +class GrayRecoder: public FilterStream { +public: + + GrayRecoder(Stream *strA, int widthA, int heightA, + GfxImageColorMap *colorMapA); + virtual ~GrayRecoder(); + virtual Stream *copy(); + virtual StreamKind getKind() { return strWeird; } + virtual void reset(); + virtual void close(); + virtual int getChar() + { return (bufIdx >= width && !fillBuf()) ? EOF : buf[bufIdx++]; } + virtual int lookChar() + { return (bufIdx >= width && !fillBuf()) ? EOF : buf[bufIdx]; } + virtual GString *getPSFilter(int psLevel, const char *indent) { return NULL; } + virtual GBool isBinary(GBool last = gTrue) { return gTrue; } + virtual GBool isEncoder() { return gTrue; } + +private: + + GBool fillBuf(); + + int width, height; + GfxImageColorMap *colorMap; + ImageStream *imgStr; + Guchar *buf; + int bufIdx; +}; + +GrayRecoder::GrayRecoder(Stream *strA, int widthA, int heightA, + GfxImageColorMap *colorMapA): + FilterStream(strA) { + width = widthA; + height = heightA; + colorMap = colorMapA; + imgStr = NULL; + buf = (Guchar *)gmalloc(width); + bufIdx = width; +} + +GrayRecoder::~GrayRecoder() { + gfree(buf); + if (str->isEncoder()) { + delete str; + } +} + +Stream *GrayRecoder::copy() { + error(errInternal, -1, "Called copy() on GrayRecoder"); + return NULL; +} + +void GrayRecoder::reset() { + imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), + colorMap->getBits()); + imgStr->reset(); +} + +void GrayRecoder::close() { + delete imgStr; + imgStr = NULL; + str->close(); +} + +GBool GrayRecoder::fillBuf() { + Guchar *line; + + if (!(line = imgStr->getLine())) { + bufIdx = width; + return gFalse; + } + //~ this should probably use the rendering intent from the image + //~ dict, or from the content stream + colorMap->getGrayByteLine(line, buf, width, + gfxRenderingIntentRelativeColorimetric); + bufIdx = 0; + return gTrue; +} + +//------------------------------------------------------------------------ +// ColorKeyToMaskEncoder +//------------------------------------------------------------------------ + +class ColorKeyToMaskEncoder: public FilterStream { +public: + + ColorKeyToMaskEncoder(Stream *strA, int widthA, int heightA, + GfxImageColorMap *colorMapA, int *maskColorsA); + virtual ~ColorKeyToMaskEncoder(); + virtual Stream *copy(); + virtual StreamKind getKind() { return strWeird; } + virtual void reset(); + virtual void close(); + virtual int getChar() + { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx++]; } + virtual int lookChar() + { return (bufIdx >= bufSize && !fillBuf()) ? EOF : buf[bufIdx]; } + virtual GString *getPSFilter(int psLevel, const char *indent) { return NULL; } + virtual GBool isBinary(GBool last = gTrue) { return gTrue; } + virtual GBool isEncoder() { return gTrue; } + +private: + + GBool fillBuf(); + + int width, height; + GfxImageColorMap *colorMap; + int numComps; + int *maskColors; + ImageStream *imgStr; + Guchar *buf; + int bufIdx; + int bufSize; +}; + +ColorKeyToMaskEncoder::ColorKeyToMaskEncoder(Stream *strA, + int widthA, int heightA, + GfxImageColorMap *colorMapA, + int *maskColorsA): + FilterStream(strA) +{ + width = widthA; + height = heightA; + colorMap = colorMapA; + numComps = colorMap->getNumPixelComps(); + maskColors = maskColorsA; + imgStr = NULL; + bufSize = (width + 7) / 8; + buf = (Guchar *)gmalloc(bufSize); + bufIdx = width; +} + +ColorKeyToMaskEncoder::~ColorKeyToMaskEncoder() { + gfree(buf); + if (str->isEncoder()) { + delete str; + } +} + +Stream *ColorKeyToMaskEncoder::copy() { + error(errInternal, -1, "Called copy() on ColorKeyToMaskEncoder"); + return NULL; +} + +void ColorKeyToMaskEncoder::reset() { + imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), + colorMap->getBits()); + imgStr->reset(); +} + +void ColorKeyToMaskEncoder::close() { + delete imgStr; + imgStr = NULL; + str->close(); +} + +GBool ColorKeyToMaskEncoder::fillBuf() { + Guchar *line, *linePtr, *bufPtr; + Guchar byte; + int x, xx, i; + + if (!(line = imgStr->getLine())) { + bufIdx = width; + return gFalse; + } + linePtr = line; + bufPtr = buf; + for (x = 0; x < width; x += 8) { + byte = 0; + for (xx = 0; xx < 8; ++xx) { + byte = (Guchar)(byte << 1); + if (x + xx < width) { + for (i = 0; i < numComps; ++i) { + if (linePtr[i] < maskColors[2 * i] || + linePtr[i] > maskColors[2 * i + 1]) { + break; + } + } + if (i >= numComps) { + byte |= 1; + } + linePtr += numComps; + } else { + byte |= 1; + } + } + *bufPtr++ = byte; + } + bufIdx = 0; + return gTrue; +} + +//------------------------------------------------------------------------ +// PSOutputDev +//------------------------------------------------------------------------ + +extern "C" { +typedef void (*SignalFunc)(int); +} + +static void outputToFile(void *stream, const char *data, int len) { + fwrite(data, 1, len, (FILE *)stream); +} + +PSOutputDev::PSOutputDev(char *fileName, PDFDoc *docA, + int firstPageA, int lastPageA, PSOutMode modeA, + int imgLLXA, int imgLLYA, int imgURXA, int imgURYA, + GBool manualCtrlA, + PSOutCustomCodeCbk customCodeCbkA, + void *customCodeCbkDataA, + GBool honorUserUnitA) { + FILE *f; + PSFileType fileTypeA; + + underlayCbk = NULL; + underlayCbkData = NULL; + overlayCbk = NULL; + overlayCbkData = NULL; + customCodeCbk = customCodeCbkA; + customCodeCbkData = customCodeCbkDataA; + + rasterizePage = NULL; + fontInfo = new GList(); + fontFileInfo = new GHash(); + imgIDs = NULL; + formIDs = NULL; + visitedResources = NULL; + saveStack = NULL; + paperSizes = NULL; + embFontList = NULL; + customColors = NULL; + haveTextClip = gFalse; + t3String = NULL; + + // open file or pipe + if (!strcmp(fileName, "-")) { + fileTypeA = psStdout; + f = stdout; + } else if (fileName[0] == '|') { + fileTypeA = psPipe; +#ifdef HAVE_POPEN +#ifndef _WIN32 + signal(SIGPIPE, (SignalFunc)SIG_IGN); +#endif + if (!(f = popen(fileName + 1, "w"))) { + error(errIO, -1, "Couldn't run print command '{0:s}'", fileName); + ok = gFalse; + return; + } +#else + error(errIO, -1, "Print commands are not supported ('{0:s}')", fileName); + ok = gFalse; + return; +#endif + } else { + fileTypeA = psFile; + if (!(f = fopen(fileName, "w"))) { + error(errIO, -1, "Couldn't open PostScript file '{0:s}'", fileName); + ok = gFalse; + return; + } + } + + init(outputToFile, f, fileTypeA, + docA, firstPageA, lastPageA, modeA, + imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA, honorUserUnitA); +} + +PSOutputDev::PSOutputDev(PSOutputFunc outputFuncA, void *outputStreamA, + PDFDoc *docA, + int firstPageA, int lastPageA, PSOutMode modeA, + int imgLLXA, int imgLLYA, int imgURXA, int imgURYA, + GBool manualCtrlA, + PSOutCustomCodeCbk customCodeCbkA, + void *customCodeCbkDataA, + GBool honorUserUnitA) { + underlayCbk = NULL; + underlayCbkData = NULL; + overlayCbk = NULL; + overlayCbkData = NULL; + customCodeCbk = customCodeCbkA; + customCodeCbkData = customCodeCbkDataA; + + rasterizePage = NULL; + fontInfo = new GList(); + fontFileInfo = new GHash(); + imgIDs = NULL; + formIDs = NULL; + visitedResources = NULL; + saveStack = NULL; + paperSizes = NULL; + embFontList = NULL; + customColors = NULL; + haveTextClip = gFalse; + t3String = NULL; + + init(outputFuncA, outputStreamA, psGeneric, + docA, firstPageA, lastPageA, modeA, + imgLLXA, imgLLYA, imgURXA, imgURYA, manualCtrlA, honorUserUnitA); +} + +void PSOutputDev::init(PSOutputFunc outputFuncA, void *outputStreamA, + PSFileType fileTypeA, PDFDoc *docA, + int firstPageA, int lastPageA, PSOutMode modeA, + int imgLLXA, int imgLLYA, int imgURXA, int imgURYA, + GBool manualCtrlA, GBool honorUserUnitA) { + Catalog *catalog; + Page *page; + PDFRectangle *box; + PSOutPaperSize *size; + PSFontFileInfo *ff; + GList *names; + double userUnit; + int pg, w, h, i; + + // initialize + ok = gTrue; + outputFunc = outputFuncA; + outputStream = outputStreamA; + fileType = fileTypeA; + doc = docA; + xref = doc->getXRef(); + catalog = doc->getCatalog(); + if ((firstPage = firstPageA) < 1) { + firstPage = 1; + } + if ((lastPage = lastPageA) > doc->getNumPages()) { + lastPage = doc->getNumPages(); + } + level = globalParams->getPSLevel(); + mode = modeA; + honorUserUnit = honorUserUnitA; + paperWidth = globalParams->getPSPaperWidth(); + paperHeight = globalParams->getPSPaperHeight(); + imgLLX = imgLLXA; + imgLLY = imgLLYA; + imgURX = imgURXA; + imgURY = imgURYA; + if (imgLLX == 0 && imgURX == 0 && imgLLY == 0 && imgURY == 0) { + globalParams->getPSImageableArea(&imgLLX, &imgLLY, &imgURX, &imgURY); + } + if (paperWidth < 0 || paperHeight < 0) { + paperMatch = gTrue; + paperSizes = new GList(); + paperWidth = paperHeight = 1; // in case the document has zero pages + for (pg = firstPage; pg <= lastPage; ++pg) { + page = catalog->getPage(pg); + if (honorUserUnit) { + userUnit = page->getUserUnit(); + } else { + userUnit = 1; + } + if (globalParams->getPSUseCropBoxAsPage()) { + w = (int)ceil(page->getCropWidth() * userUnit); + h = (int)ceil(page->getCropHeight() * userUnit); + } else { + w = (int)ceil(page->getMediaWidth() * userUnit); + h = (int)ceil(page->getMediaHeight() * userUnit); + } + for (i = 0; i < paperSizes->getLength(); ++i) { + size = (PSOutPaperSize *)paperSizes->get(i); + if (size->w == w && size->h == h) { + break; + } + } + if (i == paperSizes->getLength()) { + paperSizes->append(new PSOutPaperSize(w, h)); + } + if (w > paperWidth) { + paperWidth = w; + } + if (h > paperHeight) { + paperHeight = h; + } + } + // NB: img{LLX,LLY,URX,URY} will be set by startPage() + } else { + paperMatch = gFalse; + } + preload = globalParams->getPSPreload(); + manualCtrl = manualCtrlA; + if (mode == psModeForm) { + lastPage = firstPage; + } + processColors = 0; + inType3Char = gFalse; + +#if OPI_SUPPORT + // initialize OPI nesting levels + opi13Nest = 0; + opi20Nest = 0; +#endif + + tx0 = ty0 = -1; + xScale0 = yScale0 = 0; + rotate0 = -1; + clipLLX0 = clipLLY0 = 0; + clipURX0 = clipURY0 = -1; + + // initialize font lists, etc. + for (i = 0; i < 14; ++i) { + 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) { + if (!fontFileInfo->lookup((GString *)names->get(i))) { + ff = new PSFontFileInfo((GString *)names->get(i), fontType1, + psFontFileResident); + fontFileInfo->add(ff->psName, ff); + } + } + delete names; + imgIDLen = 0; + imgIDSize = 0; + formIDLen = 0; + formIDSize = 0; + + noStateChanges = gFalse; + saveStack = new GList(); + numTilingPatterns = 0; + nextFunc = 0; + + // initialize embedded font resource comment list + embFontList = new GString(); + + if (!manualCtrl) { + // this check is needed in case the document has zero pages + if (firstPage <= catalog->getNumPages()) { + writeHeader(catalog->getPage(firstPage)->getMediaBox(), + catalog->getPage(firstPage)->getCropBox(), + catalog->getPage(firstPage)->getRotate()); + } else { + box = new PDFRectangle(0, 0, 1, 1); + writeHeader(box, box, 0); + delete box; + } + if (mode != psModeForm) { + writePS("%%BeginProlog\n"); + } + writeXpdfProcset(); + if (mode != psModeForm) { + writePS("%%EndProlog\n"); + writePS("%%BeginSetup\n"); + } + writeDocSetup(catalog); + if (mode != psModeForm) { + writePS("%%EndSetup\n"); + } + } + + // initialize sequential page number + seqPage = 1; +} + +PSOutputDev::~PSOutputDev() { + PSOutCustomColor *cc; + + if (ok) { + if (!manualCtrl) { + writePS("%%Trailer\n"); + writeTrailer(); + if (mode != psModeForm) { + writePS("%%EOF\n"); + } + } + if (fileType == psFile) { + fclose((FILE *)outputStream); + } +#ifdef HAVE_POPEN + else if (fileType == psPipe) { + pclose((FILE *)outputStream); +#ifndef _WIN32 + signal(SIGPIPE, (SignalFunc)SIG_DFL); +#endif + } +#endif + } + gfree(rasterizePage); + if (paperSizes) { + deleteGList(paperSizes, PSOutPaperSize); + } + if (embFontList) { + delete embFontList; + } + deleteGList(fontInfo, PSFontInfo); + deleteGHash(fontFileInfo, PSFontFileInfo); + gfree(imgIDs); + gfree(formIDs); + if (saveStack) { + delete saveStack; + } + while (customColors) { + cc = customColors; + customColors = cc->next; + delete cc; + } +} + +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(PDFRectangle *mediaBox, PDFRectangle *cropBox, + int pageRotate) { + Object info, obj1; + PSOutPaperSize *size; + double x1, y1, x2, y2; + int i; + + switch (mode) { + case psModePS: + writePS("%!PS-Adobe-3.0\n"); + break; + case psModeEPS: + writePS("%!PS-Adobe-3.0 EPSF-3.0\n"); + break; + case psModeForm: + writePS("%!PS-Adobe-3.0 Resource-Form\n"); + break; + } + + writePSFmt("%XpdfVersion: {0:s}\n", xpdfVersion); + xref->getDocInfo(&info); + if (info.isDict() && info.dictLookup("Creator", &obj1)->isString()) { + writePS("%%Creator: "); + writePSTextLine(obj1.getString()); + } + obj1.free(); + if (info.isDict() && info.dictLookup("Title", &obj1)->isString()) { + writePS("%%Title: "); + writePSTextLine(obj1.getString()); + } + obj1.free(); + info.free(); + writePSFmt("%%LanguageLevel: {0:d}\n", + level >= psLevel3 ? 3 : level >= psLevel2 ? 2 : 1); + if (level == psLevel1Sep || level == psLevel2Sep || level == psLevel3Sep) { + writePS("%%DocumentProcessColors: (atend)\n"); + writePS("%%DocumentCustomColors: (atend)\n"); + } + writePS("%%DocumentSuppliedResources: (atend)\n"); + + switch (mode) { + case psModePS: + if (paperMatch) { + for (i = 0; i < paperSizes->getLength(); ++i) { + size = (PSOutPaperSize *)paperSizes->get(i); + writePSFmt("%%{0:s} {1:d}x{2:d} {1:d} {2:d} 0 () ()\n", + i==0 ? "DocumentMedia:" : "+", size->w, size->h); + } + } else { + writePSFmt("%%DocumentMedia: plain {0:d} {1:d} 0 () ()\n", + paperWidth, paperHeight); + } + writePSFmt("%%BoundingBox: 0 0 {0:d} {1:d}\n", paperWidth, paperHeight); + writePSFmt("%%Pages: {0:d}\n", lastPage - firstPage + 1); + writePS("%%EndComments\n"); + if (!paperMatch) { + writePS("%%BeginDefaults\n"); + writePS("%%PageMedia: plain\n"); + writePS("%%EndDefaults\n"); + } + break; + case psModeEPS: + epsX1 = cropBox->x1; + epsY1 = cropBox->y1; + epsX2 = cropBox->x2; + epsY2 = cropBox->y2; + if (pageRotate == 0 || pageRotate == 180) { + x1 = epsX1; + y1 = epsY1; + x2 = epsX2; + y2 = epsY2; + } else { // pageRotate == 90 || pageRotate == 270 + x1 = 0; + y1 = 0; + x2 = epsY2 - epsY1; + y2 = epsX2 - epsX1; + } + writePSFmt("%%BoundingBox: {0:d} {1:d} {2:d} {3:d}\n", + (int)floor(x1), (int)floor(y1), (int)ceil(x2), (int)ceil(y2)); + if (floor(x1) != ceil(x1) || floor(y1) != ceil(y1) || + floor(x2) != ceil(x2) || floor(y2) != ceil(y2)) { + writePSFmt("%%HiResBoundingBox: {0:.6g} {1:.6g} {2:.6g} {3:.6g}\n", + x1, y1, x2, y2); + } + writePS("%%EndComments\n"); + break; + case psModeForm: + writePS("%%EndComments\n"); + writePS("32 dict dup begin\n"); + writePSFmt("/BBox [{0:d} {1:d} {2:d} {3:d}] def\n", + (int)floor(mediaBox->x1), (int)floor(mediaBox->y1), + (int)ceil(mediaBox->x2), (int)ceil(mediaBox->y2)); + writePS("/FormType 1 def\n"); + writePS("/Matrix [1 0 0 1 0 0] def\n"); + break; + } +} + +void PSOutputDev::writeXpdfProcset() { + GBool lev1, lev2, lev3, nonSep, gray, sep; + const char **p; + const char *q; + double w; + + writePSFmt("%%BeginResource: procset xpdf {0:s} 0\n", xpdfVersion); + writePSFmt("%%Copyright: {0:s}\n", xpdfCopyright); + lev1 = lev2 = lev3 = nonSep = gray = sep = gTrue; + for (p = prolog; *p; ++p) { + if ((*p)[0] == '~') { + lev1 = lev2 = lev3 = nonSep = gray = sep = gFalse; + for (q = *p + 1; *q; ++q) { + switch (*q) { + case '1': lev1 = gTrue; break; + case '2': lev2 = gTrue; break; + case '3': lev3 = gTrue; break; + case 'g': gray = gTrue; break; + case 'n': nonSep = gTrue; break; + case 's': sep = gTrue; break; + } + } + } else if ((level == psLevel1 && lev1 && nonSep) || + (level == psLevel1Sep && lev1 && sep) || + (level == psLevel2 && lev2 && nonSep) || + (level == psLevel2Gray && lev2 && gray) || + (level == psLevel2Sep && lev2 && sep) || + (level == psLevel3 && lev3 && nonSep) || + (level == psLevel3Gray && lev3 && gray) || + (level == psLevel3Sep && lev3 && sep)) { + 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) { + for (p = cmapProlog; *p; ++p) { + writePSFmt("{0:s}\n", *p); + } + } +} + +void PSOutputDev::writeDocSetup(Catalog *catalog) { + Page *page; + Dict *resDict; + Annots *annots; + Form *form; + Object obj1, obj2, obj3; + GString *s; + GBool needDefaultFont; + int pg, i, j; + + // check to see which pages will be rasterized + if (firstPage <= lastPage) { + rasterizePage = (char *)gmalloc(lastPage - firstPage + 1); + for (pg = firstPage; pg <= lastPage; ++pg) { + rasterizePage[pg - firstPage] = (char)checkIfPageNeedsToBeRasterized(pg); + } + } else { + rasterizePage = NULL; + } + + visitedResources = (char *)gmalloc(xref->getNumObjects()); + memset(visitedResources, 0, xref->getNumObjects()); + + if (mode == psModeForm) { + // swap the form and xpdf dicts + writePS("xpdf end begin dup begin\n"); + } else { + writePS("xpdf begin\n"); + } + needDefaultFont = gFalse; + for (pg = firstPage; pg <= lastPage; ++pg) { + if (rasterizePage[pg - firstPage]) { + continue; + } + page = catalog->getPage(pg); + if ((resDict = page->getResourceDict())) { + setupResources(resDict); + } + annots = new Annots(doc, page->getAnnots(&obj1)); + obj1.free(); + if (annots->getNumAnnots()) { + needDefaultFont = gTrue; + } + for (i = 0; i < annots->getNumAnnots(); ++i) { + if (annots->getAnnot(i)->getAppearance(&obj1)->isStream()) { + obj1.streamGetDict()->lookup("Resources", &obj2); + if (obj2.isDict()) { + setupResources(obj2.getDict()); + } + obj2.free(); + } + obj1.free(); + } + delete annots; + } + if ((form = catalog->getForm())) { + if (form->getNumFields() > 0) { + needDefaultFont = gTrue; + } + 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()); + } + obj2.free(); + } + } else if (obj1.isDict()) { + setupResources(obj1.getDict()); + } + obj1.free(); + } + } + if (needDefaultFont) { + setupDefaultFont(); + } + if (mode != psModeForm) { + if (mode != psModeEPS && !manualCtrl) { + writePSFmt("{0:s} pdfSetup\n", + globalParams->getPSDuplex() ? "true" : "false"); + if (!paperMatch) { + writePSFmt("{0:d} {1:d} pdfSetupPaper\n", paperWidth, paperHeight); + } + } +#if OPI_SUPPORT + if (globalParams->getPSOPI()) { + writePS("/opiMatrix matrix currentmatrix def\n"); + } +#endif + } + if (customCodeCbk) { + if ((s = (*customCodeCbk)(this, psOutCustomDocSetup, 0, + customCodeCbkData))) { + writePS(s->getCString()); + delete s; + } + } + if (mode != psModeForm) { + writePS("end\n"); + } + + gfree(visitedResources); + visitedResources = NULL; +} + +void PSOutputDev::writePageTrailer() { + if (mode != psModeForm) { + writePS("pdfEndPage\n"); + } +} + +void PSOutputDev::writeTrailer() { + PSOutCustomColor *cc; + + if (mode == psModeForm) { + writePS("/Foo exch /Form defineresource pop\n"); + } else { + writePS("%%DocumentSuppliedResources:\n"); + writePS(embFontList->getCString()); + if (level == psLevel1Sep || level == psLevel2Sep || + level == psLevel3Sep) { + writePS("%%DocumentProcessColors:"); + if (processColors & psProcessCyan) { + writePS(" Cyan"); + } + if (processColors & psProcessMagenta) { + writePS(" Magenta"); + } + if (processColors & psProcessYellow) { + writePS(" Yellow"); + } + if (processColors & psProcessBlack) { + writePS(" Black"); + } + writePS("\n"); + writePS("%%DocumentCustomColors:"); + for (cc = customColors; cc; cc = cc->next) { + writePS(" "); + writePSString(cc->name); + } + writePS("\n"); + writePS("%%CMYKCustomColor:\n"); + for (cc = customColors; cc; cc = cc->next) { + writePSFmt("%%+ {0:.4g} {1:.4g} {2:.4g} {3:.4g} ", + cc->c, cc->m, cc->y, cc->k); + writePSString(cc->name); + writePS("\n"); + } + } + } +} + +GBool PSOutputDev::checkIfPageNeedsToBeRasterized(int pg) { + PreScanOutputDev *scan; + GBool rasterize; + + if (globalParams->getPSAlwaysRasterize()) { + rasterize = gTrue; + } else { + scan = new PreScanOutputDev(); + //~ this could depend on the printing flag, e.g., if an annotation + //~ uses transparency --> need to pass the printing flag into + //~ constructor, init, writeDocSetup + doc->getCatalog()->getPage(pg)->display(scan, 72, 72, 0, + gTrue, gTrue, gTrue); + rasterize = scan->usesTransparency() || scan->usesPatternImageMask(); + delete scan; + if (rasterize && globalParams->getPSNeverRasterize()) { + error(errSyntaxWarning, -1, + "PDF page uses transparency and the psNeverRasterize option is " + "set - output may not be correct"); + rasterize = gFalse; + } + } + return rasterize; +} + +void PSOutputDev::setupResources(Dict *resDict) { + Object xObjDict, xObjRef, xObj, patDict, patRef, pat; + Object gsDict, gsRef, gs, smask, smaskGroup, resObj; + Ref ref0; + GBool skip; + int i; + + setupFonts(resDict); + setupImages(resDict); + + //----- recursively scan XObjects + resDict->lookup("XObject", &xObjDict); + if (xObjDict.isDict()) { + for (i = 0; i < xObjDict.dictGetLength(); ++i) { + + // check for an already-visited XObject + skip = gFalse; + if ((xObjDict.dictGetValNF(i, &xObjRef)->isRef())) { + ref0 = xObjRef.getRef(); + if (ref0.num < 0 || ref0.num >= xref->getNumObjects()) { + skip = gTrue; + } else { + skip = (GBool)visitedResources[ref0.num]; + visitedResources[ref0.num] = 1; + } + } + if (!skip) { + + // process the XObject's resource dictionary + xObjDict.dictGetVal(i, &xObj); + if (xObj.isStream()) { + xObj.streamGetDict()->lookup("Resources", &resObj); + if (resObj.isDict()) { + setupResources(resObj.getDict()); + } + resObj.free(); + } + xObj.free(); + } + + xObjRef.free(); + } + } + xObjDict.free(); + + //----- recursively scan Patterns + resDict->lookup("Pattern", &patDict); + if (patDict.isDict()) { + inType3Char = gTrue; + for (i = 0; i < patDict.dictGetLength(); ++i) { + + // check for an already-visited Pattern + skip = gFalse; + if ((patDict.dictGetValNF(i, &patRef)->isRef())) { + ref0 = patRef.getRef(); + if (ref0.num < 0 || ref0.num >= xref->getNumObjects()) { + skip = gTrue; + } else { + skip = (GBool)visitedResources[ref0.num]; + visitedResources[ref0.num] = 1; + } + } + if (!skip) { + + // process the Pattern's resource dictionary + patDict.dictGetVal(i, &pat); + if (pat.isStream()) { + pat.streamGetDict()->lookup("Resources", &resObj); + if (resObj.isDict()) { + setupResources(resObj.getDict()); + } + resObj.free(); + } + pat.free(); + } + + patRef.free(); + } + 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) { + + // check for an already-visited ExtGState + skip = gFalse; + if ((gsDict.dictGetValNF(i, &gsRef)->isRef())) { + ref0 = gsRef.getRef(); + if (ref0.num < 0 || ref0.num >= xref->getNumObjects()) { + skip = gTrue; + } else { + skip = (GBool)visitedResources[ref0.num]; + visitedResources[ref0.num] = 1; + } + } + 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(); + } + + gsRef.free(); + } + } + gsDict.free(); + + setupForms(resDict); +} + +void PSOutputDev::setupFonts(Dict *resDict) { + Object obj1, obj2; + Ref r; + GfxFontDict *gfxFontDict; + GfxFont *font; + int i; + + gfxFontDict = NULL; + resDict->lookupNF("Font", &obj1); + if (obj1.isRef()) { + obj1.fetch(xref, &obj2); + if (obj2.isDict()) { + r = obj1.getRef(); + gfxFontDict = new GfxFontDict(xref, &r, obj2.getDict()); + } + obj2.free(); + } else if (obj1.isDict()) { + gfxFontDict = new GfxFontDict(xref, NULL, obj1.getDict()); + } + if (gfxFontDict) { + for (i = 0; i < gfxFontDict->getNumFonts(); ++i) { + if ((font = gfxFontDict->getFont(i))) { + setupFont(font, resDict); + } + } + delete gfxFontDict; + } + obj1.free(); +} + +void PSOutputDev::setupFont(GfxFont *font, Dict *parentResDict) { + PSFontInfo *fi; + GfxFontLoc *fontLoc; + GBool subst; + char buf[16]; + UnicodeMap *uMap; + char *charName; + double xs, ys; + int code; + double w1, w2; + int i, j; + + // check if font is already set up + 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 fontInfo entry + fi = new PSFontInfo(*font->getID()); + fontInfo->append(fi); + + xs = ys = 1; + subst = gFalse; + + if (font->getType() == fontType3) { + 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; + } + break; + 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; + case fontCIDType0COT: + fi->ff = setupExternalOpenTypeCFFFont(font, fontLoc->path); + break; + default: + break; + } + break; + 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; + } + } + + if (!fi->ff) { + if (font->isCIDFont()) { + error(errSyntaxError, -1, + "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)"); + } else { + error(errSyntaxError, -1, + "Couldn't find a font for '{0:s}'", + font->getName() ? font->getName()->getCString() + : "(unnamed)"); + } + delete fontLoc; + return; + } + + // scale substituted 8-bit fonts + if (fontLoc->locType == gfxFontLocResident && + fontLoc->substIdx >= 0) { + subst = gTrue; + for (code = 0; code < 256; ++code) { + if ((charName = ((Gfx8BitFont *)font)->getCharName(code)) && + charName[0] == 'm' && charName[1] == '\0') { + break; + } + } + if (code < 256) { + w1 = ((Gfx8BitFont *)font)->getWidth((Guchar)code); + } else { + w1 = 0; + } + w2 = psBase14SubstFonts[fontLoc->substIdx].mWidth; + xs = w1 / w2; + if (xs < 0.1) { + xs = 1; + } + } + + // handle encodings for substituted CID fonts + if (fontLoc->locType == gfxFontLocResident && + fontLoc->fontType >= fontCIDType0) { + subst = gTrue; + if ((uMap = globalParams->getUnicodeMap(fontLoc->encoding))) { + fi->ff->encoding = fontLoc->encoding->copy(); + uMap->decRefCnt(); + } else { + error(errSyntaxError, -1, + "Couldn't find Unicode map for 16-bit font encoding '{0:t}'", + fontLoc->encoding); + } + } + + delete fontLoc; + } + + // generate PostScript code to set up the font + if (font->isCIDFont()) { + if (level >= psLevel3) { + writePSFmt("/F{0:d}_{1:d} /{2:t} {3:d} pdfMakeFont16L3\n", + 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, 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, fi->ff->psName, xs, ys); + for (i = 0; i < 256; i += 8) { + writePS((char *)((i == 0) ? "[ " : " ")); + for (j = 0; j < 8; ++j) { + if (font->getType() == fontTrueType && + !subst && + !((Gfx8BitFont *)font)->getHasEncoding()) { + sprintf(buf, "c%02x", i+j); + charName = buf; + } else { + charName = ((Gfx8BitFont *)font)->getCharName(i+j); + } + writePS("/"); + writePSName(charName ? charName : (char *)".notdef"); + // the empty name is legal in PDF and PostScript, but PostScript + // uses a double-slash (//...) for "immediately evaluated names", + // so we need to add a space character here + if (charName && !charName[0]) { + writePS(" "); + } + } + writePS((i == 256-8) ? (char *)"]\n" : (char *)"\n"); + } + writePS("pdfMakeFont\n"); + } +} + +PSFontFileInfo *PSOutputDev::setupEmbeddedType1Font(GfxFont *font, Ref *id) { + GString *psName, *origFont, *cleanFont; + PSFontFileInfo *ff; + Object refObj, strObj, obj1, obj2; + Dict *dict; + char buf[4096]; + GBool rename; + int length1, length2, n; + + // check if font is already embedded + if (!font->getEmbeddedFontName()) { + rename = gTrue; + } else if ((ff = (PSFontFileInfo *) + fontFileInfo->lookup(font->getEmbeddedFontName()))) { + if (ff->loc == psFontFileEmbedded && + ff->embFontID.num == id->num && + ff->embFontID.gen == id->gen) { + return ff; + } + rename = gTrue; + } else { + rename = gFalse; + } + + // generate name + // (this assumes that the PS font name matches the PDF font name) + if (rename) { + psName = makePSFontName(font, id); + } else { + psName = font->getEmbeddedFontName()->copy(); + } + + // get the font stream and info + refObj.initRef(id->num, id->gen); + refObj.fetch(xref, &strObj); + refObj.free(); + if (!strObj.isStream()) { + error(errSyntaxError, -1, "Embedded font file object is not a stream"); + goto err1; + } + if (!(dict = strObj.streamGetDict())) { + error(errSyntaxError, -1, + "Embedded font stream is missing its dictionary"); + goto err1; + } + dict->lookup("Length1", &obj1); + dict->lookup("Length2", &obj2); + if (!obj1.isInt() || !obj2.isInt()) { + error(errSyntaxError, -1, + "Missing length fields in embedded font stream dictionary"); + obj1.free(); + obj2.free(); + goto err1; + } + length1 = obj1.getInt(); + length2 = obj2.getInt(); + obj1.free(); + obj2.free(); + + // read the font file + origFont = new GString(); + strObj.streamReset(); + while ((n = strObj.streamGetBlock(buf, sizeof(buf))) > 0) { + origFont->append(buf, n); + } + strObj.streamClose(); + strObj.free(); + + // beginning comment + writePSFmt("%%BeginResource: font {0:t}\n", psName); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // clean up the font file + cleanFont = fixType1Font(origFont, length1, length2); + if (rename) { + renameType1Font(cleanFont, psName); + } + writePSBlock(cleanFont->getCString(), cleanFont->getLength()); + delete cleanFont; + delete origFont; + + // ending comment + writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; + + err1: + strObj.free(); + delete psName; + return NULL; +} + +PSFontFileInfo *PSOutputDev::setupExternalType1Font(GfxFont *font, + GString *fileName) { + static char hexChar[17] = "0123456789abcdef"; + GString *psName; + PSFontFileInfo *ff; + FILE *fontFile; + int buf[6]; + int c, n, i; + + 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()); + } + + // beginning comment + writePSFmt("%%BeginResource: font {0:t}\n", psName); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // open the font file + if (!(fontFile = fopen(fileName->getCString(), "rb"))) { + error(errIO, -1, "Couldn't open external font file"); + return NULL; + } + + // 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((char)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((char)buf[0]); + writePSChar((char)buf[1]); + while ((c = fgetc(fontFile)) != EOF) { + writePSChar((char)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; +} + +PSFontFileInfo *PSOutputDev::setupEmbeddedType1CFont(GfxFont *font, Ref *id) { + GString *psName; + PSFontFileInfo *ff; + char *fontBuf; + int fontLen; + FoFiType1C *ffT1C; + GHashIter *iter; + + // 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) { + fontFileInfo->killIter(&iter); + return ff; + } + } + + // generate name + psName = makePSFontName(font, id); + + // beginning comment + writePSFmt("%%BeginResource: font {0:t}\n", psName); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // convert it to a Type 1 font + if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { + if ((ffT1C = FoFiType1C::make(fontBuf, fontLen))) { + ffT1C->convertToType1(psName->getCString(), NULL, gTrue, + outputFunc, outputStream); + delete ffT1C; + } + gfree(fontBuf); + } + + // ending comment + writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; +} + +PSFontFileInfo *PSOutputDev::setupEmbeddedOpenTypeT1CFont(GfxFont *font, + Ref *id) { + GString *psName; + PSFontFileInfo *ff; + char *fontBuf; + int fontLen; + FoFiTrueType *ffTT; + GHashIter *iter; + + // 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) { + fontFileInfo->killIter(&iter); + return ff; + } + } + + // generate name + psName = makePSFontName(font, id); + + // beginning comment + writePSFmt("%%BeginResource: font {0:t}\n", psName); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // convert it to a Type 1 font + if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { + if ((ffTT = FoFiTrueType::make(fontBuf, fontLen, 0, gTrue))) { + if (ffTT->isOpenTypeCFF()) { + ffTT->convertToType1(psName->getCString(), NULL, gTrue, + outputFunc, outputStream); + } + delete ffTT; + } + gfree(fontBuf); + } + + // ending comment + writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; +} + +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->type == font->getType() && + 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); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // convert it to a Type 42 font + 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; +} + +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); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // convert it to a Type 42 font + 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; +} + +PSFontFileInfo *PSOutputDev::setupEmbeddedCIDType0Font(GfxFont *font, Ref *id) { + GString *psName; + PSFontFileInfo *ff; + char *fontBuf; + int fontLen; + FoFiType1C *ffT1C; + GHashIter *iter; + + // 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) { + fontFileInfo->killIter(&iter); + return ff; + } + } + + // generate name + psName = makePSFontName(font, id); + + // beginning comment + writePSFmt("%%BeginResource: font {0:t}\n", psName); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // convert it to a Type 0 font + if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { + if ((ffT1C = FoFiType1C::make(fontBuf, fontLen))) { + if (globalParams->getPSLevel() >= psLevel3) { + // Level 3: use a CID font + ffT1C->convertToCIDType0(psName->getCString(), + ((GfxCIDFont *)font)->getCIDToGID(), + ((GfxCIDFont *)font)->getCIDToGIDLen(), + outputFunc, outputStream); + } else { + // otherwise: use a non-CID composite font + ffT1C->convertToType0(psName->getCString(), + ((GfxCIDFont *)font)->getCIDToGID(), + ((GfxCIDFont *)font)->getCIDToGIDLen(), + outputFunc, outputStream); + } + delete ffT1C; + } + gfree(fontBuf); + } + + // ending comment + writePS("%%EndResource\n"); + + ff = new PSFontFileInfo(psName, font->getType(), psFontFileEmbedded); + ff->embFontID = *id; + fontFileInfo->add(ff->psName, ff); + return ff; +} + +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->type == font->getType() && + 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); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // convert it to a Type 0 font + if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { + if ((ffTT = FoFiTrueType::make(fontBuf, fontLen, 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); + } + delete ffTT; + } + gfree(fontBuf); + } + + // ending comment + writePS("%%EndResource\n"); + + 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, cmapPlatform, cmapEncoding, 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) { + cmapPlatform = ffTT->getCmapPlatform(cmap); + cmapEncoding = ffTT->getCmapEncoding(cmap); + if ((cmapPlatform == 3 && cmapEncoding == 1) || + (cmapPlatform == 0 && cmapEncoding <= 4)) { + 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); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // convert it to a Type 0 font + //~ this should use fontNum to load the correct font + 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; +} + +PSFontFileInfo *PSOutputDev::setupEmbeddedOpenTypeCFFFont(GfxFont *font, + Ref *id) { + GString *psName; + PSFontFileInfo *ff; + char *fontBuf; + int fontLen; + FoFiTrueType *ffTT; + GHashIter *iter; + int n; + + // 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) { + fontFileInfo->killIter(&iter); + return ff; + } + } + + // generate name + psName = makePSFontName(font, id); + + // beginning comment + writePSFmt("%%BeginResource: font {0:t}\n", psName); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // convert it to a Type 0 font + if ((fontBuf = font->readEmbFontFile(xref, &fontLen))) { + if ((ffTT = FoFiTrueType::make(fontBuf, fontLen, 0, gTrue))) { + if (ffTT->isOpenTypeCFF()) { + if (globalParams->getPSLevel() >= psLevel3) { + // Level 3: use a CID font + ffTT->convertToCIDType0(psName->getCString(), + ((GfxCIDFont *)font)->getCIDToGID(), + ((GfxCIDFont *)font)->getCIDToGIDLen(), + outputFunc, outputStream); + } else { + // otherwise: use a non-CID composite font + ffTT->convertToType0(psName->getCString(), + ((GfxCIDFont *)font)->getCIDToGID(), + ((GfxCIDFont *)font)->getCIDToGIDLen(), + outputFunc, outputStream); + } + } + delete ffTT; + } + gfree(fontBuf); + } + + // 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; +} + +// This assumes an OpenType CFF font that has a Unicode cmap (in the +// OpenType section), and a CFF blob that uses an identity CID-to-GID +// mapping. +PSFontFileInfo *PSOutputDev::setupExternalOpenTypeCFFFont(GfxFont *font, + GString *fileName) { + GString *psName; + PSFontFileInfo *ff; + FoFiTrueType *ffTT; + GHashIter *iter; + CharCodeToUnicode *ctu; + Unicode uBuf[8]; + int *codeToGID; + int codeToGIDLen; + int cmap, cmapPlatform, cmapEncoding, code; + + // create a code-to-GID mapping, via Unicode + if (!(ffTT = FoFiTrueType::load(fileName->getCString(), 0, gTrue))) { + return NULL; + } + if (!ffTT->isOpenTypeCFF()) { + delete ffTT; + 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) { + cmapPlatform = ffTT->getCmapPlatform(cmap); + cmapEncoding = ffTT->getCmapEncoding(cmap); + if ((cmapPlatform == 3 && cmapEncoding == 1) || + (cmapPlatform == 0 && cmapEncoding <= 4)) { + 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; + } + } + + // generate name + psName = makePSFontName(font, font->getID()); + + // beginning comment + writePSFmt("%%BeginResource: font {0:t}\n", psName); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // convert it to a Type 0 font + if (globalParams->getPSLevel() >= psLevel3) { + // Level 3: use a CID font + ffTT->convertToCIDType0(psName->getCString(), + codeToGID, codeToGIDLen, + outputFunc, outputStream); + } else { + // otherwise: use a non-CID composite font + ffTT->convertToType0(psName->getCString(), + codeToGID, codeToGIDLen, + 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; +} + +PSFontFileInfo *PSOutputDev::setupType3Font(GfxFont *font, + Dict *parentResDict) { + PSFontFileInfo *ff; + GString *psName; + Dict *resDict; + Dict *charProcs; + Object charProc; + Gfx *gfx; + PDFRectangle box; + double *m; + 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; + setupResources(resDict); + inType3Char = gFalse; + } else { + resDict = parentResDict; + } + + // beginning comment + writePSFmt("%%BeginResource: font {0:t}\n", psName); + embFontList->append("%%+ font "); + embFontList->append(psName->getCString()); + embFontList->append("\n"); + + // font dictionary + writePS("8 dict begin\n"); + writePS("/FontType 3 def\n"); + m = font->getFontMatrix(); + writePSFmt("/FontMatrix [{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] def\n", + m[0], m[1], m[2], m[3], m[4], m[5]); + m = font->getFontBBox(); + writePSFmt("/FontBBox [{0:.6g} {1:.6g} {2:.6g} {3:.6g}] def\n", + m[0], m[1], m[2], m[3]); + writePS("/Encoding 256 array def\n"); + writePS(" 0 1 255 { Encoding exch /.notdef put } for\n"); + writePS("/BuildGlyph {\n"); + writePS(" exch /CharProcs get exch\n"); + writePS(" 2 copy known not { pop /.notdef } if\n"); + writePS(" get exec\n"); + writePS("} bind def\n"); + writePS("/BuildChar {\n"); + writePS(" 1 index /Encoding get exch get\n"); + writePS(" 1 index /BuildGlyph get exec\n"); + writePS("} bind def\n"); + if ((charProcs = ((Gfx8BitFont *)font)->getCharProcs())) { + writePSFmt("/CharProcs {0:d} dict def\n", charProcs->getLength()); + writePS("CharProcs begin\n"); + box.x1 = m[0]; + box.y1 = m[1]; + box.x2 = m[2]; + box.y2 = m[3]; + gfx = new Gfx(doc, this, resDict, &box, NULL); + inType3Char = gTrue; + for (i = 0; i < charProcs->getLength(); ++i) { + t3FillColorOnly = gFalse; + t3Cacheable = gFalse; + t3NeedsRestore = gFalse; + writePS("/"); + writePSName(charProcs->getKey(i)); + writePS(" {\n"); + gfx->display(charProcs->getValNF(i, &charProc)); + charProc.free(); + if (t3String) { + if (t3Cacheable) { + buf = GString::format("{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g} setcachedevice\n", + t3WX, t3WY, t3LLX, t3LLY, t3URX, t3URY); + } else { + buf = GString::format("{0:.6g} {1:.6g} setcharwidth\n", t3WX, t3WY); + } + (*outputFunc)(outputStream, buf->getCString(), buf->getLength()); + delete buf; + (*outputFunc)(outputStream, t3String->getCString(), + t3String->getLength()); + delete t3String; + t3String = NULL; + } + if (t3NeedsRestore) { + (*outputFunc)(outputStream, "Q\n", 2); + } + writePS("} def\n"); + } + inType3Char = gFalse; + delete gfx; + writePS("end\n"); + } + writePS("currentdict end\n"); + writePSFmt("/{0:t} exch definefont pop\n", psName); + + // ending comment + writePS("%%EndResource\n"); + + 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 +// font object, and an object ID (font file object for +GString *PSOutputDev::makePSFontName(GfxFont *font, Ref *id) { + GString *psName, *s; + + if ((s = font->getEmbeddedFontName())) { + psName = filterPSName(s); + if (!fontFileInfo->lookup(psName)) { + return psName; + } + delete psName; + } + if ((s = font->getName())) { + psName = filterPSName(s); + if (!fontFileInfo->lookup(psName)) { + return psName; + } + delete psName; + } + psName = GString::format("FF{0:d}_{1:d}", id->num, id->gen); + if ((s = font->getEmbeddedFontName())) { + s = filterPSName(s); + psName->append('_')->append(s); + delete s; + } else if ((s = font->getName())) { + s = filterPSName(s); + psName->append('_')->append(s); + delete s; + } + return psName; +} + +GString *PSOutputDev::fixType1Font(GString *font, int length1, int length2) { + Guchar *fontData; + GString *out, *binSection; + GBool pfb; + int fontSize, i; + + fontData = (Guchar *)font->getCString(); + fontSize = font->getLength(); + + // check for PFB + pfb = fontSize >= 6 && fontData[0] == 0x80 && fontData[1] == 0x01; + out = new GString(); + binSection = new GString(); + if (pfb) { + if (!splitType1PFB(fontData, fontSize, out, binSection)) { + delete out; + delete binSection; + return copyType1PFB(fontData, fontSize); + } + } else { + if (!splitType1PFA(fontData, fontSize, length1, length2, + out, binSection)) { + delete out; + delete binSection; + return copyType1PFA(fontData, fontSize); + } + } + + out->append('\n'); + + binSection = asciiHexDecodeType1EexecSection(binSection); + + if (!fixType1EexecSection(binSection, out)) { + delete out; + delete binSection; + return pfb ? copyType1PFB(fontData, fontSize) + : copyType1PFA(fontData, fontSize); + } + delete binSection; + + for (i = 0; i < 8; ++i) { + out->append("0000000000000000000000000000000000000000000000000000000000000000\n"); + } + out->append("cleartomark\n"); + + return out; +} + +// Split a Type 1 font in PFA format into a text section and a binary +// section. +GBool PSOutputDev::splitType1PFA(Guchar *font, int fontSize, + int length1, int length2, + GString *textSection, GString *binSection) { + int textLength, binStart, binLength, lastSpace, i; + + //--- extract the text section + + // Length1 is correct, and the text section ends with whitespace + if (length1 <= fontSize && + length1 >= 18 && + !memcmp(font + length1 - 18, "currentfile eexec", 17)) { + textLength = length1 - 1; + + // Length1 is correct, but the trailing whitespace is missing + } else if (length1 <= fontSize && + length1 >= 17 && + !memcmp(font + length1 - 17, "currentfile eexec", 17)) { + textLength = length1; + + // Length1 is incorrect + } else { + for (textLength = 17; textLength <= fontSize; ++textLength) { + if (!memcmp(font + textLength - 17, "currentfile eexec", 17)) { + break; + } + } + if (textLength > fontSize) { + return gFalse; + } + } + + textSection->append((char *)font, textLength); + + //--- skip whitespace between the text section and the binary section + + for (i = 0, binStart = textLength; + i < 8 && binStart < fontSize; + ++i, ++binStart) { + if (font[binStart] != ' ' && font[binStart] != '\t' && + font[binStart] != '\n' && font[binStart] != '\r') { + break; + } + } + if (i == 8) { + return gFalse; + } + + //--- extract binary section + + // if we see "0000", assume Length2 is correct + // (if Length2 is too long, it will be corrected by fixType1EexecSection) + if (length2 > 0 && length2 < INT_MAX - 4 && + binStart <= fontSize - length2 - 4 && + !memcmp(font + binStart + length2, "0000", 4)) { + binLength = length2; + + } else { + + // look for "0000" near the end of the font (note that there can + // be intervening "\n", "\r\n", etc.), then search backward + if (fontSize - binStart < 512) { + return gFalse; + } + if (!memcmp(font + fontSize - 256, "0000", 4) || + !memcmp(font + fontSize - 255, "0000", 4) || + !memcmp(font + fontSize - 254, "0000", 4) || + !memcmp(font + fontSize - 253, "0000", 4) || + !memcmp(font + fontSize - 252, "0000", 4) || + !memcmp(font + fontSize - 251, "0000", 4)) { + i = fontSize - 252; + lastSpace = -1; + while (i >= binStart) { + if (font[i] == ' ' || font[i] == '\t' || + font[i] == '\n' || font[i] == '\r') { + lastSpace = i; + --i; + } else if (font[i] == '0') { + --i; + } else { + break; + } + } + if (lastSpace < 0) { + return gFalse; + } + // check for the case where the newline/space is missing between + // the binary section and the first set of 64 '0' chars + if (lastSpace - binStart > 64 && + !memcmp(font + lastSpace - 64, + "0000000000000000000000000000000000000000000000000000000000000000", + 64)) { + binLength = lastSpace - 64 - binStart; + } else { + binLength = lastSpace - binStart; + } + + // couldn't find zeros after binary section -- assume they're + // missing and the binary section extends to the end of the file + } else { + binLength = fontSize - binStart; + } + } + + binSection->append((char *)(font + binStart), binLength); + + return gTrue; +} + +// Split a Type 1 font in PFB format into a text section and a binary +// section. +GBool PSOutputDev::splitType1PFB(Guchar *font, int fontSize, + GString *textSection, GString *binSection) { + Guchar *p; + int state, remain, len, n; + + // states: + // 0: text section + // 1: binary section + // 2: trailer section + // 3: eof + + state = 0; + p = font; + remain = fontSize; + while (remain >= 2) { + if (p[0] != 0x80) { + return gFalse; + } + switch (state) { + case 0: + if (p[1] == 0x02) { + state = 1; + } else if (p[1] != 0x01) { + return gFalse; + } + break; + case 1: + if (p[1] == 0x01) { + state = 2; + } else if (p[1] != 0x02) { + return gFalse; + } + break; + case 2: + if (p[1] == 0x03) { + state = 3; + } else if (p[1] != 0x01) { + return gFalse; + } + break; + default: // shouldn't happen + return gFalse; + } + if (state == 3) { + break; + } + + if (remain < 6) { + break; + } + len = p[2] + (p[3] << 8) + (p[4] << 16) + (p[5] << 24); + if (len < 0 || len > remain - 6) { + return gFalse; + } + + switch (state) { + case 0: + textSection->append((char *)(p + 6), len); + break; + case 1: + binSection->append((char *)(p + 6), len); + break; + case 2: + // we don't use the trailer + break; + default: // shouldn't happen + return gFalse; + } + + p += len + 6; + remain -= len + 6; + } + + if (state != 3) { + return gFalse; + } + + n = textSection->getLength(); + if (n >= 18 && !memcmp(textSection->getCString() + n - 18, + "currentfile eexec", 17)) { + // remove the trailing whitespace + textSection->del(n - 1, 1); + } else if (n >= 17 && !memcmp(textSection->getCString() + n - 17, + "currentfile eexec", 17)) { + // missing whitespace at end -- leave as-is + } else { + return gFalse; + } + + return gTrue; +} + +// If is ASCIIHex-encoded, decode it, delete , and return the +// binary version. Else return unchanged. +GString *PSOutputDev::asciiHexDecodeType1EexecSection(GString *in) { + GString *out; + char c; + Guchar byte; + int state, i; + + out = new GString(); + state = 0; + byte = 0; + for (i = 0; i < in->getLength(); ++i) { + c = in->getChar(i); + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + continue; + } + if (c >= '0' && c <= '9') { + byte = (Guchar)(byte + (c - '0')); + } else if (c >= 'A' && c <= 'F') { + byte = (Guchar)(byte + (c - 'A' + 10)); + } else if (c >= 'a' && c <= 'f') { + byte = (Guchar)(byte + (c - 'a' + 10)); + } else { + delete out; + return in; + } + if (state == 0) { + byte = (Guchar)(byte << 4); + state = 1; + } else { + out->append((char)byte); + state = 0; + byte = 0; + } + } + delete in; + return out; +} + +GBool PSOutputDev::fixType1EexecSection(GString *binSection, GString *out) { + static char hexChars[17] = "0123456789abcdef"; + Guchar buf[16], buf2[16]; + Guchar byte; + int r, i, j; + + // eexec-decode the binary section, keeping the last 16 bytes + r = 55665; + for (i = 0; i < binSection->getLength(); ++i) { + byte = (Guchar)binSection->getChar(i); + buf[i & 15] = byte ^ (Guchar)(r >> 8); + r = ((r + byte) * 52845 + 22719) & 0xffff; + } + for (j = 0; j < 16; ++j) { + buf2[j] = buf[(i + j) & 15]; + } + + // look for 'closefile' + for (i = 0; i <= 16 - 9; ++i) { + if (!memcmp(buf2 + i, "closefile", 9)) { + break; + } + } + if (i > 16 - 9) { + return gFalse; + } + // three cases: + // - short: missing space after "closefile" (i == 16 - 9) + // - correct: exactly one space after "closefile" (i == 16 - 10) + // - long: extra chars after "closefile" (i < 16 - 10) + if (i == 16 - 9) { + binSection->append((char)((Guchar)'\n' ^ (Guchar)(r >> 8))); + } else if (i < 16 - 10) { + binSection->del(binSection->getLength() - (16 - 10 - i), 16 - 10 - i); + } + + // ASCIIHex encode + for (i = 0; i < binSection->getLength(); i += 32) { + for (j = 0; j < 32 && i+j < binSection->getLength(); ++j) { + byte = (Guchar)binSection->getChar(i+j); + out->append(hexChars[(byte >> 4) & 0x0f]); + out->append(hexChars[byte & 0x0f]); + } + out->append('\n'); + } + + return gTrue; +} + +// The Type 1 cleanup code failed -- assume it's a valid PFA-format +// font and copy it to the output. +GString *PSOutputDev::copyType1PFA(Guchar *font, int fontSize) { + GString *out; + + error(errSyntaxWarning, -1, "Couldn't parse embedded Type 1 font"); + + out = new GString((char *)font, fontSize); + // append a newline to avoid problems where the original font + // doesn't end with one + out->append('\n'); + return out; +} + +// The Type 1 cleanup code failed -- assume it's a valid PFB-format +// font, decode the PFB blocks, and copy them to the output. +GString *PSOutputDev::copyType1PFB(Guchar *font, int fontSize) { + static char hexChars[17] = "0123456789abcdef"; + GString *out; + Guchar *p; + int remain, len, i, j; + + error(errSyntaxWarning, -1, "Couldn't parse embedded Type 1 (PFB) font"); + + out = new GString(); + p = font; + remain = fontSize; + while (remain >= 6 && + p[0] == 0x80 && + (p[1] == 0x01 || p[1] == 0x02)) { + len = p[2] + (p[3] << 8) + (p[4] << 16) + (p[5] << 24); + if (len > remain - 6) { + break; + } + if (p[1] == 0x01) { + out->append((char *)(p + 6), len); + } else { + for (i = 0; i < len; i += 32) { + for (j = 0; j < 32 && i+j < len; ++j) { + out->append(hexChars[(p[6+i+j] >> 4) & 0x0f]); + out->append(hexChars[p[6+i+j] & 0x0f]); + } + out->append('\n'); + } + } + p += len + 6; + remain -= len + 6; + } + // append a newline to avoid problems where the original font + // doesn't end with one + out->append('\n'); + return out; +} + +void PSOutputDev::renameType1Font(GString *font, GString *name) { + char *p1, *p2; + int i; + + if (!(p1 = strstr(font->getCString(), "\n/FontName")) && + !(p1 = strstr(font->getCString(), "\r/FontName"))) { + return; + } + p1 += 10; + while (*p1 == ' ' || *p1 == '\t' || *p1 == '\n' || *p1 == '\r') { + ++p1; + } + if (*p1 != '/') { + return; + } + ++p1; + p2 = p1; + while (*p2 && *p2 != ' ' && *p2 != '\t' && *p2 != '\n' && *p2 != '\r') { + ++p2; + } + if (!*p2) { + return; + } + i = (int)(p1 - font->getCString()); + font->del(i, (int)(p2 - p1)); + font->insert(i, name); +} + +void PSOutputDev::setupDefaultFont() { + writePS("/xpdf_default_font /Helvetica 1 1 ISOLatin1Encoding pdfMakeFont\n"); +} + +void PSOutputDev::setupImages(Dict *resDict) { + Object xObjDict, xObj, xObjRef, subtypeObj, maskObj, maskRef; + Ref imgID; + int i, j; + + if (!(mode == psModeForm || inType3Char || preload)) { + return; + } + + resDict->lookup("XObject", &xObjDict); + if (xObjDict.isDict()) { + for (i = 0; i < xObjDict.dictGetLength(); ++i) { + xObjDict.dictGetValNF(i, &xObjRef); + xObjDict.dictGetVal(i, &xObj); + if (xObj.isStream()) { + xObj.streamGetDict()->lookup("Subtype", &subtypeObj); + if (subtypeObj.isName("Image")) { + if (xObjRef.isRef()) { + imgID = xObjRef.getRef(); + for (j = 0; j < imgIDLen; ++j) { + if (imgIDs[j].num == imgID.num && imgIDs[j].gen == imgID.gen) { + break; + } + } + if (j == imgIDLen) { + if (imgIDLen >= imgIDSize) { + if (imgIDSize == 0) { + imgIDSize = 64; + } else { + imgIDSize *= 2; + } + imgIDs = (Ref *)greallocn(imgIDs, imgIDSize, sizeof(Ref)); + } + imgIDs[imgIDLen++] = imgID; + setupImage(imgID, xObj.getStream(), gFalse, NULL); + if (level >= psLevel3) { + xObj.streamGetDict()->lookup("Mask", &maskObj); + if (maskObj.isStream()) { + setupImage(imgID, maskObj.getStream(), gTrue, NULL); + } else if (level == psLevel3Gray && maskObj.isArray()) { + setupImage(imgID, xObj.getStream(), gFalse, + maskObj.getArray()); + } + maskObj.free(); + } + } + } else { + error(errSyntaxError, -1, + "Image in resource dict is not an indirect reference"); + } + } + subtypeObj.free(); + } + xObj.free(); + xObjRef.free(); + } + } + xObjDict.free(); +} + +void PSOutputDev::setupImage(Ref id, Stream *str, GBool mask, + Array *colorKeyMask) { + StreamColorSpaceMode csMode; + GfxColorSpace *colorSpace; + GfxImageColorMap *colorMap; + int maskColors[2*gfxColorMaxComps]; + Object obj1; + GBool imageMask, useLZW, useRLE, useCompressed, useASCIIHex; + GString *s; + int c, width, height, bits, size, line, col, i; + + // check for mask + str->getDict()->lookup("ImageMask", &obj1); + if (obj1.isBool()) { + imageMask = obj1.getBool(); + } else { + imageMask = gFalse; + } + obj1.free(); + + // get image size + str->getDict()->lookup("Width", &obj1); + if (!obj1.isInt() || obj1.getInt() <= 0) { + error(errSyntaxError, -1, "Invalid Width in image"); + obj1.free(); + return; + } + width = obj1.getInt(); + obj1.free(); + str->getDict()->lookup("Height", &obj1); + if (!obj1.isInt() || obj1.getInt() <= 0) { + error(errSyntaxError, -1, "Invalid Height in image"); + obj1.free(); + return; + } + height = obj1.getInt(); + obj1.free(); + + // build the color map + if (mask || imageMask) { + colorMap = NULL; + } else { + bits = 0; + csMode = streamCSNone; + str->getImageParams(&bits, &csMode); + if (bits == 0) { + str->getDict()->lookup("BitsPerComponent", &obj1); + if (!obj1.isInt()) { + error(errSyntaxError, -1, "Invalid BitsPerComponent in image"); + obj1.free(); + return; + } + bits = obj1.getInt(); + obj1.free(); + } + str->getDict()->lookup("ColorSpace", &obj1); + if (!obj1.isNull()) { + colorSpace = GfxColorSpace::parse(&obj1 + ); + } else if (csMode == streamCSDeviceGray) { + colorSpace = GfxColorSpace::create(csDeviceGray); + } else if (csMode == streamCSDeviceRGB) { + colorSpace = GfxColorSpace::create(csDeviceRGB); + } else if (csMode == streamCSDeviceCMYK) { + colorSpace = GfxColorSpace::create(csDeviceCMYK); + } else { + colorSpace = NULL; + } + obj1.free(); + if (!colorSpace) { + error(errSyntaxError, -1, "Invalid ColorSpace in image"); + return; + } + str->getDict()->lookup("Decode", &obj1); + colorMap = new GfxImageColorMap(bits, &obj1, colorSpace); + obj1.free(); + } + + // filters + if (level < psLevel2) { + useLZW = useRLE = gFalse; + useCompressed = gFalse; + useASCIIHex = gTrue; + } else { + if (colorKeyMask) { + if (globalParams->getPSUncompressPreloadedImages()) { + useLZW = useRLE = gFalse; + } else if (globalParams->getPSLZW()) { + useLZW = gTrue; + useRLE = gFalse; + } else { + useRLE = gTrue; + useLZW = gFalse; + } + useCompressed = gFalse; + } else if (colorMap && + (colorMap->getColorSpace()->getMode() == csDeviceN || + level == psLevel2Gray || level == psLevel3Gray)) { + if (globalParams->getPSLZW()) { + useLZW = gTrue; + useRLE = gFalse; + } else { + useRLE = gTrue; + useLZW = gFalse; + } + useCompressed = gFalse; + } else if (globalParams->getPSUncompressPreloadedImages()) { + useLZW = useRLE = gFalse; + useCompressed = gFalse; + } else { + s = str->getPSFilter(level < psLevel3 ? 2 : 3, ""); + if (s) { + useLZW = useRLE = gFalse; + useCompressed = gTrue; + delete s; + } else { + if (globalParams->getPSLZW()) { + useLZW = gTrue; + useRLE = gFalse; + } else { + useRLE = gTrue; + useLZW = gFalse; + } + useCompressed = gFalse; + } + } + useASCIIHex = globalParams->getPSASCIIHex(); + } + if (useCompressed) { + str = str->getUndecodedStream(); + } + if (colorKeyMask) { + memset(maskColors, 0, sizeof(maskColors)); + for (i = 0; i < colorKeyMask->getLength() && i < 2*gfxColorMaxComps; ++i) { + colorKeyMask->get(i, &obj1); + if (obj1.isInt()) { + maskColors[i] = obj1.getInt(); + } + obj1.free(); + } + str = new ColorKeyToMaskEncoder(str, width, height, colorMap, maskColors); + } else if (colorMap && (level == psLevel2Gray || level == psLevel3Gray)) { + str = new GrayRecoder(str, width, height, colorMap); + } else if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) { + str = new DeviceNRecoder(str, width, height, colorMap); + } + if (useLZW) { + str = new LZWEncoder(str); + } else if (useRLE) { + str = new RunLengthEncoder(str); + } + if (useASCIIHex) { + str = new ASCIIHexEncoder(str); + } else { + str = new ASCII85Encoder(str); + } + + // compute image data size + str->reset(); + col = size = 0; + do { + do { + c = str->getChar(); + } while (c == '\n' || c == '\r'); + if (c == (useASCIIHex ? '>' : '~') || c == EOF) { + break; + } + if (c == 'z') { + ++col; + } else { + ++col; + for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) { + do { + c = str->getChar(); + } while (c == '\n' || c == '\r'); + if (c == (useASCIIHex ? '>' : '~') || c == EOF) { + break; + } + ++col; + } + } + if (col > 225) { + ++size; + col = 0; + } + } while (c != (useASCIIHex ? '>' : '~') && c != EOF); + // add one entry for the final line of data; add another entry + // because the LZWDecode/RunLengthDecode filter may read past the end + ++size; + if (useLZW || useRLE) { + ++size; + } + writePSFmt("{0:d} array dup /{1:s}Data_{2:d}_{3:d} exch def\n", + size, (mask || colorKeyMask) ? "Mask" : "Im", id.num, id.gen); + str->close(); + + // write the data into the array + str->reset(); + line = col = 0; + writePS((char *)(useASCIIHex ? "dup 0 <" : "dup 0 <~")); + do { + do { + c = str->getChar(); + } while (c == '\n' || c == '\r'); + if (c == (useASCIIHex ? '>' : '~') || c == EOF) { + break; + } + if (c == 'z') { + writePSChar((char)c); + ++col; + } else { + writePSChar((char)c); + ++col; + for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) { + do { + c = str->getChar(); + } while (c == '\n' || c == '\r'); + if (c == (useASCIIHex ? '>' : '~') || c == EOF) { + break; + } + writePSChar((char)c); + ++col; + } + } + // each line is: "dup nnnnn <~...data...~> put" + // so max data length = 255 - 20 = 235 + // chunks are 1 or 4 bytes each, so we have to stop at 232 + // but make it 225 just to be safe + if (col > 225) { + writePS((char *)(useASCIIHex ? "> put\n" : "~> put\n")); + ++line; + writePSFmt((char *)(useASCIIHex ? "dup {0:d} <" : "dup {0:d} <~"), line); + col = 0; + } + } while (c != (useASCIIHex ? '>' : '~') && c != EOF); + writePS((char *)(useASCIIHex ? "> put\n" : "~> put\n")); + if (useLZW || useRLE) { + ++line; + writePSFmt("{0:d} <> put\n", line); + } else { + writePS("pop\n"); + } + str->close(); + + delete str; + + if (colorMap) { + delete colorMap; + } +} + +void PSOutputDev::setupForms(Dict *resDict) { + Object xObjDict, xObj, xObjRef, subtypeObj; + int i; + + if (!preload) { + return; + } + + resDict->lookup("XObject", &xObjDict); + if (xObjDict.isDict()) { + for (i = 0; i < xObjDict.dictGetLength(); ++i) { + xObjDict.dictGetValNF(i, &xObjRef); + xObjDict.dictGetVal(i, &xObj); + if (xObj.isStream()) { + xObj.streamGetDict()->lookup("Subtype", &subtypeObj); + if (subtypeObj.isName("Form")) { + if (xObjRef.isRef()) { + setupForm(&xObjRef, &xObj); + } else { + error(errSyntaxError, -1, + "Form in resource dict is not an indirect reference"); + } + } + subtypeObj.free(); + } + xObj.free(); + xObjRef.free(); + } + } + xObjDict.free(); +} + +void PSOutputDev::setupForm(Object *strRef, Object *strObj) { + Dict *dict, *resDict; + Object matrixObj, bboxObj, resObj, obj1; + double m[6], bbox[4]; + PDFRectangle box; + Gfx *gfx; + int i; + + // check if form is already defined + for (i = 0; i < formIDLen; ++i) { + if (formIDs[i].num == strRef->getRefNum() && + formIDs[i].gen == strRef->getRefGen()) { + return; + } + } + + // add entry to formIDs list + if (formIDLen >= formIDSize) { + if (formIDSize == 0) { + formIDSize = 64; + } else { + formIDSize *= 2; + } + formIDs = (Ref *)greallocn(formIDs, formIDSize, sizeof(Ref)); + } + formIDs[formIDLen++] = strRef->getRef(); + + dict = strObj->streamGetDict(); + + // get bounding box + dict->lookup("BBox", &bboxObj); + if (!bboxObj.isArray()) { + bboxObj.free(); + error(errSyntaxError, -1, "Bad form bounding box"); + return; + } + for (i = 0; i < 4; ++i) { + bboxObj.arrayGet(i, &obj1); + bbox[i] = obj1.getNum(); + obj1.free(); + } + bboxObj.free(); + + // get matrix + dict->lookup("Matrix", &matrixObj); + if (matrixObj.isArray()) { + for (i = 0; i < 6; ++i) { + matrixObj.arrayGet(i, &obj1); + m[i] = obj1.getNum(); + obj1.free(); + } + } else { + m[0] = 1; m[1] = 0; + m[2] = 0; m[3] = 1; + m[4] = 0; m[5] = 0; + } + matrixObj.free(); + + // get resources + dict->lookup("Resources", &resObj); + resDict = resObj.isDict() ? resObj.getDict() : (Dict *)NULL; + + writePSFmt("/f_{0:d}_{1:d} {{\n", 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]); + + box.x1 = bbox[0]; + box.y1 = bbox[1]; + box.x2 = bbox[2]; + box.y2 = bbox[3]; + gfx = new Gfx(doc, this, resDict, &box, &box); + gfx->display(strRef); + delete gfx; + + writePS("Q\n"); + writePS("} def\n"); + + resObj.free(); +} + +GBool PSOutputDev::checkPageSlice(Page *page, double hDPI, double vDPI, + int rotateA, GBool useMediaBox, GBool crop, + int sliceX, int sliceY, + int sliceW, int sliceH, + GBool printing, + GBool (*abortCheckCbk)(void *data), + void *abortCheckCbkData) { + int pg; +#if HAVE_SPLASH + GBool mono; + GBool useLZW; + double dpi; + SplashOutputDev *splashOut; + SplashColor paperColor; + PDFRectangle box; + GfxState *state; + SplashBitmap *bitmap; + Stream *str0, *str; + Object obj; + Guchar *p; + Guchar col[4]; + char buf[4096]; + double userUnit, hDPI2, vDPI2; + double m0, m1, m2, m3, m4, m5; + int nStripes, stripeH, stripeY; + int w, h, x, y, comp, i, n; +#endif + + pg = page->getNum(); + if (!(pg >= firstPage && pg <= lastPage && + rasterizePage[pg - firstPage])) { + return gTrue; + } + +#if HAVE_SPLASH + // get the rasterization parameters + dpi = globalParams->getPSRasterResolution(); + mono = globalParams->getPSRasterMono() || + level == psLevel1 || + level == psLevel2Gray || + level == psLevel3Gray; + useLZW = globalParams->getPSLZW(); + + // get the UserUnit + if (honorUserUnit) { + userUnit = page->getUserUnit(); + } else { + userUnit = 1; + } + + // start the PS page + page->makeBox(userUnit * dpi, userUnit * dpi, rotateA, useMediaBox, gFalse, + sliceX, sliceY, sliceW, sliceH, &box, &crop); + rotateA += page->getRotate(); + if (rotateA >= 360) { + rotateA -= 360; + } else if (rotateA < 0) { + rotateA += 360; + } + state = new GfxState(dpi, dpi, &box, rotateA, gFalse); + startPage(page->getNum(), state); + delete state; + + // set up the SplashOutputDev + if (mono) { + paperColor[0] = 0xff; + splashOut = new SplashOutputDev(splashModeMono8, 1, gFalse, + paperColor, gFalse, + globalParams->getAntialiasPrinting()); +#if SPLASH_CMYK + } else if (level == psLevel1Sep) { + paperColor[0] = paperColor[1] = paperColor[2] = paperColor[3] = 0; + splashOut = new SplashOutputDev(splashModeCMYK8, 1, gFalse, + paperColor, gFalse, + globalParams->getAntialiasPrinting()); +#endif + } else { + paperColor[0] = paperColor[1] = paperColor[2] = 0xff; + splashOut = new SplashOutputDev(splashModeRGB8, 1, gFalse, + paperColor, gFalse, + globalParams->getAntialiasPrinting()); + } + splashOut->startDoc(xref); + + // break the page into stripes + // NB: startPage() has already multiplied xScale and yScale by UserUnit + hDPI2 = xScale * dpi; + vDPI2 = yScale * dpi; + if (sliceW < 0 || sliceH < 0) { + if (useMediaBox) { + box = *page->getMediaBox(); + } else { + box = *page->getCropBox(); + } + sliceX = sliceY = 0; + sliceW = (int)((box.x2 - box.x1) * hDPI2 / 72.0); + sliceH = (int)((box.y2 - box.y1) * vDPI2 / 72.0); + } + nStripes = (int)ceil(((double)sliceW * (double)sliceH) / + (double)globalParams->getPSRasterSliceSize()); + stripeH = (sliceH + nStripes - 1) / nStripes; // cppcheck-suppress bughuntingDivByZero + + // render the stripes + for (stripeY = sliceY; stripeY < sliceH; stripeY += stripeH) { + + // rasterize a stripe + page->makeBox(hDPI2, vDPI2, 0, useMediaBox, gFalse, + sliceX, stripeY, sliceW, stripeH, &box, &crop); + m0 = box.x2 - box.x1; + m1 = 0; + m2 = 0; + m3 = box.y2 - box.y1; + m4 = box.x1; + m5 = box.y1; + page->displaySlice(splashOut, hDPI2, vDPI2, + (360 - page->getRotate()) % 360, useMediaBox, crop, + sliceX, stripeY, sliceW, stripeH, + printing, abortCheckCbk, abortCheckCbkData); + + // draw the rasterized image + bitmap = splashOut->getBitmap(); + w = bitmap->getWidth(); + h = bitmap->getHeight(); + writePS("gsave\n"); + writePSFmt("[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] concat\n", + m0, m1, m2, m3, m4, m5); + switch (level) { + case psLevel1: + writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1\n", + w, h, w, -h, h); + p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize(); + i = 0; + for (y = 0; y < h; ++y) { + for (x = 0; x < w; ++x) { + writePSFmt("{0:02x}", *p++); + if (++i == 32) { + writePSChar('\n'); + i = 0; + } + } + } + if (i != 0) { + writePSChar('\n'); + } + break; + case psLevel1Sep: + writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1Sep\n", + w, h, w, -h, h); + p = bitmap->getDataPtr() + (h - 1) * bitmap->getRowSize(); + i = 0; + col[0] = col[1] = col[2] = col[3] = 0; + for (y = 0; y < h; ++y) { + for (comp = 0; comp < 4; ++comp) { + for (x = 0; x < w; ++x) { + writePSFmt("{0:02x}", p[4*x + comp]); + col[comp] |= p[4*x + comp]; + if (++i == 32) { + writePSChar('\n'); + i = 0; + } + } + } + p -= bitmap->getRowSize(); + } + if (i != 0) { + writePSChar('\n'); + } + if (col[0]) { + processColors |= psProcessCyan; + } + if (col[1]) { + processColors |= psProcessMagenta; + } + if (col[2]) { + processColors |= psProcessYellow; + } + if (col[3]) { + processColors |= psProcessBlack; + } + break; + case psLevel2: + case psLevel2Gray: + case psLevel2Sep: + case psLevel3: + case psLevel3Gray: + case psLevel3Sep: + if (mono) { + writePS("/DeviceGray setcolorspace\n"); + } else { + writePS("/DeviceRGB setcolorspace\n"); + } + writePS("<<\n /ImageType 1\n"); + writePSFmt(" /Width {0:d}\n", bitmap->getWidth()); + writePSFmt(" /Height {0:d}\n", bitmap->getHeight()); + writePSFmt(" /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n", w, -h, h); + writePS(" /BitsPerComponent 8\n"); + if (mono) { + writePS(" /Decode [0 1]\n"); + } else { + writePS(" /Decode [0 1 0 1 0 1]\n"); + } + writePS(" /DataSource currentfile\n"); + if (globalParams->getPSASCIIHex()) { + writePS(" /ASCIIHexDecode filter\n"); + } else { + writePS(" /ASCII85Decode filter\n"); + } + 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); + 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 ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); + } + str->close(); + delete str; + delete str0; + writePSChar('\n'); + processColors |= mono ? psProcessBlack : psProcessCMYK; + break; + } + writePS("grestore\n"); + } + + delete splashOut; + + // finish the PS page + endPage(); + + return gFalse; +#else // HAVE_SPLASH + + error(errSyntaxWarning, -1, + "PDF page uses transparency and PSOutputDev was built without" + " the Splash rasterizer - output may not be correct"); + return gTrue; +#endif // HAVE_SPLASH +} + +void PSOutputDev::startPage(int pageNum, GfxState *state) { + Page *page; + double userUnit; + int x1, y1, x2, y2, width, height, t; + int imgWidth, imgHeight, imgWidth2, imgHeight2; + GBool landscape; + GString *s; + + page = doc->getCatalog()->getPage(pageNum); + if (honorUserUnit) { + userUnit = page->getUserUnit(); + } else { + userUnit = 1; + } + + if (mode == psModePS) { + writePSFmt("%%Page: {0:d} {1:d}\n", pageNum, seqPage); + if (paperMatch) { + imgLLX = imgLLY = 0; + if (globalParams->getPSUseCropBoxAsPage()) { + imgURX = (int)ceil(page->getCropWidth() * userUnit); + imgURY = (int)ceil(page->getCropHeight() * userUnit); + } else { + imgURX = (int)ceil(page->getMediaWidth() * userUnit); + imgURY = (int)ceil(page->getMediaHeight() * userUnit); + } + if (state->getRotate() == 90 || state->getRotate() == 270) { + t = imgURX; + imgURX = imgURY; + imgURY = t; + } + writePSFmt("%%PageMedia: {0:d}x{1:d}\n", imgURX, imgURY); + writePSFmt("%%PageBoundingBox: 0 0 {0:d} {1:d}\n", imgURX, imgURY); + } + writePS("%%BeginPageSetup\n"); + } + if (mode != psModeForm) { + writePS("xpdf begin\n"); + } + + // set up paper size for paper=match mode + // NB: this must be done *before* the saveState() for overlays. + if (mode == psModePS && paperMatch) { + writePSFmt("{0:d} {1:d} pdfSetupPaper\n", imgURX, imgURY); + } + + // underlays + if (underlayCbk) { + (*underlayCbk)(this, underlayCbkData); + } + if (overlayCbk) { + saveState(NULL); + } + + switch (mode) { + + case psModePS: + // rotate, translate, and scale page + imgWidth = imgURX - imgLLX; + imgHeight = imgURY - imgLLY; + x1 = (int)floor(state->getX1()); + y1 = (int)floor(state->getY1()); + x2 = (int)ceil(state->getX2()); + y2 = (int)ceil(state->getY2()); + width = x2 - x1; + height = y2 - y1; + tx = ty = 0; + // rotation and portrait/landscape mode + if (paperMatch) { + rotate = (360 - state->getRotate()) % 360; + landscape = gFalse; + } else if (rotate0 >= 0) { + rotate = (360 - rotate0) % 360; + landscape = gFalse; + } else { + rotate = (360 - state->getRotate()) % 360; + if (rotate == 0 || rotate == 180) { + if ((width < height && imgWidth > imgHeight && height > imgHeight) || + (width > height && imgWidth < imgHeight && width > imgWidth)) { + rotate += 90; + landscape = gTrue; + } else { + landscape = gFalse; + } + } else { // rotate == 90 || rotate == 270 + if ((height < width && imgWidth > imgHeight && width > imgHeight) || + (height > width && imgWidth < imgHeight && height > imgWidth)) { + rotate = 270 - rotate; + landscape = gTrue; + } else { + landscape = gFalse; + } + } + } + writePSFmt("%%PageOrientation: {0:s}\n", + landscape ? "Landscape" : "Portrait"); + writePS("pdfStartPage\n"); + if (rotate == 0) { + imgWidth2 = imgWidth; + imgHeight2 = imgHeight; + } else if (rotate == 90) { + writePS("90 rotate\n"); + ty = -imgWidth; + imgWidth2 = imgHeight; + imgHeight2 = imgWidth; + } else if (rotate == 180) { + writePS("180 rotate\n"); + imgWidth2 = imgWidth; + imgHeight2 = imgHeight; + tx = -imgWidth; + ty = -imgHeight; + } else { // rotate == 270 + writePS("270 rotate\n"); + tx = -imgHeight; + imgWidth2 = imgHeight; + imgHeight2 = imgWidth; + } + // shrink or expand + if (xScale0 > 0 && yScale0 > 0) { + xScale = xScale0 * userUnit; + yScale = yScale0 * userUnit; + } else if ((globalParams->getPSShrinkLarger() && + (width * userUnit > imgWidth2 || + height * userUnit > imgHeight2)) || + (globalParams->getPSExpandSmaller() && + (width * userUnit < imgWidth2 && + height * userUnit < imgHeight2))) { + xScale = (double)imgWidth2 / (double)width; + yScale = (double)imgHeight2 / (double)height; + if (yScale < xScale) { + xScale = yScale; + } else { + yScale = xScale; + } + } else { + xScale = yScale = userUnit; + } + // deal with odd bounding boxes or clipping + if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) { + tx -= xScale * clipLLX0; + ty -= yScale * clipLLY0; + } else { + tx -= xScale * x1; + ty -= yScale * y1; + } + // center + if (tx0 >= 0 && ty0 >= 0) { + tx += (rotate == 0 || rotate == 180) ? tx0 : ty0; + ty += (rotate == 0 || rotate == 180) ? ty0 : -tx0; + } else if (globalParams->getPSCenter()) { + if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) { + tx += (imgWidth2 - xScale * (clipURX0 - clipLLX0)) / 2; + ty += (imgHeight2 - yScale * (clipURY0 - clipLLY0)) / 2; + } else { + tx += (imgWidth2 - xScale * width) / 2; + ty += (imgHeight2 - yScale * height) / 2; + } + } + tx += (rotate == 0 || rotate == 180) ? imgLLX : imgLLY; + ty += (rotate == 0 || rotate == 180) ? imgLLY : -imgLLX; + if (tx != 0 || ty != 0) { + writePSFmt("{0:.6g} {1:.6g} translate\n", tx, ty); + } + if (xScale != 1 || yScale != 1) { + writePSFmt("{0:.4f} {1:.4f} scale\n", xScale, yScale); + } + if (clipLLX0 < clipURX0 && clipLLY0 < clipURY0) { + writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} re W\n", + clipLLX0, clipLLY0, clipURX0 - clipLLX0, clipURY0 - clipLLY0); + } else { + writePSFmt("{0:d} {1:d} {2:d} {3:d} re W\n", x1, y1, x2 - x1, y2 - y1); + } + + ++seqPage; + break; + + case psModeEPS: + writePS("pdfStartPage\n"); + tx = ty = 0; + rotate = (360 - state->getRotate()) % 360; + if (rotate == 0) { + } else if (rotate == 90) { + writePS("90 rotate\n"); + tx = -epsX1; + ty = -epsY2; + } else if (rotate == 180) { + writePS("180 rotate\n"); + tx = -(epsX1 + epsX2); + ty = -(epsY1 + epsY2); + } else { // rotate == 270 + writePS("270 rotate\n"); + tx = -epsX2; + ty = -epsY1; + } + if (tx != 0 || ty != 0) { + writePSFmt("{0:.6g} {1:.6g} translate\n", tx, ty); + } + xScale = yScale = 1; + break; + + case psModeForm: + writePS("/PaintProc {\n"); + writePS("begin xpdf begin\n"); + writePS("pdfStartPage\n"); + tx = ty = 0; + xScale = yScale = 1; + rotate = 0; + break; + } + + if (level == psLevel2Gray || level == psLevel3Gray) { + writePS("/DeviceGray setcolorspace\n"); + } + + if (customCodeCbk) { + if ((s = (*customCodeCbk)(this, psOutCustomPageSetup, pageNum, + customCodeCbkData))) { + writePS(s->getCString()); + delete s; + } + } + + if (mode == psModePS) { + writePS("%%EndPageSetup\n"); + } + + noStateChanges = gFalse; +} + +void PSOutputDev::endPage() { + if (overlayCbk) { + restoreState(NULL); + (*overlayCbk)(this, overlayCbkData); + } + + if (mode == psModeForm) { + writePS("pdfEndPage\n"); + writePS("end end\n"); + writePS("} def\n"); + writePS("end end\n"); + } else { + if (!manualCtrl) { + writePS("showpage\n"); + } + writePS("%%PageTrailer\n"); + writePageTrailer(); + writePS("end\n"); + } +} + +void PSOutputDev::saveState(GfxState *state) { + // The noStateChanges and saveStack fields are used to implement an + // optimization to reduce gsave/grestore nesting. The idea is to + // look for sequences like this: + // q q AAA Q BBB Q (where AAA and BBB are sequences of operations) + // and transform them to: + // q AAA Q q BBB Q + if (noStateChanges) { + // any non-NULL pointer will work here + saveStack->append(this); + } else { + saveStack->append((PSOutputDev *)NULL); + writePS("q\n"); + noStateChanges = gTrue; + } +} + +void PSOutputDev::restoreState(GfxState *state) { + if (saveStack->getLength()) { + writePS("Q\n"); + if (saveStack->del(saveStack->getLength() - 1)) { + writePS("q\n"); + noStateChanges = gTrue; + } else { + noStateChanges = gFalse; + } + } +} + +void PSOutputDev::updateCTM(GfxState *state, double m11, double m12, + double m21, double m22, double m31, double m32) { + if (m11 == 1 && m12 == 0 && m21 == 0 && m22 == 1 && m31 == 0 && m32 == 0) { + return; + } + if (fabs(m11 * m22 - m12 * m21) < 1e-10) { + // avoid a singular (or close-to-singular) matrix + writePSFmt("[0.00001 0 0 0.00001 {0:.6g} {1:.6g}] cm\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); + } + noStateChanges = gFalse; +} + +void PSOutputDev::updateLineDash(GfxState *state) { + double *dash; + double start; + int length, i; + + state->getLineDash(&dash, &length, &start); + writePS("["); + for (i = 0; i < length; ++i) { + writePSFmt("{0:.6g}{1:w}", + dash[i] < 0 ? 0 : dash[i], + (i == length-1) ? 0 : 1); + } + writePSFmt("] {0:.6g} d\n", start); + noStateChanges = gFalse; +} + +void PSOutputDev::updateFlatness(GfxState *state) { + writePSFmt("{0:.4g} i\n", state->getFlatness()); + noStateChanges = gFalse; +} + +void PSOutputDev::updateLineJoin(GfxState *state) { + writePSFmt("{0:d} j\n", state->getLineJoin()); + noStateChanges = gFalse; +} + +void PSOutputDev::updateLineCap(GfxState *state) { + writePSFmt("{0:d} J\n", state->getLineCap()); + noStateChanges = gFalse; +} + +void PSOutputDev::updateMiterLimit(GfxState *state) { + writePSFmt("{0:.4g} M\n", state->getMiterLimit()); + noStateChanges = gFalse; +} + +void PSOutputDev::updateLineWidth(GfxState *state) { + writePSFmt("{0:.6g} w\n", state->getLineWidth()); + noStateChanges = gFalse; +} + +void PSOutputDev::updateFillColorSpace(GfxState *state) { + switch (level) { + case psLevel1: + case psLevel1Sep: + break; + case psLevel2: + case psLevel3: + if (state->getFillColorSpace()->getMode() != csPattern) { + dumpColorSpaceL2(state, state->getFillColorSpace(), + gTrue, gFalse, gFalse); + writePS(" cs\n"); + noStateChanges = gFalse; + } + break; + case psLevel2Gray: + case psLevel3Gray: + case psLevel2Sep: + case psLevel3Sep: + break; + } +} + +void PSOutputDev::updateStrokeColorSpace(GfxState *state) { + switch (level) { + case psLevel1: + case psLevel1Sep: + break; + case psLevel2: + case psLevel3: + if (state->getStrokeColorSpace()->getMode() != csPattern) { + dumpColorSpaceL2(state, state->getStrokeColorSpace(), + gTrue, gFalse, gFalse); + writePS(" CS\n"); + noStateChanges = gFalse; + } + break; + case psLevel2Gray: + case psLevel3Gray: + case psLevel2Sep: + case psLevel3Sep: + break; + } +} + +void PSOutputDev::updateFillColor(GfxState *state) { + GfxColor color; + GfxColor *colorPtr; + GfxGray gray; + GfxCMYK cmyk; + GfxSeparationColorSpace *sepCS; + double c, m, y, k; + int i; + + switch (level) { + case psLevel1: + case psLevel2Gray: + case psLevel3Gray: + state->getFillGray(&gray); + writePSFmt("{0:.4g} g\n", colToDbl(gray)); + break; + case psLevel1Sep: + state->getFillCMYK(&cmyk); + c = colToDbl(cmyk.c); + m = colToDbl(cmyk.m); + y = colToDbl(cmyk.y); + k = colToDbl(cmyk.k); + writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} k\n", c, m, y, k); + addProcessColor(c, m, y, k); + break; + case psLevel2: + case psLevel3: + if (state->getFillColorSpace()->getMode() != csPattern) { + colorPtr = state->getFillColor(); + writePS("["); + for (i = 0; i < state->getFillColorSpace()->getNComps(); ++i) { + if (i > 0) { + writePS(" "); + } + writePSFmt("{0:.4g}", colToDbl(colorPtr->c[i])); + } + writePS("] sc\n"); + } + break; + case psLevel2Sep: + case psLevel3Sep: + if (state->getFillColorSpace()->getMode() == csSeparation) { + sepCS = (GfxSeparationColorSpace *)state->getFillColorSpace(); + color.c[0] = gfxColorComp1; + sepCS->getCMYK(&color, &cmyk, state->getRenderingIntent()); + writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) ck\n", + colToDbl(state->getFillColor()->c[0]), + colToDbl(cmyk.c), colToDbl(cmyk.m), + colToDbl(cmyk.y), colToDbl(cmyk.k), + sepCS->getName()); + addCustomColor(state, sepCS); + } else { + state->getFillCMYK(&cmyk); + c = colToDbl(cmyk.c); + m = colToDbl(cmyk.m); + y = colToDbl(cmyk.y); + k = colToDbl(cmyk.k); + writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} k\n", c, m, y, k); + addProcessColor(c, m, y, k); + } + break; + } + t3Cacheable = gFalse; + noStateChanges = gFalse; +} + +void PSOutputDev::updateStrokeColor(GfxState *state) { + GfxColor color; + GfxColor *colorPtr; + GfxGray gray; + GfxCMYK cmyk; + GfxSeparationColorSpace *sepCS; + double c, m, y, k; + int i; + + switch (level) { + case psLevel1: + case psLevel2Gray: + case psLevel3Gray: + state->getStrokeGray(&gray); + writePSFmt("{0:.4g} G\n", colToDbl(gray)); + break; + case psLevel1Sep: + state->getStrokeCMYK(&cmyk); + c = colToDbl(cmyk.c); + m = colToDbl(cmyk.m); + y = colToDbl(cmyk.y); + k = colToDbl(cmyk.k); + writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} K\n", c, m, y, k); + addProcessColor(c, m, y, k); + break; + case psLevel2: + case psLevel3: + if (state->getStrokeColorSpace()->getMode() != csPattern) { + colorPtr = state->getStrokeColor(); + writePS("["); + for (i = 0; i < state->getStrokeColorSpace()->getNComps(); ++i) { + if (i > 0) { + writePS(" "); + } + writePSFmt("{0:.4g}", colToDbl(colorPtr->c[i])); + } + writePS("] SC\n"); + } + break; + case psLevel2Sep: + case psLevel3Sep: + if (state->getStrokeColorSpace()->getMode() == csSeparation) { + sepCS = (GfxSeparationColorSpace *)state->getStrokeColorSpace(); + color.c[0] = gfxColorComp1; + sepCS->getCMYK(&color, &cmyk, state->getRenderingIntent()); + writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} ({5:t}) CK\n", + colToDbl(state->getStrokeColor()->c[0]), + colToDbl(cmyk.c), colToDbl(cmyk.m), + colToDbl(cmyk.y), colToDbl(cmyk.k), + sepCS->getName()); + addCustomColor(state, sepCS); + } else { + state->getStrokeCMYK(&cmyk); + c = colToDbl(cmyk.c); + m = colToDbl(cmyk.m); + y = colToDbl(cmyk.y); + k = colToDbl(cmyk.k); + writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} K\n", c, m, y, k); + addProcessColor(c, m, y, k); + } + break; + } + t3Cacheable = gFalse; + noStateChanges = gFalse; +} + +void PSOutputDev::addProcessColor(double c, double m, double y, double k) { + if (c > 0) { + processColors |= psProcessCyan; + } + if (m > 0) { + processColors |= psProcessMagenta; + } + if (y > 0) { + processColors |= psProcessYellow; + } + if (k > 0) { + processColors |= psProcessBlack; + } +} + +void PSOutputDev::addCustomColor(GfxState *state, + GfxSeparationColorSpace *sepCS) { + PSOutCustomColor *cc; + GfxColor color; + GfxCMYK cmyk; + + for (cc = customColors; cc; cc = cc->next) { + if (!cc->name->cmp(sepCS->getName())) { + return; + } + } + color.c[0] = gfxColorComp1; + sepCS->getCMYK(&color, &cmyk, state->getRenderingIntent()); + cc = new PSOutCustomColor(colToDbl(cmyk.c), colToDbl(cmyk.m), + colToDbl(cmyk.y), colToDbl(cmyk.k), + sepCS->getName()->copy()); + cc->next = customColors; + customColors = cc; +} + +void PSOutputDev::addCustomColors(GfxState *state, + GfxDeviceNColorSpace *devnCS) { + PSOutCustomColor *cc; + GfxColor color; + GfxCMYK cmyk; + int i; + + for (i = 0; i < devnCS->getNComps(); ++i) { + color.c[i] = 0; + } + for (i = 0; i < devnCS->getNComps(); ++i) { + for (cc = customColors; cc; cc = cc->next) { + if (!cc->name->cmp(devnCS->getColorantName(i))) { + break; + } + } + if (cc) { + continue; + } + color.c[i] = gfxColorComp1; + devnCS->getCMYK(&color, &cmyk, state->getRenderingIntent()); + color.c[i] = 0; + cc = new PSOutCustomColor(colToDbl(cmyk.c), colToDbl(cmyk.m), + colToDbl(cmyk.y), colToDbl(cmyk.k), + devnCS->getColorantName(i)->copy()); + cc->next = customColors; + customColors = cc; + } +} + +void PSOutputDev::updateFillOverprint(GfxState *state) { + if (level == psLevel2 || level == psLevel2Sep || + level == psLevel3 || level == psLevel3Sep) { + writePSFmt("{0:s} op\n", state->getFillOverprint() ? "true" : "false"); + noStateChanges = gFalse; + } +} + +void PSOutputDev::updateStrokeOverprint(GfxState *state) { + if (level == psLevel2 || level == psLevel2Sep || + level == psLevel3 || level == psLevel3Sep) { + writePSFmt("{0:s} OP\n", state->getStrokeOverprint() ? "true" : "false"); + noStateChanges = gFalse; + } +} + +void PSOutputDev::updateOverprintMode(GfxState *state) { + if (level == psLevel3 || level == psLevel3Sep) { + writePSFmt("{0:s} opm\n", state->getOverprintMode() ? "true" : "false"); + noStateChanges = gFalse; + } +} + +void PSOutputDev::updateTransfer(GfxState *state) { + Function **funcs; + int i; + + funcs = state->getTransfer(); + if (funcs[0] && funcs[1] && funcs[2] && funcs[3]) { + if (level == psLevel2 || level == psLevel2Sep || + level == psLevel3 || level == psLevel3Sep) { + for (i = 0; i < 4; ++i) { + cvtFunction(funcs[i]); + } + writePS("setcolortransfer\n"); + } else { + cvtFunction(funcs[3]); + writePS("settransfer\n"); + } + } else if (funcs[0]) { + cvtFunction(funcs[0]); + writePS("settransfer\n"); + } else { + writePS("{} settransfer\n"); + } + noStateChanges = gFalse; +} + +void PSOutputDev::updateFont(GfxState *state) { + if (state->getFont()) { + if (state->getFont()->getTag() && + !state->getFont()->getTag()->cmp("xpdf_default_font")) { + writePSFmt("/xpdf_default_font {0:.6g} Tf\n", + fabs(state->getFontSize()) < 0.0001 ? 0.0001 + : state->getFontSize()); + } else { + writePSFmt("/F{0:d}_{1:d} {2:.6g} Tf\n", + state->getFont()->getID()->num, state->getFont()->getID()->gen, + fabs(state->getFontSize()) < 0.0001 ? 0.0001 + : state->getFontSize()); + } + noStateChanges = gFalse; + } +} + +void PSOutputDev::updateTextMat(GfxState *state) { + double *mat; + + mat = state->getTextMat(); + if (fabs(mat[0] * mat[3] - mat[1] * mat[2]) < 1e-10) { + // avoid a singular (or close-to-singular) matrix + writePSFmt("[0.00001 0 0 0.00001 {0:.6g} {1:.6g}] Tm\n", mat[4], mat[5]); + } else { + writePSFmt("[{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] Tm\n", + mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); + } + noStateChanges = gFalse; +} + +void PSOutputDev::updateCharSpace(GfxState *state) { + writePSFmt("{0:.6g} Tc\n", state->getCharSpace()); + noStateChanges = gFalse; +} + +void PSOutputDev::updateRender(GfxState *state) { + int rm; + + rm = state->getRender(); + writePSFmt("{0:d} Tr\n", rm); + rm &= 3; + if (rm != 0 && rm != 3) { + t3Cacheable = gFalse; + } + noStateChanges = gFalse; +} + +void PSOutputDev::updateRise(GfxState *state) { + writePSFmt("{0:.6g} Ts\n", state->getRise()); + noStateChanges = gFalse; +} + +void PSOutputDev::updateWordSpace(GfxState *state) { + writePSFmt("{0:.6g} Tw\n", state->getWordSpace()); + noStateChanges = gFalse; +} + +void PSOutputDev::updateHorizScaling(GfxState *state) { + double h; + + h = state->getHorizScaling(); + if (fabs(h) < 0.01) { + h = 0.01; + } + writePSFmt("{0:.6g} Tz\n", h); + noStateChanges = gFalse; +} + +void PSOutputDev::updateTextPos(GfxState *state) { + writePSFmt("{0:.6g} {1:.6g} Td\n", state->getLineX(), state->getLineY()); + noStateChanges = gFalse; +} + +void PSOutputDev::updateTextShift(GfxState *state, double shift) { + if (state->getFont()->getWMode()) { + writePSFmt("{0:.6g} TJmV\n", shift); + } else { + writePSFmt("{0:.6g} TJm\n", shift); + } + noStateChanges = gFalse; +} + +void PSOutputDev::saveTextPos(GfxState *state) { + writePS("currentpoint\n"); + noStateChanges = gFalse; +} + +void PSOutputDev::restoreTextPos(GfxState *state) { + writePS("m\n"); + noStateChanges = gFalse; +} + +void PSOutputDev::stroke(GfxState *state) { + doPath(state->getPath()); + if (inType3Char && t3FillColorOnly) { + // if we're constructing a cacheable Type 3 glyph, we need to do + // everything in the fill color + writePS("Sf\n"); + } else { + writePS("S\n"); + } + noStateChanges = gFalse; +} + +void PSOutputDev::fill(GfxState *state) { + doPath(state->getPath()); + writePS("f\n"); + noStateChanges = gFalse; +} + +void PSOutputDev::eoFill(GfxState *state) { + doPath(state->getPath()); + writePS("f*\n"); + noStateChanges = gFalse; +} + +void PSOutputDev::tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, + int paintType, int tilingType, + Dict *resDict, + double *mat, double *bbox, + int x0, int y0, int x1, int y1, + double xStep, double yStep) { + if (level <= psLevel1Sep) { + tilingPatternFillL1(state, gfx, strRef, paintType, tilingType, + resDict, mat, bbox, x0, y0, x1, y1, xStep, yStep); + } else { + tilingPatternFillL2(state, gfx, strRef, paintType, tilingType, + resDict, mat, bbox, x0, y0, x1, y1, xStep, yStep); + } +} + +void PSOutputDev::tilingPatternFillL1(GfxState *state, Gfx *gfx, + Object *strRef, + int paintType, int tilingType, + Dict *resDict, + double *mat, double *bbox, + int x0, int y0, int x1, int y1, + double xStep, double yStep) { + PDFRectangle box; + Gfx *gfx2; + + // define a Type 3 font + writePS("8 dict begin\n"); + writePS("/FontType 3 def\n"); + writePS("/FontMatrix [1 0 0 1 0 0] def\n"); + writePSFmt("/FontBBox [{0:.6g} {1:.6g} {2:.6g} {3:.6g}] def\n", + bbox[0], bbox[1], bbox[2], bbox[3]); + writePS("/Encoding 256 array def\n"); + writePS(" 0 1 255 { Encoding exch /.notdef put } for\n"); + writePS(" Encoding 120 /x put\n"); + writePS("/BuildGlyph {\n"); + writePS(" exch /CharProcs get exch\n"); + writePS(" 2 copy known not { pop /.notdef } if\n"); + writePS(" get exec\n"); + writePS("} bind def\n"); + writePS("/BuildChar {\n"); + writePS(" 1 index /Encoding get exch get\n"); + writePS(" 1 index /BuildGlyph get exec\n"); + writePS("} bind def\n"); + writePS("/CharProcs 1 dict def\n"); + writePS("CharProcs begin\n"); + box.x1 = bbox[0]; + box.y1 = bbox[1]; + box.x2 = bbox[2]; + box.y2 = bbox[3]; + 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", + xStep, bbox[0], bbox[1], bbox[2], bbox[3]); + t3FillColorOnly = gTrue; + } else { + if (x1 - 1 <= x0) { + writePS("1 0 setcharwidth\n"); + } else { + writePSFmt("{0:.6g} 0 setcharwidth\n", xStep); + } + t3FillColorOnly = gFalse; + } + inType3Char = gTrue; + ++numTilingPatterns; + gfx2->display(strRef); + --numTilingPatterns; + inType3Char = gFalse; + writePS("} def\n"); + delete gfx2; + writePS("end\n"); + writePS("currentdict end\n"); + writePSFmt("/xpdfTile{0:d} exch definefont pop\n", numTilingPatterns); + + // draw the tiles + writePSFmt("/xpdfTile{0:d} findfont setfont\n", numTilingPatterns); + writePS("fCol\n"); + writePSFmt("gsave [{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] concat\n", + mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); + writePSFmt("{0:d} 1 {1:d} {{ {2:.6g} exch {3:.6g} mul m {4:d} 1 {5:d} {{ pop (x) show }} for }} for\n", + y0, y1 - 1, x0 * xStep, yStep, x0, x1 - 1); + writePS("grestore\n"); + noStateChanges = gFalse; +} + +void PSOutputDev::tilingPatternFillL2(GfxState *state, Gfx *gfx, + Object *strRef, + int paintType, int tilingType, + Dict *resDict, + double *mat, double *bbox, + int x0, int y0, int x1, int y1, + double xStep, double yStep) { + PDFRectangle box; + Gfx *gfx2; + + // switch to pattern space + writePSFmt("gsave [{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] concat\n", + mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); + + // define a pattern + writePSFmt("/xpdfTile{0:d}\n", numTilingPatterns); + writePS("<<\n"); + writePS(" /PatternType 1\n"); + writePSFmt(" /PaintType {0:d}\n", paintType); + writePSFmt(" /TilingType {0:d}\n", tilingType); + writePSFmt(" /BBox [{0:.6g} {1:.6g} {2:.6g} {3:.6g}]\n", + bbox[0], bbox[1], bbox[2], bbox[3]); + writePSFmt(" /XStep {0:.6g}\n", xStep); + writePSFmt(" /YStep {0:.6g}\n", yStep); + writePS(" /PaintProc {\n"); + writePS(" pop\n"); + box.x1 = bbox[0]; + box.y1 = bbox[1]; + box.x2 = bbox[2]; + box.y2 = bbox[3]; + gfx2 = new Gfx(doc, this, resDict, &box, NULL); + gfx2->takeContentStreamStack(gfx); + t3FillColorOnly = paintType == 2; + inType3Char = gTrue; + ++numTilingPatterns; + gfx2->display(strRef); + --numTilingPatterns; + inType3Char = gFalse; + delete gfx2; + writePS(" }\n"); + writePS(">> matrix makepattern def\n"); + + // set the pattern + if (paintType == 2) { + writePS("currentcolor "); + } + writePSFmt("xpdfTile{0:d} setpattern\n", numTilingPatterns); + + // fill with the pattern + writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} rectfill\n", + x0 * xStep + bbox[0], + y0 * yStep + bbox[1], + (x1 - x0) * xStep + bbox[2], + (y1 - y0) * yStep + bbox[3]); + + writePS("grestore\n"); + noStateChanges = gFalse; +} + +GBool PSOutputDev::functionShadedFill(GfxState *state, + GfxFunctionShading *shading) { + double x0, y0, x1, y1; + double *mat; + int i; + + if (level == psLevel2Sep || level == psLevel3Sep) { + if (shading->getColorSpace()->getMode() != csDeviceCMYK) { + return gFalse; + } + processColors |= psProcessCMYK; + } + + shading->getDomain(&x0, &y0, &x1, &y1); + mat = shading->getMatrix(); + writePSFmt("/mat [{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g}] def\n", + mat[0], mat[1], mat[2], mat[3], mat[4], mat[5]); + writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps()); + if (shading->getNFuncs() == 1) { + writePS("/func "); + cvtFunction(shading->getFunc(0)); + writePS("def\n"); + } else { + writePS("/func {\n"); + for (i = 0; i < shading->getNFuncs(); ++i) { + if (i < shading->getNFuncs() - 1) { + writePS("2 copy\n"); + } + cvtFunction(shading->getFunc(i)); + writePS("exec\n"); + if (i < shading->getNFuncs() - 1) { + writePS("3 1 roll\n"); + } + } + writePS("} def\n"); + } + writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} 0 funcSH\n", x0, y0, x1, y1); + + noStateChanges = gFalse; + return gTrue; +} + +GBool PSOutputDev::axialShadedFill(GfxState *state, GfxAxialShading *shading) { + double xMin, yMin, xMax, yMax; + double x0, y0, x1, y1, dx, dy, mul; + double tMin, tMax, t, t0, t1; + int i; + + if (level == psLevel2Sep || level == psLevel3Sep) { + if (shading->getColorSpace()->getMode() != csDeviceCMYK) { + return gFalse; + } + processColors |= psProcessCMYK; + } + + // get the clip region bbox + state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); + + // compute min and max t values, based on the four corners of the + // clip region bbox + shading->getCoords(&x0, &y0, &x1, &y1); + dx = x1 - x0; + dy = y1 - y0; + if (fabs(dx) < 0.01 && fabs(dy) < 0.01) { + return gTrue; + } else { + mul = 1 / (dx * dx + dy * dy); + tMin = tMax = ((xMin - x0) * dx + (yMin - y0) * dy) * mul; + t = ((xMin - x0) * dx + (yMax - y0) * dy) * mul; + if (t < tMin) { + tMin = t; + } else if (t > tMax) { + tMax = t; + } + t = ((xMax - x0) * dx + (yMin - y0) * dy) * mul; + if (t < tMin) { + tMin = t; + } else if (t > tMax) { + tMax = t; + } + t = ((xMax - x0) * dx + (yMax - y0) * dy) * mul; + if (t < tMin) { + tMin = t; + } else if (t > tMax) { + tMax = t; + } + if (tMin < 0 && !shading->getExtend0()) { + tMin = 0; + } + if (tMax > 1 && !shading->getExtend1()) { + tMax = 1; + } + } + + // get the function domain + t0 = shading->getDomain0(); + t1 = shading->getDomain1(); + + // generate the PS code + writePSFmt("/t0 {0:.6g} def\n", t0); + writePSFmt("/t1 {0:.6g} def\n", t1); + writePSFmt("/dt {0:.6g} def\n", t1 - t0); + writePSFmt("/x0 {0:.6g} def\n", x0); + writePSFmt("/y0 {0:.6g} def\n", y0); + writePSFmt("/dx {0:.6g} def\n", x1 - x0); + writePSFmt("/x1 {0:.6g} def\n", x1); + writePSFmt("/y1 {0:.6g} def\n", y1); + writePSFmt("/dy {0:.6g} def\n", y1 - y0); + writePSFmt("/xMin {0:.6g} def\n", xMin); + writePSFmt("/yMin {0:.6g} def\n", yMin); + writePSFmt("/xMax {0:.6g} def\n", xMax); + writePSFmt("/yMax {0:.6g} def\n", yMax); + writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps()); + if (shading->getNFuncs() == 1) { + writePS("/func "); + cvtFunction(shading->getFunc(0)); + writePS("def\n"); + } else { + writePS("/func {\n"); + for (i = 0; i < shading->getNFuncs(); ++i) { + if (i < shading->getNFuncs() - 1) { + writePS("dup\n"); + } + cvtFunction(shading->getFunc(i)); + writePS("exec\n"); + if (i < shading->getNFuncs() - 1) { + writePS("exch\n"); + } + } + writePS("} def\n"); + } + writePSFmt("{0:.6g} {1:.6g} 0 axialSH\n", tMin, tMax); + + noStateChanges = gFalse; + return gTrue; +} + +GBool PSOutputDev::radialShadedFill(GfxState *state, + GfxRadialShading *shading) { + double xMin, yMin, xMax, yMax; + double x0, y0, r0, x1, y1, r1, t0, t1; + double xa, ya, ra; + double sMin, sMax, h, ta; + double sLeft, sRight, sTop, sBottom, sZero, sDiag; + GBool haveSLeft, haveSRight, haveSTop, haveSBottom, haveSZero; + GBool haveSMin, haveSMax; + double theta, alpha, a1, a2; + GBool enclosed; + int i; + + if (level == psLevel2Sep || level == psLevel3Sep) { + if (shading->getColorSpace()->getMode() != csDeviceCMYK) { + return gFalse; + } + processColors |= psProcessCMYK; + } + + // get the shading info + shading->getCoords(&x0, &y0, &r0, &x1, &y1, &r1); + t0 = shading->getDomain0(); + t1 = shading->getDomain1(); + + // Compute the point at which r(s) = 0; check for the enclosed + // circles case; and compute the angles for the tangent lines. + h = sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)); + if (h == 0) { + enclosed = gTrue; + theta = 0; // make gcc happy + } else if (r1 - r0 == 0) { + enclosed = gFalse; + theta = 0; + } else if (fabs(r1 - r0) >= h) { + enclosed = gTrue; + theta = 0; // make gcc happy + } else { + enclosed = gFalse; + theta = asin((r1 - r0) / h); + } + if (enclosed) { + a1 = 0; + a2 = 360; + } else { + alpha = atan2(y1 - y0, x1 - x0); + a1 = (180 / M_PI) * (alpha + theta) + 90; + a2 = (180 / M_PI) * (alpha - theta) - 90; + while (a2 < a1) { + a2 += 360; + } + } + + // compute the (possibly extended) s range + state->getUserClipBBox(&xMin, &yMin, &xMax, &yMax); + if (enclosed) { + sMin = 0; + sMax = 1; + } else { + // solve x(sLeft) + r(sLeft) = xMin + if ((haveSLeft = fabs((x1 + r1) - (x0 + r0)) > 0.000001)) { + sLeft = (xMin - (x0 + r0)) / ((x1 + r1) - (x0 + r0)); + } else { + sLeft = 0; // make gcc happy + } + // solve x(sRight) - r(sRight) = xMax + if ((haveSRight = fabs((x1 - r1) - (x0 - r0)) > 0.000001)) { + sRight = (xMax - (x0 - r0)) / ((x1 - r1) - (x0 - r0)); + } else { + sRight = 0; // make gcc happy + } + // solve y(sBottom) + r(sBottom) = yMin + if ((haveSBottom = fabs((y1 + r1) - (y0 + r0)) > 0.000001)) { + sBottom = (yMin - (y0 + r0)) / ((y1 + r1) - (y0 + r0)); + } else { + sBottom = 0; // make gcc happy + } + // solve y(sTop) - r(sTop) = yMax + if ((haveSTop = fabs((y1 - r1) - (y0 - r0)) > 0.000001)) { + sTop = (yMax - (y0 - r0)) / ((y1 - r1) - (y0 - r0)); + } else { + sTop = 0; // make gcc happy + } + // solve r(sZero) = 0 + if ((haveSZero = fabs(r1 - r0) > 0.000001)) { + sZero = -r0 / (r1 - r0); + } else { + sZero = 0; // make gcc happy + } + // solve r(sDiag) = sqrt((xMax-xMin)^2 + (yMax-yMin)^2) + if (haveSZero) { + sDiag = (sqrt((xMax - xMin) * (xMax - xMin) + + (yMax - yMin) * (yMax - yMin)) - r0) / (r1 - r0); + } else { + sDiag = 0; // make gcc happy + } + // compute sMin + if (shading->getExtend0()) { + sMin = 0; + haveSMin = gFalse; + if (x0 < x1 && haveSLeft && sLeft < 0) { + sMin = sLeft; + haveSMin = gTrue; + } else if (x0 > x1 && haveSRight && sRight < 0) { + sMin = sRight; + haveSMin = gTrue; + } + if (y0 < y1 && haveSBottom && sBottom < 0) { + if (!haveSMin || sBottom > sMin) { + sMin = sBottom; + haveSMin = gTrue; + } + } else if (y0 > y1 && haveSTop && sTop < 0) { + if (!haveSMin || sTop > sMin) { + sMin = sTop; + haveSMin = gTrue; + } + } + if (haveSZero && sZero < 0) { + if (!haveSMin || sZero > sMin) { + sMin = sZero; + } + } + } else { + sMin = 0; + } + // compute sMax + if (shading->getExtend1()) { + sMax = 1; + haveSMax = gFalse; + if (x1 < x0 && haveSLeft && sLeft > 1) { + sMax = sLeft; + haveSMax = gTrue; + } else if (x1 > x0 && haveSRight && sRight > 1) { + sMax = sRight; + haveSMax = gTrue; + } + if (y1 < y0 && haveSBottom && sBottom > 1) { + if (!haveSMax || sBottom < sMax) { + sMax = sBottom; + haveSMax = gTrue; + } + } else if (y1 > y0 && haveSTop && sTop > 1) { + if (!haveSMax || sTop < sMax) { + sMax = sTop; + haveSMax = gTrue; + } + } + if (haveSZero && sDiag > 1) { + if (!haveSMax || sDiag < sMax) { + sMax = sDiag; + } + } + } else { + sMax = 1; + } + } + + // generate the PS code + writePSFmt("/x0 {0:.6g} def\n", x0); + writePSFmt("/x1 {0:.6g} def\n", x1); + writePSFmt("/dx {0:.6g} def\n", x1 - x0); + writePSFmt("/y0 {0:.6g} def\n", y0); + writePSFmt("/y1 {0:.6g} def\n", y1); + writePSFmt("/dy {0:.6g} def\n", y1 - y0); + writePSFmt("/r0 {0:.6g} def\n", r0); + writePSFmt("/r1 {0:.6g} def\n", r1); + writePSFmt("/dr {0:.6g} def\n", r1 - r0); + writePSFmt("/t0 {0:.6g} def\n", t0); + writePSFmt("/t1 {0:.6g} def\n", t1); + writePSFmt("/dt {0:.6g} def\n", t1 - t0); + writePSFmt("/n {0:d} def\n", shading->getColorSpace()->getNComps()); + writePSFmt("/encl {0:s} def\n", enclosed ? "true" : "false"); + writePSFmt("/a1 {0:.6g} def\n", a1); + writePSFmt("/a2 {0:.6g} def\n", a2); + if (shading->getNFuncs() == 1) { + writePS("/func "); + cvtFunction(shading->getFunc(0)); + writePS("def\n"); + } else { + writePS("/func {\n"); + for (i = 0; i < shading->getNFuncs(); ++i) { + if (i < shading->getNFuncs() - 1) { + writePS("dup\n"); + } + cvtFunction(shading->getFunc(i)); + writePS("exec\n"); + if (i < shading->getNFuncs() - 1) { + writePS("exch\n"); + } + } + writePS("} def\n"); + } + writePSFmt("{0:.6g} {1:.6g} 0 radialSH\n", sMin, sMax); + + // extend the 'enclosed' case + if (enclosed) { + // extend the smaller circle + if ((shading->getExtend0() && r0 <= r1) || + (shading->getExtend1() && r1 < r0)) { + if (r0 <= r1) { + ta = t0; + ra = r0; + xa = x0; + ya = y0; + } else { + ta = t1; + ra = r1; + xa = x1; + ya = y1; + } + if (level == psLevel2Sep || level == psLevel3Sep) { + writePSFmt("{0:.6g} radialCol aload pop k\n", ta); + } else { + writePSFmt("{0:.6g} radialCol sc\n", ta); + } + writePSFmt("{0:.6g} {1:.6g} {2:.6g} 0 360 arc h f*\n", xa, ya, ra); + } + + // extend the larger circle + if ((shading->getExtend0() && r0 > r1) || + (shading->getExtend1() && r1 >= r0)) { + if (r0 > r1) { + ta = t0; + ra = r0; + xa = x0; + ya = y0; + } else { + ta = t1; + ra = r1; + xa = x1; + ya = y1; + } + if (level == psLevel2Sep || level == psLevel3Sep) { + writePSFmt("{0:.6g} radialCol aload pop k\n", ta); + } else { + writePSFmt("{0:.6g} radialCol sc\n", ta); + } + writePSFmt("{0:.6g} {1:.6g} {2:.6g} 0 360 arc h\n", xa, ya, ra); + writePSFmt("{0:.6g} {1:.6g} m {2:.6g} {3:.6g} l {4:.6g} {5:.6g} l {6:.6g} {7:.6g} l h f*\n", + xMin, yMin, xMin, yMax, xMax, yMax, xMax, yMin); + } + } + + noStateChanges = gFalse; + return gTrue; +} + +void PSOutputDev::clip(GfxState *state) { + doPath(state->getPath()); + writePS("W\n"); + noStateChanges = gFalse; +} + +void PSOutputDev::eoClip(GfxState *state) { + doPath(state->getPath()); + writePS("W*\n"); + noStateChanges = gFalse; +} + +void PSOutputDev::clipToStrokePath(GfxState *state) { + doPath(state->getPath()); + writePS("Ws\n"); + noStateChanges = gFalse; +} + +void PSOutputDev::doPath(GfxPath *path) { + GfxSubpath *subpath; + double x0, y0, x1, y1, x2, y2, x3, y3, x4, y4; + int n, m, i, j; + + n = path->getNumSubpaths(); + + if (n == 1 && path->getSubpath(0)->getNumPoints() == 5) { + subpath = path->getSubpath(0); + x0 = subpath->getX(0); + y0 = subpath->getY(0); + x4 = subpath->getX(4); + y4 = subpath->getY(4); + if (x4 == x0 && y4 == y0) { + x1 = subpath->getX(1); + y1 = subpath->getY(1); + x2 = subpath->getX(2); + y2 = subpath->getY(2); + x3 = subpath->getX(3); + y3 = subpath->getY(3); + if (x0 == x1 && x2 == x3 && y0 == y3 && y1 == y2) { + writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} re\n", + x0 < x2 ? x0 : x2, y0 < y1 ? y0 : y1, + fabs(x2 - x0), fabs(y1 - y0)); + return; + } else if (x0 == x3 && x1 == x2 && y0 == y1 && y2 == y3) { + writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} re\n", + x0 < x1 ? x0 : x1, y0 < y2 ? y0 : y2, + fabs(x1 - x0), fabs(y2 - y0)); + return; + } + } + } + + for (i = 0; i < n; ++i) { + subpath = path->getSubpath(i); + m = subpath->getNumPoints(); + writePSFmt("{0:.6g} {1:.6g} m\n", subpath->getX(0), subpath->getY(0)); + j = 1; + while (j < m) { + if (subpath->getCurve(j)) { + writePSFmt("{0:.6g} {1:.6g} {2:.6g} {3:.6g} {4:.6g} {5:.6g} c\n", + subpath->getX(j), subpath->getY(j), + subpath->getX(j+1), subpath->getY(j+1), + subpath->getX(j+2), subpath->getY(j+2)); + j += 3; + } else { + writePSFmt("{0:.6g} {1:.6g} l\n", subpath->getX(j), subpath->getY(j)); + ++j; + } + } + if (subpath->isClosed()) { + writePS("h\n"); + } + } +} + +void PSOutputDev::drawString(GfxState *state, GString *s) { + GfxFont *font; + int wMode; + int *codeToGID; + GString *s2; + double dx, dy, originX, originY, originX0, originY0, tOriginX0, tOriginY0; + char *p; + PSFontInfo *fi; + UnicodeMap *uMap; + CharCode code; + Unicode u[8]; + char buf[8]; + double *dxdy; + int dxdySize, len, nChars, uLen, n, m, i, j; + + // check for invisible text -- this is used by Acrobat Capture + if (state->getRender() == 3) { + return; + } + + // ignore empty strings + if (s->getLength() == 0) { + return; + } + + // get the font + if (!(font = state->getFont())) { + return; + } + wMode = font->getWMode(); + + 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()) { + 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 an 8-bit code-to-GID map + } else { + if (fi && fi->ff) { + codeToGID = fi->ff->codeToGID; + } + } + + // compute the positioning (dx, dy) for each char in the string + nChars = 0; + p = s->getCString(); + len = s->getLength(); + 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) { + dy += state->getCharSpace(); + if (n == 1 && *p == ' ') { + dy += state->getWordSpace(); + } + } else { + dx += state->getCharSpace(); + if (n == 1 && *p == ' ') { + dx += state->getWordSpace(); + } + } + dx *= state->getHorizScaling(); + if (font->isCIDFont()) { + if (uMap) { + if (nChars + uLen > dxdySize) { + do { + dxdySize *= 2; + } while (nChars + uLen > dxdySize); + dxdy = (double *)greallocn(dxdy, 2 * dxdySize, sizeof(double)); + } + for (i = 0; i < uLen; ++i) { + m = uMap->mapUnicode(u[i], buf, (int)sizeof(buf)); + for (j = 0; j < m; ++j) { + s2->append(buf[j]); + } + //~ this really needs to get the number of chars in the target + //~ encoding - which may be more than the number of Unicode + //~ chars + dxdy[2 * nChars] = dx; + dxdy[2 * nChars + 1] = dy; + ++nChars; + } + } else { + if (nChars + 1 > dxdySize) { + dxdySize *= 2; + dxdy = (double *)greallocn(dxdy, 2 * dxdySize, sizeof(double)); + } + s2->append((char)((code >> 8) & 0xff)); + s2->append((char)(code & 0xff)); + dxdy[2 * nChars] = dx; + dxdy[2 * nChars + 1] = dy; + ++nChars; + } + } else { + if (!codeToGID || codeToGID[code] >= 0) { + s2->append((char)code); + dxdy[2 * nChars] = dx; + dxdy[2 * nChars + 1] = dy; + ++nChars; + } + } + p += n; + len -= n; + } + 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) { + if (i > 0) { + writePS("\n"); + } + writePSFmt("{0:.6g}", dxdy[i]); + } + if (font->getType() == fontType3) { + writePS("] Tj3\n"); + } else { + writePS("] Tj\n"); + } + if (wMode) { + writePSFmt("{0:.6g} {1:.6g} rmoveto\n", tOriginX0, tOriginY0); + } + } + gfree(dxdy); + delete s2; + + if ((state->getRender() & 4) && font->getType() != fontType3) { + haveTextClip = gTrue; + } + + noStateChanges = gFalse; +} + +void PSOutputDev::endTextObject(GfxState *state) { + if (haveTextClip) { + writePS("Tclip\n"); + haveTextClip = gFalse; + noStateChanges = gFalse; + } +} + +void PSOutputDev::drawImageMask(GfxState *state, Object *ref, Stream *str, + int width, int height, GBool invert, + GBool inlineImg, GBool interpolate) { + int len; + + len = height * ((width + 7) / 8); + switch (level) { + case psLevel1: + case psLevel1Sep: + doImageL1(ref, state, NULL, invert, inlineImg, str, width, height, len); + break; + case psLevel2: + case psLevel2Gray: + case psLevel2Sep: + doImageL2(ref, state, NULL, invert, inlineImg, str, width, height, len, + NULL, NULL, 0, 0, gFalse); + break; + case psLevel3: + case psLevel3Gray: + case psLevel3Sep: + doImageL3(ref, state, NULL, invert, inlineImg, str, width, height, len, + NULL, NULL, 0, 0, gFalse); + break; + } + noStateChanges = gFalse; +} + +void PSOutputDev::drawImage(GfxState *state, Object *ref, Stream *str, + int width, int height, GfxImageColorMap *colorMap, + int *maskColors, GBool inlineImg, + GBool interpolate) { + int len; + + len = height * ((width * colorMap->getNumPixelComps() * + colorMap->getBits() + 7) / 8); + switch (level) { + case psLevel1: + doImageL1(ref, state, colorMap, gFalse, inlineImg, str, + width, height, len); + break; + case psLevel1Sep: + //~ handle indexed, separation, ... color spaces + doImageL1Sep(state, colorMap, gFalse, inlineImg, str, width, height, len); + break; + case psLevel2: + case psLevel2Gray: + case psLevel2Sep: + doImageL2(ref, state, colorMap, gFalse, inlineImg, str, + width, height, len, maskColors, NULL, 0, 0, gFalse); + break; + case psLevel3: + case psLevel3Gray: + case psLevel3Sep: + doImageL3(ref, state, colorMap, gFalse, inlineImg, str, + width, height, len, maskColors, NULL, 0, 0, gFalse); + break; + } + t3Cacheable = gFalse; + noStateChanges = gFalse; +} + +void PSOutputDev::drawMaskedImage(GfxState *state, Object *ref, Stream *str, + int width, int height, + GfxImageColorMap *colorMap, + Stream *maskStr, + int maskWidth, int maskHeight, + GBool maskInvert, GBool interpolate) { + int len; + + len = height * ((width * colorMap->getNumPixelComps() * + colorMap->getBits() + 7) / 8); + switch (level) { + case psLevel1: + doImageL1(ref, state, colorMap, gFalse, gFalse, str, width, height, len); + break; + case psLevel1Sep: + //~ handle indexed, separation, ... color spaces + doImageL1Sep(state, colorMap, gFalse, gFalse, str, width, height, len); + break; + case psLevel2: + case psLevel2Gray: + case psLevel2Sep: + doImageL2(ref, state, colorMap, gFalse, gFalse, str, width, height, len, + NULL, maskStr, maskWidth, maskHeight, maskInvert); + break; + case psLevel3: + case psLevel3Gray: + case psLevel3Sep: + doImageL3(ref, state, colorMap, gFalse, gFalse, str, width, height, len, + NULL, maskStr, maskWidth, maskHeight, maskInvert); + break; + } + t3Cacheable = gFalse; + noStateChanges = gFalse; +} + +void PSOutputDev::doImageL1(Object *ref, GfxState *state, + GfxImageColorMap *colorMap, + GBool invert, GBool inlineImg, + Stream *str, int width, int height, int len) { + ImageStream *imgStr; + Guchar pixBuf[gfxColorMaxComps]; + GfxGray gray; + int col, x, y, c, i; + + if ((inType3Char || preload) && !colorMap) { + if (inlineImg) { + // create an array + str = new FixedLengthEncoder(str, len); + str = new ASCIIHexEncoder(str); + str->reset(); + col = 0; + writePS("[<"); + do { + do { + c = str->getChar(); + } while (c == '\n' || c == '\r'); + if (c == '>' || c == EOF) { + break; + } + writePSChar((char)c); + ++col; + // each line is: "<...data...>" + // so max data length = 255 - 4 = 251 + // but make it 240 just to be safe + // chunks are 2 bytes each, so we need to stop on an even col number + if (col == 240) { + writePS(">\n<"); + col = 0; + } + } while (c != '>' && c != EOF); + writePS(">]\n"); + writePS("0\n"); + str->close(); + delete str; + } else { + // set up to use the array already created by setupImages() + writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen()); + } + } + + // image/imagemask command + if ((inType3Char || preload) && !colorMap) { + writePSFmt("{0:d} {1:d} {2:s} [{3:d} 0 0 {4:d} 0 {5:d}] pdfImM1a\n", + width, height, invert ? "true" : "false", + width, -height, height); + } else if (colorMap) { + writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1\n", + width, height, + width, -height, height); + } else { + writePSFmt("{0:d} {1:d} {2:s} [{3:d} 0 0 {4:d} 0 {5:d}] pdfImM1\n", + width, height, invert ? "true" : "false", + width, -height, height); + } + + // image data + if (!((inType3Char || preload) && !colorMap)) { + + if (colorMap) { + + // set up to process the data stream + imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), + colorMap->getBits()); + imgStr->reset(); + + // process the data stream + i = 0; + for (y = 0; y < height; ++y) { + + // write the line + for (x = 0; x < width; ++x) { + imgStr->getPixel(pixBuf); + colorMap->getGray(pixBuf, &gray, state->getRenderingIntent()); + writePSFmt("{0:02x}", colToByte(gray)); + if (++i == 32) { + writePSChar('\n'); + i = 0; + } + } + } + if (i != 0) { + writePSChar('\n'); + } + str->close(); + delete imgStr; + + // imagemask + } else { + str->reset(); + i = 0; + for (y = 0; y < height; ++y) { + for (x = 0; x < width; x += 8) { + writePSFmt("{0:02x}", str->getChar() & 0xff); + if (++i == 32) { + writePSChar('\n'); + i = 0; + } + } + } + if (i != 0) { + writePSChar('\n'); + } + str->close(); + } + } +} + +void PSOutputDev::doImageL1Sep(GfxState *state, GfxImageColorMap *colorMap, + GBool invert, GBool inlineImg, + Stream *str, int width, int height, int len) { + ImageStream *imgStr; + Guchar *lineBuf; + Guchar pixBuf[gfxColorMaxComps]; + GfxCMYK cmyk; + int x, y, i, comp; + + // width, height, matrix, bits per component + writePSFmt("{0:d} {1:d} 8 [{2:d} 0 0 {3:d} 0 {4:d}] pdfIm1Sep\n", + width, height, + width, -height, height); + + // allocate a line buffer + lineBuf = (Guchar *)gmallocn(width, 4); + + // set up to process the data stream + imgStr = new ImageStream(str, width, colorMap->getNumPixelComps(), + colorMap->getBits()); + imgStr->reset(); + + // process the data stream + i = 0; + for (y = 0; y < height; ++y) { + + // read the line + for (x = 0; x < width; ++x) { + imgStr->getPixel(pixBuf); + colorMap->getCMYK(pixBuf, &cmyk, state->getRenderingIntent()); + lineBuf[4*x+0] = colToByte(cmyk.c); + lineBuf[4*x+1] = colToByte(cmyk.m); + lineBuf[4*x+2] = colToByte(cmyk.y); + lineBuf[4*x+3] = colToByte(cmyk.k); + addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m), + colToDbl(cmyk.y), colToDbl(cmyk.k)); + } + + // write one line of each color component + for (comp = 0; comp < 4; ++comp) { + for (x = 0; x < width; ++x) { + writePSFmt("{0:02x}", lineBuf[4*x + comp]); + if (++i == 32) { + writePSChar('\n'); + i = 0; + } + } + } + } + + if (i != 0) { + writePSChar('\n'); + } + + str->close(); + delete imgStr; + gfree(lineBuf); +} + +void PSOutputDev::doImageL2(Object *ref, GfxState *state, + GfxImageColorMap *colorMap, + GBool invert, GBool inlineImg, + Stream *str, int width, int height, int len, + int *maskColors, Stream *maskStr, + int maskWidth, int maskHeight, GBool maskInvert) { + Stream *str2; + GString *s; + int n, numComps; + GBool useLZW, useRLE, useASCII, useASCIIHex, useCompressed; + GfxSeparationColorSpace *sepCS; + GfxColor color; + GfxCMYK cmyk; + char buf[4096]; + int c, col, i; + + // color key masking + if (maskColors && colorMap && !inlineImg) { + // can't read the stream twice for inline images -- but masking + // isn't allowed with inline images anyway + convertColorKeyMaskToClipRects(colorMap, str, width, height, maskColors); + + // explicit masking + } else if (maskStr) { + convertExplicitMaskToClipRects(maskStr, maskWidth, maskHeight, maskInvert); + } + + // color space + if (colorMap && !(level == psLevel2Gray || level == psLevel3Gray)) { + dumpColorSpaceL2(state, colorMap->getColorSpace(), gFalse, gTrue, gFalse); + writePS(" setcolorspace\n"); + } + + useASCIIHex = globalParams->getPSASCIIHex(); + + // set up the image data + if (mode == psModeForm || inType3Char || preload) { + if (inlineImg) { + // create an array + str2 = new FixedLengthEncoder(str, len); + if (colorMap && (level == psLevel2Gray || level == psLevel3Gray)) { + str2 = new GrayRecoder(str2, width, height, colorMap); + } + if (globalParams->getPSLZW()) { + str2 = new LZWEncoder(str2); + } else { + str2 = new RunLengthEncoder(str2); + } + if (useASCIIHex) { + str2 = new ASCIIHexEncoder(str2); + } else { + str2 = new ASCII85Encoder(str2); + } + str2->reset(); + col = 0; + writePS((char *)(useASCIIHex ? "[<" : "[<~")); + do { + do { + c = str2->getChar(); + } while (c == '\n' || c == '\r'); + if (c == (useASCIIHex ? '>' : '~') || c == EOF) { + break; + } + if (c == 'z') { + writePSChar((char)c); + ++col; + } else { + writePSChar((char)c); + ++col; + for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) { + do { + c = str2->getChar(); + } while (c == '\n' || c == '\r'); + if (c == (useASCIIHex ? '>' : '~') || c == EOF) { + break; + } + writePSChar((char)c); + ++col; + } + } + // each line is: "<~...data...~>" + // so max data length = 255 - 6 = 249 + // chunks are 1 or 5 bytes each, so we have to stop at 245 + // but make it 240 just to be safe + if (col > 240) { + writePS((char *)(useASCIIHex ? ">\n<" : "~>\n<~")); + col = 0; + } + } while (c != (useASCIIHex ? '>' : '~') && c != EOF); + writePS((char *)(useASCIIHex ? ">\n" : "~>\n")); + // add an extra entry because the LZWDecode/RunLengthDecode + // filter may read past the end + writePS("<>]\n"); + writePS("0\n"); + str2->close(); + delete str2; + } else { + // set up to use the array already created by setupImages() + writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen()); + } + } + + // image dictionary + writePS("<<\n /ImageType 1\n"); + + // width, height, matrix, bits per component + writePSFmt(" /Width {0:d}\n", width); + writePSFmt(" /Height {0:d}\n", height); + writePSFmt(" /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n", + width, -height, height); + if (colorMap && (colorMap->getColorSpace()->getMode() == csDeviceN || + level == psLevel2Gray || level == psLevel3Gray)) { + writePS(" /BitsPerComponent 8\n"); + } else { + writePSFmt(" /BitsPerComponent {0:d}\n", + colorMap ? colorMap->getBits() : 1); + } + + // decode + if (colorMap) { + writePS(" /Decode ["); + if ((level == psLevel2Sep || level == psLevel3Sep) && + colorMap->getColorSpace()->getMode() == csSeparation) { + // this matches up with the code in the pdfImSep operator + n = (1 << colorMap->getBits()) - 1; + writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(0) * n, + colorMap->getDecodeHigh(0) * n); + } else if (level == psLevel2Gray || level == psLevel3Gray) { + writePS("0 1"); + } else if (colorMap->getColorSpace()->getMode() == csDeviceN) { + numComps = ((GfxDeviceNColorSpace *)colorMap->getColorSpace())-> + getAlt()->getNComps(); + for (i = 0; i < numComps; ++i) { + if (i > 0) { + writePS(" "); + } + writePS("0 1"); + } + } else { + numComps = colorMap->getNumPixelComps(); + for (i = 0; i < numComps; ++i) { + if (i > 0) { + writePS(" "); + } + writePSFmt("{0:.4g} {1:.4g}", + colorMap->getDecodeLow(i), colorMap->getDecodeHigh(i)); + } + } + writePS("]\n"); + } else { + writePSFmt(" /Decode [{0:d} {1:d}]\n", invert ? 1 : 0, invert ? 0 : 1); + } + + // data source + if (mode == psModeForm || inType3Char || preload) { + writePS(" /DataSource { pdfImStr }\n"); + } else { + writePS(" /DataSource currentfile\n"); + } + + // filters + if ((mode == psModeForm || inType3Char || preload) && + globalParams->getPSUncompressPreloadedImages()) { + s = NULL; + useLZW = useRLE = gFalse; + useCompressed = gFalse; + useASCII = gFalse; + } else { + s = str->getPSFilter(level < psLevel2 ? 1 : level < psLevel3 ? 2 : 3, + " "); + if ((colorMap && (colorMap->getColorSpace()->getMode() == csDeviceN || + level == psLevel2Gray || level == psLevel3Gray)) || + inlineImg || !s) { + if (globalParams->getPSLZW()) { + useLZW = gTrue; + useRLE = gFalse; + } else { + useRLE = gTrue; + useLZW = gFalse; + } + useASCII = !(mode == psModeForm || inType3Char || preload); + useCompressed = gFalse; + } else { + useLZW = useRLE = gFalse; + useASCII = str->isBinary() && + !(mode == psModeForm || inType3Char || preload); + useCompressed = gTrue; + } + } + if (useASCII) { + writePSFmt(" /ASCII{0:s}Decode filter\n", + useASCIIHex ? "Hex" : "85"); + } + if (useLZW) { + writePS(" /LZWDecode filter\n"); + } else if (useRLE) { + writePS(" /RunLengthDecode filter\n"); + } + if (useCompressed) { + writePS(s->getCString()); + } + if (s) { + delete s; + } + + if (mode == psModeForm || inType3Char || preload) { + + // end of image dictionary + writePSFmt(">>\n{0:s}\n", colorMap ? "image" : "imagemask"); + + // get rid of the array and index + writePS("pop pop\n"); + + } else { + + // cut off inline image streams at appropriate length + if (inlineImg) { + str = new FixedLengthEncoder(str, len); + } else if (useCompressed) { + str = str->getUndecodedStream(); + } + + // recode to grayscale + if (colorMap && (level == psLevel2Gray || level == psLevel3Gray)) { + str = new GrayRecoder(str, width, height, colorMap); + + // recode DeviceN data + } else if (colorMap && colorMap->getColorSpace()->getMode() == csDeviceN) { + str = new DeviceNRecoder(str, width, height, colorMap); + } + + // add LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters + if (useLZW) { + str = new LZWEncoder(str); + } else if (useRLE) { + str = new RunLengthEncoder(str); + } + if (useASCII) { + if (useASCIIHex) { + str = new ASCIIHexEncoder(str); + } else { + str = new ASCII85Encoder(str); + } + } + + // end of image dictionary + writePS(">>\n"); +#if OPI_SUPPORT + if (opi13Nest) { + if (inlineImg) { + // this can't happen -- OPI dictionaries are in XObjects + error(errSyntaxError, -1, "OPI in inline image"); + n = 0; + } else { + // need to read the stream to count characters -- the length + // is data-dependent (because of ASCII and LZW/RunLength + // filters) + str->reset(); + n = 0; + do { + i = str->discardChars(4096); + n += i; + } while (i == 4096); + str->close(); + } + // +6/7 for "pdfIm\n" / "pdfImM\n" + // +8 for newline + trailer + n += colorMap ? 14 : 15; + writePSFmt("%%BeginData: {0:d} Hex Bytes\n", n); + } +#endif + if ((level == psLevel2Sep || level == psLevel3Sep) && colorMap && + colorMap->getColorSpace()->getMode() == csSeparation) { + color.c[0] = gfxColorComp1; + sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace(); + sepCS->getCMYK(&color, &cmyk, state->getRenderingIntent()); + writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n", + colToDbl(cmyk.c), colToDbl(cmyk.m), + colToDbl(cmyk.y), colToDbl(cmyk.k), + sepCS->getName()); + } else { + writePSFmt("{0:s}\n", colorMap ? "pdfIm" : "pdfImM"); + } + + // copy the stream data + str->reset(); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); + } + str->close(); + + // add newline and trailer to the end + writePSChar('\n'); + writePS("%-EOD-\n"); +#if OPI_SUPPORT + if (opi13Nest) { + writePS("%%EndData\n"); + } +#endif + + // delete encoders + if (useLZW || useRLE || useASCII || inlineImg) { + delete str; + } + } + + if ((maskColors && colorMap && !inlineImg) || maskStr) { + writePS("pdfImClipEnd\n"); + } +} + +// Convert color key masking to a clipping region consisting of a +// sequence of clip rectangles. +void PSOutputDev::convertColorKeyMaskToClipRects(GfxImageColorMap *colorMap, + Stream *str, + int width, int height, + int *maskColors) { + ImageStream *imgStr; + Guchar *line; + PSOutImgClipRect *rects0, *rects1, *rectsTmp, *rectsOut; + int rects0Len, rects1Len, rectsSize, rectsOutLen, rectsOutSize; + GBool emitRect, addRect, extendRect; + int numComps, i, j, x0, x1, y; + + numComps = colorMap->getNumPixelComps(); + imgStr = new ImageStream(str, width, numComps, colorMap->getBits()); + imgStr->reset(); + rects0Len = rects1Len = rectsOutLen = 0; + rectsSize = rectsOutSize = 64; + rects0 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect)); + rects1 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect)); + rectsOut = (PSOutImgClipRect *)gmallocn(rectsOutSize, + sizeof(PSOutImgClipRect)); + for (y = 0; y < height; ++y) { + if (!(line = imgStr->getLine())) { + break; + } + i = 0; + rects1Len = 0; + for (x0 = 0; x0 < width; ++x0) { + for (j = 0; j < numComps; ++j) { + if (line[x0*numComps+j] < maskColors[2*j] || + line[x0*numComps+j] > maskColors[2*j+1]) { + break; + } + } + if (j < numComps) { + break; + } + } + for (x1 = x0; x1 < width; ++x1) { + for (j = 0; j < numComps; ++j) { + if (line[x1*numComps+j] < maskColors[2*j] || + line[x1*numComps+j] > maskColors[2*j+1]) { + break; + } + } + if (j == numComps) { + break; + } + } + while (x0 < width || i < rects0Len) { + emitRect = addRect = extendRect = gFalse; + if (x0 >= width) { + emitRect = gTrue; + } else if (i >= rects0Len) { + addRect = gTrue; + } else if (rects0[i].x0 < x0) { + emitRect = gTrue; + } else if (x0 < rects0[i].x0) { + addRect = gTrue; + } else if (rects0[i].x1 == x1) { + extendRect = gTrue; + } else { + emitRect = addRect = gTrue; + } + if (emitRect) { + if (rectsOutLen == rectsOutSize) { + rectsOutSize *= 2; + rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize, + sizeof(PSOutImgClipRect)); + } + rectsOut[rectsOutLen].x0 = rects0[i].x0; + rectsOut[rectsOutLen].x1 = rects0[i].x1; + rectsOut[rectsOutLen].y0 = height - y - 1; + rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1; + ++rectsOutLen; + ++i; + } + if (addRect || extendRect) { + if (rects1Len == rectsSize) { + rectsSize *= 2; + rects0 = (PSOutImgClipRect *)greallocn(rects0, rectsSize, + sizeof(PSOutImgClipRect)); + rects1 = (PSOutImgClipRect *)greallocn(rects1, rectsSize, + sizeof(PSOutImgClipRect)); + } + rects1[rects1Len].x0 = x0; + rects1[rects1Len].x1 = x1; + if (addRect) { + rects1[rects1Len].y0 = y; + } + if (extendRect) { + rects1[rects1Len].y0 = rects0[i].y0; + ++i; + } + ++rects1Len; + for (x0 = x1; x0 < width; ++x0) { + for (j = 0; j < numComps; ++j) { + if (line[x0*numComps+j] < maskColors[2*j] || + line[x0*numComps+j] > maskColors[2*j+1]) { + break; + } + } + if (j < numComps) { + break; + } + } + for (x1 = x0; x1 < width; ++x1) { + for (j = 0; j < numComps; ++j) { + if (line[x1*numComps+j] < maskColors[2*j] || + line[x1*numComps+j] > maskColors[2*j+1]) { + break; + } + } + if (j == numComps) { + break; + } + } + } + } + rectsTmp = rects0; + rects0 = rects1; + rects1 = rectsTmp; + i = rects0Len; + rects0Len = rects1Len; + rects1Len = i; + } + for (i = 0; i < rects0Len; ++i) { + if (rectsOutLen == rectsOutSize) { + rectsOutSize *= 2; + rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize, + sizeof(PSOutImgClipRect)); + } + rectsOut[rectsOutLen].x0 = rects0[i].x0; + rectsOut[rectsOutLen].x1 = rects0[i].x1; + rectsOut[rectsOutLen].y0 = height - y - 1; + rectsOut[rectsOutLen].y1 = height - rects0[i].y0 - 1; + ++rectsOutLen; + } + writePSFmt("{0:d} {1:d}\n", width, height); + for (i = 0; i < rectsOutLen; ++i) { + writePSFmt("{0:d} {1:d} {2:d} {3:d} pr\n", + rectsOut[i].x0, rectsOut[i].y0, + rectsOut[i].x1 - rectsOut[i].x0, + rectsOut[i].y1 - rectsOut[i].y0); + } + writePS("pop pop pdfImClip\n"); + gfree(rectsOut); + gfree(rects0); + gfree(rects1); + delete imgStr; + str->close(); +} + +// Convert an explicit mask image to a clipping region consisting of a +// sequence of clip rectangles. +void PSOutputDev::convertExplicitMaskToClipRects(Stream *maskStr, + int maskWidth, int maskHeight, + GBool maskInvert) { + ImageStream *imgStr; + Guchar *line; + PSOutImgClipRect *rects0, *rects1, *rectsTmp, *rectsOut; + int rects0Len, rects1Len, rectsSize, rectsOutLen, rectsOutSize; + GBool emitRect, addRect, extendRect; + int i, x0, x1, y, maskXor; + + imgStr = new ImageStream(maskStr, maskWidth, 1, 1); + imgStr->reset(); + rects0Len = rects1Len = rectsOutLen = 0; + rectsSize = rectsOutSize = 64; + rects0 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect)); + rects1 = (PSOutImgClipRect *)gmallocn(rectsSize, sizeof(PSOutImgClipRect)); + rectsOut = (PSOutImgClipRect *)gmallocn(rectsOutSize, + sizeof(PSOutImgClipRect)); + maskXor = maskInvert ? 1 : 0; + for (y = 0; y < maskHeight; ++y) { + if (!(line = imgStr->getLine())) { + break; + } + i = 0; + rects1Len = 0; + for (x0 = 0; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) ; + for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) ; + while (x0 < maskWidth || i < rects0Len) { + emitRect = addRect = extendRect = gFalse; + if (x0 >= maskWidth) { + emitRect = gTrue; + } else if (i >= rects0Len) { + addRect = gTrue; + } else if (rects0[i].x0 < x0) { + emitRect = gTrue; + } else if (x0 < rects0[i].x0) { + addRect = gTrue; + } else if (rects0[i].x1 == x1) { + extendRect = gTrue; + } else { + emitRect = addRect = gTrue; + } + if (emitRect) { + if (rectsOutLen == rectsOutSize) { + rectsOutSize *= 2; + rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize, + sizeof(PSOutImgClipRect)); + } + rectsOut[rectsOutLen].x0 = rects0[i].x0; + rectsOut[rectsOutLen].x1 = rects0[i].x1; + rectsOut[rectsOutLen].y0 = maskHeight - y - 1; + rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1; + ++rectsOutLen; + ++i; + } + if (addRect || extendRect) { + if (rects1Len == rectsSize) { + rectsSize *= 2; + rects0 = (PSOutImgClipRect *)greallocn(rects0, rectsSize, + sizeof(PSOutImgClipRect)); + rects1 = (PSOutImgClipRect *)greallocn(rects1, rectsSize, + sizeof(PSOutImgClipRect)); + } + rects1[rects1Len].x0 = x0; + rects1[rects1Len].x1 = x1; + if (addRect) { + rects1[rects1Len].y0 = y; + } + if (extendRect) { + rects1[rects1Len].y0 = rects0[i].y0; + ++i; + } + ++rects1Len; + for (x0 = x1; x0 < maskWidth && (line[x0] ^ maskXor); ++x0) ; + for (x1 = x0; x1 < maskWidth && !(line[x1] ^ maskXor); ++x1) ; + } + } + rectsTmp = rects0; + rects0 = rects1; + rects1 = rectsTmp; + i = rects0Len; + rects0Len = rects1Len; + rects1Len = i; + } + for (i = 0; i < rects0Len; ++i) { + if (rectsOutLen == rectsOutSize) { + rectsOutSize *= 2; + rectsOut = (PSOutImgClipRect *)greallocn(rectsOut, rectsOutSize, + sizeof(PSOutImgClipRect)); + } + rectsOut[rectsOutLen].x0 = rects0[i].x0; + rectsOut[rectsOutLen].x1 = rects0[i].x1; + rectsOut[rectsOutLen].y0 = maskHeight - y - 1; + rectsOut[rectsOutLen].y1 = maskHeight - rects0[i].y0 - 1; + ++rectsOutLen; + } + writePSFmt("{0:d} {1:d}\n", maskWidth, maskHeight); + for (i = 0; i < rectsOutLen; ++i) { + writePSFmt("{0:d} {1:d} {2:d} {3:d} pr\n", + rectsOut[i].x0, rectsOut[i].y0, + rectsOut[i].x1 - rectsOut[i].x0, + rectsOut[i].y1 - rectsOut[i].y0); + } + writePS("pop pop pdfImClip\n"); + gfree(rectsOut); + gfree(rects0); + gfree(rects1); + delete imgStr; + maskStr->close(); +} + +//~ this doesn't currently support OPI +void PSOutputDev::doImageL3(Object *ref, GfxState *state, + GfxImageColorMap *colorMap, + GBool invert, GBool inlineImg, + Stream *str, int width, int height, int len, + int *maskColors, Stream *maskStr, + int maskWidth, int maskHeight, GBool maskInvert) { + Stream *str2; + GString *s; + int n, numComps; + GBool 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(); + 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 + // -- this also converts color key masking in grayscale mode + if (maskStr || (maskColors && colorMap && level == psLevel3Gray)) { + + // mask data source + if (maskColors && colorMap && level == psLevel3Gray) { + s = NULL; + if (mode == psModeForm || inType3Char || preload) { + if (globalParams->getPSUncompressPreloadedImages()) { + maskUseLZW = maskUseRLE = gFalse; + } else if (globalParams->getPSLZW()) { + maskUseLZW = gTrue; + maskUseRLE = gFalse; + } else { + maskUseRLE = gTrue; + maskUseLZW = gFalse; + } + maskUseASCII = gFalse; + maskUseCompressed = gFalse; + } else { + if (globalParams->getPSLZW()) { + maskUseLZW = gTrue; + maskUseRLE = gFalse; + } else { + maskUseRLE = gTrue; + maskUseLZW = gFalse; + } + maskUseASCII = gTrue; + } + maskUseCompressed = gFalse; + maskWidth = width; + maskHeight = height; + maskInvert = gFalse; + } else if ((mode == psModeForm || inType3Char || preload) && + globalParams->getPSUncompressPreloadedImages()) { + s = NULL; + maskUseLZW = maskUseRLE = gFalse; + maskUseCompressed = gFalse; + maskUseASCII = gFalse; + } else { + s = maskStr->getPSFilter(3, " "); + if (!s) { + if (globalParams->getPSLZW()) { + maskUseLZW = gTrue; + maskUseRLE = gFalse; + } else { + maskUseRLE = gTrue; + maskUseLZW = gFalse; + } + maskUseASCII = !(mode == psModeForm || inType3Char || preload); + maskUseCompressed = gFalse; + } else { + maskUseLZW = maskUseRLE = gFalse; + maskUseASCII = maskStr->isBinary() && + !(mode == psModeForm || inType3Char || preload); + maskUseCompressed = gTrue; + } + } + maskFilters = new GString(); + if (maskUseASCII) { + maskFilters->appendf(" /ASCII{0:s}Decode filter\n", + useASCIIHex ? "Hex" : "85"); + } + if (maskUseLZW) { + maskFilters->append(" /LZWDecode filter\n"); + } else if (maskUseRLE) { + maskFilters->append(" /RunLengthDecode filter\n"); + } + if (maskUseCompressed) { + maskFilters->append(s); + } + if (s) { + delete s; + } + if (mode == psModeForm || inType3Char || preload) { + writePSFmt("MaskData_{0:d}_{1:d} pdfMaskInit\n", + ref->getRefNum(), ref->getRefGen()); + } else { + writePS("currentfile\n"); + writePS(maskFilters->getCString()); + writePS("pdfMask\n"); + + // add the ColorKeyToMask filter + if (maskColors && colorMap && level == psLevel3Gray) { + maskStr = new ColorKeyToMaskEncoder(str, width, height, colorMap, + maskColors); + } + + // add LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters + if (maskUseCompressed) { + maskStr = maskStr->getUndecodedStream(); + } + if (maskUseLZW) { + maskStr = new LZWEncoder(maskStr); + } else if (maskUseRLE) { + maskStr = new RunLengthEncoder(maskStr); + } + if (maskUseASCII) { + if (useASCIIHex) { + maskStr = new ASCIIHexEncoder(maskStr); + } else { + maskStr = new ASCII85Encoder(maskStr); + } + } + + // copy the stream data + maskStr->reset(); + while ((n = maskStr->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); + } + maskStr->close(); + writePSChar('\n'); + writePS("%-EOD-\n"); + + // delete encoders + if (maskUseLZW || maskUseRLE || maskUseASCII) { + delete maskStr; + } + } + } + + // color space + if (colorMap && level != psLevel3Gray) { + dumpColorSpaceL2(state, colorMap->getColorSpace(), gFalse, gTrue, gFalse); + writePS(" setcolorspace\n"); + } + + // set up the image data + if (mode == psModeForm || inType3Char || preload) { + if (inlineImg) { + // create an array + str2 = new FixedLengthEncoder(str, len); + if (colorMap && level == psLevel3Gray) { + str2 = new GrayRecoder(str2, width, height, colorMap); + } + if (globalParams->getPSLZW()) { + str2 = new LZWEncoder(str2); + } else { + str2 = new RunLengthEncoder(str2); + } + if (useASCIIHex) { + str2 = new ASCIIHexEncoder(str2); + } else { + str2 = new ASCII85Encoder(str2); + } + str2->reset(); + col = 0; + writePS((char *)(useASCIIHex ? "[<" : "[<~")); + do { + do { + c = str2->getChar(); + } while (c == '\n' || c == '\r'); + if (c == (useASCIIHex ? '>' : '~') || c == EOF) { + break; + } + if (c == 'z') { + writePSChar((char)c); + ++col; + } else { + writePSChar((char)c); + ++col; + for (i = 1; i <= (useASCIIHex ? 1 : 4); ++i) { + do { + c = str2->getChar(); + } while (c == '\n' || c == '\r'); + if (c == (useASCIIHex ? '>' : '~') || c == EOF) { + break; + } + writePSChar((char)c); + ++col; + } + } + // each line is: "<~...data...~>" + // so max data length = 255 - 6 = 249 + // chunks are 1 or 5 bytes each, so we have to stop at 245 + // but make it 240 just to be safe + if (col > 240) { + writePS((char *)(useASCIIHex ? ">\n<" : "~>\n<~")); + col = 0; + } + } while (c != (useASCIIHex ? '>' : '~') && c != EOF); + writePS((char *)(useASCIIHex ? ">\n" : "~>\n")); + // add an extra entry because the LZWDecode/RunLengthDecode + // filter may read past the end + writePS("<>]\n"); + writePS("0\n"); + str2->close(); + delete str2; + } else { + // set up to use the array already created by setupImages() + writePSFmt("ImData_{0:d}_{1:d} 0\n", ref->getRefNum(), ref->getRefGen()); + } + } + + // explicit masking + if (maskStr || (maskColors && colorMap && level == psLevel3Gray)) { + writePS("<<\n /ImageType 3\n"); + writePS(" /InterleaveType 3\n"); + writePS(" /DataDict\n"); + } + + // image (data) dictionary + writePSFmt("<<\n /ImageType {0:d}\n", + (maskColors && colorMap && level != psLevel3Gray) ? 4 : 1); + + // color key masking + if (maskColors && colorMap && level != psLevel3Gray) { + writePS(" /MaskColor [\n"); + numComps = colorMap->getNumPixelComps(); + for (i = 0; i < 2 * numComps; i += 2) { + writePSFmt(" {0:d} {1:d}\n", maskColors[i], maskColors[i+1]); + } + writePS(" ]\n"); + } + + // width, height, matrix, bits per component + writePSFmt(" /Width {0:d}\n", width); + writePSFmt(" /Height {0:d}\n", height); + writePSFmt(" /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n", + width, -height, height); + if (colorMap && level == psLevel3Gray) { + writePS(" /BitsPerComponent 8\n"); + } else { + writePSFmt(" /BitsPerComponent {0:d}\n", + colorMap ? colorMap->getBits() : 1); + } + + // decode + if (colorMap) { + writePS(" /Decode ["); + if (level == psLevel3Sep && + colorMap->getColorSpace()->getMode() == csSeparation) { + // this matches up with the code in the pdfImSep operator + n = (1 << colorMap->getBits()) - 1; + writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(0) * n, + colorMap->getDecodeHigh(0) * n); + } else if (level == psLevel3Gray) { + writePS("0 1"); + } else { + numComps = colorMap->getNumPixelComps(); + for (i = 0; i < numComps; ++i) { + if (i > 0) { + writePS(" "); + } + writePSFmt("{0:.4g} {1:.4g}", colorMap->getDecodeLow(i), + colorMap->getDecodeHigh(i)); + } + } + writePS("]\n"); + } else { + writePSFmt(" /Decode [{0:d} {1:d}]\n", invert ? 1 : 0, invert ? 0 : 1); + } + + // data source + if (mode == psModeForm || inType3Char || preload) { + writePS(" /DataSource { pdfImStr }\n"); + } else { + writePS(" /DataSource currentfile\n"); + } + + // filters + if ((mode == psModeForm || inType3Char || preload) && + globalParams->getPSUncompressPreloadedImages()) { + s = NULL; + useLZW = useRLE = gFalse; + useCompressed = gFalse; + useASCII = gFalse; + } else { + s = str->getPSFilter(3, " "); + if ((colorMap && level == psLevel3Gray) || inlineImg || !s) { + if (globalParams->getPSLZW()) { + useLZW = gTrue; + useRLE = gFalse; + } else { + useRLE = gTrue; + useLZW = gFalse; + } + useASCII = !(mode == psModeForm || inType3Char || preload); + useCompressed = gFalse; + } else { + useLZW = useRLE = gFalse; + useASCII = str->isBinary() && + !(mode == psModeForm || inType3Char || preload); + useCompressed = gTrue; + } + } + if (useASCII) { + writePSFmt(" /ASCII{0:s}Decode filter\n", + useASCIIHex ? "Hex" : "85"); + } + if (useLZW) { + writePS(" /LZWDecode filter\n"); + } else if (useRLE) { + writePS(" /RunLengthDecode filter\n"); + } + if (useCompressed) { + writePS(s->getCString()); + } + if (s) { + delete s; + } + + // end of image (data) dictionary + writePS(">>\n"); + + // explicit masking + if (maskStr || (maskColors && colorMap && level == psLevel3Gray)) { + writePS(" /MaskDict\n"); + writePS("<<\n"); + writePS(" /ImageType 1\n"); + writePSFmt(" /Width {0:d}\n", maskWidth); + writePSFmt(" /Height {0:d}\n", maskHeight); + writePSFmt(" /ImageMatrix [{0:d} 0 0 {1:d} 0 {2:d}]\n", + maskWidth, -maskHeight, maskHeight); + writePS(" /BitsPerComponent 1\n"); + writePSFmt(" /Decode [{0:d} {1:d}]\n", + maskInvert ? 1 : 0, maskInvert ? 0 : 1); + + // mask data source + if (mode == psModeForm || inType3Char || preload) { + writePS(" /DataSource {pdfMaskSrc}\n"); + writePS(maskFilters->getCString()); + } else { + writePS(" /DataSource maskStream\n"); + } + delete maskFilters; + + writePS(">>\n"); + writePS(">>\n"); + } + + if (mode == psModeForm || inType3Char || preload) { + + // image command + writePSFmt("{0:s}\n", colorMap ? "image" : "imagemask"); + + } else { + + if (level == psLevel3Sep && colorMap && + colorMap->getColorSpace()->getMode() == csSeparation) { + color.c[0] = gfxColorComp1; + sepCS = (GfxSeparationColorSpace *)colorMap->getColorSpace(); + sepCS->getCMYK(&color, &cmyk, state->getRenderingIntent()); + writePSFmt("{0:.4g} {1:.4g} {2:.4g} {3:.4g} ({4:t}) pdfImSep\n", + colToDbl(cmyk.c), colToDbl(cmyk.m), + colToDbl(cmyk.y), colToDbl(cmyk.k), + sepCS->getName()); + } else { + writePSFmt("{0:s}\n", colorMap ? "pdfIm" : "pdfImM"); + } + + } + + // get rid of the array and index + if (mode == psModeForm || inType3Char || preload) { + writePS("pop pop\n"); + + // image data + } else { + + // cut off inline image streams at appropriate length + if (inlineImg) { + str = new FixedLengthEncoder(str, len); + } else if (useCompressed) { + str = str->getUndecodedStream(); + } + + // recode to grayscale + if (colorMap && level == psLevel3Gray) { + str = new GrayRecoder(str, width, height, colorMap); + } + + // add LZWEncode/RunLengthEncode and ASCIIHex/85 encode filters + if (useLZW) { + str = new LZWEncoder(str); + } else if (useRLE) { + str = new RunLengthEncoder(str); + } + if (useASCII) { + if (useASCIIHex) { + str = new ASCIIHexEncoder(str); + } else { + str = new ASCII85Encoder(str); + } + } + + // copy the stream data + str->reset(); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); + } + str->close(); + + // add newline and trailer to the end + writePSChar('\n'); + writePS("%-EOD-\n"); + + // delete encoders + if (useLZW || useRLE || useASCII || inlineImg) { + delete str; + } + } + + // close the mask stream + if (maskStr || (maskColors && colorMap && level == psLevel3Gray)) { + if (!(mode == psModeForm || inType3Char || preload)) { + writePS("pdfMaskEnd\n"); + } + } +} + +void PSOutputDev::dumpColorSpaceL2(GfxState *state, GfxColorSpace *colorSpace, + GBool genXform, GBool updateColors, + GBool map01) { + switch (colorSpace->getMode()) { + case csDeviceGray: + dumpDeviceGrayColorSpace((GfxDeviceGrayColorSpace *)colorSpace, + genXform, updateColors, map01); + break; + case csCalGray: + dumpCalGrayColorSpace((GfxCalGrayColorSpace *)colorSpace, + genXform, updateColors, map01); + break; + case csDeviceRGB: + dumpDeviceRGBColorSpace((GfxDeviceRGBColorSpace *)colorSpace, + genXform, updateColors, map01); + break; + case csCalRGB: + dumpCalRGBColorSpace((GfxCalRGBColorSpace *)colorSpace, + genXform, updateColors, map01); + break; + case csDeviceCMYK: + dumpDeviceCMYKColorSpace((GfxDeviceCMYKColorSpace *)colorSpace, + genXform, updateColors, map01); + break; + case csLab: + dumpLabColorSpace((GfxLabColorSpace *)colorSpace, + genXform, updateColors, map01); + break; + case csICCBased: + dumpICCBasedColorSpace(state, (GfxICCBasedColorSpace *)colorSpace, + genXform, updateColors, map01); + break; + case csIndexed: + dumpIndexedColorSpace(state, (GfxIndexedColorSpace *)colorSpace, + genXform, updateColors, map01); + break; + case csSeparation: + dumpSeparationColorSpace(state, (GfxSeparationColorSpace *)colorSpace, + genXform, updateColors, map01); + break; + case csDeviceN: + if (level >= psLevel3) { + dumpDeviceNColorSpaceL3(state, (GfxDeviceNColorSpace *)colorSpace, + genXform, updateColors, map01); + } else { + dumpDeviceNColorSpaceL2(state, (GfxDeviceNColorSpace *)colorSpace, + genXform, updateColors, map01); + } + break; + case csPattern: + //~ unimplemented + break; + } +} + +void PSOutputDev::dumpDeviceGrayColorSpace(GfxDeviceGrayColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01) { + writePS("/DeviceGray"); + if (genXform) { + writePS(" {}"); + } + if (updateColors) { + processColors |= psProcessBlack; + } +} + +void PSOutputDev::dumpCalGrayColorSpace(GfxCalGrayColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01) { + writePS("[/CIEBasedA <<\n"); + writePSFmt(" /DecodeA {{{0:.4g} exp}} bind\n", cs->getGamma()); + writePSFmt(" /MatrixA [{0:.4g} {1:.4g} {2:.4g}]\n", + cs->getWhiteX(), cs->getWhiteY(), cs->getWhiteZ()); + writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n", + cs->getWhiteX(), cs->getWhiteY(), cs->getWhiteZ()); + writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n", + cs->getBlackX(), cs->getBlackY(), cs->getBlackZ()); + writePS(">>]"); + if (genXform) { + writePS(" {}"); + } + if (updateColors) { + processColors |= psProcessBlack; + } +} + +void PSOutputDev::dumpDeviceRGBColorSpace(GfxDeviceRGBColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01) { + writePS("/DeviceRGB"); + if (genXform) { + writePS(" {}"); + } + if (updateColors) { + processColors |= psProcessCMYK; + } +} + +void PSOutputDev::dumpCalRGBColorSpace(GfxCalRGBColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01) { + writePS("[/CIEBasedABC <<\n"); + writePSFmt(" /DecodeABC [{{{0:.4g} exp}} bind {{{1:.4g} exp}} bind {{{2:.4g} exp}} bind]\n", + cs->getGammaR(), cs->getGammaG(), cs->getGammaB()); + writePSFmt(" /MatrixABC [{0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} {6:.4g} {7:.4g} {8:.4g}]\n", + cs->getMatrix()[0], cs->getMatrix()[1], cs->getMatrix()[2], + cs->getMatrix()[3], cs->getMatrix()[4], cs->getMatrix()[5], + cs->getMatrix()[6], cs->getMatrix()[7], cs->getMatrix()[8]); + writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n", + cs->getWhiteX(), cs->getWhiteY(), cs->getWhiteZ()); + writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n", + cs->getBlackX(), cs->getBlackY(), cs->getBlackZ()); + writePS(">>]"); + if (genXform) { + writePS(" {}"); + } + if (updateColors) { + processColors |= psProcessCMYK; + } +} + +void PSOutputDev::dumpDeviceCMYKColorSpace(GfxDeviceCMYKColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01) { + writePS("/DeviceCMYK"); + if (genXform) { + writePS(" {}"); + } + if (updateColors) { + processColors |= psProcessCMYK; + } +} + +void PSOutputDev::dumpLabColorSpace(GfxLabColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01) { + writePS("[/CIEBasedABC <<\n"); + if (map01) { + writePS(" /RangeABC [0 1 0 1 0 1]\n"); + writePSFmt(" /DecodeABC [{{100 mul 16 add 116 div}} bind {{{0:.4g} mul {1:.4g} add}} bind {{{2:.4g} mul {3:.4g} add}} bind]\n", + (cs->getAMax() - cs->getAMin()) / 500.0, + cs->getAMin() / 500.0, + (cs->getBMax() - cs->getBMin()) / 200.0, + cs->getBMin() / 200.0); + } else { + writePSFmt(" /RangeABC [0 100 {0:.4g} {1:.4g} {2:.4g} {3:.4g}]\n", + cs->getAMin(), cs->getAMax(), + cs->getBMin(), cs->getBMax()); + writePS(" /DecodeABC [{16 add 116 div} bind {500 div} bind {200 div} bind]\n"); + } + writePS(" /MatrixABC [1 1 1 1 0 0 0 0 -1]\n"); + writePS(" /DecodeLMN\n"); + writePS(" [{dup 6 29 div ge {dup dup mul mul}\n"); + writePSFmt(" {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n", + cs->getWhiteX()); + writePS(" {dup 6 29 div ge {dup dup mul mul}\n"); + writePSFmt(" {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind\n", + cs->getWhiteY()); + writePS(" {dup 6 29 div ge {dup dup mul mul}\n"); + writePSFmt(" {{4 29 div sub 108 841 div mul }} ifelse {0:.4g} mul}} bind]\n", + cs->getWhiteZ()); + writePSFmt(" /WhitePoint [{0:.4g} {1:.4g} {2:.4g}]\n", + cs->getWhiteX(), cs->getWhiteY(), cs->getWhiteZ()); + writePSFmt(" /BlackPoint [{0:.4g} {1:.4g} {2:.4g}]\n", + cs->getBlackX(), cs->getBlackY(), cs->getBlackZ()); + writePS(">>]"); + if (genXform) { + writePS(" {}"); + } + if (updateColors) { + processColors |= psProcessCMYK; + } +} + +void PSOutputDev::dumpICCBasedColorSpace(GfxState *state, + GfxICCBasedColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01) { + // there is no transform function to the alternate color space, so + // we can use it directly + dumpColorSpaceL2(state, cs->getAlt(), genXform, updateColors, gFalse); +} + + +void PSOutputDev::dumpIndexedColorSpace(GfxState *state, + GfxIndexedColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01) { + GfxColorSpace *baseCS; + GfxLabColorSpace *labCS; + Guchar *lookup, *p; + double x[gfxColorMaxComps], y[gfxColorMaxComps]; + double low[gfxColorMaxComps], range[gfxColorMaxComps]; + GfxColor color; + GfxCMYK cmyk; + Function *func; + int n, numComps, numAltComps; + int byte; + int i, j, k; + + baseCS = cs->getBase(); + writePS("[/Indexed "); + dumpColorSpaceL2(state, baseCS, gFalse, updateColors, gTrue); + n = cs->getIndexHigh(); + numComps = baseCS->getNComps(); + lookup = cs->getLookup(); + writePSFmt(" {0:d} <\n", n); + if (baseCS->getMode() == csDeviceN && level < psLevel3) { + func = ((GfxDeviceNColorSpace *)baseCS)->getTintTransformFunc(); + baseCS->getDefaultRanges(low, range, cs->getIndexHigh()); + if (((GfxDeviceNColorSpace *)baseCS)->getAlt()->getMode() == csLab) { + labCS = (GfxLabColorSpace *)((GfxDeviceNColorSpace *)baseCS)->getAlt(); + } else { + labCS = NULL; + } + numAltComps = ((GfxDeviceNColorSpace *)baseCS)->getAlt()->getNComps(); + p = lookup; + for (i = 0; i <= n; i += 8) { + writePS(" "); + for (j = i; j < i+8 && j <= n; ++j) { + for (k = 0; k < numComps; ++k) { + x[k] = low[k] + (*p++ / 255.0) * range[k]; + } + func->transform(x, y); + if (labCS) { + y[0] /= 100.0; + y[1] = (y[1] - labCS->getAMin()) / + (labCS->getAMax() - labCS->getAMin()); + y[2] = (y[2] - labCS->getBMin()) / + (labCS->getBMax() - labCS->getBMin()); + } + for (k = 0; k < numAltComps; ++k) { + byte = (int)(y[k] * 255 + 0.5); + if (byte < 0) { + byte = 0; + } else if (byte > 255) { + byte = 255; + } + writePSFmt("{0:02x}", byte); + } + if (updateColors) { + color.c[0] = dblToCol(j); + cs->getCMYK(&color, &cmyk, state->getRenderingIntent()); + addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m), + colToDbl(cmyk.y), colToDbl(cmyk.k)); + } + } + writePS("\n"); + } + } else { + for (i = 0; i <= n; i += 8) { + writePS(" "); + for (j = i; j < i+8 && j <= n; ++j) { + for (k = 0; k < numComps; ++k) { + writePSFmt("{0:02x}", lookup[j * numComps + k]); + } + if (updateColors) { + color.c[0] = dblToCol(j); + cs->getCMYK(&color, &cmyk, state->getRenderingIntent()); + addProcessColor(colToDbl(cmyk.c), colToDbl(cmyk.m), + colToDbl(cmyk.y), colToDbl(cmyk.k)); + } + } + writePS("\n"); + } + } + writePS(">]"); + if (genXform) { + writePS(" {}"); + } +} + +void PSOutputDev::dumpSeparationColorSpace(GfxState *state, + GfxSeparationColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01) { + writePS("[/Separation "); + writePSString(cs->getName()); + writePS(" "); + dumpColorSpaceL2(state, cs->getAlt(), gFalse, gFalse, gFalse); + writePS("\n"); + cvtFunction(cs->getFunc()); + writePS("]"); + if (genXform) { + writePS(" {}"); + } + if (updateColors) { + addCustomColor(state, cs); + } +} + +void PSOutputDev::dumpDeviceNColorSpaceL2(GfxState *state, + GfxDeviceNColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01) { + dumpColorSpaceL2(state, cs->getAlt(), gFalse, updateColors, map01); + if (genXform) { + writePS(" "); + cvtFunction(cs->getTintTransformFunc()); + } +} + +void PSOutputDev::dumpDeviceNColorSpaceL3(GfxState *state, + GfxDeviceNColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01) { + GString *tint; + int i; + + writePS("[/DeviceN [\n"); + for (i = 0; i < cs->getNComps(); ++i) { + writePSString(cs->getColorantName(i)); + writePS("\n"); + } + writePS("]\n"); + if ((tint = createDeviceNTintFunc(cs))) { + writePS("/DeviceCMYK\n"); + writePS(tint->getCString()); + delete tint; + } else { + dumpColorSpaceL2(state, cs->getAlt(), gFalse, gFalse, gFalse); + writePS("\n"); + cvtFunction(cs->getTintTransformFunc()); + } + writePS("]"); + if (genXform) { + writePS(" {}"); + } + if (updateColors) { + addCustomColors(state, cs); + } +} + +// If the DeviceN color space has a Colorants dictionary, and all of +// the colorants are one of: "None", "Cyan", "Magenta", "Yellow", +// "Black", or have an entry in the Colorants dict that maps to +// DeviceCMYK, then build a new tint function; else use the existing +// tint function. +GString *PSOutputDev::createDeviceNTintFunc(GfxDeviceNColorSpace *cs) { + Object *attrs; + Object colorants, sepCSObj, funcObj, obj1; + GString *name; + Function *func; + double sepIn; + double cmyk[gfxColorMaxComps][4]; + GString *tint; + GBool first; + int i, j; + + attrs = cs->getAttrs(); + if (!attrs->isDict()) { + return NULL; + } + if (!attrs->dictLookup("Colorants", &colorants)->isDict()) { + colorants.free(); + return NULL; + } + for (i = 0; i < cs->getNComps(); ++i) { + name = cs->getColorantName(i); + if (!name->cmp("None")) { + cmyk[i][0] = cmyk[i][1] = cmyk[i][2] = cmyk[i][3] = 0; + } else if (!name->cmp("Cyan")) { + cmyk[i][1] = cmyk[i][2] = cmyk[i][3] = 0; + cmyk[i][0] = 1; + } else if (!name->cmp("Magenta")) { + cmyk[i][0] = cmyk[i][2] = cmyk[i][3] = 0; + cmyk[i][1] = 1; + } else if (!name->cmp("Yellow")) { + cmyk[i][0] = cmyk[i][1] = cmyk[i][3] = 0; + cmyk[i][2] = 1; + } else if (!name->cmp("Black")) { + cmyk[i][0] = cmyk[i][1] = cmyk[i][2] = 0; + cmyk[i][3] = 1; + } else { + colorants.dictLookup(name->getCString(), &sepCSObj); + if (!sepCSObj.isArray() || sepCSObj.arrayGetLength() != 4) { + sepCSObj.free(); + colorants.free(); + return NULL; + } + if (!sepCSObj.arrayGet(0, &obj1)->isName("Separation")) { + obj1.free(); + sepCSObj.free(); + colorants.free(); + return NULL; + } + obj1.free(); + if (!sepCSObj.arrayGet(2, &obj1)->isName("DeviceCMYK")) { + obj1.free(); + sepCSObj.free(); + colorants.free(); + return NULL; + } + obj1.free(); + sepCSObj.arrayGet(3, &funcObj); + if (!(func = Function::parse(&funcObj))) { + funcObj.free(); + sepCSObj.free(); + colorants.free(); + return NULL; + } + funcObj.free(); + if (func->getInputSize() != 1 || func->getOutputSize() != 4) { + delete func; + sepCSObj.free(); + colorants.free(); + return NULL; + } + sepIn = 1; + func->transform(&sepIn, cmyk[i]); + delete func; + sepCSObj.free(); + } + } + colorants.free(); + + tint = new GString(); + tint->append("{\n"); + for (j = 0; j < 4; ++j) { // C, M, Y, K + first = gTrue; + for (i = 0; i < cs->getNComps(); ++i) { + if (cmyk[i][j] != 0) { + tint->appendf("{0:d} index {1:.4f} mul{2:s}\n", + j + cs->getNComps() - 1 - i, cmyk[i][j], + first ? "" : " add"); + first = gFalse; + } + } + if (first) { + tint->append("0\n"); + } + } + tint->appendf("{0:d} 4 roll\n", cs->getNComps() + 4); + for (i = 0; i < cs->getNComps(); ++i) { + tint->append("pop\n"); + } + tint->append("}\n"); + + return tint; +} + +#if OPI_SUPPORT +void PSOutputDev::opiBegin(GfxState *state, Dict *opiDict) { + Object dict; + + if (globalParams->getPSOPI()) { + opiDict->lookup("2.0", &dict); + if (dict.isDict()) { + opiBegin20(state, dict.getDict()); + dict.free(); + } else { + dict.free(); + opiDict->lookup("1.3", &dict); + if (dict.isDict()) { + opiBegin13(state, dict.getDict()); + } + dict.free(); + } + } +} + +void PSOutputDev::opiBegin20(GfxState *state, Dict *dict) { + Object obj1, obj2, obj3, obj4; + double width, height, left, right, top, bottom; + int w, h; + int i; + + writePS("%%BeginOPI: 2.0\n"); + writePS("%%Distilled\n"); + + dict->lookup("F", &obj1); + if (getFileSpec(&obj1, &obj2)) { + writePSFmt("%%ImageFileName: {0:t}\n", obj2.getString()); + obj2.free(); + } + obj1.free(); + + dict->lookup("MainImage", &obj1); + if (obj1.isString()) { + writePSFmt("%%MainImage: {0:t}\n", obj1.getString()); + } + obj1.free(); + + //~ ignoring 'Tags' entry + //~ need to use writePSString() and deal with >255-char lines + + dict->lookup("Size", &obj1); + if (obj1.isArray() && obj1.arrayGetLength() == 2) { + obj1.arrayGet(0, &obj2); + width = obj2.getNum(); + obj2.free(); + obj1.arrayGet(1, &obj2); + height = obj2.getNum(); + obj2.free(); + writePSFmt("%%ImageDimensions: {0:.6g} {1:.6g}\n", width, height); + } + obj1.free(); + + dict->lookup("CropRect", &obj1); + if (obj1.isArray() && obj1.arrayGetLength() == 4) { + obj1.arrayGet(0, &obj2); + left = obj2.getNum(); + obj2.free(); + obj1.arrayGet(1, &obj2); + top = obj2.getNum(); + obj2.free(); + obj1.arrayGet(2, &obj2); + right = obj2.getNum(); + obj2.free(); + obj1.arrayGet(3, &obj2); + bottom = obj2.getNum(); + obj2.free(); + writePSFmt("%%ImageCropRect: {0:.6g} {1:.6g} {2:.6g} {3:.6g}\n", + left, top, right, bottom); + } + obj1.free(); + + dict->lookup("Overprint", &obj1); + if (obj1.isBool()) { + writePSFmt("%%ImageOverprint: {0:s}\n", obj1.getBool() ? "true" : "false"); + } + obj1.free(); + + dict->lookup("Inks", &obj1); + if (obj1.isName()) { + writePSFmt("%%ImageInks: {0:s}\n", obj1.getName()); + } else if (obj1.isArray() && obj1.arrayGetLength() >= 1) { + obj1.arrayGet(0, &obj2); + if (obj2.isName()) { + writePSFmt("%%ImageInks: {0:s} {1:d}", + obj2.getName(), (obj1.arrayGetLength() - 1) / 2); + for (i = 1; i+1 < obj1.arrayGetLength(); i += 2) { + obj1.arrayGet(i, &obj3); + obj1.arrayGet(i+1, &obj4); + if (obj3.isString() && obj4.isNum()) { + writePS(" "); + writePSString(obj3.getString()); + writePSFmt(" {0:.4g}", obj4.getNum()); + } + obj3.free(); + obj4.free(); + } + writePS("\n"); + } + obj2.free(); + } + obj1.free(); + + writePS("gsave\n"); + + writePS("%%BeginIncludedImage\n"); + + dict->lookup("IncludedImageDimensions", &obj1); + if (obj1.isArray() && obj1.arrayGetLength() == 2) { + obj1.arrayGet(0, &obj2); + w = obj2.getInt(); + obj2.free(); + obj1.arrayGet(1, &obj2); + h = obj2.getInt(); + obj2.free(); + writePSFmt("%%IncludedImageDimensions: {0:d} {1:d}\n", w, h); + } + obj1.free(); + + dict->lookup("IncludedImageQuality", &obj1); + if (obj1.isNum()) { + writePSFmt("%%IncludedImageQuality: {0:.4g}\n", obj1.getNum()); + } + obj1.free(); + + ++opi20Nest; +} + +void PSOutputDev::opiBegin13(GfxState *state, Dict *dict) { + Object obj1, obj2; + int left, right, top, bottom, samples, bits, width, height; + double c, m, y, k; + double llx, lly, ulx, uly, urx, ury, lrx, lry; + double tllx, tlly, tulx, tuly, turx, tury, tlrx, tlry; + double horiz, vert; + int i, j; + + writePS("save\n"); + writePS("/opiMatrix2 matrix currentmatrix def\n"); + writePS("opiMatrix setmatrix\n"); + + dict->lookup("F", &obj1); + if (getFileSpec(&obj1, &obj2)) { + writePSFmt("%ALDImageFileName: {0:t}\n", obj2.getString()); + obj2.free(); + } + obj1.free(); + + dict->lookup("CropRect", &obj1); + if (obj1.isArray() && obj1.arrayGetLength() == 4) { + obj1.arrayGet(0, &obj2); + left = obj2.getInt(); + obj2.free(); + obj1.arrayGet(1, &obj2); + top = obj2.getInt(); + obj2.free(); + obj1.arrayGet(2, &obj2); + right = obj2.getInt(); + obj2.free(); + obj1.arrayGet(3, &obj2); + bottom = obj2.getInt(); + obj2.free(); + writePSFmt("%ALDImageCropRect: {0:d} {1:d} {2:d} {3:d}\n", + left, top, right, bottom); + } + obj1.free(); + + dict->lookup("Color", &obj1); + if (obj1.isArray() && obj1.arrayGetLength() == 5) { + obj1.arrayGet(0, &obj2); + c = obj2.getNum(); + obj2.free(); + obj1.arrayGet(1, &obj2); + m = obj2.getNum(); + obj2.free(); + obj1.arrayGet(2, &obj2); + y = obj2.getNum(); + obj2.free(); + obj1.arrayGet(3, &obj2); + k = obj2.getNum(); + obj2.free(); + obj1.arrayGet(4, &obj2); + if (obj2.isString()) { + writePSFmt("%ALDImageColor: {0:.4g} {1:.4g} {2:.4g} {3:.4g} ", + c, m, y, k); + writePSString(obj2.getString()); + writePS("\n"); + } + obj2.free(); + } + obj1.free(); + + dict->lookup("ColorType", &obj1); + if (obj1.isName()) { + writePSFmt("%ALDImageColorType: {0:s}\n", obj1.getName()); + } + obj1.free(); + + //~ ignores 'Comments' entry + //~ need to handle multiple lines + + dict->lookup("CropFixed", &obj1); + if (obj1.isArray()) { + obj1.arrayGet(0, &obj2); + ulx = obj2.getNum(); + obj2.free(); + obj1.arrayGet(1, &obj2); + uly = obj2.getNum(); + obj2.free(); + obj1.arrayGet(2, &obj2); + lrx = obj2.getNum(); + obj2.free(); + obj1.arrayGet(3, &obj2); + lry = obj2.getNum(); + obj2.free(); + writePSFmt("%ALDImageCropFixed: {0:.4g} {1:.4g} {2:.4g} {3:.4g}\n", + ulx, uly, lrx, lry); + } + obj1.free(); + + dict->lookup("GrayMap", &obj1); + if (obj1.isArray()) { + writePS("%ALDImageGrayMap:"); + for (i = 0; i < obj1.arrayGetLength(); i += 16) { + if (i > 0) { + writePS("\n%%+"); + } + for (j = 0; j < 16 && i+j < obj1.arrayGetLength(); ++j) { + obj1.arrayGet(i+j, &obj2); + writePSFmt(" {0:d}", obj2.getInt()); + obj2.free(); + } + } + writePS("\n"); + } + obj1.free(); + + dict->lookup("ID", &obj1); + if (obj1.isString()) { + writePSFmt("%ALDImageID: {0:t}\n", obj1.getString()); + } + obj1.free(); + + dict->lookup("ImageType", &obj1); + if (obj1.isArray() && obj1.arrayGetLength() == 2) { + obj1.arrayGet(0, &obj2); + samples = obj2.getInt(); + obj2.free(); + obj1.arrayGet(1, &obj2); + bits = obj2.getInt(); + obj2.free(); + writePSFmt("%ALDImageType: {0:d} {1:d}\n", samples, bits); + } + obj1.free(); + + dict->lookup("Overprint", &obj1); + if (obj1.isBool()) { + writePSFmt("%ALDImageOverprint: {0:s}\n", + obj1.getBool() ? "true" : "false"); + } + obj1.free(); + + dict->lookup("Position", &obj1); + if (obj1.isArray() && obj1.arrayGetLength() == 8) { + obj1.arrayGet(0, &obj2); + llx = obj2.getNum(); + obj2.free(); + obj1.arrayGet(1, &obj2); + lly = obj2.getNum(); + obj2.free(); + obj1.arrayGet(2, &obj2); + ulx = obj2.getNum(); + obj2.free(); + obj1.arrayGet(3, &obj2); + uly = obj2.getNum(); + obj2.free(); + obj1.arrayGet(4, &obj2); + urx = obj2.getNum(); + obj2.free(); + obj1.arrayGet(5, &obj2); + ury = obj2.getNum(); + obj2.free(); + obj1.arrayGet(6, &obj2); + lrx = obj2.getNum(); + obj2.free(); + obj1.arrayGet(7, &obj2); + lry = obj2.getNum(); + obj2.free(); + opiTransform(state, llx, lly, &tllx, &tlly); + opiTransform(state, ulx, uly, &tulx, &tuly); + opiTransform(state, urx, ury, &turx, &tury); + opiTransform(state, lrx, lry, &tlrx, &tlry); + writePSFmt("%ALDImagePosition: {0:.4g} {1:.4g} {2:.4g} {3:.4g} {4:.4g} {5:.4g} {6:.4g} {7:.4g}\n", + tllx, tlly, tulx, tuly, turx, tury, tlrx, tlry); + obj2.free(); + } + obj1.free(); + + dict->lookup("Resolution", &obj1); + if (obj1.isArray() && obj1.arrayGetLength() == 2) { + obj1.arrayGet(0, &obj2); + horiz = obj2.getNum(); + obj2.free(); + obj1.arrayGet(1, &obj2); + vert = obj2.getNum(); + obj2.free(); + writePSFmt("%ALDImageResoution: {0:.4g} {1:.4g}\n", horiz, vert); + obj2.free(); + } + obj1.free(); + + dict->lookup("Size", &obj1); + if (obj1.isArray() && obj1.arrayGetLength() == 2) { + obj1.arrayGet(0, &obj2); + width = obj2.getInt(); + obj2.free(); + obj1.arrayGet(1, &obj2); + height = obj2.getInt(); + obj2.free(); + writePSFmt("%ALDImageDimensions: {0:d} {1:d}\n", width, height); + } + obj1.free(); + + //~ ignoring 'Tags' entry + //~ need to use writePSString() and deal with >255-char lines + + dict->lookup("Tint", &obj1); + if (obj1.isNum()) { + writePSFmt("%ALDImageTint: {0:.4g}\n", obj1.getNum()); + } + obj1.free(); + + dict->lookup("Transparency", &obj1); + if (obj1.isBool()) { + writePSFmt("%ALDImageTransparency: {0:s}\n", + obj1.getBool() ? "true" : "false"); + } + obj1.free(); + + writePS("%%BeginObject: image\n"); + writePS("opiMatrix2 setmatrix\n"); + ++opi13Nest; +} + +// Convert PDF user space coordinates to PostScript default user space +// coordinates. This has to account for both the PDF CTM and the +// PSOutputDev page-fitting transform. +void PSOutputDev::opiTransform(GfxState *state, double x0, double y0, + double *x1, double *y1) { + double t; + + state->transform(x0, y0, x1, y1); + *x1 += tx; + *y1 += ty; + if (rotate == 90) { + t = *x1; + *x1 = -*y1; + *y1 = t; + } else if (rotate == 180) { + *x1 = -*x1; + *y1 = -*y1; + } else if (rotate == 270) { + t = *x1; + *x1 = *y1; + *y1 = -t; + } + *x1 *= xScale; + *y1 *= yScale; +} + +void PSOutputDev::opiEnd(GfxState *state, Dict *opiDict) { + Object dict; + + if (globalParams->getPSOPI()) { + opiDict->lookup("2.0", &dict); + if (dict.isDict()) { + writePS("%%EndIncludedImage\n"); + writePS("%%EndOPI\n"); + writePS("grestore\n"); + --opi20Nest; + dict.free(); + } else { + dict.free(); + opiDict->lookup("1.3", &dict); + if (dict.isDict()) { + writePS("%%EndObject\n"); + writePS("restore\n"); + --opi13Nest; + } + dict.free(); + } + } +} + +GBool PSOutputDev::getFileSpec(Object *fileSpec, Object *fileName) { + if (fileSpec->isString()) { + fileSpec->copy(fileName); + return gTrue; + } + if (fileSpec->isDict()) { + fileSpec->dictLookup("DOS", fileName); + if (fileName->isString()) { + return gTrue; + } + fileName->free(); + fileSpec->dictLookup("Mac", fileName); + if (fileName->isString()) { + return gTrue; + } + fileName->free(); + fileSpec->dictLookup("Unix", fileName); + if (fileName->isString()) { + return gTrue; + } + fileName->free(); + fileSpec->dictLookup("F", fileName); + if (fileName->isString()) { + return gTrue; + } + fileName->free(); + } + return gFalse; +} +#endif // OPI_SUPPORT + +void PSOutputDev::type3D0(GfxState *state, double wx, double wy) { + writePSFmt("{0:.6g} {1:.6g} setcharwidth\n", wx, wy); + writePS("q\n"); + t3NeedsRestore = gTrue; + noStateChanges = gFalse; +} + +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; + t3LLY = lly; + t3URX = urx; + t3URY = ury; + t3String = new GString(); + writePS("q\n"); + t3FillColorOnly = gTrue; + t3Cacheable = gTrue; + t3NeedsRestore = gTrue; + noStateChanges = gFalse; +} + +void PSOutputDev::drawForm(Ref id) { + writePSFmt("f_{0:d}_{1:d}\n", id.num, id.gen); + noStateChanges = gFalse; +} + +void PSOutputDev::psXObject(Stream *psStream, Stream *level1Stream) { + Stream *str; + char buf[4096]; + int n; + + if ((level == psLevel1 || level == psLevel1Sep) && level1Stream) { + str = level1Stream; + } else { + str = psStream; + } + str->reset(); + while ((n = str->getBlock(buf, sizeof(buf))) > 0) { + writePSBlock(buf, n); + } + str->close(); + noStateChanges = gFalse; +} + +//~ can nextFunc be reset to 0 -- maybe at the start of each page? +//~ or maybe at the start of each color space / pattern? +void PSOutputDev::cvtFunction(Function *func) { + SampledFunction *func0; + ExponentialFunction *func2; + StitchingFunction *func3; + PostScriptFunction *func4; + int thisFunc, m, n, nSamples, i, j, k; + + switch (func->getType()) { + + case -1: // identity + writePS("{}\n"); + break; + + case 0: // sampled + func0 = (SampledFunction *)func; + thisFunc = nextFunc++; + m = func0->getInputSize(); + n = func0->getOutputSize(); + nSamples = n; + for (i = 0; i < m; ++i) { + nSamples *= func0->getSampleSize(i); + } + writePSFmt("/xpdfSamples{0:d} [\n", thisFunc); + for (i = 0; i < nSamples; ++i) { + writePSFmt("{0:.6g}\n", func0->getSamples()[i]); + } + writePS("] def\n"); + writePSFmt("{{ {0:d} array {1:d} array {2:d} 2 roll\n", 2*m, m, m+2); + // [e01] [efrac] x0 x1 ... xm-1 + for (i = m-1; i >= 0; --i) { + // [e01] [efrac] x0 x1 ... xi + writePSFmt("{0:.6g} sub {1:.6g} mul {2:.6g} add\n", + func0->getDomainMin(i), + (func0->getEncodeMax(i) - func0->getEncodeMin(i)) / + (func0->getDomainMax(i) - func0->getDomainMin(i)), + func0->getEncodeMin(i)); + // [e01] [efrac] x0 x1 ... xi-1 xi' + writePSFmt("dup 0 lt {{ pop 0 }} {{ dup {0:d} gt {{ pop {1:d} }} if }} ifelse\n", + func0->getSampleSize(i) - 1, func0->getSampleSize(i) - 1); + // [e01] [efrac] x0 x1 ... xi-1 xi' + writePS("dup floor cvi exch dup ceiling cvi exch 2 index sub\n"); + // [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi') xi'-floor(xi') + writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+3, i); + // [e01] [efrac] x0 x1 ... xi-1 floor(xi') ceiling(xi') + writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+3, 2*i+1); + // [e01] [efrac] x0 x1 ... xi-1 floor(xi') + writePSFmt("{0:d} index {1:d} 3 2 roll put\n", i+2, 2*i); + // [e01] [efrac] x0 x1 ... xi-1 + } + // [e01] [efrac] + for (i = 0; i < n; ++i) { + // [e01] [efrac] y(0) ... y(i-1) + for (j = 0; j < (1<> k) & 1)); + for (k = m - 2; k >= 0; --k) { + writePSFmt("{0:d} mul {1:d} index {2:d} get add\n", + func0->getSampleSize(k), + i + j + 3, + 2 * k + ((j >> k) & 1)); + } + if (n > 1) { + writePSFmt("{0:d} mul {1:d} add ", n, i); + } + writePS("get\n"); + } + // [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^m-1) + for (j = 0; j < m; ++j) { + // [e01] [efrac] y(0) ... y(i-1) s(0) s(1) ... s(2^(m-j)-1) + for (k = 0; k < (1 << (m - j)); k += 2) { + // [e01] [efrac] y(0) ... y(i-1) <2^(m-j)-k s values> + writePSFmt("{0:d} index {1:d} get dup\n", + i + k/2 + (1 << (m-j)) - k, j); + writePS("3 2 roll mul exch 1 exch sub 3 2 roll mul add\n"); + writePSFmt("{0:d} 1 roll\n", k/2 + (1 << (m-j)) - k - 1); + } + // [e01] [efrac] s'(0) s'(1) ... s(2^(m-j-1)-1) + } + // [e01] [efrac] y(0) ... y(i-1) s + writePSFmt("{0:.6g} mul {1:.6g} add\n", + func0->getDecodeMax(i) - func0->getDecodeMin(i), + func0->getDecodeMin(i)); + writePSFmt("dup {0:.6g} lt {{ pop {1:.6g} }} {{ dup {2:.6g} gt {{ pop {3:.6g} }} if }} ifelse\n", + func0->getRangeMin(i), func0->getRangeMin(i), + func0->getRangeMax(i), func0->getRangeMax(i)); + // [e01] [efrac] y(0) ... y(i-1) y(i) + } + // [e01] [efrac] y(0) ... y(n-1) + writePSFmt("{0:d} {1:d} roll pop pop }}\n", n+2, n); + break; + + case 2: // exponential + func2 = (ExponentialFunction *)func; + n = func2->getOutputSize(); + writePSFmt("{{ dup {0:.6g} lt {{ pop {1:.6g} }} {{ dup {2:.6g} gt {{ pop {3:.6g} }} if }} ifelse\n", + func2->getDomainMin(0), func2->getDomainMin(0), + func2->getDomainMax(0), func2->getDomainMax(0)); + // x + for (i = 0; i < n; ++i) { + // x y(0) .. y(i-1) + writePSFmt("{0:d} index {1:.6g} exp {2:.6g} mul {3:.6g} add\n", + i, func2->getE(), func2->getC1()[i] - func2->getC0()[i], + func2->getC0()[i]); + if (func2->getHasRange()) { + writePSFmt("dup {0:.6g} lt {{ pop {1:.6g} }} {{ dup {2:.6g} gt {{ pop {3:.6g} }} if }} ifelse\n", + func2->getRangeMin(i), func2->getRangeMin(i), + func2->getRangeMax(i), func2->getRangeMax(i)); + } + } + // x y(0) .. y(n-1) + writePSFmt("{0:d} {1:d} roll pop }}\n", n+1, n); + break; + + case 3: // stitching + func3 = (StitchingFunction *)func; + thisFunc = nextFunc++; + for (i = 0; i < func3->getNumFuncs(); ++i) { + cvtFunction(func3->getFunc(i)); + writePSFmt("/xpdfFunc{0:d}_{1:d} exch def\n", thisFunc, i); + } + writePSFmt("{{ dup {0:.6g} lt {{ pop {1:.6g} }} {{ dup {2:.6g} gt {{ pop {3:.6g} }} if }} ifelse\n", + func3->getDomainMin(0), func3->getDomainMin(0), + func3->getDomainMax(0), func3->getDomainMax(0)); + for (i = 0; i < func3->getNumFuncs() - 1; ++i) { + writePSFmt("dup {0:.6g} lt {{ {1:.6g} sub {2:.6g} mul {3:.6g} add xpdfFunc{4:d}_{5:d} }} {{\n", + func3->getBounds()[i+1], + func3->getBounds()[i], + func3->getScale()[i], + func3->getEncode()[2*i], + thisFunc, i); + } + writePSFmt("{0:.6g} sub {1:.6g} mul {2:.6g} add xpdfFunc{3:d}_{4:d}\n", + func3->getBounds()[i], + func3->getScale()[i], + func3->getEncode()[2*i], + thisFunc, i); + for (i = 0; i < func3->getNumFuncs() - 1; ++i) { + writePS("} ifelse\n"); + } + writePS("}\n"); + break; + + case 4: // PostScript + func4 = (PostScriptFunction *)func; + writePS(func4->getCodeString()->getCString()); + writePS("\n"); + break; + } +} + +void PSOutputDev::writePSChar(char c) { + if (t3String) { + t3String->append(c); + } else { + (*outputFunc)(outputStream, &c, 1); + } +} + +void PSOutputDev::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); + } else { + (*outputFunc)(outputStream, s, (int)strlen(s)); + } +} + +void PSOutputDev::writePSFmt(const char *fmt, ...) { + va_list args; + GString *buf; + + va_start(args, fmt); + if (t3String) { + t3String->appendfv((char *)fmt, args); + } else { + buf = GString::formatv((char *)fmt, args); + (*outputFunc)(outputStream, buf->getCString(), buf->getLength()); + delete buf; + } + va_end(args); +} + +void PSOutputDev::writePSString(GString *s) { + Guchar *p; + int n, line; + char buf[8]; + + writePSChar('('); + line = 1; + for (p = (Guchar *)s->getCString(), n = s->getLength(); n; ++p, --n) { + if (line >= 64) { + writePSChar('\\'); + writePSChar('\n'); + line = 0; + } + if (*p == '(' || *p == ')' || *p == '\\') { + writePSChar('\\'); + writePSChar((char)*p); + line += 2; + } else if (*p < 0x20 || *p >= 0x80) { + sprintf(buf, "\\%03o", *p); + writePS(buf); + line += 4; + } else { + writePSChar((char)*p); + ++line; + } + } + writePSChar(')'); +} + +void PSOutputDev::writePSName(const char *s) { + const char *p; + char c; + + p = s; + while ((c = *p++)) { + if (c <= (char)0x20 || c >= (char)0x7f || + c == '(' || c == ')' || c == '<' || c == '>' || + c == '[' || c == ']' || c == '{' || c == '}' || + c == '/' || c == '%') { + writePSFmt("#{0:02x}", c & 0xff); + } else { + writePSChar(c); + } + } +} + +GString *PSOutputDev::filterPSName(GString *name) { + GString *name2; + char buf[8]; + int i; + char c; + + name2 = new GString(); + + // ghostscript chokes on names that begin with out-of-limits + // numbers, e.g., 1e4foo is handled correctly (as a name), but + // 1e999foo generates a limitcheck error + c = name->getChar(0); + if (c >= '0' && c <= '9') { + name2->append('f'); + } + + for (i = 0; i < name->getLength(); ++i) { + c = name->getChar(i); + if (c <= (char)0x20 || c >= (char)0x7f || + c == '(' || c == ')' || c == '<' || c == '>' || + c == '[' || c == ']' || c == '{' || c == '}' || + c == '/' || c == '%') { + sprintf(buf, "#%02x", c & 0xff); + name2->append(buf); + } else { + name2->append(c); + } + } + return name2; +} + +// Write a DSC-compliant . +void PSOutputDev::writePSTextLine(GString *s) { + TextString *ts; + Unicode *u; + int i, j; + int c; + + // - DSC comments must be printable ASCII; control chars and + // backslashes have to be escaped (we do cheap Unicode-to-ASCII + // conversion by simply ignoring the high byte) + // - lines are limited to 255 chars (we limit to 200 here to allow + // for the keyword, which was emitted by the caller) + // - lines that start with a left paren are treated as + // instead of , so we escape a leading paren + 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; + } else if (c < 0x20 || c > 0x7e || (j == 0 && c == '(')) { + writePSFmt("\\{0:03o}", c); + j += 4; + } else { + writePSChar((char)c); + ++j; + } + } + writePS("\n"); + delete ts; +} diff --git a/test/bug-hunting/cve/CVE-2019-10019/PSOutputDev.h b/test/bug-hunting/cve/CVE-2019-10019/PSOutputDev.h new file mode 100644 index 000000000..66057a7cb --- /dev/null +++ b/test/bug-hunting/cve/CVE-2019-10019/PSOutputDev.h @@ -0,0 +1,511 @@ +//======================================================================== +// +// PSOutputDev.h +// +// Copyright 1996-2003 Glyph & Cog, LLC +// +//======================================================================== + +#ifndef PSOUTPUTDEV_H +#define PSOUTPUTDEV_H + +#include + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +#include +#include "config.h" +#include "Object.h" +#include "GlobalParams.h" +#include "OutputDev.h" + +class GHash; +class PDFDoc; +class XRef; +class Function; +class GfxPath; +class GfxFont; +class GfxColorSpace; +class GfxDeviceGrayColorSpace; +class GfxCalGrayColorSpace; +class GfxDeviceRGBColorSpace; +class GfxCalRGBColorSpace; +class GfxDeviceCMYKColorSpace; +class GfxLabColorSpace; +class GfxICCBasedColorSpace; +class GfxIndexedColorSpace; +class GfxSeparationColorSpace; +class GfxDeviceNColorSpace; +class PDFRectangle; +class PSOutCustomColor; +class PSOutputDev; +class PSFontFileInfo; + +//------------------------------------------------------------------------ +// PSOutputDev +//------------------------------------------------------------------------ + +enum PSOutMode { + psModePS, + psModeEPS, + psModeForm +}; + +enum PSFileType { + psFile, // write to file + psPipe, // write to pipe + psStdout, // write to stdout + psGeneric // write to a generic stream +}; + +enum PSOutCustomCodeLocation { + psOutCustomDocSetup, + psOutCustomPageSetup +}; + +typedef void (*PSOutputFunc)(void *stream, const char *data, int len); + +typedef GString *(*PSOutCustomCodeCbk)(PSOutputDev *psOut, + PSOutCustomCodeLocation loc, int n, + void *data); + +class PSOutputDev: public OutputDev { +public: + + // Open a PostScript output file, and write the prolog. + PSOutputDev(char *fileName, PDFDoc *docA, + int firstPageA, int lastPageA, PSOutMode modeA, + int imgLLXA = 0, int imgLLYA = 0, + int imgURXA = 0, int imgURYA = 0, + GBool manualCtrlA = gFalse, + PSOutCustomCodeCbk customCodeCbkA = NULL, + void *customCodeCbkDataA = NULL, + GBool honorUserUnitA = gFalse); + + // Open a PSOutputDev that will write to a generic stream. + PSOutputDev(PSOutputFunc outputFuncA, void *outputStreamA, + PDFDoc *docA, + int firstPageA, int lastPageA, PSOutMode modeA, + int imgLLXA = 0, int imgLLYA = 0, + int imgURXA = 0, int imgURYA = 0, + GBool manualCtrlA = gFalse, + PSOutCustomCodeCbk customCodeCbkA = NULL, + void *customCodeCbkDataA = NULL, + GBool honorUserUnitA = gFalse); + + // Destructor -- writes the trailer and closes the file. + virtual ~PSOutputDev(); + + // 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? + // (Upside-down means (0,0) is the top left corner of the page.) + virtual GBool upsideDown() { return gFalse; } + + // Does this device use drawChar() or drawString()? + virtual GBool useDrawChar() { return gFalse; } + + // Does this device use tilingPatternFill()? If this returns false, + // tiling pattern fills will be reduced to a series of other drawing + // operations. + virtual GBool useTilingPatternFill() { return gTrue; } + + // Does this device use functionShadedFill(), axialShadedFill(), and + // radialShadedFill()? If this returns false, these shaded fills + // will be reduced to a series of other drawing operations. + virtual GBool useShadedFills() + { return level == psLevel2 || level == psLevel2Sep || + level == psLevel3 || level == psLevel3Sep; } + + // Does this device use drawForm()? If this returns false, + // form-type XObjects will be interpreted (i.e., unrolled). + virtual GBool useDrawForm() { return preload; } + + // Does this device use beginType3Char/endType3Char? Otherwise, + // text in Type 3 fonts will be drawn with drawChar/drawString. + virtual GBool interpretType3Chars() { return gFalse; } + + //----- header/trailer (used only if manualCtrl is true) + + // Write the document-level header. + void writeHeader(PDFRectangle *mediaBox, PDFRectangle *cropBox, + int pageRotate); + + // Write the Xpdf procset. + void writeXpdfProcset(); + + // Write the document-level setup. + void writeDocSetup(Catalog *catalog); + + // Write the trailer for the current page. + void writePageTrailer(); + + // Write the document trailer. + void writeTrailer(); + + //----- initialization and control + + // Check to see if a page slice should be displayed. If this + // returns false, the page display is aborted. Typically, an + // OutputDev will use some alternate means to display the page + // before returning false. + virtual GBool checkPageSlice(Page *page, double hDPI, double vDPI, + int rotate, GBool useMediaBox, GBool crop, + int sliceX, int sliceY, int sliceW, int sliceH, + GBool printing, + GBool (*abortCheckCbk)(void *data) = NULL, + void *abortCheckCbkData = NULL); + + // Start a page. + virtual void startPage(int pageNum, GfxState *state); + + // End a page. + virtual void endPage(); + + //----- save/restore graphics state + virtual void saveState(GfxState *state); + virtual void restoreState(GfxState *state); + + //----- update graphics state + virtual void updateCTM(GfxState *state, double m11, double m12, + double m21, double m22, double m31, double m32); + virtual void updateLineDash(GfxState *state); + virtual void updateFlatness(GfxState *state); + virtual void updateLineJoin(GfxState *state); + virtual void updateLineCap(GfxState *state); + virtual void updateMiterLimit(GfxState *state); + virtual void updateLineWidth(GfxState *state); + virtual void updateFillColorSpace(GfxState *state); + virtual void updateStrokeColorSpace(GfxState *state); + virtual void updateFillColor(GfxState *state); + virtual void updateStrokeColor(GfxState *state); + virtual void updateFillOverprint(GfxState *state); + virtual void updateStrokeOverprint(GfxState *state); + virtual void updateOverprintMode(GfxState *state); + virtual void updateTransfer(GfxState *state); + + //----- update text state + virtual void updateFont(GfxState *state); + virtual void updateTextMat(GfxState *state); + virtual void updateCharSpace(GfxState *state); + virtual void updateRender(GfxState *state); + virtual void updateRise(GfxState *state); + virtual void updateWordSpace(GfxState *state); + virtual void updateHorizScaling(GfxState *state); + virtual void updateTextPos(GfxState *state); + virtual void updateTextShift(GfxState *state, double shift); + virtual void saveTextPos(GfxState *state); + virtual void restoreTextPos(GfxState *state); + + //----- path painting + virtual void stroke(GfxState *state); + virtual void fill(GfxState *state); + virtual void eoFill(GfxState *state); + virtual void tilingPatternFill(GfxState *state, Gfx *gfx, Object *strRef, + int paintType, int tilingType, Dict *resDict, + double *mat, double *bbox, + int x0, int y0, int x1, int y1, + double xStep, double yStep); + virtual GBool functionShadedFill(GfxState *state, + GfxFunctionShading *shading); + virtual GBool axialShadedFill(GfxState *state, GfxAxialShading *shading); + virtual GBool radialShadedFill(GfxState *state, GfxRadialShading *shading); + + //----- path clipping + virtual void clip(GfxState *state); + virtual void eoClip(GfxState *state); + virtual void clipToStrokePath(GfxState *state); + + //----- text drawing + virtual void drawString(GfxState *state, GString *s); + virtual void endTextObject(GfxState *state); + + //----- image drawing + virtual void drawImageMask(GfxState *state, Object *ref, Stream *str, + int width, int height, GBool invert, + GBool inlineImg, GBool interpolate); + virtual void drawImage(GfxState *state, Object *ref, Stream *str, + int width, int height, GfxImageColorMap *colorMap, + 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); + +#if OPI_SUPPORT + //----- OPI functions + virtual void opiBegin(GfxState *state, Dict *opiDict); + virtual void opiEnd(GfxState *state, Dict *opiDict); +#endif + + //----- Type 3 font operators + virtual void type3D0(GfxState *state, double wx, double wy); + virtual void type3D1(GfxState *state, double wx, double wy, + double llx, double lly, double urx, double ury); + + //----- form XObjects + virtual void drawForm(Ref ref); + + //----- PostScript XObjects + virtual void psXObject(Stream *psStream, Stream *level1Stream); + + //----- miscellaneous + void setImageableArea(int imgLLXA, int imgLLYA, int imgURXA, int imgURYA) + { imgLLX = imgLLXA; imgLLY = imgLLYA; imgURX = imgURXA; imgURY = imgURYA; } + void setOffset(double x, double y) + { tx0 = x; ty0 = y; } + void setScale(double x, double y) + { xScale0 = x; yScale0 = y; } + void setRotate(int rotateA) + { rotate0 = rotateA; } + void setClip(double llx, double lly, double urx, double ury) + { clipLLX0 = llx; clipLLY0 = lly; clipURX0 = urx; clipURY0 = ury; } + void setUnderlayCbk(void (*cbk)(PSOutputDev *psOut, void *data), + void *data) + { underlayCbk = cbk; underlayCbkData = data; } + void setOverlayCbk(void (*cbk)(PSOutputDev *psOut, void *data), + void *data) + { overlayCbk = cbk; overlayCbkData = data; } + + void writePSChar(char c); + void writePSBlock(char *s, int len); + void writePS(const char *s); + void writePSFmt(const char *fmt, ...); + void writePSString(GString *s); + void writePSName(const char *s); + +private: + + void init(PSOutputFunc outputFuncA, void *outputStreamA, + PSFileType fileTypeA, PDFDoc *docA, + int firstPageA, int lastPageA, PSOutMode modeA, + int imgLLXA, int imgLLYA, int imgURXA, int imgURYA, + GBool manualCtrlA, GBool honorUserUnitA); + GBool checkIfPageNeedsToBeRasterized(int pg); + void setupResources(Dict *resDict); + void setupFonts(Dict *resDict); + void setupFont(GfxFont *font, 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 *setupExternalOpenTypeCFFFont(GfxFont *font, + GString *fileName); + PSFontFileInfo *setupType3Font(GfxFont *font, Dict *parentResDict); + GString *makePSFontName(GfxFont *font, Ref *id); + GString *fixType1Font(GString *font, int length1, int length2); + GBool splitType1PFA(Guchar *font, int fontSize, + int length1, int length2, + GString *textSection, GString *binSection); + GBool splitType1PFB(Guchar *font, int fontSize, + GString *textSection, GString *binSection); + GString *asciiHexDecodeType1EexecSection(GString *in); + GBool fixType1EexecSection(GString *binSection, GString *out); + GString *copyType1PFA(Guchar *font, int fontSize); + GString *copyType1PFB(Guchar *font, int fontSize); + void renameType1Font(GString *font, GString *name); + void setupDefaultFont(); + void setupImages(Dict *resDict); + void setupImage(Ref id, Stream *str, GBool mask, Array *colorKeyMask); + void setupForms(Dict *resDict); + void setupForm(Object *strRef, Object *strObj); + void addProcessColor(double c, double m, double y, double k); + void addCustomColor(GfxState *state, GfxSeparationColorSpace *sepCS); + void addCustomColors(GfxState *state, GfxDeviceNColorSpace *devnCS); + void tilingPatternFillL1(GfxState *state, Gfx *gfx, Object *strRef, + int paintType, int tilingType, Dict *resDict, + double *mat, double *bbox, + int x0, int y0, int x1, int y1, + double xStep, double yStep); + void tilingPatternFillL2(GfxState *state, Gfx *gfx, Object *strRef, + int paintType, int tilingType, Dict *resDict, + double *mat, double *bbox, + int x0, int y0, int x1, int y1, + double xStep, double yStep); + void doPath(GfxPath *path); + void doImageL1(Object *ref, GfxState *state, + GfxImageColorMap *colorMap, + GBool invert, GBool inlineImg, + Stream *str, int width, int height, int len); + void doImageL1Sep(GfxState *state, GfxImageColorMap *colorMap, + GBool invert, GBool inlineImg, + Stream *str, int width, int height, int len); + void doImageL2(Object *ref, GfxState *state, + GfxImageColorMap *colorMap, + GBool invert, GBool inlineImg, + Stream *str, int width, int height, int len, + int *maskColors, Stream *maskStr, + int maskWidth, int maskHeight, GBool maskInvert); + void convertColorKeyMaskToClipRects(GfxImageColorMap *colorMap, + Stream *str, + int width, int height, + int *maskColors); + void convertExplicitMaskToClipRects(Stream *maskStr, + int maskWidth, int maskHeight, + GBool maskInvert); + void doImageL3(Object *ref, GfxState *state, + GfxImageColorMap *colorMap, + GBool invert, GBool inlineImg, + Stream *str, int width, int height, int len, + int *maskColors, Stream *maskStr, + int maskWidth, int maskHeight, GBool maskInvert); + void dumpColorSpaceL2(GfxState *state, GfxColorSpace *colorSpace, + GBool genXform, GBool updateColors, + GBool map01); + void dumpDeviceGrayColorSpace(GfxDeviceGrayColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01); + void dumpCalGrayColorSpace(GfxCalGrayColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01); + void dumpDeviceRGBColorSpace(GfxDeviceRGBColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01); + void dumpCalRGBColorSpace(GfxCalRGBColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01); + void dumpDeviceCMYKColorSpace(GfxDeviceCMYKColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01); + void dumpLabColorSpace(GfxLabColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01); + void dumpICCBasedColorSpace(GfxState *state, GfxICCBasedColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01); + void dumpIndexedColorSpace(GfxState *state, + GfxIndexedColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01); + void dumpSeparationColorSpace(GfxState *state, + GfxSeparationColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01); + void dumpDeviceNColorSpaceL2(GfxState *state, GfxDeviceNColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01); + void dumpDeviceNColorSpaceL3(GfxState *state, GfxDeviceNColorSpace *cs, + GBool genXform, GBool updateColors, + GBool map01); + GString *createDeviceNTintFunc(GfxDeviceNColorSpace *cs); +#if OPI_SUPPORT + void opiBegin20(GfxState *state, Dict *dict); + void opiBegin13(GfxState *state, Dict *dict); + void opiTransform(GfxState *state, double x0, double y0, + double *x1, double *y1); + GBool getFileSpec(Object *fileSpec, Object *fileName); +#endif + void cvtFunction(Function *func); + GString *filterPSName(GString *name); + void writePSTextLine(GString *s); + + PSLevel level; // PostScript level + PSOutMode mode; // PostScript mode (PS, EPS, form) + int paperWidth; // width of paper, in pts + int paperHeight; // height of paper, in pts + GBool paperMatch; // true if paper size is set to match each page + int imgLLX, imgLLY, // imageable area, in pts + imgURX, imgURY; + GBool preload; // load all images into memory, and + // predefine forms + + PSOutputFunc outputFunc; + void *outputStream; + PSFileType fileType; // file / pipe / stdout + GBool manualCtrl; + int seqPage; // current sequential page number + void (*underlayCbk)(PSOutputDev *psOut, void *data); + void *underlayCbkData; + void (*overlayCbk)(PSOutputDev *psOut, void *data); + void *overlayCbkData; + GString *(*customCodeCbk)(PSOutputDev *psOut, + PSOutCustomCodeLocation loc, int n, + void *data); + void *customCodeCbkData; + GBool honorUserUnit; + + PDFDoc *doc; + XRef *xref; // the xref table for this PDF file + + int firstPage; // first output page + int lastPage; // last output page + char *rasterizePage; // boolean for each page - true if page + // needs to be rasterized + + 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 + Ref *formIDs; // list of IDs for predefined forms + int formIDLen; // number of entries in formIDs array + int formIDSize; // size of formIDs array + char *visitedResources; // vector of resource objects already visited + GBool noStateChanges; // true if there have been no state changes + // since the last save + GList *saveStack; // "no state changes" flag for each + // pending save + int numTilingPatterns; // current number of nested tiling patterns + int nextFunc; // next unique number to use for a function + + GList *paperSizes; // list of used paper sizes, if paperMatch + // is true [PSOutPaperSize] + double tx0, ty0; // global translation + double xScale0, yScale0; // global scaling + int rotate0; // rotation angle (0, 90, 180, 270) + double clipLLX0, clipLLY0, + clipURX0, clipURY0; + double tx, ty; // global translation for current page + double xScale, yScale; // global scaling for current page + int rotate; // rotation angle for current page + double epsX1, epsY1, // EPS bounding box (unrotated) + epsX2, epsY2; + + GString *embFontList; // resource comments for embedded fonts + + int processColors; // used process colors + PSOutCustomColor // used custom colors + *customColors; + + GBool haveTextClip; // set if text has been drawn with a + // clipping render mode + + GBool inType3Char; // inside a Type 3 CharProc + GString *t3String; // Type 3 content string + double t3WX, t3WY, // Type 3 character parameters + t3LLX, t3LLY, t3URX, t3URY; + GBool t3FillColorOnly; // operators should only use the fill color + GBool t3Cacheable; // cleared if char is not cacheable + GBool t3NeedsRestore; // set if a 'q' operator was issued + +#if OPI_SUPPORT + int opi13Nest; // nesting level of OPI 1.3 objects + int opi20Nest; // nesting level of OPI 2.0 objects +#endif + + GBool ok; // set up ok? + + friend class WinPDFPrinter; +}; + +#endif