/*

    This file is part of the extensible drawing editor Ipe.
    Copyright (C) 1993-2004  Otfried Cheong

    Ipe 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.

    As a special exception, you have permission to link Ipe with the
    CGAL library and distribute executables, as long as you follow the
    requirements of the Gnu General Public License in regard to all of
    the software in the executable aside from CGAL.

    Ipe 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 Ipe; if not, you can find it at
    "http://www.gnu.org/copyleft/gpl.html", or write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/
/*
 * figtoipe.cpp
 *
 * This program converts files in FIG format (as used by Xfig) to XML
 * format as used by Ipe 6.0.
 *
 * All versions of the FIG file format are documented here:
 *  "http://duke.usask.ca/~macphed/soft/fig/formats.html"
 *
 * This program can read only versions 3.0, 3.1, and 3.2.
 *
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <algorithm>

#define FIGTOIPE_VERSION "figtoipe 2004/11/20"

const int MEDIABOX_WIDTH = 595;
const int MEDIABOX_HEIGHT = 842;

// --------------------------------------------------------------------

struct Arrow {
  int iType;
  int iStyle;
  double iThickness; // 1/80 inch
  double iWidth;     // Fig units
  double iHeight;    // Fig units
};

struct Point {
  int iX, iY;
};

struct FigObject {
  int iType;
  int iSubtype;     // meaning depends on type
  int iLinestyle;   // solid, dashed, etc
  int iThickness;   // 0 means no stroke
  int iPenColor;
  int iFillColor;
  int iDepth;       // depth ordering
  int iPenStyle;    // not used by FIG
  int iAreaFill;    // how to fill: color, pattern
  double iStyle;    // length of dash/dot pattern
  int iCapStyle;
  int iJoinStyle;
  int iDirection;
  int iForwardArrow;
  Arrow iForward;
  int iBackwardArrow;
  Arrow iBackward;
  double iCenterX, iCenterY;  // center of ellipse and arc
  Point iArc1, iArc2, iArc3;
  double iAngle;    // orientation of main axis of ellipse
  Point iRadius;    // half-axes of ellipse
  int iArcBoxRadius;
  Point iPos;       // position of text
  int iFont;
  int iFontFlags;
  double iFontSize;
  std::vector<char> iString;
  std::vector<Point> iPoints;
};

// --------------------------------------------------------------------

class FigReader {
public:
  FigReader(FILE *fig) :
    iFig(fig) { }
  bool ReadHeader();
  double Magnification() const { return iMagnification; }
  double UnitsPerPoint() const { return iUnitsPerPoint; }
  bool ReadObjects();
  const std::vector<FigObject> &Objects() const { return iObjects; }
  const unsigned int *UserColors() const { return iUserColors; }
private:
  bool GetLine(char *buf);
  int GetInt();
  double GetDouble();
  Point GetPoint();
  void GetColor();
  void GetArc(FigObject &obj);
  void GetEllipse(FigObject &obj);
  void GetPolyline(FigObject &obj);
  void GetSpline(FigObject &obj);
  void GetText(FigObject &obj);
  void GetArrow(Arrow &a);
  void GetArrows(FigObject &obj);
  int ComputeDepth(unsigned int &i);

private:
  FILE *iFig;
  int iVersion;  // minor version of FIG format
  double iMagnification;
  double iUnitsPerPoint;
  std::vector<FigObject> iObjects;
  unsigned int iUserColors[512];
};

// --------------------------------------------------------------------

const int BUFSIZE = 0x100;

// skip comment lines (in the header)
bool FigReader::GetLine(char *buf)
{
  do {
    if (fgets(buf, BUFSIZE, iFig) != buf)
      return false;
  } while (buf[0] == '#');
  return true;
}

int FigReader::GetInt()
{
  int num = -1;
  fscanf(iFig, "%d", &num);
  return num;
}

double FigReader::GetDouble()
{
  double num;
  fscanf(iFig, "%lg", &num);
  return num;
}

Point FigReader::GetPoint()
{
  Point p;
  p.iX = GetInt();
  p.iY = GetInt();
  return p;
}

void FigReader::GetArrow(Arrow &a)
{
  a.iType = GetInt();
  a.iStyle = GetInt();
  a.iThickness = GetDouble();
  a.iWidth = GetDouble();
  a.iHeight = GetDouble();
}

void FigReader::GetArrows(FigObject &obj)
{
  if (obj.iForwardArrow)
    GetArrow(obj.iForward);
  if (obj.iBackwardArrow)
    GetArrow(obj.iBackward);
}

// --------------------------------------------------------------------

bool FigReader::ReadHeader()
{
  char line[BUFSIZE];
  if (fgets(line, BUFSIZE, iFig) != line)
      return false;
  if (strncmp(line, "#FIG", 4))
    return false;

  // check FIG version
  int majorVersion;
  sscanf(line + 4, "%d.%d", &majorVersion, &iVersion);
  if (majorVersion != 3) {
    fprintf(stderr, "Figtoipe supports FIG versions 3.0 - 3.2 only.\n");
    return false;
  }

  fprintf(stderr, "FIG format version %d.%d\n", majorVersion, iVersion);

  int resolution;
  if (iVersion < 2) {
    if (!GetLine(line))
      return false;
    sscanf(line, "%d", &resolution);
  }

  // skip orientation and justification
  if (!GetLine(line))
    return false;
  if (!GetLine(line))
    return false;

  bool metric = false;
  if (!GetLine(line))
    return false;
  if (!strncmp(line, "Metric", 6))
    metric = true;
  (void) metric; // not yet used

  int magnification = 100;
  if (iVersion < 2) {
    // coordinate system (not used)
    (void) GetInt();
    if (!GetLine(line))
      return false;
  } else {
    // Version 3.2:
    // papersize
    if (!GetLine(line))
      return false;
    // export and print magnification
    if (!GetLine(line))
      return false;
    sscanf(line, "%d", &magnification);
    // multi-page mode
    if (!GetLine(line))
      return false;
    // transparent color
    if (!GetLine(line))
      return false;
    // resolution and coord_system
    if (!GetLine(line))
      return false;
    int coord_system;
    sscanf(line, "%d %d", &resolution, &coord_system);
  }
  iUnitsPerPoint = (resolution / 72.0);
  iMagnification = magnification / 100.0;
  return true;
}

// link start and end of compounds together,
// and assign depth to compound object
int FigReader::ComputeDepth(unsigned int &i)
{
  if (iObjects[i].iType != 6)
    return iObjects[i++].iDepth;
  int pos = i;
  int depth = 1000;
  ++i;
  while (iObjects[i].iType != -6) {
    int od = ComputeDepth(i);
    if (od < depth) depth = od;
  }
  iObjects[pos].iDepth = depth;
  iObjects[pos].iSubtype = i;
  ++i;
  return depth;
}

// --------------------------------------------------------------------

// objects are appended to list
bool FigReader::ReadObjects()
{
  int level = 0;
  for (;;) {
    int objType = GetInt();
    if (objType == -1) { // EOF
      if (level > 0)
	return false;
      unsigned int i = 0;
      while (i < iObjects.size())
	ComputeDepth(i);
      return true;
    }
    if (objType == 0) { // user-defined color
      GetColor();
    } else {
      FigObject obj;
      obj.iType = objType;
      switch (obj.iType) {
      case 1: // ELLIPSE
	GetEllipse(obj);
	break;
      case 2: // POLYLINE
	GetPolyline(obj);
	break;
      case 3: // SPLINE
	GetSpline(obj);
	break;
      case 4: // TEXT
	GetText(obj);
	break;
      case 5: // ARC
	GetArc(obj);
	break;
      case 6: // COMPOUND
	(void) GetInt(); // read and ignore bounding box
	(void) GetInt();
	(void) GetInt();
	(void) GetInt();
	++level;
	break;
      case -6: // END of COMPOUND
	if (level == 0)
	  return false;
	--level;
	break;
      default:
	fprintf(stderr, "Unknown object type in FIG file.\n");
	return false;
      }
      iObjects.push_back(obj);
    }
  }
}

void FigReader::GetColor()
{
  int colorNum = GetInt();    // color number
  int rgb;
  fscanf(iFig," #%x", &rgb);  // RGB string in hex
  iUserColors[colorNum - 32] = rgb;
}

void FigReader::GetEllipse(FigObject &obj)
{
  obj.iSubtype = GetInt();
  obj.iLinestyle = GetInt();
  obj.iThickness = GetInt();
  obj.iPenColor = GetInt();
  obj.iFillColor = GetInt();
  obj.iDepth = GetInt();
  obj.iPenStyle = GetInt();  // not used
  obj.iAreaFill = GetInt();
  obj.iStyle = GetDouble();
  obj.iDirection = GetInt(); // always 1
  obj.iAngle = GetDouble();  // radians, the angle of the x-axis
  obj.iCenterX = GetInt();
  obj.iCenterY = GetInt();
  obj.iRadius = GetPoint();
  (void) GetPoint(); // start
  (void) GetPoint(); // end
}

void FigReader::GetPolyline(FigObject &obj)
{
  obj.iSubtype = GetInt();
  obj.iLinestyle = GetInt();
  obj.iThickness = GetInt();
  obj.iPenColor = GetInt();
  obj.iFillColor = GetInt();
  obj.iDepth = GetInt();
  obj.iPenStyle = GetInt(); // not used
  obj.iAreaFill = GetInt();
  obj.iStyle = GetDouble();
  obj.iJoinStyle = GetInt();
  obj.iCapStyle = GetInt();
  obj.iArcBoxRadius = GetInt();
  obj.iForwardArrow = GetInt();
  obj.iBackwardArrow = GetInt();
  int nPoints = GetInt();
  GetArrows(obj);
  if (obj.iSubtype == 5) { // Imported image
    // orientation and filename
    fscanf(iFig, "%*d %*s");
  }
  for (int i = 0; i < nPoints; ++i) {
    Point p = GetPoint();
    obj.iPoints.push_back(p);
  }
}


void FigReader::GetSpline(FigObject &obj)
{
  /* 0: opened approximated spline
     1: closed approximated spline
     2: opened interpolated spline
     3: closed interpolated spline
     4: opened x-spline (FIG 3.2)
     5: closed x-spline (FIG 3.2)
  */
  obj.iSubtype = GetInt();
  obj.iLinestyle = GetInt();
  obj.iThickness = GetInt();
  obj.iPenColor = GetInt();
  obj.iFillColor = GetInt();
  obj.iDepth = GetInt();
  obj.iPenStyle = GetInt(); // not used
  obj.iAreaFill = GetInt();
  obj.iStyle = GetDouble();
  obj.iCapStyle = GetInt();
  obj.iForwardArrow = GetInt();
  obj.iBackwardArrow = GetInt();

  int nPoints = GetInt();
  GetArrows(obj);

  for (int i = 0; i < nPoints; ++i) {
    Point p = GetPoint();
    obj.iPoints.push_back(p);
  }

  if (iVersion == 2) {
    // shape factors exist in FIG 3.2 only
    for (int i = 0; i < nPoints; ++i) {
      (void) GetDouble(); // double shapeFactor
    }
  } else {
    if (obj.iSubtype > 1) {
      for (int i = 0; i < nPoints; ++i) {
	(void) GetDouble(); // double lx
	(void) GetDouble(); // double ly
	(void) GetDouble(); // double rx
	(void) GetDouble(); // double ry
      }
    }
  }
}

