/* -*- Mode: C; c-basic-offset: 4 -*- */
/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998 Alexander Larsson
 *
 * cgm.c -- CGM plugin for dia
 * Copyright (C) 1999-2000 James Henstridge.
 * Copyright (C) 2000 Henk Jan Priester.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <math.h>
#include <glib.h>
#include <errno.h>

#include "intl.h"
#include "message.h"
#include "geometry.h"
#include "filter.h"
#include "plug-ins.h"
#include "diarenderer.h"
#include "dia_image.h"

#include <gdk/gdk.h>

#define CGM_TYPE_RENDERER           (cgm_renderer_get_type ())
#define CGM_RENDERER(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), CGM_TYPE_RENDERER, CgmRenderer))
#define CGM_RENDERER_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST ((klass), CGM_TYPE_RENDERER, CgmRendererClass))
#define CGM_IS_RENDERER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CGM_TYPE_RENDERER))
#define CGM_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CGM_TYPE_RENDERER, CgmRendererClass))

GType cgm_renderer_get_type (void) G_GNUC_CONST;

typedef struct _CgmRenderer CgmRenderer;
typedef struct _CgmRendererClass CgmRendererClass;

struct _CgmRendererClass
{
  DiaRendererClass parent_class;
};

static const gchar *dia_version_string = "Dia-" VERSION;
#define IS_ODD(n) (n & 0x01)

/* --- routines to write various quantities to the CGM stream. --- */
/* signed integers are stored in two complement this is common on Unix */
static void
write_uint16(FILE *fp, guint16 n)
{
    putc( (n & 0xff00) >> 8, fp);
    putc( n & 0xff, fp);
}

static void
write_int16(FILE *fp, gint16 n)
{
    write_uint16(fp, (guint16)n);
}

static void
write_uint32(FILE *fp, guint32 n)
{
    putc((n >> 24) & 0xff, fp);
    putc((n >> 16) & 0xff, fp); 
    putc((n >> 8) & 0xff, fp); 
    putc(n & 0xff, fp); 
}

static void
write_colour(FILE *fp, Color *c)
{
    putc((int)(c->red   * 255), fp);
    putc((int)(c->green * 255), fp);
    putc((int)(c->blue  * 255), fp);
}

#define REALSIZE 4
/* 32 bit fixed point real number */
/* stored as 16 bit signed (SI) number and a 16 bit unsigned (UI) for */
/* the fraction */
/* value is SI + UI / 2^16 */
static void
write_real(FILE *fp, double x)
{
    guint32  n;

    if ( x < 0 )
    {
        gint32   wholepart;
        guint16  fraction;

        wholepart = (gint32)x;
   
        fraction = (guint16)((x - wholepart) * -65536);
        if ( fraction > 0 )
        {
            wholepart--;
            fraction = (guint16)(65536 - fraction);
        }
        n = (guint32)(wholepart << 16) | fraction;
    }
    else
        n = (guint32) (x * (1 << 16));
    write_uint32(fp, n);
}

static void
write_elhead(FILE *fp, int el_class, int el_id, int nparams)
{
    guint16 head;

    head = (el_class & 0x0f) << 12;
    head += (el_id & 0x7f) << 5;

    if (nparams >= 31) {
	/* use long header format */
	head += 31;
	write_uint16(fp, head);
	write_int16(fp, (gint16)nparams);
    } else {
	/* use short header format */
	head += nparams & 0x1f;
	write_uint16(fp, head);
    }
}


/* --- font stuff --- */

static gchar *fontlist;
static gint fontlistlen;
static GHashTable *fonthash;
#define FONT_NUM(font) GPOINTER_TO_INT(g_hash_table_lookup(fonthash, \
     dia_font_get_family(font)))

static void
init_fonts(void)
{
        /* PANGO FIXME: this is probably broken to some extent now */
    static gboolean alreadyrun = FALSE;
    GString *str;
    gint i;
    PangoContext *context;
    PangoFontFamily **families;
    int n_families;
    const char *familyname;
    
    if (alreadyrun) return;
    alreadyrun = TRUE;

    context = gdk_pango_context_get();
    pango_context_list_families(context,&families,&n_families);
    
    fonthash = g_hash_table_new(g_str_hash, g_str_equal);
    str = g_string_new(NULL);
    for (i = 0; i < n_families; ++i) {
        familyname = pango_font_family_get_name(families[i]);
        
        g_string_append_c(str, strlen(familyname));
        g_string_append(str, familyname);
        g_hash_table_insert(fonthash, (gpointer)familyname,
                            GINT_TO_POINTER(i+1));
    }
    fontlist = str->str;
    fontlistlen = str->len;
    g_string_free(str, FALSE);
}

/* --- CGM line attributes --- */
typedef struct _LineAttrCGM
{
    int         cap;
    int         join;
    int         style;
    real        width;
    Color       color;

} LineAttrCGM;

