/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998 Alexander Larsson
 *
 * wpg.c -- WordPerfect Graphics export plugin for dia
 * Copyright (C) 2000, Hans Breuer, <Hans@Breuer.Org>
 *   based on dummy.c 
 *
 * 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.
 */

/*
 * ToDo:
 * - if helper points - like arc centers - are not in Dia's extent box
 *   their clipping produces unpredictable results 
 * - the font setting needs improvement (maybe on Dia's side)
 * - bezier curves are not correctly converted to WPG's poly curve
 *   (two point beziers are)
 *
 * MayBe:
 * - full featured import (IMO Dia's import support should be improved
 *   before / on the way ...)
 */

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

#include <glib.h>
#include <glib/gstdio.h>

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

/* format specific */
#include "wpg_defs.h"

/* #define DEBUG_WPG */

/* Import is not yet finished but only implemented to check the
 * export capabilities and to investigate other programs WPG
 * formats. 
 * The following is not the reason for <en/dis>abling import, 
 * but it should do it for now ...
 */
#define WPG_WITH_IMPORT defined (_MSC_VER)

/*
 * helper macros
 */
#define SC(a)  ((a) * renderer->Scale)
#define SCX(a) (((a) + renderer->XOffset) * renderer->Scale)
#define SCY(a) (((a) + renderer->YOffset) * renderer->Scale)

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

#define WPG_TYPE_RENDERER           (wpg_renderer_get_type ())
#define WPG_RENDERER(obj)           (G_TYPE_CHECK_INSTANCE_CAST ((obj), WPG_TYPE_RENDERER, WpgRenderer))
#define WPG_RENDERER_CLASS(klass)   (G_TYPE_CHECK_CLASS_CAST ((klass), WPG_TYPE_RENDERER, WpgRendererClass))
#define WPG_IS_RENDERER(obj)        (G_TYPE_CHECK_INSTANCE_TYPE ((obj), WPG_TYPE_RENDERER))
#define WPG_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), WPG_TYPE_RENDERER, WpgRendererClass))

GType wpg_renderer_get_type (void) G_GNUC_CONST;

typedef struct _WpgRenderer WpgRenderer;
typedef struct _WpgRendererClass WpgRendererClass;

struct _WpgRenderer
{
  DiaRenderer parent_instance;

  FILE *file;

  real Scale;   /* to WPU == 1/1200 inch */
  real XOffset, YOffset; /* in dia units */

  real dash_length;

  WPGStartData Box;
  WPGFillAttr  FillAttr;
  WPGLineAttr  LineAttr;
  WPGTextStyle TextStyle;

  WPGColorRGB* pPal;
};

struct _WpgRendererClass
{
  DiaRendererClass parent_class;
};

G_END_DECLS

#ifdef DEBUG_WPG
#  define DIAG_NOTE(action) action
#else
#  define DIAG_NOTE(action)
#endif

#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
/* shortcut if testing of indirection isn't needed anymore */
#define fwrite_le(a,b,c,d) fwrite(a,b,c,d)
#else
static size_t
fwrite_le(void* buf, size_t size, size_t count, FILE* f)
{
  size_t n = 0;
  guint i;

  g_assert((1 == size) || (2 == size) || (4 == size));

  if (4 == size)
  {
    gint32 i32;

    for (i = 0; i < count; i++)
    {
      i32 = GINT32_TO_LE(((gint32*)buf)[i]);
      n += fwrite(&i32, sizeof(gint32), 1, f);
    }
  }
  else if (2 == size)
  {
    gint16 i16;

    for (i = 0; i < count; i++)
    {
      i16 = GINT16_TO_LE(((gint16*)buf)[i]);
      n += fwrite(&i16, sizeof(gint16), 1, f);
    }
  }
  else
    n = fwrite(buf, size, count, f);

  return n;
}
#endif

/* 
 * color cube len, WPG is limited to 256 colors. To get simple
 * color reduction algorithms only 216 = 6^3 of them are used.
 */
#define CC_LEN 6

/*
 * "To avoid problems with WPC products the first 16 colors of
 *  the color map should never be changed". 
 * Define WPG_NUM_DEF_COLORS to 0 ignore this statement (It
 * appears to work more reliable; tested with WinWord 97 and
 * Designer 4.1 - the latter does not respect the color maps
 * start color).
 */
#define WPG_NUM_DEF_COLORS 0 /* 16 */

/* 
 * helper functions 
 */
static guint8
LookupColor(WpgRenderer *renderer, Color* colour)
{
  /* find nearest color in the renderers color map */
  /* the following hack does only work with a correctly 
     sorted color map */
  unsigned long i = (int)floor(colour->red * (CC_LEN - 1))
                  + (int)floor(colour->green * (CC_LEN - 1)) * CC_LEN
                  + (int)floor(colour->blue * (CC_LEN - 1)) * CC_LEN  * CC_LEN;
  DIAG_NOTE(g_message("LookupColor %d.", i));
  if (i >= CC_LEN * CC_LEN * CC_LEN)
    return CC_LEN * CC_LEN * CC_LEN - 1 + WPG_NUM_DEF_COLORS;
  else 
    return i + WPG_NUM_DEF_COLORS;
}