void FigReader::GetText(FigObject &obj)
{
  obj.iSubtype = GetInt();
  obj.iThickness = 1;       // stroke
  obj.iPenColor = GetInt();
  obj.iDepth = GetInt();
  obj.iPenStyle = GetInt(); // not used
  obj.iFont = GetInt();
  obj.iFontSize = GetDouble();
  obj.iAngle = GetDouble();
  obj.iFontFlags = GetInt();
  (void) GetDouble(); // height
  (void) GetDouble(); // length
  obj.iPos = GetPoint();
  // skip blank
  fgetc(iFig);
  std::vector<char> string;
  for (;;) {
    int ch = fgetc(iFig);
    if (ch == EOF)
      break;
    if (ch < 0x80) {
      string.push_back(char(ch));
    } else {
      // convert to UTF-8
      string.push_back(char(0xc0 + ((ch >> 6) & 0x3)));
      string.push_back(char(ch & 0x3f));
    }
    if (string.size() >= 4 &&
	!strncmp(&string[string.size() - 4], "\\001", 4)) {
      string.resize(string.size() - 4);
      break;
    }
  }
  string.push_back('\0');
  obj.iString = string;
}

void FigReader::GetArc(FigObject &obj)
{
  obj.iSubtype = GetInt();
  obj.iLinestyle = GetInt();
  obj.iThickness = GetInt();
  obj.iPenColor = GetInt();
  obj.iFillColor = GetInt();
  obj.iDepth = GetInt();
  obj.iPenStyle = GetInt();  // not used
  obj.iAreaFill = GetInt();
  obj.iStyle = GetDouble();
  obj.iCapStyle = GetInt();
  obj.iDirection = GetInt();
  obj.iForwardArrow = GetInt();
  obj.iBackwardArrow = GetInt();
  obj.iCenterX = GetDouble();
  obj.iCenterY = GetDouble();
  obj.iArc1 = GetPoint();
  obj.iArc2 = GetPoint();
  obj.iArc3 = GetPoint();
  GetArrows(obj);
}

