/* * Copyright 2006, Haiku. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Stephan Aßmus */ #include #include #include #include #include #include #include #include "support.h" #include "Icon.h" #include "GradientTransformable.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" // write_line 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; } // #pragma mark - // constructor SVGExporter::SVGExporter() : Exporter(), fGradientCount(0), fOriginalEntry(NULL) { } // destructor SVGExporter::~SVGExporter() { delete fOriginalEntry; } // Export status_t SVGExporter::Export(const Icon* icon, BPositionIO* stream) { if (!icon || !stream) return B_BAD_VALUE; // if (fOriginalEntry && *fOriginalEntry == *refToFinalFile) { // if (!_DisplayWarning()) { // return B_CANCELED; // } else { // delete fOriginalEntry; // fOriginalEntry = NULL; // } // } BString helper; fGradientCount = 0; // header helper << "\n"; status_t ret = write_line(stream, helper); // image size if (ret >= B_OK) { helper << "\n"; ret = write_line(stream, helper); } if (ret >= B_OK) { // start (the only) layer helper << " \n"; ret = write_line(stream, helper); // export all shapes if (ret >= B_OK) { int32 count = icon->Shapes()->CountShapes(); for (int32 i = 0; i < count; i++) { Shape* shape = icon->Shapes()->ShapeAtFast(i); ret = _ExportShape(shape, stream); if (ret < B_OK) break; } } // finish (the only) layer if (ret >= B_OK) { helper << " \n"; ret = write_line(stream, helper); } } // footer if (ret >= B_OK) { helper << "\n"; ret = write_line(stream, helper); } return ret; } // MIMEType const char* SVGExporter::MIMEType() { return "image/svg+xml"; } // Extension const char* SVGExporter::Extension() { return "svg"; } // #pragma mark - // SetOriginalEntry void SVGExporter::SetOriginalEntry(const entry_ref* ref) { if (ref) { delete fOriginalEntry; fOriginalEntry = new entry_ref(*ref); } } // DisplayWarning 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; } // #pragma mark - // convert_join_mode_svg 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; } // convert_cap_mode_svg 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; } // #pragma mark - // _ExportShape status_t SVGExporter::_ExportShape(const Shape* shape, BPositionIO* stream) { if (shape->MaxVisibilityScale() < 1.0 || shape->MinVisibilityScale() > 1.0) { // don't export shapes which are not visible at the // default scale return B_OK; } const Style* style = shape->Style(); char color[64]; status_t ret = _GetFill(style, color, stream); if (ret < B_OK) return ret; // The transformation matrix is extracted again in order to // maintain the effect of a distorted outline. There is of // course a difference when applying the transformation to // the points of the path, then stroking the transformed // path with an outline of a certain width, opposed to applying // the transformation to the points of the generated stroke // as well. Adobe SVG Viewer is supporting this fairly well, // though I have come across SVG software that doesn't (InkScape). // start new shape and write transform matrix BString helper; helper << " (shape->TransformerAt(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); } // if (shape->FillingRule() == FILL_MODE_EVEN_ODD && // shape->Paths()->CountPaths() > 1) // helper << "; fill-rule:evenodd"; helper << "\"\n"; } helper << " d=\""; ret = write_line(stream, helper); if (ret < B_OK) return ret; int32 count = shape->Paths()->CountPaths(); for (int32 i = 0; i < count; i++) { VectorPath* path = shape->Paths()->PathAtFast(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) { // vertical line helper << "V"; append_float(helper, b.y, 2); } else if (a.y == b.y) { // horizontal line helper << "H"; append_float(helper, b.x, 2); } else { // line helper << "L"; append_float(helper, b.x, 2); helper << " "; append_float(helper, b.y, 2); } } else { // cubic curve 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 (!shape->IsIdentity()) { helper << " transform=\""; _AppendMatrix(shape, helper); helper << "\n"; } if (ret >= B_OK) { helper << " />\n"; ret = write_line(stream, helper); } return ret; } // _ExportGradient status_t SVGExporter::_ExportGradient(const Gradient* gradient, BPositionIO* stream) { BString helper; // start new gradient tag if (gradient->Type() == GRADIENT_CIRCULAR) { helper << " 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"; // write stop tags 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 << " 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"; } // finish gradient tag if (gradient->Type() == GRADIENT_CIRCULAR) { helper << " \n"; } else { helper << " \n"; } return write_line(stream, helper); } // _AppendMatrix void SVGExporter::_AppendMatrix(const Transformable* object, BString& string) const { string << "matrix("; // append_float(string, object->sx); // string << ","; // append_float(string, object->shy); // string << ","; // append_float(string, object->shx); // string << ","; // append_float(string, object->sy); // string << ","; // append_float(string, object->tx); // string << ","; // append_float(string, object->ty); 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 << ")\""; } // _GetFill 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; }