static void
WriteRecHead(WpgRenderer *renderer, WPG_Type Type, guint32 Size)
{
  if (Size < 255)
  {
    WPGHead8 rh;
    rh.Type = (guint8)Type;
    rh.Size = (guint8)Size;
    fwrite(&rh, sizeof(guint8), 2, renderer->file);
  }
  else if (Size < 32768)
  {
    WPGHead16 rh;
    rh.Type  = (guint8)Type;
    rh.Dummy = 0xFF;
    rh.Size  = (guint16)Size;
    fwrite(&rh, sizeof(guint8), 2, renderer->file);
    fwrite_le(&(rh.Size), sizeof(guint16), 1, renderer->file);
  }
  else
  {
    WPGHead32 rh;
    rh.Type  = (guint8)Type;
    rh.Dummy = 0xFF;
    rh.Size  = Size;

    /* To avoid problems with stucture packing this struct
     * is written in parts ...
     */
    fwrite(&rh, sizeof(guint8), 2, renderer->file);
    fwrite_le(&rh.Size, sizeof(guint32), 1, renderer->file);
  }
}

static void
WriteLineAttr(WpgRenderer *renderer, Color* colour)
{
  g_assert(4 == sizeof(WPGLineAttr));

  WriteRecHead(renderer, WPG_LINEATTR, sizeof(WPGLineAttr));
  renderer->LineAttr.Color = LookupColor(renderer, colour);
  fwrite(&renderer->LineAttr, sizeof(guint8), 2, renderer->file);
  fwrite_le(&renderer->LineAttr.Width, sizeof(guint16), 1, renderer->file);
}

static void
WriteFillAttr(WpgRenderer *renderer, Color* colour, gboolean bFill)
{
  g_assert(2 == sizeof(WPGFillAttr));

  WriteRecHead(renderer, WPG_FILLATTR, sizeof(WPGFillAttr));
  if (bFill)
  {
    renderer->FillAttr.Color = LookupColor(renderer, colour);
    fwrite(&renderer->FillAttr, sizeof(WPGFillAttr), 1, renderer->file);
  }
  else
  {
    WPGFillAttr fa;
    fa.Color = LookupColor(renderer, colour);
    fa.Type = WPG_FA_HOLLOW;
    fwrite(&fa, sizeof(WPGFillAttr), 1, renderer->file);
  }
}

/* 
 * render functions 
 */ 
static void
begin_render(DiaRenderer *self)
{
  WpgRenderer *renderer = WPG_RENDERER (self);
#if 0
  const WPGFileHead wpgFileHead = { "\377WPC", 16, 
                                   1, 22,
                                   1, 0, /* Version */ 
                                   0, 0};
#else
  /* static conversion to little endian */
  const char wpgFileHead[16] = {255, 'W', 'P', 'C', 16, 0, 0, 0, 
                                1, 22, 1, 0, 0, 0, 0, 0};
#endif

  gint16 i;
  guint8* pPal;
  Color color = {1, 1, 1};

  DIAG_NOTE(g_message("begin_render"));

  fwrite(&wpgFileHead, 1, 16, renderer->file);

  /* bounding box */
  WriteRecHead(renderer, WPG_START, sizeof(WPGStartData));
#if 0
  fwrite(&renderer->Box, sizeof(WPGStartData), 1, renderer->file);
#else
  g_assert(6 == sizeof(WPGStartData));
  fwrite(&renderer->Box, sizeof(guint8), 2, renderer->file);
  fwrite_le(&renderer->Box.Width, sizeof(guint16), 2, renderer->file);
#endif
  /* initialize a well known colormap, see LookupColor */
  pPal = g_new(guint8, CC_LEN*CC_LEN*CC_LEN*3);
  for (i = 0; i < CC_LEN * CC_LEN * CC_LEN; i++)
  {
    pPal[3*i  ] = ((i % CC_LEN) * 255) / (CC_LEN - 1);   /* red */
    pPal[3*i+1] = (((i / CC_LEN) % CC_LEN) * 255) / (CC_LEN - 1); /* green */
    pPal[3*i+2] = ((i / (CC_LEN * CC_LEN)) * 255) / (CC_LEN - 1); /* blue varies least */
    /*
    g_print("%d\t%d\t%d\n", pPal[3*i  ], pPal[3*i+1], pPal[3*i+2]);
     */
  } 

  WriteRecHead(renderer, WPG_COLORMAP, CC_LEN*CC_LEN*CC_LEN*3 + 2*sizeof(guint16));
  i = WPG_NUM_DEF_COLORS; /* start color */
  fwrite_le(&i, sizeof(gint16), 1, renderer->file);
  i = CC_LEN*CC_LEN*CC_LEN;
  fwrite_le(&i, sizeof(gint16), 1, renderer->file);
  fwrite(pPal, 1, CC_LEN*CC_LEN*CC_LEN*3, renderer->file);

  /* FIXME: following 3 lines needed to make filling work !? */
  renderer->FillAttr.Type = WPG_FA_SOLID;
  WriteFillAttr(renderer, &color, TRUE);
  WriteFillAttr(renderer, &color, FALSE);

  g_free(pPal);
}

static void
end_render(DiaRenderer *self)
{
  WpgRenderer *renderer = WPG_RENDERER (self);

  DIAG_NOTE(g_message( "end_render"));

  WriteRecHead(renderer, WPG_END, 0); /* no data following */
  fclose(renderer->file);
  renderer->file = NULL;
}

