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