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