static void
set_linewidth(DiaRenderer *self, real linewidth)
{  
  WpgRenderer *renderer = WPG_RENDERER (self);

  DIAG_NOTE(g_message("set_linewidth %f", linewidth));

  renderer->LineAttr.Width = SC(linewidth);
}

static void
set_linecaps(DiaRenderer *self, LineCaps mode)
{
  DIAG_NOTE(g_message("set_linecaps %d", mode));

  switch(mode) {
  case LINECAPS_BUTT:
    break;
  case LINECAPS_ROUND:
    break;
  case LINECAPS_PROJECTING:
    break;
  default:
    message_error("WpgRenderer : Unsupported fill mode specified!\n");
  }
}

static void
set_linejoin(DiaRenderer *self, LineJoin mode)
{
  DIAG_NOTE(g_message("set_join %d", mode));

  switch(mode) {
  case LINEJOIN_MITER:
    break;
  case LINEJOIN_ROUND:
    break;
  case LINEJOIN_BEVEL:
    break;
  default:
    message_error("WpgRenderer : Unsupported fill mode specified!\n");
  }
}

static void
set_linestyle(DiaRenderer *self, LineStyle mode)
{
  WpgRenderer *renderer = WPG_RENDERER (self);

  DIAG_NOTE(g_message("set_linestyle %d", mode));

  /* line type */
  switch (mode) {
  case LINESTYLE_SOLID:
    renderer->LineAttr.Type = WPG_LA_SOLID;
    break;
  case LINESTYLE_DASHED:
    if (renderer->dash_length < 0.5)
      renderer->LineAttr.Type = WPG_LA_SHORTDASH;
    else
      renderer->LineAttr.Type = WPG_LA_MEDIUMDASH;
    break;
  case LINESTYLE_DASH_DOT:
    renderer->LineAttr.Type = WPG_LA_DASHDOT;
    break;
  case LINESTYLE_DASH_DOT_DOT:
    renderer->LineAttr.Type = WPG_LA_DASHDOTDOT;
    break;
  case LINESTYLE_DOTTED:
    renderer->LineAttr.Type = WPG_LA_DOTS;
    break;
  default:
    message_error("WpgRenderer : Unsupported fill mode specified!\n");
  }
}

static void
set_dashlength(DiaRenderer *self, real length)
{  
  WpgRenderer *renderer = WPG_RENDERER (self);

  DIAG_NOTE(g_message("set_dashlength %f", length));

  /* dot = 20% of len */
  renderer->dash_length = length;
}

static void
set_fillstyle(DiaRenderer *self, FillStyle mode)
{
  WpgRenderer *renderer = WPG_RENDERER (self);

  DIAG_NOTE(g_message("set_fillstyle %d", mode));

  switch(mode) {
  case FILLSTYLE_SOLID:
    renderer->FillAttr.Type = WPG_FA_SOLID;
    break;
  default:
    message_error("WpgRenderer : Unsupported fill mode specified!\n");
  }
}

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

  /* FIXME PANGO: this is a little broken. Use better matching. */
    
  const char *family_name;
  DIAG_NOTE(g_message("set_font %f %s", height, font->name));
  renderer->TextStyle.Height = SC(height);

  family_name = dia_font_get_family(font);
  
  if ((strstr(family_name, "courier")) || (strstr(family_name, "monospace")))
    renderer->TextStyle.Font = 0x0DF0;
  else if ((strstr(family_name, "times")) || (strstr(family_name, "serif")))
    renderer->TextStyle.Font = 0x1950;
  else
    renderer->TextStyle.Font = 0x1150; /* Helv */

}

/* Need to translate coord system:
 * 
 *   Dia x,y -> Wpg x,-y
 *
 * doing it before scaling.
 */
static void
draw_line(DiaRenderer *self, 
          Point *start, Point *end, 
          Color *line_colour)
{
  WpgRenderer *renderer = WPG_RENDERER (self);
  gint16 pData[4];

  DIAG_NOTE(g_message("draw_line %f,%f -> %f, %f", 
            start->x, start->y, end->x, end->y));

  WriteLineAttr(renderer, line_colour);
  WriteRecHead(renderer, WPG_LINE, 4 * sizeof(gint16));

  /* point data */
  pData[0] = SCX(start->x);
  pData[1] = SCY(-start->y);
  pData[2] = SCX(end->x);
  pData[3] = SCY(-end->y);

  fwrite_le(pData, sizeof(gint16), 4, renderer->file);
}

static void
draw_polyline(DiaRenderer *self, 
              Point *points, int num_points, 
              Color *line_colour)
{
  WpgRenderer *renderer = WPG_RENDERER (self);
  int i;
  gint16* pData;

  DIAG_NOTE(g_message("draw_polyline n:%d %f,%f ...", 
            num_points, points->x, points->y));

  g_return_if_fail(1 < num_points);

  WriteLineAttr(renderer, line_colour);
  WriteRecHead(renderer, WPG_POLYLINE, num_points * 2 * sizeof(gint16) + sizeof(gint16));

  pData = g_new(gint16, num_points * 2);

  /* number of points */
  pData[0] = num_points;
  fwrite_le(pData, sizeof(gint16), 1, renderer->file);

  /* point data */
  for (i = 0; i < num_points; i++)
  {
    pData[2*i]   = SCX(points[i].x);
    pData[2*i+1] = SCY(-points[i].y);
  }

  fwrite_le(pData, sizeof(gint16), num_points*2, renderer->file);

  g_free(pData);
}