// --------------------------------------------------------------------

unsigned int ColorTable[] = {
  0x000000, 0x0000ff, 0x00ff00, 0x00ffff,
  0xff0000, 0xff00ff, 0xffff00, 0xffffff,
  0x000090, 0x0000b0, 0x0000d0, 0x87ceff,
  0x009000, 0x00b000, 0x00d000, 0x009090,
  0x00b0b0, 0x00d0d0, 0x900000, 0xb00000,
  0xd00000, 0x900090, 0xb000b0, 0xd000d0,
  0x803000, 0xa04000, 0xc06000, 0xff8080,
  0xffa0a0, 0xffc0c0, 0xffe0e0, 0xffd700
};

class FigWriter {
public:
  FigWriter(FILE *xml, double mag, double upp, const unsigned int *uc) :
    iXml(xml), iMagnification(mag),  iUnitsPerPoint(upp),
    iUserColors(uc) { }
  void WriteObjects(const std::vector<FigObject> &objects, int start, int fin);

private:
  void WriteEllipse(const FigObject &obj);
  void WritePolyline(const FigObject &obj);
  void WriteSpline(const FigObject &obj);
  void WriteText(const FigObject &obj);
  void WriteArc(const FigObject &obj);

  void WriteStroke(const FigObject &obj);
  void WriteFill(const FigObject &obj);
  void WriteLineStyle(const FigObject &obj);
  void WriteArrows(const FigObject &obj);

