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 if (areawin
->area
== NULL
) return;
191 gv
= XInternAtom(dpy
, "GHOSTVIEW", False
);
192 gvc
= XInternAtom(dpy
, "GHOSTVIEW_COLORS", False
);
193 gvpage
= XInternAtom(dpy
, "PAGE", False
);
194 gvnext
= XInternAtom(dpy
, "NEXT", False
);
195 gvdone
= XInternAtom(dpy
, "DONE", False
);
200 Tk_CreateClientMessageHandler((Tk_ClientMessageProc
*)handle_client
);
202 #endif /* HAVE_CAIRO */
205 /*------------------------------------------------------*/
206 /* Send a ClientMessage event */
207 /*------------------------------------------------------*/
210 void send_client(Atom msg
)
214 if (mwin
== 0) return; /* Have to wait for gs */
215 /* to give us window # */
217 event
.xclient
.type
= ClientMessage
;
218 event
.xclient
.display
= dpy
;
219 event
.xclient
.window
= areawin
->window
;
220 event
.xclient
.message_type
= msg
;
221 event
.xclient
.format
= 32;
222 event
.xclient
.data
.l
[0] = mwin
;
223 event
.xclient
.data
.l
[1] = bbuf
;
224 XSendEvent(dpy
, mwin
, False
, 0, &event
);
227 #endif /* HAVE_CAIRO */
229 /*------------------------------------------------------*/
230 /* Ask ghostscript to produce the next page. */
231 /*------------------------------------------------------*/
236 if (gs_state
!= GS_READY
) {
237 /* If we're in the middle of rendering something, halt */
238 /* immediately and start over. */
239 if (gs_state
== GS_PENDING
)
244 gs_state
= GS_PENDING
;
247 fprintf(stdout
, "Xcircuit: Sent NEXT message to ghostscript\n");
249 #endif /* HAVE_CAIRO */
252 /*--------------------------------------------------------*/
253 /* Start a ghostscript process and arrange the I/O pipes */
254 /* (Commented lines cause ghostscript to relay its stderr */
255 /* to xcircuit's stderr) */
256 /*--------------------------------------------------------*/
261 int std_out
[2], std_err
[2], ret
;
263 unsigned int pipe_size
= 8196;
264 int pipe_mode
= _O_BINARY
;
267 static char env_str1
[128], env_str2
[64];
271 if (bbuf
!= (Pixmap
)NULL
) Tk_FreePixmap(dpy
, bbuf
);
272 bbuf
= Tk_GetPixmap(dpy
, dbuf
, areawin
->width
, areawin
->height
,
273 Tk_Depth(areawin
->area
));
274 #else /* !TCL_WRAPPER */
275 if (bbuf
!= (Pixmap
)NULL
) XFreePixmap(dpy
, bbuf
);
276 bbuf
= XCreatePixmap(dpy
, dbuf
, areawin
->width
, areawin
->height
,
277 DefaultDepthOfScreen(xcScreen(areawin
->area
)));
278 #endif /* TCL_WRAPPER */
286 ret
= _pipe(fgs
, pipe_size
, pipe_mode
);
287 ret
= _pipe(std_out
, pipe_size
, pipe_mode
);
293 ret
= _pipe(std_err
, pipe_size
, pipe_mode
);
294 #endif /* XC_WIN32 */
297 /* We need a complicated pipe here, with input going from xcircuit */
298 /* to gs to provide scale/position information, and input going from */
299 /* the background file to gs for rendering. */
300 /* Here is not the place to do it. Set up gs to take stdin as input.*/
303 if (gsproc
== INVALID_HANDLE_VALUE
) {
305 PROCESS_INFORMATION pr_info
;
309 sprintf(env_str1
, "DISPLAY=%s", XDisplayString(dpy
));
311 sprintf(env_str2
, "GHOSTVIEW=%ld %ld", (long)areawin
->window
, (long)bbuf
);
314 setenv("DISPLAY", XDisplayString(dpy
), True
);
315 sprintf(_STR
, "%ld %ld", (long)areastruct
.areawin
, (long)bbuf
);
316 setenv("GHOSTVIEW", _STR
, True
);
319 SetHandleInformation((HANDLE
)_get_osfhandle(fgs
[1]), HANDLE_FLAG_INHERIT
, 0);
320 SetHandleInformation((HANDLE
)_get_osfhandle(std_out
[0]), HANDLE_FLAG_INHERIT
, 0);
322 SetHandleInformation((HANDLE
)_get_osfhandle(std_err
[0]), HANDLE_FLAG_INHERIT
, 0);
325 ZeroMemory(&st_info
, sizeof(STARTUPINFO
));
326 ZeroMemory(&pr_info
, sizeof(PROCESS_INFORMATION
));
327 st_info
.cb
= sizeof(STARTUPINFO
);
328 st_info
.dwFlags
= STARTF_USESTDHANDLES
;
329 st_info
.hStdOutput
= (HANDLE
)_get_osfhandle(std_out
[1]);
330 st_info
.hStdInput
= (HANDLE
)_get_osfhandle(fgs
[0]);
332 st_info
.hStdError
= (HANDLE
)_get_osfhandle(std_err
[1]);
335 snprintf(cmd
, 4095, "%s -dNOPAUSE -", GS_EXEC
);
336 if (CreateProcess(NULL
, cmd
, NULL
, NULL
, TRUE
, 0, NULL
, NULL
, &st_info
, &pr_info
) == 0) {
337 Wprintf("Error: ghostscript not running");
340 CloseHandle(pr_info
.hThread
);
341 gsproc
= pr_info
.hProcess
;
344 if (gsproc
< 0) { /* Ghostscript is not running yet */
346 if (gsproc
== 0) { /* child process (gs) */
348 fprintf(stdout
, "Calling %s\n", GS_EXEC
);
364 sprintf(env_str1
, "DISPLAY=%s", XDisplayString(dpy
));
366 sprintf(env_str2
, "GHOSTVIEW=%ld %ld", (long)areawin
->window
, (long)bbuf
);
369 setenv("DISPLAY", XDisplayString(dpy
), True
);
370 sprintf(_STR
, "%ld %ld", (long)areawin
->window
, (long)bbuf
);
371 setenv("GHOSTVIEW", _STR
, True
);
374 execlp(GS_EXEC
, "gs", "-dNOPAUSE", "-", (char *)NULL
);
376 fprintf(stderr
, "Exec of gs failed\n");
379 else if (gsproc
< 0) {
380 Wprintf("Error: ghostscript not running");
381 return; /* error condition */
386 #endif /* HAVE_CAIRO */
388 /*--------------------------------------------------------*/
389 /* Parse the background file for Bounding Box information */
390 /*--------------------------------------------------------*/
392 void parse_bg(FILE *fi
, FILE *fbg
) {
394 Boolean bflag
= False
;
395 int llx
, lly
, urx
, ury
;
399 psscale
= getpsscale(xobjs
.pagelist
[areawin
->page
]->outscale
, areawin
->page
);
402 if (fgets(line_in
, 255, fi
) == NULL
) {
403 Wprintf("Error: end of file before end of insert.");
406 else if (strstr(line_in
, "end_insert") != NULL
) break;
409 if ((bbptr
= strstr(line_in
, "BoundingBox:")) != NULL
) {
410 if (strstr(line_in
, "(atend)") == NULL
) {
412 sscanf(bbptr
+ 12, "%d %d %d %d", &llx
, &lly
, &urx
, &ury
);
413 /* compute user coordinate bounds from PostScript bounds */
415 fprintf(stdout
, "BBox %d %d %d %d PostScript coordinates\n",
418 llx
= (int)((float)llx
/ psscale
);
419 lly
= (int)((float)lly
/ psscale
);
420 urx
= (int)((float)urx
/ psscale
);
421 ury
= (int)((float)ury
/ psscale
);
423 fprintf(stdout
, "BBox %d %d %d %d XCircuit coordinates\n",
427 xobjs
.pagelist
[areawin
->page
]->background
.bbox
.lowerleft
.x
= llx
;
428 xobjs
.pagelist
[areawin
->page
]->background
.bbox
.lowerleft
.y
= lly
;
429 xobjs
.pagelist
[areawin
->page
]->background
.bbox
.width
= (urx
- llx
);
430 xobjs
.pagelist
[areawin
->page
]->background
.bbox
.height
= (ury
- lly
);
431 if (fbg
== (FILE *)NULL
) break;
435 if (fbg
!= (FILE *)NULL
) fputs(line_in
, fbg
);
439 /*-------------------------------------------------------*/
440 /* Get bounding box information from the background file */
441 /*-------------------------------------------------------*/
448 fname
= xobjs
.pagelist
[areawin
->page
]->background
.name
;
449 if ((fi
= fopen(fname
, "r")) == NULL
) {
450 fprintf(stderr
, "Failure to open background file to get bounding box info\n");
453 parse_bg(fi
, (FILE *)NULL
);
457 /*------------------------------------------------------------*/
458 /* Adjust object's bounding box based on the background image */
459 /*------------------------------------------------------------*/
461 void backgroundbbox(int mpage
)
463 int llx
, lly
, urx
, ury
, tmp
;
464 objectptr thisobj
= xobjs
.pagelist
[mpage
]->pageinst
->thisobject
;
465 psbkground
*thisbg
= &xobjs
.pagelist
[mpage
]->background
;
467 llx
= thisobj
->bbox
.lowerleft
.x
;
468 lly
= thisobj
->bbox
.lowerleft
.y
;
469 urx
= thisobj
->bbox
.width
+ llx
;
470 ury
= thisobj
->bbox
.height
+ lly
;
472 if (thisbg
->bbox
.lowerleft
.x
< llx
) llx
= thisbg
->bbox
.lowerleft
.x
;
473 if (thisbg
->bbox
.lowerleft
.y
< lly
) lly
= thisbg
->bbox
.lowerleft
.y
;
474 tmp
= thisbg
->bbox
.width
+ thisbg
->bbox
.lowerleft
.x
;
475 if (tmp
> urx
) urx
= tmp
;
476 tmp
= thisbg
->bbox
.height
+ thisbg
->bbox
.lowerleft
.y
;
477 if (tmp
> ury
) ury
= tmp
;
479 thisobj
->bbox
.lowerleft
.x
= llx
;
480 thisobj
->bbox
.lowerleft
.y
= lly
;
481 thisobj
->bbox
.width
= urx
- llx
;
482 thisobj
->bbox
.height
= ury
- lly
;
485 /*------------------------------------------------------*/
486 /* Read a background PostScript image from a file and */
487 /* store in a temporary file, passing that filename to */
488 /* the background property of the page. */
489 /*------------------------------------------------------*/
491 void readbackground(FILE *fi
)
493 FILE *fbg
= (FILE *)NULL
;
495 char *file_in
= (char *)malloc(9 + strlen(xobjs
.tempdir
));
497 /* "@" denotes a temporary file */
498 sprintf(file_in
, "@%s/XXXXXX", xobjs
.tempdir
);
501 tfd
= mktemp(file_in
+ 1);
503 tfd
= mkstemp(file_in
+ 1);
505 if (tfd
== -1) fprintf(stderr
, "Error generating temporary filename\n");
507 if ((fbg
= fdopen(tfd
, "w")) == NULL
) {
508 fprintf(stderr
, "Error opening temporary file \"%s\"\n", file_in
+ 1);
512 /* Read the file to the restore directive or end_insertion */
513 /* Skip restore directive and end_insertion command */
517 if (fbg
!= (FILE *)NULL
) {
519 register_bg(file_in
);
524 /*------------------------------------------------------*/
525 /* Save a background PostScript image to the output */
526 /* file by streaming directly from the background file */
527 /*------------------------------------------------------*/
529 void savebackground(FILE *fo
, char *psfilename
)
532 char *fname
= psfilename
;
535 if (fname
[0] == '@') fname
++;
537 if ((psf
= fopen(fname
, "r")) == NULL
) {
538 fprintf(stderr
, "Error opening background file \"%s\" for reading.\n", fname
);
543 if (fgets(line_in
, 255, psf
) == NULL
)
551 /*--------------------------------------------------------------*/
552 /* Set up a page to render a PostScript image when redrawing. */
553 /* This includes starting the ghostscript process if it has */
554 /* not already been started. This routine does not draw the */
555 /* image, which is done on refresh. */
556 /*--------------------------------------------------------------*/
558 void register_bg(char *gsfile
)
565 #endif /* !HAVE_CAIRO */
567 xobjs
.pagelist
[areawin
->page
]->background
.name
=
568 (char *) malloc(strlen(gsfile
) + 1);
569 strcpy(xobjs
.pagelist
[areawin
->page
]->background
.name
, gsfile
);
572 /*------------------------------------------------------*/
573 /* Load a generic (non-xcircuit) postscript file as the */
574 /* background for the page. This function is called */
575 /* by the file import routine, and so it completes by */
576 /* running zoomview(), which redraws the image and */
578 /*------------------------------------------------------*/
580 void loadbackground()
584 updatepagebounds(topobject
);
585 zoomview(areawin
->area
, NULL
, NULL
);
588 /*------------------------------------------------------*/
589 /* Send text to the ghostscript renderer */
590 /*------------------------------------------------------*/
593 void send_to_gs(char *text
)
596 write(fgs
[1], text
, strlen(text
));
597 tcflush(fgs
[1], TCOFLUSH
);
599 _write(fgs
[1], text
, (unsigned int)strlen(text
));
602 fprintf(stdout
, "writing: %s", text
);
605 #endif /* !HAVE_CAIRO */
607 /*------------------------------------------------------*/
608 /* write scale and position to ghostscript */
609 /* and tell ghostscript to run the requested file */
610 /*------------------------------------------------------*/
613 void write_scale_position_and_run_gs(float norm
, float xpos
, float ypos
,
616 send_to_gs("/GSobj save def\n");
617 send_to_gs("/setpagedevice {pop} def\n");
618 send_to_gs("gsave\n");
619 sprintf(_STR
, "%3.2f %3.2f translate\n", xpos
, ypos
);
621 sprintf(_STR
, "%3.2f %3.2f scale\n", norm
, norm
);
623 sprintf(_STR
, "(%s) run\n", bgfile
);
625 send_to_gs("GSobj restore\n");
626 send_to_gs("grestore\n");
628 Wprintf("Rendering background image.");
629 XDefineCursor(dpy
, areawin
->window
, WAITFOR
);
631 #endif /* !HAVE_CAIRO */
633 /*------------------------------------------------------*/
634 /* Call ghostscript to render the background into the */
636 /*------------------------------------------------------*/
638 int renderbackground()
641 float psnorm
, psxpos
, psypos
, defscale
;
642 float devres
= 0.96; /* = 72.0 / 75.0, ps_units/in : screen_dpi */
645 if (gsproc
< 0) return -1;
646 #endif /* !HAVE_CAIRO */
648 defscale
= (xobjs
.pagelist
[areawin
->page
]->coordstyle
== CM
) ?
651 psnorm
= areawin
->vscale
* (1.0 / defscale
) * devres
;
653 psxpos
= (float)(-areawin
->pcorner
.x
) * areawin
->vscale
* devres
;
654 psypos
= (float)(-areawin
->pcorner
.y
) * areawin
->vscale
* devres
;
656 psypos
+= areawin
->height
/ 12.;
657 #endif /* !HAVE_CAIRO */
659 /* Conditions for re-rendering: Must have a background specified */
660 /* and must be on the page, not a library or other object. */
662 if (xobjs
.pagelist
[areawin
->page
]->background
.name
== (char *)NULL
)
664 else if (areawin
->lastbackground
665 == xobjs
.pagelist
[areawin
->page
]->background
.name
) {
669 if (is_page(topobject
) == -1)
672 bgfile
= xobjs
.pagelist
[areawin
->page
]->background
.name
;
673 if (*bgfile
== '@') bgfile
++;
675 /* Ask ghostscript to produce the next page */
678 /* Set the last background name to NULL; this will get the */
679 /* value when the rendering is done. */
681 areawin
->lastbackground
= NULL
;
684 fprintf(stdout
, "Rendering background from file \"%s\"\n", bgfile
);
686 Wprintf("Rendering background image.");
688 /* write scale and position to ghostscript */
689 /* and tell ghostscript to run the requested file */
691 write_scale_position_and_run_gs(psnorm
, psxpos
, psypos
, bgfile
);
693 /* The page will be refreshed when we receive confirmation */
694 /* from ghostscript that the buffer has been rendered. */
699 /*------------------------------------------------------*/
700 /* Copy the rendered background pixmap to the window. */
701 /*------------------------------------------------------*/
705 /* Don't copy if the buffer is not ready to use */
706 if (gs_state
!= GS_READY
)
709 /* Only draw on a top-level page */
710 if (is_page(topobject
) == -1)
714 cairo_set_source_surface(areawin
->cr
, bbuf
, 0., 0.);
715 cairo_paint(areawin
->cr
);
717 XCopyArea(dpy
, bbuf
, dbuf
, areawin
->gc
, 0, 0,
718 areawin
->width
, areawin
->height
, 0, 0);
724 /*------------------------------------------------------*/
725 /* Exit ghostscript. . . not so gently */
726 /*------------------------------------------------------*/
732 if (gsproc
== INVALID_HANDLE_VALUE
) return -1; /* gs not running */
734 if (gsproc
< 0) return -1; /* gs not running */
738 fprintf(stdout
, "Waiting for gs to exit\n");
741 kill(gsproc
, SIGKILL
);
742 waitpid(gsproc
, NULL
, 0);
744 TerminateProcess(gsproc
, -1);
747 fprintf(stdout
, "gs has exited\n");
752 gsproc
= INVALID_HANDLE_VALUE
;
756 #endif /* !HAVE_CAIRO */
762 /*------------------------------------------------------*/
763 /* Restart ghostscript */
764 /*------------------------------------------------------*/
770 if (gsproc
== INVALID_HANDLE_VALUE
) return -1;
772 if (gsproc
< 0) return -1;
779 #endif /* !HAVE_CAIRO */
783 /*----------------------------------------------------------------------*/