1/* 2 * Copyright 2015 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "src/svg/SkSVGDevice.h" 9 10#include <memory> 11 12#include "include/core/SkBitmap.h" 13#include "include/core/SkBlendMode.h" 14#include "include/core/SkColorFilter.h" 15#include "include/core/SkData.h" 16#include "include/core/SkImage.h" 17#include "include/core/SkImageEncoder.h" 18#include "include/core/SkPaint.h" 19#include "include/core/SkPathBuilder.h" 20#include "include/core/SkShader.h" 21#include "include/core/SkStream.h" 22#include "include/core/SkTypeface.h" 23#include "include/private/SkChecksum.h" 24#include "include/private/SkTHash.h" 25#include "include/private/SkTPin.h" 26#include "include/private/SkTo.h" 27#include "include/svg/SkSVGCanvas.h" 28#include "include/utils/SkBase64.h" 29#include "src/codec/SkJpegCodec.h" 30#include "src/core/SkAnnotationKeys.h" 31#include "src/core/SkClipStack.h" 32#include "src/core/SkDraw.h" 33#include "src/core/SkFontPriv.h" 34#include "src/core/SkUtils.h" 35#include "src/image/SkImage_Base.h" 36#include "src/shaders/SkShaderBase.h" 37#include "src/xml/SkXMLWriter.h" 38 39namespace { 40 41static SkString svg_color(SkColor color) { 42 // https://www.w3.org/TR/css-color-3/#html4 43 auto named_color = [](SkColor c) -> const char* { 44 switch (c & 0xffffff) { 45 case 0x000000: return "black"; 46 case 0x000080: return "navy"; 47 case 0x0000ff: return "blue"; 48 case 0x008000: return "green"; 49 case 0x008080: return "teal"; 50 case 0x00ff00: return "lime"; 51 case 0x00ffff: return "aqua"; 52 case 0x800000: return "maroon"; 53 case 0x800080: return "purple"; 54 case 0x808000: return "olive"; 55 case 0x808080: return "gray"; 56 case 0xc0c0c0: return "silver"; 57 case 0xff0000: return "red"; 58 case 0xff00ff: return "fuchsia"; 59 case 0xffff00: return "yellow"; 60 case 0xffffff: return "white"; 61 default: break; 62 } 63 64 return nullptr; 65 }; 66 67 if (const auto* nc = named_color(color)) { 68 return SkString(nc); 69 } 70 71 uint8_t r = SkColorGetR(color); 72 uint8_t g = SkColorGetG(color); 73 uint8_t b = SkColorGetB(color); 74 75 // Some users care about every byte here, so we'll use hex colors with single-digit channels 76 // when possible. 77 uint8_t rh = r >> 4; 78 uint8_t rl = r & 0xf; 79 uint8_t gh = g >> 4; 80 uint8_t gl = g & 0xf; 81 uint8_t bh = b >> 4; 82 uint8_t bl = b & 0xf; 83 if ((rh == rl) && (gh == gl) && (bh == bl)) { 84 return SkStringPrintf("#%1X%1X%1X", rh, gh, bh); 85 } 86 87 return SkStringPrintf("#%02X%02X%02X", r, g, b); 88} 89 90static SkScalar svg_opacity(SkColor color) { 91 return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE; 92} 93 94// Keep in sync with SkPaint::Cap 95static const char* cap_map[] = { 96 nullptr, // kButt_Cap (default) 97 "round", // kRound_Cap 98 "square" // kSquare_Cap 99}; 100static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry"); 101 102static const char* svg_cap(SkPaint::Cap cap) { 103 SkASSERT(cap < SK_ARRAY_COUNT(cap_map)); 104 return cap_map[cap]; 105} 106 107// Keep in sync with SkPaint::Join 108static const char* join_map[] = { 109 nullptr, // kMiter_Join (default) 110 "round", // kRound_Join 111 "bevel" // kBevel_Join 112}; 113static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry"); 114 115static const char* svg_join(SkPaint::Join join) { 116 SkASSERT(join < SK_ARRAY_COUNT(join_map)); 117 return join_map[join]; 118} 119 120static SkString svg_transform(const SkMatrix& t) { 121 SkASSERT(!t.isIdentity()); 122 123 SkString tstr; 124 switch (t.getType()) { 125 case SkMatrix::kPerspective_Mask: 126 // TODO: handle perspective matrices? 127 break; 128 case SkMatrix::kTranslate_Mask: 129 tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY()); 130 break; 131 case SkMatrix::kScale_Mask: 132 tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY()); 133 break; 134 default: 135 // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined 136 // | a c e | 137 // | b d f | 138 // | 0 0 1 | 139 tstr.printf("matrix(%g %g %g %g %g %g)", 140 t.getScaleX(), t.getSkewY(), 141 t.getSkewX(), t.getScaleY(), 142 t.getTranslateX(), t.getTranslateY()); 143 break; 144 } 145 146 return tstr; 147} 148 149struct Resources { 150 Resources(const SkPaint& paint) 151 : fPaintServer(svg_color(paint.getColor())) {} 152 153 SkString fPaintServer; 154 SkString fColorFilter; 155}; 156 157// Determine if the paint requires us to reset the viewport. 158// Currently, we do this whenever the paint shader calls 159// for a repeating image. 160bool RequiresViewportReset(const SkPaint& paint) { 161 SkShader* shader = paint.getShader(); 162 if (!shader) 163 return false; 164 165 SkTileMode xy[2]; 166 SkImage* image = shader->isAImage(nullptr, xy); 167 168 if (!image) 169 return false; 170 171 for (int i = 0; i < 2; i++) { 172 if (xy[i] == SkTileMode::kRepeat) 173 return true; 174 } 175 return false; 176} 177 178void AddPath(const SkGlyphRun& glyphRun, const SkPoint& offset, SkPath* path) { 179 struct Rec { 180 SkPath* fPath; 181 const SkPoint fOffset; 182 const SkPoint* fPos; 183 } rec = { path, offset, glyphRun.positions().data() }; 184 185 glyphRun.font().getPaths(glyphRun.glyphsIDs().data(), SkToInt(glyphRun.glyphsIDs().size()), 186 [](const SkPath* path, const SkMatrix& mx, void* ctx) { 187 Rec* rec = reinterpret_cast<Rec*>(ctx); 188 if (path) { 189 SkMatrix total = mx; 190 total.postTranslate(rec->fPos->fX + rec->fOffset.fX, 191 rec->fPos->fY + rec->fOffset.fY); 192 rec->fPath->addPath(*path, total); 193 } else { 194 // TODO: this is going to drop color emojis. 195 } 196 rec->fPos += 1; // move to the next glyph's position 197 }, &rec); 198} 199 200} // namespace 201 202// For now all this does is serve unique serial IDs, but it will eventually evolve to track 203// and deduplicate resources. 204class SkSVGDevice::ResourceBucket : ::SkNoncopyable { 205public: 206 ResourceBucket() 207 : fGradientCount(0) 208 , fPathCount(0) 209 , fImageCount(0) 210 , fPatternCount(0) 211 , fColorFilterCount(0) {} 212 213 SkString addLinearGradient() { 214 return SkStringPrintf("gradient_%d", fGradientCount++); 215 } 216 217 SkString addPath() { 218 return SkStringPrintf("path_%d", fPathCount++); 219 } 220 221 SkString addImage() { 222 return SkStringPrintf("img_%d", fImageCount++); 223 } 224 225 SkString addColorFilter() { return SkStringPrintf("cfilter_%d", fColorFilterCount++); } 226 227 SkString addPattern() { 228 return SkStringPrintf("pattern_%d", fPatternCount++); 229 } 230 231private: 232 uint32_t fGradientCount; 233 uint32_t fPathCount; 234 uint32_t fImageCount; 235 uint32_t fPatternCount; 236 uint32_t fColorFilterCount; 237}; 238 239struct SkSVGDevice::MxCp { 240 const SkMatrix* fMatrix; 241 const SkClipStack* fClipStack; 242 243 MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {} 244 MxCp(SkSVGDevice* device) : fMatrix(&device->localToDevice()), fClipStack(&device->cs()) {} 245}; 246 247class SkSVGDevice::AutoElement : ::SkNoncopyable { 248public: 249 AutoElement(const char name[], SkXMLWriter* writer) 250 : fWriter(writer) 251 , fResourceBucket(nullptr) { 252 fWriter->startElement(name); 253 } 254 255 AutoElement(const char name[], const std::unique_ptr<SkXMLWriter>& writer) 256 : AutoElement(name, writer.get()) {} 257 258 AutoElement(const char name[], SkSVGDevice* svgdev, 259 ResourceBucket* bucket, const MxCp& mc, const SkPaint& paint) 260 : fWriter(svgdev->fWriter.get()) 261 , fResourceBucket(bucket) { 262 263 svgdev->syncClipStack(*mc.fClipStack); 264 Resources res = this->addResources(mc, paint); 265 266 fWriter->startElement(name); 267 268 this->addPaint(paint, res); 269 270 if (!mc.fMatrix->isIdentity()) { 271 this->addAttribute("transform", svg_transform(*mc.fMatrix)); 272 } 273 } 274 275 ~AutoElement() { 276 fWriter->endElement(); 277 } 278 279 void addAttribute(const char name[], const char val[]) { 280 fWriter->addAttribute(name, val); 281 } 282 283 void addAttribute(const char name[], const SkString& val) { 284 fWriter->addAttribute(name, val.c_str()); 285 } 286 287 void addAttribute(const char name[], int32_t val) { 288 fWriter->addS32Attribute(name, val); 289 } 290 291 void addAttribute(const char name[], SkScalar val) { 292 fWriter->addScalarAttribute(name, val); 293 } 294 295 void addText(const SkString& text) { 296 fWriter->addText(text.c_str(), text.size()); 297 } 298 299 void addRectAttributes(const SkRect&); 300 void addPathAttributes(const SkPath&, SkParsePath::PathEncoding); 301 void addTextAttributes(const SkFont&); 302 303private: 304 Resources addResources(const MxCp&, const SkPaint& paint); 305 void addShaderResources(const SkPaint& paint, Resources* resources); 306 void addGradientShaderResources(const SkShader* shader, const SkPaint& paint, 307 Resources* resources); 308 void addColorFilterResources(const SkColorFilter& cf, Resources* resources); 309 void addImageShaderResources(const SkShader* shader, const SkPaint& paint, 310 Resources* resources); 311 312 void addPatternDef(const SkBitmap& bm); 313 314 void addPaint(const SkPaint& paint, const Resources& resources); 315 316 317 SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader); 318 319 SkXMLWriter* fWriter; 320 ResourceBucket* fResourceBucket; 321}; 322 323void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) { 324 // Path effects are applied to all vector graphics (rects, rrects, ovals, 325 // paths etc). This should only happen when a path effect is attached to 326 // non-vector graphics (text, image) or a new vector graphics primitive is 327 //added that is not handled by base drawPath() routine. 328 if (paint.getPathEffect() != nullptr) { 329 SkDebugf("Unsupported path effect in addPaint."); 330 } 331 SkPaint::Style style = paint.getStyle(); 332 if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) { 333 static constexpr char kDefaultFill[] = "black"; 334 if (!resources.fPaintServer.equals(kDefaultFill)) { 335 this->addAttribute("fill", resources.fPaintServer); 336 337 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) { 338 this->addAttribute("fill-opacity", svg_opacity(paint.getColor())); 339 } 340 } 341 } else { 342 SkASSERT(style == SkPaint::kStroke_Style); 343 this->addAttribute("fill", "none"); 344 } 345 346 if (!resources.fColorFilter.isEmpty()) { 347 this->addAttribute("filter", resources.fColorFilter.c_str()); 348 } 349 350 if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) { 351 this->addAttribute("stroke", resources.fPaintServer); 352 353 SkScalar strokeWidth = paint.getStrokeWidth(); 354 if (strokeWidth == 0) { 355 // Hairline stroke 356 strokeWidth = 1; 357 this->addAttribute("vector-effect", "non-scaling-stroke"); 358 } 359 this->addAttribute("stroke-width", strokeWidth); 360 361 if (const char* cap = svg_cap(paint.getStrokeCap())) { 362 this->addAttribute("stroke-linecap", cap); 363 } 364 365 if (const char* join = svg_join(paint.getStrokeJoin())) { 366 this->addAttribute("stroke-linejoin", join); 367 } 368 369 if (paint.getStrokeJoin() == SkPaint::kMiter_Join) { 370 this->addAttribute("stroke-miterlimit", paint.getStrokeMiter()); 371 } 372 373 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) { 374 this->addAttribute("stroke-opacity", svg_opacity(paint.getColor())); 375 } 376 } else { 377 SkASSERT(style == SkPaint::kFill_Style); 378 // SVG default stroke value is "none". 379 } 380} 381 382Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) { 383 Resources resources(paint); 384 385 if (paint.getShader()) { 386 AutoElement defs("defs", fWriter); 387 388 this->addShaderResources(paint, &resources); 389 } 390 391 if (const SkColorFilter* cf = paint.getColorFilter()) { 392 // TODO: Implement skia color filters for blend modes other than SrcIn 393 SkBlendMode mode; 394 if (cf->asAColorMode(nullptr, &mode) && mode == SkBlendMode::kSrcIn) { 395 this->addColorFilterResources(*cf, &resources); 396 } 397 } 398 399 return resources; 400} 401 402void SkSVGDevice::AutoElement::addGradientShaderResources(const SkShader* shader, 403 const SkPaint& paint, 404 Resources* resources) { 405 SkShader::GradientInfo grInfo; 406 memset(&grInfo, 0, sizeof(grInfo)); 407 const auto gradient_type = shader->asAGradient(&grInfo); 408 409 if (gradient_type != SkShader::kColor_GradientType && 410 gradient_type != SkShader::kLinear_GradientType) { 411 // TODO: other gradient support 412 return; 413 } 414 415 SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount); 416 SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount); 417 grInfo.fColors = grColors.get(); 418 grInfo.fColorOffsets = grOffsets.get(); 419 420 // One more call to get the actual colors/offsets. 421 shader->asAGradient(&grInfo); 422 SkASSERT(grInfo.fColorCount <= grColors.count()); 423 SkASSERT(grInfo.fColorCount <= grOffsets.count()); 424 425 SkASSERT(grColors.size() > 0); 426 resources->fPaintServer = gradient_type == SkShader::kColor_GradientType 427 ? svg_color(grColors[0]) 428 : SkStringPrintf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str()); 429} 430 431void SkSVGDevice::AutoElement::addColorFilterResources(const SkColorFilter& cf, 432 Resources* resources) { 433 SkString colorfilterID = fResourceBucket->addColorFilter(); 434 { 435 AutoElement filterElement("filter", fWriter); 436 filterElement.addAttribute("id", colorfilterID); 437 filterElement.addAttribute("x", "0%"); 438 filterElement.addAttribute("y", "0%"); 439 filterElement.addAttribute("width", "100%"); 440 filterElement.addAttribute("height", "100%"); 441 442 SkColor filterColor; 443 SkBlendMode mode; 444 bool asAColorMode = cf.asAColorMode(&filterColor, &mode); 445 SkAssertResult(asAColorMode); 446 SkASSERT(mode == SkBlendMode::kSrcIn); 447 448 { 449 // first flood with filter color 450 AutoElement floodElement("feFlood", fWriter); 451 floodElement.addAttribute("flood-color", svg_color(filterColor)); 452 floodElement.addAttribute("flood-opacity", svg_opacity(filterColor)); 453 floodElement.addAttribute("result", "flood"); 454 } 455 456 { 457 // apply the transform to filter color 458 AutoElement compositeElement("feComposite", fWriter); 459 compositeElement.addAttribute("in", "flood"); 460 compositeElement.addAttribute("operator", "in"); 461 } 462 } 463 resources->fColorFilter.printf("url(#%s)", colorfilterID.c_str()); 464} 465 466namespace { 467bool is_png(const void* bytes, size_t length) { 468 constexpr uint8_t kPngSig[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; 469 return length >= sizeof(kPngSig) && !memcmp(bytes, kPngSig, sizeof(kPngSig)); 470} 471} // namespace 472 473// Returns data uri from bytes. 474// it will use any cached data if available, otherwise will 475// encode as png. 476sk_sp<SkData> AsDataUri(SkImage* image) { 477 sk_sp<SkData> imageData = image->encodeToData(); 478 if (!imageData) { 479 return nullptr; 480 } 481 482 const char* selectedPrefix = nullptr; 483 size_t selectedPrefixLength = 0; 484 485#ifdef SK_CODEC_DECODES_JPEG 486 if (SkJpegCodec::IsJpeg(imageData->data(), imageData->size())) { 487 const static char jpgDataPrefix[] = "data:image/jpeg;base64,"; 488 selectedPrefix = jpgDataPrefix; 489 selectedPrefixLength = sizeof(jpgDataPrefix); 490 } 491 else 492#endif 493 { 494 if (!is_png(imageData->data(), imageData->size())) { 495#ifdef SK_ENCODE_PNG 496 imageData = image->encodeToData(SkEncodedImageFormat::kPNG, 100); 497#else 498 return nullptr; 499#endif 500 } 501 const static char pngDataPrefix[] = "data:image/png;base64,"; 502 selectedPrefix = pngDataPrefix; 503 selectedPrefixLength = sizeof(pngDataPrefix); 504 } 505 506 size_t b64Size = SkBase64::Encode(imageData->data(), imageData->size(), nullptr); 507 sk_sp<SkData> dataUri = SkData::MakeUninitialized(selectedPrefixLength + b64Size); 508 char* dest = (char*)dataUri->writable_data(); 509 memcpy(dest, selectedPrefix, selectedPrefixLength); 510 SkBase64::Encode(imageData->data(), imageData->size(), dest + selectedPrefixLength - 1); 511 dest[dataUri->size() - 1] = 0; 512 return dataUri; 513} 514 515void SkSVGDevice::AutoElement::addImageShaderResources(const SkShader* shader, const SkPaint& paint, 516 Resources* resources) { 517 SkMatrix outMatrix; 518 519 SkTileMode xy[2]; 520 SkImage* image = shader->isAImage(&outMatrix, xy); 521 SkASSERT(image); 522 523 SkString patternDims[2]; // width, height 524 525 sk_sp<SkData> dataUri = AsDataUri(image); 526 if (!dataUri) { 527 return; 528 } 529 SkIRect imageSize = image->bounds(); 530 for (int i = 0; i < 2; i++) { 531 int imageDimension = i == 0 ? imageSize.width() : imageSize.height(); 532 switch (xy[i]) { 533 case SkTileMode::kRepeat: 534 patternDims[i].appendScalar(imageDimension); 535 break; 536 default: 537 // TODO: other tile modes? 538 patternDims[i] = "100%"; 539 } 540 } 541 542 SkString patternID = fResourceBucket->addPattern(); 543 { 544 AutoElement pattern("pattern", fWriter); 545 pattern.addAttribute("id", patternID); 546 pattern.addAttribute("patternUnits", "userSpaceOnUse"); 547 pattern.addAttribute("patternContentUnits", "userSpaceOnUse"); 548 pattern.addAttribute("width", patternDims[0]); 549 pattern.addAttribute("height", patternDims[1]); 550 pattern.addAttribute("x", 0); 551 pattern.addAttribute("y", 0); 552 553 { 554 SkString imageID = fResourceBucket->addImage(); 555 AutoElement imageTag("image", fWriter); 556 imageTag.addAttribute("id", imageID); 557 imageTag.addAttribute("x", 0); 558 imageTag.addAttribute("y", 0); 559 imageTag.addAttribute("width", image->width()); 560 imageTag.addAttribute("height", image->height()); 561 imageTag.addAttribute("xlink:href", static_cast<const char*>(dataUri->data())); 562 } 563 } 564 resources->fPaintServer.printf("url(#%s)", patternID.c_str()); 565} 566 567void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) { 568 const SkShader* shader = paint.getShader(); 569 SkASSERT(shader); 570 571 if (shader->asAGradient(nullptr) != SkShader::kNone_GradientType) { 572 this->addGradientShaderResources(shader, paint, resources); 573 } else if (shader->isAImage()) { 574 this->addImageShaderResources(shader, paint, resources); 575 } 576 // TODO: other shader types? 577} 578 579SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info, 580 const SkShader* shader) { 581 SkASSERT(fResourceBucket); 582 SkString id = fResourceBucket->addLinearGradient(); 583 584 { 585 AutoElement gradient("linearGradient", fWriter); 586 587 gradient.addAttribute("id", id); 588 gradient.addAttribute("gradientUnits", "userSpaceOnUse"); 589 gradient.addAttribute("x1", info.fPoint[0].x()); 590 gradient.addAttribute("y1", info.fPoint[0].y()); 591 gradient.addAttribute("x2", info.fPoint[1].x()); 592 gradient.addAttribute("y2", info.fPoint[1].y()); 593 594 if (!as_SB(shader)->getLocalMatrix().isIdentity()) { 595 this->addAttribute("gradientTransform", svg_transform(as_SB(shader)->getLocalMatrix())); 596 } 597 598 SkASSERT(info.fColorCount >= 2); 599 for (int i = 0; i < info.fColorCount; ++i) { 600 SkColor color = info.fColors[i]; 601 SkString colorStr(svg_color(color)); 602 603 { 604 AutoElement stop("stop", fWriter); 605 stop.addAttribute("offset", info.fColorOffsets[i]); 606 stop.addAttribute("stop-color", colorStr.c_str()); 607 608 if (SK_AlphaOPAQUE != SkColorGetA(color)) { 609 stop.addAttribute("stop-opacity", svg_opacity(color)); 610 } 611 } 612 } 613 } 614 615 return id; 616} 617 618void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) { 619 // x, y default to 0 620 if (rect.x() != 0) { 621 this->addAttribute("x", rect.x()); 622 } 623 if (rect.y() != 0) { 624 this->addAttribute("y", rect.y()); 625 } 626 627 this->addAttribute("width", rect.width()); 628 this->addAttribute("height", rect.height()); 629} 630 631void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path, 632 SkParsePath::PathEncoding encoding) { 633 SkString pathData; 634 SkParsePath::ToSVGString(path, &pathData, encoding); 635 this->addAttribute("d", pathData); 636} 637 638void SkSVGDevice::AutoElement::addTextAttributes(const SkFont& font) { 639 this->addAttribute("font-size", font.getSize()); 640 641 SkString familyName; 642 SkTHashSet<SkString> familySet; 643 sk_sp<SkTypeface> tface = font.refTypefaceOrDefault(); 644 645 SkASSERT(tface); 646 SkFontStyle style = tface->fontStyle(); 647 if (style.slant() == SkFontStyle::kItalic_Slant) { 648 this->addAttribute("font-style", "italic"); 649 } else if (style.slant() == SkFontStyle::kOblique_Slant) { 650 this->addAttribute("font-style", "oblique"); 651 } 652 int weightIndex = (SkTPin(style.weight(), 100, 900) - 50) / 100; 653 if (weightIndex != 3) { 654 static constexpr const char* weights[] = { 655 "100", "200", "300", "normal", "400", "500", "600", "bold", "800", "900" 656 }; 657 this->addAttribute("font-weight", weights[weightIndex]); 658 } 659 int stretchIndex = style.width() - 1; 660 if (stretchIndex != 4) { 661 static constexpr const char* stretches[] = { 662 "ultra-condensed", "extra-condensed", "condensed", "semi-condensed", 663 "normal", 664 "semi-expanded", "expanded", "extra-expanded", "ultra-expanded" 665 }; 666 this->addAttribute("font-stretch", stretches[stretchIndex]); 667 } 668 669 sk_sp<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator()); 670 SkTypeface::LocalizedString familyString; 671 if (familyNameIter) { 672 while (familyNameIter->next(&familyString)) { 673 if (familySet.contains(familyString.fString)) { 674 continue; 675 } 676 familySet.add(familyString.fString); 677 familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str()); 678 } 679 } 680 if (!familyName.isEmpty()) { 681 this->addAttribute("font-family", familyName); 682 } 683} 684 685sk_sp<SkBaseDevice> SkSVGDevice::Make(const SkISize& size, std::unique_ptr<SkXMLWriter> writer, 686 uint32_t flags) { 687 return writer ? sk_sp<SkBaseDevice>(new SkSVGDevice(size, std::move(writer), flags)) 688 : nullptr; 689} 690 691SkSVGDevice::SkSVGDevice(const SkISize& size, std::unique_ptr<SkXMLWriter> writer, uint32_t flags) 692 : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight), 693 SkSurfaceProps(0, kUnknown_SkPixelGeometry)) 694 , fWriter(std::move(writer)) 695 , fResourceBucket(new ResourceBucket) 696 , fFlags(flags) 697{ 698 SkASSERT(fWriter); 699 700 fWriter->writeHeader(); 701 702 // The root <svg> tag gets closed by the destructor. 703 fRootElement = std::make_unique<AutoElement>("svg", fWriter); 704 705 fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg"); 706 fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); 707 fRootElement->addAttribute("width", size.width()); 708 fRootElement->addAttribute("height", size.height()); 709} 710 711SkSVGDevice::~SkSVGDevice() { 712 // Pop order is important. 713 while (!fClipStack.empty()) { 714 fClipStack.pop_back(); 715 } 716} 717 718SkParsePath::PathEncoding SkSVGDevice::pathEncoding() const { 719 return (fFlags & SkSVGCanvas::kRelativePathEncoding_Flag) 720 ? SkParsePath::PathEncoding::Relative 721 : SkParsePath::PathEncoding::Absolute; 722} 723 724void SkSVGDevice::syncClipStack(const SkClipStack& cs) { 725 SkClipStack::B2TIter iter(cs); 726 727 const SkClipStack::Element* elem; 728 size_t rec_idx = 0; 729 730 // First, find/preserve the common bottom. 731 while ((elem = iter.next()) && (rec_idx < fClipStack.size())) { 732 if (fClipStack[SkToInt(rec_idx)].fGenID != elem->getGenID()) { 733 break; 734 } 735 rec_idx++; 736 } 737 738 // Discard out-of-date stack top. 739 while (fClipStack.size() > rec_idx) { 740 fClipStack.pop_back(); 741 } 742 743 auto define_clip = [this](const SkClipStack::Element* e) { 744 const auto cid = SkStringPrintf("cl_%x", e->getGenID()); 745 746 AutoElement clip_path("clipPath", fWriter); 747 clip_path.addAttribute("id", cid); 748 749 // TODO: handle non-intersect clips. 750 751 switch (e->getDeviceSpaceType()) { 752 case SkClipStack::Element::DeviceSpaceType::kEmpty: { 753 // TODO: can we skip this? 754 AutoElement rect("rect", fWriter); 755 } break; 756 case SkClipStack::Element::DeviceSpaceType::kRect: { 757 AutoElement rect("rect", fWriter); 758 rect.addRectAttributes(e->getDeviceSpaceRect()); 759 } break; 760 case SkClipStack::Element::DeviceSpaceType::kRRect: { 761 // TODO: complex rrect handling? 762 const auto& rr = e->getDeviceSpaceRRect(); 763 const auto radii = rr.getSimpleRadii(); 764 765 AutoElement rrect("rect", fWriter); 766 rrect.addRectAttributes(rr.rect()); 767 rrect.addAttribute("rx", radii.x()); 768 rrect.addAttribute("ry", radii.y()); 769 } break; 770 case SkClipStack::Element::DeviceSpaceType::kPath: { 771 const auto& p = e->getDeviceSpacePath(); 772 AutoElement path("path", fWriter); 773 path.addPathAttributes(p, this->pathEncoding()); 774 if (p.getFillType() == SkPathFillType::kEvenOdd) { 775 path.addAttribute("clip-rule", "evenodd"); 776 } 777 } break; 778 case SkClipStack::Element::DeviceSpaceType::kShader: 779 // TODO: handle shader clipping, perhaps rasterize and apply as a mask image? 780 break; 781 } 782 783 return cid; 784 }; 785 786 // Rebuild the top. 787 while (elem) { 788 const auto cid = define_clip(elem); 789 790 auto clip_grp = std::make_unique<AutoElement>("g", fWriter); 791 clip_grp->addAttribute("clip-path", SkStringPrintf("url(#%s)", cid.c_str())); 792 793 fClipStack.push_back({ std::move(clip_grp), elem->getGenID() }); 794 795 elem = iter.next(); 796 } 797} 798 799void SkSVGDevice::drawPaint(const SkPaint& paint) { 800 AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint); 801 rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()), 802 SkIntToScalar(this->height()))); 803} 804 805void SkSVGDevice::drawAnnotation(const SkRect& rect, const char key[], SkData* value) { 806 if (!value) { 807 return; 808 } 809 810 if (!strcmp(SkAnnotationKeys::URL_Key(), key) || 811 !strcmp(SkAnnotationKeys::Link_Named_Dest_Key(), key)) { 812 this->cs().save(); 813 this->cs().clipRect(rect, this->localToDevice(), SkClipOp::kIntersect, true); 814 SkRect transformedRect = this->cs().bounds(this->getGlobalBounds()); 815 this->cs().restore(); 816 if (transformedRect.isEmpty()) { 817 return; 818 } 819 820 SkString url(static_cast<const char*>(value->data()), value->size() - 1); 821 AutoElement a("a", fWriter); 822 a.addAttribute("xlink:href", url.c_str()); 823 { 824 AutoElement r("rect", fWriter); 825 r.addAttribute("fill-opacity", "0.0"); 826 r.addRectAttributes(transformedRect); 827 } 828 } 829} 830 831void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count, 832 const SkPoint pts[], const SkPaint& paint) { 833 SkPathBuilder path; 834 835 switch (mode) { 836 // todo 837 case SkCanvas::kPoints_PointMode: 838 // TODO? 839 break; 840 case SkCanvas::kLines_PointMode: 841 count -= 1; 842 for (size_t i = 0; i < count; i += 2) { 843 path.moveTo(pts[i]); 844 path.lineTo(pts[i+1]); 845 } 846 break; 847 case SkCanvas::kPolygon_PointMode: 848 if (count > 1) { 849 path.addPolygon(pts, SkToInt(count), false); 850 } 851 break; 852 } 853 854 this->drawPath(path.detach(), paint, true); 855} 856 857void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) { 858 std::unique_ptr<AutoElement> svg; 859 if (RequiresViewportReset(paint)) { 860 svg = std::make_unique<AutoElement>("svg", this, fResourceBucket.get(), MxCp(this), paint); 861 svg->addRectAttributes(r); 862 } 863 864 AutoElement rect("rect", this, fResourceBucket.get(), MxCp(this), paint); 865 866 if (svg) { 867 rect.addAttribute("x", 0); 868 rect.addAttribute("y", 0); 869 rect.addAttribute("width", "100%"); 870 rect.addAttribute("height", "100%"); 871 } else { 872 rect.addRectAttributes(r); 873 } 874} 875 876void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) { 877 AutoElement ellipse("ellipse", this, fResourceBucket.get(), MxCp(this), paint); 878 ellipse.addAttribute("cx", oval.centerX()); 879 ellipse.addAttribute("cy", oval.centerY()); 880 ellipse.addAttribute("rx", oval.width() / 2); 881 ellipse.addAttribute("ry", oval.height() / 2); 882} 883 884void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) { 885 AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), paint); 886 elem.addPathAttributes(SkPath::RRect(rr), this->pathEncoding()); 887} 888 889void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint, bool pathIsMutable) { 890 if (path.isInverseFillType()) { 891 SkDebugf("Inverse path fill type not yet implemented."); 892 return; 893 } 894 895 SkPath pathStorage; 896 SkPath* pathPtr = const_cast<SkPath*>(&path); 897 SkTCopyOnFirstWrite<SkPaint> path_paint(paint); 898 899 // Apply path effect from paint to path. 900 if (path_paint->getPathEffect()) { 901 if (!pathIsMutable) { 902 pathPtr = &pathStorage; 903 } 904 bool fill = path_paint->getFillPath(path, pathPtr); 905 if (fill) { 906 // Path should be filled. 907 path_paint.writable()->setStyle(SkPaint::kFill_Style); 908 } else { 909 // Path should be drawn with a hairline (width == 0). 910 path_paint.writable()->setStyle(SkPaint::kStroke_Style); 911 path_paint.writable()->setStrokeWidth(0); 912 } 913 914 path_paint.writable()->setPathEffect(nullptr); // path effect processed 915 } 916 917 // Create path element. 918 AutoElement elem("path", this, fResourceBucket.get(), MxCp(this), *path_paint); 919 elem.addPathAttributes(*pathPtr, this->pathEncoding()); 920 921 // TODO: inverse fill types? 922 if (pathPtr->getFillType() == SkPathFillType::kEvenOdd) { 923 elem.addAttribute("fill-rule", "evenodd"); 924 } 925} 926 927static sk_sp<SkData> encode(const SkBitmap& src) { 928 SkDynamicMemoryWStream buf; 929 return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr; 930} 931 932void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) { 933 sk_sp<SkData> pngData = encode(bm); 934 if (!pngData) { 935 return; 936 } 937 938 size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr); 939 SkAutoTMalloc<char> b64Data(b64Size); 940 SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get()); 941 942 SkString svgImageData("data:image/png;base64,"); 943 svgImageData.append(b64Data.get(), b64Size); 944 945 SkString imageID = fResourceBucket->addImage(); 946 { 947 AutoElement defs("defs", fWriter); 948 { 949 AutoElement image("image", fWriter); 950 image.addAttribute("id", imageID); 951 image.addAttribute("width", bm.width()); 952 image.addAttribute("height", bm.height()); 953 image.addAttribute("xlink:href", svgImageData); 954 } 955 } 956 957 { 958 AutoElement imageUse("use", this, fResourceBucket.get(), mc, paint); 959 imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str())); 960 } 961} 962 963void SkSVGDevice::drawImageRect(const SkImage* image, const SkRect* src, const SkRect& dst, 964 const SkSamplingOptions& sampling, const SkPaint& paint, 965 SkCanvas::SrcRectConstraint constraint) { 966 SkBitmap bm; 967 // TODO: support gpu images 968 if (!as_IB(image)->getROPixels(nullptr, &bm)) { 969 return; 970 } 971 972 SkClipStack* cs = &this->cs(); 973 SkClipStack::AutoRestore ar(cs, false); 974 if (src && *src != SkRect::Make(bm.bounds())) { 975 cs->save(); 976 cs->clipRect(dst, this->localToDevice(), SkClipOp::kIntersect, paint.isAntiAlias()); 977 } 978 979 SkMatrix adjustedMatrix = this->localToDevice() 980 * SkMatrix::RectToRect(src ? *src : SkRect::Make(bm.bounds()), dst); 981 982 drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint); 983} 984 985class SVGTextBuilder : SkNoncopyable { 986public: 987 SVGTextBuilder(SkPoint origin, const SkGlyphRun& glyphRun) 988 : fOrigin(origin) { 989 auto runSize = glyphRun.runSize(); 990 SkAutoSTArray<64, SkUnichar> unichars(runSize); 991 SkFontPriv::GlyphsToUnichars(glyphRun.font(), glyphRun.glyphsIDs().data(), 992 runSize, unichars.get()); 993 auto positions = glyphRun.positions(); 994 for (size_t i = 0; i < runSize; ++i) { 995 this->appendUnichar(unichars[i], positions[i]); 996 } 997 } 998 999 const SkString& text() const { return fText; } 1000 const SkString& posX() const { return fPosXStr; } 1001 const SkString& posY() const { return fHasConstY ? fConstYStr : fPosYStr; } 1002 1003private: 1004 void appendUnichar(SkUnichar c, SkPoint position) { 1005 bool discardPos = false; 1006 bool isWhitespace = false; 1007 1008 switch(c) { 1009 case ' ': 1010 case '\t': 1011 // consolidate whitespace to match SVG's xml:space=default munging 1012 // (http://www.w3.org/TR/SVG/text.html#WhiteSpace) 1013 if (fLastCharWasWhitespace) { 1014 discardPos = true; 1015 } else { 1016 fText.appendUnichar(c); 1017 } 1018 isWhitespace = true; 1019 break; 1020 case '\0': 1021 // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these 1022 // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets) 1023 discardPos = true; 1024 isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation 1025 break; 1026 case '&': 1027 fText.append("&"); 1028 break; 1029 case '"': 1030 fText.append("""); 1031 break; 1032 case '\'': 1033 fText.append("'"); 1034 break; 1035 case '<': 1036 fText.append("<"); 1037 break; 1038 case '>': 1039 fText.append(">"); 1040 break; 1041 default: 1042 fText.appendUnichar(c); 1043 break; 1044 } 1045 1046 fLastCharWasWhitespace = isWhitespace; 1047 1048 if (discardPos) { 1049 return; 1050 } 1051 1052 position += fOrigin; 1053 fPosXStr.appendf("%.8g, ", position.fX); 1054 fPosYStr.appendf("%.8g, ", position.fY); 1055 1056 if (fConstYStr.isEmpty()) { 1057 fConstYStr = fPosYStr; 1058 fConstY = position.fY; 1059 } else { 1060 fHasConstY &= SkScalarNearlyEqual(fConstY, position.fY); 1061 } 1062 } 1063 1064 const SkPoint fOrigin; 1065 1066 SkString fText, 1067 fPosXStr, fPosYStr, 1068 fConstYStr; 1069 SkScalar fConstY; 1070 bool fLastCharWasWhitespace = true, // start off in whitespace mode to strip leading space 1071 fHasConstY = true; 1072}; 1073 1074void SkSVGDevice::onDrawGlyphRunList(const SkGlyphRunList& glyphRunList, const SkPaint& paint) { 1075 SkASSERT(!glyphRunList.hasRSXForm()); 1076 const auto draw_as_path = (fFlags & SkSVGCanvas::kConvertTextToPaths_Flag) || 1077 paint.getPathEffect(); 1078 1079 if (draw_as_path) { 1080 // Emit a single <path> element. 1081 SkPath path; 1082 for (auto& glyphRun : glyphRunList) { 1083 AddPath(glyphRun, glyphRunList.origin(), &path); 1084 } 1085 1086 this->drawPath(path, paint); 1087 1088 return; 1089 } 1090 1091 // Emit one <text> element for each run. 1092 for (auto& glyphRun : glyphRunList) { 1093 AutoElement elem("text", this, fResourceBucket.get(), MxCp(this), paint); 1094 elem.addTextAttributes(glyphRun.font()); 1095 1096 SVGTextBuilder builder(glyphRunList.origin(), glyphRun); 1097 elem.addAttribute("x", builder.posX()); 1098 elem.addAttribute("y", builder.posY()); 1099 elem.addText(builder.text()); 1100 } 1101} 1102 1103void SkSVGDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) { 1104 // todo 1105} 1106