1 // -*-c++-*-
2 
3 // fixg2sxd - a utility to convert fig to sxd format
4 
5 // Copyright (C) 2003-2010 Alexander Bürger, acfb@users.sourceforge.net
6 
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 #include "xfigobjects.h"
22 #include "colors.h"
23 #include "misc.h"
24 #include "xmlwrite.h"
25 #include "zipwrite.h"
26 
27 #include <cstdlib>
28 #include <clocale>
29 #include <string>
30 #include <cstring>
31 #include <ctime>
32 #include <strings.h>
33 #include <iostream>
34 #include <fstream>
35 #include <sstream>
36 #include <vector>
37 
38 #define SOME_STYLES_ONLY_IN_CONTENT 1
39 
40 using namespace std;
41 
42 static int papersize;
43 static enum { Portrait, Landscape } orientation = Portrait;
44 
45 static const int MIN_STYLEBASE = 4, MAX_STYLEBASE=255;
46 
47 static const char* papersizes[15] = {
48     "Letter", "Legal", "Ledger", "Tabloid",
49     "A", "B", "C", "D", "E", "A4", "A3", "A2", "A1", "A0", "B5"
50 };
51 
52 // all sizes in cm
53 static float paperwidths[15] =  {
54     21.59 /*Letter*/, 21.59 /*Legal*/, 10, 27.958/*Tabloid*/,
55     10, 10, 10, 10, 10, 21.0/*A4*/, 29.7, 42.0, 59.4, 84.1
56 };
57 static float paperheights[15] = {
58     27.94 /*Letter*/, 35.565/*Legal*/, 10, 43.127/*Tabloid*/,
59     10, 10, 10, 10, 10, 29.7/*A4*/, 42.0, 59.4, 84.1, 118.9
60 };
61 
62 static vector<XfigObject*> xfigobjects;
63 
64 static const char* xmlheader =
65     "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
66 
67 static const char* xmlnamespaces =
68     " xmlns:draw=\"http://openoffice.org/2000/drawing\""
69     " xmlns:style=\"http://openoffice.org/2000/style\""
70     " xmlns:office=\"http://openoffice.org/2000/office\""
71     " xmlns:text=\"http://openoffice.org/2000/text\""
72     " xmlns:svg=\"http://www.w3.org/2000/svg\""
73     " xmlns:fo=\"http://www.w3.org/1999/XSL/Format\""
74     " xmlns:dc=\"http://purl.org/dc/elements/1.1/\""
75     " xmlns:meta=\"http://openoffice.org/2000/meta\""
76     " xmlns:xlink=\"http://www.w3.org/1999/xlink\"";
77 
write_file(ostream & sxdfile)78 static void write_file( ostream& sxdfile )
79 {
80     float pw = paperwidths[papersize];
81     float ph = paperheights[papersize];
82     if( orientation == Landscape )
83         swap( pw, ph );
84 
85     Node styles("office:styles");
86     Node autostyles("office:automatic-styles");
87 
88     Node& linebase = styles.subnode("style:style");
89     linebase["style:name"] << LineFillStyle::base;
90     linebase["style:family"] << "graphics";
91     linebase["style:parent-style-name"] << "standard";
92 
93     Node& textbase = styles.subnode("style:style");
94     textbase["style:name"] << TextStyle::base;
95     textbase["style:parent-style-name"] << "standard";
96     textbase["style:family"] << "graphics"; // paragraph
97     textbase["style:class"] << "text";
98     Node& textbaseprop = textbase.subnode("style:properties");
99     textbaseprop["fo:font-family"] << "Helvetica";
100     textbaseprop["fo:font-weight"] << "normal";
101     textbaseprop["fo:font-style"] << "normal";
102     textbaseprop["fo:color"] << "#000000";
103     textbaseprop["fo:font-size"] << "10pt";
104     textbaseprop["fo:padding-left"] << "0cm";
105     textbaseprop["fo:padding-right"] << "0cm";
106     textbaseprop["fo:padding-top"] << "0cm";
107     textbaseprop["fo:padding-bottom"] << "0cm";
108     textbaseprop["draw:fill"] << "none";
109     textbaseprop["draw:stroke"] << "none";
110 
111 #ifndef SOME_STYLES_ONLY_IN_CONTENT
112     // does not work here as of OOo 2.4.0
113     Node& narrow = autostyles.subnode("style:style");
114     narrow["style:name"] << "P1";
115     narrow["style:family"] << "paragraph";
116     Node& narrowprop = narrow.subnode("style:properties");
117     narrowprop["style:text-scale"] << "90%";
118 #endif
119 
120     for( set<TextStyle>::iterator i=textstyles.begin();
121          i!=textstyles.end(); ++i )
122         i->write( styles );
123     for( arrowset::iterator i=arrows.begin();
124          i!=arrows.end(); ++i )
125         i->write( styles );
126     for( set<LineFillStyle>::iterator i=linefillstyles.begin();
127          i!=linefillstyles.end(); ++i )
128         i->write( styles );
129 
130     Node& pm = autostyles.subnode("style:page-master");
131     pm["style:name"] << "PM1";
132     Node& pmprop = pm.subnode( "style:properties" );
133     pmprop["fo:margin-top"] << "0cm";
134     pmprop["fo:margin-bottom"] << "0cm";
135     pmprop["fo:margin-left"] << "0cm";
136     pmprop["fo:margin-right"] << "0cm";
137     pmprop["fo:page-width"] << pw << "cm";
138     pmprop["fo:page-height"] << ph << "cm";
139     pmprop["style:print-orientation"]
140         << (orientation == Landscape ? "landscape" : "portrait");
141 
142     Node& dp1 = autostyles.subnode("style:style");
143     dp1["style:family"] << "drawing-page";
144     dp1["style:name"] << "dp1";
145     Node& dp1prop = dp1.subnode("style:properties");
146     dp1prop["draw:background-size"] << "border";
147     dp1prop["draw:fill"] << "none";
148 
149     Node& img = autostyles.subnode("style:style");
150     img["style:family"] << "graphics";
151     img["style:name"] << "gr_img";
152     Node& imgprop = img.subnode("style:properties");
153     imgprop["draw:stroke"] << "none";
154     imgprop["draw:fill"] << "none";
155 
156     if( xfigobjects.size()>0 ) {
157         // loop over all objects to find minmal/maximal depth
158         vector<XfigObject*>::iterator i=xfigobjects.begin();
159         depth_max = (*i)->depth;
160         while( ++i != xfigobjects.end() )
161             depth_max = max( depth_max, (*i)->depth );
162     }
163 
164     ZipWriter sxd( sxdfile );
165 
166     // write mimetype
167     sxd.GetStream("mimetype") << "application/vnd.sun.xml.draw" << flush;
168 
169     { // write manifest
170         Node manifest("manifest:manifest");
171         manifest["xmlns:manifest"] << "http://openoffice.org/2001/manifest";
172         Node& zip = manifest.subnode("manifest:file-entry");
173         zip["manifest:media-type"] << "application/vnd.sun.xml.draw";
174         zip["manifest:full-path"] << "/";
175         Node& cx = manifest.subnode("manifest:file-entry");
176         cx["manifest:media-type"] << "text/xml";
177         cx["manifest:full-path"] << "content.xml";
178         Node& sx = manifest.subnode("manifest:file-entry");
179         sx["manifest:media-type"] << "text/xml";
180         sx["manifest:full-path"] << "styles.xml";
181         Node& mx = manifest.subnode("manifest:file-entry");
182         mx["manifest:media-type"] << "text/xml";
183         mx["manifest:full-path"] << "meta.xml";
184         sxd.GetStream("META-INF/manifest.xml" )
185             << xmlheader << endl
186             << "<!DOCTYPE manifest:manifest PUBLIC "
187             "\"-//OpenOffice.org//DTD Manifest 1.0//EN\" \"Manifest.dtd\">"
188             << endl
189             << manifest;
190     }
191 
192     { // write meta.xml
193         time_t now = time(0);
194         char creationtime[30];
195         strftime( creationtime, sizeof(creationtime), "%Y-%m-%dT%H:%M:%S",
196                   localtime(&now) );
197         sxd.GetStream("meta.xml")
198             << xmlheader << endl
199             << "<!DOCTYPE office:document-meta PUBLIC"
200             " \"-//OpenOffice.org//DTD OfficeDocument 1.0//EN\""
201             " \"office.dtd\">" << endl
202             << "<office:document-meta" << xmlnamespaces
203             << " office:version=\"1.0\">" << endl
204             << " <office:meta>" << endl
205             << "  <meta:generator>fig2sxd</meta:generator>" << endl
206             << "  <meta:creation-date>" << creationtime
207             << "</meta:creation-date>" << endl
208             << " </office:meta>" << endl
209             << "</office:document-meta>" << endl;
210     }
211 
212     { // write content.xml
213         ostream& contentxml = sxd.GetStream( "content.xml" );
214         contentxml << xmlheader << endl
215                    << "<!DOCTYPE office:document-content PUBLIC"
216             " \"-//OpenOffice.org//DTD OfficeDocument 1.0//EN\""
217             " \"office.dtd\">" << endl
218                    << "<office:document-content" << xmlnamespaces
219                    << " office:version=\"1.0\" office:class=\"drawing\">"
220                    << endl;
221 
222 #ifdef SOME_STYLES_ONLY_IN_CONTENT
223         // for narrow text, scaling must be defined here; other places
224         // do not work as of OOo 2.4.0
225         Node auto2("office:automatic-styles");
226         Node& P1 = auto2.subnode("style:style");
227         P1["style:name"] << "P1";
228         P1["style:family"] << "paragraph";
229         Node& P1prop = P1.subnode("style:properties");
230         P1prop["style:text-scale"] << "90%";
231         contentxml << auto2;
232 #endif
233 
234         contentxml << "<office:body>" << endl
235                    << "<draw:page draw:name=\"page1\""
236                    << " draw:master-page-name=\"Standard\""
237                    << " draw:style-name=\"dp1\">" << endl;
238         for( vector<XfigObject*>::iterator i=xfigobjects.begin();
239              i != xfigobjects.end(); ++i )
240             (*i)->write( contentxml );
241         contentxml << "</draw:page>" << endl
242                    << "</office:body>" << endl
243                    << "</office:document-content>" << endl;
244     }
245 
246     { // write styles.xml
247         ostream& stylesxml = sxd.GetStream( "styles.xml" );
248         stylesxml << xmlheader << endl
249                   << "<!DOCTYPE office:document-styles PUBLIC"
250             " \"-//OpenOffice.org//DTD OfficeDocument 1.0//EN\""
251             " \"office.dtd\">" << endl
252                   << "<office:document-styles" << xmlnamespaces
253                   << " office:version=\"1.0\">" << endl
254                   << styles
255                   << autostyles
256                   << "<office:master-styles>" << endl
257                   << "  <draw:layer-set>" << endl
258                   << "   <draw:layer draw:name=\"layout\"/>" << endl
259                   << "   <draw:layer draw:name=\"background\"/>" << endl
260                   << "   <draw:layer draw:name=\"backgroundobjects\"/>" << endl
261                   << "   <draw:layer draw:name=\"controls\"/>" << endl
262                   << "   <draw:layer draw:name=\"measurelines\"/>" << endl
263                   << "  </draw:layer-set>" << endl
264                   << " <style:master-page style:name=\"Standard\""
265                   << "  style:page-master-name=\"PM1\""
266                   << "  draw:style-name=\"dp1\"/>" << endl
267                   << "</office:master-styles>" << endl
268                   << "</office:document-styles>" << endl;
269     }
270 }
271 
read_file(istream & figfile)272 static void read_file( istream& figfile )
273 {
274     string linebuf;
275 
276     // check if it's a fig file; first line is a comment, but we
277     // should not skip it
278     getline( figfile, linebuf );
279     if( linebuf.compare( 0, 8, "#FIG 3.2" ) != 0 ) {
280         cerr << "linebuf =>" << linebuf << "<=" << endl;
281         fail( "Not a figfile or unknown fig format (only 3.2 is known)." );
282     }
283 
284     // read orientation
285     skip_comment( figfile );
286     getline( figfile, linebuf );
287     if( linebuf == "Landscape" ) {
288         orientation = Landscape;
289     } else if( linebuf != "Portrait" ) {
290         fail( "Bad orientation." );
291     }
292 
293     // read justification
294     enum { Center, FlushLeft } justification = Center;
295     skip_comment( figfile );
296     getline( figfile, linebuf );
297     if( linebuf == "Flush left" ) {
298         justification = FlushLeft;
299     } else if( linebuf != "Center" ) {
300         fail( "Bad justification." );
301     }
302 
303     // read units
304     enum { Metric, Inches } units = Metric;
305     skip_comment( figfile );
306     getline( figfile, linebuf );
307     if( linebuf == "Inches" ) {
308         units = Inches;
309     } else if( linebuf != "Metric" ) {
310         fail( "Bad units." );
311     }
312 
313     // read papersize
314     skip_comment( figfile );
315     string papersize_name;
316     figfile >> papersize_name;
317     for( papersize=0; papersize<15; ++papersize ) {
318         if( strcasecmp( papersize_name.c_str(), papersizes[papersize] ) == 0 )
319             break;
320     }
321     if( papersize == 15 )
322         fail( "Bad papersize." );
323 
324     // read magnification
325     skip_comment( figfile );
326     float magnification;
327     figfile >> magnification;
328     figfile.ignore( 1024, '\n' );
329 
330     // read multipageness
331     skip_comment( figfile );
332     enum { Single, Multiple } multipage = Single;
333     getline( figfile, linebuf );
334     if( linebuf == "Multiple" ) {
335         multipage = Multiple;
336     } else if( linebuf != "Single" ) {
337         fail( "Bad multipageness." );
338     }
339 
340     // read transparent color
341     skip_comment( figfile );
342     int transparentcolor = -5;
343     figfile >> transparentcolor;
344     if( transparentcolor < -3 || transparentcolor > 543 )
345         fail( "Bad transparent color number." );
346 
347     // skip optional comment
348     skip_comment( figfile );
349 
350     // read resolution and coordinate system id
351     int coord_system;
352     figfile >> resolution >> coord_system;
353     if( resolution < 1 || coord_system != 2 )
354         fail( "Bad resolution / coord_system." );
355 
356     while( 1 ) {
357         // ignore comments; returns EOF if file ends
358         skip_comment( figfile );
359         if( figfile.eof() )
360             break;
361 
362         int object_code;
363         figfile >> object_code;
364         switch( object_code ) {
365         case 0:
366             read_color( figfile );
367             break;
368         case 1: {
369             Ellipse* e = new Ellipse();
370             e->read( figfile );
371             xfigobjects.push_back( e );
372             break; }
373         case 2: {
374             Poly* p = new Poly();
375             p->read( figfile );
376             xfigobjects.push_back( p );
377             break; }
378         case 3: {
379             Spline* s = new Spline();
380             s->read( figfile );
381             xfigobjects.push_back( s );
382             break; }
383         case 4: {
384             Text* t = new Text();
385             t->read( figfile );
386             xfigobjects.push_back( t );
387             break; }
388         case 5: {
389             Arc* a = new Arc();
390             a->read( figfile );
391             xfigobjects.push_back( a );
392             break; }
393         case 6: {
394             OpenCompound* c = new OpenCompound();
395             c->read( figfile );
396             xfigobjects.push_back( c );
397             break; }
398         case -6: {
399             CloseCompound* c = new CloseCompound();
400             c->read( figfile );
401             xfigobjects.push_back( c );
402             break; }
403         default: {
404             ostringstream err;
405             err << "Unknown object code: " << object_code;
406             throw err.str(); }
407         }
408     }
409 }
410 
usage(const char * cmdname,bool err=true)411 static void usage( const char* cmdname, bool err=true )
412 {
413     ostream& out = (err ? cerr : cout);
414     out << cmdname << " [-w] [-l(ine)w(idth)1 l] [-stylebase s] figfile [sxdfile]" << endl << endl
415         << "Using `-' as figfile makes the program read from stdin." << endl
416         << endl
417         << "Omitting sxdfile when figfile ends with .fig or .xfig, makes"<<endl
418         << "the sxd file be named like figfile with .(x)fig replaced by .sxd."
419         << endl << endl
420         << "With -linewidth1 or -lw1 the with of lines with thickness 1 in xfig" << endl
421         << "can be set (in cm), e.g. to 0 to have fine lines." << endl << endl
422         << "With -w files with out-of-specification values will be accepted," << endl
423         << "but a warning will be printed and the bad values sanitized." << endl << endl
424         << "With -stylebase the \"Fig2Sxd\" prefix for styles can be replaced." << endl
425         << "This can be useful if several converted figures are joined in" << endl
426         << "the same document. The replacement prefix must consist of " << MIN_STYLEBASE << ".."
427         << MAX_STYLEBASE << endl << "alphanumeric characters." << endl << endl
428         << "This is fig2sxd version 0.22.1. (C) 2003-2015 Alexander Bürger."
429         << endl;
430 }
431 
option(const char * option,const char * & value,int & argc,char ** argv)432 static bool option( const char* option, const char* &value,
433                     int &argc, char** argv )
434 {
435     bool found = false;
436     for( int a=1; a<argc; a++ ) {
437         if( strcmp( option, argv[a] ) == 0 && a+1 < argc ) {
438             value = argv[a+1];
439             for( int aa=a; aa<argc-2; aa++ )
440                 argv[aa] = argv[aa+2];
441             argc -= 2;
442             a--;
443             found = true;
444         }
445     }
446     return found;
447 }
448 
449 #if 1
option0(const char * option,int & argc,char ** argv)450 static bool option0( const char* option, int &argc, char** argv )
451 {
452     bool found = false;
453     for( int a=1; a<argc; a++ ) {
454         if( strcmp( option, argv[a] ) == 0 ) {
455             for( int aa=a; aa<argc-1; aa++ )
456                 argv[aa] = argv[aa+1];
457             argc -= 1;
458             a--;
459             found = true;
460         }
461     }
462     return found;
463 }
464 #endif
465 
466 extern bool out_of_range_error;
467 
try_catched_main(int argc,char * argv[])468 int try_catched_main( int argc, char* argv[] )
469 {
470     setlocale( LC_NUMERIC, "C" );
471 
472     const char* tmp;
473     if( option( "-linewidth1", tmp, argc, argv )
474         | option( "-lw1", tmp, argc, argv ) )
475     {
476         float w = atof( tmp );
477         if( w<0 )
478             fail( "linewith must be >= 0" );
479         LineFillStyle::linewith1 = w;
480     }
481     if( option( "-stylebase", tmp, argc, argv ) ) {
482         const int length = strlen(tmp);
483         bool okay = length>=MIN_STYLEBASE && length<=MAX_STYLEBASE;
484         for(int i=0; okay && i<length; ++i) {
485             if( !isalnum(tmp[i]) )
486                 okay = false;
487         }
488         if( okay ) {
489             ostringstream lbase;
490             lbase << tmp << "Line";
491             LineFillStyle::base = lbase.str();
492             ostringstream tbase;
493             tbase << tmp << "Text";
494             TextStyle::base = tbase.str();
495         } else {
496             cerr << "The style prefix must consist of " << MIN_STYLEBASE << ".."
497                  << MAX_STYLEBASE <<" alphanumeric characters." << endl;
498             exit( EXIT_FAILURE );
499         }
500     }
501     if( option0( "-w", argc, argv ) ) {
502         out_of_range_error = false;
503         cerr << "Out of range values will be sanitized." << endl;
504     }
505 
506     bool dotfig=false, dotxfig=false;
507     string figfilename;
508     unsigned int ffnl=0;
509     if( argc>=2 ) {
510         figfilename = argv[1];
511         ffnl = figfilename.length();
512     }
513     if( argc==2 ) {
514         dotfig  = ( ffnl>4 && figfilename.rfind( ".fig")+4==ffnl );
515         dotxfig = ( ffnl>5 && figfilename.rfind(".xfig")+5==ffnl );
516     }
517     if( argc != 3 && !dotfig && !dotxfig ) {
518         usage( argv[0], true );
519         exit( EXIT_FAILURE );
520     }
521 
522     initcolors();
523 
524     // read xfig file / stdin
525     if( figfilename != "-" ) {
526         ifstream infile( figfilename.c_str() );
527         read_file( infile );
528     } else {
529         read_file( std::cin );
530     }
531 
532     // write sxd file / stdout
533     string sxd;
534     if( dotfig )
535         sxd = figfilename.substr(0,ffnl-4)+".sxd";
536     else if( dotxfig )
537         sxd = figfilename.substr(0,ffnl-5)+".sxd";
538     else
539         sxd = argv[2];
540     if( sxd.empty() )
541         throw string( "output filename is empty" );
542     if( sxd != "-" ) {
543         ofstream sxdfile( sxd.c_str(), ios::binary );
544         write_file( sxdfile );
545     } else {
546         write_file( std::cout );
547     }
548     return EXIT_SUCCESS;
549 }
550 
main(int argc,char * argv[])551 int main( int argc, char* argv[] )
552 {
553     try {
554         return try_catched_main( argc, argv );
555     } catch( string msg ) {
556         cerr << msg << endl << endl
557              << "If you think this is message is caused by a problem in fig2sxd," << endl
558              << "please contact the author(s) via http://fig2sxd.sourceforge.net/."  << endl
559              << "Thank you." << endl << endl
560              << "To get your file converted anyway, you could try the '-w' option." << endl;
561         return EXIT_FAILURE;
562     }
563 }
564