/* --- CGM File/Edge attributes --- */
typedef struct _FillEdgeAttrCGM
{

   int          fill_style;          /* Fill style */
   Color        fill_color;          /* Fill color */

   int          edgevis;             /* Edge visibility */
   int          cap;                 /* Edge cap */
   int          join;                /* Edge join */
   int          style;               /* Edge style */
   real         width;               /* Edge width */ 
   Color        color;               /* Edge color */

} FillEdgeAttrCGM;


/* --- CGM Text attributes --- */
typedef struct _TextAttrCGM
{
   int          font_num;
   real         font_height;
   Color        color;

} TextAttrCGM;


/* --- the renderer --- */

struct _CgmRenderer
{
    DiaRenderer parent_instance;

    FILE *file;

    DiaFont *font;

    real y0, y1; 

    LineAttrCGM  lcurrent, linfile;

    FillEdgeAttrCGM fcurrent, finfile;

    TextAttrCGM    tcurrent, tinfile;

};



static void begin_render(DiaRenderer *self);
static void end_render(DiaRenderer *self);
static void set_linewidth(DiaRenderer *self, real linewidth);
static void set_linecaps(DiaRenderer *self, LineCaps mode);
static void set_linejoin(DiaRenderer *self, LineJoin mode);
static void set_linestyle(DiaRenderer *self, LineStyle mode);
static void set_dashlength(DiaRenderer *self, real length);
static void set_fillstyle(DiaRenderer *self, FillStyle mode);
static void set_font(DiaRenderer *self, DiaFont *font, real height);
static void draw_line(DiaRenderer *self, 
		      Point *start, Point *end, 
		      Color *line_colour);
static void draw_polyline(DiaRenderer *self, 
			  Point *points, int num_points, 
			  Color *line_colour);
static void draw_polygon(DiaRenderer *self, 
			 Point *points, int num_points, 
			 Color *line_colour);
static void fill_polygon(DiaRenderer *self, 
			 Point *points, int num_points, 
			 Color *line_colour);
static void draw_rect(DiaRenderer *self, 
		      Point *ul_corner, Point *lr_corner,
		      Color *colour);
static void fill_rect(DiaRenderer *self, 
		      Point *ul_corner, Point *lr_corner,
		      Color *colour);
static void draw_arc(DiaRenderer *self, 
		     Point *center,
		     real width, real height,
		     real angle1, real angle2,
		     Color *colour);
static void fill_arc(DiaRenderer *self, 
		     Point *center,
		     real width, real height,
		     real angle1, real angle2,
		     Color *colour);
static void draw_ellipse(DiaRenderer *self, 
			 Point *center,
			 real width, real height,
			 Color *colour);
static void fill_ellipse(DiaRenderer *self, 
			 Point *center,
			 real width, real height,
			 Color *colour);
static void draw_bezier(DiaRenderer *self, 
			BezPoint *points,
			int numpoints,
			Color *colour);
static void fill_bezier(DiaRenderer *self, 
			BezPoint *points, /* Last point must be same as first point */
			int numpoints,
			Color *colour);
static void draw_string(DiaRenderer *self,
			const char *text,
			Point *pos, Alignment alignment,
			Color *colour);
static void draw_image(DiaRenderer *self,
		       Point *point,
		       real width, real height,
		       DiaImage image);

static void
init_attributes( CgmRenderer *renderer )
{
    /* current values, (defaults) */
    renderer->lcurrent.cap   = 3;            /* round */
    renderer->lcurrent.join  = 2;            /* mitre */
    renderer->lcurrent.style = 1;
    renderer->lcurrent.width = 0.1;
    renderer->lcurrent.color.red   = 0;
    renderer->lcurrent.color.green = 0;
    renderer->lcurrent.color.blue  = 0;

    renderer->linfile.cap    = -1;
    renderer->linfile.join   = -1;
    renderer->linfile.style  = -1;
    renderer->linfile.width  = -1.0;
    renderer->linfile.color.red   = -1;
    renderer->linfile.color.green = -1;
    renderer->linfile.color.blue  = -1;

    /* fill/edge defaults */
    renderer->fcurrent.fill_style = 1;          /* solid */
    renderer->fcurrent.fill_color.red = 0;
    renderer->fcurrent.fill_color.green = 0;
    renderer->fcurrent.fill_color.blue = 0;

    renderer->fcurrent.edgevis = 0;
    renderer->fcurrent.cap   = 3;            /* round */
    renderer->fcurrent.join  = 2;            /* mitre */
    renderer->fcurrent.style = 1;
    renderer->fcurrent.width = 0.1;
    renderer->fcurrent.color.red   = 0;
    renderer->fcurrent.color.green = 0;
    renderer->fcurrent.color.blue  = 0;
   
    renderer->finfile.fill_style = -1;
    renderer->finfile.fill_color.red = -1;
    renderer->finfile.fill_color.green = -1;
    renderer->finfile.fill_color.blue = -1;

    renderer->finfile.edgevis = -1;
    renderer->finfile.cap   = -1;            
    renderer->finfile.join  = -1;           
    renderer->finfile.style = -1;
    renderer->finfile.width = -1.;
    renderer->finfile.color.red   = -1.0;
    renderer->finfile.color.green = -1.0;
    renderer->finfile.color.blue  = -1.0;

    renderer->tcurrent.font_num    = 1;
    renderer->tcurrent.font_height = 0.1;
    renderer->tcurrent.color.red   = 0.0;
    renderer->tcurrent.color.green = 0.0;
    renderer->tcurrent.color.blue  = 0.0;

    renderer->tinfile.font_num    = -1;
    renderer->tinfile.font_height = -1.0;
    renderer->tinfile.color.red   = -1.0;
    renderer->tinfile.color.green = -1.0;
    renderer->tinfile.color.blue  = -1.0;
   
}
    


