/* * Copyright (c) 1994 Paul Vojta. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * ||| to do: * *interpreter resource * -safer argument * palette resource and arguments */ #include "oconfig.h" #ifdef PS_GS /* whole file */ #include "kpathsea/c-pathmx.h" #include #include /* for timeval */ #include #ifdef NeXT typedef int pid_t; #endif /* if POSIX O_NONBLOCK is not available, use O_NDELAY */ #if !defined(O_NONBLOCK) && defined(O_NDELAY) #define O_NONBLOCK O_NDELAY #endif /* Condition for retrying a write */ #include #ifdef EWOULDBLOCK #ifdef EAGAIN #define AGAIN_CONDITION (errno == EWOULDBLOCK || errno == EAGAIN) #else /* EAGAIN */ #define AGAIN_CONDITION (errno == EWOULDBLOCK) #endif /* EAGAIN */ #else /* EWOULDBLOCK */ #ifdef EAGAIN #define AGAIN_CONDITION (errno == EAGAIN) #endif /* EAGAIN */ #endif /* EWOULDBLOCK */ #ifdef STREAMSCONN #include #else #ifdef _AIX #include /* for fd_set stuff on RS/6000 */ #else #ifdef HAVE_SYS_BSDTYPES_H #include /* for fd_set on ISC 4.0 */ #endif /* not */ #endif /* not _AIX */ #endif /* not STREAMSCONN */ #if HAS_SIGIO #include #include #ifndef FASYNC #undef HAS_SIGIO #define HAS_SIGIO 0 #endif #endif #ifdef VFORK #if VFORK == include #include #endif #else #define vfork fork #endif extern _Xconst char psheader[]; extern int psheaderlen; #define postscript _postscript #define fore_Pixel _fore_Pixel #define back_Pixel _back_Pixel extern void qt_processEvents(); /* global procedures (besides initGS) */ static void toggle_gs ARGS((void)); static void destroy_gs ARGS((void)); static void interrupt_gs ARGS((void)); static void endpage_gs ARGS((void)); static void drawbegin_gs ARGS((int, int, char *)); static void drawraw_gs ARGS((char *)); static void drawfile_gs ARGS((char *)); static void drawend_gs ARGS((char *)); static struct psprocs gs_procs = { /* toggle */ toggle_gs, /* destroy */ destroy_gs, /* interrupt */ interrupt_gs, /* endpage */ endpage_gs, /* drawbegin */ drawbegin_gs, /* drawraw */ drawraw_gs, /* drawfile */ drawfile_gs, /* drawend */ drawend_gs}; static int std_in[2]; static int std_out[2]; #define GS_in (std_in[1]) #define GS_out (std_out[0]) static char *argv[] = {"gs", "-sDEVICE=x11alpha", "-dNOPAUSE", "-dSAFER", "-q", "-", NULL}; static pid_t GS_pid; static unsigned int GS_page_w; /* how big our current page is */ static unsigned int GS_page_h; static int GS_mag; /* magnification currently in use */ static int GS_shrink; /* shrink factor currently in use */ static Boolean GS_active; /* if we've started a page yet */ static int GS_pending; /* number of ack's we're expecting */ static Boolean GS_sending; /* if we're in the middle of send() */ static Boolean GS_pending_int; /* if interrupt rec'd while in send() */ static Atom gs_atom; static Atom gs_colors_atom; #define XtPageOrientationPortrait 0 /* * Our replacement for setenv(), which is not available on all systems. */ #ifndef HAVE_SETENV /* define this if you're a performance freak and if your system has setenv. */ #define setenv(var, str, repl) _setenv(var, str) /* repl always True */ extern char **environ; static void _setenv(var, str) _Xconst char *var; _Xconst char *str; { int len1; int len2; char *newvar; char **linep; static Boolean malloced = False; len1 = strlen(var); len2 = strlen(str) + 1; newvar = xmalloc((unsigned int) len1 + len2 + 1, "_setenv"); (void) bcopy(var, newvar, len1); newvar[len1++] = '='; (void) bcopy(str, newvar + len1, len2); for (linep = environ; *linep != NULL; ++linep) if (memcmp(*linep, newvar, len1) == 0) { *linep = newvar; return; } len1 = linep - environ; if (malloced) { environ = (char **) realloc((char *) environ, (unsigned int) (len1 + 2) * sizeof(char *)); if (environ == NULL) oops("! Cannot allocate %d bytes for string list in _setenv.\n", (len1 + 2) * sizeof(char *)); } else { linep = (char **) xmalloc((unsigned int)(len1 + 2) * sizeof(char *), "string list in _setenv"); (void) bcopy((char *) environ, (char *) linep, len1 * sizeof(char *)); environ = linep; malloced = True; } environ[len1] = newvar; environ[len1 + 1] = NULL; } #endif /* HAVE_SETENV */ /* * ghostscript I/O code. This should send PS code to ghostscript, * receive acknowledgements, and receive X events in the meantime. * It also checks for SIGPIPE errors. */ #ifndef STREAMSCONN static int numfds; static fd_set readfds; static fd_set writefds; #define XDVI_ISSET(a, b, c) FD_ISSET(a, b) #else /* STREAMSCONN */ struct pollfd fds[3] = {{0, POLLOUT, 0}, {0, POLLIN, 0}, {0, POLLIN, 0}}; #define XDVI_ISSET(a, b, c) (fds[c].revents) #endif /* STREAMSCONN */ #define LINELEN 81 static char line[LINELEN + 1]; static char *linepos = line; static char ackstr[] = "\347\310\376"; static void read_from_gs() { int bytes; char *line_end; char *p; bytes = read(GS_out, linepos, line + LINELEN - linepos); if (bytes < 0) return; line_end = linepos + bytes; /* Check for ack strings */ for (p = line; p < line_end - 2; ++p) { p = memchr(p, '\347', line_end - p - 2); if (p == NULL) break; if (memcmp(p, ackstr, 3) == 0) { --GS_pending; if (debug & DBG_PS) Printf("Got GS ack; %d pending.\n", GS_pending); if (p > line) { *p = '\0'; Printf("gs: %s\n", line); } p += 3; (void) bcopy(p, line, line_end - p); line_end -= p - line; linepos = p = line; --p; } } for (;;) { p = memchr(linepos, '\n', line_end - linepos); if (p == NULL) break; *p = '\0'; Printf("gs: %s\n", line); ++p; (void) bcopy(p, line, line_end - p); line_end -= p - line; linepos = line; } linepos = line_end; /* * Normally we'd hold text until a newline character, but the buffer * is full. So we flush it, being careful not to cut up an ack string. */ if (linepos >= line + LINELEN) { p = line + LINELEN; if ((*--p != '\347' && *--p != '\347' && *--p != '\347') || memcmp(p, ackstr, line + LINELEN - p) != 0) p = line + LINELEN; *p = '\0'; Printf("gs: %s\n", line); *p = '\347'; linepos = line; while (p < line + LINELEN) *linepos++ = *p++; } } /* * For handling of SIGPIPE signals from send() */ static Boolean sigpipe_error = False; /* ARGSUSED */ static void gs_sigpipe_handler(sig, code, scp, addr) int sig; int code; struct sigcontext *scp; char *addr; { sigpipe_error = True; } #ifdef _POSIX_SOURCE static struct sigaction sigpipe_handler_struct; /* initialized to {gs_sigpipe_handler, (sigset_t) 0, 0} in initGS */ #endif /* * This actually sends the bytes to ghostscript. */ static void send(cp, len) _Xconst char *cp; int len; { int bytes; #ifdef _POSIX_SOURCE struct sigaction orig; #else void (*orig)(); #endif #ifdef STREAMSCONN int retval; #endif if (GS_pid < 0) return; #ifdef _POSIX_SOURCE (void) sigaction(SIGPIPE, &sigpipe_handler_struct, &orig); #else orig = signal(SIGPIPE, gs_sigpipe_handler); #endif sigpipe_error = False; GS_sending = True; #if HAS_SIGIO (void) fcntl(ConnectionNumber(DISP), F_SETFL, fcntl(ConnectionNumber(DISP), F_GETFL, 0) & ~FASYNC); #endif #ifndef STREAMSCONN FD_ZERO(&readfds); FD_ZERO(&writefds); #endif for (;;) { #ifndef STREAMSCONN FD_SET(ConnectionNumber(DISP), &readfds); FD_SET(GS_in, &writefds); FD_SET(GS_out, &readfds); if (select(numfds, &readfds, &writefds, (fd_set *) NULL, (struct timeval *) NULL) < 0 && errno != EINTR) { perror("select (gs_send)"); break; } #else /* STREAMSCONN */ for (;;) { retval = poll(fds, XtNumber(fds), -1); if (retval >= 0 || errno != EAGAIN) break; } if (retval < 0) { perror("poll (gs_send)"); break; } #endif /* STREAMSCONN */ if (XDVI_ISSET(GS_out, &readfds, 1)) read_from_gs(); if (XDVI_ISSET(GS_in, &writefds, 0)) { bytes = write(GS_in, cp, len); if (bytes == -1) { if (!AGAIN_CONDITION) perror("gs_send"); } else { cp += bytes; len -= bytes; if (len == 0) break; } if (sigpipe_error) break; } if (XDVI_ISSET(ConnectionNumber(DISP), &readfds, 2)) { #ifndef KDVI allow_can = False; read_events(False); allow_can = True; if (GS_pid < 0) break; /* if timeout occurred */ #else qt_processEvents(); #endif /* KDVI */ } } #if HAS_SIGIO (void) fcntl(ConnectionNumber(DISP), F_SETFL, fcntl(ConnectionNumber(DISP), F_GETFL, 0) | FASYNC); #endif /* put back generic handler for SIGPIPE */ #ifdef _POSIX_SOURCE (void) sigaction(SIGPIPE, &orig, (struct sigaction *) NULL); #else (void) signal(SIGPIPE, orig); #endif GS_sending = False; if (sigpipe_error) { Fputs("ghostscript died unexpectedly.\n", stderr); destroy_gs(); draw_bbox(); } if (GS_pending_int) { GS_pending_int = False; interrupt_gs(); } } /* * Wait for acknowledgement from gs. */ static void waitack(waittime) int waittime; { struct timeval tv; struct timeval tv2; #ifndef STREAMSCONN struct timeval *timeout = (struct timeval *) NULL; #else int timeout = -1; int retval; #endif #if HAS_SIGIO int oldflags; #endif if (GS_pending == 0) return; #if HAS_SIGIO oldflags = fcntl(ConnectionNumber(DISP), F_GETFL, 0); (void) fcntl(ConnectionNumber(DISP), F_SETFL, oldflags & ~FASYNC); #endif if (waittime != 0) { (void) gettimeofday(&tv, (struct timezone *) NULL); tv.tv_sec += waittime; #ifndef STREAMSCONN timeout = &tv2; #endif } #ifndef STREAMSCONN FD_ZERO(&readfds); #endif while (GS_pending > 0) { if (waittime != 0) { (void) gettimeofday(&tv2, (struct timezone *) NULL); #ifndef STREAMSCONN if (timercmp(&tv2, &tv, >=)) { destroy_gs(); break; } tv2.tv_sec = tv.tv_sec - tv2.tv_sec; tv2.tv_usec = tv.tv_usec + 1000000 - tv2.tv_usec; if (tv2.tv_usec >= 1000000) tv2.tv_usec -= 1000000; else --tv2.tv_sec; #else timeout = 1000 * (int) (tv.tv_sec - tv2.tv_sec) + ((long) tv.tv_usec - (long) tv2.tv_usec) / 1000; if (timeout <= 0) { destroy_gs(); break; } #endif } #ifndef STREAMSCONN FD_SET(ConnectionNumber(DISP), &readfds); FD_SET(GS_out, &readfds); if (select(numfds, &readfds, (fd_set *) NULL, (fd_set *) NULL, timeout) < 0 && errno != EINTR) { perror("select (gs_waitack)"); break; } #else /* STREAMSCONN */ for (;;) { retval = poll(fds + 1, XtNumber(fds) - 1, timeout); if (retval >= 0 || errno != EAGAIN) break; } if (retval < 0) { perror("poll (gs_waitack)"); break; } #endif /* STREAMSCONN */ if (XDVI_ISSET(GS_out, &readfds, 1)) read_from_gs(); if (XDVI_ISSET(ConnectionNumber(DISP), &readfds, 2)) { #ifndef KDVI allow_can = False; read_events(False); allow_can = True; #else qt_processEvents(); #endif /* KDVI */ } } #if HAS_SIGIO (void) fcntl(ConnectionNumber(DISP), F_SETFL, oldflags); #endif /* If you bail out here, change the call in interrupt_gs(). */ } /* * Fork a process to run ghostscript. This is done using the * x11 device (which needs to be compiled in). Normally the x11 * device uses ClientMessage events to communicate with the calling * program, but we don't do this. The reason for using the ClientMessage * events is that otherwise ghostview doesn't know when a non-conforming * postscript program calls showpage. That doesn't affect us here, * since in fact we disable showpage. */ Boolean initGS() { char buf[100]; /* * This string reads chunks (delimited by %%xdvimark). * The first character of a chunk tells whether a given chunk * is to be done within save/restore or not. * The `H' at the end tells it that the first group is a * header; i.e., no save/restore. * `execute' is unique to ghostscript. */ static _Xconst char str1[] = "\ /xdvi$run {$error /newerror false put {currentfile cvx execute} stopped pop} def \ /xdvi$ack (\347\310\376) def \ /xdvi$dslen countdictstack def \ {currentfile read pop 72 eq \ {xdvi$run} \ {xdvi$run \ clear countdictstack xdvi$dslen sub {end} repeat } \ ifelse \ {(%%xdvimark) currentfile =string {readline} stopped \ {clear} {pop eq {exit} if} ifelse }loop \ flushpage xdvi$ack print flush \ }loop\nH"; static _Xconst char str2[] = "matrix currentmatrix \ dup dup 4 get round 4 exch put \ dup dup 5 get round 5 exch put setmatrix\n\ stop\n%%xdvimark\n"; #ifndef KDVI gs_atom = XInternAtom(DISP, "GHOSTVIEW", False); /* send bpixmap, orientation, bbox (in pixels), and h & v resolution */ Sprintf(buf, "%ld %d 0 0 %u %u 72 72", None, /* bpixmap */ XtPageOrientationPortrait, GS_page_w = page_w, GS_page_h = page_h); XChangeProperty(DISP, mane.win, gs_atom, XA_STRING, 8, PropModeReplace, (unsigned char *) buf, strlen(buf)); gs_colors_atom = XInternAtom(DISP, "GHOSTVIEW_COLORS", False); Sprintf(buf, "%s %ld %ld", "Color", fore_Pixel, back_Pixel); XChangeProperty(DISP, mane.win, gs_colors_atom, XA_STRING, 8, PropModeReplace, (unsigned char *) buf, strlen(buf)); XSync(DISP, False); /* update the window */ if (xpipe(std_in) != 0 || xpipe(std_out) != 0) { perror("pipe"); return False; } Fflush(stderr); /* to avoid double flushing */ GS_pid = vfork(); if (GS_pid == 0) { /* child */ Sprintf(buf, "%ld", mane.win); setenv("GHOSTVIEW", buf, True); setenv("DISPLAY", XDisplayString(DISP), True); (void) close(std_in[1]); (void) dup2(std_in[0], 0); (void) close(std_in[0]); (void) close(std_out[0]); (void) dup2(std_out[1], 1); (void) dup2(std_out[1], 2); (void) close(std_out[1]); (void) execvp(argv[0], argv); Fprintf(stderr, "Execvp of %s failed.\n", argv[0]); Fflush(stderr); _exit(1); } #else /* KDVI */ extern int useAlpha; extern Window mainwin; gs_atom = XInternAtom(DISP, "GHOSTVIEW", False); /* send bpixmap, orientation, bbox (in pixels), and h & v resolution */ Sprintf(buf, "%ld %d 0 0 %u %u 72 72", None, /* bpixmap */ XtPageOrientationPortrait, GS_page_w = page_w, GS_page_h = page_h); XChangeProperty(DISP, mainwin, gs_atom, XA_STRING, 8, PropModeReplace, (unsigned char *) buf, strlen(buf)); gs_colors_atom = XInternAtom(DISP, "GHOSTVIEW_COLORS", False); Sprintf(buf, "%s %ld %ld", "Color", fore_Pixel, back_Pixel); XChangeProperty(DISP, mainwin, gs_colors_atom, XA_STRING, 8, PropModeReplace, (unsigned char *) buf, strlen(buf)); XSync(DISP, False); /* update the window */ if (xpipe(std_in) != 0 || xpipe(std_out) != 0) { perror("pipe"); return False; } Fflush(stderr); /* to avoid double flushing */ GS_pid = vfork(); if (GS_pid == 0) { /* child */ Sprintf(buf, "%ld %ld", mainwin, mane.win); setenv("GHOSTVIEW", buf, True); setenv("DISPLAY", XDisplayString(DISP), True); (void) close(std_in[1]); (void) dup2(std_in[0], 0); (void) close(std_in[0]); (void) close(std_out[0]); (void) dup2(std_out[1], 1); (void) dup2(std_out[1], 2); (void) close(std_out[1]); argv[1] = useAlpha ? "-sDEVICE=x11alpha" : "-sDEVICE=x11"; (void) execvp(argv[0], argv); Fprintf(stderr, "Execvp of %s failed.\n", argv[0]); Fflush(stderr); _exit(1); } #endif /* KDVI */ if (GS_pid == -1) { /* error */ perror("vfork"); return False; } (void) close(std_in[0]); (void) close(std_out[1]); /* Set std_in for non-blocking I/O */ (void) fcntl(std_in[1], F_SETFL, fcntl(std_in[1], F_GETFL, 0) | O_NONBLOCK); #ifdef _POSIX_SOURCE sigpipe_handler_struct.sa_handler = gs_sigpipe_handler; sigemptyset(&sigpipe_handler_struct.sa_mask); #endif #ifndef STREAMSCONN numfds = ConnectionNumber(DISP); if (numfds < std_in[1]) numfds = std_in[1]; if (numfds < std_out[0]) numfds = std_out[0]; ++numfds; #else /* STREAMSCONN */ fds[0].fd = std_in[1]; fds[1].fd = std_out[0]; fds[2].fd = ConnectionNumber(DISP); #endif /* STREAMSCONN */ psp = gs_procs; GS_active = GS_sending = GS_pending_int = False; GS_pending = 1; GS_mag = GS_shrink = -1; send(str1, sizeof(str1) - 1); send(psheader, psheaderlen); Sprintf(buf, "[1 0 0 -1 0 %d] concat\n", page_h); send(buf, strlen(buf)); send(str2, sizeof(str2) - 1); waitack(0); if (GS_pid < 0) { /* if something happened */ destroy_gs(); return False; } if (!postscript) toggle_gs(); /* if we got a 'v' already */ else { #ifndef KDVI canit = True; /* ||| redraw the page */ longjmp(canit_env, 1); #endif /* KDVI */ } return True; } static void toggle_gs() { if (debug & DBG_PS) Puts("Toggling GS on or off"); if (postscript) psp.drawbegin = drawbegin_gs; else { interrupt_gs(); psp.drawbegin = drawbegin_none; } } static void destroy_gs() { if (debug & DBG_PS) Puts("Destroying GS process"); if (linepos > line) { *linepos = '\0'; Printf("gs: %s\n", line); linepos = line; } if (GS_pid >= 0) { if (kill(GS_pid, SIGKILL) < 0 && errno != ESRCH) perror("destroy_gs"); GS_pid = -1; } (void) close(GS_in); (void) close(GS_out); GS_active = GS_sending = GS_pending_int = False; GS_pending = 0; } static void interrupt_gs() { static _Xconst char str[] = " stop\n%%xdvimark\n"; if (debug & DBG_PS) Puts("Running interrupt_gs()"); if (GS_sending) GS_pending_int = True; else { if (GS_active) { /* * ||| what I'd really like to do here is cause gs to execute * the interrupt routine in errordict. But so far (gs 2.6.1) * that has not been implemented in ghostscript. */ send(str, sizeof(str) - 1); GS_active = False; } psp.interrupt = NullProc; /* prevent deep recursion in waitack */ waitack(5); psp.interrupt = interrupt_gs; } } static void endpage_gs() { static _Xconst char str[] = "stop\n%%xdvimark\n"; if (debug & DBG_PS) Puts("Running endpage_gs()"); if (GS_active) { send(str, sizeof(str) - 1); GS_active = False; waitack(0); } } static void drawbegin_gs(xul, yul, cp) int xul, yul; char *cp; { char buf[100]; static _Xconst char str[] = " TeXDict begin\n"; /* check page_w and page_h to see that they haven't increased */ if (page_w > GS_page_w || page_h > GS_page_h) { /* It would be nice if we could just resize the window, but I * don't see a convenient way to do that. */ destroy_gs(); } if (GS_pid < 0) (void) initGS(); if (!GS_active) { if (magnification != GS_mag) { Sprintf(buf, "H TeXDict begin /DVImag %d 1000 div def \ end stop\n%%%%xdvimark\n", GS_mag = magnification); send(buf, strlen(buf)); ++GS_pending; } if (mane.shrinkfactor != GS_shrink) { Sprintf(buf, "H TeXDict begin %d %d div dup \ /Resolution X /VResolution X \ end stop\n%%%%xdvimark\n", pixels_per_inch, GS_shrink = mane.shrinkfactor); send(buf, strlen(buf)); ++GS_pending; } send(str, sizeof(str) - 1); GS_active = True; ++GS_pending; } /* This allows the X side to clear the page */ XSync(DISP, False); Sprintf(buf, "%d %d moveto\n", xul, yul); send(buf, strlen(buf)); if (debug & DBG_PS) Printf("drawbegin at %d,%d: sending `%s'\n", xul, yul, cp); send(cp, strlen(cp)); } static void drawraw_gs(cp) char *cp; { int len = strlen(cp); if (!GS_active) return; if (debug & DBG_PS) Printf("raw ps sent to context: %s\n", cp); cp[len] = '\n'; send(cp, len + 1); } static void drawfile_gs(cp) char *cp; { char buf[PATH_MAX + 7]; if (!GS_active) return; if (debug & DBG_PS) Printf("printing file %s\n", cp); Sprintf(buf, "(%s)run\n", cp); send(buf, strlen(buf)); } static void drawend_gs(cp) char *cp; { if (!GS_active) return; if (debug & DBG_PS) Printf("end ps: %s\n", cp); send(cp, strlen(cp)); send("\n", 1); } #endif /* PS_GS */