static void
draw_polygon(DiaRenderer *self, 
             Point *points, int num_points, 
             Color *line_colour)
{
  WpgRenderer *renderer = WPG_RENDERER (self);
  gint16* pData;
  int i;

  DIAG_NOTE(g_message("draw_polygon n:%d %f,%f ...", 
            num_points, points->x, points->y));

  WriteLineAttr(renderer, line_colour);
  WriteRecHead(renderer, WPG_POLYGON, (num_points * 2 + 1) * sizeof(gint16));

  pData = g_new(gint16, num_points * 2);

  /* number of vertices */
  pData[0] = num_points;
  fwrite_le(pData, sizeof(gint16), 1, renderer->file);

  /* point data */
  for (i = 0; i < num_points; i++)
  {
    pData[2*i]   = SCX(points[i].x);
    pData[2*i+1] = SCY(-points[i].y);
  }

  fwrite_le(pData, sizeof(gint16), num_points*2, renderer->file);

  g_free(pData);
}

static void
fill_polygon(DiaRenderer *self, 
             Point *points, int num_points, 
             Color *colour)
{
  WpgRenderer *renderer = WPG_RENDERER (self);

  DIAG_NOTE(g_message("fill_polygon n:%d %f,%f ...", 
            num_points, points->x, points->y));

  WriteFillAttr(renderer, colour, TRUE);
  draw_polygon(self,points,num_points,colour);
  WriteFillAttr(renderer, colour, FALSE);
}

static void
draw_rect(DiaRenderer *self, 
          Point *ul_corner, Point *lr_corner,
          Color *colour)
{
  WpgRenderer *renderer = WPG_RENDERER (self);
  gint16* pData;
  DIAG_NOTE(g_message("draw_rect %f,%f -> %f,%f", 
            ul_corner->x, ul_corner->y, lr_corner->x, lr_corner->y));

  WriteLineAttr(renderer, colour);
  WriteRecHead(renderer, WPG_RECTANGLE, 4*sizeof(gint16));

  pData = g_new(gint16, 4);
  pData[0] = SCX(ul_corner->x); /* lower left corner ! */
  pData[1] = SCY(-lr_corner->y);
  pData[2] = SC(lr_corner->x - ul_corner->x); /* width */
  pData[3] = SC(lr_corner->y - ul_corner->y); /* height */

  fwrite_le(pData, sizeof(gint16), 4, renderer->file);

  g_free(pData);
}

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

  DIAG_NOTE(g_message("fill_rect %f,%f -> %f,%f", 
            ul_corner->x, ul_corner->y, lr_corner->x, lr_corner->y));

  WriteFillAttr(renderer, colour, TRUE);
  draw_rect(self, ul_corner, lr_corner, colour);
  WriteFillAttr(renderer, colour, FALSE);
}

static void
draw_arc(DiaRenderer *self, 
	 Point *center,
	 real width, real height,
	 real angle1, real angle2,
	 Color *colour)
{
  WpgRenderer *renderer = WPG_RENDERER (self);
  WPGEllipse ell;

  DIAG_NOTE(g_message("draw_arc %fx%f <%f,<%f", 
            width, height, angle1, angle2));

  ell.x = SCX(center->x);
  ell.y = SCY(-center->y);
  ell.RotAngle = 0;
  ell.rx = SC(width / 2.0);
  ell.ry = SC(height / 2.0);

  ell.StartAngle = angle1;
  ell.EndAngle   = angle2;
  ell.Flags = 0;

  WriteLineAttr(renderer, colour);
  WriteRecHead(renderer, WPG_ELLIPSE, sizeof(WPGEllipse));

  g_assert(16 == sizeof(WPGEllipse));
  fwrite_le(&ell, sizeof(guint16), sizeof(WPGEllipse) / sizeof(guint16), renderer->file);
}

static void
fill_arc(DiaRenderer *self, 
         Point *center,
         real width, real height,
         real angle1, real angle2,
         Color *colour)
{
  WpgRenderer *renderer = WPG_RENDERER (self);
  WPGEllipse ell;

  DIAG_NOTE(g_message("fill_arc %fx%f <%f,<%f", 
            width, height, angle1, angle2));

  ell.x = SCX(center->x);
  ell.y = SCY(-center->y);
  ell.RotAngle = 0;
  ell.rx = SC(width / 2.0);
  ell.ry = SC(height / 2.0);

  ell.StartAngle = angle1;
  ell.EndAngle   = angle2;
  ell.Flags = 0; /* 0: connect to center; 1: connect start and end */

  WriteLineAttr(renderer, colour);
  WriteFillAttr(renderer, colour, TRUE);
  WriteRecHead(renderer, WPG_ELLIPSE, sizeof(WPGEllipse));

  g_assert(16 == sizeof(WPGEllipse));
  fwrite_le(&ell, sizeof(guint16), sizeof(WPGEllipse) / sizeof(guint16), renderer->file);
  WriteFillAttr(renderer, colour, FALSE);
}