static real
swap_y( CgmRenderer *renderer, real y)
{
   return  (renderer->y0 + renderer->y1 - y);
}


static void
write_line_attributes( CgmRenderer *renderer, Color *color )
{
LineAttrCGM    *lnew, *lold;

    lnew = &renderer->lcurrent;
    lold = &renderer->linfile; 

    if ( lnew->cap != lold->cap )
    {
        write_elhead(renderer->file, 5, 37, 4);
        write_int16(renderer->file, lnew->cap);
        write_int16(renderer->file, 3);      /* cap of dashlines match */
                                             /* normal cap */
        lold->cap = lnew->cap;
    }
    if ( lnew->join != lold->join )
    {
        write_elhead(renderer->file, 5, 38, 2);
        write_int16(renderer->file, lnew->join);
        lold->join = lnew->join; 
    }
    if ( lnew->style != lold->style )
    {
        write_elhead(renderer->file, 5, 2, 2);
        write_int16(renderer->file, lnew->style);
        lold->style = lnew->style;
    }
    if ( lnew->width != lold->width )
    {
        write_elhead(renderer->file, 5, 3, REALSIZE);
        write_real(renderer->file, lnew->width);
        lold->width = lnew->width;
    }
    lnew->color = *color;
    if ( lnew->color.red != lold->color.red ||
         lnew->color.green != lold->color.green ||
         lnew->color.blue != lold->color.blue )
    {
        write_elhead(renderer->file, 5, 4, 3); /* line colour */
        write_colour(renderer->file, &lnew->color);
        putc(0, renderer->file);
        lold->color = lnew->color;
    }
}


/*
** Update the fill/edge attributes.
**
** fill_color		!= NULL, style solid, interrior-color is fill_color
**                      == NULL, style empty
**
** edge_color           != NULL, edge on with the color and the other 
**                               attributes
**                      == NULL, edge off
*/

static void
write_filledge_attributes( CgmRenderer *renderer, Color *fill_color,
                           Color *edge_color )
{
FillEdgeAttrCGM    *fnew, *fold;

    fnew = &renderer->fcurrent;
    fold = &renderer->finfile; 
    /*
    ** Set the edge attributes
    */

    if ( edge_color == NULL )
    {
        fnew->edgevis = 0;   /* edge off */
        if ( fnew->edgevis != fold->edgevis )
        {
            write_elhead(renderer->file, 5, 30, 2);    
            write_int16(renderer->file, fnew->edgevis);
            fold->edgevis = fnew->edgevis;
        }
    }
    else
    {
        fnew->edgevis = 1;   /* edge on */
        if ( fnew->edgevis != fold->edgevis )
        {
            write_elhead(renderer->file, 5, 30, 2);  
            write_int16(renderer->file, fnew->edgevis);
            fold->edgevis = fnew->edgevis;
        }
        if ( fnew->cap != fold->cap )
        {
            write_elhead(renderer->file, 5, 44, 4);
            write_int16(renderer->file, fnew->cap);
            write_int16(renderer->file, 3);  /* cap of dashlines match */
                                             /* normal cap */
            fold->cap = fnew->cap;
        }
        if ( fnew->join != fold->join )
        {
            write_elhead(renderer->file, 5, 45, 2);
            write_int16(renderer->file, fnew->join);
            fold->join = fnew->join; 
        }
        if ( fnew->style != fold->style )
        {
            write_elhead(renderer->file, 5, 27, 2);
            write_int16(renderer->file, fnew->style);
            fold->style = fnew->style;
        }
        if ( fnew->width != fold->width )
        {
            write_elhead(renderer->file, 5, 28, REALSIZE);
            write_real(renderer->file, fnew->width);
            fold->width = fnew->width;
        }
        fnew->color = *edge_color;
        if ( fnew->color.red != fold->color.red ||
             fnew->color.green != fold->color.green ||
             fnew->color.blue != fold->color.blue )
        {
            write_elhead(renderer->file, 5, 29, 3); /* line colour */
            write_colour(renderer->file, &fnew->color);
            putc(0, renderer->file);
            fold->color = fnew->color;
        }
    }

    if ( fill_color == NULL )
    {
        fnew->fill_style = 4;       /* empty */
        if ( fnew->fill_style != fold->fill_style )
        {
            write_elhead(renderer->file, 5, 22, 2); 
            write_int16(renderer->file, fnew->fill_style);
            fold->fill_style = fnew->fill_style;
        }
    }
    else
    {
        fnew->fill_style = 1;       /* solid fill */
        if ( fnew->fill_style != fold->fill_style )
        {
            write_elhead(renderer->file, 5, 22, 2); 
            write_int16(renderer->file, fnew->fill_style);
            fold->fill_style = fnew->fill_style;
        }
        fnew->fill_color = *fill_color;
        if ( fnew->fill_color.red != fold->fill_color.red ||
             fnew->fill_color.green != fold->fill_color.green ||
             fnew->fill_color.blue != fold->fill_color.blue )
        {
            write_elhead(renderer->file, 5, 23, 3);   /* fill colour */
            write_colour(renderer->file, &fnew->fill_color);
            putc(0, renderer->file);
            fold->fill_color = fnew->fill_color;
        }
    }
}



