karbon/svg/svgexport.cpp
1/* This file is part of the KDE project
2 Copyright (C) 2002, 2003 The Karbon Developers
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18*/
19
20#include <tqcstring.h>
21#include <tqdom.h>
22#include <tqfile.h>
23#include <tqstring.h>
24#include <tqvaluelist.h>
25
26#include <kgenericfactory.h>
27#include <KoFilter.h>
28#include <KoFilterChain.h>
29#include <KoStore.h>
30
31#include "svgexport.h"
32#include "vcolor.h"
33#include "vcomposite.h"
34#include "vdashpattern.h"
35#include "vdocument.h"
36#include "vfill.h"
37#include "vgradient.h"
38#include "vgroup.h"
39#include "vimage.h"
40#include "vlayer.h"
41#include "vpath.h"
42#include "vpattern.h"
43#include "vsegment.h"
44#include "vselection.h"
45#include "vstroke.h"
46#include "vtext.h"
47#include <commands/vtransformcmd.h>
48
49#include <kdebug.h>
50
51TQString INDENT(" ");
52
53void
54printIndentation( TQTextStream *stream, unsigned int indent )
55{
56 for( unsigned int i = 0; i < indent;++i)
57 *stream << INDENT;
58}
59
60typedef KGenericFactory<SvgExport, KoFilter> SvgExportFactory;
61K_EXPORT_COMPONENT_FACTORY( libkarbonsvgexport, SvgExportFactory( "kofficefilters" ) )
62
63
64SvgExport::SvgExport( KoFilter*, const char*, const TQStringList& )
65 : KoFilter(), m_indent( 0 ), m_indent2( 0 ), m_trans( 0L )
66{
67 m_gc.setAutoDelete( true );
68}
69
70KoFilter::ConversionStatus
71SvgExport::convert( const TQCString& from, const TQCString& to )
72{
73 if ( to != "image/svg+xml" || from != "application/x-karbon" )
74 {
75 return KoFilter::NotImplemented;
76 }
77
78 KoStoreDevice* storeIn = m_chain->storageFile( "root", KoStore::Read );
79 if( !storeIn )
80 return KoFilter::StupidError;
81
82 TQFile fileOut( m_chain->outputFile() );
83 if( !fileOut.open( IO_WriteOnly ) )
84 {
85 delete storeIn;
86 return KoFilter::StupidError;
87 }
88
89 TQDomDocument domIn;
90 domIn.setContent( storeIn );
91 TQDomElement docNode = domIn.documentElement();
92
93 m_stream = new TQTextStream( &fileOut );
94 TQString body;
95 m_body = new TQTextStream( &body, IO_ReadWrite );
96 TQString defs;
97 m_defs = new TQTextStream( &defs, IO_ReadWrite );
98
99 // load the document and export it:
100 VDocument doc;
101 doc.load( docNode );
102 doc.accept( *this );
103
104 *m_stream << defs;
105 *m_stream << body;
106
107 fileOut.close();
108
109 delete m_stream;
110 delete m_defs;
111 delete m_body;
112
113 return KoFilter::OK;
114}
115
116void
117SvgExport::visitVDocument( VDocument& document )
118{
119 // select all objects:
120 document.selection()->append();
121
122 // get the bounding box of the page
123 KoRect rect( 0, 0, document.width(), document.height() );
124
125 // standard header:
126 *m_defs <<
127 "<?xml version=\"1.0\" standalone=\"no\"?>\n" <<
128 "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 20010904//EN\" " <<
129 "\"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd\">"
130 << endl;
131
132 // add some PR. one line is more than enough.
133 *m_defs <<
134 "<!-- Created using Karbon14, part of koffice: http://www.trinitydesktop.org -->" << endl;
135
136 *m_defs <<
137 "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"" <<
138 rect.width() << "px\" height=\"" << rect.height() << "px\">" << endl;
139 printIndentation( m_defs, ++m_indent2 );
140 *m_defs << "<defs>" << endl;
141
142 m_indent++;
143 m_indent2++;
144
145 // we dont need the selection anymore:
146 document.selection()->clear();
147
148 // set up gc
149 SvgGraphicsContext *gc = new SvgGraphicsContext;
150 m_gc.push( gc );
151
152 TQWMatrix mat;
153 mat.scale( 1, -1 );
154 mat.translate( 0, -document.height() );
155
156 m_trans = new VTransformCmd( 0L, mat, false );
157
158 // export layers:
159 VVisitor::visitVDocument( document );
160
161 delete m_trans;
162 m_trans = 0L;
163
164 // end tag:
165 printIndentation( m_defs, --m_indent2 );
166 *m_defs << "</defs>" << endl;
167 *m_body << "</svg>" << endl;
168}
169
170TQString
171SvgExport::getID( VObject *obj )
172{
173 if( obj && !obj->name().isEmpty() )
174 return TQString( " id=\"%1\"" ).arg( obj->name() );
175 return TQString();
176}
177
178void
179SvgExport::visitVGroup( VGroup& group )
180{
181 printIndentation( m_body, m_indent++ );
182 *m_body << "<g" << getID( &group ) << ">" << endl;
183 VVisitor::visitVGroup( group );
184 printIndentation( m_body, --m_indent );
185 *m_body << "</g>" << endl;
186}
187
188// horrible but at least something gets exported now
189// will need this for patterns
190void
191SvgExport::visitVImage( VImage& image )
192{
193 printIndentation( m_body, m_indent );
194 *m_body << "<image ";
195 VVisitor::visitVImage( image );
196 *m_body << "x=\"" << "\" ";
197 *m_body << "y=\"" << "\" ";
198 *m_body << "width=\"" << "\" ";
199 *m_body << "height=\"" << "\" ";
200 *m_body << "xlink:href=\"" << "\"";
201 *m_body << " />" << endl;
202}
203
204void
205SvgExport::visitVLayer( VLayer& layer )
206{
207 printIndentation( m_body, m_indent++ );
208 *m_body << "<g" << getID( &layer ) << ">" << endl;
209 //*m_body << " transform=\"scale(1, -1) translate(0, -" << layer.document()->height() << ")\">" << endl;
210 VVisitor::visitVLayer( layer );
211 printIndentation( m_body, --m_indent );
212 *m_body << "</g>" << endl;
213}
214
215void
216SvgExport::writePathToStream( VPath &composite, const TQString &id, TQTextStream *stream, unsigned int indent )
217{
218 if( ! stream )
219 return;
220
221 printIndentation( stream, indent );
222 *stream << "<path" << id;
223
224 VVisitor::visitVPath( composite );
225
226 getFill( *( composite.fill() ), stream );
227 getStroke( *( composite.stroke() ), stream );
228
229 TQString d;
230 composite.saveSvgPath( d );
231 *stream << " d=\"" << d << "\" ";
232
233 if( composite.fillRule() != m_gc.current()->fillRule )
234 {
235 if( composite.fillRule() == evenOdd )
236 *stream << " fill-rule=\"evenodd\"";
237 else
238 *stream << " fill-rule=\"nonzero\"";
239 }
240
241 *stream << " />" << endl;
242}
243
244void
245SvgExport::visitVPath( VPath& composite )
246{
247 m_trans->visitVPath( composite );
248 writePathToStream( composite, getID( &composite ), m_body, m_indent );
249 m_trans->visitVPath( composite );
250}
251
252void
253SvgExport::visitVSubpath( VSubpath& )
254{
255}
256
257TQString createUID()
258{
259 static unsigned int nr = 0;
260
261 return "defitem" + TQString().setNum( nr++ );
262}
263
264void
265SvgExport::getColorStops( const TQPtrVector<VColorStop> &colorStops )
266{
267 m_indent2++;
268 for( unsigned int i = 0; i < colorStops.count() ; i++ )
269 {
270 printIndentation( m_defs, m_indent2 );
271 *m_defs << "<stop stop-color=\"";
272 getHexColor( m_defs, colorStops.at( i )->color );
273 *m_defs << "\" offset=\"" << TQString().setNum( colorStops.at( i )->rampPoint );
274 *m_defs << "\" stop-opacity=\"" << colorStops.at( i )->color.opacity() << "\"" << " />" << endl;
275 }
276 m_indent2--;
277}
278
279void
280SvgExport::getGradient( const VGradient& grad )
281{
282 TQString uid = createUID();
283 if( grad.type() == VGradient::linear )
284 {
285 printIndentation( m_defs, m_indent2 );
286 // do linear grad
287 *m_defs << "<linearGradient id=\"" << uid << "\" ";
288 *m_defs << "gradientUnits=\"userSpaceOnUse\" ";
289 *m_defs << "x1=\"" << grad.origin().x() << "\" ";
290 *m_defs << "y1=\"" << grad.origin().y() << "\" ";
291 *m_defs << "x2=\"" << grad.vector().x() << "\" ";
292 *m_defs << "y2=\"" << grad.vector().y() << "\" ";
293 if( grad.repeatMethod() == VGradient::reflect )
294 *m_defs << "spreadMethod=\"reflect\" ";
295 else if( grad.repeatMethod() == VGradient::repeat )
296 *m_defs << "spreadMethod=\"repeat\" ";
297 *m_defs << ">" << endl;
298
299 // color stops
300 getColorStops( grad.colorStops() );
301
302 printIndentation( m_defs, m_indent2 );
303 *m_defs << "</linearGradient>" << endl;
304 *m_body << "url(#" << uid << ")";
305 }
306 else if( grad.type() == VGradient::radial )
307 {
308 // do radial grad
309 printIndentation( m_defs, m_indent2 );
310 *m_defs << "<radialGradient id=\"" << uid << "\" ";
311 *m_defs << "gradientUnits=\"userSpaceOnUse\" ";
312 *m_defs << "cx=\"" << grad.origin().x() << "\" ";
313 *m_defs << "cy=\"" << grad.origin().y() << "\" ";
314 *m_defs << "fx=\"" << grad.focalPoint().x() << "\" ";
315 *m_defs << "fy=\"" << grad.focalPoint().y() << "\" ";
316 double r = sqrt( pow( grad.vector().x() - grad.origin().x(), 2 ) + pow( grad.vector().y() - grad.origin().y(), 2 ) );
317 *m_defs << "r=\"" << TQString().setNum( r ) << "\" ";
318 if( grad.repeatMethod() == VGradient::reflect )
319 *m_defs << "spreadMethod=\"reflect\" ";
320 else if( grad.repeatMethod() == VGradient::repeat )
321 *m_defs << "spreadMethod=\"repeat\" ";
322 *m_defs << ">" << endl;
323
324 // color stops
325 getColorStops( grad.colorStops() );
326
327 printIndentation( m_defs, m_indent2 );
328 *m_defs << "</radialGradient>" << endl;
329 *m_body << "url(#" << uid << ")";
330 }
331 // gah! pointless abbreviation of conical to conic
332 else if( grad.type() == VGradient::conic )
333 {
334 // fake conical grad as radial.
335 // fugly but better than data loss.
336 printIndentation( m_defs, m_indent2 );
337 *m_defs << "<radialGradient id=\"" << uid << "\" ";
338 *m_defs << "gradientUnits=\"userSpaceOnUse\" ";
339 *m_defs << "cx=\"" << grad.origin().x() << "\" ";
340 *m_defs << "cy=\"" << grad.origin().y() << "\" ";
341 *m_defs << "fx=\"" << grad.focalPoint().x() << "\" ";
342 *m_defs << "fy=\"" << grad.focalPoint().y() << "\" ";
343 double r = sqrt( pow( grad.vector().x() - grad.origin().x(), 2 ) + pow( grad.vector().y() - grad.origin().y(), 2 ) );
344 *m_defs << "r=\"" << TQString().setNum( r ) << "\" ";
345 if( grad.repeatMethod() == VGradient::reflect )
346 *m_defs << "spreadMethod=\"reflect\" ";
347 else if( grad.repeatMethod() == VGradient::repeat )
348 *m_defs << "spreadMethod=\"repeat\" ";
349 *m_defs << ">" << endl;
350
351 // color stops
352 getColorStops( grad.colorStops() );
353
354 printIndentation( m_defs, m_indent2 );
355 *m_defs << "</radialGradient>" << endl;
356 *m_body << "url(#" << uid << ")";
357 }
358}
359
360// better than nothing
361void
362SvgExport::getPattern( const VPattern & )
363{
364 TQString uid = createUID();
365 printIndentation( m_defs, m_indent2 );
366 *m_defs << "<pattern id=\"" << uid << "\" ";
367 *m_defs << "width=\"" << "\" ";
368 *m_defs << "height=\"" << "\" ";
369 *m_defs << "patternUnits=\"userSpaceOnUse\" ";
370 *m_defs << "patternContentUnits=\"userSpaceOnUse\" ";
371 *m_defs << " />" << endl;
372 // TODO: insert hard work here ;)
373 printIndentation( m_defs, m_indent2 );
374 *m_defs << "</pattern>" << endl;
375 *m_body << "url(#" << uid << ")";
376}
377
378void
379SvgExport::getFill( const VFill& fill, TQTextStream *stream )
380{
381 *stream << " fill=\"";
382 if( fill.type() == VFill::none )
383 *stream << "none";
384 else if( fill.type() == VFill::grad )
385 getGradient( fill.gradient() );
386 else if( fill.type() == VFill::patt )
387 getPattern( fill.pattern() );
388 else
389 getHexColor( stream, fill.color() );
390 *stream << "\"";
391
392 if( fill.color().opacity() != m_gc.current()->fill.color().opacity() )
393 *stream << " fill-opacity=\"" << fill.color().opacity() << "\"";
394}
395
396void
397SvgExport::getStroke( const VStroke& stroke, TQTextStream *stream )
398{
399 if( stroke.type() != m_gc.current()->stroke.type() )
400 {
401 *stream << " stroke=\"";
402 if( stroke.type() == VStroke::none )
403 *stream << "none";
404 else if( stroke.type() == VStroke::grad )
405 getGradient( stroke.gradient() );
406 else
407 getHexColor( stream, stroke.color() );
408 *stream << "\"";
409 }
410
411 if( stroke.color().opacity() != m_gc.current()->stroke.color().opacity() )
412 *stream << " stroke-opacity=\"" << stroke.color().opacity() << "\"";
413
414 if( stroke.lineWidth() != m_gc.current()->stroke.lineWidth() )
415 *stream << " stroke-width=\"" << stroke.lineWidth() << "\"";
416
417 if( stroke.lineCap() != m_gc.current()->stroke.lineCap() )
418 {
419 if( stroke.lineCap() == VStroke::capButt )
420 *stream << " stroke-linecap=\"butt\"";
421 else if( stroke.lineCap() == VStroke::capRound )
422 *stream << " stroke-linecap=\"round\"";
423 else if( stroke.lineCap() == VStroke::capSquare )
424 *stream << " stroke-linecap=\"square\"";
425 }
426
427 if( stroke.lineJoin() != m_gc.current()->stroke.lineJoin() )
428 {
429 if( stroke.lineJoin() == VStroke::joinMiter )
430 {
431 *stream << " stroke-linejoin=\"miter\"";
432 *stream << " stroke-miterlimit=\"" << stroke.miterLimit() << "\"";
433 }
434 else if( stroke.lineJoin() == VStroke::joinRound )
435 *stream << " stroke-linejoin=\"round\"";
436 else if( stroke.lineJoin() == VStroke::joinBevel )
437 *stream << " stroke-linejoin=\"bevel\"";
438 }
439
440 // dash
441 if( stroke.dashPattern().array().count() > 0 )
442 {
443 *stream << " stroke-dashoffset=\"" << stroke.dashPattern().offset() << "\"";
444 *stream << " stroke-dasharray=\" ";
445
446 TQValueListConstIterator<float> itr;
447 for(itr = stroke.dashPattern().array().begin(); itr != stroke.dashPattern().array().end(); ++itr )
448 {
449 *stream << *itr << " ";
450 }
451 *stream << "\"";
452 }
453}
454
455void
456SvgExport::getHexColor( TQTextStream *stream, const VColor& color )
457{
458 // Convert the various color-spaces to hex
459
460 TQString Output;
461
462 VColor copy( color );
463 copy.setColorSpace( VColor::rgb );
464
465 Output.sprintf( "#%02x%02x%02x", int( copy[0] * 255.0 ), int( copy[1] * 255.0 ), int( copy[2] * 255.0 ) );
466
467 *stream << Output;
468}
469
470void
471SvgExport::visitVText( VText& text )
472{
473 VPath path( 0L );
474 path.combinePath( text.basePath() );
475
476 m_trans->visitVPath( path );
477
478 TQString id = createUID();
479 writePathToStream( path, " id=\""+ id + "\"", m_defs, m_indent2 );
480
481 printIndentation( m_body, m_indent++ );
482 *m_body << "<text" << getID( &text );
483 //*m_body << " transform=\"scale(1, -1) translate(0, -" << text.document()->height() << ")\"";
484 getFill( *( text.fill() ), m_body );
485 getStroke( *( text.stroke() ), m_body );
486
487 *m_body << " font-family=\"" << text.font().family() << "\"";
488 *m_body << " font-size=\"" << text.font().pointSize() << "\"";
489 if( text.font().bold() )
490 *m_body << " font-weight=\"bold\"";
491 if( text.font().italic() )
492 *m_body << " font-style=\"italic\"";
493 if( text.alignment() == VText::Center )
494 *m_body << " text-anchor=\"middle\"";
495 else if( text.alignment() == VText::Right )
496 *m_body << " text-anchor=\"end\"";
497
498 *m_body << ">" << endl;
499
500 printIndentation( m_body, m_indent );
501 *m_body << "<textPath xlink:href=\"#" << id << "\"";
502 if( text.offset() > 0.0 )
503 *m_body << " startOffset=\"" << text.offset() * 100.0 << "%\"";
504 *m_body << ">";
505 *m_body << text.text();
506 *m_body << "</textPath>" << endl;
507 printIndentation( m_body, --m_indent );
508 *m_body << "</text>" << endl;
509}
510
511#include "svgexport.moc"
512