static void
draw_ellipse(DiaRenderer *self, 
             Point *center,
             real width, real height,
             Color *colour)
{
  WpgRenderer *renderer = WPG_RENDERER (self);
  WPGEllipse ell;

  DIAG_NOTE(g_message("draw_ellipse %fx%f center @ %f,%f", 
            width, height, center->x, center->y));

  ell.x = SCX(center->x);
  ell.y = SCY(-center->y);
  ell.RotAngle = 0;
  ell.rx = SC(width / 2.0);
  ell.ry = SC(height / 2.0);

  ell.StartAngle = 0;
  ell.EndAngle   = 360;
  ell.Flags = 0;

  WriteLineAttr(renderer, colour);
  WriteRecHead(renderer, WPG_ELLIPSE, sizeof(WPGEllipse));

  g_assert(16 == sizeof(WPGEllipse));
  fwrite_le(&ell, sizeof(guint16), sizeof(WPGEllipse) / sizeof(guint16), renderer->file);
}

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

  DIAG_NOTE(g_message("fill_ellipse %fx%f center @ %f,%f", 
            width, height, center->x, center->y));

  WriteFillAttr(renderer, colour, TRUE);
  draw_ellipse(self,center,width,height,colour);
  WriteFillAttr(renderer, colour, FALSE);
}

#ifdef USE_OUR_DRAW_BEZIER
static void
draw_bezier(DiaRenderer *self, 
            BezPoint *points,
            int numpoints,
            Color *colour)
{
  WpgRenderer *renderer = WPG_RENDERER (self);
  gint16* pData;
  int i;

  DIAG_NOTE(g_message("draw_bezier n:%d %fx%f ...", 
            numpoints, points->p1.x, points->p1.y));

  WriteLineAttr(renderer, colour);
  WriteRecHead(renderer, WPG_POLYCURVE, (numpoints * 2) * 2 * sizeof(gint16) + 3 * sizeof(gint16));

  pData = g_new(gint16, numpoints * 6);

  /* ??? size of equivalent data in pre5.1 files ? */
#if 1
  memset(pData, 0, sizeof(gint16)*2);
#else
  /* try first point instead */
  pData[0] = SCX(points[0].p1.x);
  pData[1] = SCY(-points[0].p1.y);
#endif
  fwrite_le(pData, sizeof(gint16), 2, renderer->file);

  pData[0] = numpoints * 2;
  fwrite_le(pData, sizeof(gint16), 1, renderer->file);

  /* WPG's Poly Curve is not quite the same as DIA's Bezier.
   * There is only one Control Point per Point and I can't 
   * get it right, but at least they are read without error.
   */
  for (i = 0; i < numpoints; i++)
  {
    switch (points[i].type)
    {
    case BEZ_MOVE_TO:
    case BEZ_LINE_TO:
      /* real point */
      pData[4*i  ] = SCX( points[i].p1.x);
      pData[4*i+1] = SCY(-points[i].p1.y);

      /* control point (1st from next point) */
      if (i+1 < numpoints) {
        pData[4*i+2] = SCX( points[i+1].p1.x);
        pData[4*i+3] = SCY(-points[i+1].p1.y);
      }
      else {
        pData[4*i+2] = SCX( points[i].p1.x);
        pData[4*i+3] = SCY(-points[i].p1.y);
      }
      break;
    case BEZ_CURVE_TO:
      if (0 && (i+1 < numpoints)) {
        /* real point ?? */
        pData[4*i  ] = SCX( points[i].p3.x);
        pData[4*i+1] = SCY(-points[i].p3.y);
        /* control point (1st from next point) */
        pData[4*i+2] = SCX( points[i+1].p1.x);
        pData[4*i+3] = SCY(-points[i+1].p1.y);
      }
      else {
        /* need to swap these ?? */
        /* real point ?? */
        pData[4*i  ] = SCX( points[i].p2.x);
        pData[4*i+1] = SCY(-points[i].p2.y);

        /* control point ?? */
        pData[4*i+2] = SCX( points[i].p3.x);
        pData[4*i+3] = SCY(-points[i].p3.y);
      }
      break;
    }
  }

  fwrite_le(pData, sizeof(gint16), numpoints*4, renderer->file);
  g_free(pData);
}
#endif /* USE_OUR_DRAW_BEZIER */

static void
draw_string(DiaRenderer *self,
            const char *text,
            Point *pos, Alignment alignment,
            Color *colour)
{
  WpgRenderer *renderer = WPG_RENDERER (self);
  gint16 len;
  WPGPoint pt;

  len = strlen(text);

  DIAG_NOTE(g_message("draw_string(%d) %f,%f %s", 
            len, pos->x, pos->y, text));

  if (len < 1) return; /* shouldn't this be handled by Dia's core ? */

  renderer->TextStyle.YAlign = 3; /* bottom ??? */

  switch (alignment) {
  case ALIGN_LEFT:
    renderer->TextStyle.XAlign = 0;
    break;
  case ALIGN_CENTER:
    renderer->TextStyle.XAlign = 1;
    break;
  case ALIGN_RIGHT:
    renderer->TextStyle.XAlign = 2;
    break;
  }

  renderer->TextStyle.Color = LookupColor(renderer, colour);
  renderer->TextStyle.Angle = 0;

  renderer->TextStyle.Width = renderer->TextStyle.Height * 0.6; /* ??? */

  WriteRecHead(renderer, WPG_TEXTSTYLE, sizeof(WPGTextStyle));

#if 0
  fwrite(&renderer->TextStyle, sizeof(WPGTextStyle), 1, renderer->file);
#else
  g_assert(22 == sizeof(WPGTextStyle));
  fwrite_le(&renderer->TextStyle.Width, sizeof(guint16), 1, renderer->file);
  fwrite_le(&renderer->TextStyle.Height, sizeof(guint16), 1, renderer->file);
  fwrite(&renderer->TextStyle.Reserved, 1, sizeof(renderer->TextStyle.Reserved), renderer->file);
  fwrite_le(&renderer->TextStyle.Font, sizeof(guint16), 1, renderer->file);
  fwrite(&renderer->TextStyle.Reserved2, sizeof(guint8), 1, renderer->file);
  fwrite(&renderer->TextStyle.XAlign, 1, 1, renderer->file);
  fwrite(&renderer->TextStyle.YAlign, 1, 1, renderer->file);
  fwrite(&renderer->TextStyle.Color, 1, 1, renderer->file);
  fwrite_le(&renderer->TextStyle.Angle, sizeof(guint16), 1, renderer->file);
#endif
  pt.x = SCX(pos->x);
  pt.y = SCY(-pos->y);

  WriteRecHead(renderer, WPG_TEXT, 3*sizeof(gint16) + len);
  fwrite_le(&len, sizeof(gint16), 1, renderer->file);
  fwrite_le(&pt.x,  sizeof(guint16), 1, renderer->file);
  fwrite_le(&pt.y,  sizeof(guint16), 1, renderer->file);
  fwrite(text, 1, len, renderer->file);
}

