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 <event.h>
12 #include <bio.h>
13 #include <ctype.h>
14 #include "page.h"
16 typedef struct PSInfo PSInfo;
17 typedef struct Page Page;
19 struct Page {
20 char *name;
21 int offset; /* offset of page beginning within file */
22 };
24 struct PSInfo {
25 GSInfo gs;
26 Rectangle bbox; /* default bounding box */
27 Page *page;
28 int npage;
29 int clueless; /* don't know where page boundaries are */
30 long psoff; /* location of %! in file */
31 char ctm[256];
32 };
34 static int pswritepage(Document *d, int fd, int page);
35 static Image* psdrawpage(Document *d, int page);
36 static char* pspagename(Document*, int);
38 #define R(r) (r).min.x, (r).min.y, (r).max.x, (r).max.y
39 Rectangle
40 rdbbox(char *p)
41 {
42 Rectangle r;
43 int a;
44 char *f[4];
45 while(*p == ':' || *p == ' ' || *p == '\t')
46 p++;
47 if(tokenize(p, f, 4) != 4)
48 return Rect(0,0,0,0);
49 r = Rect(atoi(f[0]), atoi(f[1]), atoi(f[2]), atoi(f[3]));
50 r = canonrect(r);
51 if(Dx(r) <= 0 || Dy(r) <= 0)
52 return Rect(0,0,0,0);
54 if(truetoboundingbox)
55 return r;
57 /* initdraw not called yet, can't use %R */
58 if(chatty) fprint(2, "[%d %d %d %d] -> ", R(r));
59 /*
60 * attempt to sniff out A4, 8½×11, others
61 * A4 is 596×842
62 * 8½×11 is 612×792
63 */
65 a = Dx(r)*Dy(r);
66 if(a < 300*300){ /* really small, probably supposed to be */
67 /* empty */
68 } else if(Dx(r) <= 596 && r.max.x <= 596 && Dy(r) > 792 && Dy(r) <= 842 && r.max.y <= 842) /* A4 */
69 r = Rect(0, 0, 596, 842);
70 else { /* cast up to 8½×11 */
71 if(Dx(r) <= 612 && r.max.x <= 612){
72 r.min.x = 0;
73 r.max.x = 612;
74 }
75 if(Dy(r) <= 792 && r.max.y <= 792){
76 r.min.y = 0;
77 r.max.y = 792;
78 }
79 }
80 if(chatty) fprint(2, "[%d %d %d %d]\n", R(r));
81 return r;
82 }
84 #define RECT(X) X.min.x, X.min.y, X.max.x, X.max.y
86 int
87 prefix(char *x, char *y)
88 {
89 return strncmp(x, y, strlen(y)) == 0;
90 }
92 /*
93 * document ps is really being printed as n-up pages.
94 * we need to treat every n pages as 1.
95 */
96 void
97 repaginate(PSInfo *ps, int n)
98 {
99 int i, np, onp;
100 Page *page;
102 page = ps->page;
103 onp = ps->npage;
104 np = (ps->npage+n-1)/n;
106 if(chatty) {
107 for(i=0; i<=onp+1; i++)
108 print("page %d: %d\n", i, page[i].offset);
111 for(i=0; i<np; i++)
112 page[i] = page[n*i];
114 /* trailer */
115 page[np] = page[onp];
117 /* EOF */
118 page[np+1] = page[onp+1];
120 ps->npage = np;
122 if(chatty) {
123 for(i=0; i<=np+1; i++)
124 print("page %d: %d\n", i, page[i].offset);
129 Document*
130 initps(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf)
132 Document *d;
133 PSInfo *ps;
134 char *p;
135 char *q, *r;
136 char eol;
137 char *nargv[1];
138 char fdbuf[20];
139 char tmp[32];
140 int fd;
141 int i;
142 int incomments;
143 int cantranslate;
144 int trailer=0;
145 int nesting=0;
146 int dumb=0;
147 int landscape=0;
148 long psoff;
149 long npage, mpage;
150 Page *page;
151 Rectangle bbox = Rect(0,0,0,0);
153 if(argc > 1) {
154 fprint(2, "can only view one ps file at a time\n");
155 return nil;
158 fprint(2, "reading through postscript...\n");
159 if(b == nil){ /* standard input; spool to disk (ouch) */
160 fd = spooltodisk(buf, nbuf, nil);
161 sprint(fdbuf, "/fd/%d", fd);
162 b = Bopen(fdbuf, OREAD);
163 if(b == nil){
164 fprint(2, "cannot open disk spool file\n");
165 wexits("Bopen temp");
167 nargv[0] = fdbuf;
168 argv = nargv;
171 /* find %!, perhaps after PCL nonsense */
172 Bseek(b, 0, 0);
173 psoff = 0;
174 eol = 0;
175 for(i=0; i<16; i++){
176 psoff = Boffset(b);
177 if(!(p = Brdline(b, eol='\n')) && !(p = Brdline(b, eol='\r'))) {
178 fprint(2, "cannot find end of first line\n");
179 wexits("initps");
181 if(p[0]=='\x1B')
182 p++, psoff++;
183 if(p[0] == '%' && p[1] == '!')
184 break;
186 if(i == 16){
187 werrstr("not ps");
188 return nil;
191 /* page counting */
192 npage = 0;
193 mpage = 16;
194 page = emalloc(mpage*sizeof(*page));
195 memset(page, 0, mpage*sizeof(*page));
197 cantranslate = goodps;
198 incomments = 1;
199 Keepreading:
200 while(p = Brdline(b, eol)) {
201 if(p[0] == '%')
202 if(chatty) fprint(2, "ps %.*s\n", utfnlen(p, Blinelen(b)-1), p);
203 if(npage == mpage) {
204 mpage *= 2;
205 page = erealloc(page, mpage*sizeof(*page));
206 memset(&page[npage], 0, npage*sizeof(*page));
209 if(p[0] != '%' || p[1] != '%')
210 continue;
212 if(prefix(p, "%%BeginDocument")) {
213 nesting++;
214 continue;
216 if(nesting > 0 && prefix(p, "%%EndDocument")) {
217 nesting--;
218 continue;
220 if(nesting)
221 continue;
223 if(prefix(p, "%%EndComment")) {
224 incomments = 0;
225 continue;
227 if(reverse == -1 && prefix(p, "%%PageOrder")) {
228 /* glean whether we should reverse the viewing order */
229 p[Blinelen(b)-1] = 0;
230 if(strstr(p, "Ascend"))
231 reverse = 0;
232 else if(strstr(p, "Descend"))
233 reverse = 1;
234 else if(strstr(p, "Special"))
235 dumb = 1;
236 p[Blinelen(b)-1] = '\n';
237 continue;
238 } else if(prefix(p, "%%Trailer")) {
239 incomments = 1;
240 page[npage].offset = Boffset(b)-Blinelen(b);
241 trailer = 1;
242 continue;
243 } else if(incomments && prefix(p, "%%Orientation")) {
244 if(strstr(p, "Landscape"))
245 landscape = 1;
246 } else if(incomments && Dx(bbox)==0 && prefix(p, q="%%BoundingBox")) {
247 bbox = rdbbox(p+strlen(q)+1);
248 if(chatty)
249 /* can't use %R because haven't initdraw() */
250 fprint(2, "document bbox [%d %d %d %d]\n",
251 RECT(bbox));
252 continue;
255 /*
256 * If they use the initgraphics command, we can't play our translation tricks.
257 */
258 p[Blinelen(b)-1] = 0;
259 if((q=strstr(p, "initgraphics")) && ((r=strchr(p, '%'))==nil || r > q))
260 cantranslate = 0;
261 p[Blinelen(b)-1] = eol;
263 if(!prefix(p, "%%Page:"))
264 continue;
266 /*
267 * figure out of the %%Page: line contains a page number
268 * or some other page description to use in the menu bar.
270 * lines look like %%Page: x y or %%Page: x
271 * we prefer just x, and will generate our
272 * own if necessary.
273 */
274 p[Blinelen(b)-1] = 0;
275 if(chatty) fprint(2, "page %s\n", p);
276 r = p+7;
277 while(*r == ' ' || *r == '\t')
278 r++;
279 q = r;
280 while(*q && *q != ' ' && *q != '\t')
281 q++;
282 free(page[npage].name);
283 if(*r) {
284 if(*r == '"' && *q == '"')
285 r++, q--;
286 if(*q)
287 *q = 0;
288 page[npage].name = estrdup(r);
289 *q = 'x';
290 } else {
291 snprint(tmp, sizeof tmp, "p %ld", npage+1);
292 page[npage].name = estrdup(tmp);
295 /*
296 * store the offset info for later viewing
297 */
298 trailer = 0;
299 p[Blinelen(b)-1] = eol;
300 page[npage++].offset = Boffset(b)-Blinelen(b);
302 if(Blinelen(b) > 0){
303 fprint(2, "page: linelen %d\n", Blinelen(b));
304 Bseek(b, Blinelen(b), 1);
305 goto Keepreading;
308 if(Dx(bbox) == 0 || Dy(bbox) == 0)
309 bbox = Rect(0,0,612,792); /* 8½×11 */
310 /*
311 * if we didn't find any pages, assume the document
312 * is one big page
313 */
314 if(npage == 0) {
315 dumb = 1;
316 if(chatty) fprint(2, "don't know where pages are\n");
317 reverse = 0;
318 goodps = 0;
319 trailer = 0;
320 page[npage].name = "p 1";
321 page[npage++].offset = 0;
324 if(npage+2 > mpage) {
325 mpage += 2;
326 page = erealloc(page, mpage*sizeof(*page));
327 memset(&page[mpage-2], 0, 2*sizeof(*page));
330 if(!trailer)
331 page[npage].offset = Boffset(b);
333 Bseek(b, 0, 2); /* EOF */
334 page[npage+1].offset = Boffset(b);
336 d = emalloc(sizeof(*d));
337 ps = emalloc(sizeof(*ps));
338 ps->page = page;
339 ps->npage = npage;
340 ps->bbox = bbox;
341 ps->psoff = psoff;
343 d->extra = ps;
344 d->npage = ps->npage;
345 d->b = b;
346 d->drawpage = psdrawpage;
347 d->pagename = pspagename;
349 d->fwdonly = ps->clueless = dumb;
350 d->docname = argv[0];
352 if(spawngs(&ps->gs, "-dSAFER") < 0)
353 return nil;
355 if(!cantranslate)
356 bbox.min = ZP;
357 setdim(&ps->gs, bbox, ppi, landscape);
359 if(goodps){
360 /*
361 * We want to only send the page (i.e. not header and trailer) information
362 * for each page, so initialize the device by sending the header now.
363 */
364 pswritepage(d, ps->gs.gsfd, -1);
365 waitgs(&ps->gs);
368 if(dumb) {
369 fprint(ps->gs.gsfd, "(%s) run\n", argv[0]);
370 fprint(ps->gs.gsfd, "(/fd/3) (w) file dup (THIS IS NOT A PLAN9 BITMAP 01234567890123456789012345678901234567890123456789\\n) writestring flushfile\n");
373 ps->bbox = bbox;
375 return d;
378 static int
379 pswritepage(Document *d, int fd, int page)
381 Biobuf *b = d->b;
382 PSInfo *ps = d->extra;
383 int t, n, i;
384 long begin, end;
385 char buf[8192];
387 if(page == -1)
388 begin = ps->psoff;
389 else
390 begin = ps->page[page].offset;
392 end = ps->page[page+1].offset;
394 if(chatty) {
395 fprint(2, "writepage(%d)... from #%ld to #%ld...\n",
396 page, begin, end);
398 Bseek(b, begin, 0);
400 t = end-begin;
401 n = sizeof(buf);
402 if(n > t) n = t;
403 while(t > 0 && (i=Bread(b, buf, n)) > 0) {
404 if(write(fd, buf, i) != i)
405 return -1;
406 t -= i;
407 if(n > t)
408 n = t;
410 return end-begin;
413 static Image*
414 psdrawpage(Document *d, int page)
416 PSInfo *ps = d->extra;
417 Image *im;
419 if(ps->clueless)
420 return readimage(display, ps->gs.gsdfd, 0);
422 waitgs(&ps->gs);
424 if(goodps)
425 pswritepage(d, ps->gs.gsfd, page);
426 else {
427 pswritepage(d, ps->gs.gsfd, -1);
428 pswritepage(d, ps->gs.gsfd, page);
429 pswritepage(d, ps->gs.gsfd, d->npage);
431 /*
432 * If last line terminator is \r, gs will read ahead to check for \n
433 * so send one to avoid deadlock.
434 */
435 write(ps->gs.gsfd, "\n", 1);
436 im = readimage(display, ps->gs.gsdfd, 0);
437 if(im == nil) {
438 fprint(2, "fatal: readimage error %r\n");
439 wexits("readimage");
441 waitgs(&ps->gs);
443 return im;
446 static char*
447 pspagename(Document *d, int page)
449 PSInfo *ps = (PSInfo *) d->extra;
450 return ps->page[page].name;