  double X(double x);
  double Y(double y);

private:
  FILE *iXml;
  double iMagnification;
  double iUnitsPerPoint;
  const unsigned int *iUserColors;
};

double FigWriter::X(double x)
{
  return (x / iUnitsPerPoint) * iMagnification;
}

double FigWriter::Y(double y)
{
  return MEDIABOX_HEIGHT - X(y);
}

void FigWriter::WriteStroke(const FigObject &obj)
{
  if (obj.iThickness == 0)  // no stroke
    return;
  int col = obj.iPenColor;
  if (col < 0) col = 0;     // make default black
  unsigned int rgb;
  if (col < 32)
    rgb = ColorTable[col];
  else
    rgb = iUserColors[col - 32];
  fprintf(iXml, " stroke=\"%g %g %g\"",
	  ((rgb >> 16) & 0xff) / 255.0,
	  ((rgb >> 8) & 0xff) / 255.0,
	  (rgb & 0xff) / 255.0);
}

void FigWriter::WriteFill(const FigObject &obj)
{
  // unfilled
  if (obj.iAreaFill == -1)
    return;

  int fill = obj.iAreaFill;
  if (fill > 40) {
    fprintf(stderr, "WARNING: fill pattern %d replaced by solid filling.\n",
	    fill);
    fill = 20;
  }

  if (obj.iFillColor < 1) { // BLACK & DEFAULT
    fprintf(iXml, " fill=\"%g\"", 1.0 - (fill / 20.0));
  } else {
    unsigned int rgb;
    if (obj.iFillColor < 32)
      rgb = ColorTable[obj.iFillColor];
    else
      rgb = iUserColors[obj.iFillColor - 32];
    double r = ((rgb >> 16) & 0xff) / 255.0;
    double g = ((rgb >> 8) & 0xff) / 255.0;
    double b = (rgb & 0xff) / 255.0;
    if (fill < 20) {
      // mix down to black
      double scale = fill / 20.0;
      r *= scale;
      g *= scale;
      b *= scale;
    } else if (fill > 20) {
      // mix up to white
      double scale = (40 - fill) / 20.0;  // 40 is pure white
      r = 1.0 - (1.0 - r) * scale;
      g = 1.0 - (1.0 - g) * scale;
      b = 1.0 - (1.0 - b) * scale;
    }
    fprintf(iXml, " fill=\"%g %g %g\"", r, g, b);
  }
}