static void
draw_image(DiaRenderer *self,
           Point *point,
           real width, real height,
           DiaImage *image)
{
  WpgRenderer *renderer = WPG_RENDERER (self);
  WPGBitmap2 bmp;
  guint8 * pDiaImg = NULL, * pOut = NULL, * pIn = NULL, * p = NULL;
  guint8 b_1 = 0, b = 0, cnt;
  int x, y, stride;

  bmp.Angle  = 0;
  bmp.Left   = SCX(point->x);
  bmp.Top    = SCY(-point->y - height);
  bmp.Right  = SCX(point->x + width);
  bmp.Bottom = SCY(-point->y);

  bmp.Width  = dia_image_width(image);
  bmp.Height = dia_image_height(image);
  bmp.Depth  = 8; /* maximum allowed */

  bmp.Xdpi = 72; /* ??? */
  bmp.Ydpi = 72;

  DIAG_NOTE(g_message("draw_image %fx%f [%d,%d] @%f,%f", 
            width, height, bmp.Width, bmp.Height, point->x, point->y));

  pDiaImg = dia_image_rgb_data(image);
  stride = dia_image_rowstride(image);
  pOut = g_new(guint8, bmp.Width * bmp.Height * 2); /* space for worst case RLE */
  p = pOut;

  for (y = 0; y < bmp.Height; y++)
  {
    /* starting from last line but left to right */
    pIn = pDiaImg + stride * (bmp.Height - 1 - y);
    cnt = 0; /* reset with every line */
    for (x = 0; x < bmp.Width; x ++)
    {
      /* simple color reduction to default palette */
      b = (((CC_LEN - 1) * pIn[0]) / 255) /* red */
        + (((CC_LEN - 1) * pIn[1]) / 255) * CC_LEN /* green */
        + (((CC_LEN - 1) * pIn[2]) / 255) * CC_LEN * CC_LEN /* blue */
        + WPG_NUM_DEF_COLORS;

      pIn += 3; /* increase read pointer */

      if (cnt > 0)
      {
        if ((b == b_1) && (cnt < 127)) 
          cnt++; /* increase counter*/
        else
        {
          *p++ = (cnt | 0x80); /* write run length and value */
          *p++ = b_1;
          cnt = 1; /* reset */
          b_1 = b;
        }
      }
      else
      {
        b_1 = b;
        cnt = 1;
      }
    }
    *p++ = (cnt | 0x80); /* write last value(s) */
    *p++ = b;
  }
  DIAG_NOTE(g_message( "Width x Height: %d RLE: %d", 
	      bmp.Width * bmp.Height, p - pOut));

  if ((p - pOut) > 32767) {
    message_warning("WmfRenderer : Bitmap size exceeds blocksize. Ignored.");
  }
  else {
    WriteRecHead(renderer, WPG_BITMAP2, sizeof(WPGBitmap2) + (p - pOut));
#if 0
    fwrite(&bmp, sizeof(WPGBitmap2), 1, renderer->file);
#else
    g_assert(20 == sizeof(WPGBitmap2));
    fwrite(&bmp, sizeof(guint16), sizeof(WPGBitmap2) / sizeof(guint16), renderer->file);
#endif
    fwrite(pOut, sizeof(guint8), p - pOut, renderer->file);
  }
#if 0
  /* RLE diagnose */
  {
    FILE* f;
    int i, j;
    f = g_fopen("c:\\temp\\wpg_bmp.raw", "wb");
    j = p - pOut;
    for (i = 0; i < j; i+=2)
    {
      for (x = 0; x < (pOut[i] & 0x7F); x++) 
        fwrite(&pOut[i+1], 1, 1, f);
    }
    fclose(f);
  }
#endif
  g_free(pDiaImg);
  g_free(pOut);
}

/* gobject boiler plate */
static void wpg_renderer_class_init (WpgRendererClass *klass);

static gpointer parent_class = NULL;

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

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

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

