xref: /haiku/src/apps/icon-o-matic/import_export/svg/SVGExporter.cpp (revision b2b94ad94f541e09456be05515d4dacaeb551bc2)
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 // #pragma mark -
129 
130 // SetOriginalEntry
131 void
132 SVGExporter::SetOriginalEntry(const entry_ref* ref)
133 {
134 	if (ref) {
135 		delete fOriginalEntry;
136 		fOriginalEntry = new entry_ref(*ref);
137 	}
138 }
139 
140 // DisplayWarning
141 bool
142 SVGExporter::_DisplayWarning() const
143 {
144 	BAlert* alert = new BAlert(B_TRANSLATE("warning"),
145 							   B_TRANSLATE("Icon-O-Matic might not have "
146 							   "interpreted all data from the SVG "
147 							   "when it was loaded. "
148 							   "By overwriting the original "
149 							   "file, this information would now "
150 							   "be lost."),
151 							   B_TRANSLATE("Cancel"),
152 							   B_TRANSLATE("Overwrite"));
153 	alert->SetShortcut(0, B_ESCAPE);
154 	return alert->Go() == 1;
155 }
156 
157 // #pragma mark -
158 
159 // convert_join_mode_svg
160 const char*
161 convert_join_mode_svg(agg::line_join_e mode)
162 {
163 	const char* svgMode = "miter";
164 	switch (mode) {
165 		case agg::round_join:
166 			svgMode = "round";
167 			break;
168 		case agg::bevel_join:
169 			svgMode = "bevel";
170 			break;
171 		case agg::miter_join:
172 		default:
173 			break;
174 	}
175 	return svgMode;
176 }
177 
178 // convert_cap_mode_svg
179 const char*
180 convert_cap_mode_svg(agg::line_cap_e mode)
181 {
182 	const char* svgMode = "butt";
183 	switch (mode) {
184 		case agg::square_cap:
185 			svgMode = "square";
186 			break;
187 		case agg::round_cap:
188 			svgMode = "round";
189 			break;
190 		case agg::butt_cap:
191 		default:
192 			break;
193 	}
194 	return svgMode;
195 }
196 
197 // #pragma mark -
198 
199 // _ExportShape
200 status_t
201 SVGExporter::_ExportShape(const Shape* shape, BPositionIO* stream)
202 {
203 	if (shape->MaxVisibilityScale() < 1.0
204 		|| shape->MinVisibilityScale() > 1.0) {
205 		// don't export shapes which are not visible at the
206 		// default scale
207 		return B_OK;
208 	}
209 
210 	const Style* style = shape->Style();
211 
212 	char color[64];
213 	status_t ret = _GetFill(style, color, stream);
214 
215 	if (ret < B_OK)
216 		return ret;
217 
218 	// The transformation matrix is extracted again in order to
219 	// maintain the effect of a distorted outline. There is of
220 	// course a difference when applying the transformation to
221 	// the points of the path, then stroking the transformed
222 	// path with an outline of a certain width, opposed to applying
223 	// the transformation to the points of the generated stroke
224 	// as well. Adobe SVG Viewer is supporting this fairly well,
225 	// though I have come across SVG software that doesn't (InkScape).
226 
227 	// start new shape and write transform matrix
228 	BString helper;
229 	helper << "  <path ";
230 
231 	// hack to see if this is an outline shape
232 	StrokeTransformer* stroke
233 		= dynamic_cast<StrokeTransformer*>(shape->TransformerAt(0));
234 	if (stroke) {
235 		helper << "style=\"fill:none; stroke:" << color;
236 		if (!style->Gradient() && style->Color().alpha < 255) {
237 			helper << "; stroke-opacity:";
238 			append_float(helper, style->Color().alpha / 255.0);
239 		}
240 		helper << "; stroke-width:";
241 		append_float(helper, stroke->width());
242 
243 		if (stroke->line_cap() != agg::butt_cap) {
244 			helper << "; stroke-linecap:";
245 			helper << convert_cap_mode_svg(stroke->line_cap());
246 		}
247 
248 		if (stroke->line_join() != agg::miter_join) {
249 			helper << "; stroke-linejoin:";
250 			helper << convert_join_mode_svg(stroke->line_join());
251 		}
252 
253 		helper << "\"\n";
254 	} else {
255 		helper << "style=\"fill:" << color;
256 
257 		if (!style->Gradient() && style->Color().alpha < 255) {
258 			helper << "; fill-opacity:";
259 			append_float(helper, style->Color().alpha / 255.0);
260 		}
261 
262 //		if (shape->FillingRule() == FILL_MODE_EVEN_ODD &&
263 //			shape->Paths()->CountPaths() > 1)
264 //			helper << "; fill-rule:evenodd";
265 
266 		helper << "\"\n";
267 	}
268 
269 	helper << "        d=\"";
270 	ret = write_line(stream, helper);
271 
272 	if (ret < B_OK)
273 		return ret;
274 
275 	int32 count = shape->Paths()->CountPaths();
276 	for (int32 i = 0; i < count; i++) {
277 		VectorPath* path = shape->Paths()->PathAtFast(i);
278 
279 		if (i > 0) {
280 			helper << "\n           ";
281 		}
282 
283 		BPoint a, aIn, aOut;
284 		if (path->GetPointAt(0, a)) {
285 			helper << "M";
286 			append_float(helper, a.x, 2);
287 			helper << " ";
288 			append_float(helper, a.y, 2);
289 		}
290 
291 		int32 pointCount = path->CountPoints();
292 		for (int32 j = 0; j < pointCount; j++) {
293 
294 			if (!path->GetPointsAt(j, a, aIn, aOut))
295 				break;
296 
297 			BPoint b, bIn, bOut;
298 			if ((j + 1 < pointCount && path->GetPointsAt(j + 1, b, bIn, bOut))
299 				|| (path->IsClosed() && path->GetPointsAt(0, b, bIn, bOut))) {
300 
301 				if (aOut == a && bIn == b) {
302 					if (a.x == b.x) {
303 						// vertical line
304 						helper << "V";
305 						append_float(helper, b.y, 2);
306 					} else if (a.y == b.y) {
307 						// horizontal line
308 						helper << "H";
309 						append_float(helper, b.x, 2);
310 					} else {
311 						// line
312 						helper << "L";
313 						append_float(helper, b.x, 2);
314 						helper << " ";
315 						append_float(helper, b.y, 2);
316 					}
317 				} else {
318 					// cubic curve
319 					helper << "C";
320 					append_float(helper, aOut.x, 2);
321 					helper << " ";
322 					append_float(helper, aOut.y, 2);
323 					helper << " ";
324 					append_float(helper, bIn.x, 2);
325 					helper << " ";
326 					append_float(helper, bIn.y, 2);
327 					helper << " ";
328 					append_float(helper, b.x, 2);
329 					helper << " ";
330 					append_float(helper, b.y, 2);
331 				}
332 			}
333 		}
334 		if (path->IsClosed())
335 			helper << "z";
336 
337 		ret = write_line(stream, helper);
338 		if (ret < B_OK)
339 			break;
340 	}
341 	helper << "\"\n";
342 
343 	if (!shape->IsIdentity()) {
344 		helper << "        transform=\"";
345 		_AppendMatrix(shape, helper);
346 		helper << "\n";
347 	}
348 
349 	if (ret >= B_OK) {
350 		helper << "  />\n";
351 
352 		ret = write_line(stream, helper);
353 	}
354 
355 	return ret;
356 }
357 
358 // _ExportGradient
359 status_t
360 SVGExporter::_ExportGradient(const Gradient* gradient, BPositionIO* stream)
361 {
362 	BString helper;
363 
364 	// start new gradient tag
365 	if (gradient->Type() == GRADIENT_CIRCULAR) {
366 		helper << "  <radialGradient ";
367 	} else {
368 		helper << "  <linearGradient ";
369 	}
370 
371 	// id
372 	BString gradientName("gradient");
373 	gradientName << fGradientCount;
374 
375 	helper << "id=\"" << gradientName << "\" gradientUnits=\"userSpaceOnUse\"";
376 
377 	// write gradient transformation
378 	if (gradient->Type() == GRADIENT_CIRCULAR) {
379 		helper << " cx=\"0\" cy=\"0\" r=\"64\"";
380 		if (!gradient->IsIdentity()) {
381 			helper << " gradientTransform=\"";
382 			_AppendMatrix(gradient, helper);
383 		}
384 	} else {
385 		double x1 = -64.0;
386 		double y1 = -64.0;
387 		double x2 = 64.0;
388 		double y2 = -64.0;
389 		gradient->Transform(&x1, &y1);
390 		gradient->Transform(&x2, &y2);
391 
392 		helper << " x1=\"";
393 		append_float(helper, x1, 2);
394 		helper << "\"";
395 		helper << " y1=\"";
396 		append_float(helper, y1, 2);
397 		helper << "\"";
398 		helper << " x2=\"";
399 		append_float(helper, x2, 2);
400 		helper << "\"";
401 		helper << " y2=\"";
402 		append_float(helper, y2, 2);
403 		helper << "\"";
404 	}
405 
406 	helper << ">\n";
407 
408 	// write stop tags
409 	char color[16];
410 	for (int32 i = 0; BGradient::ColorStop* stop = gradient->ColorAt(i); i++) {
411 
412 		sprintf(color, "%.2x%.2x%.2x", stop->color.red,
413 									   stop->color.green,
414 									   stop->color.blue);
415 		color[6] = 0;
416 
417 		helper << "   <stop offset=\"";
418 		append_float(helper, stop->offset);
419 		helper << "\" stop-color=\"#" << color << "\"";
420 
421 		if (stop->color.alpha < 255) {
422 			helper << " stop-opacity=\"";
423 			append_float(helper, (float)stop->color.alpha / 255.0);
424 			helper << "\"";
425 		}
426 		helper << "/>\n";
427 	}
428 
429 	// finish gradient tag
430 	if (gradient->Type() == GRADIENT_CIRCULAR) {
431 		helper << "  </radialGradient>\n";
432 	} else {
433 		helper << "  </linearGradient>\n";
434 	}
435 
436 	return write_line(stream, helper);
437 }
438 
439 // _AppendMatrix
440 void
441 SVGExporter::_AppendMatrix(const Transformable* object, BString& string) const
442 {
443 	string << "matrix(";
444 //	append_float(string, object->sx);
445 //	string << ",";
446 //	append_float(string, object->shy);
447 //	string << ",";
448 //	append_float(string, object->shx);
449 //	string << ",";
450 //	append_float(string, object->sy);
451 //	string << ",";
452 //	append_float(string, object->tx);
453 //	string << ",";
454 //	append_float(string, object->ty);
455 double matrix[Transformable::matrix_size];
456 object->StoreTo(matrix);
457 append_float(string, matrix[0]);
458 string << ",";
459 append_float(string, matrix[1]);
460 string << ",";
461 append_float(string, matrix[2]);
462 string << ",";
463 append_float(string, matrix[3]);
464 string << ",";
465 append_float(string, matrix[4]);
466 string << ",";
467 append_float(string, matrix[5]);
468 	string << ")\"";
469 }
470 
471 // _GetFill
472 status_t
473 SVGExporter::_GetFill(const Style* style, char* string,
474 					  BPositionIO* stream)
475 {
476 	status_t ret = B_OK;
477 	if (Gradient* gradient = style->Gradient()) {
478 		ret = _ExportGradient(gradient, stream);
479 		sprintf(string, "url(#gradient%" B_PRId32 ")", fGradientCount++);
480 	} else {
481 		sprintf(string, "#%.2x%.2x%.2x", style->Color().red,
482 										 style->Color().green,
483 										 style->Color().blue);
484 	}
485 	return ret;
486 }
487