void FigWriter::WriteLineStyle(const FigObject &obj)
{
  if (obj.iThickness == 0)
    return;
  fprintf(iXml, " pen=\"%g\"",
	  iMagnification * 72.0 * (obj.iThickness / 80.0));
  switch (obj.iLinestyle) {
  case -1: // Default
  case 0:  // Solid
    break;
  case 1:
    fprintf(iXml, " dash=\"dashed\"");
    break;
  case 2:
    fprintf(iXml, " dash=\"dotted\"");
    break;
  case 3:
    fprintf(iXml, " dash=\"dash dotted\"");
    break;
  case 4:
    fprintf(iXml, " dash=\"dash dot dotted\"");
    break;
  case 5: // Dash-triple-dotted (maybe put this in a stylesheet)
    fprintf(iXml, " dash=\"[4 2 1 2 1 2 1 2] 0\"");
    break;
  }
}

void FigWriter::WriteArrows(const FigObject &obj)
{
  if (obj.iForwardArrow)
    fprintf(iXml, " arrow=\"%g\"", X(obj.iForward.iHeight));
  if (obj.iBackwardArrow)
    fprintf(iXml, " backarrow=\"%g\"", X(obj.iBackward.iHeight));
}

// --------------------------------------------------------------------

class DepthCompare {
public:
  DepthCompare(const std::vector<FigObject> &objects)
    : iObjects(objects) { /* nothing */ }
  int operator()(int lhs, int rhs) const
  {
    return (iObjects[lhs].iDepth > iObjects[rhs].iDepth);
  }
private:
  const std::vector<FigObject> &iObjects;
};

void FigWriter::WriteObjects(const std::vector<FigObject> &objects,
			     int start, int fin)
{
  // collect indices of objects
  std::vector<int> objs;
  int i = start;
  while (i < fin) {
    objs.push_back(i);
    if (objects[i].iType == 6)
      i = objects[i].iSubtype;  // link to END OF COMPOUND
    ++i;
  }
  // now sort the objects
  DepthCompare comp(objects);
  std::stable_sort(objs.begin(), objs.end(), comp);
  // now render them
  for (unsigned int j = 0; j < objs.size(); ++j) {
    i = objs[j];
    switch (objects[i].iType) {
    case 1: // ELLIPSE
      WriteEllipse(objects[i]);
      break;
    case 2: // POLYLINE
      WritePolyline(objects[i]);
      break;
    case 3: // SPLINE
      WriteSpline(objects[i]);
	break;
    case 4: // TEXT
      WriteText(objects[i]);
      break;
    case 5: // ARC
      WriteArc(objects[i]);
      break;
    case 6: // COMPOUND
      fprintf(iXml, "<group>\n");
      // recursively render elements of the compound
      WriteObjects(objects, i+1, objects[i].iSubtype);
      fprintf(iXml, "</group>\n");
      break;
    }
  }
}