static void
wpg_renderer_finalize (GObject *object)
{
  WpgRenderer *wpg_renderer = WPG_RENDERER (object);

  if (wpg_renderer->file)
    fclose(wpg_renderer->file);
  wpg_renderer->file = NULL;

  G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
wpg_renderer_class_init (WpgRendererClass *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 = wpg_renderer_finalize;

  /* renderer members */
  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->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_string  = draw_string;
  renderer_class->draw_image   = draw_image;

  /* medium level functions */
  renderer_class->draw_rect = draw_rect;
  renderer_class->draw_polyline  = draw_polyline;
  renderer_class->draw_polygon   = draw_polygon;

#ifdef USE_OUR_DRAW_BEZIER
  /* The bezier implementation above does not match, use base class approximation */
  renderer_class->draw_bezier   = draw_bezier;
#endif
  /* Implemented by base class approximation
  renderer_class->fill_bezier   = fill_bezier;
   */
}

/* dia export funtion */
static void
export_data(DiagramData *data, const gchar *filename, 
            const gchar *diafilename, void* user_data)
{
  WpgRenderer *renderer;
  FILE *file;
  Rectangle *extent;
  real width, height;

  file = g_fopen(filename, "wb"); /* "wb" for binary! */

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

  renderer = g_object_new (WPG_TYPE_RENDERER, NULL);

  renderer->file = file;

  extent = &data->extents;

  /* use extents */
  DIAG_NOTE(g_message("export_data extents %f,%f -> %f,%f", 
            extent->left, extent->top, extent->right, extent->bottom));

  width  = extent->right - extent->left;
  height = extent->bottom - extent->top;
#if 0
  /* extend to use full range */
  renderer->Scale = 0.001;
  if (width > height)
    while (renderer->Scale * width < 3276.7) renderer->Scale *= 10.0;
  else
    while (renderer->Scale * height < 3276.7) renderer->Scale *= 10.0;
#else
  /* scale from Dia's cm to WPU (1/1200 inch) */
  renderer->Scale = 1200.0 / 2.54;
  /* avoid int16 overflow */
  if (width > height)
    while (renderer->Scale * width > 32767) renderer->Scale /= 10.0;
  else
    while (renderer->Scale * height > 32767) renderer->Scale /= 10.0;
  renderer->XOffset = - extent->left;
  renderer->YOffset = - extent->top;
#endif
  renderer->Box.Width  = width * renderer->Scale;
  renderer->Box.Height = height * renderer->Scale;
  renderer->Box.Flag   = 0;
  renderer->Box.Version = 0;

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

  g_object_unref(renderer);
}

#if WPG_WITH_IMPORT
/*
 * Import (under construction)
 */
static void
import_object(DiaRenderer* self, DiagramData *dia,
              WPG_Type type, int iSize, guchar* pData)
{
  WpgRenderer *renderer = WPG_RENDERER(self);
  WPGPoint* pts = NULL;
  gint16* pInt16 = NULL;
  int    iNum = 0;
  gint32 iPre51 = 0;

  switch (type) {
  case WPG_LINE:
    iNum = 2;
    pts = (WPGPoint*)pData;
    break;
  case WPG_POLYLINE:
    pInt16 = (gint16*)pData;
    iNum = pInt16[0];
    pts = (WPGPoint*)(pData + sizeof(gint16));
    break;
  case WPG_RECTANGLE:
    iNum = 2;
    pts = (WPGPoint*)pData;
    break;
  case WPG_POLYGON:
    pInt16 = (gint16*)pData;
    iNum = pInt16[0];
    pts = (WPGPoint*)(pData + sizeof(gint16));
    break;
  case WPG_ELLIPSE:
    {
      WPGEllipse* pEll;
      pEll = (WPGEllipse*)pData;
    }
    break;
  case WPG_POLYCURVE:
    iPre51 = *((gint32*)pData);
    pInt16 = (gint16*)pData;
    iNum = pInt16[2];
    pts = (WPGPoint*)(pData + 3*sizeof(gint16));
    DIAG_NOTE(g_message("POLYCURVE Num pts %d Pre51 %d", iNum, iPre51));
    break;
  } /* switch */
  DIAG_NOTE(g_message("Type %d Num pts %d Size %d", type, iNum, iSize));
} 

static gboolean
import_data (const gchar *filename, DiagramData *dia, void* user_data)
{
  FILE* f;
  gboolean bRet;
  WPGHead8 rh;

  f = g_fopen(filename, "rb");

  if (NULL == f) {
    message_error(_("Couldn't open: '%s' for reading.\n"), 
		  dia_message_filename(filename));
    bRet = FALSE;
  }
  
  /* check header */
  if (bRet) {
    WPGFileHead fhead;
    bRet = ( (1 == fread(&fhead, sizeof(WPGFileHead), 1, f))
            && fhead.fid[0] == 255 && fhead.fid[1] == 'W'
            && fhead.fid[2] == 'P' && fhead.fid[3] == 'C'
            && (1 == fhead.MajorVersion) && (0 == fhead.MinorVersion));
    if (!bRet)
      message_error(_("File: %s type/version unsupported.\n"), 
		    dia_message_filename(filename));
  }

  if (bRet) {
    int iSize;
    gint16 i16, iNum16;
    guint8 i8;
    WpgRenderer *ren = g_object_new (WPG_TYPE_RENDERER, NULL);

    ren->pPal = g_new0(WPGColorRGB, 256);

    DIAG_NOTE(g_message("Parsing: %s ", filename));

    do {
      if (1 == fread(&rh, sizeof(WPGHead8), 1, f)) {
        if (rh.Size < 0xFF)
          iSize = rh.Size;
        else {
          bRet = (1 == fread(&i16, sizeof(guint16), 1, f));
          if (0x8000 & i16) {
            DIAG_NOTE(g_print("Large Object: hi:lo %04X", (int)i16));
            iSize = i16 << 16;
            /* Reading large objects involves major uglyness. Instead of getting 
             * one size, as implied by "Encyclopedia of Graphics File Formats",
             * it would require putting together small chunks of data to one large 
             * object. The criteria when to stop isn't absolutely clear.
             */
            iSize = 0;
            bRet = (1 == fread(&i16, sizeof(guint16), 1, f));
            DIAG_NOTE(g_print("Large Object: %d\n", (int)i16));
            iSize += i16;
#if 1
            /* Ignore this large objec part */
            fseek(f, iSize, SEEK_CUR);
            continue;
#endif
          }
          else
            iSize = i16; 
        }
      } else
        iSize = 0;

      /* DIAG_NOTE(g_message("Type %d Size %d", rh.Type, iSize)); */
      if (iSize > 0) {
        switch (rh.Type) {
        case WPG_FILLATTR:
          bRet = (1 == fread(&ren->FillAttr, sizeof(WPGFillAttr), 1, f));
          break;
        case WPG_LINEATTR:
          bRet = (1 == fread(&ren->LineAttr, sizeof(WPGLineAttr), 1, f));
          break;
        case WPG_START:
          bRet = (1 == fread(&ren->Box, sizeof(WPGStartData), 1, f));
          break;
        case WPG_STARTWPG2:
          /* not sure if this is the right thing to do */
          bRet &= (1 == fread(&i8, sizeof(gint8), 1, f));
          bRet &= (1 == fread(&i16, sizeof(gint16), 1, f));
          DIAG_NOTE(g_message("Ignoring tag WPG_STARTWPG2, Size %d\n Type? %d Size? %d", 
                    iSize, (int)i8, i16));
          fseek(f, iSize - 3, SEEK_CUR);
          break;
        case WPG_LINE:
        case WPG_POLYLINE:
        case WPG_RECTANGLE:
        case WPG_POLYGON:
        case WPG_POLYCURVE:
        case WPG_ELLIPSE:
          {
            guchar* pData;

            pData = g_new(guchar, iSize);
            bRet = (iSize == (int)fread(pData, 1, iSize, f));
            import_object(DIA_RENDERER(ren), dia, rh.Type, iSize, pData);
            g_free(pData);
          }
          break;
        case WPG_COLORMAP:
          bRet &= (1 == fread(&i16, sizeof(gint16), 1, f));
          bRet &= (1 == fread(&iNum16, sizeof(gint16), 1, f));
          if (iNum16 != (gint16)((iSize - 2) / sizeof(WPGColorRGB)))
            g_warning("WpgImporter : colormap size/header mismatch.");
          if (i16 >= 0 && i16 <= iSize) {
            bRet &= (iNum16 == (int)fread(&ren->pPal[i16], sizeof(WPGColorRGB), iNum16, f));
          }
          else {
            g_warning("WpgImporter : start color map index out of range.");
            bRet &= (iNum16 == (int)fread(ren->pPal, sizeof(WPGColorRGB), iNum16, f));
          }
          break;
        case WPG_BITMAP2:
          {
            WPGBitmap2 bmp;

            fread(&bmp, sizeof(WPGBitmap2), 1, f);
            DIAG_NOTE(g_message("Bitmap %dx%d %d bits @%d,%d %d,%d.", 
                      bmp.Width, bmp.Height, bmp.Depth, 
                      bmp.Left, bmp.Top, bmp.Right, bmp.Bottom));
            fseek(f, iSize - sizeof(WPGBitmap2), SEEK_CUR);
          }
          break;
        default:
          fseek(f, iSize, SEEK_CUR);
          g_warning ("Unknown Type %d Size %d.", (int)rh.Type, iSize);
        } /* switch */
      } /* if iSize */
      else {
        if (WPG_END != rh.Type)
          g_warning("Size 0 on Type %d, expecting WPG_END\n", (int)rh.Type);
      }
    }
    while ((iSize > 0) && (bRet));

    if (!bRet)
      g_warning("WpgImporter : unexpected eof. Type %d, Size %d.\n",
                rh.Type, iSize);
    if (ren->pPal) 
      g_free(ren->pPal);
    g_object_unref (ren);
  } /* bRet */

  if (f) fclose(f);
  return bRet;
}

#endif /* WPG_WITH_IMPORT */

static const gchar *extensions[] = { "wpg", NULL };
static DiaExportFilter my_export_filter = {
    N_("WPG"),
    extensions,
    export_data
};

#if WPG_WITH_IMPORT
DiaImportFilter my_import_filter = {
    N_("WPG"),
    extensions,
    import_data
};
#endif /* WPG_WITH_IMPORT */

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

static void
_plugin_unload (PluginInfo *info)
{
  filter_unregister_export(&my_export_filter);
#if WPG_WITH_IMPORT
  filter_unregister_import(&my_import_filter);
#endif
}

DIA_PLUGIN_CHECK_INIT

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

  filter_register_export(&my_export_filter);
#if WPG_WITH_IMPORT
  filter_register_import(&my_import_filter);
#endif

  return DIA_PLUGIN_INIT_OK;
}