static void
write_text_attributes( CgmRenderer *renderer, Color *text_color)
{
TextAttrCGM    *tnew, *told;


    tnew = &renderer->tcurrent;
    told = &renderer->tinfile; 
    /*
    ** Set the text attributes
    */
    if ( tnew->font_num != told->font_num )
    {
        write_elhead(renderer->file, 5, 10, 2);
        write_int16(renderer->file, tnew->font_num);
        told->font_num = tnew->font_num;
    }

    if ( tnew->font_height != told->font_height )
    {
        real    h_basecap;

        /* in CGM we need the base-cap height, I used the 0.9 to correct */ 
        /* it because it was still to high. There might be a better way */
        /* but for now.... */

        h_basecap = 0.9 * (tnew->font_height -
                           dia_font_descent("Aq",renderer->font,
                           tnew->font_height));
        write_elhead(renderer->file, 5, 15, REALSIZE);
        write_real(renderer->file, h_basecap);
        told->font_height = tnew->font_height;
    }

    tnew->color = *text_color;
    if ( tnew->color.red != told->color.red ||
         tnew->color.green != told->color.green ||
         tnew->color.blue != told->color.blue )
    {
        write_elhead(renderer->file, 5, 14, 3);   /* text colour */
        write_colour(renderer->file, &tnew->color);
        putc(0, renderer->file);
        told->color = tnew->color;
    }
}


static void
begin_render(DiaRenderer *self)
{
}

static void
end_render(DiaRenderer *self)
{
    CgmRenderer *renderer = CGM_RENDERER(self);

    /* end picture */
    write_elhead(renderer->file, 0, 5, 0);
    /* end metafile */
    write_elhead(renderer->file, 0, 2, 0);

    fclose(renderer->file);
}

static void
set_linewidth(DiaRenderer *self, real linewidth)
{  /* 0 == hairline **/
    CgmRenderer *renderer = CGM_RENDERER(self);

    /* update current line and edge width */
    renderer->lcurrent.width = renderer->fcurrent.width = linewidth;
}

static void
set_linecaps(DiaRenderer *self, LineCaps mode)
{
    CgmRenderer *renderer = CGM_RENDERER(self);
    int  cap;

    switch(mode) 
    {
    case LINECAPS_BUTT:
	cap = 2;
	break;
    case LINECAPS_ROUND:
	cap = 3;
	break;
    case LINECAPS_PROJECTING:
	cap = 4;
	break;
    default:
	cap = 2;
        break;
    }
    renderer->lcurrent.cap = renderer->fcurrent.cap = cap;
}

static void
set_linejoin(DiaRenderer *self, LineJoin mode)
{
    CgmRenderer *renderer = CGM_RENDERER(self);
    int    join;

    switch(mode) 
    {
    case LINEJOIN_MITER:
	join = 2;
	break;
    case LINEJOIN_ROUND:
	join = 3;
	break;
    case LINEJOIN_BEVEL:
        join = 4;
	break;
    default:
	join = 2;
        break;
    }
    renderer->lcurrent.join = renderer->fcurrent.join = join;
}

static void
set_linestyle(DiaRenderer *self, LineStyle mode)
{
    CgmRenderer *renderer = CGM_RENDERER(self);
    gint16   style;

    switch(mode)
    {
    case LINESTYLE_DASHED:
       style = 2;
       break;
    case LINESTYLE_DASH_DOT:
       style = 4;
       break;
    case LINESTYLE_DASH_DOT_DOT:
       style = 5;
       break;
    case LINESTYLE_DOTTED:
       style = 3;
       break;
    case LINESTYLE_SOLID:
    default:
       style = 1;
       break;
    }
    renderer->lcurrent.style = renderer->fcurrent.style = style;
}

static void
set_dashlength(DiaRenderer *self, real length)
{  /* dot = 20% of len */
    /* CGM doesn't support setting a dash length */
}

static void
set_fillstyle(DiaRenderer *self, FillStyle mode)
{
#if 0
    switch(mode) {
    case FILLSTYLE_SOLID:
	write_elhead(renderer->file, 5, 22, 2);
	write_int16(renderer->file, 1);
	break;
    default:
	message_error("svg_renderer: Unsupported fill mode specified!\n");
    }
#endif
}

