| /* | 
 |  * gs interface for page. | 
 |  * ps.c and pdf.c both use these routines. | 
 |  * a caveat: if you run more than one gs, only the last  | 
 |  * one gets killed by killgs  | 
 |  */ | 
 | #include <u.h> | 
 | #include <libc.h> | 
 | #include <draw.h> | 
 | #include <event.h> | 
 | #include <bio.h> | 
 | #include "page.h" | 
 |  | 
 | static int gspid;	/* globals for atexit */ | 
 | static int gsfd; | 
 | static void	killgs(void); | 
 |  | 
 | static void | 
 | killgs(void) | 
 | { | 
 | 	char tmpfile[100]; | 
 |  | 
 | 	close(gsfd); | 
 | 	postnote(PNGROUP, getpid(), "die"); | 
 |  | 
 | 	/* | 
 | 	 * from ghostscript's use.txt: | 
 | 	 * ``Ghostscript currently doesn't do a very good job of deleting temporary | 
 | 	 * files when it exits; you may have to delete them manually from time to | 
 | 	 * time.'' | 
 | 	 */ | 
 | 	sprint(tmpfile, "/tmp/gs_%.5da", (gspid+300000)%100000); | 
 | 	if(chatty) fprint(2, "remove %s...\n", tmpfile); | 
 | 	remove(tmpfile); | 
 | 	sleep(100); | 
 | 	postnote(PNPROC, gspid, "die yankee pig dog"); | 
 | } | 
 |  | 
 | int | 
 | spawnwriter(GSInfo *g, Biobuf *b) | 
 | { | 
 | 	char buf[4096]; | 
 | 	int n; | 
 | 	int fd; | 
 |  | 
 | 	switch(fork()){ | 
 | 	case -1:	return -1; | 
 | 	case 0:	break; | 
 | 	default:	return 0; | 
 | 	} | 
 |  | 
 | 	Bseek(b, 0, 0); | 
 | 	fd = g->gsfd; | 
 | 	while((n = Bread(b, buf, sizeof buf)) > 0) | 
 | 		write(fd, buf, n); | 
 | 	fprint(fd, "(/fd/3) (w) file dup (THIS IS NOT AN INFERNO BITMAP\\n) writestring flushfile\n"); | 
 | 	_exits(0); | 
 | 	return -1; | 
 | } | 
 |  | 
 | int | 
 | spawnreader(int fd) | 
 | { | 
 | 	int n, pfd[2]; | 
 | 	char buf[1024]; | 
 |  | 
 | 	if(pipe(pfd)<0) | 
 | 		return -1; | 
 | 	switch(fork()){ | 
 | 	case -1: | 
 | 		return -1; | 
 | 	case 0: | 
 | 		break; | 
 | 	default: | 
 | 		close(pfd[0]); | 
 | 		return pfd[1]; | 
 | 	} | 
 |  | 
 | 	close(pfd[1]); | 
 | 	switch(fork()){ | 
 | 	case -1: | 
 | 		wexits("fork failed"); | 
 | 	case 0: | 
 | 		while((n=read(fd, buf, sizeof buf)) > 0) { | 
 | 			write(1, buf, n); | 
 | 			write(pfd[0], buf, n); | 
 | 		} | 
 | 		break; | 
 | 	default: | 
 | 		while((n=read(pfd[0], buf, sizeof buf)) > 0) { | 
 | 			write(1, buf, n); | 
 | 			write(fd, buf, n); | 
 | 		} | 
 | 		break; | 
 | 	} | 
 | 	postnote(PNGROUP, getpid(), "i'm die-ing"); | 
 | 	_exits(0); | 
 | 	return -1; | 
 | } | 
 |  | 
 | void | 
 | spawnmonitor(int fd) | 
 | { | 
 | 	char buf[4096]; | 
 | 	char *xbuf; | 
 | 	int n; | 
 | 	int out; | 
 | 	int first; | 
 |  | 
 | 	switch(rfork(RFFDG|RFNOTEG|RFPROC)){ | 
 | 	case -1: | 
 | 	default: | 
 | 		return; | 
 |  | 
 | 	case 0: | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	out = open("/dev/cons", OWRITE); | 
 | 	if(out < 0) | 
 | 		out = 2; | 
 |  | 
 | 	xbuf = buf;	/* for ease of acid */ | 
 | 	first = 1; | 
 | 	while((n = read(fd, xbuf, sizeof buf)) > 0){ | 
 | 		if(first){ | 
 | 			first = 0; | 
 | 			fprint(2, "Ghostscript Error:\n"); | 
 | 		} | 
 | 		write(out, xbuf, n); | 
 | 		alarm(500); | 
 | 	} | 
 | 	_exits(0); | 
 | } | 
 |  | 
 | int  | 
 | spawngs(GSInfo *g) | 
 | { | 
 | 	char *args[16]; | 
 | 	char tb[32], gb[32]; | 
 | 	int i, nargs; | 
 | 	int devnull; | 
 | 	int stdinout[2]; | 
 | 	int dataout[2]; | 
 | 	int errout[2]; | 
 |  | 
 | 	/* | 
 | 	 * spawn gs | 
 | 	 * | 
 |  	 * gs's standard input is fed from stdinout. | 
 | 	 * gs output written to fd-2 (i.e. output we generate intentionally) is fed to stdinout. | 
 | 	 * gs output written to fd 1 (i.e. ouptut gs generates on error) is fed to errout. | 
 | 	 * gs data output is written to fd 3, which is dataout. | 
 | 	 */ | 
 | 	if(pipe(stdinout) < 0 || pipe(dataout)<0 || pipe(errout)<0) | 
 | 		return -1; | 
 |  | 
 | 	nargs = 0; | 
 | 	args[nargs++] = "gs"; | 
 | 	args[nargs++] = "-dNOPAUSE"; | 
 | 	args[nargs++] = "-dSAFER"; | 
 | 	args[nargs++] = "-sDEVICE=plan9"; | 
 | 	args[nargs++] = "-sOutputFile=/fd/3"; | 
 | 	args[nargs++] = "-dQUIET"; | 
 | 	args[nargs++] = "-r100"; | 
 | 	sprint(tb, "-dTextAlphaBits=%d", textbits); | 
 | 	sprint(gb, "-dGraphicsAlphaBits=%d", gfxbits); | 
 | 	if(textbits) | 
 | 		args[nargs++] = tb; | 
 | 	if(gfxbits) | 
 | 		args[nargs++] = gb; | 
 | 	args[nargs++] = "-"; | 
 | 	args[nargs] = nil; | 
 |  | 
 | 	gspid = fork(); | 
 | 	if(gspid == 0) { | 
 | 		close(stdinout[1]); | 
 | 		close(dataout[1]); | 
 | 		close(errout[1]); | 
 |  | 
 | 		/* | 
 | 		 * Horrible problem: we want to dup fd's 0-4 below, | 
 | 		 * but some of the source fd's might have those small numbers. | 
 | 		 * So we need to reallocate those.  In order to not step on | 
 | 		 * anything else, we'll dup the fd's to higher ones using | 
 | 		 * dup(x, -1), but we need to use up the lower ones first. | 
 | 		 */ | 
 | 		while((devnull = open("/dev/null", ORDWR)) < 5) | 
 | 			; | 
 |  | 
 | 		stdinout[0] = dup(stdinout[0], -1); | 
 | 		errout[0] = dup(errout[0], -1); | 
 | 		dataout[0] = dup(dataout[0], -1); | 
 |  | 
 | 		dup(stdinout[0], 0); | 
 | 		dup(errout[0], 1); | 
 | 		dup(devnull, 2);	/* never anything useful */ | 
 | 		dup(dataout[0], 3); | 
 | 		dup(stdinout[0], 4); | 
 | 		for(i=5; i<20; i++) | 
 | 			close(i); | 
 | 		exec("/bin/gs", args); | 
 | 		wexits("exec"); | 
 | 	} | 
 | 	close(stdinout[0]); | 
 | 	close(errout[0]); | 
 | 	close(dataout[0]); | 
 | 	atexit(killgs); | 
 |  | 
 | 	if(teegs) | 
 | 		stdinout[1] = spawnreader(stdinout[1]); | 
 |  | 
 | 	gsfd = g->gsfd = stdinout[1]; | 
 | 	g->gsdfd = dataout[1]; | 
 | 	g->gspid = gspid; | 
 |  | 
 | 	spawnmonitor(errout[1]); | 
 | 	Binit(&g->gsrd, g->gsfd, OREAD); | 
 |  | 
 | 	gscmd(g, "/PAGEOUT (/fd/4) (w) file def\n"); | 
 | 	gscmd(g, "/PAGE== { PAGEOUT exch write==only PAGEOUT (\\n) writestring PAGEOUT flushfile } def\n"); | 
 | 	waitgs(g); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | int | 
 | gscmd(GSInfo *gs, char *fmt, ...) | 
 | { | 
 | 	char buf[1024]; | 
 | 	int n; | 
 |  | 
 | 	va_list v; | 
 | 	va_start(v, fmt); | 
 | 	n = vseprint(buf, buf+sizeof buf, fmt, v) - buf; | 
 | 	if(n <= 0) | 
 | 		return n; | 
 |  | 
 | 	if(chatty) { | 
 | 		fprint(2, "cmd: "); | 
 | 		write(2, buf, n); | 
 | 	} | 
 |  | 
 | 	if(write(gs->gsfd, buf, n) != 0) | 
 | 		return -1; | 
 |  | 
 | 	return n; | 
 | } | 
 |  | 
 | /* | 
 |  * set the dimensions of the bitmap we expect to get back from GS. | 
 |  */ | 
 | void | 
 | setdim(GSInfo *gs, Rectangle bbox, int ppi, int landscape) | 
 | { | 
 | 	Rectangle pbox; | 
 |  | 
 | 	if(chatty) | 
 | 		fprint(2, "setdim: bbox=%R\n", bbox); | 
 |  | 
 | 	if(ppi) | 
 | 		gs->ppi = ppi; | 
 |  | 
 | 	gscmd(gs, "mark\n"); | 
 | 	if(ppi) | 
 | 		gscmd(gs, "/HWResolution [%d %d]\n", ppi, ppi); | 
 |  | 
 | 	if(!Dx(bbox)) | 
 | 		bbox = Rect(0, 0, 612, 792);	/* 8½×11 */ | 
 |  | 
 | 	switch(landscape){ | 
 | 	case 0: | 
 | 		pbox = bbox; | 
 | 		break; | 
 | 	case 1: | 
 | 		pbox = Rect(bbox.min.y, bbox.min.x, bbox.max.y, bbox.max.x); | 
 | 		break; | 
 | 	} | 
 | 	gscmd(gs, "/PageSize [%d %d]\n", Dx(pbox), Dy(pbox)); | 
 | 	gscmd(gs, "/Margins [%d %d]\n", -pbox.min.x, -pbox.min.y); | 
 | 	gscmd(gs, "currentdevice putdeviceprops pop\n"); | 
 | 	gscmd(gs, "/#copies 1 store\n"); | 
 |  | 
 | 	if(!eqpt(bbox.min, ZP)) | 
 | 		gscmd(gs, "%d %d translate\n", -bbox.min.x, -bbox.min.y); | 
 |  | 
 | 	switch(landscape){ | 
 | 	case 0: | 
 | 		break; | 
 | 	case 1: | 
 | 		gscmd(gs, "%d 0 translate\n", Dy(bbox)); | 
 | 		gscmd(gs, "90 rotate\n"); | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	waitgs(gs); | 
 | } | 
 |  | 
 | void | 
 | waitgs(GSInfo *gs) | 
 | { | 
 | 	/* we figure out that gs is done by telling it to | 
 | 	 * print something and waiting until it does. | 
 | 	 */ | 
 | 	char *p; | 
 | 	Biobuf *b = &gs->gsrd; | 
 | 	uchar buf[1024]; | 
 | 	int n; | 
 |  | 
 | //	gscmd(gs, "(\\n**bstack\\n) print flush\n"); | 
 | //	gscmd(gs, "stack flush\n"); | 
 | //	gscmd(gs, "(**estack\\n) print flush\n"); | 
 | 	gscmd(gs, "(\\n//GO.SYSIN DD\\n) PAGE==\n"); | 
 |  | 
 | 	alarm(300*1000); | 
 | 	for(;;) { | 
 | 		p = Brdline(b, '\n'); | 
 | 		if(p == nil) { | 
 | 			n = Bbuffered(b); | 
 | 			if(n <= 0) | 
 | 				break; | 
 | 			if(n > sizeof buf) | 
 | 				n = sizeof buf; | 
 | 			Bread(b, buf, n); | 
 | 			continue; | 
 | 		} | 
 | 		p[Blinelen(b)-1] = 0; | 
 | 		if(chatty) fprint(2, "p: "); | 
 | 		if(chatty) write(2, p, Blinelen(b)-1); | 
 | 		if(chatty) fprint(2, "\n"); | 
 | 		if(strstr(p, "Error:")) { | 
 | 			alarm(0); | 
 | 			fprint(2, "ghostscript error: %s\n", p); | 
 | 			wexits("gs error"); | 
 | 		} | 
 |  | 
 | 		if(strstr(p, "//GO.SYSIN DD")) { | 
 | 			break; | 
 | 		} | 
 | 	} | 
 | 	alarm(0); | 
 | } |