xref: /haiku/src/apps/icon-o-matic/import_export/svg/SVGExporter.cpp (revision d2e1e872611179c9cfaa43ce11bd58b1e3554e4b)
1 /*
2  * Copyright 2006, Haiku. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Stephan Aßmus <superstippi@gmx.de>
7  */
8 
9 #include <stdio.h>
10 
11 #include <Alert.h>
12 #include <DataIO.h>
13 #include <File.h>
14 #include <String.h>
15 
16 #include "support.h"
17 
18 #include "Icon.h"
19 #include "Gradient.h"
20 #include "Shape.h"
21 #include "StrokeTransformer.h"
22 #include "Style.h"
23 #include "VectorPath.h"
24 
25 #include "SVGExporter.h"
26 
27 // write_line
28 static status_t
29 write_line(BPositionIO* stream, BString& string)
30 {
31 	ssize_t written = stream->Write(string.String(), string.Length());
32 	if (written > B_OK && written < string.Length())
33 		written = B_ERROR;
34 	string.SetTo("");
35 	return written;
36 }
37 
38 // #pragma mark -
39 
40 // constructor
41 SVGExporter::SVGExporter()
42 	: Exporter(),
43 	  fGradientCount(0),
44 	  fOriginalEntry(NULL)
45 {
46 }
47 
48 // destructor
49 SVGExporter::~SVGExporter()
50 {
51 	delete fOriginalEntry;
52 }
53 
54 // Export
55 status_t
56 SVGExporter::Export(const Icon* icon, BPositionIO* stream)
57 {
58 	if (!icon || !stream)
59 		return B_BAD_VALUE;
60 
61 //	if (fOriginalEntry && *fOriginalEntry == *refToFinalFile) {
62 //		if (!_DisplayWarning()) {
63 //			return B_CANCELED;
64 //		} else {
65 //			delete fOriginalEntry;
66 //			fOriginalEntry = NULL;
67 //		}
68 //	}
69 
70 	BString helper;
71 
72 	fGradientCount = 0;
73 
74 	// header
75 	helper << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
76 	status_t ret = write_line(stream, helper);
77 
78 	// image size
79 	if (ret >= B_OK) {
80 		helper << "<svg width=\"" << 64 << "\""
81 			   << " height=\"" << 64 << "\""
82 			   << " color-interpolation=\"linearRGB\">\n";
83 		ret = write_line(stream, helper);
84 	}
85 
86 	if (ret >= B_OK) {
87 		// start (the only) layer
88 		helper << " <g>\n";
89 		ret = write_line(stream, helper);
90 
91 		// export all shapes
92 		if (ret >= B_OK) {
93 			int32 count = icon->Shapes()->CountShapes();
94 			for (int32 i = 0; i < count; i++) {
95 				Shape* shape = icon->Shapes()->ShapeAtFast(i);
96 				ret = _ExportShape(shape, stream);
97 				if (ret < B_OK)
98 					break;
99 			}
100 		}
101 
102 		// finish (the only) layer
103 		if (ret >= B_OK) {
104 			helper << " </g>\n";
105 			ret = write_line(stream, helper);
106 		}
107 	}
108 
109 	// footer
110 	if (ret >= B_OK) {
111 		helper << "</svg>\n";
112 		ret = write_line(stream, helper);
113 	}
114 	return ret;
115 }
116 
117 // MIMEType
118 const char*
119 SVGExporter::MIMEType()
120 {
121 	return "image/svg+xml";
122 }
123 
124 // Extension
125 const char*
126 SVGExporter::Extension()
127 {
128 	return "svg";
129 }
130 
131 // #pragma mark -
132 
133 // SetOriginalEntry
134 void
135 SVGExporter::SetOriginalEntry(const entry_ref* ref)
136 {
137 	if (ref) {
138 		delete fOriginalEntry;
139 		fOriginalEntry = new entry_ref(*ref);
140 	}
141 }
142 
143 // DisplayWarning
144 bool
145 SVGExporter::_DisplayWarning() const
146 {
147 	BAlert* alert = new BAlert("warning",
148 							   "Icon-O-Matic might not have "
149 							   "interpreted all data from the SVG "
150 							   "when it was loaded. "
151 							   "By overwriting the original "
152 							   "file, this information would now "
153 							   "be lost.",
154 							   "Cancel", "Overwrite");
155 	return alert->Go() == 1;
156 }
157 
158 // #pragma mark -
159 
160 // convert_join_mode_svg
161 const char*
162 convert_join_mode_svg(agg::line_join_e mode)
163 {
164 	const char* svgMode = "miter";
165 	switch (mode) {
166 		case agg::round_join:
167 			svgMode = "round";
168 			break;
169 		case agg::bevel_join:
170 			svgMode = "bevel";
171 			break;
172 		case agg::miter_join:
173 		default:
174 			break;
175 	}
176 	return svgMode;
177 }
178 
179 // convert_cap_mode_svg
180 const char*
181 convert_cap_mode_svg(agg::line_cap_e mode)
182 {
183 	const char* svgMode = "butt";
184 	switch (mode) {
185 		case agg::square_cap:
186 			svgMode = "square";
187 			break;
188 		case agg::round_cap:
189 			svgMode = "round";
190 			break;
191 		case agg::butt_cap:
192 		default:
193 			break;
194 	}
195 	return svgMode;
196 }
197 
198 // #pragma mark -
199 
200 // _ExportShape
201 status_t
202 SVGExporter::_ExportShape(const Shape* shape, BPositionIO* stream)
203 {
204 	if (shape->MaxVisibilityScale() < 1.0
205 		|| shape->MinVisibilityScale() > 1.0) {
206 		// don't export shapes which are not visible at the
207 		// default scale
208 		return B_OK;
209 	}
210 
211 	const Style* style = shape->Style();
212 
213 	char color[64];
214 	status_t ret = _GetFill(style, color, stream);
215 
216 	if (ret < B_OK)
217 		return ret;
218 
219 	// The transformation matrix is extracted again in order to
220 	// maintain the effect of a distorted outline. There is of
221 	// course a difference when applying the transformation to
222 	// the points of the path, then stroking the transformed
223 	// path with an outline of a certain width, opposed to applying
224 	// the transformation to the points of the generated stroke
225 	// as well. Adobe SVG Viewer is supporting this fairly well,
226 	// though I have come across SVG software that doesn't (InkScape).
227 
228 	// start new shape and write transform matrix
229 	BString helper;
230 	helper << "  <path ";
231 
232 	// hack to see if this is an outline shape
233 	StrokeTransformer* stroke
234 		= dynamic_cast<StrokeTransformer*>(shape->TransformerAt(0));
235 	if (stroke) {
236 		helper << "style=\"fill:none; stroke:" << color;
237 		if (!style->Gradient() && style->Color().alpha < 255) {
238 			helper << "; stroke-opacity:";
239 			append_float(helper, style->Color().alpha / 255.0);
240 		}
241 		helper << "; stroke-width:";
242 		append_float(helper, stroke->width());
243 
244 		if (stroke->line_cap() != agg::butt_cap) {
245 			helper << "; stroke-linecap:";
246 			helper << convert_cap_mode_svg(stroke->line_cap());
247 		}
248 
249 		if (stroke->line_join() != agg::miter_join) {
250 			helper << "; stroke-linejoin:";
251 			helper << convert_join_mode_svg(stroke->line_join());
252 		}
253 
254 		helper << "\"\n";
255 	} else {
256 		helper << "style=\"fill:" << color;
257 
258 		if (!style->Gradient() && style->Color().alpha < 255) {
259 			helper << "; fill-opacity:";
260 			append_float(helper, style->Color().alpha / 255.0);
261 		}
262 
263 //		if (shape->FillingRule() == FILL_MODE_EVEN_ODD &&
264 //			shape->Paths()->CountPaths() > 1)
265 //			helper << "; fill-rule:evenodd";
266 
267 		helper << "\"\n";
268 	}
269 
270 	helper << "        d=\"";
271 	ret = write_line(stream, helper);
272 
273 	if (ret < B_OK)
274 		return ret;
275 
276 	int32 count = shape->Paths()->CountPaths();
277 	for (int32 i = 0; i < count; i++) {
278 		VectorPath* path = shape->Paths()->PathAtFast(i);
279 
280 		if (i > 0) {
281 			helper << "\n           ";
282 		}
283 
284 		BPoint a, aIn, aOut;
285 		if (path->GetPointAt(0, a)) {
286 			helper << "M";
287 			append_float(helper, a.x, 2);
288 			helper << " ";
289 			append_float(helper, a.y, 2);
290 		}
291 
292 		int32 pointCount = path->CountPoints();
293 		for (int32 j = 0; j < pointCount; j++) {
294 
295 			if (!path->GetPointsAt(j, a, aIn, aOut))
296 				break;
297 
298 			BPoint b, bIn, bOut;
299 			if ((j + 1 < pointCount && path->GetPointsAt(j + 1, b, bIn, bOut))
300 				|| (path->IsClosed() && path->GetPointsAt(0, b, bIn, bOut))) {
301 
302 				if (aOut == a && bIn == b) {
303 					if (a.x == b.x) {
304 						// vertical line
305 						helper << "V";
306 						append_float(helper, b.y, 2);
307 					} else if (a.y == b.y) {
308 						// horizontal line
309 						helper << "H";
310 						append_float(helper, b.x, 2);
311 					} else {
312 						// line
313 						helper << "L";
314 						append_float(helper, b.x, 2);
315 						helper << " ";
316 						append_float(helper, b.y, 2);
317 					}
318 				} else {
319 					// cubic curve
320 					helper << "C";
321 					append_float(helper, aOut.x, 2);
322 					helper << " ";
323 					append_float(helper, aOut.y, 2);
324 					helper << " ";
325 					append_float(helper, bIn.x, 2);
326 					helper << " ";
327 					append_float(helper, bIn.y, 2);
328 					helper << " ";
329 					append_float(helper, b.x, 2);
330 					helper << " ";
331 					append_float(helper, b.y, 2);
332 				}
333 			}
334 		}
335 		if (path->IsClosed())
336 			helper << "z";
337 
338 		ret = write_line(stream, helper);
339 		if (ret < B_OK)
340 			break;
341 	}
342 	helper << "\"\n";
343 
344 	if (!shape->IsIdentity()) {
345 		helper << "        transform=\"";
346 		_AppendMatrix(shape, helper);
347 		helper << "\n";
348 	}
349 
350 	if (ret >= B_OK) {
351 		helper << "  />\n";
352 
353 		ret = write_line(stream, helper);
354 	}
355 
356 	return ret;
357 }
358 
359 // _ExportGradient
360 status_t
361 SVGExporter::_ExportGradient(const Gradient* gradient, BPositionIO* stream)
362 {
363 	BString helper;
364 
365 	// start new gradient tag
366 	if (gradient->Type() == GRADIENT_CIRCULAR) {
367 		helper << "  <radialGradient ";
368 	} else {
369 		helper << "  <linearGradient ";
370 	}
371 
372 	// id
373 	BString gradientName("gradient");
374 	gradientName << fGradientCount;
375 
376 	helper << "id=\"" << gradientName << "\" gradientUnits=\"userSpaceOnUse\"";
377 
378 	// write gradient transformation
379 	if (gradient->Type() == GRADIENT_CIRCULAR) {
380 		helper << " cx=\"0\" cy=\"0\" r=\"64\"";
381 		if (!gradient->IsIdentity()) {
382 			helper << " gradientTransform=\"";
383 			_AppendMatrix(gradient, helper);
384 		}
385 	} else {
386 		double x1 = -64.0;
387 		double y1 = -64.0;
388 		double x2 = 64.0;
389 		double y2 = -64.0;
390 		gradient->Transform(&x1, &y1);
391 		gradient->Transform(&x2, &y2);
392 
393 		helper << " x1=\"";
394 		append_float(helper, x1, 2);
395 		helper << "\"";
396 		helper << " y1=\"";
397 		append_float(helper, y1, 2);
398 		helper << "\"";
399 		helper << " x2=\"";
400 		append_float(helper, x2, 2);
401 		helper << "\"";
402 		helper << " y2=\"";
403 		append_float(helper, y2, 2);
404 		helper << "\"";
405 	}
406 
407 	helper << ">\n";
408 
409 	// write stop tags
410 	char color[16];
411 	for (int32 i = 0; color_step* stop = gradient->ColorAt(i); i++) {
412 
413 		sprintf(color, "%.2x%.2x%.2x", stop->color.red,
414 									   stop->color.green,
415 									   stop->color.blue);
416 		color[6] = 0;
417 
418 		helper << "   <stop offset=\"";
419 		append_float(helper, stop->offset);
420 		helper << "\" stop-color=\"#" << color << "\"";
421 
422 		if (stop->color.alpha < 255) {
423 			helper << " stop-opacity=\"";
424 			append_float(helper, (float)stop->color.alpha / 255.0);
425 			helper << "\"";
426 		}
427 		helper << "/>\n";
428 	}
429 
430 	// finish gradient tag
431 	if (gradient->Type() == GRADIENT_CIRCULAR) {
432 		helper << "  </radialGradient>\n";
433 	} else {
434 		helper << "  </linearGradient>\n";
435 	}
436 
437 	return write_line(stream, helper);
438 }
439 
440 // _AppendMatrix
441 void
442 SVGExporter::_AppendMatrix(const Transformable* object, BString& string) const
443 {
444 	string << "matrix(";
445 //	append_float(string, object->sx);
446 //	string << ",";
447 //	append_float(string, object->shy);
448 //	string << ",";
449 //	append_float(string, object->shx);
450 //	string << ",";
451 //	append_float(string, object->sy);
452 //	string << ",";
453 //	append_float(string, object->tx);
454 //	string << ",";
455 //	append_float(string, object->ty);
456 double matrix[Transformable::matrix_size];
457 object->StoreTo(matrix);
458 append_float(string, matrix[0]);
459 string << ",";
460 append_float(string, matrix[1]);
461 string << ",";
462 append_float(string, matrix[2]);
463 string << ",";
464 append_float(string, matrix[3]);
465 string << ",";
466 append_float(string, matrix[4]);
467 string << ",";
468 append_float(string, matrix[5]);
469 	string << ")\"";
470 }
471 
472 // _GetFill
473 status_t
474 SVGExporter::_GetFill(const Style* style, char* string,
475 					  BPositionIO* stream)
476 {
477 	status_t ret = B_OK;
478 	if (Gradient* gradient = style->Gradient()) {
479 		ret = _ExportGradient(gradient, stream);
480 		sprintf(string, "url(#gradient%ld)", fGradientCount++);
481 	} else {
482 		sprintf(string, "#%.2x%.2x%.2x", style->Color().red,
483 										 style->Color().green,
484 										 style->Color().blue);
485 	}
486 	return ret;
487 }
488