static void
set_font(DiaRenderer *self, DiaFont *font, real height)
{
    CgmRenderer *renderer = CGM_RENDERER(self);

    if (renderer->font != NULL)
	dia_font_unref(renderer->font);
    renderer->font = dia_font_ref(font);
    renderer->tcurrent.font_num = FONT_NUM(font);
    renderer->tcurrent.font_height = height;
}

static void
draw_line(DiaRenderer *self, 
	  Point *start, Point *end, 
	  Color *line_colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);

    write_line_attributes(renderer, line_colour);

    write_elhead(renderer->file, 4, 1, 4 * REALSIZE);
    write_real(renderer->file, start->x);
    write_real(renderer->file, swap_y(renderer, start->y));
    write_real(renderer->file, end->x);
    write_real(renderer->file, swap_y(renderer, end->y));
}

static void
draw_polyline(DiaRenderer *self, 
	      Point *points, int num_points, 
	      Color *line_colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);
    int i;

    write_line_attributes(renderer, line_colour);

    write_elhead(renderer->file, 4, 1, num_points * 2 * REALSIZE);
    for (i = 0; i < num_points; i++) {
	write_real(renderer->file, points[i].x);
	write_real(renderer->file, swap_y(renderer, points[i].y));
    }
}

static void
draw_polygon(DiaRenderer *self, 
	     Point *points, int num_points, 
	     Color *line_colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);
    int i;

    write_filledge_attributes(renderer, NULL, line_colour);

    write_elhead(renderer->file, 4, 7, num_points * 2 * REALSIZE);
    for (i = 0; i < num_points; i++) {
	write_real(renderer->file, points[i].x);
	write_real(renderer->file, swap_y(renderer, points[i].y));
    }
}

static void
fill_polygon(DiaRenderer *self, 
	     Point *points, int num_points, 
	     Color *colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);
    int i;

    write_filledge_attributes(renderer, colour, NULL);

    write_elhead(renderer->file, 4, 7, num_points * 2 * REALSIZE);
    for (i = 0; i < num_points; i++) {
	write_real(renderer->file, points[i].x);
	write_real(renderer->file, swap_y(renderer, points[i].y));
    }
}

static void
draw_rect(DiaRenderer *self, 
	  Point *ul_corner, Point *lr_corner,
	  Color *colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);

    write_filledge_attributes(renderer, NULL, colour);

    write_elhead(renderer->file, 4, 11, 4 * REALSIZE);
    write_real(renderer->file, ul_corner->x);
    write_real(renderer->file, swap_y(renderer, ul_corner->y));
    write_real(renderer->file, lr_corner->x);
    write_real(renderer->file, swap_y(renderer, lr_corner->y));
}

static void
fill_rect(DiaRenderer *self, 
	  Point *ul_corner, Point *lr_corner,
	  Color *colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);

    write_filledge_attributes(renderer, colour, NULL);

    write_elhead(renderer->file, 4, 11, 4 * REALSIZE);
    write_real(renderer->file, ul_corner->x);
    write_real(renderer->file, swap_y(renderer, ul_corner->y));
    write_real(renderer->file, lr_corner->x);
    write_real(renderer->file, swap_y(renderer, lr_corner->y));
}



static void
write_ellarc(CgmRenderer *renderer,
             int   elemid,
             Point *center,
             real  width, real  height,
             real  angle1, real angle2 )
{
    real rx = width / 2, ry = height / 2;
    int  len;
    real ynew;

    /*
    ** Angle's are in degrees, need to be converted to 2PI.
    */
    angle1 = (angle1 / 360.0) * 2 * M_PI;
    angle2 = (angle2 / 360.0) * 2 * M_PI;

    ynew = swap_y(renderer, center->y);

    /*
    ** Elliptical Arc (18) or Elliptical Arc close (19).
    */
    len = elemid == 18 ? (10 * REALSIZE) : (10 * REALSIZE + 2);
    write_elhead(renderer->file, 4, elemid, len);
    write_real(renderer->file, center->x);        /* center */
    write_real(renderer->file, ynew);
    write_real(renderer->file, center->x + rx);   /* axes 1 */
    write_real(renderer->file, ynew);
    write_real(renderer->file, center->x);        /* axes 2 */
    write_real(renderer->file, ynew + ry);

    write_real(renderer->file, cos(angle1)); /* vector1 */
    write_real(renderer->file, sin(angle1));
    write_real(renderer->file, cos(angle2)); /* vector2 */
    write_real(renderer->file, sin(angle2));

    /*
    ** Elliptical arc close, use PIE closure.
    */
    if ( elemid == 19 )
        write_int16(renderer->file, 0);
} 


static void
draw_arc(DiaRenderer *self, 
	 Point *center,
	 real width, real height,
	 real angle1, real angle2,
	 Color *colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);

    write_line_attributes(renderer, colour);
    write_ellarc(renderer, 18, center, width, height, angle1, angle2);
}

