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