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