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