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 "SkSVGDevice.h" 9 10 #include "SkBase64.h" 11 #include "SkBitmap.h" 12 #include "SkChecksum.h" 13 #include "SkClipStack.h" 14 #include "SkData.h" 15 #include "SkDraw.h" 16 #include "SkImageEncoder.h" 17 #include "SkPaint.h" 18 #include "SkParsePath.h" 19 #include "SkShader.h" 20 #include "SkStream.h" 21 #include "SkTHash.h" 22 #include "SkTypeface.h" 23 #include "SkUtils.h" 24 #include "SkXMLWriter.h" 25 #include "SkClipOpPriv.h" 26 27 namespace { 28 29 static SkString svg_color(SkColor color) { 30 return SkStringPrintf("rgb(%u,%u,%u)", 31 SkColorGetR(color), 32 SkColorGetG(color), 33 SkColorGetB(color)); 34 } 35 36 static SkScalar svg_opacity(SkColor color) { 37 return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE; 38 } 39 40 // Keep in sync with SkPaint::Cap 41 static const char* cap_map[] = { 42 nullptr, // kButt_Cap (default) 43 "round", // kRound_Cap 44 "square" // kSquare_Cap 45 }; 46 static_assert(SK_ARRAY_COUNT(cap_map) == SkPaint::kCapCount, "missing_cap_map_entry"); 47 48 static const char* svg_cap(SkPaint::Cap cap) { 49 SkASSERT(cap < SK_ARRAY_COUNT(cap_map)); 50 return cap_map[cap]; 51 } 52 53 // Keep in sync with SkPaint::Join 54 static const char* join_map[] = { 55 nullptr, // kMiter_Join (default) 56 "round", // kRound_Join 57 "bevel" // kBevel_Join 58 }; 59 static_assert(SK_ARRAY_COUNT(join_map) == SkPaint::kJoinCount, "missing_join_map_entry"); 60 61 static const char* svg_join(SkPaint::Join join) { 62 SkASSERT(join < SK_ARRAY_COUNT(join_map)); 63 return join_map[join]; 64 } 65 66 // Keep in sync with SkPaint::Align 67 static const char* text_align_map[] = { 68 nullptr, // kLeft_Align (default) 69 "middle", // kCenter_Align 70 "end" // kRight_Align 71 }; 72 static_assert(SK_ARRAY_COUNT(text_align_map) == SkPaint::kAlignCount, 73 "missing_text_align_map_entry"); 74 static const char* svg_text_align(SkPaint::Align align) { 75 SkASSERT(align < SK_ARRAY_COUNT(text_align_map)); 76 return text_align_map[align]; 77 } 78 79 static SkString svg_transform(const SkMatrix& t) { 80 SkASSERT(!t.isIdentity()); 81 82 SkString tstr; 83 switch (t.getType()) { 84 case SkMatrix::kPerspective_Mask: 85 SkDebugf("Can't handle perspective matrices."); 86 break; 87 case SkMatrix::kTranslate_Mask: 88 tstr.printf("translate(%g %g)", t.getTranslateX(), t.getTranslateY()); 89 break; 90 case SkMatrix::kScale_Mask: 91 tstr.printf("scale(%g %g)", t.getScaleX(), t.getScaleY()); 92 break; 93 default: 94 // http://www.w3.org/TR/SVG/coords.html#TransformMatrixDefined 95 // | a c e | 96 // | b d f | 97 // | 0 0 1 | 98 tstr.printf("matrix(%g %g %g %g %g %g)", 99 t.getScaleX(), t.getSkewY(), 100 t.getSkewX(), t.getScaleY(), 101 t.getTranslateX(), t.getTranslateY()); 102 break; 103 } 104 105 return tstr; 106 } 107 108 struct Resources { 109 Resources(const SkPaint& paint) 110 : fPaintServer(svg_color(paint.getColor())) {} 111 112 SkString fPaintServer; 113 SkString fClip; 114 }; 115 116 class SVGTextBuilder : SkNoncopyable { 117 public: 118 SVGTextBuilder(const void* text, size_t byteLen, const SkPaint& paint, const SkPoint& offset, 119 unsigned scalarsPerPos, const SkScalar pos[] = nullptr) 120 : fOffset(offset) 121 , fScalarsPerPos(scalarsPerPos) 122 , fPos(pos) 123 , fLastCharWasWhitespace(true) // start off in whitespace mode to strip all leading space 124 { 125 SkASSERT(scalarsPerPos <= 2); 126 SkASSERT(scalarsPerPos == 0 || SkToBool(pos)); 127 128 int count = paint.countText(text, byteLen); 129 130 switch(paint.getTextEncoding()) { 131 case SkPaint::kGlyphID_TextEncoding: { 132 SkASSERT(count * sizeof(uint16_t) == byteLen); 133 SkAutoSTArray<64, SkUnichar> unichars(count); 134 paint.glyphsToUnichars((const uint16_t*)text, count, unichars.get()); 135 for (int i = 0; i < count; ++i) { 136 this->appendUnichar(unichars[i]); 137 } 138 } break; 139 case SkPaint::kUTF8_TextEncoding: { 140 const char* c8 = reinterpret_cast<const char*>(text); 141 for (int i = 0; i < count; ++i) { 142 this->appendUnichar(SkUTF8_NextUnichar(&c8)); 143 } 144 SkASSERT(reinterpret_cast<const char*>(text) + byteLen == c8); 145 } break; 146 case SkPaint::kUTF16_TextEncoding: { 147 const uint16_t* c16 = reinterpret_cast<const uint16_t*>(text); 148 for (int i = 0; i < count; ++i) { 149 this->appendUnichar(SkUTF16_NextUnichar(&c16)); 150 } 151 SkASSERT(SkIsAlign2(byteLen)); 152 SkASSERT(reinterpret_cast<const uint16_t*>(text) + (byteLen / 2) == c16); 153 } break; 154 case SkPaint::kUTF32_TextEncoding: { 155 SkASSERT(count * sizeof(uint32_t) == byteLen); 156 const uint32_t* c32 = reinterpret_cast<const uint32_t*>(text); 157 for (int i = 0; i < count; ++i) { 158 this->appendUnichar(c32[i]); 159 } 160 } break; 161 default: 162 SkFAIL("unknown text encoding"); 163 } 164 165 if (scalarsPerPos < 2) { 166 SkASSERT(fPosY.isEmpty()); 167 fPosY.appendScalar(offset.y()); // DrawText or DrawPosTextH (fixed Y). 168 } 169 170 if (scalarsPerPos < 1) { 171 SkASSERT(fPosX.isEmpty()); 172 fPosX.appendScalar(offset.x()); // DrawText (X also fixed). 173 } 174 } 175 176 const SkString& text() const { return fText; } 177 const SkString& posX() const { return fPosX; } 178 const SkString& posY() const { return fPosY; } 179 180 private: 181 void appendUnichar(SkUnichar c) { 182 bool discardPos = false; 183 bool isWhitespace = false; 184 185 switch(c) { 186 case ' ': 187 case '\t': 188 // consolidate whitespace to match SVG's xml:space=default munging 189 // (http://www.w3.org/TR/SVG/text.html#WhiteSpace) 190 if (fLastCharWasWhitespace) { 191 discardPos = true; 192 } else { 193 fText.appendUnichar(c); 194 } 195 isWhitespace = true; 196 break; 197 case '\0': 198 // SkPaint::glyphsToUnichars() returns \0 for inconvertible glyphs, but these 199 // are not legal XML characters (http://www.w3.org/TR/REC-xml/#charsets) 200 discardPos = true; 201 isWhitespace = fLastCharWasWhitespace; // preserve whitespace consolidation 202 break; 203 case '&': 204 fText.append("&"); 205 break; 206 case '"': 207 fText.append("""); 208 break; 209 case '\'': 210 fText.append("'"); 211 break; 212 case '<': 213 fText.append("<"); 214 break; 215 case '>': 216 fText.append(">"); 217 break; 218 default: 219 fText.appendUnichar(c); 220 break; 221 } 222 223 this->advancePos(discardPos); 224 fLastCharWasWhitespace = isWhitespace; 225 } 226 227 void advancePos(bool discard) { 228 if (!discard && fScalarsPerPos > 0) { 229 fPosX.appendf("%.8g, ", fOffset.x() + fPos[0]); 230 if (fScalarsPerPos > 1) { 231 SkASSERT(fScalarsPerPos == 2); 232 fPosY.appendf("%.8g, ", fOffset.y() + fPos[1]); 233 } 234 } 235 fPos += fScalarsPerPos; 236 } 237 238 const SkPoint& fOffset; 239 const unsigned fScalarsPerPos; 240 const SkScalar* fPos; 241 242 SkString fText, fPosX, fPosY; 243 bool fLastCharWasWhitespace; 244 }; 245 246 } 247 248 // For now all this does is serve unique serial IDs, but it will eventually evolve to track 249 // and deduplicate resources. 250 class SkSVGDevice::ResourceBucket : ::SkNoncopyable { 251 public: 252 ResourceBucket() : fGradientCount(0), fClipCount(0), fPathCount(0), fImageCount(0) {} 253 254 SkString addLinearGradient() { 255 return SkStringPrintf("gradient_%d", fGradientCount++); 256 } 257 258 SkString addClip() { 259 return SkStringPrintf("clip_%d", fClipCount++); 260 } 261 262 SkString addPath() { 263 return SkStringPrintf("path_%d", fPathCount++); 264 } 265 266 SkString addImage() { 267 return SkStringPrintf("img_%d", fImageCount++); 268 } 269 270 private: 271 uint32_t fGradientCount; 272 uint32_t fClipCount; 273 uint32_t fPathCount; 274 uint32_t fImageCount; 275 }; 276 277 struct SkSVGDevice::MxCp { 278 const SkMatrix* fMatrix; 279 const SkClipStack* fClipStack; 280 281 MxCp(const SkMatrix* mx, const SkClipStack* cs) : fMatrix(mx), fClipStack(cs) {} 282 MxCp(SkSVGDevice* device) : fMatrix(&device->ctm()), fClipStack(&device->cs()) {} 283 }; 284 285 class SkSVGDevice::AutoElement : ::SkNoncopyable { 286 public: 287 AutoElement(const char name[], SkXMLWriter* writer) 288 : fWriter(writer) 289 , fResourceBucket(nullptr) { 290 fWriter->startElement(name); 291 } 292 293 AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket, 294 const MxCp& mc, const SkPaint& paint) 295 : fWriter(writer) 296 , fResourceBucket(bucket) { 297 298 Resources res = this->addResources(mc, paint); 299 if (!res.fClip.isEmpty()) { 300 // The clip is in device space. Apply it via a <g> wrapper to avoid local transform 301 // interference. 302 fClipGroup.reset(new AutoElement("g", fWriter)); 303 fClipGroup->addAttribute("clip-path",res.fClip); 304 } 305 306 fWriter->startElement(name); 307 308 this->addPaint(paint, res); 309 310 if (!mc.fMatrix->isIdentity()) { 311 this->addAttribute("transform", svg_transform(*mc.fMatrix)); 312 } 313 } 314 315 ~AutoElement() { 316 fWriter->endElement(); 317 } 318 319 void addAttribute(const char name[], const char val[]) { 320 fWriter->addAttribute(name, val); 321 } 322 323 void addAttribute(const char name[], const SkString& val) { 324 fWriter->addAttribute(name, val.c_str()); 325 } 326 327 void addAttribute(const char name[], int32_t val) { 328 fWriter->addS32Attribute(name, val); 329 } 330 331 void addAttribute(const char name[], SkScalar val) { 332 fWriter->addScalarAttribute(name, val); 333 } 334 335 void addText(const SkString& text) { 336 fWriter->addText(text.c_str(), text.size()); 337 } 338 339 void addRectAttributes(const SkRect&); 340 void addPathAttributes(const SkPath&); 341 void addTextAttributes(const SkPaint&); 342 343 private: 344 Resources addResources(const MxCp&, const SkPaint& paint); 345 void addClipResources(const MxCp&, Resources* resources); 346 void addShaderResources(const SkPaint& paint, Resources* resources); 347 348 void addPaint(const SkPaint& paint, const Resources& resources); 349 350 SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader); 351 352 SkXMLWriter* fWriter; 353 ResourceBucket* fResourceBucket; 354 std::unique_ptr<AutoElement> fClipGroup; 355 }; 356 357 void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) { 358 SkPaint::Style style = paint.getStyle(); 359 if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) { 360 this->addAttribute("fill", resources.fPaintServer); 361 362 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) { 363 this->addAttribute("fill-opacity", svg_opacity(paint.getColor())); 364 } 365 } else { 366 SkASSERT(style == SkPaint::kStroke_Style); 367 this->addAttribute("fill", "none"); 368 } 369 370 if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) { 371 this->addAttribute("stroke", resources.fPaintServer); 372 373 SkScalar strokeWidth = paint.getStrokeWidth(); 374 if (strokeWidth == 0) { 375 // Hairline stroke 376 strokeWidth = 1; 377 this->addAttribute("vector-effect", "non-scaling-stroke"); 378 } 379 this->addAttribute("stroke-width", strokeWidth); 380 381 if (const char* cap = svg_cap(paint.getStrokeCap())) { 382 this->addAttribute("stroke-linecap", cap); 383 } 384 385 if (const char* join = svg_join(paint.getStrokeJoin())) { 386 this->addAttribute("stroke-linejoin", join); 387 } 388 389 if (paint.getStrokeJoin() == SkPaint::kMiter_Join) { 390 this->addAttribute("stroke-miterlimit", paint.getStrokeMiter()); 391 } 392 393 if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) { 394 this->addAttribute("stroke-opacity", svg_opacity(paint.getColor())); 395 } 396 } else { 397 SkASSERT(style == SkPaint::kFill_Style); 398 this->addAttribute("stroke", "none"); 399 } 400 } 401 402 Resources SkSVGDevice::AutoElement::addResources(const MxCp& mc, const SkPaint& paint) { 403 Resources resources(paint); 404 405 // FIXME: this is a weak heuristic and we end up with LOTS of redundant clips. 406 bool hasClip = !mc.fClipStack->isWideOpen(); 407 bool hasShader = SkToBool(paint.getShader()); 408 409 if (hasClip || hasShader) { 410 AutoElement defs("defs", fWriter); 411 412 if (hasClip) { 413 this->addClipResources(mc, &resources); 414 } 415 416 if (hasShader) { 417 this->addShaderResources(paint, &resources); 418 } 419 } 420 421 return resources; 422 } 423 424 void SkSVGDevice::AutoElement::addShaderResources(const SkPaint& paint, Resources* resources) { 425 const SkShader* shader = paint.getShader(); 426 SkASSERT(SkToBool(shader)); 427 428 SkShader::GradientInfo grInfo; 429 grInfo.fColorCount = 0; 430 if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) { 431 // TODO: non-linear gradient support 432 SkDebugf("unsupported shader type\n"); 433 return; 434 } 435 436 SkAutoSTArray<16, SkColor> grColors(grInfo.fColorCount); 437 SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount); 438 grInfo.fColors = grColors.get(); 439 grInfo.fColorOffsets = grOffsets.get(); 440 441 // One more call to get the actual colors/offsets. 442 shader->asAGradient(&grInfo); 443 SkASSERT(grInfo.fColorCount <= grColors.count()); 444 SkASSERT(grInfo.fColorCount <= grOffsets.count()); 445 446 resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str()); 447 } 448 449 void SkSVGDevice::AutoElement::addClipResources(const MxCp& mc, Resources* resources) { 450 SkASSERT(!mc.fClipStack->isWideOpen()); 451 452 SkPath clipPath; 453 (void) mc.fClipStack->asPath(&clipPath); 454 455 SkString clipID = fResourceBucket->addClip(); 456 const char* clipRule = clipPath.getFillType() == SkPath::kEvenOdd_FillType ? 457 "evenodd" : "nonzero"; 458 { 459 // clipPath is in device space, but since we're only pushing transform attributes 460 // to the leaf nodes, so are all our elements => SVG userSpaceOnUse == device space. 461 AutoElement clipPathElement("clipPath", fWriter); 462 clipPathElement.addAttribute("id", clipID); 463 464 SkRect clipRect = SkRect::MakeEmpty(); 465 if (clipPath.isEmpty() || clipPath.isRect(&clipRect)) { 466 AutoElement rectElement("rect", fWriter); 467 rectElement.addRectAttributes(clipRect); 468 rectElement.addAttribute("clip-rule", clipRule); 469 } else { 470 AutoElement pathElement("path", fWriter); 471 pathElement.addPathAttributes(clipPath); 472 pathElement.addAttribute("clip-rule", clipRule); 473 } 474 } 475 476 resources->fClip.printf("url(#%s)", clipID.c_str()); 477 } 478 479 SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info, 480 const SkShader* shader) { 481 SkASSERT(fResourceBucket); 482 SkString id = fResourceBucket->addLinearGradient(); 483 484 { 485 AutoElement gradient("linearGradient", fWriter); 486 487 gradient.addAttribute("id", id); 488 gradient.addAttribute("gradientUnits", "userSpaceOnUse"); 489 gradient.addAttribute("x1", info.fPoint[0].x()); 490 gradient.addAttribute("y1", info.fPoint[0].y()); 491 gradient.addAttribute("x2", info.fPoint[1].x()); 492 gradient.addAttribute("y2", info.fPoint[1].y()); 493 494 if (!shader->getLocalMatrix().isIdentity()) { 495 this->addAttribute("gradientTransform", svg_transform(shader->getLocalMatrix())); 496 } 497 498 SkASSERT(info.fColorCount >= 2); 499 for (int i = 0; i < info.fColorCount; ++i) { 500 SkColor color = info.fColors[i]; 501 SkString colorStr(svg_color(color)); 502 503 { 504 AutoElement stop("stop", fWriter); 505 stop.addAttribute("offset", info.fColorOffsets[i]); 506 stop.addAttribute("stop-color", colorStr.c_str()); 507 508 if (SK_AlphaOPAQUE != SkColorGetA(color)) { 509 stop.addAttribute("stop-opacity", svg_opacity(color)); 510 } 511 } 512 } 513 } 514 515 return id; 516 } 517 518 void SkSVGDevice::AutoElement::addRectAttributes(const SkRect& rect) { 519 // x, y default to 0 520 if (rect.x() != 0) { 521 this->addAttribute("x", rect.x()); 522 } 523 if (rect.y() != 0) { 524 this->addAttribute("y", rect.y()); 525 } 526 527 this->addAttribute("width", rect.width()); 528 this->addAttribute("height", rect.height()); 529 } 530 531 void SkSVGDevice::AutoElement::addPathAttributes(const SkPath& path) { 532 SkString pathData; 533 SkParsePath::ToSVGString(path, &pathData); 534 this->addAttribute("d", pathData); 535 } 536 537 void SkSVGDevice::AutoElement::addTextAttributes(const SkPaint& paint) { 538 this->addAttribute("font-size", paint.getTextSize()); 539 540 if (const char* textAlign = svg_text_align(paint.getTextAlign())) { 541 this->addAttribute("text-anchor", textAlign); 542 } 543 544 SkString familyName; 545 SkTHashSet<SkString> familySet; 546 sk_sp<SkTypeface> tface(paint.getTypeface() ? paint.refTypeface() : SkTypeface::MakeDefault()); 547 548 SkASSERT(tface); 549 SkTypeface::Style style = tface->style(); 550 if (style & SkTypeface::kItalic) { 551 this->addAttribute("font-style", "italic"); 552 } 553 if (style & SkTypeface::kBold) { 554 this->addAttribute("font-weight", "bold"); 555 } 556 557 sk_sp<SkTypeface::LocalizedStrings> familyNameIter(tface->createFamilyNameIterator()); 558 SkTypeface::LocalizedString familyString; 559 if (familyNameIter) { 560 while (familyNameIter->next(&familyString)) { 561 if (familySet.contains(familyString.fString)) { 562 continue; 563 } 564 familySet.add(familyString.fString); 565 familyName.appendf((familyName.isEmpty() ? "%s" : ", %s"), familyString.fString.c_str()); 566 } 567 } 568 if (!familyName.isEmpty()) { 569 this->addAttribute("font-family", familyName); 570 } 571 } 572 573 SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkXMLWriter* writer) { 574 if (!writer) { 575 return nullptr; 576 } 577 578 return new SkSVGDevice(size, writer); 579 } 580 581 SkSVGDevice::SkSVGDevice(const SkISize& size, SkXMLWriter* writer) 582 : INHERITED(SkImageInfo::MakeUnknown(size.fWidth, size.fHeight), 583 SkSurfaceProps(0, kUnknown_SkPixelGeometry)) 584 , fWriter(writer) 585 , fResourceBucket(new ResourceBucket) 586 { 587 SkASSERT(writer); 588 589 fWriter->writeHeader(); 590 591 // The root <svg> tag gets closed by the destructor. 592 fRootElement.reset(new AutoElement("svg", fWriter)); 593 594 fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg"); 595 fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); 596 fRootElement->addAttribute("width", size.width()); 597 fRootElement->addAttribute("height", size.height()); 598 } 599 600 SkSVGDevice::~SkSVGDevice() { 601 } 602 603 void SkSVGDevice::drawPaint(const SkPaint& paint) { 604 AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint); 605 rect.addRectAttributes(SkRect::MakeWH(SkIntToScalar(this->width()), 606 SkIntToScalar(this->height()))); 607 } 608 609 void SkSVGDevice::drawPoints(SkCanvas::PointMode mode, size_t count, 610 const SkPoint pts[], const SkPaint& paint) { 611 SkPath path; 612 613 switch (mode) { 614 // todo 615 case SkCanvas::kPoints_PointMode: 616 SkDebugf("unsupported operation: drawPoints(kPoints_PointMode)\n"); 617 break; 618 case SkCanvas::kLines_PointMode: 619 count -= 1; 620 for (size_t i = 0; i < count; i += 2) { 621 path.rewind(); 622 path.moveTo(pts[i]); 623 path.lineTo(pts[i+1]); 624 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint); 625 elem.addPathAttributes(path); 626 } 627 break; 628 case SkCanvas::kPolygon_PointMode: 629 if (count > 1) { 630 path.addPoly(pts, SkToInt(count), false); 631 path.moveTo(pts[0]); 632 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint); 633 elem.addPathAttributes(path); 634 } 635 break; 636 } 637 } 638 639 void SkSVGDevice::drawRect(const SkRect& r, const SkPaint& paint) { 640 AutoElement rect("rect", fWriter, fResourceBucket.get(), MxCp(this), paint); 641 rect.addRectAttributes(r); 642 } 643 644 void SkSVGDevice::drawOval(const SkRect& oval, const SkPaint& paint) { 645 AutoElement ellipse("ellipse", fWriter, fResourceBucket.get(), MxCp(this), paint); 646 ellipse.addAttribute("cx", oval.centerX()); 647 ellipse.addAttribute("cy", oval.centerY()); 648 ellipse.addAttribute("rx", oval.width() / 2); 649 ellipse.addAttribute("ry", oval.height() / 2); 650 } 651 652 void SkSVGDevice::drawRRect(const SkRRect& rr, const SkPaint& paint) { 653 SkPath path; 654 path.addRRect(rr); 655 656 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint); 657 elem.addPathAttributes(path); 658 } 659 660 void SkSVGDevice::drawPath(const SkPath& path, const SkPaint& paint, 661 const SkMatrix* prePathMatrix, bool pathIsMutable) { 662 AutoElement elem("path", fWriter, fResourceBucket.get(), MxCp(this), paint); 663 elem.addPathAttributes(path); 664 665 // TODO: inverse fill types? 666 if (path.getFillType() == SkPath::kEvenOdd_FillType) { 667 elem.addAttribute("fill-rule", "evenodd"); 668 } 669 } 670 671 static sk_sp<SkData> encode(const SkBitmap& src) { 672 SkDynamicMemoryWStream buf; 673 return SkEncodeImage(&buf, src, SkEncodedImageFormat::kPNG, 80) ? buf.detachAsData() : nullptr; 674 } 675 676 void SkSVGDevice::drawBitmapCommon(const MxCp& mc, const SkBitmap& bm, const SkPaint& paint) { 677 sk_sp<SkData> pngData = encode(bm); 678 if (!pngData) { 679 return; 680 } 681 682 size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), nullptr); 683 SkAutoTMalloc<char> b64Data(b64Size); 684 SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get()); 685 686 SkString svgImageData("data:image/png;base64,"); 687 svgImageData.append(b64Data.get(), b64Size); 688 689 SkString imageID = fResourceBucket->addImage(); 690 { 691 AutoElement defs("defs", fWriter); 692 { 693 AutoElement image("image", fWriter); 694 image.addAttribute("id", imageID); 695 image.addAttribute("width", bm.width()); 696 image.addAttribute("height", bm.height()); 697 image.addAttribute("xlink:href", svgImageData); 698 } 699 } 700 701 { 702 AutoElement imageUse("use", fWriter, fResourceBucket.get(), mc, paint); 703 imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str())); 704 } 705 } 706 707 void SkSVGDevice::drawBitmap(const SkBitmap& bitmap, 708 const SkMatrix& matrix, const SkPaint& paint) { 709 MxCp mc(this); 710 SkMatrix adjustedMatrix = *mc.fMatrix; 711 adjustedMatrix.preConcat(matrix); 712 mc.fMatrix = &adjustedMatrix; 713 714 drawBitmapCommon(mc, bitmap, paint); 715 } 716 717 void SkSVGDevice::drawSprite(const SkBitmap& bitmap, 718 int x, int y, const SkPaint& paint) { 719 MxCp mc(this); 720 SkMatrix adjustedMatrix = *mc.fMatrix; 721 adjustedMatrix.preTranslate(SkIntToScalar(x), SkIntToScalar(y)); 722 mc.fMatrix = &adjustedMatrix; 723 724 drawBitmapCommon(mc, bitmap, paint); 725 } 726 727 void SkSVGDevice::drawBitmapRect(const SkBitmap& bm, const SkRect* srcOrNull, 728 const SkRect& dst, const SkPaint& paint, 729 SkCanvas::SrcRectConstraint) { 730 SkClipStack* cs = &this->cs(); 731 SkClipStack::AutoRestore ar(cs, false); 732 if (srcOrNull && *srcOrNull != SkRect::Make(bm.bounds())) { 733 cs->save(); 734 cs->clipRect(dst, this->ctm(), kIntersect_SkClipOp, paint.isAntiAlias()); 735 } 736 737 SkMatrix adjustedMatrix; 738 adjustedMatrix.setRectToRect(srcOrNull ? *srcOrNull : SkRect::Make(bm.bounds()), 739 dst, 740 SkMatrix::kFill_ScaleToFit); 741 adjustedMatrix.postConcat(this->ctm()); 742 743 drawBitmapCommon(MxCp(&adjustedMatrix, cs), bm, paint); 744 } 745 746 void SkSVGDevice::drawText(const void* text, size_t len, 747 SkScalar x, SkScalar y, const SkPaint& paint) { 748 AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), paint); 749 elem.addTextAttributes(paint); 750 751 SVGTextBuilder builder(text, len, paint, SkPoint::Make(x, y), 0); 752 elem.addAttribute("x", builder.posX()); 753 elem.addAttribute("y", builder.posY()); 754 elem.addText(builder.text()); 755 } 756 757 void SkSVGDevice::drawPosText(const void* text, size_t len, 758 const SkScalar pos[], int scalarsPerPos, const SkPoint& offset, 759 const SkPaint& paint) { 760 SkASSERT(scalarsPerPos == 1 || scalarsPerPos == 2); 761 762 AutoElement elem("text", fWriter, fResourceBucket.get(), MxCp(this), paint); 763 elem.addTextAttributes(paint); 764 765 SVGTextBuilder builder(text, len, paint, offset, scalarsPerPos, pos); 766 elem.addAttribute("x", builder.posX()); 767 elem.addAttribute("y", builder.posY()); 768 elem.addText(builder.text()); 769 } 770 771 void SkSVGDevice::drawTextOnPath(const void* text, size_t len, const SkPath& path, 772 const SkMatrix* matrix, const SkPaint& paint) { 773 SkString pathID = fResourceBucket->addPath(); 774 775 { 776 AutoElement defs("defs", fWriter); 777 AutoElement pathElement("path", fWriter); 778 pathElement.addAttribute("id", pathID); 779 pathElement.addPathAttributes(path); 780 781 } 782 783 { 784 AutoElement textElement("text", fWriter); 785 textElement.addTextAttributes(paint); 786 787 if (matrix && !matrix->isIdentity()) { 788 textElement.addAttribute("transform", svg_transform(*matrix)); 789 } 790 791 { 792 AutoElement textPathElement("textPath", fWriter); 793 textPathElement.addAttribute("xlink:href", SkStringPrintf("#%s", pathID.c_str())); 794 795 if (paint.getTextAlign() != SkPaint::kLeft_Align) { 796 SkASSERT(paint.getTextAlign() == SkPaint::kCenter_Align || 797 paint.getTextAlign() == SkPaint::kRight_Align); 798 textPathElement.addAttribute("startOffset", 799 paint.getTextAlign() == SkPaint::kCenter_Align ? "50%" : "100%"); 800 } 801 802 SVGTextBuilder builder(text, len, paint, SkPoint::Make(0, 0), 0); 803 textPathElement.addText(builder.text()); 804 } 805 } 806 } 807 808 void SkSVGDevice::drawVertices(const SkVertices*, SkBlendMode, const SkPaint&) { 809 // todo 810 SkDebugf("unsupported operation: drawVertices()\n"); 811 } 812 813 void SkSVGDevice::drawDevice(SkBaseDevice*, int x, int y, 814 const SkPaint&) { 815 // todo 816 SkDebugf("unsupported operation: drawDevice()\n"); 817 } 818