1 /*
2  * in_ps.cpp -- read PS and PDF files using GS
3  * by pts@fazekas.hu at Tue Sep 30 12:33:11 CEST 2003
4  */
5 
6 #ifdef __GNUC__
7 #ifndef __clang__
8 #pragma implementation
9 #endif
10 #endif
11 
12 #include "image.hpp"
13 
14 #if USE_IN_PS || USE_IN_PDF
15 
16 #include "error.hpp"
17 #include "gensio.hpp"
18 #include "helpere.hpp"
19 #include <string.h> /* memchr() */
20 #include <stdio.h> /* printf() */
21 
22 #if OS_COTY==COTY_WIN9X || OS_COTY==COTY_WINNT
23 #  define GS "gswin32c"
24 #else
25 #  define GS "gs"
26 #endif
27 
28 #endif /* USE_IN_PS || USE_IN_PDF */
29 
30 /** Adds a Ghostscript invocation command. Works for both PS and PDF. */
add_gs_cmd(SimBuffer::B & cmd,SimBuffer::Flat const & hints)31 static void add_gs_cmd(SimBuffer::B &cmd, SimBuffer::Flat const& hints) {
32   cmd << GS " -r72 -q -dTextAlphaBits=4 -dGraphicsAlphaBits=4 -dLastPage=1 -sDEVICE=pnmraw -dDELAYSAFER -dBATCH -dNOPAUSE -sOutputFile=%D ";
33   char const *p=hints(), *r;
34   /* Dat: hints ends by ',' '\0' */
35   // Files::FILEW(stdout) << hints << ";;\n";
36   while (*p!=',') p++; /* Dat: safe, because hints is assumed to start and end by ',' */
37   while (1) {
38     assert(*p==',');
39     if (*++p=='\0') break;
40     if (p[0]=='g' && p[1]=='s' && p[2]=='=') {
41       r=p+=3;
42       while (*p!=',') p++;
43       cmd.vi_write(r, p-r); /* Dat: -r... in here overrides -r72 above */
44       cmd << ' ';
45     } else {
46       while (*p!=',') p++;
47     }
48   }
49 }
50 
51 #if USE_IN_PS
52 
53 /* !! -r144 and scale back..., also for PDF -- to enhance image quality */
54 
55 #undef  DO_KEEP_TEMP
56 #define DO_KEEP_TEMP 0
57 
in_ps_reader_low(Image::Loader::UFD * ufd,char const * bboxline,SimBuffer::Flat const & hints)58 static Image::Sampled *in_ps_reader_low(Image::Loader::UFD* ufd, char const*bboxline, SimBuffer::Flat const& hints) {
59   SimBuffer::B mainfn;
60   if (!Files::find_tmpnam(mainfn)) Error::sev(Error::EERROR) << "in_ps_reader" << ": tmpnam() failed" << (Error*)0;
61   mainfn.term0();
62   #if DO_KEEP_TEMP
63     printf("m: %s\n", mainfn());
64   #else
65     Files::tmpRemoveCleanup(mainfn());
66   #endif
67   FILE *f=fopen(mainfn(),"w");
68   fprintf(f, "%s", bboxline);
69   /* vvv Dat: ignore extra calls to `showpage' */
70   fprintf(f, "/showpage [ currentdict  /showpage  {}  /put load  /showpage load ] cvx def\n");
71   fprintf(f, "_IFN (r) file cvx exec\nshowpage\n"); /* Dat: doesn't rely on GS to
72     recognise EPSF-x.y comment, so works with both old and new gs */
73   // ^^^ !! DOS EPSF etc. instead of exec/run
74   fclose(f);
75   // Error::sev(Error::EERROR) << "Cannot load PS images yet." << (Error*)0;
76   /* Dat: -dLastPage=1 has no effect, but we keep it for PDF compatibility */
77   /* !! keep only 1st page, without setpagedevice for PS files */
78   /* Dat: -dSAFER won't let me open the file with `/' under ESP Ghostscript 7.05.6 (2003-02-05) */
79   /* Imp: win9X command line too long? */
80   SimBuffer::B cmd;
81   add_gs_cmd(cmd,hints);
82   cmd << " -s_IFN=%S -- %*";
83   /*fprintf(stderr,"cmd:%s\n",cmd.term0()()); */
84   HelperE helper(cmd.term0()(), mainfn()); /* Run external process GS */
85   Filter::UngetFILED* ufdd=(Filter::UngetFILED*)ufd;
86   int i=ufdd->vi_getcc();
87   if (i<0) Error::sev(Error::EERROR) << "in_ps_reader: Empty PostScript file." << (Error*)0; /* should never happen */
88   ((Filter::PipeE*)&helper)->vi_putcc(i);
89   Encoder::writeFrom(*(Filter::PipeE*)&helper, *ufdd);
90   ((Filter::PipeE*)&helper)->vi_write(0,0); /* Signal EOF */
91   #if !DO_KEEP_TEMP
92     remove(mainfn());
93   #endif
94   return helper.getImg();
95 }
96 
in_ps_reader(Image::Loader::UFD * ufd,SimBuffer::Flat const & hints)97 static Image::Sampled *in_ps_reader(Image::Loader::UFD* ufd, SimBuffer::Flat const& hints) {
98   /* Use the paper size (<</PageSize[...]>> setpagedevice; a4; letter etc.)
99    * set up by the PostScript file, or the system default paper size.
100    */
101   return in_ps_reader_low(ufd, "", hints);
102 }
103 
in_eps_reader(Image::Loader::UFD * ufd,SimBuffer::Flat const & hints)104 static Image::Sampled *in_eps_reader(Image::Loader::UFD* ufd, SimBuffer::Flat const& hints) {
105   /* Use the *BoundingBox if available, else use the paper size set up by
106    * the EPS file, or the system default paper size.
107    */
108   double llx=0.0, lly=0.0, urx=0.0, ury=0.0;
109   Filter::UngetFILED* ufdd=(Filter::UngetFILED*)ufd;
110   /* ^^^ SUXX: no warning for ufdd=ufdd */
111   /* SUXX: valgrind, checkergcc: no indication of segfault due to stack overflow inside fgetc() */
112   SimBuffer::B line; /* Imp: limit for max line length etc. */
113   #if 0
114   while ((line.clearFree(), ufdd->appendLine(line), line)) {
115     line.term0();
116     printf("line: %s", line());
117   }
118   #endif
119   slen_t line0ofs;
120   int had=0;
121   while ((line0ofs=line.getLength(), ufdd->appendLine(line), line0ofs!=line.getLength())) {
122     char const *thisline=line()+line0ofs;
123     line.term0();
124     // printf("line: %s", thisline);
125     if (thisline[0]=='\n' || thisline[0]=='\r') continue; /* empty line */
126     if (thisline[0]=='%' && thisline[1]=='!') continue; /* %!PS-... */
127     if (thisline[0]!='%' || thisline[1]!='%') break; /* non-ADSC comment */
128          if (had<3 && 4==sscanf(thisline+2, "ExactBoundingBox:%lg%lg%lg%lg", &llx, &lly, &urx, &ury)) had=3;
129     else if (had<2 && 4==sscanf(thisline+2, "HiResBoundingBox:%lg%lg%lg%lg", &llx, &lly, &urx, &ury)) had=2;
130     else if (had<1 && 4==sscanf(thisline+2, "BoundingBox:%lg%lg%lg%lg", &llx, &lly, &urx, &ury)) had=1;
131     /* Dat: finds MetaPost hiresbbox after %%EndComments */
132     // printf("line: %s", line()+line0ofs);
133   }
134   ufdd->unread(line(), line.getLength()); line.clearFree();
135   char bboxline[400];
136   if (had!=0) {
137     // fprintf(stderr, "bbox=[%"PTS_CFG_PRINTFGLEN"g %"PTS_CFG_PRINTFGLEN"g %"PTS_CFG_PRINTFGLEN"g %"PTS_CFG_PRINTFGLEN"g]\n", llx, lly, urx, ury);
138     /* Dat: we must call translate _after_ setpagedevice (so it will take effect), at least with ESP Ghostscript 7.05.6 (2003-02-05); BUGFIX at Fri Aug 12 22:49:07 CEST 2005 */
139     sprintf(bboxline,
140       "<</PageSize[%" PTS_CFG_PRINTFGLEN "g %" PTS_CFG_PRINTFGLEN "g]>>setpagedevice\n"
141       /* removing /PageSize also cancels /a4, /a5 etc. */
142       /* we need `currentmatrix ... setpagedevice setmatrix' because
143        * setpagedevice cancels the current transformation matrix so our
144        * `translate' below would be canceled if the EPS file contains
145        * `a5', `setpagedevice' etc.
146        */
147       "/setpagedevice{matrix currentmatrix exch "
148       "dup length dict copy dup /PageSize undef setpagedevice "
149       "setmatrix}bind def\n"
150       "%" PTS_CFG_PRINTFGLEN "g %" PTS_CFG_PRINTFGLEN "g translate\n"
151       , urx-llx, ury-lly, -llx, -lly);
152   } else {
153     Error::sev(Error::WARNING) << "in_eps_reader: missing EPS bbox" << (Error*)0;
154     bboxline[0]='\0';
155   }
156 
157   return in_ps_reader_low(ufd, bboxline, hints);
158 }
159 
in_ps_checker(char buf[Image::Loader::MAGIC_LEN],char[Image::Loader::MAGIC_LEN],SimBuffer::Flat const &,Image::Loader::UFD *)160 static Image::Loader::reader_t in_ps_checker(char buf[Image::Loader::MAGIC_LEN], char [Image::Loader::MAGIC_LEN], SimBuffer::Flat const&, Image::Loader::UFD*) {
161   if (0!=memcmp(buf,"%!PS-Adobe-",11)) return 0;
162   char const *p=buf+11, *pend=buf+Image::Loader::MAGIC_LEN;
163   while (p!=pend && *p!=' ' && *p!='\t') p++;
164   while (p!=pend && (*p==' ' || *p=='\t')) p++;
165   /* Imp: option to accept BoundingBox for non-EPS PS */
166   return (0==strncmp(p,"EPSF-",5)) ? in_eps_reader : in_ps_reader;
167   /* ^^^ BUGFIX at Fri Nov 26 12:13:58 CET 2004 */
168   /* ^^^ BUGFIX again at Thu Jan  6 10:25:54 CET 2005 */
169 }
170 
171 #else
172 #define in_ps_checker (Image::Loader::checker_t)NULLP
173 #endif /* USE_IN_PS */
174 
175 Image::Loader in_ps_loader = { "PS", in_ps_checker, 0 };
176 
177 #if USE_IN_PDF
178 
in_pdf_reader(Image::Loader::UFD * ufd,SimBuffer::Flat const & hints)179 static Image::Sampled *in_pdf_reader(Image::Loader::UFD* ufd, SimBuffer::Flat const& hints) {
180   // Error::sev(Error::EERROR) << "Cannot load PDF images yet." << (Error*)0;
181   SimBuffer::B cmd;
182   add_gs_cmd(cmd,hints);
183   cmd << " -- %S";
184   /* Dat: -dSAFER won't let me open the file with `/' under ESP Ghostscript 7.05.6 (2003-02-05) */
185   /* Imp: win9X command line too long? */
186   fprintf(stderr, "gs_cmd=(%s)\n", cmd.term0()());
187   HelperE helper(cmd.term0()()); /* Run external process GS */
188   Encoder::writeFrom(*(Filter::PipeE*)&helper, *(Filter::UngetFILED*)ufd);
189   ((Filter::PipeE*)&helper)->vi_write(0,0); /* Signal EOF */
190   return helper.getImg();
191 }
192 
in_pdf_checker(char buf[Image::Loader::MAGIC_LEN],char[Image::Loader::MAGIC_LEN],SimBuffer::Flat const &,Image::Loader::UFD *)193 static Image::Loader::reader_t in_pdf_checker(char buf[Image::Loader::MAGIC_LEN], char [Image::Loader::MAGIC_LEN], SimBuffer::Flat const&, Image::Loader::UFD*) {
194   return 0==memcmp(buf,"%PDF-",5) ? in_pdf_reader : 0;
195 }
196 
197 #else
198 #define in_pdf_checker (Image::Loader::checker_t)NULLP
199 #endif /* USE_IN_PDF */
200 
201 Image::Loader in_pdf_loader = { "PDF", in_pdf_checker, 0 };
202