static void
fill_arc(DiaRenderer *self, 
	 Point *center,
	 real width, real height,
	 real angle1, real angle2,
	 Color *colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);

    write_filledge_attributes(renderer, colour, NULL);
    write_ellarc(renderer, 19, center, width, height, angle1, angle2);
}

static void
draw_ellipse(DiaRenderer *self, 
	     Point *center,
	     real width, real height,
	     Color *colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);
    real  ynew;

    write_filledge_attributes(renderer, NULL, colour);

    ynew = swap_y(renderer, center->y);
    write_elhead(renderer->file, 4, 17, 6 * REALSIZE);
    write_real(renderer->file, center->x); /* center */
    write_real(renderer->file, ynew);
    write_real(renderer->file, center->x);      /* axes 1 */
    write_real(renderer->file, ynew + height/2);
    write_real(renderer->file, center->x + width/2); /* axes 2 */
    write_real(renderer->file, ynew );
}

static void
fill_ellipse(DiaRenderer *self, 
	     Point *center,
	     real width, real height,
	     Color *colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);
    real ynew;

    write_filledge_attributes(renderer, colour, NULL);

    ynew = swap_y(renderer, center->y);
    write_elhead(renderer->file, 4, 17, 6 * REALSIZE);
    write_real(renderer->file, center->x); /* center */
    write_real(renderer->file, ynew);
    write_real(renderer->file, center->x);      /* axes 1 */
    write_real(renderer->file, ynew + height/2);
    write_real(renderer->file, center->x + width/2); /* axes 2 */
    write_real(renderer->file, ynew);
}


static void 
write_bezier(CgmRenderer *renderer, 
             BezPoint *points, 
             int numpoints) 
{
    int     i;
    Point   current;

    if (points[0].type != BEZ_MOVE_TO)
	g_warning("first BezPoint must be a BEZ_MOVE_TO");

    current.x = points[0].p1.x;
    current.y = swap_y(renderer, points[0].p1.y);

    for (i = 1; i < numpoints; i++)
    {
	switch (points[i].type) 
        {
	case BEZ_MOVE_TO:
	    g_warning("only first BezPoint can be a BEZ_MOVE_TO");
	    break;
	case BEZ_LINE_TO:
            write_elhead(renderer->file, 4, 1, 4 * REALSIZE);
            write_real(renderer->file, current.x);
            write_real(renderer->file, current.y);
            write_real(renderer->file, points[i].p1.x);
            write_real(renderer->file, swap_y(renderer, points[i].p1.y));
            current.x = points[i].p1.x;
            current.y = swap_y(renderer, points[i].p1.y);
            break;
	case BEZ_CURVE_TO:
            write_elhead(renderer->file, 4, 26, 8 * REALSIZE + 2);
            write_int16(renderer->file, 1);
            write_real(renderer->file, current.x);
            write_real(renderer->file, current.y);
            write_real(renderer->file, points[i].p1.x);
            write_real(renderer->file, swap_y(renderer, points[i].p1.y));
            write_real(renderer->file, points[i].p2.x);
            write_real(renderer->file, swap_y(renderer, points[i].p2.y));
            write_real(renderer->file, points[i].p3.x);
            write_real(renderer->file, swap_y(renderer, points[i].p3.y));
            current.x = points[i].p3.x;
            current.y = swap_y(renderer, points[i].p3.y);
            break;
        }
    }
}


static void
draw_bezier(DiaRenderer *self, 
	    BezPoint *points,
	    int numpoints,
	    Color *colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);

    if ( numpoints < 2 )
        return;

    write_line_attributes(renderer, colour);
    write_bezier(renderer, points, numpoints);
}


static void
fill_bezier(DiaRenderer *self, 
	    BezPoint *points, /* Last point must be same as first point */
	    int numpoints,
	    Color *colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);

    if ( numpoints < 2 )
        return;

    write_filledge_attributes(renderer, colour, NULL);

    /*
    ** A filled bezier is created by using it within a figure.
    */
    write_elhead(renderer->file, 0, 8, 0);     /* begin figure */
    write_bezier(renderer, points, numpoints);
    write_elhead(renderer->file, 0, 9, 0);     /* end figure */
}



