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