// --------------------------------------------------------------------

void FigWriter::WriteEllipse(const FigObject &obj)
{
  fprintf(iXml, "<path ");
  WriteStroke(obj);
  WriteFill(obj);
  WriteLineStyle(obj);
  fprintf(iXml, ">\n");
  double ca = cos(obj.iAngle);
  double sa = sin(obj.iAngle);
  fprintf(iXml, "%g %g %g %g %g %g e\n</path>\n",
	  X(obj.iRadius.iX * ca),
	  X(-obj.iRadius.iX * sa),
	  X(obj.iRadius.iY * sa),
	  X(obj.iRadius.iY * ca),
	  X(obj.iCenterX), Y(obj.iCenterY));
}

void FigWriter::WritePolyline(const FigObject &obj)
{
  /* 1: polyline
     2: box
     3: polygon
     4: arc-box
     5: imported-picture bounding-box
  */
  if (obj.iPoints.size() < 2) {
    fprintf(stderr,
	    "WARNING: polyline with less than two vertices ignored.\n");
    return;
  }
  if (obj.iSubtype == 4)
    fprintf(stderr, "WARNING: turning arc-box into rectangle.\n");
  if (obj.iSubtype == 5)
    fprintf(stderr, "WARNING: replacing imported picture by rectangle.\n");

  fprintf(iXml, "<path ");
  WriteStroke(obj);
  WriteFill(obj);
  WriteLineStyle(obj);
  WriteArrows(obj);
  if (obj.iJoinStyle)
    fprintf(iXml, " join=\"%d\"", obj.iJoinStyle);
  if (obj.iCapStyle)
    fprintf(iXml, " cap=\"%d\"", obj.iCapStyle);
  fprintf(iXml, ">\n");
  for (unsigned int i = 0; i < obj.iPoints.size(); ++i) {
    int x = obj.iPoints[i].iX;
    int y = obj.iPoints[i].iY;
    if (i == 0) {
      fprintf(iXml, "%g %g m\n", X(x), Y(y));
    } else if (i == obj.iPoints.size() - 1 && obj.iSubtype > 1) {
      fprintf(iXml, "h\n"); // close path
    } else {
      fprintf(iXml, "%g %g l\n", X(x), Y(y));
    }
  }
  fprintf(iXml, "</path>\n");
}

void FigWriter::WriteSpline(const FigObject &obj)
{
  /* 0: opened approximated spline
     1: closed approximated spline
     2: opened interpolated spline
     3: closed interpolated spline
     4: opened x-spline (FIG 3.2)
     5: closed x-spline (FIG 3.2)
  */
  FigObject obj1 = obj;
  obj1.iJoinStyle = 0;
  obj1.iSubtype = (obj.iSubtype & 1) ? 3 : 1;
  fprintf(stderr, "WARNING: spline replaced by polyline.\n");
  WritePolyline(obj1);
}

