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