/* Dia -- an diagram creation/manipulation program
 * Copyright (C) 1998, 1999 Alexander Larsson
 *
 * Custom Objects -- objects defined in XML rather than C.
 * Copyright (C) 1999 James Henstridge.
 *
 * 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.
 */
#include <config.h>

#include <stdlib.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xmlmemory.h>
#include <float.h>
#include <string.h>
#include "dia_xml_libxml.h"
#include "shape_info.h"
#include "custom_util.h"
#include "custom_object.h"
#include "dia_image.h"
#include "message.h"
#include "intl.h"

#define FONT_HEIGHT_DEFAULT 1
#define TEXT_ALIGNMENT_DEFAULT ALIGN_CENTER

static ShapeInfo *load_shape_info(const gchar *filename);

static GHashTable *name_to_info = NULL;

ShapeInfo *
shape_info_load(const gchar *filename)
{
  ShapeInfo *info = load_shape_info(filename);

  if (!info)
    return NULL;
  if (!name_to_info)
    name_to_info = g_hash_table_new(g_str_hash, g_str_equal);
  g_hash_table_insert(name_to_info, info->name, info);
  g_assert(shape_info_getbyname(info->name)==info);

  return info;
}

ShapeInfo *
shape_info_get(ObjectNode obj_node)
{
  ShapeInfo *info = NULL;
  char *str;

  str = xmlGetProp(obj_node, "type");
  if (str && name_to_info) {
    info = g_hash_table_lookup(name_to_info, str);
    xmlFree(str);
  }
  return info;
}

ShapeInfo *shape_info_getbyname(const gchar *name)
{
  if (name && name_to_info) {
    return g_hash_table_lookup(name_to_info,name);
  }
  return NULL;
}

/**
 * shape_info_realise :
 * @info : the shape to realise
 * 
 * Puts the ShapeInfo into a form suitable for actual use (lazy loading)
 *
 */

void shape_info_realise(ShapeInfo* info)
{
  GList* tmp;
  
  for (tmp = info->display_list; tmp != NULL; tmp = tmp->next) {
    GraphicElement *el = tmp->data;
    if (el->type == GE_TEXT) {
          /* set default values for text style */
      if (!el->text.s.font_height)
        el->text.s.font_height = FONT_HEIGHT_DEFAULT;
      if (!el->text.s.font)
        el->text.s.font = dia_font_new_from_style(DIA_FONT_SANS,1.0);
      if (el->text.s.alignment == -1)
        el->text.s.alignment = TEXT_ALIGNMENT_DEFAULT;
      if (!el->text.object) {
        el->text.object = new_text(el->text.string,
                                   el->text.s.font,
                                   el->text.s.font_height,
                                   &el->text.anchor,
                                   &color_black,
                                   el->text.s.alignment);
      }
      text_calc_boundingbox(el->text.object, &el->text.text_bounds);
    }
  }  
}


static void
parse_path(ShapeInfo *info, const char *path_str, DiaSvgStyle *s, const char* filename)
{
  GArray *points;
  gchar *pathdata = (gchar *)path_str, *unparsed;
  gboolean closed = FALSE;

  do {
    points = dia_svg_parse_path (pathdata, &unparsed, &closed);

    if (points->len > 0) {
      if (g_array_index(points, BezPoint, 0).type != BEZ_MOVE_TO) {
        message_warning (_("The file '%s' has invalid path data.\n"
	                   "svg:path data must start with moveto."),
			   dia_message_filename(filename));
      } else if (closed) {
        /* if there is some unclosed commands, add them as a GE_SHAPE */
	GraphicElementPath *el = g_malloc(sizeof(GraphicElementPath) +
					    points->len * sizeof(BezPoint));
	el->type = GE_SHAPE;
	dia_svg_style_init (&el->s, s);
	el->npoints = points->len;
	memcpy((char *)el->points, points->data, points->len*sizeof(BezPoint));
	info->display_list = g_list_append(info->display_list, el);
      } else {
        /* if there is some unclosed commands, add them as a GE_PATH */
	GraphicElementPath *el = g_malloc(sizeof(GraphicElementPath) +
				          points->len * sizeof(BezPoint));
	el->type = GE_PATH;
	dia_svg_style_init (&el->s, s);
	el->npoints = points->len;
	memcpy((char *)el->points, points->data, points->len*sizeof(BezPoint));
	info->display_list = g_list_append(info->display_list, el);
      }
      g_array_set_size (points, 0);
    }
    pathdata = unparsed;
    unparsed = NULL;
  } while (pathdata);

  g_array_free (points, TRUE);
}

