1 /* 2 * ps.c 3 * 4 * provide postscript file reading support for page 5 */ 6 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" 14 15 typedef struct PSInfo PSInfo; 16 typedef struct Page Page; 17 18 struct Page { 19 char *name; 20 int offset; /* offset of page beginning within file */ 21 }; 22 23 struct PSInfo { 24 GSInfo; 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 }; 32 33 static int pswritepage(Document *d, int fd, int page); 34 static Image* psdrawpage(Document *d, int page); 35 static char* pspagename(Document*, int); 36 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); 52 53 if(truetoboundingbox) 54 return r; 55 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 */ 63 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 } 82 83 #define RECT(X) X.min.x, X.min.y, X.max.x, X.max.y 84 85 int 86 prefix(char *x, char *y) 87 { 88 return strncmp(x, y, strlen(y)) == 0; 89 } 90 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; 100 101 page = ps->page; 102 onp = ps->npage; 103 np = (ps->npage+n-1)/n; 104 105 if(chatty) { 106 for(i=0; i<=onp+1; i++) 107 print("page %d: %d\n", i, page[i].offset); 108 } 109 110 for(i=0; i<np; i++) 111 page[i] = page[n*i]; 112 113 /* trailer */ 114 page[np] = page[onp]; 115 116 /* EOF */ 117 page[np+1] = page[onp+1]; 118 119 ps->npage = np; 120 121 if(chatty) { 122 for(i=0; i<=np+1; i++) 123 print("page %d: %d\n", i, page[i].offset); 124 } 125 126 } 127 128 Document* 129 initps(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf) 130 { 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); 151 152 if(argc > 1) { 153 fprint(2, "can only view one ps file at a time\n"); 154 return nil; 155 } 156 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"); 165 } 166 nargv[0] = fdbuf; 167 argv = nargv; 168 } 169 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"); 179 } 180 if(p[0]=='\x1B') 181 p++, psoff++; 182 if(p[0] == '%' && p[1] == '!') 183 break; 184 } 185 if(i == 16){ 186 werrstr("not ps"); 187 return nil; 188 } 189 190 /* page counting */ 191 npage = 0; 192 mpage = 16; 193 page = emalloc(mpage*sizeof(*page)); 194 memset(page, 0, mpage*sizeof(*page)); 195 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)); 206 } 207 208 if(p[0] != '%' || p[1] != '%') 209 continue; 210 211 if(prefix(p, "%%BeginDocument")) { 212 nesting++; 213 continue; 214 } 215 if(nesting > 0 && prefix(p, "%%EndDocument")) { 216 nesting--; 217 continue; 218 } 219 if(nesting) 220 continue; 221 222 if(prefix(p, "%%EndComment")) { 223 incomments = 0; 224 continue; 225 } 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; 252 } 253 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; 261 262 if(!prefix(p, "%%Page:")) 263 continue; 264 265 /* 266 * figure out of the %%Page: line contains a page number 267 * or some other page description to use in the menu bar. 268 * 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); 292 } 293 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); 300 } 301 if(Blinelen(b) > 0){ 302 fprint(2, "page: linelen %d\n", Blinelen(b)); 303 Bseek(b, Blinelen(b), 1); 304 goto Keepreading; 305 } 306 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; 321 } 322 323 if(npage+2 > mpage) { 324 mpage += 2; 325 page = erealloc(page, mpage*sizeof(*page)); 326 memset(&page[mpage-2], 0, 2*sizeof(*page)); 327 } 328 329 if(!trailer) 330 page[npage].offset = Boffset(b); 331 332 Bseek(b, 0, 2); /* EOF */ 333 page[npage+1].offset = Boffset(b); 334 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; 341 342 d->extra = ps; 343 d->npage = ps->npage; 344 d->b = b; 345 d->drawpage = psdrawpage; 346 d->pagename = pspagename; 347 348 d->fwdonly = ps->clueless = dumb; 349 d->docname = argv[0]; 350 351 if(spawngs(ps, "-dSAFER") < 0) 352 return nil; 353 354 if(!cantranslate) 355 bbox.min = ZP; 356 setdim(ps, bbox, ppi, landscape); 357 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->gsfd, -1); 364 waitgs(ps); 365 } 366 367 if(dumb) { 368 fprint(ps->gsfd, "(%s) run\n", argv[0]); 369 fprint(ps->gsfd, "(/fd/3) (w) file dup (THIS IS NOT A PLAN9 BITMAP 01234567890123456789012345678901234567890123456789\\n) writestring flushfile\n"); 370 } 371 372 ps->bbox = bbox; 373 374 return d; 375 } 376 377 static int 378 pswritepage(Document *d, int fd, int page) 379 { 380 Biobuf *b = d->b; 381 PSInfo *ps = d->extra; 382 int t, n, i; 383 long begin, end; 384 char buf[8192]; 385 386 if(page == -1) 387 begin = ps->psoff; 388 else 389 begin = ps->page[page].offset; 390 391 end = ps->page[page+1].offset; 392 393 if(chatty) { 394 fprint(2, "writepage(%d)... from #%ld to #%ld...\n", 395 page, begin, end); 396 } 397 Bseek(b, begin, 0); 398 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; 408 } 409 return end-begin; 410 } 411 412 static Image* 413 psdrawpage(Document *d, int page) 414 { 415 PSInfo *ps = d->extra; 416 Image *im; 417 418 if(ps->clueless) 419 return readimage(display, ps->gsdfd, 0); 420 421 waitgs(ps); 422 423 if(goodps) 424 pswritepage(d, ps->gsfd, page); 425 else { 426 pswritepage(d, ps->gsfd, -1); 427 pswritepage(d, ps->gsfd, page); 428 pswritepage(d, ps->gsfd, d->npage); 429 } 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->gsfd, "\n", 1); 435 im = readimage(display, ps->gsdfd, 0); 436 if(im == nil) { 437 fprint(2, "fatal: readimage error %r\n"); 438 wexits("readimage"); 439 } 440 waitgs(ps); 441 442 return im; 443 } 444 445 static char* 446 pspagename(Document *d, int page) 447 { 448 PSInfo *ps = (PSInfo *) d->extra; 449 return ps->page[page].name; 450 } 451