--- /dev/null
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+* This software is part of the graphviz package *
+* http://www.graphviz.org/ *
+* *
+* Copyright (c) 1994-2004 AT&T Corp. *
+* and is licensed under the *
+* Common Public License, Version 1.0 *
+* by AT&T Corp. *
+* *
+* Information and Software Systems Research *
+* AT&T Research, Florham Park NJ *
+**********************************************************/
+
+#include "gvplugin.h"
+
+extern gvplugin_installed_t gvrender_core_ps_types;
+extern gvplugin_installed_t gvrender_core_svg_types;
+
+static gvplugin_api_t apis[] = {
+ {API_render, &gvrender_core_ps_types},
+ {API_render, &gvrender_core_svg_types},
+ {(api_t)0, NULL},
+};
+
+gvplugin_library_t gvplugin_core_LTX_library = { "core", apis };
--- /dev/null
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+* This software is part of the graphviz package *
+* http://www.graphviz.org/ *
+* *
+* Copyright (c) 1994-2004 AT&T Corp. *
+* and is licensed under the *
+* Common Public License, Version 1.0 *
+* by AT&T Corp. *
+* *
+* Information and Software Systems Research *
+* AT&T Research, Florham Park NJ *
+**********************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#ifdef MSWIN32
+#include <io.h>
+#endif
+#endif
+
+#undef HAVE_LIBGD
+
+#include "textpara.h"
+#include "gvplugin_render.h"
+#include "gv_ps.h"
+#include "const.h"
+#include "macros.h"
+#ifdef HAVE_LIBGD
+#include "gd.h"
+#endif
+
+extern void cat_libfile(FILE * ofp, char **arglib, char **stdlib);
+extern void epsf_define(FILE * of);
+extern char *ps_string(char *ins, int latin);
+
+typedef enum { FORMAT_PS, FORMAT_PS2, } format_type;
+
+static box DBB;
+static int isLatin1;
+static char setupLatin1;
+
+static char *last_fontname;
+static double last_fontsize;
+static gvcolor_t last_color;
+
+static void psgen_begin_job(GVJ_t * job)
+{
+ last_fontname = NULL;
+ last_fontsize = 0.0;
+ last_color.u.HSV[0] = 0;
+ last_color.u.HSV[1] = 0;
+ last_color.u.HSV[2] = 0;
+
+ fprintf(job->output_file, "%%!PS-Adobe-2.0\n");
+ fprintf(job->output_file, "%%%%Creator: %s version %s (%s)\n",
+ job->common->info[0], job->common->info[1], job->common->info[2]);
+ fprintf(job->output_file, "%%%%For: %s\n", job->common->user);
+}
+
+static void psgen_end_job(GVJ_t * job)
+{
+ fprintf(job->output_file, "%%%%Trailer\n");
+ fprintf(job->output_file, "%%%%Pages: %d\n", job->common->viewNum);
+ if (job->common->show_boxes == NULL)
+ fprintf(job->output_file, "%%%%BoundingBox: %d %d %d %d\n",
+ DBB.LL.x, DBB.LL.y, DBB.UR.x, DBB.UR.y);
+ fprintf(job->output_file, "end\nrestore\n");
+ fprintf(job->output_file, "%%%%EOF\n");
+}
+
+static void psgen_begin_graph(GVJ_t * job, char *graphname)
+{
+ setupLatin1 = FALSE;
+
+ if (job->common->viewNum == 0) {
+ fprintf(job->output_file, "%%%%Title: %s\n", graphname);
+ fprintf(job->output_file, "%%%%Pages: (atend)\n");
+ if (job->common->show_boxes == NULL)
+ fprintf(job->output_file, "%%%%BoundingBox: (atend)\n");
+ fprintf(job->output_file, "%%%%EndComments\nsave\n");
+ cat_libfile(job->output_file, job->common->lib, gv_ps_txt);
+ epsf_define(job->output_file);
+ }
+#ifdef FIXME
+ isLatin1 = (GD_charset(g) == CHAR_LATIN1);
+ if (isLatin1 && !setupLatin1) {
+ fprintf(job->output_file, "setupLatin1\n"); /* as defined in ps header */
+ setupLatin1 = TRUE;
+ }
+#endif
+#ifdef FIXME
+ /* Set base URL for relative links (for Distiller >= 3.0) */
+ if (((s = agget(g, "href")) && s[0])
+ || ((s = agget(g, "URL")) && s[0])) {
+ fprintf(job->output_file, "[ {Catalog} << /URI << /Base (%s) >> >>\n"
+ "/PUT pdfmark\n", s);
+ }
+#endif
+}
+
+static void psgen_end_graph(GVJ_t * job)
+{
+#if 0
+ if (EPSF_contents) {
+ dtclose(EPSF_contents);
+ EPSF_contents = 0;
+ N_EPSF_files = 0;
+ }
+ onetime = FALSE;
+#endif
+}
+
+static void psgen_begin_layer(GVJ_t * job, char *layername, int layerNum, int numLayers)
+{
+ fprintf(job->output_file, "%d %d setlayer\n", layerNum, numLayers);
+}
+
+static point sub_points(point p0, point p1)
+{
+ p0.x -= p1.x;
+ p0.y -= p1.y;
+ return p0;
+}
+
+static void psgen_begin_page(GVJ_t * job)
+{
+ point sz;
+ box PB, pbr;
+
+ BF2B(job->boundingBox, PB);
+
+ sz = sub_points(PB.UR, PB.LL);
+ if (job->rotation) {
+ pbr.LL.x = PB.LL.y;
+ pbr.LL.y = PB.LL.x;
+ pbr.UR.x = PB.UR.y;
+ pbr.UR.y = PB.UR.x;
+ }
+ else {
+ pbr = PB;
+ }
+
+ if (job->common->viewNum == 0)
+ DBB = pbr;
+ else
+ EXPANDBB(DBB, pbr);
+
+ fprintf(job->output_file, "%%%%Page: %d %d\n",
+ job->common->viewNum + 1, job->common->viewNum + 1);
+ if (job->common->show_boxes == NULL)
+ fprintf(job->output_file, "%%%%PageBoundingBox: %d %d %d %d\n",
+ pbr.LL.x, pbr.LL.y, pbr.UR.x, pbr.UR.y);
+ fprintf(job->output_file, "%%%%PageOrientation: %s\n",
+ (job->rotation ? "Landscape" : "Portrait"));
+ if (job->common->show_boxes == NULL)
+ fprintf(job->output_file, "gsave\n%d %d %d %d boxprim clip newpath\n",
+ pbr.LL.x, pbr.LL.y, pbr.UR.x, pbr.UR.y);
+ fprintf(job->output_file, "gsave %g set_scale\n", job->zoom);
+ if (job->rotation) {
+ fprintf(job->output_file, "%d rotate\n", job->rotation);
+ fprintf(job->output_file, "%g %g translate\n",
+ -job->pageBox.LL.x + pbr.LL.y / job->zoom,
+ -job->pageBox.UR.y - pbr.LL.x / job->zoom);
+ }
+ else
+ fprintf(job->output_file, "%g %g translate\n",
+ -job->pageBox.LL.x + pbr.LL.x / job->zoom,
+ -job->pageBox.LL.y + pbr.LL.y / job->zoom);
+
+#if 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(job->output_file, "[ /CropBox [%d %d %d %d] /PAGES pdfmark\n",
+ PB.LL.x, PB.LL.y, PB.UR.x, PB.UR.y);
+ }
+#endif
+}
+
+static void psgen_end_page(GVJ_t * job)
+{
+ if (job->common->show_boxes)
+ cat_libfile(job->output_file, NULL, job->common->show_boxes + 1);
+ /* 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(job->output_file, "endpage\nshowpage\ngrestore\n");
+ fprintf(job->output_file, "%%%%PageTrailer\n");
+ fprintf(job->output_file, "%%%%EndPage: %d\n", job->common->viewNum);
+}
+
+static void psgen_begin_cluster(GVJ_t * job, char *clustername, long id)
+{
+ fprintf(job->output_file, "%% %s\n", clustername);
+
+#if 0
+ /* Embed information for Distiller to generate hyperlinked PDF */
+ map_begin_cluster(g);
+#endif
+}
+
+static void psgen_begin_node(GVJ_t * job, char *nodename, long id)
+{
+#if 0
+ /* Embed information for Distiller to generate hyperlinked PDF */
+ map_begin_node(n);
+#endif
+}
+
+static void
+psgen_begin_edge(GVJ_t * job, char *tailname, bool directed,
+ char *headname, long id)
+{
+#if 0
+ /* Embed information for Distiller, so it can generate hyperactive PDF */
+ map_begin_edge(e);
+#endif
+}
+
+static void
+psgen_begin_anchor(GVJ_t * job, char *href, char *tooltip, char *target)
+{
+}
+
+static void psgen_end_anchor(GVJ_t * job)
+{
+}
+
+static void
+ps_set_pen_style(GVJ_t *job)
+{
+#if 0
+ double penwidth = job->style->penwidth * job->zoom;
+ char *p, *line, **s = job->rawstyle;
+
+ fprintf(stderr,"%g setlinewidth\n", penwidth);
+
+ while (s && (p = line = *s++)) {
+ if (streq(line, "setlinewidth"))
+ continue;
+ while (*p)
+ p++;
+ p++;
+ while (*p) {
+ fprintf(stderr,"%s ", p);
+ while (*p)
+ p++;
+ p++;
+ }
+// if (streq(line, "invis"))
+// job->style->width = 0;
+ fprintf(stderr, "%s\n", line);
+ }
+#endif
+}
+
+static void ps_set_color(GVJ_t *job, gvcolor_t *color)
+{
+ if (color) {
+ if ( last_color.u.HSV[0] != color->u.HSV[0]
+ || last_color.u.HSV[1] != color->u.HSV[1]
+ || last_color.u.HSV[2] != color->u.HSV[2]) {
+ fprintf(job->output_file, "%.3f %.3f %.3f %scolor\n",
+ color->u.HSV[0], color->u.HSV[1], color->u.HSV[2], job->objname);
+ last_color.u.HSV[0] = color->u.HSV[0];
+ last_color.u.HSV[1] = color->u.HSV[1];
+ last_color.u.HSV[2] = color->u.HSV[2];
+ }
+ }
+}
+
+static void psgen_textpara(GVJ_t * job, pointf p, textpara_t * para)
+{
+ double adj, sz;
+ char *str;
+
+ ps_set_color(job, &(job->style->pencolor));
+ if (para->fontname) {
+ sz = para->fontsize;
+ if (sz != last_fontsize
+ || last_fontname == NULL
+ || strcmp(para->fontname, last_fontname) != 0) {
+ fprintf(job->output_file, "%.2f /%s set_font\n", sz, para->fontname);
+ last_fontsize = sz;
+ last_fontname = para->fontname;
+ }
+ }
+
+ str = ps_string(para->str,isLatin1);
+ if (para->xshow) {
+ switch (para->just) {
+ case 'l':
+ break;
+ case 'r':
+ p.x -= para->width;
+ break;
+ default:
+ case 'n':
+ p.x -= para->width / 2;
+ break;
+ }
+ fprintf(job->output_file, "%g %g moveto\n%s\n[%s]\nxshow\n",
+ p.x, p.y, str, para->xshow);
+ } else {
+ switch (para->just) {
+ case 'l':
+ adj = 0.0;
+ break;
+ case 'r':
+ adj = -1.0;
+ break;
+ default:
+ case 'n':
+ adj = -0.5;
+ break;
+ }
+ fprintf(job->output_file, "%g %g moveto %1f %.1f %s alignedtext\n",
+ p.x, p.y, para->width, adj, str);
+ }
+}
+
+static void psgen_ellipse(GVJ_t * job, pointf * A, int filled)
+{
+ /* A[] contains 2 points: the center and corner. */
+
+ if (filled) {
+ ps_set_color(job, &(job->style->fillcolor));
+ fprintf(job->output_file, "%g %g %g %g ellipse_path fill\n",
+ A[0].x, A[0].y, fabs(A[1].x - A[0].x), fabs(A[1].y - A[0].y));
+ }
+ ps_set_pen_style(job);
+ ps_set_color(job, &(job->style->pencolor));
+ fprintf(job->output_file, "%g %g %g %g ellipse_path stroke\n",
+ A[0].x, A[0].y, fabs(A[1].x - A[0].x), fabs(A[1].y - A[0].y));
+}
+
+static void
+psgen_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start,
+ int arrow_at_end, int filled)
+{
+ int j;
+
+ if (filled) {
+ ps_set_color(job, &(job->style->fillcolor));
+ fprintf(job->output_file, "newpath %g %g moveto\n", A[0].x, A[0].y);
+ for (j = 1; j < n; j += 3)
+ fprintf(job->output_file, "%g %g %g %g %g %g 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(job->output_file, "closepath fill\n");
+ }
+ ps_set_pen_style(job);
+ ps_set_color(job, &(job->style->pencolor));
+ fprintf(job->output_file, "newpath %g %g moveto\n", A[0].x, A[0].y);
+ for (j = 1; j < n; j += 3)
+ fprintf(job->output_file, "%g %g %g %g %g %g 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(job->output_file, "stroke\n");
+}
+
+static void psgen_polygon(GVJ_t * job, pointf * A, int n, int filled)
+{
+ int j;
+
+ if (filled) {
+ ps_set_color(job, &(job->style->fillcolor));
+ fprintf(job->output_file, "newpath %g %g moveto\n", A[0].x, A[0].y);
+ for (j = 1; j < n; j++)
+ fprintf(job->output_file, "%g %g lineto\n", A[j].x, A[j].y);
+ fprintf(job->output_file, "closepath fill\n");
+ }
+ ps_set_pen_style(job);
+ ps_set_color(job, &(job->style->pencolor));
+ fprintf(job->output_file, "newpath %g %g moveto\n", A[0].x, A[0].y);
+ for (j = 1; j < n; j++)
+ fprintf(job->output_file, "%g %g lineto\n", A[j].x, A[j].y);
+ fprintf(job->output_file, "closepath stroke\n");
+}
+
+static void psgen_polyline(GVJ_t * job, pointf * A, int n)
+{
+ int j;
+
+ ps_set_pen_style(job);
+ ps_set_color(job, &(job->style->pencolor));
+ fprintf(job->output_file, "newpath %g %g moveto\n", A[0].x, A[0].y);
+ for (j = 1; j < n; j++)
+ fprintf(job->output_file, "%g %g lineto\n", A[j].x, A[j].y);
+ fprintf(job->output_file, "stroke\n");
+}
+
+static void psgen_comment(GVJ_t * job, char *str)
+{
+ fprintf(job->output_file, "%% %s\n", str);
+}
+
+static void ps_freeimage_gd (void *data)
+{
+#ifdef HAVE_LIBGD
+ gdImageDestroy((gdImagePtr)data);
+#endif
+}
+
+static void ps_freeimage_ps (void *data)
+{
+#if 0
+ free (data);
+#endif
+}
+
+#ifdef HAVE_LIBGD
+static void writePSBitmap (GVJ_t *job, gdImagePtr im, boxf b)
+{
+ int x, y, px;
+
+ fprintf(job->output_file, "gsave\n");
+
+ /* this sets the position of the image */
+ fprintf(job->output_file, "%g %g translate %% lower-left coordinate\n", b.LL.x, b.LL.y);
+
+ /* this sets the rendered size to fit the box */
+ fprintf(job->output_file,"%g %g scale\n", b.UR.x - b.LL.x, b.UR.y - b.LL.y);
+
+ /* xsize ysize bits-per-sample [matrix] */
+ fprintf(job->output_file, "%d %d 8 [%d 0 0 %d 0 %d]\n", im->sx, im->sy,
+ im->sx, -(im->sy), im->sy);
+
+ fprintf(job->output_file, "{<\n");
+ for (y = 0; y < im->sy; y++) {
+ for (x = 0; x < im->sx; x++) {
+ if (im->trueColor) {
+ px = gdImageTrueColorPixel(im, x, y);
+ fprintf(job->output_file, "%02x%02x%02x",
+ gdTrueColorGetRed(px),
+ gdTrueColorGetGreen(px),
+ gdTrueColorGetBlue(px));
+ }
+ else {
+ px = gdImagePalettePixel(im, x, y);
+ fprintf(job->output_file, "%02x%02x%02x",
+ im->red[px],
+ im->green[px],
+ im->blue[px]);
+ }
+ }
+ fprintf(job->output_file, "\n");
+ }
+
+ fprintf(job->output_file, ">}\n");
+ fprintf(job->output_file, "false 3 colorimage\n");
+
+ fprintf(job->output_file, "grestore\n");
+
+}
+#endif
+
+
+/* ps_usershape:
+ * Images for postscript are complicated by the old epsf shape, as
+ * well as user-defined shapes using postscript code.
+ * If the name is custom, we look for the image stored in the
+ * current node's shapefile attribute.
+ * Else we see if name is a user-defined postscript function
+ * Else we assume name is the name of the image. This occurs when
+ * the image is part of an html label.
+ */
+static void
+psgen_usershape(GVJ_t * job, usershape_t *us, boxf b, bool filled)
+{
+ int j;
+#ifdef HAVE_LIBGD
+ gdImagePtr gd_img = NULL;
+#endif
+#ifdef XXX_PS
+ ps_image_t *ps_img = NULL;
+#endif
+ point offset;
+
+ if (!us->f) {
+#ifdef XXX_PS
+ if (find_user_shape(us->name)) {
+ if (filled) {
+ ps_begin_context();
+ ps_set_color(S[SP].fillcolor);
+ fprintf(job->output_file, "[ ");
+ for (j = 0; j < n; j++)
+ fprintf(job->output_file, "%d %d ", A[j].x, A[j].y);
+ fprintf(job->output_file, "%d %d ", A[0].x, A[0].y);
+ fprintf(job->output_file, "] %d true %s\n", n, us->name);
+ ps_end_context();
+ }
+ fprintf(job->output_file, "[ ");
+ for (j = 0; j < n; j++)
+ fprintf(job->output_file, "%d %d ", A[j].x, A[j].y);
+ fprintf(job->output_file, "%d %d ", A[0].x, A[0].y);
+ fprintf(job->output_file, "] %d false %s\n", n, us->name);
+ }
+ else { /* name not find by find_ser_shape */ }
+#endif
+ return;
+ }
+
+ if (us->data) {
+ if (us->datafree == ps_freeimage_gd) {
+#ifdef HAVE_LIBGD
+ gd_img = (gdImagePtr)(us->data); /* use cached data */
+#endif
+ }
+ else if (us->datafree == ps_freeimage_ps) {
+#ifdef XXX_PS
+ ps_img = (ps_image_t *)(us->data); /* use cached data */
+#endif
+ }
+ else {
+ us->datafree(us->data); /* free incompatible cache data */
+ us->data = NULL;
+ }
+ }
+
+#ifdef HAVE_LIBGD
+#ifdef XXX_PS
+ if (!ps_img && !gd_img) { /* read file into cache */
+#else
+ if (!gd_img) { /* read file into cache */
+#endif
+#else
+#ifdef XXX_PS
+ if (!ps_img) { /* read file into cache */
+#else
+ if (false) { /* nothing to do */
+#endif
+#endif
+ fseek(us->f, 0, SEEK_SET);
+ switch (us->type) {
+#ifdef HAVE_LIBGD
+#ifdef HAVE_GD_PNG
+ case FT_PNG:
+ gd_img = gdImageCreateFromPng(us->f);
+ break;
+#endif
+#ifdef HAVE_GD_GIF
+ case FT_GIF:
+ gd_img = gdImageCreateFromGif(us->f);
+ break;
+#endif
+#ifdef HAVE_GD_JPEG
+ case FT_JPEG:
+ gd_img = gdImageCreateFromJpeg(us->f);
+ break;
+#endif
+#endif
+#ifdef XXX_PS
+ case FT_PS:
+ case FT_EPS:
+ ps_img = ps_usershape_to_image(us->name);
+ break;
+#endif
+ default:
+ break;
+ }
+#ifdef HAVE_LIBGD
+ if (gd_img) {
+ us->data = (void*)gd_img;
+ us->datafree = ps_freeimage_gd;
+ }
+#endif
+#ifdef XXX_PS
+ if (ps_img) {
+ us->data = (void*)ps_img;
+ us->datafree = ps_freeimage_ps;
+ }
+#endif
+ }
+
+#ifdef XXX_PS
+ if (ps_img) {
+ ps_begin_context();
+ offset.x = -ps_img->origin.x - (ps_img->size.x) / 2;
+ offset.y = -ps_img->origin.y - (ps_img->size.y) / 2;
+ fprintf(job->output_file, "%d %d translate newpath\n",
+ ND_coord_i(Curnode).x + offset.x,
+ ND_coord_i(Curnode).y + offset.y);
+ if (ps_img->must_inline)
+ epsf_emit_body(ps_img, job->output_file);
+ else
+ fprintf(job->output_file, "user_shape_%d\n", ps_img->macro_id);
+ ps_end_context();
+ return;
+ }
+#endif
+
+#ifdef HAVE_LIBGD
+ if (gd_img) {
+ writePSBitmap (job, gd_img, b);
+ return;
+ }
+#endif
+
+#if 0
+/* FIXME */
+ /* some other type of image */
+ agerr(AGERR, "usershape %s is not supported in PostScript output\n", us->name);
+#endif
+}
+
+static gvrender_engine_t psgen_engine = {
+ psgen_begin_job,
+ psgen_end_job,
+ psgen_begin_graph,
+ psgen_end_graph,
+ psgen_begin_layer,
+ 0, /* psgen_end_layer */
+ psgen_begin_page,
+ psgen_end_page,
+ psgen_begin_cluster,
+ 0, /* psgen_end_cluster */
+ 0, /* psgen_begin_nodes */
+ 0, /* psgen_end_nodes */
+ 0, /* psgen_begin_edges */
+ 0, /* psgen_end_edges */
+ psgen_begin_node,
+ 0, /* psgen_end_node */
+ psgen_begin_edge,
+ 0, /* psgen_end_edge */
+ psgen_begin_anchor,
+ psgen_end_anchor,
+ psgen_textpara,
+ 0, /* psgen_resolve_color */
+ psgen_ellipse,
+ psgen_polygon,
+ psgen_bezier,
+ psgen_polyline,
+ psgen_comment,
+ psgen_usershape
+};
+
+static gvrender_features_t psgen_features = {
+ GVRENDER_DOES_MULTIGRAPH_OUTPUT_FILES
+ | GVRENDER_DOES_TRANSFORM,
+ DEFAULT_PRINT_MARGIN, /* default margin - points */
+ {72.,72.}, /* default dpi */
+ NULL, /* knowncolors */
+ 0, /* sizeof knowncolors */
+ HSV_DOUBLE, /* color_type */
+};
+
+gvplugin_installed_t gvrender_core_ps_types[] = {
+ {FORMAT_PS, "ps", -2, &psgen_engine, &psgen_features},
+ {FORMAT_PS2, "ps2", -2, &psgen_engine, &psgen_features},
+ {0, NULL, 0, NULL, NULL}
+};
--- /dev/null
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+* This software is part of the graphviz package *
+* http://www.graphviz.org/ *
+* *
+* Copyright (c) 1994-2004 AT&T Corp. *
+* and is licensed under the *
+* Common Public License, Version 1.0 *
+* by AT&T Corp. *
+* *
+* Information and Software Systems Research *
+* AT&T Research, Florham Park NJ *
+**********************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+
+#ifdef HAVE_LIBZ
+#include <zlib.h>
+#endif
+#ifdef MSWIN32
+#include <io.h>
+#endif
+
+#include "macros.h"
+#include "const.h"
+#include "types.h"
+
+#include "gvplugin_render.h"
+
+typedef enum { FORMAT_SVG, FORMAT_SVGZ, } format_type;
+
+extern char *xml_string(char *str);
+
+static char *GraphName;
+
+/* SVG dash array */
+static char *sdarray = "5,2";
+/* SVG dot array */
+static char *sdotarray = "1,5";
+
+static void svggen_fputs(GVJ_t * job, char *s)
+{
+ int len;
+
+ len = strlen(s);
+ switch (job->render.id) {
+ case FORMAT_SVGZ:
+#ifdef HAVE_LIBZ
+ gzwrite((gzFile *) (job->output_file), s, (unsigned) len);
+#endif
+ break;
+ case FORMAT_SVG:
+ fwrite(s, sizeof(char), (unsigned) len, job->output_file);
+ break;
+ }
+}
+
+/* svggen_printf:
+ * Note that this function is unsafe due to the fixed buffer size.
+ * It should only be used when the caller is sure the input will not
+ * overflow the buffer. In particular, it should be avoided for
+ * input coming from users. Also, if vsnprintf is available, the
+ * code should check for return values to use it safely.
+ */
+static void svggen_printf(GVJ_t * job, const char *format, ...)
+{
+ char buf[BUFSIZ];
+ va_list argp;
+
+ va_start(argp, format);
+#ifdef HAVE_VSNPRINTF
+ (void) vsnprintf(buf, sizeof(buf), format, argp);
+#else
+ (void) vsprintf(buf, format, argp);
+#endif
+ va_end(argp);
+
+ svggen_fputs(job, buf);
+}
+
+static void svggen_bzptarray(GVJ_t * job, pointf * A, int n)
+{
+ int i;
+ char c;
+
+ c = 'M'; /* first point */
+ for (i = 0; i < n; i++) {
+ svggen_printf(job, "%c%g,%g", c, A[i].x, A[i].y);
+ if (i == 0)
+ c = 'C'; /* second point */
+ else
+ c = ' '; /* remaining points */
+ }
+}
+
+static void svggen_print_color(GVJ_t * job, gvcolor_t color)
+{
+ static char buf[SMALLBUF];
+
+ switch (color.type) {
+ case COLOR_STRING:
+ svggen_fputs(job, color.u.string);
+ break;
+ case RGBA_BYTE:
+ sprintf(buf, "#%02x%02x%02x",
+ color.u.rgba[0], color.u.rgba[1], color.u.rgba[2]);
+ svggen_fputs(job, buf);
+ break;
+ default:
+ assert(0); /* internal error */
+ }
+}
+
+static void svggen_font(GVJ_t * job)
+{
+ gvstyle_t *style = job->style;
+ char buf[BUFSIZ];
+ int needstyle = 0;
+
+ strcpy(buf, " style=\"");
+ if (strcasecmp(style->fontfam, DEFAULT_FONTNAME)) {
+ sprintf(buf + strlen(buf), "font-family:%s;", style->fontfam);
+ needstyle++;
+ }
+ if (style->fontsz != DEFAULT_FONTSIZE) {
+ sprintf(buf + strlen(buf), "font-size:%.2f;", (style->fontsz));
+ needstyle++;
+ }
+ switch (style->pencolor.type) {
+ case COLOR_STRING:
+ if (strcasecmp(style->pencolor.u.string, "black")) {
+ sprintf(buf + strlen(buf), "fill:%s;",
+ style->pencolor.u.string);
+ needstyle++;
+ }
+ break;
+ case RGBA_BYTE:
+ sprintf(buf + strlen(buf), "fill:#%02x%02x%02x;",
+ style->pencolor.u.rgba[0],
+ style->pencolor.u.rgba[1], style->pencolor.u.rgba[2]);
+ needstyle++;
+ break;
+ default:
+ assert(0); /* internal error */
+ }
+ if (needstyle) {
+ strcat(buf, "\"");
+ svggen_fputs(job, buf);
+ }
+}
+
+
+static void svggen_grstyle(GVJ_t * job, int filled)
+{
+ gvstyle_t *style = job->style;
+
+ svggen_fputs(job, " style=\"fill:");
+ if (filled)
+ svggen_print_color(job, style->fillcolor);
+ else
+ svggen_fputs(job, "none");
+ svggen_fputs(job, ";stroke:");
+ svggen_print_color(job, style->pencolor);
+ if (style->penwidth != PENWIDTH_NORMAL)
+ svggen_printf(job, ";stroke-width:%g", style->penwidth);
+ if (style->pen == PEN_DASHED) {
+ svggen_printf(job, ";stroke-dasharray:%s", sdarray);
+ } else if (style->pen == PEN_DOTTED) {
+ svggen_printf(job, ";stroke-dasharray:%s", sdotarray);
+ }
+ svggen_fputs(job, ";\"");
+}
+
+static void svggen_comment(GVJ_t * job, char *str)
+{
+ svggen_fputs(job, "<!-- ");
+ svggen_fputs(job, xml_string(str));
+ svggen_fputs(job, " -->\n");
+}
+
+static void svggen_begin_job(GVJ_t * job)
+{
+#if HAVE_LIBZ
+ int fd;
+#endif
+
+ switch (job->render.id) {
+ case FORMAT_SVGZ:
+#if HAVE_LIBZ
+ /* open dup so can gzclose independent of FILE close */
+ fd = dup(fileno(job->output_file));
+#ifdef HAVE_SETMODE
+#ifdef O_BINARY
+ /*
+ * Windows will do \n -> \r\n translations on
+ * stdout unless told otherwise.
+ */
+ setmode(fd, O_BINARY);
+#endif
+#endif
+
+ job->output_file = (FILE *) (gzdopen(fd, "wb"));
+ if (!job->output_file) {
+ (job->common->errorfn) ("Error opening compressed output file\n");
+ exit(1);
+ }
+ break;
+#else
+ (job->gvc->errorfn) ("No libz support.\n");
+ exit(1);
+#endif
+ case FORMAT_SVG:
+ break;
+ }
+
+ svggen_fputs(job,
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
+ svggen_fputs(job,
+ "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.0//EN\"\n");
+ svggen_fputs(job,
+ " \"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\"");
+
+ /* This is to work around a bug in the SVG 1.0 DTD */
+ svggen_fputs(job,
+ " [\n <!ATTLIST svg xmlns:xlink CDATA #FIXED \"http://www.w3.org/1999/xlink\">\n]");
+
+ svggen_fputs(job, ">\n<!-- Generated by ");
+ svggen_fputs(job, xml_string(job->common->info[0]));
+ svggen_fputs(job, " version ");
+ svggen_fputs(job, xml_string(job->common->info[1]));
+ svggen_fputs(job, " (");
+ svggen_fputs(job, xml_string(job->common->info[2]));
+ svggen_fputs(job, ")\n For user: ");
+ svggen_fputs(job, xml_string(job->common->user));
+ svggen_fputs(job, " -->\n");
+}
+
+static void svggen_begin_graph(GVJ_t * job, char *graphname)
+{
+ GraphName = graphname;
+
+ svggen_fputs(job, "<!-- Title: ");
+ if (GraphName[0])
+ svggen_fputs(job, xml_string(GraphName));
+ svggen_printf(job, " Pages: %d -->\n", job->pagesArraySize.x * job->pagesArraySize.y);
+
+ if (ROUND(job->dpi.x) == POINTS_PER_INCH && ROUND(job->dpi.y) == POINTS_PER_INCH) {
+ svggen_printf(job, "<svg width=\"%dpt\" height=\"%dpt\"\n",
+ job->width, job->height);
+ }
+ else {
+ svggen_printf(job, "<svg width=\"%dpx\" height=\"%dpx\"\n",
+ ROUND(job->dpi.x * job->width / POINTS_PER_INCH),
+ ROUND(job->dpi.y * job->height / POINTS_PER_INCH));
+ }
+ /* establish absolute units in points */
+ svggen_printf(job, " viewBox = \"%d %d %d %d\"\n", 0, 0, job->width, job->height);
+ /* namespace of svg */
+ svggen_fputs(job, " xmlns=\"http://www.w3.org/2000/svg\"");
+ /* namespace of xlink */
+ svggen_fputs(job, " xmlns:xlink=\"http://www.w3.org/1999/xlink\"");
+ svggen_fputs(job, ">\n");
+}
+
+static void svggen_end_graph(GVJ_t * job)
+{
+ svggen_fputs(job, "</svg>\n");
+ switch (job->render.id) {
+ case FORMAT_SVGZ:
+#ifdef HAVE_LIBZ
+ gzclose((gzFile *) (job->output_file));
+ break;
+#else
+ (job->gvc->errorfn) ("No libz support\n");
+ exit(1);
+#endif
+ case FORMAT_SVG:
+ break;
+ }
+}
+
+static void svggen_begin_layer(GVJ_t * job, char *layername, int layerNum, int numLayers)
+{
+ svggen_fputs(job, "<g id=\"");
+ svggen_fputs(job, xml_string(layername));
+ svggen_fputs(job, "\" class=\"layer\">\n");
+}
+
+static void svggen_end_layer(GVJ_t * job)
+{
+ svggen_fputs(job, "</g>\n");
+}
+
+static void svggen_begin_page(GVJ_t * job)
+{
+ /* its really just a page of the graph, but its still a graph,
+ * and it is the entire graph if we're not currently paging */
+ svggen_printf(job, "<g id=\"graph%d\" class=\"graph\"", job->common->viewNum + 1);
+ if (job->zoom != 1.0)
+ svggen_printf(job, " transform = \"scale(%f)\"\n", job->zoom);
+ if (job->rotation) {
+ svggen_printf(job, "transform=\"rotate(-90 %g %g)\" ", 0, 0);
+ }
+ /* default style */
+ svggen_fputs(job, " style=\"font-family:");
+ svggen_fputs(job, job->style->fontfam);
+ svggen_printf(job, ";font-size:%.2f;\">\n", job->style->fontsz);
+ if (GraphName[0])
+ svggen_fputs(job, xml_string(GraphName));
+ svggen_fputs(job, "<title>");
+ svggen_fputs(job, xml_string(GraphName));
+ svggen_fputs(job, "</title>\n");
+}
+
+static void svggen_end_page(GVJ_t * job)
+{
+ svggen_fputs(job, "</g>\n");
+}
+
+static void svggen_begin_cluster(GVJ_t * job, char *clustername, long id)
+{
+ svggen_printf(job, "<g id=\"cluster%ld\" class=\"cluster\">", id);
+ svggen_fputs(job, "<title>");
+ svggen_fputs(job, xml_string(clustername));
+ svggen_fputs(job, "</title>\n");
+}
+
+static void svggen_end_cluster(GVJ_t * job)
+{
+ svggen_fputs(job, "</g>\n");
+}
+
+static void svggen_begin_node(GVJ_t * job, char *nodename, long id)
+{
+ svggen_printf(job, "<g id=\"node%ld\" class=\"node\">", id);
+ svggen_fputs(job, "<title>");
+ svggen_fputs(job, xml_string(nodename));
+ svggen_fputs(job, "</title>\n");
+}
+
+static void svggen_end_node(GVJ_t * job)
+{
+ svggen_fputs(job, "</g>\n");
+}
+
+static void
+svggen_begin_edge(GVJ_t * job, char *tailname, bool directed,
+ char *headname, long id)
+{
+ char *edgeop;
+
+ svggen_printf(job, "<g id=\"edge%ld\" class=\"edge\">", id);
+ if (directed)
+ edgeop = "->";
+ else
+ edgeop = "--";
+ svggen_fputs(job, "<title>");
+ svggen_fputs(job, xml_string(tailname));
+ svggen_fputs(job, edgeop);
+ /* can't do this in single svggen_printf because
+ * xml_string's buffer gets reused. */
+ svggen_fputs(job, xml_string(headname));
+ svggen_fputs(job, "</title>\n");
+}
+
+static void svggen_end_edge(GVJ_t * job)
+{
+ svggen_fputs(job, "</g>\n");
+}
+
+static void
+svggen_begin_anchor(GVJ_t * job, char *href, char *tooltip, char *target)
+{
+ svggen_fputs(job, "<a xlink:href=\"");
+ svggen_fputs(job, xml_string(href));
+ if (tooltip && tooltip[0]) {
+ svggen_fputs(job, "\" xlink:title=\"");
+ svggen_fputs(job, xml_string(tooltip));
+ }
+ if (target && target[0]) {
+ svggen_fputs(job, "\" target=\"");
+ svggen_fputs(job, xml_string(target));
+ }
+ svggen_fputs(job, "\">\n");
+}
+
+static void svggen_end_anchor(GVJ_t * job)
+{
+ svggen_fputs(job, "</a>\n");
+}
+
+static void svggen_textpara(GVJ_t * job, pointf p, textpara_t * para)
+{
+ char *anchor;
+
+ switch (para->just) {
+ case 'l':
+ anchor = "start";
+ break;
+ case 'r':
+ anchor = "end";
+ break;
+ default:
+ case 'n':
+ anchor = "middle";
+ break;
+ }
+
+ svggen_printf(job, "<text text-anchor=\"%s\" ", anchor);
+ svggen_printf(job, "x=\"%g\" y=\"%g\"", p.x, p.y);
+ svggen_font(job);
+ svggen_fputs(job, ">");
+ svggen_fputs(job, xml_string(para->str));
+ svggen_fputs(job, "</text>\n");
+}
+
+static void svggen_ellipse(GVJ_t * job, pointf * A, int filled)
+{
+ /* A[] contains 2 points: the center and corner. */
+ svggen_fputs(job, "<ellipse");
+ svggen_grstyle(job, filled);
+ svggen_printf(job, " cx=\"%g\" cy=\"%g\"", A[0].x, A[0].y);
+ svggen_printf(job, " rx=\"%g\" ry=\"%g\"",
+ fabs(A[1].x - A[0].x), fabs(A[1].y - A[0].y));
+ svggen_fputs(job, "/>\n");
+}
+
+static void
+svggen_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start,
+ int arrow_at_end, int filled)
+{
+ svggen_fputs(job, "<path");
+ svggen_grstyle(job, filled);
+ svggen_fputs(job, " d=\"");
+ svggen_bzptarray(job, A, n);
+ svggen_fputs(job, "\"/>\n");
+}
+
+static void svggen_polygon(GVJ_t * job, pointf * A, int n, int filled)
+{
+ int i;
+
+ svggen_fputs(job, "<polygon");
+ svggen_grstyle(job, filled);
+ svggen_fputs(job, " points=\"");
+ for (i = 0; i < n; i++)
+ svggen_printf(job, "%g,%g ", A[i].x, A[i].y);
+ svggen_printf(job, "%g,%g", A[0].x, A[0].y); /* because Adobe SVG is broken */
+ svggen_fputs(job, "\"/>\n");
+}
+
+static void svggen_polyline(GVJ_t * job, pointf * A, int n)
+{
+ int i;
+
+ svggen_fputs(job, "<polyline");
+ svggen_grstyle(job, 0);
+ svggen_fputs(job, " points=\"");
+ for (i = 0; i < n; i++)
+ svggen_printf(job, "%g,%g ", A[i].x, A[i].y);
+ svggen_fputs(job, "\"/>\n");
+}
+
+#if 0
+static void
+svggen_user_shape(GVJ_t * job, char *name, pointf * A, int n, int filled)
+{
+ int i;
+ point p;
+ pointf pf;
+ point sz;
+ char *imagefile;
+ int minx, miny;
+
+ if (job->style->pen == PEN_NONE) {
+ /* its invisible, don't draw */
+ return;
+ }
+ imagefile = agget(gvc->n, "shapefile");
+ if (imagefile == 0) {
+ svggen_polygon(gvc, A, n, filled);
+ return;
+ }
+ pf.x = ND_coord_i(gvc->n).x - ND_lw_i(gvc->n);
+ pf.y = ND_coord_i(gvc->n).y + ND_ht_i(gvc->n) / 2;
+ p = svgpt(gvc, pf);
+ sz.x = ROUND((ND_lw_i(gvc->n) + ND_rw_i(gvc->n)));
+ sz.y = ROUND(ND_ht_i(gvc->n));
+
+ svggen_fputs(job, "<clipPath id=\"mypath");
+ svggen_fputs(job, name);
+ svggen_fputs(job, gvc->n->name);
+ svggen_fputs(job, "\">\n<polygon points=\"");
+ minx = svgpt(gvc, A[0]).x;
+ miny = svgpt(gvc, A[0]).y;
+ for (i = 0; i < n; i++) {
+ p = svgpt(gvc, A[i]);
+
+ if (p.x < minx)
+ minx = p.x;
+ if (p.y < miny)
+ miny = p.y;
+
+ svggen_printf(job, "%d,%d ", p.x, p.y);
+ }
+ /* because Adobe SVG is broken (?) */
+ p = svgpt(gvc, A[0]);
+ svggen_printf(job, "%d,%d ", p.x, p.y);
+ svggen_fputs(job, "\"/>\n</clipPath>\n<image xlink:href=\"");
+ svggen_fputs(job, imagefile);
+ svggen_printf(job,
+ "\" width=\"%dpx\" height=\"%dpx\" preserveAspectRatio=\"xMidYMid meet\" x=\"%d\" y=\"%d\" clip-path=\"url(#mypath",
+ sz.x, sz.y, minx, miny);
+ svggen_fputs(job, name);
+ svggen_fputs(job, gvc->n->name);
+ svggen_fputs(job, ")\"/>\n");
+}
+#endif
+
+/* color names from http://www.w3.org/TR/SVG/types.html */
+/* NB. List must be LANG_C sorted */
+static char *svggen_knowncolors[] = {
+ "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure",
+ "beige", "bisque", "black", "blanchedalmond", "blue",
+ "blueviolet", "brown", "burlywood",
+ "cadetblue", "chartreuse", "chocolate", "coral",
+ "cornflowerblue", "cornsilk", "crimson", "cyan",
+ "darkblue", "darkcyan", "darkgoldenrod", "darkgray",
+ "darkgreen", "darkgrey", "darkkhaki", "darkmagenta",
+ "darkolivegreen", "darkorange", "darkorchid", "darkred",
+ "darksalmon", "darkseagreen", "darkslateblue", "darkslategray",
+ "darkslategrey", "darkturquoise", "darkviolet", "deeppink",
+ "deepskyblue", "dimgray", "dimgrey", "dodgerblue",
+ "firebrick", "floralwhite", "forestgreen", "fuchsia",
+ "gainsboro", "ghostwhite", "gold", "goldenrod", "gray",
+ "green", "greenyellow", "grey",
+ "honeydew", "hotpink", "indianred",
+ "indigo", "ivory", "khaki",
+ "lavender", "lavenderblush", "lawngreen", "lemonchiffon",
+ "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow",
+ "lightgray", "lightgreen", "lightgrey", "lightpink",
+ "lightsalmon", "lightseagreen", "lightskyblue",
+ "lightslategray", "lightslategrey", "lightsteelblue",
+ "lightyellow", "lime", "limegreen", "linen",
+ "magenta", "maroon", "mediumaquamarine", "mediumblue",
+ "mediumorchid", "mediumpurple", "mediumseagreen",
+ "mediumslateblue", "mediumspringgreen", "mediumturquoise",
+ "mediumvioletred", "midnightblue", "mintcream",
+ "mistyrose", "moccasin",
+ "navajowhite", "navy", "oldlace",
+ "olive", "olivedrab", "orange", "orangered", "orchid",
+ "palegoldenrod", "palegreen", "paleturquoise",
+ "palevioletred", "papayawhip", "peachpuff", "peru", "pink",
+ "plum", "powderblue", "purple",
+ "red", "rosybrown", "royalblue",
+ "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell",
+ "sienna", "silver", "skyblue", "slateblue", "slategray",
+ "slategrey", "snow", "springgreen", "steelblue",
+ "tan", "teal", "thistle", "tomato", "turquoise",
+ "violet",
+ "wheat", "white", "whitesmoke",
+ "yellow", "yellowgreen"
+};
+
+gvrender_engine_t svggen_engine = {
+ svggen_begin_job,
+ 0, /* svggen_end_job */
+ svggen_begin_graph,
+ svggen_end_graph,
+ svggen_begin_layer,
+ svggen_end_layer,
+ svggen_begin_page,
+ svggen_end_page,
+ svggen_begin_cluster,
+ svggen_end_cluster,
+ 0, /* svggen_begin_nodes */
+ 0, /* svggen_end_nodes */
+ 0, /* svggen_begin_edges */
+ 0, /* svggen_end_edges */
+ svggen_begin_node,
+ svggen_end_node,
+ svggen_begin_edge,
+ svggen_end_edge,
+ svggen_begin_anchor,
+ svggen_end_anchor,
+ svggen_textpara,
+ 0, /* svggen_resolve_color */
+ svggen_ellipse,
+ svggen_polygon,
+ svggen_bezier,
+ svggen_polyline,
+ svggen_comment,
+ 0 /* FIXME svggen_user_shape, */
+};
+
+gvrender_features_t svggen_features = {
+ GVRENDER_DOES_TRUECOLOR
+ | GVRENDER_Y_GOES_DOWN, /* flags*/
+ DEFAULT_EMBED_MARGIN, /* default margin - points */
+ {72.,72.}, /* default dpi */
+ svggen_knowncolors, /* knowncolors */
+ sizeof(svggen_knowncolors) / sizeof(char *), /* sizeof knowncolors */
+ RGBA_BYTE, /* color_type */
+};
+
+gvplugin_installed_t gvrender_svggen_types[] = {
+ {FORMAT_SVG, "svg", -1, &svggen_engine, &svggen_features},
+#if HAVE_LIBZ
+ {FORMAT_SVGZ, "svgz", -1, &svggen_engine, &svggen_features},
+#endif
+ {0, NULL, 0, NULL, NULL}
+};
#include "gvplugin.h"
extern gvplugin_installed_t gvrender_gd_types;
-extern gvplugin_installed_t gvrender_ps_types;
+extern gvplugin_installed_t gvrender_gd_ps_types;
extern gvplugin_installed_t gvtextlayout_gd_types;
static gvplugin_api_t apis[] = {
{API_render, &gvrender_gd_types},
- {API_render, &gvrender_ps_types},
+ {API_render, &gvrender_gd_ps_types},
{API_textlayout, &gvtextlayout_gd_types},
{(api_t)0, NULL},
};
--- /dev/null
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+* This software is part of the graphviz package *
+* http://www.graphviz.org/ *
+* *
+* Copyright (c) 1994-2004 AT&T Corp. *
+* and is licensed under the *
+* Common Public License, Version 1.0 *
+* by AT&T Corp. *
+* *
+* Information and Software Systems Research *
+* AT&T Research, Florham Park NJ *
+**********************************************************/
+
+#include "gvplugin.h"
+
+extern gvplugin_installed_t gvrender_pangogen_types;
+extern gvplugin_installed_t gvtextlayout_pangogen_types;
+
+static gvplugin_api_t apis[] = {
+ {API_render, &gvrender_pangogen_types},
+ {API_textlayout, &gvtextlayout_pangogen_types},
+ {(api_t)0, NULL},
+};
+
+gvplugin_library_t gvplugin_pango_LTX_library = { "pango", apis };
--- /dev/null
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+* This software is part of the graphviz package *
+* http://www.graphviz.org/ *
+* *
+* Copyright (c) 1994-2004 AT&T Corp. *
+* and is licensed under the *
+* Common Public License, Version 1.0 *
+* by AT&T Corp. *
+* *
+* Information and Software Systems Research *
+* AT&T Research, Florham Park NJ *
+**********************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#include <math.h>
+#include <assert.h>
+
+#if defined(HAVE_FENV_H) && defined(HAVE_FESETENV) && defined(HAVE_FEGETENV) && defined(HAVE_FEENABLEEXCEPT)
+
+/* _GNU_SOURCE is needed (supposedly) for the feenableexcept
+ * prototype to be defined in fenv.h on GNU systems.
+ * Presumably it will do no harm on other systems.
+ */
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+/* We are not supposed to need __USE_GNU, but I can't see
+ * how to get the prototype for fedisableexcept from
+ * /usr/include/fenv.h without it.
+ */
+#ifndef __USE_GNU
+#define __USE_GNU
+#endif
+
+# include <fenv.h>
+#elif HAVE_FPU_CONTROL_H
+# include <fpu_control.h>
+#elif HAVE_SYS_FPU_H
+# include <sys/fpu.h>
+#endif
+
+#include "gvplugin_render.h"
+
+#ifdef HAVE_PANGOCAIRO
+#include <pango/pangocairo.h>
+
+typedef enum { FORMAT_PNG,
+ FORMAT_PS,
+ FORMAT_PDF,
+ FORMAT_SVG,
+ FORMAT_GTK,
+ FORMAT_XLIB,
+ FORMAT_XCB,
+ FORMAT_SDL,
+ FORMAT_GLITZ,
+ FORMAT_QUARTZ,
+ FORMAT_WIN32,
+ } format_type;
+
+#define ARRAY_SIZE(A) (sizeof(A)/sizeof(A[0]))
+
+static double dashed[] = {10.};
+static int dashed_len = ARRAY_SIZE(dashed);
+
+static double dotted[] = {2., 10.};
+static int dotted_len = ARRAY_SIZE(dotted);
+
+#ifdef CAIRO_HAS_PNG_SURFACE
+#include <cairo-png.h>
+#endif
+
+#ifdef CAIRO_HAS_PS_SURFACE
+#include <cairo-ps.h>
+#endif
+
+#ifdef CAIRO_HAS_PDF_SURFACE
+#include <cairo-pdf.h>
+#endif
+
+#ifdef CAIRO_HAS_SVG_SURFACE
+#include <cairo-svg.h>
+#endif
+
+#ifdef CAIRO_HAS_XLIB_SURFACE
+#include <cairo-xlib.h>
+#endif
+
+#if defined(HAVE_FENV_H) && defined(HAVE_FESETENV) && defined(HAVE_FEGETENV) && defined(HAVE_FEENABLEEXCEPT)
+/* place to save fp environment temporarily */
+static fenv_t fenv; /* FIXME - not thread safe */
+#endif
+
+static void cairogen_set_color(cairo_t * cr, gvcolor_t * color)
+{
+ cairo_set_source_rgba(cr, color->u.RGBA[0], color->u.RGBA[1],
+ color->u.RGBA[2], color->u.RGBA[3]);
+}
+
+static cairo_status_t
+writer (void *closure, const unsigned char *data, unsigned int length)
+{
+ if (length == fwrite(data, 1, length, (FILE *)closure))
+ return CAIRO_STATUS_SUCCESS;
+ return CAIRO_STATUS_WRITE_ERROR;
+}
+
+static cairo_status_t
+reader (void *closure, unsigned char *data, unsigned int length)
+{
+ if (length == fread(data, 1, length, (FILE *)closure)
+ || feof((FILE *)closure))
+ return CAIRO_STATUS_SUCCESS;
+ return CAIRO_STATUS_READ_ERROR;
+}
+
+static void cairogen_begin_graph(GVJ_t * job, char *graphname)
+{
+ cairo_t *cr;
+ cairo_surface_t *surface;
+ double width, height;
+
+#if defined(HAVE_FENV_H) && defined(HAVE_FESETENV) && defined(HAVE_FEGETENV) && defined(HAVE_FEDISABLEEXCEPT)
+ /* cairo generates FE_INVALID and other exceptions we
+ * want to ignore for now. Save the current fenv and
+ * set one just for cairo.
+ * The fenv is restored in cairogen_end_graph */
+ fegetenv(&fenv);
+ fedisableexcept(FE_ALL_EXCEPT);
+#endif
+
+ cr = (cairo_t *) job->surface; /* might be NULL */
+
+ /* device size with margins all around */
+ width = job->width + 2 * ROUND(job->margin.x * job->dpi.x / POINTS_PER_INCH);
+ height = job->height + 2 * ROUND(job->margin.y * job->dpi.y / POINTS_PER_INCH);
+
+ switch (job->render.id) {
+#ifdef CAIRO_HAS_PNG_FUNCTIONS
+ case FORMAT_PNG:
+ if (!cr) {
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+ width, height);
+ cr = cairo_create(surface);
+ cairo_surface_destroy (surface);
+ }
+ break;
+#endif
+#ifdef CAIRO_HAS_PS_SURFACE
+ case FORMAT_PS:
+ if (!cr) {
+ surface = cairo_ps_surface_create_for_stream (writer,
+ job->output_file, width, height);
+ cr = cairo_create(surface);
+ cairo_surface_destroy (surface);
+ }
+ break;
+#endif
+#ifdef CAIRO_HAS_PDF_SURFACE
+ case FORMAT_PDF:
+ if (!cr) {
+ surface = cairo_pdf_surface_create_for_stream (writer,
+ job->output_file, width, height);
+ cr = cairo_create(surface);
+ cairo_surface_destroy (surface);
+ }
+ break;
+#endif
+#ifdef CAIRO_HAS_SVG_SURFACE
+ case FORMAT_SVG:
+ if (!cr) {
+ surface = cairo_svg_surface_create_for_stream (writer,
+ job->output_file, width, height);
+ cr = cairo_create(surface);
+ cairo_surface_destroy (surface);
+ }
+ break;
+#endif
+#ifdef CAIRO_HAS_XLIB_SURFACE
+ case FORMAT_GTK:
+ break;
+ case FORMAT_XLIB:
+ break;
+#endif
+#ifdef CAIRO_HAS_XCB_SURFACE
+ case FORMAT_XCB:
+ break;
+#endif
+#ifdef CAIRO_HAS_SDL_SURFACE
+ case FORMAT_SDL:
+ break;
+#endif
+#ifdef CAIRO_HAS_GLITZ_SURFACE
+ case FORMAT_GLITZ:
+ break;
+#endif
+ default:
+ break;
+ }
+ job->surface = (void *) cr;
+}
+
+static void cairogen_end_graph(GVJ_t * job)
+{
+ cairo_t *cr = (cairo_t *) job->surface;
+ cairo_surface_t *surface;
+
+ switch (job->render.id) {
+#ifdef CAIRO_HAS_PNG_FUNCTIONS
+ case FORMAT_PNG:
+ surface = cairo_get_target(cr);
+ cairo_surface_write_to_png_stream(surface, writer, job->output_file);
+#endif
+ default:
+ break;
+ }
+ if (!job->external_surface)
+ cairo_destroy(cr);
+
+#if defined HAVE_FENV_H && defined HAVE_FESETENV && defined HAVE_FEGETENV && defined(HAVE_FEENABLEEXCEPT)
+ /* Restore FP environment */
+ fesetenv(&fenv);
+#endif
+}
+
+static void cairogen_begin_page(GVJ_t * job)
+{
+ cairo_t *cr = (cairo_t *) job->surface;
+
+ cairo_save(cr);
+ cairo_scale(cr, job->zoom * job->dpi.x / POINTS_PER_INCH,
+ job->zoom * job->dpi.y / POINTS_PER_INCH);
+ if (job->rotation) {
+ cairo_rotate(cr, job->rotation * -M_PI / 180.);
+ cairo_translate(cr,
+ -job->margin.x / job->zoom - job->pageBox.UR.x,
+ job->margin.y / job->zoom + job->pageBox.UR.y);
+ }
+ else
+ cairo_translate(cr,
+ job->margin.x / job->zoom - job->pageBox.LL.x,
+ job->margin.y / job->zoom + job->pageBox.UR.y);
+}
+
+static void cairogen_end_page(GVJ_t * job)
+{
+ cairo_t *cr = (cairo_t *) job->surface;
+
+ cairo_restore(cr);
+
+ switch (job->render.id) {
+#ifdef CAIRO_HAS_PS_SURFACE
+ case FORMAT_PS:
+ cairo_show_page(cr);
+ break;
+#endif
+#ifdef CAIRO_HAS_PDF_SURFACE
+ case FORMAT_PDF:
+ cairo_show_page(cr);
+ break;
+#endif
+#ifdef CAIRO_HAS_SVG_SURFACE
+ case FORMAT_SVG:
+ cairo_show_page(cr);
+ break;
+#endif
+ default:
+ break;
+ }
+}
+
+static void cairogen_textpara(GVJ_t * job, pointf p, textpara_t * para)
+{
+ gvstyle_t *style = job->style;
+ cairo_t *cr = (cairo_t *) job->surface;
+ pointf offset;
+ PangoLayout *layout = (PangoLayout*)(para->layout);
+ PangoLayoutIter* iter;
+
+ cairo_set_dash (cr, dashed, 0, 0.0); /* clear any dashing */
+ cairogen_set_color(cr, &(style->pencolor));
+
+ switch (para->just) {
+ case 'r':
+ offset.x = para->width;
+ break;
+ case 'l':
+ offset.x = 0.0;
+ break;
+ case 'n':
+ default:
+ offset.x = para->width / 2.0;
+ break;
+ }
+ /* offset to baseline */
+ iter = pango_layout_get_iter (layout);
+ offset.y = pango_layout_iter_get_baseline (iter) / PANGO_SCALE;
+
+ cairo_move_to (cr, p.x-offset.x, -p.y-offset.y);
+ pango_cairo_show_layout(cr, layout);
+}
+
+static void cairogen_ellipse(GVJ_t * job, pointf * A, int filled)
+{
+ gvstyle_t *style = job->style;
+ cairo_t *cr = (cairo_t *) job->surface;
+ cairo_matrix_t matrix;
+ double rx, ry;
+
+ if (style->pen == PEN_DASHED) {
+ cairo_set_dash (cr, dashed, dashed_len, 0.0);
+ } else if (style->pen == PEN_DOTTED) {
+ cairo_set_dash (cr, dotted, dotted_len, 0.0);
+ } else {
+ cairo_set_dash (cr, dashed, 0, 0.0);
+ }
+ cairo_set_line_width (cr, style->penwidth * job->compscale.x);
+
+ cairo_get_matrix(cr, &matrix);
+ cairo_translate(cr, A[0].x, -A[0].y);
+
+ rx = fabs(A[1].x - A[0].x);
+ ry = fabs(A[1].y - A[0].y);
+ cairo_scale(cr, 1, ry / rx);
+ cairo_move_to(cr, rx, 0);
+ cairo_arc(cr, 0, 0, rx, 0, 2 * M_PI);
+ cairo_close_path(cr);
+
+ cairo_set_matrix(cr, &matrix);
+
+ if (filled) {
+ cairogen_set_color(cr, &(style->fillcolor));
+ cairo_fill_preserve(cr);
+ }
+ cairogen_set_color(cr, &(style->pencolor));
+ cairo_stroke(cr);
+}
+
+static void
+cairogen_polygon(GVJ_t * job, pointf * A, int n, int filled)
+{
+ gvstyle_t *style = job->style;
+ cairo_t *cr = (cairo_t *) job->surface;
+ int i;
+
+ if (style->pen == PEN_DASHED) {
+ cairo_set_dash (cr, dashed, dashed_len, 0.0);
+ } else if (style->pen == PEN_DOTTED) {
+ cairo_set_dash (cr, dotted, dotted_len, 0.0);
+ } else {
+ cairo_set_dash (cr, dashed, 0, 0.0);
+ }
+ cairo_set_line_width (cr, style->penwidth * job->compscale.x);
+ cairo_move_to(cr, A[0].x, -A[0].y);
+ for (i = 1; i < n; i++)
+ cairo_line_to(cr, A[i].x, -A[i].y);
+ cairo_close_path(cr);
+ if (filled) {
+ cairogen_set_color(cr, &(style->fillcolor));
+ cairo_fill_preserve(cr);
+ }
+ cairogen_set_color(cr, &(style->pencolor));
+ cairo_stroke(cr);
+}
+
+static void
+cairogen_bezier(GVJ_t * job, pointf * A, int n, int arrow_at_start,
+ int arrow_at_end, int filled)
+{
+ gvstyle_t *style = job->style;
+ cairo_t *cr = (cairo_t *) job->surface;
+ int i;
+
+ if (style->pen == PEN_DASHED) {
+ cairo_set_dash (cr, dashed, dashed_len, 0.0);
+ } else if (style->pen == PEN_DOTTED) {
+ cairo_set_dash (cr, dotted, dotted_len, 0.0);
+ } else {
+ cairo_set_dash (cr, dashed, 0, 0.0);
+ }
+ cairo_set_line_width (cr, style->penwidth * job->compscale.x);
+ cairo_move_to(cr, A[0].x, -A[0].y);
+ for (i = 1; i < n; i += 3)
+ cairo_curve_to(cr, A[i].x, -A[i].y, A[i + 1].x, -A[i + 1].y,
+ A[i + 2].x, -A[i + 2].y);
+ cairogen_set_color(cr, &(style->pencolor));
+ cairo_stroke(cr);
+}
+
+static void
+cairogen_polyline(GVJ_t * job, pointf * A, int n)
+{
+ gvstyle_t *style = job->style;
+ cairo_t *cr = (cairo_t *) job->surface;
+ int i;
+
+ if (style->pen == PEN_DASHED) {
+ cairo_set_dash (cr, dashed, dashed_len, 0.0);
+ } else if (style->pen == PEN_DOTTED) {
+ cairo_set_dash (cr, dotted, dotted_len, 0.0);
+ } else {
+ cairo_set_dash (cr, dashed, 0, 0.0);
+ }
+ cairo_set_line_width (cr, style->penwidth * job->compscale.x);
+ cairo_move_to(cr, A[0].x, -A[0].y);
+ for (i = 1; i < n; i++)
+ cairo_line_to(cr, A[i].x, -A[i].y);
+ cairogen_set_color(cr, &(style->pencolor));
+ cairo_stroke(cr);
+}
+
+static void cairogen_freeimage(void *data)
+{
+ cairo_destroy((cairo_t*)data);
+}
+
+static void
+cairogen_usershape(GVJ_t * job, usershape_t *us, boxf b, bool filled)
+{
+ cairo_t *cr = (cairo_t *) job->surface; /* target context */
+ cairo_surface_t *surface1 = NULL; /* source surface */
+
+ if (us->data) {
+ if (us->datafree == cairogen_freeimage)
+ surface1 = (cairo_surface_t*)(us->data); /* use cached data */
+ else {
+ us->datafree(us->data); /* free incompatible cache data */
+ us->data = NULL;
+ }
+ }
+ if (!surface1) { /* read file into cache */
+ fseek(us->f, 0, SEEK_SET);
+ switch (us->type) {
+#ifdef CAIRO_HAS_PNG_FUNCTIONS
+ case FT_PNG:
+ surface1 = cairo_image_surface_create_from_png_stream(reader, us->f);
+ cairo_surface_reference(surface1);
+ break;
+#endif
+ default:
+ surface1 = NULL;
+ }
+ if (surface1) {
+ us->data = (void*)surface1;
+ us->datafree = cairogen_freeimage;
+ }
+ }
+ if (surface1) {
+ cairo_save(cr);
+ cairo_translate(cr, ROUND(b.LL.x), ROUND(-b.UR.y));
+ cairo_scale(cr, (b.UR.x - b.LL.x) / us->w,
+ (b.UR.y - b.LL.y) / us->h);
+ cairo_set_source_surface (cr, surface1, 0, 0);
+ cairo_paint (cr);
+ cairo_restore(cr);
+ }
+}
+
+static gvrender_engine_t cairogen_engine = {
+ 0, /* cairogen_begin_job */
+ 0, /* cairogen_end_job */
+ cairogen_begin_graph,
+ cairogen_end_graph,
+ 0, /* cairogen_begin_layer */
+ 0, /* cairogen_end_layer */
+ cairogen_begin_page,
+ cairogen_end_page,
+ 0, /* cairogen_begin_cluster */
+ 0, /* cairogen_end_cluster */
+ 0, /* cairogen_begin_nodes */
+ 0, /* cairogen_end_nodes */
+ 0, /* cairogen_begin_edges */
+ 0, /* cairogen_end_edges */
+ 0, /* cairogen_begin_node */
+ 0, /* cairogen_end_node */
+ 0, /* cairogen_begin_edge */
+ 0, /* cairogen_end_edge */
+ 0, /* cairogen_begin_anchor */
+ 0, /* cairogen_end_anchor */
+ cairogen_textpara,
+ 0, /* cairogen_resolve_color */
+ cairogen_ellipse,
+ cairogen_polygon,
+ cairogen_bezier,
+ cairogen_polyline,
+ 0, /* cairogen_comment */
+ cairogen_usershape
+};
+
+static gvrender_features_t cairogen_features = {
+ GVRENDER_DOES_TRUECOLOR
+ | GVRENDER_DOES_TRANSFORM, /* flags */
+ 0, /* default margin - points */
+ {96.,96.}, /* default dpi */
+ 0, /* knowncolors */
+ 0, /* sizeof knowncolors */
+ RGBA_DOUBLE, /* color_type */
+ 0, /* device */
+};
+
+static gvrender_features_t cairogen_features_ps = {
+ GVRENDER_DOES_TRUECOLOR
+ | GVRENDER_DOES_TRANSFORM, /* flags */
+ 36, /* default margin - points */
+ {72.,72.}, /* postscript 72 dpi */
+ 0, /* knowncolors */
+ 0, /* sizeof knowncolors */
+ RGBA_DOUBLE, /* color_type */
+ 0, /* device */
+};
+
+#if 0
+static gvrender_features_t cairogen_features_x = {
+ GVRENDER_DOES_TRUECOLOR
+ | GVRENDER_DOES_TRANSFORM
+ | GVRENDER_X11_EVENTS, /* flags */
+ 0, /* default margin - points */
+ {96.,96.}, /* default dpi */
+ 0, /* knowncolors */
+ 0, /* sizeof knowncolors */
+ RGBA_DOUBLE, /* color_type */
+ "xlib", /* device */
+};
+
+static gvrender_features_t cairogen_features_gtk = {
+ GVRENDER_DOES_TRUECOLOR
+ | GVRENDER_DOES_TRANSFORM
+ | GVRENDER_X11_EVENTS, /* flags */
+ 0, /* default margin - points */
+ {96.,96.}, /* default dpi */
+ 0, /* knowncolors */
+ 0, /* sizeof knowncolors */
+ RGBA_DOUBLE, /* color_type */
+ "gtk", /* device */
+};
+#endif
+#endif
+
+gvplugin_installed_t gvrender_pangogen_types[] = {
+#ifdef HAVE_PANGOCAIRO
+#ifdef CAIRO_HAS_PNG_FUNCTIONS
+ {FORMAT_PNG, "png", 10, &cairogen_engine, &cairogen_features},
+#endif
+#ifdef CAIRO_HAS_PS_SURFACE
+ {FORMAT_PS, "ps", -10, &cairogen_engine, &cairogen_features_ps},
+#endif
+#ifdef CAIRO_HAS_PDF_SURFACE
+ {FORMAT_PDF, "pdf", 0, &cairogen_engine, &cairogen_features_ps},
+#endif
+#ifdef CAIRO_HAS_SVG_SURFACE
+ {FORMAT_SVG, "svg", -10, &cairogen_engine, &cairogen_features_ps},
+#endif
+#if 0
+#ifdef CAIRO_HAS_XLIB_SURFACE
+ {FORMAT_GTK, "gtk", 0, &cairogen_engine, &cairogen_features_gtk},
+ {FORMAT_XLIB, "xlib", 0, &cairogen_engine, &cairogen_features_x},
+#endif
+#ifdef CAIRO_HAS_XCB_SURFACE
+ {FORMAT_XCB, "xcb", 0, &cairogen_engine, &cairogen_features_x},
+#endif
+#ifdef CAIRO_HAS_SDL_SURFACE
+ {FORMAT_SDL, "sdl", 0, &cairogen_engine, &cairogen_features_x},
+#endif
+#ifdef CAIRO_HAS_GLITZ_SURFACE
+ {FORMAT_GLITZ, "glitz", 0, &cairogen_engine, &cairogen_features_x},
+#endif
+#ifdef CAIRO_HAS_QUARTZ_SURFACE
+ {FORMAT_QUARTZ, "quartz", 0, &cairogen_engine, &cairogen_features_x},
+#endif
+#ifdef CAIRO_HAS_WIN32_SURFACE
+ {FORMAT_WIN32, "win32", 0, &cairogen_engine, &cairogen_features_x},
+#endif
+#endif
+#endif
+ {0, NULL, 0, NULL, NULL}
+};
--- /dev/null
+/* $Id$ $Revision$ */
+/* vim:set shiftwidth=4 ts=8: */
+
+/**********************************************************
+* This software is part of the graphviz package *
+* http://www.graphviz.org/ *
+* *
+* Copyright (c) 1994-2004 AT&T Corp. *
+* and is licensed under the *
+* Common Public License, Version 1.0 *
+* by AT&T Corp. *
+* *
+* Information and Software Systems Research *
+* AT&T Research, Florham Park NJ *
+**********************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include "gvplugin_textlayout.h"
+
+#ifdef HAVE_PANGOCAIRO
+#include <pango/pangocairo.h>
+
+static void pangocairo_free_layout (void *layout)
+{
+ g_object_unref((PangoLayout*)layout);
+}
+
+static void pangocairo_textlayout(textpara_t * para, char **fontpath)
+{
+ static PangoFontMap *fontmap;
+ static PangoContext *context;
+ PangoFontDescription *desc;
+ PangoLayout *layout;
+ PangoRectangle ink_rect, logical_rect;
+#if ENABLE_PANGO_XSHOW
+ PangoRectangle char_rect;
+ PangoLayoutIter* iter;
+#endif
+#ifdef ENABLE_PANGO_MARKUP
+ PangoAttrList *attrs;
+ GError *error = NULL;
+#endif
+ char *text;
+
+ if (!fontmap)
+ fontmap = pango_cairo_font_map_get_default();
+ pango_cairo_font_map_set_resolution(PANGO_CAIRO_FONT_MAP(fontmap), (double)POINTS_PER_INCH);
+ if (!context)
+ context = pango_cairo_font_map_create_context (PANGO_CAIRO_FONT_MAP(fontmap));
+
+ desc = pango_font_description_new();
+ pango_font_description_set_family (desc, para->fontname);
+ pango_font_description_set_size (desc, (gint)(para->fontsize * PANGO_SCALE));
+
+#ifdef ENABLE_PANGO_MARKUP
+ if (! pango_parse_markup (para->str, -1, 0, &attrs, &text, NULL, &error))
+ die(error->message);
+#else
+ text = para->str;
+#endif
+
+ layout = pango_layout_new (context);
+ para->layout = (void *)layout; /* layout free with textpara - see labels.c */
+ para->free_layout = pangocairo_free_layout; /* function for freeing pango layout */
+
+ pango_layout_set_text (layout, text, -1);
+ pango_layout_set_font_description (layout, desc);
+#ifdef ENABLE_PANGO_MARKUP
+ pango_layout_set_attributes (layout, attrs);
+#endif
+
+ pango_layout_get_extents (layout, &ink_rect, &logical_rect);
+ para->width = (double)logical_rect.width / PANGO_SCALE;
+ para->height = (double)logical_rect.height / PANGO_SCALE;
+
+ /* determine position of each character in the layout */
+ para->xshow = NULL;
+#ifdef ENABLE_PANGO_XSHOW
+/* FIXME - unfinished code */
+ iter = pango_layout_get_iter (layout);
+ do {
+ pango_layout_iter_get_char_extents (iter, &char_rect);
+ char_rect.x /= PANGO_SCALE;
+ char_rect.y /= PANGO_SCALE;
+ } while (pango_layout_iter_next_char (iter));
+ pango_layout_iter_free (iter);
+#endif
+
+ pango_font_description_free (desc);
+
+ *fontpath = "[pango]";
+}
+
+static gvtextlayout_engine_t pangocairo_textlayout_engine = {
+ pangocairo_textlayout,
+};
+#endif
+
+gvplugin_installed_t gvtextlayout_pangocairogen_types[] = {
+#ifdef HAVE_PANGOCAIRO
+ {0, "textlayout", 10, &pangocairo_textlayout_engine, NULL},
+#endif
+ {0, NULL, 0, NULL, NULL}
+};