Blob


1 /*
2 * ps.c
3 *
4 * provide postscript file reading support for page
5 */
7 #include <u.h>
8 #include <libc.h>
9 #include <draw.h>
10 #include <cursor.h>
11 #include <thread.h>
12 #include <bio.h>
13 #include <ctype.h>
14 #include "page.h"
16 static int pswritepage(Document *d, int fd, int page);
17 static Image* psdrawpage(Document *d, int page);
18 static char* pspagename(Document*, int);
20 #define R(r) (r).min.x, (r).min.y, (r).max.x, (r).max.y
21 Rectangle
22 rdbbox(char *p)
23 {
24 Rectangle r;
25 int a;
26 char *f[4];
27 while(*p == ':' || *p == ' ' || *p == '\t')
28 p++;
29 if(tokenize(p, f, 4) != 4)
30 return Rect(0,0,0,0);
31 r = Rect(atoi(f[0]), atoi(f[1]), atoi(f[2]), atoi(f[3]));
32 r = canonrect(r);
33 if(Dx(r) <= 0 || Dy(r) <= 0)
34 return Rect(0,0,0,0);
36 if(truetoboundingbox)
37 return r;
39 /* initdraw not called yet, can't use %R */
40 if(chatty) fprint(2, "[%d %d %d %d] -> ", R(r));
41 /*
42 * attempt to sniff out A4, 8½×11, others
43 * A4 is 596×842
44 * 8½×11 is 612×792
45 */
47 a = Dx(r)*Dy(r);
48 if(a < 300*300){ /* really small, probably supposed to be */
49 /* empty */
50 } else if(Dx(r) <= 596 && r.max.x <= 596 && Dy(r) > 792 && Dy(r) <= 842 && r.max.y <= 842) /* A4 */
51 r = Rect(0, 0, 596, 842);
52 else { /* cast up to 8½×11 */
53 if(Dx(r) <= 612 && r.max.x <= 612){
54 r.min.x = 0;
55 r.max.x = 612;
56 }
57 if(Dy(r) <= 792 && r.max.y <= 792){
58 r.min.y = 0;
59 r.max.y = 792;
60 }
61 }
62 if(chatty) fprint(2, "[%d %d %d %d]\n", R(r));
63 return r;
64 }
66 #define RECT(X) X.min.x, X.min.y, X.max.x, X.max.y
68 int
69 prefix(char *x, char *y)
70 {
71 return strncmp(x, y, strlen(y)) == 0;
72 }
74 /*
75 * document ps is really being printed as n-up pages.
76 * we need to treat every n pages as 1.
77 */
78 void
79 repaginate(PSInfo *ps, int n)
80 {
81 int i, np, onp;
82 Page *page;
84 page = ps->page;
85 onp = ps->npage;
86 np = (ps->npage+n-1)/n;
88 if(chatty) {
89 for(i=0; i<=onp+1; i++)
90 print("page %d: %d\n", i, page[i].offset);
91 }
93 for(i=0; i<np; i++)
94 page[i] = page[n*i];
96 /* trailer */
97 page[np] = page[onp];
99 /* EOF */
100 page[np+1] = page[onp+1];
102 ps->npage = np;
104 if(chatty) {
105 for(i=0; i<=np+1; i++)
106 print("page %d: %d\n", i, page[i].offset);
111 Document*
112 initps(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
114 Document *d;
115 PSInfo *ps;
116 char *p;
117 char *q, *r;
118 char eol;
119 char *nargv[1];
120 char fdbuf[20];
121 char tmp[32];
122 int fd;
123 int i;
124 int incomments;
125 int cantranslate;
126 int trailer=0;
127 int nesting=0;
128 int dumb=0;
129 int landscape=0;
130 long psoff;
131 long npage, mpage;
132 Page *page;
133 Rectangle bbox = Rect(0,0,0,0);
135 if(argc > 1) {
136 fprint(2, "can only view one ps file at a time\n");
137 return nil;
140 fprint(2, "reading through postscript...\n");
141 if(b == nil){ /* standard input; spool to disk (ouch) */
142 fd = spooltodisk(buf, nbuf, nil);
143 sprint(fdbuf, "/dev/fd/%d", fd);
144 b = Bopen(fdbuf, OREAD);
145 if(b == nil){
146 fprint(2, "cannot open disk spool file\n");
147 wexits("Bopen temp");
149 nargv[0] = fdbuf;
150 argv = nargv;
153 /* find %!, perhaps after PCL nonsense */
154 Bseek(b, 0, 0);
155 psoff = 0;
156 eol = 0;
157 for(i=0; i<16; i++){
158 psoff = Boffset(b);
159 if(!(p = Brdline(b, eol='\n')) && !(p = Brdline(b, eol='\r'))) {
160 fprint(2, "cannot find end of first line\n");
161 wexits("initps");
163 if(p[0]=='\x1B')
164 p++, psoff++;
165 if(p[0] == '%' && p[1] == '!')
166 break;
168 if(i == 16){
169 werrstr("not ps");
170 return nil;
173 /* page counting */
174 npage = 0;
175 mpage = 16;
176 page = emalloc(mpage*sizeof(*page));
177 memset(page, 0, mpage*sizeof(*page));
179 cantranslate = goodps;
180 incomments = 1;
181 Keepreading:
182 while(p = Brdline(b, eol)) {
183 if(p[0] == '%')
184 if(chatty) fprint(2, "ps %.*s\n", utfnlen(p, Blinelen(b)-1), p);
185 if(npage == mpage) {
186 mpage *= 2;
187 page = erealloc(page, mpage*sizeof(*page));
188 memset(&page[npage], 0, npage*sizeof(*page));
191 if(p[0] != '%' || p[1] != '%')
192 continue;
194 if(prefix(p, "%%BeginDocument")) {
195 nesting++;
196 continue;
198 if(nesting > 0 && prefix(p, "%%EndDocument")) {
199 nesting--;
200 continue;
202 if(nesting)
203 continue;
205 if(prefix(p, "%%EndComment")) {
206 incomments = 0;
207 continue;
209 if(reverse == -1 && prefix(p, "%%PageOrder")) {
210 /* glean whether we should reverse the viewing order */
211 p[Blinelen(b)-1] = 0;
212 if(strstr(p, "Ascend"))
213 reverse = 0;
214 else if(strstr(p, "Descend"))
215 reverse = 1;
216 else if(strstr(p, "Special"))
217 dumb = 1;
218 p[Blinelen(b)-1] = '\n';
219 continue;
220 } else if(prefix(p, "%%Trailer")) {
221 incomments = 1;
222 page[npage].offset = Boffset(b)-Blinelen(b);
223 trailer = 1;
224 continue;
225 } else if(incomments && prefix(p, "%%Orientation")) {
226 if(strstr(p, "Landscape"))
227 landscape = 1;
228 } else if(incomments && Dx(bbox)==0 && prefix(p, q="%%BoundingBox")) {
229 bbox = rdbbox(p+strlen(q)+1);
230 if(chatty)
231 /* can't use %R because haven't initdraw() */
232 fprint(2, "document bbox [%d %d %d %d]\n",
233 RECT(bbox));
234 continue;
237 /*
238 * If they use the initgraphics command, we can't play our translation tricks.
239 */
240 p[Blinelen(b)-1] = 0;
241 if((q=strstr(p, "initgraphics")) && ((r=strchr(p, '%'))==nil || r > q))
242 cantranslate = 0;
243 p[Blinelen(b)-1] = eol;
245 if(!prefix(p, "%%Page:"))
246 continue;
248 /*
249 * figure out of the %%Page: line contains a page number
250 * or some other page description to use in the menu bar.
252 * lines look like %%Page: x y or %%Page: x
253 * we prefer just x, and will generate our
254 * own if necessary.
255 */
256 p[Blinelen(b)-1] = 0;
257 if(chatty) fprint(2, "page %s\n", p);
258 r = p+7;
259 while(*r == ' ' || *r == '\t')
260 r++;
261 q = r;
262 while(*q && *q != ' ' && *q != '\t')
263 q++;
264 free(page[npage].name);
265 if(*r) {
266 if(*r == '"' && *q == '"')
267 r++, q--;
268 if(*q)
269 *q = 0;
270 page[npage].name = estrdup(r);
271 *q = 'x';
272 } else {
273 snprint(tmp, sizeof tmp, "p %ld", npage+1);
274 page[npage].name = estrdup(tmp);
277 /*
278 * store the offset info for later viewing
279 */
280 trailer = 0;
281 p[Blinelen(b)-1] = eol;
282 page[npage++].offset = Boffset(b)-Blinelen(b);
284 if(Blinelen(b) > 0){
285 fprint(2, "page: linelen %d\n", Blinelen(b));
286 Bseek(b, Blinelen(b), 1);
287 goto Keepreading;
290 if(Dx(bbox) == 0 || Dy(bbox) == 0)
291 bbox = Rect(0,0,612,792); /* 8½×11 */
292 /*
293 * if we didn't find any pages, assume the document
294 * is one big page
295 */
296 if(npage == 0) {
297 dumb = 1;
298 if(chatty) fprint(2, "don't know where pages are\n");
299 reverse = 0;
300 goodps = 0;
301 trailer = 0;
302 page[npage].name = "p 1";
303 page[npage++].offset = 0;
306 if(npage+2 > mpage) {
307 mpage += 2;
308 page = erealloc(page, mpage*sizeof(*page));
309 memset(&page[mpage-2], 0, 2*sizeof(*page));
312 if(!trailer)
313 page[npage].offset = Boffset(b);
315 Bseek(b, 0, 2); /* EOF */
316 page[npage+1].offset = Boffset(b);
318 d = emalloc(sizeof(*d));
319 ps = emalloc(sizeof(*ps));
320 ps->page = page;
321 ps->npage = npage;
322 ps->bbox = bbox;
323 ps->psoff = psoff;
325 d->extra = ps;
326 d->npage = ps->npage;
327 d->b = b;
328 d->drawpage = psdrawpage;
329 d->pagename = pspagename;
331 d->fwdonly = ps->clueless = dumb;
332 d->docname = argv[0];
333 /*
334 * "tag" the doc as an image for now since there still is the "blank page"
335 * problem for ps files.
336 */
337 d->type = Tgfx;
339 if(spawngs(&ps->gs, "-dSAFER") < 0)
340 return nil;
342 if(!cantranslate)
343 bbox.min = ZP;
344 setdim(&ps->gs, bbox, ppi, landscape);
346 if(goodps){
347 /*
348 * We want to only send the page (i.e. not header and trailer) information
349 * for each page, so initialize the device by sending the header now.
350 */
351 pswritepage(d, ps->gs.gsfd, -1);
352 waitgs(&ps->gs);
355 if(dumb) {
356 fprint(ps->gs.gsfd, "(%s) run\n", argv[0]);
357 fprint(ps->gs.gsfd, "(/dev/fd/3) (w) file dup (THIS IS NOT A PLAN9 BITMAP 01234567890123456789012345678901234567890123456789\\n) writestring flushfile\n");
360 ps->bbox = bbox;
362 return d;
365 static int
366 pswritepage(Document *d, int fd, int page)
368 Biobuf *b = d->b;
369 PSInfo *ps = d->extra;
370 int t, n, i;
371 long begin, end;
372 char buf[8192];
374 if(page == -1)
375 begin = ps->psoff;
376 else
377 begin = ps->page[page].offset;
379 end = ps->page[page+1].offset;
381 if(chatty) {
382 fprint(2, "writepage(%d)... from #%ld to #%ld...\n",
383 page, begin, end);
385 Bseek(b, begin, 0);
387 t = end-begin;
388 n = sizeof(buf);
389 if(n > t) n = t;
390 while(t > 0 && (i=Bread(b, buf, n)) > 0) {
391 if(write(fd, buf, i) != i)
392 return -1;
393 t -= i;
394 if(n > t)
395 n = t;
397 return end-begin;
400 static Image*
401 psdrawpage(Document *d, int page)
403 PSInfo *ps = d->extra;
404 Image *im;
406 if(ps->clueless)
407 return convert(&ps->gs.g);
409 waitgs(&ps->gs);
411 if(goodps)
412 pswritepage(d, ps->gs.gsfd, page);
413 else {
414 pswritepage(d, ps->gs.gsfd, -1);
415 pswritepage(d, ps->gs.gsfd, page);
416 pswritepage(d, ps->gs.gsfd, d->npage);
418 /*
419 * If last line terminator is \r, gs will read ahead to check for \n
420 * so send one to avoid deadlock.
421 */
422 write(ps->gs.gsfd, "\n", 1);
423 im = convert(&ps->gs.g);
424 if(im == nil) {
425 fprint(2, "fatal: readimage error %r\n");
426 wexits("readimage");
428 waitgs(&ps->gs);
430 return im;
433 static char*
434 pspagename(Document *d, int page)
436 PSInfo *ps = (PSInfo *) d->extra;
437 return ps->page[page].name;