void FigWriter::WriteText(const FigObject &obj)
{
  fprintf(iXml, "<text size=\"%g\" pos=\"%g %g\"",
	  iMagnification * obj.iFontSize,
	  X(obj.iPos.iX), Y(obj.iPos.iY));
  WriteStroke(obj);
  // 0: Left justified; 1: Center justified; 2: Right justified
  if (obj.iSubtype == 1)
    fprintf(iXml, " halign=\"center\"");
  else if (obj.iSubtype == 2)
    fprintf(iXml, " halign=\"right\"");
  if (!(obj.iFontFlags & 1))
    fprintf(iXml, " transformable=\"yes\"");
  if (obj.iAngle != 0.0) {
    double ca = cos(obj.iAngle);
    double sa = sin(obj.iAngle);
    fprintf(iXml, " matrix=\"%g %g %g %g 0 0\"", ca, sa, -sa, ca);
  }
  fprintf(iXml, " type=\"label\">");
  int font = obj.iFont;
  if (obj.iFontFlags & 2) {
    // "special" Latex text
    font = 0; // needs no special treatment
  } else if (obj.iFontFlags & 4) {
    // Postscript font: set font to default
    font = 0;
    fprintf(stderr, "WARNING: postscript font ignored.\n");
  }
  switch (font) {
  case 0: // Default font
    fprintf(iXml, "%s", &obj.iString[0]);
    break;
  case 1: // Roman
    fprintf(iXml, "\\textrm{%s}", &obj.iString[0]);
    break;
  case 2: // Bold
    fprintf(iXml, "\\textbf{%s}", &obj.iString[0]);
    break;
  case 3: // Italic
    fprintf(iXml, "\\emph{%s}", &obj.iString[0]);
    break;
  case 4: // Sans Serif
    fprintf(iXml, "\\textsf{%s}", &obj.iString[0]);
    break;
  case 5: // Typewriter
    fprintf(iXml, "\\texttt{%s}", &obj.iString[0]);
    break;
  }
  fprintf(iXml, "</text>\n");
}

void FigWriter::WriteArc(const FigObject &obj)
{
  // 0: pie-wedge (closed); 1: open ended arc
  fprintf(iXml, "<path ");
  WriteStroke(obj);
  WriteFill(obj);
  WriteLineStyle(obj);
  fprintf(iXml, ">\n");
  Point beg = (obj.iDirection == 0) ? obj.iArc3 : obj.iArc1;
  Point end = (obj.iDirection == 0) ? obj.iArc1 : obj.iArc3;
  fprintf(iXml, "%g %g m\n", X(beg.iX), Y(beg.iY));
  double dx = obj.iArc1.iX - obj.iCenterX;
  double dy = obj.iArc1.iY - obj.iCenterY;
  double radius = sqrt(dx*dx + dy*dy);
  fprintf(iXml, "%g 0 0 %g %g %g %g %g a\n</path>\n",
	  X(radius), X(radius),
	  X(obj.iCenterX), Y(obj.iCenterY),
	  X(end.iX), Y(end.iY));
}

// --------------------------------------------------------------------

int main(int argc, char **argv)
{
  if (argc != 3) {
    fprintf(stderr, "Usage: figtoipe <figfile> <xmlfile>\n");
    exit(9);
  }

  const char *figname = argv[1];
  const char *xmlname = argv[2];

  FILE *fig = fopen(figname, "r");
  if (!fig) {
    fprintf(stderr,"figtoipe: cannot open '%s'\n", figname);
    exit(-1);
  }

  FigReader fr(fig);
  if (!fr.ReadHeader()) {
    fprintf(stderr, "figtoipe: cannot parse header of '%s'\n", figname);
    exit(-1);
  }

  fprintf(stderr,
	  "Converting at %g FIG units per point, magnification %g.\n",
	  fr.UnitsPerPoint(), fr.Magnification());

  if (!fr.ReadObjects()) {
    fprintf(stderr, "Error reading FIG file.\n");
    exit(9);
  }
  fclose(fig);

  FILE *xml = fopen(xmlname, "w");
  if (!xml) {
    fprintf(stderr, "figtoipe: cannot open '%s'\n", xmlname);
    exit(-1);
  }

  FigWriter fw(xml, fr.Magnification(), fr.UnitsPerPoint(), fr.UserColors());

  fprintf(xml, "<ipe creator=\"%s\" media=\"%d %d %d %d\">\n",
	  FIGTOIPE_VERSION, 0, 0, MEDIABOX_WIDTH, MEDIABOX_HEIGHT);
  fprintf(xml, "<page>\n");

  fw.WriteObjects(fr.Objects(), 0, fr.Objects().size());

  fprintf(xml, "</page>\n");
  fprintf(xml, "</ipe>\n");

  fclose(xml);
  return 0;
}

// --------------------------------------------------------------------
