1 /*----------------------------------------------------------------------*/
2 /* render.c --- Ghostscript rendering of background PostScript files */
3 /* Copyright (c) 2002 Tim Edwards, Johns Hopkins University */
4 /* These routines work only if ghostscript is on the system. */
5 /*----------------------------------------------------------------------*/
19 #include <sys/wait.h> /* for waitpid() */
21 #include <sys/types.h>
26 #include <X11/Intrinsic.h>
27 #include <X11/StringDefs.h>
28 #include <X11/Xatom.h>
31 #include <X11/Xatom.h>
35 /*------------------------------------------------------------------------*/
37 /*------------------------------------------------------------------------*/
43 #include "colordefs.h"
46 /*----------------------------------------------------------------------*/
47 /* Function prototype declarations */
48 /*----------------------------------------------------------------------*/
49 #include "prototypes.h"
51 /*------------------------------------------------------------------------*/
52 /* External Variable definitions */
53 /*------------------------------------------------------------------------*/
55 extern char _STR2
[250], _STR
[150];
56 extern Globaldata xobjs
;
57 extern XCWindowData
*areawin
;
59 extern int number_colors
;
60 extern colorindex
*colorlist
;
61 extern Cursor appcursors
[NUM_CURSORS
];
64 cairo_surface_t
*bbuf
= NULL
;
65 #else /* HAVE_CAIRO */
66 Pixmap bbuf
= (Pixmap
)NULL
; /* background buffer */
68 #endif /* HAVE_CAIRO */
74 gs_state_t gs_state
; /* Track state of the renderer */
76 /*------------------------------------------------------*/
77 /* Global variable definitions */
78 /*------------------------------------------------------*/
81 Atom gv
, gvc
, gvpage
, gvnext
, gvdone
;
83 pid_t gsproc
= -1; /* ghostscript process */
85 HANDLE gsproc
= INVALID_HANDLE_VALUE
;
87 int fgs
[2]; /* stdin pipe pair for ghostscript */
88 Window mwin
= 0; /* "false" window hack to get gs */
89 /* process to capture ClientMessage */
91 #endif /* HAVE_CAIRO */
93 /*--------------------------------------------------------------*/
94 /* Preliminary in allowing generic PostScript backgrounds */
95 /* via the ghostscript interpreter: Set the GHOSTVIEW and */
96 /* GHOSTVIEW atoms, and set the GHOSTVIEW environtment */
98 /*--------------------------------------------------------------*/
100 void ghostinit_local()
103 sprintf(_STR
, "%ld %d %d %d %d %d %g %g %d %d %d %d",
105 areawin
->width
* 75 / 72,
106 areawin
->height
* 75 / 72,
107 75.0, 75.0, 0, 0, 0, 0);
108 XChangeProperty(dpy
, areawin
->window
, gv
, XA_STRING
, 8, PropModeReplace
,
110 sprintf(_STR
, "%s %d %d", "Color", (int)FOREGROUND
, (int)BACKGROUND
);
111 XChangeProperty(dpy
, areawin
->window
, gvc
, XA_STRING
, 8, PropModeReplace
,
114 #endif /* HAVE_CAIRO */
119 /*------------------------------------------------------*/
120 /* Client message handler. Called by either the Tk */
121 /* client message handler (see below) or the Xt handler */
122 /* (see xcircuit.c). */
124 /* Returns True if a client message was received from */
125 /* the ghostscript process, False if not. */
126 /*------------------------------------------------------*/
128 Boolean
render_client(XEvent
*eventPtr
)
131 if (eventPtr
->xclient
.message_type
== gvpage
) {
133 fprintf(stdout
, "Xcircuit: Received PAGE message from ghostscript\n");
135 mwin
= eventPtr
->xclient
.data
.l
[0];
136 Wprintf("Background finished.");
137 XDefineCursor(dpy
, areawin
->window
, DEFAULTCURSOR
);
139 /* Mark this as the most recently rendered background, so we don't */
140 /* have to render more than necessary. */
141 areawin
->lastbackground
= xobjs
.pagelist
[areawin
->page
]->background
.name
;
143 drawarea(areawin
->area
, NULL
, NULL
);
145 else if (eventPtr
->xclient
.message_type
== gvdone
) {
147 fprintf(stdout
, "Xcircuit: Received DONE message from ghostscript\n");
155 atomname
= XGetAtomName(dpy
, eventPtr
->xclient
.message_type
);
156 if (atomname
!= NULL
) {
157 fprintf(stderr
, "Received client message using atom \"%s\"\n", atomname
);
162 #endif /* HAVE_CAIRO */
166 /*------------------------------------------------------*/
167 /* Tk client handler procedure for the above routine. */
168 /*------------------------------------------------------*/
173 void handle_client(ClientData clientData
, XEvent
*eventPtr
)
175 if (render_client(eventPtr
) == False
)
176 fprintf(stderr
, "Xcircuit: Received unknown client message\n");
178 #endif /* HAVE_CAIRO */
182 /*------------------------------------------------------*/
183 /* Global initialization */
184 /*------------------------------------------------------*/
189 gv
= XInternAtom(dpy
, "GHOSTVIEW", False
);
190 gvc
= XInternAtom(dpy
, "GHOSTVIEW_COLORS", False
);
191 gvpage
= XInternAtom(dpy
, "PAGE", False
);
192 gvnext
= XInternAtom(dpy
, "NEXT", False
);
193 gvdone
= XInternAtom(dpy
, "DONE", False
);
198 Tk_CreateClientMessageHandler((Tk_ClientMessageProc
*)handle_client
);
200 #endif /* HAVE_CAIRO */
203 /*------------------------------------------------------*/
204 /* Send a ClientMessage event */
205 /*------------------------------------------------------*/
208 void send_client(Atom msg
)
212 if (mwin
== 0) return; /* Have to wait for gs */
213 /* to give us window # */
215 event
.xclient
.type
= ClientMessage
;
216 event
.xclient
.display
= dpy
;
217 event
.xclient
.window
= areawin
->window
;
218 event
.xclient
.message_type
= msg
;
219 event
.xclient
.format
= 32;
220 event
.xclient
.data
.l
[0] = mwin
;
221 event
.xclient
.data
.l
[1] = bbuf
;
222 XSendEvent(dpy
, mwin
, False
, 0, &event
);
225 #endif /* HAVE_CAIRO */
227 /*------------------------------------------------------*/
228 /* Ask ghostscript to produce the next page. */
229 /*------------------------------------------------------*/
234 if (gs_state
!= GS_READY
) {
235 /* If we're in the middle of rendering something, halt */
236 /* immediately and start over. */
237 if (gs_state
== GS_PENDING
)
242 gs_state
= GS_PENDING
;
245 fprintf(stdout
, "Xcircuit: Sent NEXT message to ghostscript\n");
247 #endif /* HAVE_CAIRO */
250 /*--------------------------------------------------------*/
251 /* Start a ghostscript process and arrange the I/O pipes */
252 /* (Commented lines cause ghostscript to relay its stderr */
253 /* to xcircuit's stderr) */
254 /*--------------------------------------------------------*/
259 int std_out
[2], std_err
[2], ret
;
261 unsigned int pipe_size
= 8196;
262 int pipe_mode
= _O_BINARY
;
265 static char env_str1
[128], env_str2
[64];
269 if (bbuf
!= (Pixmap
)NULL
) Tk_FreePixmap(dpy
, bbuf
);
270 bbuf
= Tk_GetPixmap(dpy
, dbuf
, areawin
->width
, areawin
->height
,
271 Tk_Depth(areawin
->area
));
272 #else /* !TCL_WRAPPER */
273 if (bbuf
!= (Pixmap
)NULL
) XFreePixmap(dpy
, bbuf
);
274 bbuf
= XCreatePixmap(dpy
, dbuf
, areawin
->width
, areawin
->height
,
275 DefaultDepthOfScreen(xcScreen(areawin
->area
)));
276 #endif /* TCL_WRAPPER */
284 ret
= _pipe(fgs
, pipe_size
, pipe_mode
);
285 ret
= _pipe(std_out
, pipe_size
, pipe_mode
);
291 ret
= _pipe(std_err
, pipe_size
, pipe_mode
);
292 #endif /* XC_WIN32 */
295 /* We need a complicated pipe here, with input going from xcircuit */
296 /* to gs to provide scale/position information, and input going from */
297 /* the background file to gs for rendering. */
298 /* Here is not the place to do it. Set up gs to take stdin as input.*/
301 if (gsproc
== INVALID_HANDLE_VALUE
) {
303 PROCESS_INFORMATION pr_info
;
307 sprintf(env_str1
, "DISPLAY=%s", XDisplayString(dpy
));
309 sprintf(env_str2
, "GHOSTVIEW=%ld %ld", (long)areawin
->window
, (long)bbuf
);
312 setenv("DISPLAY", XDisplayString(dpy
), True
);
313 sprintf(_STR
, "%ld %ld", (long)areastruct
.areawin
, (long)bbuf
);
314 setenv("GHOSTVIEW", _STR
, True
);
317 SetHandleInformation((HANDLE
)_get_osfhandle(fgs
[1]), HANDLE_FLAG_INHERIT
, 0);
318 SetHandleInformation((HANDLE
)_get_osfhandle(std_out
[0]), HANDLE_FLAG_INHERIT
, 0);
320 SetHandleInformation((HANDLE
)_get_osfhandle(std_err
[0]), HANDLE_FLAG_INHERIT
, 0);
323 ZeroMemory(&st_info
, sizeof(STARTUPINFO
));
324 ZeroMemory(&pr_info
, sizeof(PROCESS_INFORMATION
));
325 st_info
.cb
= sizeof(STARTUPINFO
);
326 st_info
.dwFlags
= STARTF_USESTDHANDLES
;
327 st_info
.hStdOutput
= (HANDLE
)_get_osfhandle(std_out
[1]);
328 st_info
.hStdInput
= (HANDLE
)_get_osfhandle(fgs
[0]);
330 st_info
.hStdError
= (HANDLE
)_get_osfhandle(std_err
[1]);
333 snprintf(cmd
, 4095, "%s -dNOPAUSE -", GS_EXEC
);
334 if (CreateProcess(NULL
, cmd
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st_info
, &pr_info
) == 0) {
335 Wprintf("Error: ghostscript not running");
338 CloseHandle(pr_info
.hThread
);
339 gsproc
= pr_info
.hProcess
;
342 if (gsproc
< 0) { /* Ghostscript is not running yet */
344 if (gsproc
== 0) { /* child process (gs) */
346 fprintf(stdout
, "Calling %s\n", GS_EXEC
);
362 sprintf(env_str1
, "DISPLAY=%s", XDisplayString(dpy
));
364 sprintf(env_str2
, "GHOSTVIEW=%ld %ld", (long)areawin
->window
, (long)bbuf
);
367 setenv("DISPLAY", XDisplayString(dpy
), True
);
368 sprintf(_STR
, "%ld %ld", (long)areawin
->window
, (long)bbuf
);
369 setenv("GHOSTVIEW", _STR
, True
);
372 execlp(GS_EXEC
, "gs", "-dNOPAUSE", "-", (char *)NULL
);
374 fprintf(stderr
, "Exec of gs failed\n");
377 else if (gsproc
< 0) {
378 Wprintf("Error: ghostscript not running");
379 return; /* error condition */
384 #endif /* HAVE_CAIRO */
386 /*--------------------------------------------------------*/
387 /* Parse the background file for Bounding Box information */
388 /*--------------------------------------------------------*/
390 void parse_bg(FILE *fi
, FILE *fbg
) {
392 Boolean bflag
= False
;
393 int llx
, lly
, urx
, ury
;
397 psscale
= getpsscale(xobjs
.pagelist
[areawin
->page
]->outscale
, areawin
->page
);
400 if (fgets(line_in
, 255, fi
) == NULL
) {
401 Wprintf("Error: end of file before end of insert.");
404 else if (strstr(line_in
, "end_insert") != NULL
) break;
407 if ((bbptr
= strstr(line_in
, "BoundingBox:")) != NULL
) {
408 if (strstr(line_in
, "(atend)") == NULL
) {
410 sscanf(bbptr
+ 12, "%d %d %d %d", &llx
, &lly
, &urx
, &ury
);
411 /* compute user coordinate bounds from PostScript bounds */
413 fprintf(stdout
, "BBox %d %d %d %d PostScript coordinates\n",
416 llx
= (int)((float)llx
/ psscale
);
417 lly
= (int)((float)lly
/ psscale
);
418 urx
= (int)((float)urx
/ psscale
);
419 ury
= (int)((float)ury
/ psscale
);
421 fprintf(stdout
, "BBox %d %d %d %d XCircuit coordinates\n",
425 xobjs
.pagelist
[areawin
->page
]->background
.bbox
.lowerleft
.x
= llx
;
426 xobjs
.pagelist
[areawin
->page
]->background
.bbox
.lowerleft
.y
= lly
;
427 xobjs
.pagelist
[areawin
->page
]->background
.bbox
.width
= (urx
- llx
);
428 xobjs
.pagelist
[areawin
->page
]->background
.bbox
.height
= (ury
- lly
);
429 if (fbg
== (FILE *)NULL
) break;
433 if (fbg
!= (FILE *)NULL
) fputs(line_in
, fbg
);
437 /*-------------------------------------------------------*/
438 /* Get bounding box information from the background file */
439 /*-------------------------------------------------------*/
446 fname
= xobjs
.pagelist
[areawin
->page
]->background
.name
;
447 if ((fi
= fopen(fname
, "r")) == NULL
) {
448 fprintf(stderr
, "Failure to open background file to get bounding box info\n");
451 parse_bg(fi
, (FILE *)NULL
);
455 /*------------------------------------------------------------*/
456 /* Adjust object's bounding box based on the background image */
457 /*------------------------------------------------------------*/
459 void backgroundbbox(int mpage
)
461 int llx
, lly
, urx
, ury
, tmp
;
462 objectptr thisobj
= xobjs
.pagelist
[mpage
]->pageinst
->thisobject
;
463 psbkground
*thisbg
= &xobjs
.pagelist
[mpage
]->background
;
465 llx
= thisobj
->bbox
.lowerleft
.x
;
466 lly
= thisobj
->bbox
.lowerleft
.y
;
467 urx
= thisobj
->bbox
.width
+ llx
;
468 ury
= thisobj
->bbox
.height
+ lly
;
470 if (thisbg
->bbox
.lowerleft
.x
< llx
) llx
= thisbg
->bbox
.lowerleft
.x
;
471 if (thisbg
->bbox
.lowerleft
.y
< lly
) lly
= thisbg
->bbox
.lowerleft
.y
;
472 tmp
= thisbg
->bbox
.width
+ thisbg
->bbox
.lowerleft
.x
;
473 if (tmp
> urx
) urx
= tmp
;
474 tmp
= thisbg
->bbox
.height
+ thisbg
->bbox
.lowerleft
.y
;
475 if (tmp
> ury
) ury
= tmp
;
477 thisobj
->bbox
.lowerleft
.x
= llx
;
478 thisobj
->bbox
.lowerleft
.y
= lly
;
479 thisobj
->bbox
.width
= urx
- llx
;
480 thisobj
->bbox
.height
= ury
- lly
;
483 /*------------------------------------------------------*/
484 /* Read a background PostScript image from a file and */
485 /* store in a temporary file, passing that filename to */
486 /* the background property of the page. */
487 /*------------------------------------------------------*/
489 void readbackground(FILE *fi
)
491 FILE *fbg
= (FILE *)NULL
;
493 char *file_in
= (char *)malloc(9 + strlen(xobjs
.tempdir
));
495 /* "@" denotes a temporary file */
496 sprintf(file_in
, "@%s/XXXXXX", xobjs
.tempdir
);
499 tfd
= mktemp(file_in
+ 1);
501 tfd
= mkstemp(file_in
+ 1);
503 if (tfd
== -1) fprintf(stderr
, "Error generating temporary filename\n");
505 if ((fbg
= fdopen(tfd
, "w")) == NULL
) {
506 fprintf(stderr
, "Error opening temporary file \"%s\"\n", file_in
+ 1);
510 /* Read the file to the restore directive or end_insertion */
511 /* Skip restore directive and end_insertion command */
515 if (fbg
!= (FILE *)NULL
) {
517 register_bg(file_in
);
522 /*------------------------------------------------------*/
523 /* Save a background PostScript image to the output */
524 /* file by streaming directly from the background file */
525 /*------------------------------------------------------*/
527 void savebackground(FILE *fo
, char *psfilename
)
530 char *fname
= psfilename
;
533 if (fname
[0] == '@') fname
++;
535 if ((psf
= fopen(fname
, "r")) == NULL
) {
536 fprintf(stderr
, "Error opening background file \"%s\" for reading.\n", fname
);
541 if (fgets(line_in
, 255, psf
) == NULL
)
549 /*--------------------------------------------------------------*/
550 /* Set up a page to render a PostScript image when redrawing. */
551 /* This includes starting the ghostscript process if it has */
552 /* not already been started. This routine does not draw the */
553 /* image, which is done on refresh. */
554 /*--------------------------------------------------------------*/
556 void register_bg(char *gsfile
)
563 #endif /* !HAVE_CAIRO */
565 xobjs
.pagelist
[areawin
->page
]->background
.name
=
566 (char *) malloc(strlen(gsfile
) + 1);
567 strcpy(xobjs
.pagelist
[areawin
->page
]->background
.name
, gsfile
);
570 /*------------------------------------------------------*/
571 /* Load a generic (non-xcircuit) postscript file as the */
572 /* background for the page. This function is called */
573 /* by the file import routine, and so it completes by */
574 /* running zoomview(), which redraws the image and */
576 /*------------------------------------------------------*/
578 void loadbackground()
582 updatepagebounds(topobject
);
583 zoomview(areawin
->area
, NULL
, NULL
);
586 /*------------------------------------------------------*/
587 /* Send text to the ghostscript renderer */
588 /*------------------------------------------------------*/
591 void send_to_gs(char *text
)
594 write(fgs
[1], text
, strlen(text
));
595 tcflush(fgs
[1], TCOFLUSH
);
597 _write(fgs
[1], text
, (unsigned int)strlen(text
));
600 fprintf(stdout
, "writing: %s", text
);
603 #endif /* !HAVE_CAIRO */
605 /*------------------------------------------------------*/
606 /* write scale and position to ghostscript */
607 /* and tell ghostscript to run the requested file */
608 /*------------------------------------------------------*/
611 void write_scale_position_and_run_gs(float norm
, float xpos
, float ypos
,
614 send_to_gs("/GSobj save def\n");
615 send_to_gs("/setpagedevice {pop} def\n");
616 send_to_gs("gsave\n");
617 sprintf(_STR
, "%3.2f %3.2f translate\n", xpos
, ypos
);
619 sprintf(_STR
, "%3.2f %3.2f scale\n", norm
, norm
);
621 sprintf(_STR
, "(%s) run\n", bgfile
);
623 send_to_gs("GSobj restore\n");
624 send_to_gs("grestore\n");
626 Wprintf("Rendering background image.");
627 XDefineCursor(dpy
, areawin
->window
, WAITFOR
);
629 #endif /* !HAVE_CAIRO */
631 /*------------------------------------------------------*/
632 /* Call ghostscript to render the background into the */
634 /*------------------------------------------------------*/
636 int renderbackground()
639 float psnorm
, psxpos
, psypos
, defscale
;
640 float devres
= 0.96; /* = 72.0 / 75.0, ps_units/in : screen_dpi */
643 if (gsproc
< 0) return -1;
644 #endif /* !HAVE_CAIRO */
646 defscale
= (xobjs
.pagelist
[areawin
->page
]->coordstyle
== CM
) ?
649 psnorm
= areawin
->vscale
* (1.0 / defscale
) * devres
;
651 psxpos
= (float)(-areawin
->pcorner
.x
) * areawin
->vscale
* devres
;
652 psypos
= (float)(-areawin
->pcorner
.y
) * areawin
->vscale
* devres
;
654 psypos
+= areawin
->height
/ 12.;
655 #endif /* !HAVE_CAIRO */
657 /* Conditions for re-rendering: Must have a background specified */
658 /* and must be on the page, not a library or other object. */
660 if (xobjs
.pagelist
[areawin
->page
]->background
.name
== (char *)NULL
)
662 else if (areawin
->lastbackground
663 == xobjs
.pagelist
[areawin
->page
]->background
.name
) {
667 if (is_page(topobject
) == -1)
670 bgfile
= xobjs
.pagelist
[areawin
->page
]->background
.name
;
671 if (*bgfile
== '@') bgfile
++;
673 /* Ask ghostscript to produce the next page */
676 /* Set the last background name to NULL; this will get the */
677 /* value when the rendering is done. */
679 areawin
->lastbackground
= NULL
;
682 fprintf(stdout
, "Rendering background from file \"%s\"\n", bgfile
);
684 Wprintf("Rendering background image.");
686 /* write scale and position to ghostscript */
687 /* and tell ghostscript to run the requested file */
689 write_scale_position_and_run_gs(psnorm
, psxpos
, psypos
, bgfile
);
691 /* The page will be refreshed when we receive confirmation */
692 /* from ghostscript that the buffer has been rendered. */
697 /*------------------------------------------------------*/
698 /* Copy the rendered background pixmap to the window. */
699 /*------------------------------------------------------*/
703 /* Don't copy if the buffer is not ready to use */
704 if (gs_state
!= GS_READY
)
707 /* Only draw on a top-level page */
708 if (is_page(topobject
) == -1)
712 cairo_set_source_surface(areawin
->cr
, bbuf
, 0., 0.);
713 cairo_paint(areawin
->cr
);
715 XCopyArea(dpy
, bbuf
, dbuf
, areawin
->gc
, 0, 0,
716 areawin
->width
, areawin
->height
, 0, 0);
722 /*------------------------------------------------------*/
723 /* Exit ghostscript. . . not so gently */
724 /*------------------------------------------------------*/
730 if (gsproc
== INVALID_HANDLE_VALUE
) return -1; /* gs not running */
732 if (gsproc
< 0) return -1; /* gs not running */
736 fprintf(stdout
, "Waiting for gs to exit\n");
739 kill(gsproc
, SIGKILL
);
740 waitpid(gsproc
, NULL
, 0);
742 TerminateProcess(gsproc
, -1);
745 fprintf(stdout
, "gs has exited\n");
750 gsproc
= INVALID_HANDLE_VALUE
;
754 #endif /* !HAVE_CAIRO */
760 /*------------------------------------------------------*/
761 /* Restart ghostscript */
762 /*------------------------------------------------------*/
768 if (gsproc
== INVALID_HANDLE_VALUE
) return -1;
770 if (gsproc
< 0) return -1;
777 #endif /* !HAVE_CAIRO */
781 /*----------------------------------------------------------------------*/