/* 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 #include #include #include #include #include #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 iString; std::vector 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 &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 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 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 &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 &objects) : iObjects(objects) { /* nothing */ } int operator()(int lhs, int rhs) const { return (iObjects[lhs].iDepth > iObjects[rhs].iDepth); } private: const std::vector &iObjects; }; void FigWriter::WriteObjects(const std::vector &objects, int start, int fin) { // collect indices of objects std::vector 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, "\n"); // recursively render elements of the compound WriteObjects(objects, i+1, objects[i].iSubtype); fprintf(iXml, "\n"); break; } } } // -------------------------------------------------------------------- void FigWriter::WriteEllipse(const FigObject &obj) { fprintf(iXml, "\n"); double ca = cos(obj.iAngle); double sa = sin(obj.iAngle); fprintf(iXml, "%g %g %g %g %g %g e\n\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, "\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, "\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, ""); 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, "\n"); } void FigWriter::WriteArc(const FigObject &obj) { // 0: pie-wedge (closed); 1: open ended arc 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\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 \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, "\n", FIGTOIPE_VERSION, 0, 0, MEDIABOX_WIDTH, MEDIABOX_HEIGHT); fprintf(xml, "\n"); fw.WriteObjects(fr.Objects(), 0, fr.Objects().size()); fprintf(xml, "\n"); fprintf(xml, "\n"); fclose(xml); return 0; } // --------------------------------------------------------------------