| Index: experimental/svg/SkSVGDevice.cpp | 
| diff --git a/experimental/svg/SkSVGDevice.cpp b/experimental/svg/SkSVGDevice.cpp | 
| index 4a662e7b9470c71b8922fa30cd09a1e430bb7910..c0ca612cdec3b877f774bc018d885ec16b40e032 100644 | 
| --- a/experimental/svg/SkSVGDevice.cpp | 
| +++ b/experimental/svg/SkSVGDevice.cpp | 
| @@ -11,26 +11,227 @@ | 
| #include "SkDraw.h" | 
| #include "SkPaint.h" | 
| #include "SkParsePath.h" | 
| +#include "SkShader.h" | 
| #include "SkStream.h" | 
| #include "SkXMLWriter.h" | 
|  | 
| namespace { | 
|  | 
| -class AutoElement { | 
| +static SkString svg_color(SkColor color) { | 
| +    SkString colorStr; | 
| +    colorStr.printf("rgb(%u,%u,%u)", | 
| +                     SkColorGetR(color), | 
| +                     SkColorGetG(color), | 
| +                     SkColorGetB(color)); | 
| +    return colorStr; | 
| +} | 
| + | 
| +static SkScalar svg_opacity(SkColor color) { | 
| +    return SkIntToScalar(SkColorGetA(color)) / SK_AlphaOPAQUE; | 
| +} | 
| + | 
| +struct Resources { | 
| +    Resources(const SkPaint& paint) | 
| +        : fPaintServer(svg_color(paint.getColor())) {} | 
| + | 
| +    SkString fPaintServer; | 
| +}; | 
| + | 
| +} | 
| + | 
| +// For now all this does is serve unique serial IDs, but it will eventually evolve to track | 
| +// and deduplicate resources. | 
| +class SkSVGDevice::ResourceBucket : ::SkNoncopyable { | 
| +public: | 
| +    ResourceBucket() : fGradientCount(0) {} | 
| + | 
| +    SkString addLinearGradient() { | 
| +        SkString id; | 
| +        id.printf("gradient_%d", fGradientCount++); | 
| +        return id; | 
| +    } | 
| + | 
| +private: | 
| +    uint32_t fGradientCount; | 
| +}; | 
| + | 
| +class SkSVGDevice::AutoElement : ::SkNoncopyable { | 
| public: | 
| AutoElement(const char name[], SkXMLWriter* writer) | 
| -        : fWriter(writer) { | 
| +        : fWriter(writer) | 
| +        , fResourceBucket(NULL) { | 
| +        fWriter->startElement(name); | 
| +    } | 
| + | 
| +    AutoElement(const char name[], SkXMLWriter* writer, ResourceBucket* bucket, | 
| +                const SkDraw& draw, const SkPaint& paint) | 
| +        : fWriter(writer) | 
| +        , fResourceBucket(bucket) { | 
| + | 
| +        Resources res = this->addResources(paint); | 
| + | 
| fWriter->startElement(name); | 
| + | 
| +        this->addPaint(paint, res); | 
| +        this->addTransform(*draw.fMatrix); | 
| } | 
|  | 
| ~AutoElement() { | 
| fWriter->endElement(); | 
| } | 
|  | 
| +    void addAttribute(const char name[], const char val[]) { | 
| +        fWriter->addAttribute(name, val); | 
| +    } | 
| + | 
| +    void addAttribute(const char name[], const SkString& val) { | 
| +        fWriter->addAttribute(name, val.c_str()); | 
| +    } | 
| + | 
| +    void addAttribute(const char name[], int32_t val) { | 
| +        fWriter->addS32Attribute(name, val); | 
| +    } | 
| + | 
| +    void addAttribute(const char name[], SkScalar val) { | 
| +        fWriter->addScalarAttribute(name, val); | 
| +    } | 
| + | 
| private: | 
| -    SkXMLWriter* fWriter; | 
| +    Resources addResources(const SkPaint& paint); | 
| +    void addResourceDefs(const SkPaint& paint, Resources* resources); | 
| + | 
| +    void addPaint(const SkPaint& paint, const Resources& resources); | 
| +    void addTransform(const SkMatrix& transform, const char name[] = "transform"); | 
| + | 
| +    SkString addLinearGradientDef(const SkShader::GradientInfo& info, const SkShader* shader); | 
| + | 
| +    SkXMLWriter*    fWriter; | 
| +    ResourceBucket* fResourceBucket; | 
| }; | 
|  | 
| +void SkSVGDevice::AutoElement::addPaint(const SkPaint& paint, const Resources& resources) { | 
| +    SkPaint::Style style = paint.getStyle(); | 
| +    if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) { | 
| +        this->addAttribute("fill", resources.fPaintServer); | 
| +    } else { | 
| +        this->addAttribute("fill", "none"); | 
| +    } | 
| + | 
| +    if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) { | 
| +        this->addAttribute("stroke", resources.fPaintServer); | 
| +        this->addAttribute("stroke-width", paint.getStrokeWidth()); | 
| +    } else { | 
| +        this->addAttribute("stroke", "none"); | 
| +    } | 
| + | 
| +    if (SK_AlphaOPAQUE != SkColorGetA(paint.getColor())) { | 
| +        this->addAttribute("opacity", svg_opacity(paint.getColor())); | 
| +    } | 
| +} | 
| + | 
| +void SkSVGDevice::AutoElement::addTransform(const SkMatrix& t, const char name[]) { | 
| +    if (t.isIdentity()) { | 
| +        return; | 
| +    } | 
| + | 
| +    SkString tstr; | 
| +    switch (t.getType()) { | 
| +    case SkMatrix::kPerspective_Mask: | 
| +        SkDebugf("Can't handle perspective matrices."); | 
| +        break; | 
| +    case SkMatrix::kTranslate_Mask: | 
| +        tstr.printf("translate(%g %g)", | 
| +                     SkScalarToFloat(t.getTranslateX()), | 
| +                     SkScalarToFloat(t.getTranslateY())); | 
| +        break; | 
| +    case SkMatrix::kScale_Mask: | 
| +        tstr.printf("scale(%g %g)", | 
| +                     SkScalarToFloat(t.getScaleX()), | 
| +                     SkScalarToFloat(t.getScaleY())); | 
| +        break; | 
| +    default: | 
| +        tstr.printf("matrix(%g %g %g %g %g %g)", | 
| +                     SkScalarToFloat(t.getScaleX()), SkScalarToFloat(t.getSkewY()), | 
| +                     SkScalarToFloat(t.getSkewX()), SkScalarToFloat(t.getScaleY()), | 
| +                     SkScalarToFloat(t.getTranslateX()), SkScalarToFloat(t.getTranslateY())); | 
| +        break; | 
| +    } | 
| + | 
| +    fWriter->addAttribute(name, tstr.c_str()); | 
| +} | 
| + | 
| +Resources SkSVGDevice::AutoElement::addResources(const SkPaint& paint) { | 
| +    Resources resources(paint); | 
| +    this->addResourceDefs(paint, &resources); | 
| +    return resources; | 
| +} | 
| + | 
| +void SkSVGDevice::AutoElement::addResourceDefs(const SkPaint& paint, Resources* resources) { | 
| +    const SkShader* shader = paint.getShader(); | 
| +    if (!SkToBool(shader)) { | 
| +        // TODO: clip support | 
| +        return; | 
| +    } | 
| + | 
| +    SkShader::GradientInfo grInfo; | 
| +    grInfo.fColorCount = 0; | 
| +    if (SkShader::kLinear_GradientType != shader->asAGradient(&grInfo)) { | 
| +        // TODO: non-linear gradient support | 
| +        SkDebugf("unsupported shader type\n"); | 
| +        return; | 
| +    } | 
| + | 
| +    { | 
| +        AutoElement defs("defs", fWriter); | 
| + | 
| +        SkAutoSTArray<16, SkColor>  grColors(grInfo.fColorCount); | 
| +        SkAutoSTArray<16, SkScalar> grOffsets(grInfo.fColorCount); | 
| +        grInfo.fColors = grColors.get(); | 
| +        grInfo.fColorOffsets = grOffsets.get(); | 
| + | 
| +        // One more call to get the actual colors/offsets. | 
| +        shader->asAGradient(&grInfo); | 
| +        SkASSERT(grInfo.fColorCount <= grColors.count()); | 
| +        SkASSERT(grInfo.fColorCount <= grOffsets.count()); | 
| + | 
| +        resources->fPaintServer.printf("url(#%s)", addLinearGradientDef(grInfo, shader).c_str()); | 
| +    } | 
| +} | 
| + | 
| +SkString SkSVGDevice::AutoElement::addLinearGradientDef(const SkShader::GradientInfo& info, | 
| +                                                        const SkShader* shader) { | 
| +    SkASSERT(fResourceBucket); | 
| +    SkString id = fResourceBucket->addLinearGradient(); | 
| + | 
| +    { | 
| +        AutoElement gradient("linearGradient", fWriter); | 
| + | 
| +        gradient.addAttribute("id", id); | 
| +        gradient.addAttribute("gradientUnits", "userSpaceOnUse"); | 
| +        gradient.addAttribute("x1", info.fPoint[0].x()); | 
| +        gradient.addAttribute("y1", info.fPoint[0].y()); | 
| +        gradient.addAttribute("x2", info.fPoint[1].x()); | 
| +        gradient.addAttribute("y2", info.fPoint[1].y()); | 
| +        gradient.addTransform(shader->getLocalMatrix(), "gradientTransform"); | 
| + | 
| +        SkASSERT(info.fColorCount >= 2); | 
| +        for (int i = 0; i < info.fColorCount; ++i) { | 
| +            SkColor color = info.fColors[i]; | 
| +            SkString colorStr(svg_color(color)); | 
| + | 
| +            { | 
| +                AutoElement stop("stop", fWriter); | 
| +                stop.addAttribute("offset", info.fColorOffsets[i]); | 
| +                stop.addAttribute("stop-color", colorStr.c_str()); | 
| + | 
| +                if (SK_AlphaOPAQUE != SkColorGetA(color)) { | 
| +                    stop.addAttribute("stop-opacity", svg_opacity(color)); | 
| +                } | 
| +            } | 
| +        } | 
| +    } | 
| + | 
| +    return id; | 
| } | 
|  | 
| SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkWStream* wstream) { | 
| @@ -42,22 +243,23 @@ SkBaseDevice* SkSVGDevice::Create(const SkISize& size, SkWStream* wstream) { | 
| } | 
|  | 
| SkSVGDevice::SkSVGDevice(const SkISize& size, SkWStream* wstream) | 
| -    : fWriter(SkNEW_ARGS(SkXMLStreamWriter, (wstream))) { | 
| +    : fWriter(SkNEW_ARGS(SkXMLStreamWriter, (wstream))) | 
| +    , fResourceBucket(SkNEW(ResourceBucket)) { | 
|  | 
| fLegacyBitmap.setInfo(SkImageInfo::MakeUnknown(size.width(), size.height())); | 
|  | 
| fWriter->writeHeader(); | 
| -    fWriter->startElement("svg"); | 
| -    fWriter->addAttribute("xmlns", "http://www.w3.org/2000/svg"); | 
| -    fWriter->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); | 
| -    fWriter->addS32Attribute("width", size.width()); | 
| -    fWriter->addS32Attribute("height", size.height()); | 
| + | 
| +    // The root <svg> tag gets closed by the destructor. | 
| +    fRootElement.reset(SkNEW_ARGS(AutoElement, ("svg", fWriter))); | 
| + | 
| +    fRootElement->addAttribute("xmlns", "http://www.w3.org/2000/svg"); | 
| +    fRootElement->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); | 
| +    fRootElement->addAttribute("width", size.width()); | 
| +    fRootElement->addAttribute("height", size.height()); | 
| } | 
|  | 
| SkSVGDevice::~SkSVGDevice() { | 
| -    fWriter->endElement(); | 
| -    fWriter->flush(); | 
| -    SkDELETE(fWriter); | 
| } | 
|  | 
| SkImageInfo SkSVGDevice::imageInfo() const { | 
| @@ -68,109 +270,85 @@ const SkBitmap& SkSVGDevice::onAccessBitmap() { | 
| return fLegacyBitmap; | 
| } | 
|  | 
| -void SkSVGDevice::addPaint(const SkPaint& paint) { | 
| -    SkColor color = paint.getColor(); | 
| -    SkString colorStr; | 
| -    colorStr.appendf("rgb(%u,%u,%u)", SkColorGetR(color), SkColorGetG(color), SkColorGetB(color)); | 
| - | 
| -    SkPaint::Style style = paint.getStyle(); | 
| -    if (style == SkPaint::kFill_Style || style == SkPaint::kStrokeAndFill_Style) { | 
| -        fWriter->addAttribute("fill", colorStr.c_str()); | 
| -    } else { | 
| -        fWriter->addAttribute("fill", "none"); | 
| -    } | 
| - | 
| -    if (style == SkPaint::kStroke_Style || style == SkPaint::kStrokeAndFill_Style) { | 
| -        fWriter->addAttribute("stroke", colorStr.c_str()); | 
| -        fWriter->addScalarAttribute("stroke-width", paint.getStrokeWidth()); | 
| -    } else { | 
| -        fWriter->addAttribute("stroke", "none"); | 
| -    } | 
| -} | 
| - | 
| -void SkSVGDevice::addTransform(const SkMatrix &t) { | 
| -    if (t.isIdentity()) { | 
| -        return; | 
| -    } | 
| - | 
| -    SkString tstr; | 
| -    tstr.appendf("matrix(%g %g %g %g %g %g)", | 
| -                 SkScalarToFloat(t.getScaleX()), SkScalarToFloat(t.getSkewY()), | 
| -                 SkScalarToFloat(t.getSkewX()), SkScalarToFloat(t.getScaleY()), | 
| -                 SkScalarToFloat(t.getTranslateX()), SkScalarToFloat(t.getTranslateY())); | 
| -    fWriter->addAttribute("transform", tstr.c_str()); | 
| -} | 
| - | 
| -void SkSVGDevice::drawPaint(const SkDraw&, const SkPaint& paint) { | 
| -    // todo | 
| +void SkSVGDevice::drawPaint(const SkDraw& draw, const SkPaint& paint) { | 
| +    AutoElement rect("rect", fWriter, fResourceBucket, draw, paint); | 
| +    rect.addAttribute("x", 0); | 
| +    rect.addAttribute("y", 0); | 
| +    rect.addAttribute("width", this->width()); | 
| +    rect.addAttribute("height", this->height()); | 
| } | 
|  | 
| void SkSVGDevice::drawPoints(const SkDraw&, SkCanvas::PointMode mode, size_t count, | 
| const SkPoint[], const SkPaint& paint) { | 
| // todo | 
| +    SkDebugf("unsupported operation: drawPoints()"); | 
| } | 
|  | 
| void SkSVGDevice::drawRect(const SkDraw& draw, const SkRect& r, const SkPaint& paint) { | 
| -    AutoElement elem("rect", fWriter); | 
| - | 
| -    fWriter->addScalarAttribute("x", r.fLeft); | 
| -    fWriter->addScalarAttribute("y", r.fTop); | 
| -    fWriter->addScalarAttribute("width", r.width()); | 
| -    fWriter->addScalarAttribute("height", r.height()); | 
| - | 
| -    this->addPaint(paint); | 
| -    this->addTransform(*draw.fMatrix); | 
| +    AutoElement rect("rect", fWriter, fResourceBucket, draw, paint); | 
| +    rect.addAttribute("x", r.fLeft); | 
| +    rect.addAttribute("y", r.fTop); | 
| +    rect.addAttribute("width", r.width()); | 
| +    rect.addAttribute("height", r.height()); | 
| } | 
|  | 
| -void SkSVGDevice::drawOval(const SkDraw&, const SkRect& oval, const SkPaint& paint) { | 
| -    // todo | 
| +void SkSVGDevice::drawOval(const SkDraw& draw, const SkRect& oval, const SkPaint& paint) { | 
| +    AutoElement ellipse("ellipse", fWriter, fResourceBucket, draw, paint); | 
| +    ellipse.addAttribute("cx", oval.centerX()); | 
| +    ellipse.addAttribute("cy", oval.centerY()); | 
| +    ellipse.addAttribute("rx", oval.width() / 2); | 
| +    ellipse.addAttribute("ry", oval.height() / 2); | 
| } | 
|  | 
| void SkSVGDevice::drawRRect(const SkDraw&, const SkRRect& rr, const SkPaint& paint) { | 
| // todo | 
| +    SkDebugf("unsupported operation: drawRRect()"); | 
| } | 
|  | 
| void SkSVGDevice::drawPath(const SkDraw& draw, const SkPath& path, const SkPaint& paint, | 
| const SkMatrix* prePathMatrix, bool pathIsMutable) { | 
| -    AutoElement elem("path", fWriter); | 
| +    AutoElement elem("path", fWriter, fResourceBucket, draw, paint); | 
|  | 
| SkString pathStr; | 
| SkParsePath::ToSVGString(path, &pathStr); | 
| -    fWriter->addAttribute("d", pathStr.c_str()); | 
| - | 
| -    this->addPaint(paint); | 
| -    this->addTransform(*draw.fMatrix); | 
| +    elem.addAttribute("d", pathStr.c_str()); | 
| } | 
|  | 
| void SkSVGDevice::drawBitmap(const SkDraw&, const SkBitmap& bitmap, | 
| const SkMatrix& matrix, const SkPaint& paint) { | 
| // todo | 
| +    SkDebugf("unsupported operation: drawBitmap()"); | 
| } | 
|  | 
| void SkSVGDevice::drawSprite(const SkDraw&, const SkBitmap& bitmap, | 
| int x, int y, const SkPaint& paint) { | 
| // todo | 
| +    SkDebugf("unsupported operation: drawSprite()"); | 
| } | 
|  | 
| void SkSVGDevice::drawBitmapRect(const SkDraw&, const SkBitmap&, const SkRect* srcOrNull, | 
| const SkRect& dst, const SkPaint& paint, | 
| SkCanvas::DrawBitmapRectFlags flags) { | 
| // todo | 
| +    SkDebugf("unsupported operation: drawBitmapRect()"); | 
| } | 
|  | 
| void SkSVGDevice::drawText(const SkDraw&, const void* text, size_t len, | 
| SkScalar x, SkScalar y, const SkPaint& paint) { | 
| // todo | 
| +    SkDebugf("unsupported operation: drawText()"); | 
| } | 
|  | 
| void SkSVGDevice::drawPosText(const SkDraw&, const void* text, size_t len,const SkScalar pos[], | 
| int scalarsPerPos, const SkPoint& offset, const SkPaint& paint) { | 
| // todo | 
| +    SkDebugf("unsupported operation: drawPosText()"); | 
| } | 
|  | 
| void SkSVGDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len, const SkPath& path, | 
| const SkMatrix* matrix, const SkPaint& paint) { | 
| // todo | 
| +    SkDebugf("unsupported operation: drawTextOnPath()"); | 
| } | 
|  | 
| void SkSVGDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode, int vertexCount, | 
| @@ -179,9 +357,11 @@ void SkSVGDevice::drawVertices(const SkDraw&, SkCanvas::VertexMode, int vertexCo | 
| const uint16_t indices[], int indexCount, | 
| const SkPaint& paint) { | 
| // todo | 
| +    SkDebugf("unsupported operation: drawVertices()"); | 
| } | 
|  | 
| void SkSVGDevice::drawDevice(const SkDraw&, SkBaseDevice*, int x, int y, | 
| const SkPaint&) { | 
| // todo | 
| +    SkDebugf("unsupported operation: drawDevice()"); | 
| } | 
|  |