static void
parse_svg_node(ShapeInfo *info, xmlNodePtr node, xmlNsPtr svg_ns,
               DiaSvgStyle *style, const gchar *filename)
{
  xmlChar *str;

      /* walk SVG node ... */
  for (node = node->xmlChildrenNode; node != NULL; node = node->next) {
    GraphicElement *el = NULL;
    DiaSvgStyle s;

    if (xmlIsBlankNode(node)) continue;
    if (node->type != XML_ELEMENT_NODE || node->ns != svg_ns)
      continue;
    dia_svg_style_init (&s, style);
    dia_svg_parse_style(node, &s);
    if (!strcmp(node->name, "line")) {
      GraphicElementLine *line = g_new0(GraphicElementLine, 1);

      el = (GraphicElement *)line;
      line->type = GE_LINE;
      str = xmlGetProp(node, "x1");
      if (str) {
        line->p1.x = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "y1");
      if (str) {
        line->p1.y = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "x2");
      if (str) {
        line->p2.x = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "y2");
      if (str) {
        line->p2.y = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
    } else if (!strcmp(node->name, "polyline")) {
      GraphicElementPoly *poly;
      GArray *arr = g_array_new(FALSE, FALSE, sizeof(real));
      real val, *rarr;
      char *tmp;
      int i;

      tmp = str = xmlGetProp(node, "points");
      while (tmp[0] != '\0') {
            /* skip junk */
        while (tmp[0] != '\0' && !g_ascii_isdigit(tmp[0]) && tmp[0]!='.'&&tmp[0]!='-')
          tmp++;
        if (tmp[0] == '\0') break;
        val = g_ascii_strtod(tmp, &tmp);
        g_array_append_val(arr, val);
      }
      xmlFree(str);
      val = 0;
      if (arr->len % 2 == 1) 
        g_array_append_val(arr, val);
      poly = g_malloc0(sizeof(GraphicElementPoly) + arr->len/2*sizeof(Point));
      el = (GraphicElement *)poly;
      poly->type = GE_POLYLINE;
      poly->npoints = arr->len / 2;
      rarr = (real *)arr->data;
      for (i = 0; i < poly->npoints; i++) {
        poly->points[i].x = rarr[2*i];
        poly->points[i].y = rarr[2*i+1];
      }
      g_array_free(arr, TRUE);
    } else if (!strcmp(node->name, "polygon")) {
      GraphicElementPoly *poly;
      GArray *arr = g_array_new(FALSE, FALSE, sizeof(real));
      real val, *rarr;
      char *tmp;
      int i;

      tmp = str = xmlGetProp(node, "points");
      while (tmp[0] != '\0') {
            /* skip junk */
        while (tmp[0] != '\0' && !g_ascii_isdigit(tmp[0]) && tmp[0]!='.'&&tmp[0]!='-')
          tmp++;
        if (tmp[0] == '\0') break;
        val = g_ascii_strtod(tmp, &tmp);
        g_array_append_val(arr, val);
      }
      xmlFree(str);
      val = 0;
      if (arr->len % 2 == 1) 
        g_array_append_val(arr, val);
      poly = g_malloc0(sizeof(GraphicElementPoly) + arr->len/2*sizeof(Point));
      el = (GraphicElement *)poly;
      poly->type = GE_POLYGON;
      poly->npoints = arr->len / 2;
      rarr = (real *)arr->data;
      for (i = 0; i < poly->npoints; i++) {
        poly->points[i].x = rarr[2*i];
        poly->points[i].y = rarr[2*i+1];
      }
      g_array_free(arr, TRUE);
    } else if (!strcmp(node->name, "rect")) {
      GraphicElementRect *rect = g_new0(GraphicElementRect, 1);

      el = (GraphicElement *)rect;
      rect->type = GE_RECT;
      str = xmlGetProp(node, "x");
      if (str) {
        rect->corner1.x = g_ascii_strtod(str, NULL);
        xmlFree(str);
      } else rect->corner1.x = 0;
      str = xmlGetProp(node, "y");
      if (str) {
        rect->corner1.y = g_ascii_strtod(str, NULL);
        xmlFree(str);
      } else rect->corner1.y = 0;
      str = xmlGetProp(node, "width");
      if (str) {
        rect->corner2.x = rect->corner1.x + g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "height");
      if (str) {
        rect->corner2.y = rect->corner1.y + g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
    } else if (!strcmp(node->name, "text")) {

      GraphicElementText *text = g_new(GraphicElementText, 1);
      el = (GraphicElement *)text;
      text->type = GE_TEXT;
      text->object = NULL;
      str = xmlGetProp(node, "x");
      if (str) {
        text->anchor.x = g_ascii_strtod(str, NULL);
        xmlFree(str);
      } else text->anchor.x = 0;
      str = xmlGetProp(node, "y");
      if (str) {
        text->anchor.y = g_ascii_strtod(str, NULL);
        xmlFree(str);
      } else text->anchor.y = 0;

      str = xmlNodeGetContent(node);
      if (str) {
        text->string = g_strdup(str);
        xmlFree(str);
      } else text->string = g_strdup("");
    } else if (!strcmp(node->name, "circle")) {
      GraphicElementEllipse *ellipse = g_new0(GraphicElementEllipse, 1);

      el = (GraphicElement *)ellipse;
      ellipse->type = GE_ELLIPSE;
      str = xmlGetProp(node, "cx");
      if (str) {
        ellipse->center.x = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "cy");
      if (str) {
        ellipse->center.y = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "r");
      if (str) {
        ellipse->width = ellipse->height = 2 * g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
    } else if (!strcmp(node->name, "ellipse")) {
      GraphicElementEllipse *ellipse = g_new0(GraphicElementEllipse, 1);

      el = (GraphicElement *)ellipse;
      ellipse->type = GE_ELLIPSE;
      str = xmlGetProp(node, "cx");
      if (str) {
        ellipse->center.x = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "cy");
      if (str) {
        ellipse->center.y = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "rx");
      if (str) {
        ellipse->width = 2 * g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "ry");
      if (str) {
        ellipse->height = 2 * g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
    } else if (!strcmp(node->name, "path")) {
      str = xmlGetProp(node, "d");
      if (str) {
        parse_path(info, str, &s, filename);
        xmlFree(str);
      }
    } else if (!strcmp(node->name, "image")) {
      GraphicElementImage *image = g_new0(GraphicElementImage, 1);

      el = (GraphicElement *)image;
      image->type = GE_IMAGE;
      str = xmlGetProp(node, "x");
      if (str) {
        image->topleft.x = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "y");
      if (str) {
        image->topleft.y = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "width");
      if (str) {
        image->width = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "height");
      if (str) {
        image->height = g_ascii_strtod(str, NULL);
        xmlFree(str);
      }
      str = xmlGetProp(node, "xlink:href");
      if (!str) /* this doesn't look right but it appears to work w/o namespace --hb */
        str = xmlGetProp(node, "href");
      if (str) {
        gchar *imgfn = g_filename_from_uri(str, NULL, NULL);

        if (!imgfn)
	  /* despite it's name it ensures an absolute filename */
          imgfn = custom_get_relative_filename(filename, str);

        image->image = dia_image_load(imgfn);
        /* w/o the image we would crash later */
        if (!image->image) {
          g_warning("failed to load image file %s", imgfn ? imgfn : "(NULL)");
          image->image = dia_image_get_broken();
        }
        g_free(imgfn);
        xmlFree(str);
      }
    } else if (!strcmp(node->name, "g")) {
          /* add elements from the group element */
      parse_svg_node(info, node, svg_ns, &s, filename);
    }
    if (el) {
      el->any.s = s;
      if (el->any.s.font)
        el->any.s.font = g_object_ref(s.font);
      info->display_list = g_list_append(info->display_list, el);
    }
    if (s.font)
      dia_font_unref (s.font);
  }
}

static void
check_point(ShapeInfo *info, Point *pt)
{
  if (pt->x < info->shape_bounds.left)
    info->shape_bounds.left = pt->x;
  if (pt->x > info->shape_bounds.right)
    info->shape_bounds.right = pt->x;
  if (pt->y < info->shape_bounds.top)
    info->shape_bounds.top = pt->y;
  if (pt->y > info->shape_bounds.bottom)
    info->shape_bounds.bottom = pt->y;
}

static void
update_bounds(ShapeInfo *info)
{
  GList *tmp;
  Point pt;
  for (tmp = info->display_list; tmp; tmp = tmp->next) {
    GraphicElement *el = tmp->data;
    int i;

    switch (el->type) {
    case GE_LINE:
      check_point(info, &(el->line.p1));
      check_point(info, &(el->line.p2));
      break;
    case GE_POLYLINE:
      for (i = 0; i < el->polyline.npoints; i++)
	check_point(info, &(el->polyline.points[i]));
      break;
    case GE_POLYGON:
      for (i = 0; i < el->polygon.npoints; i++)
	check_point(info, &(el->polygon.points[i]));
      break;
    case GE_RECT:
      check_point(info, &(el->rect.corner1));
      check_point(info, &(el->rect.corner2));
      break;
    case GE_TEXT:
      check_point(info, &(el->text.anchor));
      break;
    case GE_ELLIPSE:
      pt = el->ellipse.center;
      pt.x -= el->ellipse.width / 2.0;
      pt.y -= el->ellipse.height / 2.0;
      check_point(info, &pt);
      pt.x += el->ellipse.width;
      pt.y += el->ellipse.height;
      check_point(info, &pt);
      break;
    case GE_PATH:
    case GE_SHAPE:
      for (i = 0; i < el->path.npoints; i++)
	switch (el->path.points[i].type) {
	case BEZ_CURVE_TO:
	  check_point(info, &el->path.points[i].p3);
	  check_point(info, &el->path.points[i].p2);
	case BEZ_MOVE_TO:
	case BEZ_LINE_TO:
	  check_point(info, &el->path.points[i].p1);
	}
      break;
    case GE_IMAGE:
      check_point(info, &(el->image.topleft));
      pt.x = el->image.topleft.x + el->image.width;
      pt.y = el->image.topleft.y + el->image.height;
      check_point(info, &pt);
      break;
    }
  }
}

static ShapeInfo *
load_shape_info(const gchar *filename)
{
  xmlDocPtr doc = xmlDoParseFile(filename);
  xmlNsPtr shape_ns, svg_ns;
  xmlNodePtr node, root, ext_node = NULL;
  ShapeInfo *info;
  char *tmp;
  int i;
  
  if (!doc) {
    g_warning("parse error for %s", filename);
    return NULL;
  }
  /* skip (emacs) comments */
  root = doc->xmlRootNode;
  while (root && (root->type != XML_ELEMENT_NODE)) root = root->next;
  if (!root) return NULL;
  if (xmlIsBlankNode(root)) return NULL;

  if (!(shape_ns = xmlSearchNsByHref(doc, root,
		"http://www.daa.com.au/~james/dia-shape-ns"))) {
    xmlFreeDoc(doc);
    g_warning("could not find shape namespace");
    return NULL;
  }
  if (!(svg_ns = xmlSearchNsByHref(doc, root, "http://www.w3.org/2000/svg"))) {
    xmlFreeDoc(doc);
    g_warning("could not find svg namespace");
    return NULL;
  }
  if (root->ns != shape_ns || strcmp(root->name, "shape")) {
    g_warning("root element was %s -- expecting shape", root->name);
    xmlFreeDoc(doc);
    return NULL;
  }

  info = g_new0(ShapeInfo, 1);
  info->shape_bounds.top = DBL_MAX;
  info->shape_bounds.left = DBL_MAX;
  info->shape_bounds.bottom = -DBL_MAX;
  info->shape_bounds.right = -DBL_MAX;
  info->aspect_type = SHAPE_ASPECT_FREE; 
  info->main_cp = -1;

  i = 0;
  for (node = root->xmlChildrenNode; node != NULL; node = node->next) {
    if (xmlIsBlankNode(node)) continue;
    if (node->type != XML_ELEMENT_NODE) continue;
    if (node->ns == shape_ns && !strcmp(node->name, "name")) {
      tmp = xmlNodeGetContent(node);
      g_free(info->name);
      info->name = g_strdup(tmp);
      xmlFree(tmp);
    } else if (node->ns == shape_ns && !strcmp(node->name, "icon")) {
      tmp = xmlNodeGetContent(node);
      g_free(info->icon);
      info->icon = custom_get_relative_filename(filename, tmp);
      xmlFree(tmp);
    } else if (node->ns == shape_ns && !strcmp(node->name, "connections")) {
      GArray *arr = g_array_new(FALSE, FALSE, sizeof(Point));
      xmlNodePtr pt_node;

      for (pt_node = node->xmlChildrenNode; 
           pt_node != NULL; 
           pt_node = pt_node->next) {
        if (xmlIsBlankNode(pt_node)) continue;

	if (pt_node->ns == shape_ns && !strcmp(pt_node->name, "point")) {
	  Point pt = { 0.0, 0.0 };
	  xmlChar *str;

	  str = xmlGetProp(pt_node, "x");
	  if (str) {
	    pt.x = g_ascii_strtod(str, NULL);
	    xmlFree(str);
	  }
	  str = xmlGetProp(pt_node, "y");
	  if (str) {
	    pt.y = g_ascii_strtod(str, NULL);
	    xmlFree(str);
	  }
	  g_array_append_val(arr, pt);
	  str = xmlGetProp(pt_node, "main");
	  if (str && str[0] != '\0') {
	    if (info->main_cp != -1) {
	      message_warning("More than one main connection point in %s.  Only the first one will be used.\n",
			      info->name);
	    } else {
	      info->main_cp = i;
	    }
	    xmlFree(str);
	  }
	}
	i++;
      }
      info->nconnections = arr->len;
      info->connections = (Point *)arr->data;
      g_array_free(arr, FALSE);
    } else if (node->ns == shape_ns && !strcmp(node->name, "textbox")) {
      xmlChar *str;
      
      str = xmlGetProp(node, "x1");
      if (str) {
	info->text_bounds.left = g_ascii_strtod(str, NULL);
	xmlFree(str);
      }
      str = xmlGetProp(node, "y1");
      if (str) {
	info->text_bounds.top = g_ascii_strtod(str, NULL);
	xmlFree(str);
      }
      str = xmlGetProp(node, "x2");
      if (str) {
	info->text_bounds.right = g_ascii_strtod(str, NULL);
	xmlFree(str);
      }
      str = xmlGetProp(node, "y2");
      if (str) {
	info->text_bounds.bottom = g_ascii_strtod(str, NULL);
	xmlFree(str);
      }
      info->resize_with_text = TRUE;
      str = xmlGetProp(node, "resize");
      if (str) {
        info->resize_with_text = TRUE;
        if (!strcmp(str,"no"))
          info->resize_with_text = FALSE;
	xmlFree(str);
      }
      info->text_align = ALIGN_CENTER;
      str = xmlGetProp(node, "align");
      if (str) {
	if (!strcmp(str, "left")) 
	  info->text_align = ALIGN_LEFT;
	else if (!strcmp(str, "right"))
	  info->text_align = ALIGN_RIGHT;
	xmlFree(str);
      }
      info->has_text = TRUE;
    } else if (node->ns == shape_ns && !strcmp(node->name, "aspectratio")) {
      tmp = xmlGetProp(node, "type");
      if (tmp) {
	if (!strcmp(tmp, "free"))
	  info->aspect_type = SHAPE_ASPECT_FREE;
	else if (!strcmp(tmp, "fixed"))
	  info->aspect_type = SHAPE_ASPECT_FIXED;
	else if (!strcmp(tmp, "range")) {
	  char *str;

	  info->aspect_type = SHAPE_ASPECT_RANGE;
	  info->aspect_min = 0.0;
	  info->aspect_max = G_MAXFLOAT;
	  str = xmlGetProp(node, "min");
	  if (str) {
	    info->aspect_min = g_ascii_strtod(str, NULL);
	    xmlFree(str);
	  }
	  str = xmlGetProp(node, "max");
	  if (str) {
	    info->aspect_max = g_ascii_strtod(str, NULL);
	    xmlFree(str);
	  }
	  if (info->aspect_max < info->aspect_min) {
	    real asp = info->aspect_max;
	    info->aspect_max = info->aspect_min;
	    info->aspect_min = asp;
	  }
	}
	xmlFree(tmp);
      }
    } else if (node->ns == svg_ns && !strcmp(node->name, "svg")) {
      DiaSvgStyle s = {
	1.0, DIA_SVG_COLOUR_FOREGROUND, DIA_SVG_COLOUR_NONE,
	DIA_SVG_LINECAPS_DEFAULT, DIA_SVG_LINEJOIN_DEFAULT, DIA_SVG_LINESTYLE_DEFAULT, 1.0
      };

      dia_svg_parse_style(node, &s);
      parse_svg_node(info, node, svg_ns, &s, filename);
      update_bounds(info);
    }
	else if (!strcmp(node->name, "ext_attributes")) {
      ext_node = node;
    }
  }
  /*MC 11/03 parse ext attributes if any & prepare prop tables */
  custom_setup_properties (info, ext_node);
  xmlFreeDoc(doc);
  return info;
}

void
shape_info_print(ShapeInfo *info)
{
  GList *tmp;
  int i;

  g_print("Name        : %s\n", info->name);
  g_print("Connections :\n");
  for (i = 0; i < info->nconnections; i++)
    g_print("  (%g, %g)\n", info->connections[i].x, info->connections[i].y);
  g_print("Shape bounds: (%g, %g) - (%g, %g)\n",
	  info->shape_bounds.left, info->shape_bounds.top,
	  info->shape_bounds.right, info->shape_bounds.bottom);
  if (info->has_text)
    g_print("Text bounds : (%g, %g) - (%g, %g)\n",
	    info->text_bounds.left, info->text_bounds.top,
	    info->text_bounds.right, info->text_bounds.bottom);
  g_print("Aspect ratio: ");
  switch (info->aspect_type) {
  case SHAPE_ASPECT_FREE: g_print("free\n"); break;
  case SHAPE_ASPECT_FIXED: g_print("fixed\n"); break;
  case SHAPE_ASPECT_RANGE:
    g_print("range %g - %g\n", info->aspect_min, info->aspect_max); break;
  }
  g_print("Display list:\n");
  for (tmp = info->display_list; tmp; tmp = tmp->next) {
    GraphicElement *el = tmp->data;
    int i;

    switch (el->type) {
    case GE_LINE:
      g_print("  line: (%g, %g) (%g, %g)\n", el->line.p1.x, el->line.p1.y,
	      el->line.p2.x, el->line.p2.y);
      break;
    case GE_POLYLINE:
      g_print("  polyline:");
      for (i = 0; i < el->polyline.npoints; i++)
	g_print(" (%g, %g)", el->polyline.points[i].x,
		el->polyline.points[i].y);
      g_print("\n");
      break;
    case GE_POLYGON:
      g_print("  polygon:");
      for (i = 0; i < el->polygon.npoints; i++)
	g_print(" (%g, %g)", el->polygon.points[i].x,
		el->polygon.points[i].y);
      g_print("\n");
      break;
    case GE_RECT:
      g_print("  rect: (%g, %g) (%g, %g)\n",
	      el->rect.corner1.x, el->rect.corner1.y,
	      el->rect.corner2.x, el->rect.corner2.y);
      break;
    case GE_TEXT:
      g_print("  text: (%g, %g)\n",
	      el->text.anchor.x, el->text.anchor.y);
      break;
    case GE_ELLIPSE:
      g_print("  ellipse: center=(%g, %g) width=%g height=%g\n",
	      el->ellipse.center.x, el->ellipse.center.y,
	      el->ellipse.width, el->ellipse.height);
      break;
    case GE_PATH:
      g_print("  path:");
      for (i = 0; i < el->path.npoints; i++)
	switch (el->path.points[i].type) {
	case BEZ_MOVE_TO:
	  g_print(" M (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y);
	  break;
	case BEZ_LINE_TO:
	  g_print(" L (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y);
	  break;
	case BEZ_CURVE_TO:
	  g_print(" C (%g, %g) (%g, %g) (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y, el->path.points[i].p2.x,
		  el->path.points[i].p2.y, el->path.points[i].p3.x,
		  el->path.points[i].p3.y);
	  break;
	}
      break;
    case GE_SHAPE:
      g_print("  shape:");
      for (i = 0; i < el->path.npoints; i++)
	switch (el->path.points[i].type) {
	case BEZ_MOVE_TO:
	  g_print(" M (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y);
	  break;
	case BEZ_LINE_TO:
	  g_print(" L (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y);
	  break;
	case BEZ_CURVE_TO:
	  g_print(" C (%g, %g) (%g, %g) (%g, %g)", el->path.points[i].p1.x,
		  el->path.points[i].p1.y, el->path.points[i].p2.x,
		  el->path.points[i].p2.y, el->path.points[i].p3.x,
		  el->path.points[i].p3.y);
	  break;
	}
      break;
    case GE_IMAGE :
      g_print("  image topleft=(%g, %g) width=%g height=%g file=%s\n",
              el->image.topleft.x, el->image.topleft.y,
              el->image.width, el->image.height,
              el->image.image ? dia_image_filename(el->image.image) : "(nil)");
      break;
    default:
      break;
    }
  }
  g_print("\n");
}
