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