static void
draw_string(DiaRenderer *self,
	    const char *text,
	    Point *pos, Alignment alignment,
	    Color *colour)
{
    CgmRenderer *renderer = CGM_RENDERER(self);
    double x = pos->x, y = swap_y(renderer, pos->y);
    gint len, chunk;
    const gint maxfirstchunk = 255 - 2 * REALSIZE - 2 - 1;
    const gint maxappendchunk = 255 - 2 - 1;

    /* check for empty strings */
    len = strlen(text);
    if ( len == 0 )
        return;

    write_text_attributes(renderer, colour);

    switch (alignment) {
    case ALIGN_LEFT:
        break;
    case ALIGN_CENTER:
        x -= dia_font_string_width(text, renderer->font, 
                                   renderer->tcurrent.font_height)/2;
        break;
    case ALIGN_RIGHT:
        x -= dia_font_string_width(text, renderer->font, 
                                   renderer->tcurrent.font_height);
        break;
    }
    /* work out size of first chunk of text */
    chunk = MIN(maxfirstchunk, len);
    write_elhead(renderer->file, 4, 4, 2 * REALSIZE + 2 + 1 + chunk);
    write_real(renderer->file, x);
    write_real(renderer->file, y);
    write_int16(renderer->file, (len == chunk)); /* last chunk? */
    putc(chunk, renderer->file);
    fwrite(text, sizeof(char), chunk, renderer->file);
    if (!IS_ODD(chunk))
	putc(0, renderer->file);

    len -= chunk;
    text += chunk;
    while (len > 0) {
	/* append text */
	chunk = MIN(maxappendchunk, len);
	write_elhead(renderer->file, 4, 6, 2 + 1 + chunk);
	write_int16(renderer->file, (len == chunk));
	putc(chunk, renderer->file);
	fwrite(text, sizeof(char), chunk, renderer->file);
	if (!IS_ODD(chunk))
	    putc(0, renderer->file);

	len -= chunk;
	text += chunk;
    }
}


static void
draw_image(DiaRenderer *self,
	   Point *point,
	   real width, real height,
	   DiaImage image)
{
    CgmRenderer *renderer = CGM_RENDERER(self);
    const gint maxlen = 32767 - 6 * REALSIZE - 4 * 2;
    double x1 = point->x, y1 = swap_y(renderer, point->y), 
           x2 = x1+width, y2 = y1-height;
    gint rowlen = dia_image_width(image) * 3, lines = dia_image_height(image);
    double linesize = (y1 - y2) / lines;
    gint chunk, clines = lines;
    guint8 *pImg, *ptr;

    if (rowlen > maxlen) {
	message_error(_("Image row length larger than maximum cell array.\n"
			"Image not exported to CGM."));
	return;
    }

    ptr = pImg = dia_image_rgb_data(image);

    while (lines > 0) {
	chunk = MIN(rowlen * lines, maxlen);
	clines = chunk / rowlen;
	chunk = clines * rowlen;

	write_elhead(renderer->file, 4, 9, 6*REALSIZE + 8 + chunk);
	write_real(renderer->file, x1); /* first corner */
	write_real(renderer->file, y1);
	write_real(renderer->file, x2); /* second corner */
	write_real(renderer->file, y1 - linesize*clines/*y2*/);
	write_real(renderer->file, x2); /* third corner */
	write_real(renderer->file, y1);

	/* write image size */
	write_int16(renderer->file, dia_image_width(image));
	write_int16(renderer->file, clines);

	write_int16(renderer->file, 8); /* colour precision */
	write_int16(renderer->file, 1); /* packed encoding */

	fwrite(ptr, sizeof(guint8), chunk, renderer->file);

	lines -= clines;
	ptr += chunk;
	y1 -= clines * linesize;
    }
    g_free (pImg);
}

static void
export_cgm(DiagramData *data, const gchar *filename, 
           const gchar *diafilename, void* user_data)
{
    CgmRenderer *renderer;
    FILE *file;
    Rectangle *extent;
    gint len;

    file = fopen(filename, "wb");

    if (file == NULL) {
	message_error(_("Can't open output file %s: %s\n"), 
		      dia_message_filename(filename), strerror(errno));
	return;
    }

    renderer = g_object_new(CGM_TYPE_RENDERER, NULL);

    renderer->file = file;

    /* write BEGMF */
    len = strlen(dia_version_string);
    write_elhead(file, 0, 1, len + 1);
    putc(len, file);
    fwrite(dia_version_string, sizeof(char), len, file);
    if (!IS_ODD(len))
	putc(0, file);

    /* write metafile version */
    write_elhead(file, 1, 1, 2);
    write_uint16(file, 3); /* use version 3 because we may use polybeziers */

#if 0
    /* write metafile description -- use original dia filename */
    len = strlen(diafilename);
    write_elhead(file, 1, 2, len + 1);
    putc(len, file);
    fwrite(diafilename, sizeof(char), len, file);
    if (!IS_ODD(len))
	putc(0, file);
#endif

    /* set integer precision */
    write_elhead(file, 1, 4, 2);
    write_int16(file, 16);

    /* write element virtual device unit type */
    write_elhead(file, 1, 3, 2);
    write_int16(file, 1); /* real number */

    /* write the colour precision */
    write_elhead(file, 1, 7, 2);
    write_int16(file, 8); /* 8 bits per chanel */

    /* write element list command */
    write_elhead(file, 1, 11, 6);
    write_int16(file,  1);
    write_int16(file, -1);
    write_int16(file,  5);

    /* write font list */
    init_fonts();
    write_elhead(file, 1, 13, fontlistlen);
    fwrite(fontlist, sizeof(char), fontlistlen, file);
    if (IS_ODD(fontlistlen))
	putc(0, file);
 
    /* begin picture */
    len = strlen(diafilename);
    write_elhead(file, 0, 3, len + 1);
    putc(len, file);
    fwrite(diafilename, sizeof(char), len, file);
    if (!IS_ODD(len))
	putc(0, file);

    /* write the colour mode string */
    write_elhead(file, 2, 2, 2);
    write_int16(file, 1); /* direct colour mode (as opposed to indexed) */

    /* write edge width mode */
    write_elhead(file, 2, 5, 2);
    write_int16(file, 0); /* relative to virtual device coordinates */

    /* write line width mode */
    write_elhead(file, 2, 3, 2);
    write_int16(file, 0); /* relative to virtual device coordinates */

    extent = &data->extents;

    /*
    ** Change the swap Y-coordinate in the CGM, to get a more 
    ** 'portable' CGM   
    */
    /* write extents */
    write_elhead(file, 2, 6, 4 * REALSIZE);
    write_real(file, extent->left);
    write_real(file, extent->top);
    write_real(file, extent->right);
    write_real(file, extent->bottom);
    renderer->y1 = extent->top;
    renderer->y0 = extent->bottom;

    /* write back colour */
    write_elhead(file, 2, 7, 3);
    write_colour(file, &data->bg_color);
    putc(0, file);

    /* begin the picture body */
    write_elhead(file, 0, 4, 0);

    /* make text be the right way up */
    write_elhead(file, 5, 16, 4 * REALSIZE);
    write_real(file,  0);
    write_real(file,  1);
    write_real(file,  1);
    write_real(file,  0);

    /* set the text alignment to left/base */
    write_elhead(file, 5, 18, 4 + 2 * REALSIZE);
    write_int16(file, 1);          /* left */
    write_int16(file, 4);          /* base */
    write_real(file, 0.0);
    write_real(file, 0.0);

    init_attributes(renderer);

    data_render(data, DIA_RENDERER(renderer), NULL, NULL, NULL);

    dia_font_unref(renderer->font);
    g_object_unref(renderer);
}

