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