]> granicus.if.org Git - graphviz/commitdiff
- add a "core" plugin for renderers that are always available
authorellson <devnull@localhost>
Sat, 10 Jun 2006 01:44:28 +0000 (01:44 +0000)
committerellson <devnull@localhost>
Sat, 10 Jun 2006 01:44:28 +0000 (01:44 +0000)
- rename *pangocairo.c to *pango.c

plugin/core/gvplugin_core.c [new file with mode: 0644]
plugin/core/gvrender_core_ps.c [new file with mode: 0644]
plugin/core/gvrender_core_svg.c [new file with mode: 0644]
plugin/gd/gvplugin_gd.c
plugin/pango/gvplugin_pango.c [new file with mode: 0644]
plugin/pango/gvrender_pango.c [new file with mode: 0644]
plugin/pango/gvtextlayout_pango.c [new file with mode: 0644]

diff --git a/plugin/core/gvplugin_core.c b/plugin/core/gvplugin_core.c
new file mode 100644 (file)
index 0000000..f53e31e
--- /dev/null
@@ -0,0 +1,28 @@
+/* $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 };
diff --git a/plugin/core/gvrender_core_ps.c b/plugin/core/gvrender_core_ps.c
new file mode 100644 (file)
index 0000000..491cbde
--- /dev/null
@@ -0,0 +1,670 @@
+/* $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}
+};
diff --git a/plugin/core/gvrender_core_svg.c b/plugin/core/gvrender_core_svg.c
new file mode 100644 (file)
index 0000000..ca2c3c7
--- /dev/null
@@ -0,0 +1,623 @@
+/* $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 = "&#45;&gt;";
+    else
+       edgeop = "&#45;&#45;";
+    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}
+};
index b68754b43b18efb087741c3626ffae897f892d93..9a89b7f474409955c8b921607bcce5115a2fb2bb 100644 (file)
 #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},
 };
diff --git a/plugin/pango/gvplugin_pango.c b/plugin/pango/gvplugin_pango.c
new file mode 100644 (file)
index 0000000..b266405
--- /dev/null
@@ -0,0 +1,28 @@
+/* $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 };
diff --git a/plugin/pango/gvrender_pango.c b/plugin/pango/gvrender_pango.c
new file mode 100644 (file)
index 0000000..dba9cb0
--- /dev/null
@@ -0,0 +1,589 @@
+/* $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}
+};
diff --git a/plugin/pango/gvtextlayout_pango.c b/plugin/pango/gvtextlayout_pango.c
new file mode 100644 (file)
index 0000000..04fcf74
--- /dev/null
@@ -0,0 +1,108 @@
+/* $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}
+};