/* GObject stuff */
static void cgm_renderer_class_init (CgmRendererClass *klass);

static gpointer parent_class = NULL;

GType
cgm_renderer_get_type (void)
{
  static GType object_type = 0;

  if (!object_type)
    {
      static const GTypeInfo object_info =
      {
        sizeof (CgmRendererClass),
        (GBaseInitFunc) NULL,
        (GBaseFinalizeFunc) NULL,
        (GClassInitFunc) cgm_renderer_class_init,
        NULL,           /* class_finalize */
        NULL,           /* class_data */
        sizeof (CgmRenderer),
        0,              /* n_preallocs */
	NULL            /* init */
      };

      object_type = g_type_register_static (DIA_TYPE_RENDERER,
                                            "CgmRenderer",
                                            &object_info, 0);
    }
  
  return object_type;
}

static void
cgm_renderer_finalize (GObject *object)
{
  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
cgm_renderer_class_init (CgmRendererClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    DiaRendererClass *renderer_class = DIA_RENDERER_CLASS (klass);

    parent_class = g_type_class_peek_parent (klass);

    object_class->finalize = cgm_renderer_finalize;

    renderer_class->begin_render = begin_render;
    renderer_class->end_render = end_render;

    renderer_class->set_linewidth = set_linewidth;
    renderer_class->set_linecaps = set_linecaps;
    renderer_class->set_linejoin = set_linejoin;
    renderer_class->set_linestyle = set_linestyle;
    renderer_class->set_dashlength = set_dashlength;
    renderer_class->set_fillstyle = set_fillstyle;
    renderer_class->set_font = set_font;
  
    renderer_class->draw_line = draw_line;
    renderer_class->draw_polyline = draw_polyline;
  
    renderer_class->draw_polygon = draw_polygon;
    renderer_class->fill_polygon = fill_polygon;

    renderer_class->draw_rect = draw_rect;
    renderer_class->fill_rect = fill_rect;

    renderer_class->draw_arc = draw_arc;
    renderer_class->fill_arc = fill_arc;

    renderer_class->draw_ellipse = draw_ellipse;
    renderer_class->fill_ellipse = fill_ellipse;

    renderer_class->draw_bezier = draw_bezier;
    renderer_class->fill_bezier = fill_bezier;

    renderer_class->draw_string = draw_string;

    renderer_class->draw_image = draw_image;
}


static const gchar *extensions[] = { "cgm", NULL };
static DiaExportFilter cgm_export_filter = {
    N_("Computer Graphics Metafile"),
    extensions,
    export_cgm
};


/* --- dia plug-in interface --- */
static gboolean
_plugin_can_unload (PluginInfo *info)
{
  return TRUE;
}

static void
_plugin_unload (PluginInfo *info)
{
  filter_unregister_export(&cgm_export_filter);
}

DIA_PLUGIN_CHECK_INIT

PluginInitResult
dia_plugin_init(PluginInfo *info)
{
    if (!dia_plugin_info_init(info, "CGM",
			      _("Computer Graphics Metafile export filter"),
			      _plugin_can_unload,
                              _plugin_unload))
	return DIA_PLUGIN_INIT_ERROR;

    filter_register_export(&cgm_export_filter);

    return DIA_PLUGIN_INIT_OK;
}
