* Copyright 2006, 2023, Haiku. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
* Zardshard
*/
#include <stdio.h>
#include <Alert.h>
#include <Catalog.h>
#include <DataIO.h>
#include <File.h>
#include <Locale.h>
#include <String.h>
#include "support.h"
#include "Icon.h"
#include "GradientTransformable.h"
#include "PathSourceShape.h"
#include "Shape.h"
#include "StrokeTransformer.h"
#include "Style.h"
#include "VectorPath.h"
#include "SVGExporter.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Icon-O-Matic-SVGExport"
static status_t
write_line(BPositionIO* stream, BString& string)
{
ssize_t written = stream->Write(string.String(), string.Length());
if (written > B_OK && written < string.Length())
written = B_ERROR;
string.SetTo("");
return written;
}
SVGExporter::SVGExporter()
: Exporter(),
fShownUnsupportedGradientWarning(false),
fGradientCount(0),
fOriginalEntry(NULL)
{
}
SVGExporter::~SVGExporter()
{
delete fOriginalEntry;
}
status_t
SVGExporter::Export(const Icon* icon, BPositionIO* stream)
{
if (!icon || !stream)
return B_BAD_VALUE;
BString helper;
fGradientCount = 0;
helper << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
status_t ret = write_line(stream, helper);
if (ret >= B_OK) {
helper << "<svg version=\"1.1\" width=\"" << 64 << "\""
<< " height=\"" << 64 << "\""
<< " color-interpolation=\"linearRGB\"\n"
<< " xmlns:svg=\"http://www.w3.org/2000/svg\""
<< " xmlns=\"http://www.w3.org/2000/svg\">\n";
ret = write_line(stream, helper);
}
if (ret >= B_OK) {
helper << " <g>\n";
ret = write_line(stream, helper);
if (ret >= B_OK) {
int32 count = icon->Shapes()->CountItems();
for (int32 i = 0; i < count; i++) {
Shape* shape = icon->Shapes()->ItemAtFast(i);
ret = _ExportShape(shape, stream);
if (ret < B_OK)
break;
}
}
if (ret >= B_OK) {
helper << " </g>\n";
ret = write_line(stream, helper);
}
}
if (ret >= B_OK) {
helper << "</svg>\n";
ret = write_line(stream, helper);
}
return ret;
}
void
SVGExporter::SetOriginalEntry(const entry_ref* ref)
{
if (ref) {
delete fOriginalEntry;
fOriginalEntry = new entry_ref(*ref);
}
}
bool
SVGExporter::_DisplayWarning() const
{
BAlert* alert = new BAlert(B_TRANSLATE("warning"),
B_TRANSLATE("Icon-O-Matic might not have "
"interpreted all data from the SVG "
"when it was loaded. "
"By overwriting the original "
"file, this information would now "
"be lost."),
B_TRANSLATE("Cancel"),
B_TRANSLATE("Overwrite"));
alert->SetShortcut(0, B_ESCAPE);
return alert->Go() == 1;
}
void
SVGExporter::_DisplayUnsupportedGradientWarning() const
{
if (fShownUnsupportedGradientWarning)
return;
BAlert* alert = new BAlert(
B_TRANSLATE("SVG export"),
B_TRANSLATE("SVG does not support some gradient types "
"(conic, diamond, xy, sqrt_xy).\n\n"
"They will be exported as linear approximations and "
"may look different in other viewers."),
B_TRANSLATE("OK")
);
alert->Go();
fShownUnsupportedGradientWarning = true;
}
const char*
convert_join_mode_svg(agg::line_join_e mode)
{
const char* svgMode = "miter";
switch (mode) {
case agg::round_join:
svgMode = "round";
break;
case agg::bevel_join:
svgMode = "bevel";
break;
case agg::miter_join:
default:
break;
}
return svgMode;
}
const char*
convert_cap_mode_svg(agg::line_cap_e mode)
{
const char* svgMode = "butt";
switch (mode) {
case agg::square_cap:
svgMode = "square";
break;
case agg::round_cap:
svgMode = "round";
break;
case agg::butt_cap:
default:
break;
}
return svgMode;
}
status_t
SVGExporter::_ExportShape(const Shape* shape, BPositionIO* stream)
{
if (!shape->Visible(1.0)) {
return B_OK;
}
const PathSourceShape* pathSourceShape = dynamic_cast<const PathSourceShape*>(shape);
if (pathSourceShape == NULL)
return B_OK;
const Style* style = pathSourceShape->Style();
char color[64];
status_t ret = _GetFill(style, color, stream);
if (ret < B_OK)
return ret;
BString helper;
helper << " <path ";
StrokeTransformer* stroke
= dynamic_cast<StrokeTransformer*>(pathSourceShape->Transformers()->ItemAt(0));
if (stroke) {
helper << "style=\"fill:none; stroke:" << color;
if (!style->Gradient() && style->Color().alpha < 255) {
helper << "; stroke-opacity:";
append_float(helper, style->Color().alpha / 255.0);
}
helper << "; stroke-width:";
append_float(helper, stroke->width());
if (stroke->line_cap() != agg::butt_cap) {
helper << "; stroke-linecap:";
helper << convert_cap_mode_svg(stroke->line_cap());
}
if (stroke->line_join() != agg::miter_join) {
helper << "; stroke-linejoin:";
helper << convert_join_mode_svg(stroke->line_join());
}
helper << "\"\n";
} else {
helper << "style=\"fill:" << color;
if (!style->Gradient() && style->Color().alpha < 255) {
helper << "; fill-opacity:";
append_float(helper, style->Color().alpha / 255.0);
}
helper << "\"\n";
}
helper << " d=\"";
ret = write_line(stream, helper);
if (ret < B_OK)
return ret;
int32 count = pathSourceShape->Paths()->CountItems();
for (int32 i = 0; i < count; i++) {
VectorPath* path = pathSourceShape->Paths()->ItemAtFast(i);
if (i > 0) {
helper << "\n ";
}
BPoint a, aIn, aOut;
if (path->GetPointAt(0, a)) {
helper << "M";
append_float(helper, a.x, 2);
helper << " ";
append_float(helper, a.y, 2);
}
int32 pointCount = path->CountPoints();
for (int32 j = 0; j < pointCount; j++) {
if (!path->GetPointsAt(j, a, aIn, aOut))
break;
BPoint b, bIn, bOut;
if ((j + 1 < pointCount && path->GetPointsAt(j + 1, b, bIn, bOut))
|| (path->IsClosed() && path->GetPointsAt(0, b, bIn, bOut))) {
if (aOut == a && bIn == b) {
if (a.x == b.x) {
helper << "V";
append_float(helper, b.y, 2);
} else if (a.y == b.y) {
helper << "H";
append_float(helper, b.x, 2);
} else {
helper << "L";
append_float(helper, b.x, 2);
helper << " ";
append_float(helper, b.y, 2);
}
} else {
helper << "C";
append_float(helper, aOut.x, 2);
helper << " ";
append_float(helper, aOut.y, 2);
helper << " ";
append_float(helper, bIn.x, 2);
helper << " ";
append_float(helper, bIn.y, 2);
helper << " ";
append_float(helper, b.x, 2);
helper << " ";
append_float(helper, b.y, 2);
}
}
}
if (path->IsClosed())
helper << "z";
ret = write_line(stream, helper);
if (ret < B_OK)
break;
}
helper << "\"\n";
if (!pathSourceShape->IsIdentity()) {
helper << " transform=\"";
_AppendMatrix(pathSourceShape, helper);
helper << "\n";
}
if (ret >= B_OK) {
helper << " />\n";
ret = write_line(stream, helper);
}
return ret;
}
status_t
SVGExporter::_ExportGradient(const Gradient* gradient, BPositionIO* stream)
{
BString helper;
if (gradient->Type() == GRADIENT_CONIC
|| gradient->Type() == GRADIENT_DIAMOND
|| gradient->Type() == GRADIENT_XY
|| gradient->Type() == GRADIENT_SQRT_XY)
_DisplayUnsupportedGradientWarning();
if (gradient->Type() == GRADIENT_CIRCULAR) {
helper << " <radialGradient ";
} else {
helper << " <linearGradient ";
}
BString gradientName("gradient");
gradientName << fGradientCount;
helper << "id=\"" << gradientName << "\" gradientUnits=\"userSpaceOnUse\"";
if (gradient->Type() == GRADIENT_CIRCULAR) {
helper << " cx=\"0\" cy=\"0\" r=\"64\"";
if (!gradient->IsIdentity()) {
helper << " gradientTransform=\"";
_AppendMatrix(gradient, helper);
}
} else {
double x1 = -64.0;
double y1 = -64.0;
double x2 = 64.0;
double y2 = -64.0;
gradient->Transform(&x1, &y1);
gradient->Transform(&x2, &y2);
helper << " x1=\"";
append_float(helper, x1, 2);
helper << "\"";
helper << " y1=\"";
append_float(helper, y1, 2);
helper << "\"";
helper << " x2=\"";
append_float(helper, x2, 2);
helper << "\"";
helper << " y2=\"";
append_float(helper, y2, 2);
helper << "\"";
}
helper << ">\n";
char color[16];
for (int32 i = 0; BGradient::ColorStop* stop = gradient->ColorAt(i); i++) {
sprintf(color, "%.2x%.2x%.2x", stop->color.red,
stop->color.green,
stop->color.blue);
color[6] = 0;
helper << " <stop offset=\"";
append_float(helper, stop->offset);
helper << "\" stop-color=\"#" << color << "\"";
if (stop->color.alpha < 255) {
helper << " stop-opacity=\"";
append_float(helper, (float)stop->color.alpha / 255.0);
helper << "\"";
}
helper << "/>\n";
}
if (gradient->Type() == GRADIENT_CIRCULAR) {
helper << " </radialGradient>\n";
} else {
helper << " </linearGradient>\n";
}
return write_line(stream, helper);
}
void
SVGExporter::_AppendMatrix(const Transformable* object, BString& string) const
{
string << "matrix(";
double matrix[Transformable::matrix_size];
object->StoreTo(matrix);
append_float(string, matrix[0]);
string << ",";
append_float(string, matrix[1]);
string << ",";
append_float(string, matrix[2]);
string << ",";
append_float(string, matrix[3]);
string << ",";
append_float(string, matrix[4]);
string << ",";
append_float(string, matrix[5]);
string << ")\"";
}
status_t
SVGExporter::_GetFill(const Style* style, char* string,
BPositionIO* stream)
{
status_t ret = B_OK;
if (Gradient* gradient = style->Gradient()) {
ret = _ExportGradient(gradient, stream);
sprintf(string, "url(#gradient%" B_PRId32 ")", fGradientCount++);
} else {
sprintf(string, "#%.2x%.2x%.2x", style->Color().red,
style->Color().green,
style->Color().blue);
}
return ret;
}