ps.c (9072B)
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 <cursor.h> 11 #include <thread.h> 12 #include <bio.h> 13 #include <ctype.h> 14 #include "page.h" 15 16 static int pswritepage(Document *d, int fd, int page); 17 static Image* psdrawpage(Document *d, int page); 18 static char* pspagename(Document*, int); 19 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); 35 36 if(truetoboundingbox) 37 return r; 38 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 */ 46 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 } 65 66 #define RECT(X) X.min.x, X.min.y, X.max.x, X.max.y 67 68 int 69 prefix(char *x, char *y) 70 { 71 return strncmp(x, y, strlen(y)) == 0; 72 } 73 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; 83 84 page = ps->page; 85 onp = ps->npage; 86 np = (ps->npage+n-1)/n; 87 88 if(chatty) { 89 for(i=0; i<=onp+1; i++) 90 print("page %d: %d\n", i, page[i].offset); 91 } 92 93 for(i=0; i<np; i++) 94 page[i] = page[n*i]; 95 96 /* trailer */ 97 page[np] = page[onp]; 98 99 /* EOF */ 100 page[np+1] = page[onp+1]; 101 102 ps->npage = np; 103 104 if(chatty) { 105 for(i=0; i<=np+1; i++) 106 print("page %d: %d\n", i, page[i].offset); 107 } 108 109 } 110 111 Document* 112 initps(Biobuf *b, int argc, char **argv, uchar *buf, int nbuf) 113 { 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); 134 135 if(argc > 1) { 136 fprint(2, "can only view one ps file at a time\n"); 137 return nil; 138 } 139 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"); 148 } 149 nargv[0] = fdbuf; 150 argv = nargv; 151 } 152 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"); 162 } 163 if(p[0]=='\x1B') 164 p++, psoff++; 165 if(p[0] == '%' && p[1] == '!') 166 break; 167 } 168 if(i == 16){ 169 werrstr("not ps"); 170 return nil; 171 } 172 173 /* page counting */ 174 npage = 0; 175 mpage = 16; 176 page = emalloc(mpage*sizeof(*page)); 177 memset(page, 0, mpage*sizeof(*page)); 178 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)); 189 } 190 191 if(p[0] != '%' || p[1] != '%') 192 continue; 193 194 if(prefix(p, "%%BeginDocument")) { 195 nesting++; 196 continue; 197 } 198 if(nesting > 0 && prefix(p, "%%EndDocument")) { 199 nesting--; 200 continue; 201 } 202 if(nesting) 203 continue; 204 205 if(prefix(p, "%%EndComment")) { 206 incomments = 0; 207 continue; 208 } 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; 235 } 236 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; 244 245 if(!prefix(p, "%%Page:")) 246 continue; 247 248 /* 249 * figure out of the %%Page: line contains a page number 250 * or some other page description to use in the menu bar. 251 * 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); 275 } 276 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); 283 } 284 if(Blinelen(b) > 0){ 285 fprint(2, "page: linelen %d\n", Blinelen(b)); 286 Bseek(b, Blinelen(b), 1); 287 goto Keepreading; 288 } 289 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; 304 } 305 306 if(npage+2 > mpage) { 307 mpage += 2; 308 page = erealloc(page, mpage*sizeof(*page)); 309 memset(&page[mpage-2], 0, 2*sizeof(*page)); 310 } 311 312 if(!trailer) 313 page[npage].offset = Boffset(b); 314 315 Bseek(b, 0, 2); /* EOF */ 316 page[npage+1].offset = Boffset(b); 317 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; 324 325 d->extra = ps; 326 d->npage = ps->npage; 327 d->b = b; 328 d->drawpage = psdrawpage; 329 d->pagename = pspagename; 330 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; 338 339 if(spawngs(&ps->gs, "-dSAFER") < 0) 340 return nil; 341 342 if(!cantranslate) 343 bbox.min = ZP; 344 setdim(&ps->gs, bbox, ppi, landscape); 345 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); 353 } 354 355 if(dumb) { 356 fprint(ps->gs.gsfd, "(%s) run PAGEFLUSH\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"); 358 close(ps->gs.gsfd); 359 } 360 361 ps->bbox = bbox; 362 363 return d; 364 } 365 366 static int 367 pswritepage(Document *d, int fd, int page) 368 { 369 Biobuf *b = d->b; 370 PSInfo *ps = d->extra; 371 int t, n, i; 372 long begin, end; 373 char buf[8192]; 374 375 if(page == -1) 376 begin = ps->psoff; 377 else 378 begin = ps->page[page].offset; 379 380 end = ps->page[page+1].offset; 381 382 if(chatty) { 383 fprint(2, "writepage(%d)... from #%ld to #%ld...\n", 384 page, begin, end); 385 } 386 Bseek(b, begin, 0); 387 388 t = end-begin; 389 n = sizeof(buf); 390 if(n > t) n = t; 391 while(t > 0 && (i=Bread(b, buf, n)) > 0) { 392 if(write(fd, buf, i) != i) 393 return -1; 394 t -= i; 395 if(n > t) 396 n = t; 397 } 398 return end-begin; 399 } 400 401 static Image* 402 psdrawpage(Document *d, int page) 403 { 404 PSInfo *ps = d->extra; 405 Image *im; 406 407 if(ps->clueless) 408 return convert(&ps->gs.g); 409 410 waitgs(&ps->gs); 411 412 if(goodps) 413 pswritepage(d, ps->gs.gsfd, page); 414 else { 415 pswritepage(d, ps->gs.gsfd, -1); 416 pswritepage(d, ps->gs.gsfd, page); 417 pswritepage(d, ps->gs.gsfd, d->npage); 418 } 419 /* 420 * If last line terminator is \r, gs will read ahead to check for \n 421 * so send one to avoid deadlock. 422 */ 423 write(ps->gs.gsfd, "\n", 1); 424 fprint(ps->gs.gsfd, "\nPAGEFLUSH\n"); 425 im = convert(&ps->gs.g); 426 if(im == nil) { 427 fprint(2, "fatal: readimage error %r\n"); 428 wexits("readimage"); 429 } 430 waitgs(&ps->gs); 431 432 return im; 433 } 434 435 static char* 436 pspagename(Document *d, int page) 437 { 438 PSInfo *ps = (PSInfo *) d->extra; 439 return ps->page[page].name; 440 }