/*
This software may only be used by you under license from AT&T Corp.
("AT&T"). A copy of AT&T's Source Code Agreement is available at
AT&T's Internet website having the URL:
<http://www.research.att.com/sw/tools/graphviz/license/source.html>
If you received this software without first entering into a license
with AT&T, you have an infringing copy of this software and cannot use
it without violating AT&T's intellectual property rights.
*/
#pragma prototyped
/*
J$: added `pdfmark' URL embedding. PostScript rendered from
dot files with URL attributes will get active PDF links
from Adobe's Distiller.
*/
#define PDFMAX 3240 /* Maximum size of Distiller's PDF canvas */
#include "render.h"
#include "ps.h"
#ifndef MSWIN32
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <stdio.h>
static int N_pages,Cur_page;
/* static point Pages; */
static box PB;
static int onetime = TRUE;
static char *Fill = "fill\n";
static char *Stroke = "stroke\n";
static char *Newpath_Moveto = "newpath %d %d moveto\n";
static char **U_lib;
typedef struct grcontext_t {
char *pencolor,*fillcolor,*font;
double size;
} grcontext_t;
#define STACKSIZE 8
static grcontext_t S[STACKSIZE];
static int SP = 0;
static void
ps_reset(void)
{
onetime = TRUE;
}
static void
ps_begin_job(FILE *ofp,graph_t *g, char **lib, char *user, char *info[],
point pages)
{
/* Pages = pages; */
U_lib = lib;
/* wrong when drawing more than one than one graph - use (atend) */
N_pages = pages.x * pages.y;
Cur_page = 0;
fprintf(Output_file,"%%!PS-Adobe-2.0\n");
fprintf(Output_file,"%%%%Creator: %s version %s (%s)\n",
info[0], info[1],info[2]);
fprintf(Output_file,"%%%%For: %s\n",user);
fprintf(Output_file,"%%%%Title: %s\n",g->name);
fprintf(Output_file,"%%%%Pages: (atend)\n");
/* remainder is emitted by first begin_graph */
}
static void
ps_end_job(void)
{
fprintf(Output_file,"%%%%Trailer\n");
fprintf(Output_file,"%%%%Pages: %d\n",Cur_page);
fprintf(Output_file,"end\nrestore\n");
fprintf(Output_file,"%%%%EOF\n");
}
static void
ps_comment(void* obj, attrsym_t* sym)
{
char *str;
str = late_string(obj,sym,"");
if (str[0]) fprintf(Output_file,"%% %s\n",str);
}
#define N_EPSF 32
static int N_EPSF_files;
static char *EPSF_contents[N_EPSF];
typedef struct epsf_s {
int macro_id;
point offset;
} epsf_t;
void epsf_init(node_t* n)
{
char *str,*contents;
char line[BUFSIZ];
FILE *fp;
struct stat statbuf;
int i, saw_bb;
int lx,ly,ux,uy;
epsf_t *desc;
if (N_EPSF_files >= N_EPSF) {
agerr(AGERR, "Can't read another EPSF file. Maximum number (%d) exceeded.\n", N_EPSF);
return;
}
str = agget(n,"shapefile");
if (str && str[0]) {
if ((fp = fopen(str,"r"))) {
/* try to find size */
saw_bb = FALSE;
while (fgets(line, sizeof(line), fp)) {
if (sscanf(line,"%%%%BoundingBox: %d %d %d %d",&lx,&ly,&ux,&uy) == 4) {
saw_bb = TRUE;
break;
}
}
if (saw_bb) {
ND_width(n) = PS2INCH(ux - lx);
ND_height(n) = PS2INCH(uy - ly);
fstat(fileno(fp),&statbuf);
i = N_EPSF_files++;
ND_shape_info(n) = desc = NEW(epsf_t);
desc->macro_id = i;
desc->offset.x = -lx - (ux - lx)/2;
desc->offset.y = -ly - (uy - ly)/2;
contents = EPSF_contents[i] = N_GNEW(statbuf.st_size+1,char);
fseek(fp,0,SEEK_SET);
fread(contents,statbuf.st_size,1,fp);
contents[statbuf.st_size] = '\0';
fclose(fp);
}
else agerr(AGWARN, "BoundingBox not found in epsf file %s\n",str);
}
else agerr(AGWARN, "couldn't open epsf file %s\n",str);
}
else agerr(AGWARN, "shapefile not set for epsf node %s\n",n->name);
}
void epsf_free(node_t* n)
{
/* FIXME What about the allocated EPSF_contents[i] ? */
if (ND_shape_info(n)) free(ND_shape_info(n));
}
void epsf_gencode(node_t *n)
{
epsf_t *desc;
desc = (epsf_t*)(ND_shape_info(n));
if (!desc) return;
CodeGen->begin_node(n);
CodeGen->begin_context();
if (desc) fprintf(Output_file,"%d %d translate newpath user_shape_%d\n",
ND_coord_i(n).x+desc->offset.x,ND_coord_i(n).y+desc->offset.y,
desc->macro_id);
CodeGen->end_context();
CodeGen->end_node();
}
static void
epsf_define(void)
{
int i;
#if 0
char *p;
#endif
for (i = 0; i < N_EPSF_files; i++) {
fprintf(Output_file,"/user_shape_%d {",i);
#if 0
p = EPSF_contents[i];
while (*p) {
if ((p[0] == '%') && (p[1] == '%') && (p[1] != '!')) {
while (*p++ != '\n');
continue;
}
do {fputc(*p,Output_file);} while (*p++ != '\n');
#endif
if (fputs("%%BeginDocument:\n", Output_file) == EOF) {
perror("epsf_define()->fputs");
exit(EXIT_FAILURE);
}
if (fputs(EPSF_contents[i], Output_file) == EOF) {
perror("epsf_define()->fputs");
exit(EXIT_FAILURE);
}
if (fputs("%%EndDocument\n", Output_file) == EOF) {
perror("epsf_define()->fputs");
exit(EXIT_FAILURE);
}
if (fputs("} bind def\n",Output_file) == EOF) {
perror("epsf_define()->fputs");
exit(EXIT_FAILURE);
}
free(EPSF_contents[i]);
#if 0
fprintf(Output_file,"} bind def\n");
#endif
}
N_EPSF_files = 0;
}
static void
ps_begin_graph(graph_t* g, box bb, point pb)
{
char *s;
static char setupLatin1 = FALSE;
PB = bb;
if (onetime) {
fprintf(Output_file,"%%%%BoundingBox: %d %d %d %d\n",
bb.LL.x-1,bb.LL.y-1,bb.UR.x+1,bb.UR.y+1);
ps_comment(g,agfindattr(g,"comment"));
fprintf(Output_file,"%%%%EndComments\nsave\n");
cat_libfile(Output_file,U_lib,ps_txt);
epsf_define();
/* Set base URL for relative links (for Distiller >= 3.0) */
if ((s = agget(g, "URL")) && strlen(s)) {
fprintf(Output_file,
"[ {Catalog} << /URI << /Base (%s) >> >>\n"
"/PUT pdfmark\n", s);
}
}
if (GD_has_Latin1char(g) && !setupLatin1) {
fprintf(Output_file,"setupLatin1\n"); /* as defined in ps header */
setupLatin1 = TRUE;
}
}
static void
ps_end_graph(void)
{
onetime = FALSE;
}
static void
ps_begin_page(graph_t *g, point page, double scale, int rot, point offset)
{
point sz;
Cur_page++;
sz = sub_points(PB.UR,PB.LL);
fprintf(Output_file,"%%%%Page: %d %d\n",Cur_page,Cur_page);
fprintf(Output_file,"%%%%PageBoundingBox: %d %d %d %d\n",
PB.LL.x,PB.LL.y,PB.UR.x+1,PB.UR.y+1);
fprintf(Output_file,"%%%%PageOrientation: %s\n",(rot?"Landscape":"Portrait"));
fprintf(Output_file,"gsave\n%d %d %d %d boxprim clip newpath\n",
PB.LL.x-1, PB.LL.y-1, sz.x + 2, sz.y + 2);
fprintf(Output_file,"%d %d translate\n",PB.LL.x,PB.LL.y);
if (rot) fprintf(Output_file,"gsave %d %d translate %d rotate\n",
PB.UR.x-PB.LL.x,0,rot);
fprintf(Output_file,"%d %d %d beginpage\n",page.x,page.y,N_pages);
if (rot) fprintf(Output_file,"grestore\n");
if (scale != 1.0) fprintf(Output_file,"%.4f set_scale\n",scale);
fprintf(Output_file,"%d %d translate %d rotate\n",offset.x,offset.y,rot);
assert(SP == 0);
S[SP].font = S[SP].pencolor = S[SP].fillcolor = "";
S[SP].size = 0.0;
/* Define the size of the PS canvas */
if (Output_lang == PDF) {
if (PB.UR.x >= PDFMAX || PB.UR.y >= PDFMAX)
agerr(AGWARN,
"canvas size (%d,%d) exceeds PDF limit (%d)\n"
"\t(suggest setting a bounding box size, see dot(1))\n",
PB.UR.x, PB.UR.y, PDFMAX);
fprintf(Output_file,"[ /CropBox [%d %d %d %d] /PAGES pdfmark\n",
PB.LL.x, PB.LL.y, PB.UR.x+1, PB.UR.y+1);
}
}
static void
ps_end_page(void)
{
/* the showpage is really a no-op, but at least one PS processor
* out there needs to see this literal token. endpage does the real work.
*/
fprintf(Output_file,"endpage\nshowpage\ngrestore\n");
fprintf(Output_file,"%%%%PageTrailer\n");
fprintf(Output_file,"%%%%EndPage: %d\n",Cur_page);
assert(SP == 0);
}
static void
ps_begin_cluster(graph_t* g)
{
fprintf(Output_file,"%% %s\n",g->name);
/* Embed information for Distiller to generate hyperlinked PDF */
map_begin_cluster(g);
}
static void ps_end_cluster(void) { }
static void ps_begin_nodes(void) { }
static void ps_end_nodes(void) { }
static void ps_begin_edges(void) { }
static void ps_end_edges(void) { }
static void
ps_begin_node(node_t* n)
{
fprintf(Output_file,"\n%%\t%s\n",n->name);
ps_comment(n,N_comment);
/* Embed information for Distiller to generate hyperlinked PDF */
map_begin_node(n);
}
static void ps_end_node (void) { }
static void
ps_begin_edge (edge_t* e)
{
fprintf(Output_file,"\n%%\t%s -> %s\n",e->tail->name,e->head->name);
ps_comment(e,E_comment);
/* Embed information for Distiller, so it can generate hyperactive PDF */
map_begin_edge(e);
}
static void ps_end_edge (void) { }
static void
ps_begin_context(void)
{
fprintf(Output_file,"gsave 10 dict begin\n");
if (SP == STACKSIZE - 1) agerr(AGWARN, "psgen stk ovfl\n");
else {SP++; S[SP] = S[SP-1];}
}
static void
ps_end_context(void)
{
if (SP == 0) agerr(AGWARN, "psgen stk undfl\n");
else SP--;
fprintf(Output_file,"end grestore\n");
}
static void
ps_set_font(char* name, double size)
{
if (strcmp(S[SP].font,name) || (size != S[SP].size)) {
fprintf(Output_file,"%.2f /%s set_font\n",size,name);
S[SP].font = name;
S[SP].size = size;
}
}
static void
ps_set_color(char* name)
{
static char *op[] = {"graph","node","edge","sethsb"};
color_t color;
colorxlate(name,&color,HSV_DOUBLE);
fprintf(Output_file,"%.3f %.3f %.3f %scolor\n",
color.u.HSV[0],color.u.HSV[1],color.u.HSV[2],op[Obj]);
}
static void
ps_set_pencolor(char* name)
{
if (strcmp(name,S[SP].pencolor)) {
ps_set_color(name); /* change pen color immediately */
S[SP].pencolor = name;
}
}
static void
ps_set_fillcolor(char* name)
{
S[SP].fillcolor = name; /* defer changes to fill color to shape */
}
static void
ps_set_style(char** s)
{
char *line,*p;
while ((p = line = *s++)) {
while (*p) p++; p++;
while (*p) {
fprintf(Output_file,"%s ",p);
while (*p) p++; p++;
}
fprintf(Output_file,"%s\n",line);
}
}
char *
ps_string(char *s)
{
static char *buf = NULL;
static int bufsize = 0;
int pos = 0;
char *p;
if (!buf) {
bufsize = 64;
buf = N_GNEW(bufsize,char);
}
p = buf;
*p++ = LPAREN;
pos++;
while (*s) {
if (pos > (bufsize-8)) {
bufsize *= 2;
buf = grealloc(buf,bufsize);
p = buf + pos;
}
if ((*s == LPAREN) || (*s == RPAREN) || (*s == '\\')) {
*p++ = '\\';
pos++;
}
*p++ = *s++;
pos++;
}
*p++ = RPAREN;
*p = '\0';
return buf;
}
static void
ps_textline(point p, textline_t *line)
{
double adj;
switch(line->just) {
case 'l':
adj = 0.0;
break;
case 'r':
adj = -1.0;
break;
default:
case 'n':
adj = -0.5;
break;
}
fprintf(Output_file,"%d %d moveto %d %.1f %s alignedtext\n",
p.x,p.y,line->width,adj,ps_string(line->str));
}
static void
ps_bezier(point *A, int n, int arrow_at_start, int arrow_at_end)
{
int j;
if (arrow_at_start || arrow_at_end)
agerr(AGERR, "ps_bezier illegal arrow args\n");
fprintf(Output_file,Newpath_Moveto,A[0].x,A[0].y);
for (j = 1; j < n; j += 3)
fprintf(Output_file,"%d %d %d %d %d %d curveto\n",
A[j].x,A[j].y,A[j+1].x,A[j+1].y,A[j+2].x,A[j+2].y);
fprintf(Output_file,Stroke);
}
static void
ps_polygon(point *A, int n, int filled)
{
int j;
if (filled) {
ps_set_color(S[SP].fillcolor);
fprintf(Output_file,Newpath_Moveto,A[0].x,A[0].y);
for (j = 1; j < n; j++)
fprintf(Output_file,"%d %d lineto\n",A[j].x,A[j].y);
fprintf(Output_file,"closepath\n");
fprintf(Output_file, Fill);
ps_set_color(S[SP].pencolor);
}
fprintf(Output_file,Newpath_Moveto,A[0].x,A[0].y);
for (j = 1; j < n; j++)
fprintf(Output_file,"%d %d lineto\n",A[j].x,A[j].y);
fprintf(Output_file,"closepath\n");
fprintf(Output_file, Stroke);
}
static void
ps_ellipse(point p, int rx, int ry, int filled)
{
if (filled) {
ps_set_color(S[SP].fillcolor);
fprintf(Output_file,"%d %d %d %d ellipse_path\n",p.x,p.y,rx,ry);
fprintf(Output_file, Fill);
ps_set_color(S[SP].pencolor);
}
if (!filled || (filled && strcmp(S[SP].fillcolor,S[SP].pencolor))) {
fprintf(Output_file,"%d %d %d %d ellipse_path\n",p.x,p.y,rx,ry);
fprintf(Output_file, Stroke);
}
}
static void
ps_polyline(point* A, int n)
{
int j;
fprintf(Output_file,Newpath_Moveto,A[0].x,A[0].y);
for (j = 1; j < n; j ++) fprintf(Output_file,"%d %d lineto\n",A[j].x,A[j].y);
fprintf(Output_file,Stroke);
}
static void
ps_user_shape(char *name, point *A, int sides, int filled)
{
int j;
fprintf(Output_file,"[ ");
for (j = 0; j < sides; j++) fprintf(Output_file,"%d %d ",A[j].x,A[j].y);
fprintf(Output_file,"%d %d ",A[0].x,A[0].y);
fprintf(Output_file,"] %d %s %s\n",sides,(filled?"true":"false"),name);
}
codegen_t PS_CodeGen = {
ps_reset,
ps_begin_job, ps_end_job,
ps_begin_graph, ps_end_graph,
ps_begin_page, ps_end_page,
ps_begin_cluster, ps_end_cluster,
ps_begin_nodes, ps_end_nodes,
ps_begin_edges, ps_end_edges,
ps_begin_node, ps_end_node,
ps_begin_edge, ps_end_edge,
ps_begin_context, ps_end_context,
ps_set_font, ps_textline,
ps_set_pencolor, ps_set_fillcolor, ps_set_style,
ps_ellipse, ps_polygon,
ps_bezier, ps_polyline,
0 /* ps_arrowhead */, ps_user_shape,
ps_comment, 0 /* ps_textsize */
};
|