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