Implemented transparent gradients

R=vandebo@chromium.org, edisonn@google.com

Author: richardlin@chromium.org

Review URL: https://chromiumcodereview.appspot.com/18585002

git-svn-id: http://skia.googlecode.com/svn/trunk@10297 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index d323009..f7077a9 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -425,14 +425,7 @@
     // PDF treats a shader as a color, so we only set one or the other.
     if (state.fShaderIndex >= 0) {
         if (state.fShaderIndex != currentEntry()->fShaderIndex) {
-            SkString resourceName = SkPDFResourceDict::getResourceName(
-                    SkPDFResourceDict::kPattern_ResourceType,
-                    state.fShaderIndex);
-            fContentStream->writeText("/Pattern CS /Pattern cs /");
-            fContentStream->writeText(resourceName.c_str());
-            fContentStream->writeText(" SCN /");
-            fContentStream->writeText(resourceName.c_str());
-            fContentStream->writeText(" scn\n");
+            SkPDFUtils::ApplyPattern(state.fShaderIndex, fContentStream);
             currentEntry()->fShaderIndex = state.fShaderIndex;
         }
     } else {
@@ -1407,7 +1400,8 @@
     this->drawPaint(draw, stockPaint);
     SkAutoTUnref<SkPDFFormXObject> maskFormXObject(createFormXObjectFromDevice());
     SkAutoTUnref<SkPDFGraphicState> sMaskGS(
-        SkPDFGraphicState::GetSMaskGraphicState(maskFormXObject, invertClip));
+        SkPDFGraphicState::GetSMaskGraphicState(maskFormXObject, invertClip,
+                                                SkPDFGraphicState::kAlpha_SMaskMode));
 
     // Draw the xobject with the clip as a mask.
     ScopedContentEntry content(this, &fExistingClipStack, fExistingClipRegion,
@@ -1563,12 +1557,16 @@
     if (xfermode == SkXfermode::kSrcIn_Mode ||
             xfermode == SkXfermode::kSrcOut_Mode) {
         sMaskGS.reset(SkPDFGraphicState::GetSMaskGraphicState(
-                dst, xfermode == SkXfermode::kSrcOut_Mode));
+                dst,
+                xfermode == SkXfermode::kSrcOut_Mode,
+                SkPDFGraphicState::kAlpha_SMaskMode));
         fXObjectResources.push(srcFormXObject.get());
         srcFormXObject.get()->ref();
     } else {
         sMaskGS.reset(SkPDFGraphicState::GetSMaskGraphicState(
-                srcFormXObject.get(), xfermode == SkXfermode::kDstOut_Mode));
+                srcFormXObject.get(),
+                xfermode == SkXfermode::kDstOut_Mode,
+                SkPDFGraphicState::kAlpha_SMaskMode));
         // dst already added to fXObjectResources in drawFormXObjectWithClip.
     }
     SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
diff --git a/src/pdf/SkPDFFormXObject.cpp b/src/pdf/SkPDFFormXObject.cpp
index 6958e27..1635b0f 100644
--- a/src/pdf/SkPDFFormXObject.cpp
+++ b/src/pdf/SkPDFFormXObject.cpp
@@ -28,10 +28,8 @@
     SkAutoTUnref<SkStream> content(device->content());
     setData(content.get());
 
-    insertName("Type", "XObject");
-    insertName("Subtype", "Form");
-    SkSafeUnref(this->insert("BBox", device->copyMediaBox()));
-    insert("Resources", resourceDict);
+    SkAutoTUnref<SkPDFArray> bboxArray(device->copyMediaBox());
+    init(NULL, resourceDict, bboxArray);
 
     // We invert the initial transform and apply that to the xobject so that
     // it doesn't get applied twice. We can't just undo it because it's
@@ -45,11 +43,41 @@
         }
         insert("Matrix", SkPDFUtils::MatrixToArray(inverse))->unref();
     }
+}
+
+/**
+ * Creates a FormXObject from a content stream and associated resources.
+ */
+SkPDFFormXObject::SkPDFFormXObject(SkStream* content, SkRect bbox,
+                                   SkPDFResourceDict* resourceDict) {
+    SkTSet<SkPDFObject*> emptySet;
+    resourceDict->getReferencedResources(emptySet, &fResources, false);
+
+    setData(content);
+
+    SkAutoTUnref<SkPDFArray> bboxArray(SkPDFUtils::RectToArray(bbox));
+    init("DeviceRGB", resourceDict, bboxArray);
+}
+
+/**
+ * Common initialization code.
+ * Note that bbox is unreferenced here, so calling code does not need worry.
+ */
+void SkPDFFormXObject::init(const char* colorSpace,
+                            SkPDFDict* resourceDict, SkPDFArray* bbox) {
+    insertName("Type", "XObject");
+    insertName("Subtype", "Form");
+    insert("Resources", resourceDict);
+    insert("BBox", bbox);
 
     // Right now SkPDFFormXObject is only used for saveLayer, which implies
     // isolated blending.  Do this conditionally if that changes.
     SkAutoTUnref<SkPDFDict> group(new SkPDFDict("Group"));
     group->insertName("S", "Transparency");
+
+    if (colorSpace != NULL) {
+        group->insertName("CS", colorSpace);
+    }
     group->insert("I", new SkPDFBool(true))->unref();  // Isolated.
     insert("Group", group.get());
 }
diff --git a/src/pdf/SkPDFFormXObject.h b/src/pdf/SkPDFFormXObject.h
index b1a6f74..38c04b1 100644
--- a/src/pdf/SkPDFFormXObject.h
+++ b/src/pdf/SkPDFFormXObject.h
@@ -12,7 +12,9 @@
 
 #include "SkPDFStream.h"
 #include "SkPDFTypes.h"
+#include "SkRect.h"
 #include "SkRefCnt.h"
+#include "SkPDFResourceDict.h"
 #include "SkString.h"
 
 class SkMatrix;
@@ -36,6 +38,13 @@
      *  @param device      The set of graphical elements on this form.
      */
     explicit SkPDFFormXObject(SkPDFDevice* device);
+    /**
+     * Create a PDF form XObject from a raw content stream and associated
+     * resources.
+     */
+    explicit SkPDFFormXObject(SkStream* content,
+                              SkRect bbox,
+                              SkPDFResourceDict* resourceDict);
     virtual ~SkPDFFormXObject();
 
     // The SkPDFObject interface.
@@ -43,6 +52,9 @@
                               SkTSet<SkPDFObject*>* newResourceObjects);
 
 private:
+    void init(const char* colorSpace,
+              SkPDFDict* resourceDict, SkPDFArray* bbox);
+
     SkTSet<SkPDFObject*> fResources;
 };
 
diff --git a/src/pdf/SkPDFGraphicState.cpp b/src/pdf/SkPDFGraphicState.cpp
index 9cf2145..43d22f3 100644
--- a/src/pdf/SkPDFGraphicState.cpp
+++ b/src/pdf/SkPDFGraphicState.cpp
@@ -138,13 +138,17 @@
 
 // static
 SkPDFGraphicState* SkPDFGraphicState::GetSMaskGraphicState(
-        SkPDFFormXObject* sMask, bool invert) {
+        SkPDFFormXObject* sMask, bool invert, SkPDFSMaskMode sMaskMode) {
     // The practical chances of using the same mask more than once are unlikely
     // enough that it's not worth canonicalizing.
     SkAutoMutexAcquire lock(CanonicalPaintsMutex());
 
     SkAutoTUnref<SkPDFDict> sMaskDict(new SkPDFDict("Mask"));
-    sMaskDict->insertName("S", "Alpha");
+    if (sMaskMode == kAlpha_SMaskMode) {
+        sMaskDict->insertName("S", "Alpha");
+    } else if (sMaskMode == kLuminosity_SMaskMode) {
+        sMaskDict->insertName("S", "Luminosity");
+    }
     sMaskDict->insert("G", new SkPDFObjRef(sMask))->unref();
 
     SkPDFGraphicState* result = new SkPDFGraphicState;
diff --git a/src/pdf/SkPDFGraphicState.h b/src/pdf/SkPDFGraphicState.h
index 64f34f8..84c4291 100644
--- a/src/pdf/SkPDFGraphicState.h
+++ b/src/pdf/SkPDFGraphicState.h
@@ -28,6 +28,11 @@
 */
 class SkPDFGraphicState : public SkPDFDict {
 public:
+    enum SkPDFSMaskMode {
+        kAlpha_SMaskMode,
+        kLuminosity_SMaskMode
+    };
+
     virtual ~SkPDFGraphicState();
 
     virtual void getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
@@ -51,11 +56,13 @@
     /** Make a graphic state that only sets the passed soft mask. The
      *  reference count of the object is incremented and it is the caller's
      *  responsibility to unreference it when done.
-     *  @param sMask  The form xobject to use as a soft mask.
-     *  @param invert Indicates if the alpha of the sMask should be inverted.
+     *  @param sMask     The form xobject to use as a soft mask.
+     *  @param invert    Indicates if the alpha of the sMask should be inverted.
+     *  @param sMaskMode Whether to use alpha or luminosity for the sMask.
      */
     static SkPDFGraphicState* GetSMaskGraphicState(SkPDFFormXObject* sMask,
-                                                   bool invert);
+                                                   bool invert,
+                                                   SkPDFSMaskMode sMaskMode);
 
     /** Get a graphic state that only unsets the soft mask. The reference
      *  count of the object is incremented and it is the caller's responsibility
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index ef41c01..9394f1b 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -13,13 +13,15 @@
 #include "SkData.h"
 #include "SkPDFCatalog.h"
 #include "SkPDFDevice.h"
-#include "SkPDFTypes.h"
+#include "SkPDFFormXObject.h"
+#include "SkPDFGraphicState.h"
 #include "SkPDFResourceDict.h"
 #include "SkPDFUtils.h"
 #include "SkScalar.h"
 #include "SkStream.h"
 #include "SkTemplates.h"
 #include "SkThread.h"
+#include "SkTSet.h"
 #include "SkTypes.h"
 
 static bool transformBBox(const SkMatrix& matrix, SkRect* bbox) {
@@ -399,7 +401,7 @@
 public:
     SkShader::GradientType fType;
     SkShader::GradientInfo fInfo;
-    SkAutoFree fColorData;
+    SkAutoFree fColorData;    // This provides storage for arrays in fInfo.
     SkMatrix fCanvasTransform;
     SkMatrix fShaderTransform;
     SkIRect fBBox;
@@ -408,9 +410,20 @@
     uint32_t fPixelGeneration;
     SkShader::TileMode fImageTileModes[2];
 
-    explicit State(const SkShader& shader, const SkMatrix& canvasTransform,
-                   const SkIRect& bbox);
+    State(const SkShader& shader, const SkMatrix& canvasTransform,
+          const SkIRect& bbox);
+
     bool operator==(const State& b) const;
+
+    SkPDFShader::State* CreateAlphaToLuminosityState() const;
+    SkPDFShader::State* CreateOpaqueState() const;
+
+    bool GradientHasAlpha() const;
+
+private:
+    State(const State& other);
+    State operator=(const State& rhs);
+    void AllocateGradientInfoStorage();
 };
 
 class SkPDFFunctionShader : public SkPDFDict, public SkPDFShader {
@@ -441,6 +454,40 @@
     SkPDFStream* makePSFunction(const SkString& psCode, SkPDFArray* domain);
 };
 
+/**
+ * A shader for PDF gradients. This encapsulates the function shader
+ * inside a tiling pattern while providing a common pattern interface.
+ * The encapsulation allows the use of a SMask for transparency gradients.
+ */
+class SkPDFAlphaFunctionShader : public SkPDFStream, public SkPDFShader {
+public:
+    explicit SkPDFAlphaFunctionShader(SkPDFShader::State* state);
+    virtual ~SkPDFAlphaFunctionShader() {
+        if (isValid()) {
+            RemoveShader(this);
+        }
+    }
+
+    virtual bool isValid() {
+        return fColorShader.get() != NULL;
+    }
+
+private:
+    SkAutoTDelete<const SkPDFShader::State> fState;
+
+    SkPDFGraphicState* CreateSMaskGraphicState();
+
+    void getResources(const SkTSet<SkPDFObject*>& knownResourceObjects,
+                      SkTSet<SkPDFObject*>* newResourceObjects) {
+        fResourceDict->getReferencedResources(knownResourceObjects,
+                                              newResourceObjects,
+                                              true);
+    }
+
+    SkAutoTUnref<SkPDFObject> fColorShader;
+    SkAutoTUnref<SkPDFResourceDict> fResourceDict;
+};
+
 class SkPDFImageShader : public SkPDFStream, public SkPDFShader {
 public:
     explicit SkPDFImageShader(SkPDFShader::State* state);
@@ -468,21 +515,10 @@
 SkPDFShader::SkPDFShader() {}
 
 // static
-void SkPDFShader::RemoveShader(SkPDFObject* shader) {
-    SkAutoMutexAcquire lock(CanonicalShadersMutex());
-    ShaderCanonicalEntry entry(shader, NULL);
-    int index = CanonicalShaders().find(entry);
-    SkASSERT(index >= 0);
-    CanonicalShaders().removeShuffle(index);
-}
-
-// static
-SkPDFObject* SkPDFShader::GetPDFShader(const SkShader& shader,
-                                       const SkMatrix& matrix,
-                                       const SkIRect& surfaceBBox) {
+SkPDFObject* SkPDFShader::GetPDFShaderByState(State* inState) {
     SkPDFObject* result;
-    SkAutoMutexAcquire lock(CanonicalShadersMutex());
-    SkAutoTDelete<State> shaderState(new State(shader, matrix, surfaceBBox));
+
+    SkAutoTDelete<State> shaderState(inState);
     if (shaderState.get()->fType == SkShader::kNone_GradientType &&
             shaderState.get()->fImage.isNull()) {
         // TODO(vandebo) This drops SKComposeShader on the floor.  We could
@@ -508,10 +544,17 @@
         valid = imageShader->isValid();
         result = imageShader;
     } else {
-        SkPDFFunctionShader* functionShader =
-            new SkPDFFunctionShader(shaderState.detach());
-        valid = functionShader->isValid();
-        result = functionShader;
+        if (shaderState.get()->GradientHasAlpha()) {
+            SkPDFAlphaFunctionShader* gradientShader =
+                SkNEW_ARGS(SkPDFAlphaFunctionShader, (shaderState.detach()));
+            valid = gradientShader->isValid();
+            result = gradientShader;
+        } else {
+            SkPDFFunctionShader* functionShader =
+                SkNEW_ARGS(SkPDFFunctionShader, (shaderState.detach()));
+            valid = functionShader->isValid();
+            result = functionShader;
+        }
     }
     if (!valid) {
         delete result;
@@ -523,6 +566,24 @@
 }
 
 // static
+void SkPDFShader::RemoveShader(SkPDFObject* shader) {
+    SkAutoMutexAcquire lock(CanonicalShadersMutex());
+    ShaderCanonicalEntry entry(shader, NULL);
+    int index = CanonicalShaders().find(entry);
+    SkASSERT(index >= 0);
+    CanonicalShaders().removeShuffle(index);
+}
+
+// static
+SkPDFObject* SkPDFShader::GetPDFShader(const SkShader& shader,
+                                       const SkMatrix& matrix,
+                                       const SkIRect& surfaceBBox) {
+    SkAutoMutexAcquire lock(CanonicalShadersMutex());
+    return GetPDFShaderByState(
+            SkNEW_ARGS(State, (shader, matrix, surfaceBBox)));
+}
+
+// static
 SkTDArray<SkPDFShader::ShaderCanonicalEntry>& SkPDFShader::CanonicalShaders() {
     // This initialization is only thread safe with gcc.
     static SkTDArray<ShaderCanonicalEntry> gCanonicalShaders;
@@ -556,6 +617,108 @@
     return range;
 }
 
+static SkPDFResourceDict* get_gradient_resource_dict(
+        SkPDFObject* functionShader,
+        SkPDFObject* gState) {
+    SkPDFResourceDict* dict = new SkPDFResourceDict();
+
+    if (functionShader != NULL) {
+        dict->insertResourceAsReference(
+                SkPDFResourceDict::kPattern_ResourceType, 0, functionShader);
+    }
+    if (gState != NULL) {
+        dict->insertResourceAsReference(
+                SkPDFResourceDict::kExtGState_ResourceType, 0, gState);
+    }
+
+    return dict;
+}
+
+static void populate_tiling_pattern_dict(SkPDFDict* pattern,
+                                      SkRect& bbox, SkPDFDict* resources,
+                                      const SkMatrix& matrix) {
+    const int kTiling_PatternType = 1;
+    const int kColoredTilingPattern_PaintType = 1;
+    const int kConstantSpacing_TilingType = 1;
+
+    pattern->insertName("Type", "Pattern");
+    pattern->insertInt("PatternType", kTiling_PatternType);
+    pattern->insertInt("PaintType", kColoredTilingPattern_PaintType);
+    pattern->insertInt("TilingType", kConstantSpacing_TilingType);
+    pattern->insert("BBox", SkPDFUtils::RectToArray(bbox))->unref();
+    pattern->insertScalar("XStep", bbox.width());
+    pattern->insertScalar("YStep", bbox.height());
+    pattern->insert("Resources", resources);
+    if (!matrix.isIdentity()) {
+        pattern->insert("Matrix", SkPDFUtils::MatrixToArray(matrix))->unref();
+    }
+}
+
+/**
+ * Creates a content stream which fills the pattern P0 across bounds.
+ * @param gsIndex A graphics state resource index to apply, or <0 if no
+ * graphics state to apply.
+ */
+static SkStream* create_pattern_fill_content(int gsIndex, SkRect& bounds) {
+    SkDynamicMemoryWStream content;
+    if (gsIndex >= 0) {
+        SkPDFUtils::ApplyGraphicState(gsIndex, &content);
+    }
+    SkPDFUtils::ApplyPattern(0, &content);
+    SkPDFUtils::AppendRectangle(bounds, &content);
+    SkPDFUtils::PaintPath(SkPaint::kFill_Style, SkPath::kEvenOdd_FillType,
+                          &content);
+
+    return content.detachAsStream();
+}
+
+/**
+ * Creates a ExtGState with the SMask set to the luminosityShader in
+ * luminosity mode. The shader pattern extends to the bbox.
+ */
+SkPDFGraphicState* SkPDFAlphaFunctionShader::CreateSMaskGraphicState() {
+    SkRect bbox;
+    bbox.set(fState.get()->fBBox);
+
+    SkAutoTUnref<SkPDFObject> luminosityShader(
+            SkPDFShader::GetPDFShaderByState(
+                 fState->CreateAlphaToLuminosityState()));
+
+    SkAutoTUnref<SkStream> alphaStream(create_pattern_fill_content(-1, bbox));
+
+    SkAutoTUnref<SkPDFResourceDict>
+        resources(get_gradient_resource_dict(luminosityShader, NULL));
+
+    SkAutoTUnref<SkPDFFormXObject> alphaMask(
+            new SkPDFFormXObject(alphaStream.get(), bbox, resources.get()));
+
+    return SkPDFGraphicState::GetSMaskGraphicState(
+            alphaMask.get(), false,
+            SkPDFGraphicState::kLuminosity_SMaskMode);
+}
+
+SkPDFAlphaFunctionShader::SkPDFAlphaFunctionShader(SkPDFShader::State* state)
+        : fState(state) {
+    SkRect bbox;
+    bbox.set(fState.get()->fBBox);
+
+    fColorShader.reset(
+            SkPDFShader::GetPDFShaderByState(state->CreateOpaqueState()));
+
+    // Create resource dict with alpha graphics state as G0 and
+    // pattern shader as P0, then write content stream.
+    SkAutoTUnref<SkPDFGraphicState> alphaGs(CreateSMaskGraphicState());
+    fResourceDict.reset(
+            get_gradient_resource_dict(fColorShader.get(), alphaGs.get()));
+
+    SkAutoTUnref<SkStream> colorStream(
+            create_pattern_fill_content(0, bbox));
+    setData(colorStream.get());
+
+    populate_tiling_pattern_dict(this, bbox, fResourceDict.get(),
+                                 SkMatrix::I());
+}
+
 SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state)
         : SkPDFDict("Pattern"),
           fState(state) {
@@ -831,28 +994,14 @@
         }
     }
 
-    SkAutoTUnref<SkPDFArray> patternBBoxArray(new SkPDFArray);
-    patternBBoxArray->reserve(4);
-    patternBBoxArray->appendScalar(patternBBox.fLeft);
-    patternBBoxArray->appendScalar(patternBBox.fTop);
-    patternBBoxArray->appendScalar(patternBBox.fRight);
-    patternBBoxArray->appendScalar(patternBBox.fBottom);
-
     // Put the canvas into the pattern stream (fContent).
     SkAutoTUnref<SkStream> content(pattern.content());
     setData(content.get());
     SkPDFResourceDict* resourceDict = pattern.getResourceDict();
     resourceDict->getReferencedResources(fResources, &fResources, false);
 
-    insertName("Type", "Pattern");
-    insertInt("PatternType", 1);
-    insertInt("PaintType", 1);
-    insertInt("TilingType", 1);
-    insert("BBox", patternBBoxArray.get());
-    insertScalar("XStep", patternBBox.width());
-    insertScalar("YStep", patternBBox.height());
-    insert("Resources", resourceDict);
-    insert("Matrix", SkPDFUtils::MatrixToArray(finalMatrix))->unref();
+    populate_tiling_pattern_dict(this, patternBBox,
+                                 pattern.getResourceDict(), finalMatrix);
 
     fState.get()->fImage.unlockPixels();
 }
@@ -958,11 +1107,86 @@
         SkASSERT(matrix.isIdentity());
         fPixelGeneration = fImage.getGenerationID();
     } else {
-        fColorData.set(sk_malloc_throw(
-                    fInfo.fColorCount * (sizeof(SkColor) + sizeof(SkScalar))));
-        fInfo.fColors = reinterpret_cast<SkColor*>(fColorData.get());
-        fInfo.fColorOffsets =
-            reinterpret_cast<SkScalar*>(fInfo.fColors + fInfo.fColorCount);
+        AllocateGradientInfoStorage();
         shader.asAGradient(&fInfo);
     }
 }
+
+SkPDFShader::State::State(const SkPDFShader::State& other)
+  : fType(other.fType),
+    fCanvasTransform(other.fCanvasTransform),
+    fShaderTransform(other.fShaderTransform),
+    fBBox(other.fBBox)
+{
+    // Only gradients supported for now, since that is all that is used.
+    // If needed, image state copy constructor can be added here later.
+    SkASSERT(fType != SkShader::kNone_GradientType);
+
+    if (fType != SkShader::kNone_GradientType) {
+        fInfo = other.fInfo;
+
+        AllocateGradientInfoStorage();
+        for (int i = 0; i < fInfo.fColorCount; i++) {
+            fInfo.fColors[i] = other.fInfo.fColors[i];
+            fInfo.fColorOffsets[i] = other.fInfo.fColorOffsets[i];
+        }
+    }
+}
+
+/**
+ * Create a copy of this gradient state with alpha assigned to RGB luminousity.
+ * Only valid for gradient states.
+ */
+SkPDFShader::State* SkPDFShader::State::CreateAlphaToLuminosityState() const {
+    SkASSERT(fType != SkShader::kNone_GradientType);
+
+    SkPDFShader::State* newState = new SkPDFShader::State(*this);
+
+    for (int i = 0; i < fInfo.fColorCount; i++) {
+        SkAlpha alpha = SkColorGetA(fInfo.fColors[i]);
+        newState->fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha);
+    }
+
+    return newState;
+}
+
+/**
+ * Create a copy of this gradient state with alpha set to fully opaque
+ * Only valid for gradient states.
+ */
+SkPDFShader::State* SkPDFShader::State::CreateOpaqueState() const {
+    SkASSERT(fType != SkShader::kNone_GradientType);
+
+    SkPDFShader::State* newState = new SkPDFShader::State(*this);
+    for (int i = 0; i < fInfo.fColorCount; i++) {
+        newState->fInfo.fColors[i] = SkColorSetA(fInfo.fColors[i],
+                                                 SK_AlphaOPAQUE);
+    }
+
+    return newState;
+}
+
+/**
+ * Returns true if state is a gradient and the gradient has alpha.
+ */
+bool SkPDFShader::State::GradientHasAlpha() const {
+    if (fType == SkShader::kNone_GradientType) {
+        return false;
+    }
+
+    for (int i = 0; i < fInfo.fColorCount; i++) {
+        SkAlpha alpha = SkColorGetA(fInfo.fColors[i]);
+        if (alpha != SK_AlphaOPAQUE) {
+            return true;
+        }
+    }
+    return false;
+}
+
+void SkPDFShader::State::AllocateGradientInfoStorage() {
+    fColorData.set(sk_malloc_throw(
+               fInfo.fColorCount * (sizeof(SkColor) + sizeof(SkScalar))));
+    fInfo.fColors = reinterpret_cast<SkColor*>(fColorData.get());
+    fInfo.fColorOffsets =
+            reinterpret_cast<SkScalar*>(fInfo.fColors + fInfo.fColorCount);
+}
diff --git a/src/pdf/SkPDFShader.h b/src/pdf/SkPDFShader.h
index f41bdf0..0b292e9 100644
--- a/src/pdf/SkPDFShader.h
+++ b/src/pdf/SkPDFShader.h
@@ -57,6 +57,11 @@
     // This should be made a hash table if performance is a problem.
     static SkTDArray<ShaderCanonicalEntry>& CanonicalShaders();
     static SkBaseMutex& CanonicalShadersMutex();
+
+    // This is an internal method.
+    // CanonicalShadersMutex() should already be acquired.
+    // This also takes ownership of shaderState.
+    static SkPDFObject* GetPDFShaderByState(State* shaderState);
     static void RemoveShader(SkPDFObject* shader);
 
     SkPDFShader();
diff --git a/src/pdf/SkPDFUtils.cpp b/src/pdf/SkPDFUtils.cpp
index 16abc2f..d034270 100644
--- a/src/pdf/SkPDFUtils.cpp
+++ b/src/pdf/SkPDFUtils.cpp
@@ -17,6 +17,17 @@
 #include "SkString.h"
 #include "SkPDFTypes.h"
 
+//static
+SkPDFArray* SkPDFUtils::RectToArray(const SkRect& rect) {
+    SkPDFArray* result = new SkPDFArray();
+    result->reserve(4);
+    result->appendScalar(rect.fLeft);
+    result->appendScalar(rect.fTop);
+    result->appendScalar(rect.fRight);
+    result->appendScalar(rect.fBottom);
+    return result;
+}
+
 // static
 SkPDFArray* SkPDFUtils::MatrixToArray(const SkMatrix& matrix) {
     SkScalar values[6];
@@ -222,3 +233,17 @@
             objectIndex).c_str());
     content->writeText(" gs\n");
 }
+
+// static
+void SkPDFUtils::ApplyPattern(int objectIndex, SkWStream* content) {
+    // Select Pattern color space (CS, cs) and set pattern object as current
+    // color (SCN, scn)
+    SkString resourceName = SkPDFResourceDict::getResourceName(
+            SkPDFResourceDict::kPattern_ResourceType,
+            objectIndex);
+    content->writeText("/Pattern CS/Pattern cs/");
+    content->writeText(resourceName.c_str());
+    content->writeText(" SCN/");
+    content->writeText(resourceName.c_str());
+    content->writeText(" scn\n");
+}
diff --git a/src/pdf/SkPDFUtils.h b/src/pdf/SkPDFUtils.h
index 1bdbf78..4f54db6 100644
--- a/src/pdf/SkPDFUtils.h
+++ b/src/pdf/SkPDFUtils.h
@@ -35,6 +35,7 @@
 
 class SkPDFUtils {
 public:
+    static SkPDFArray* RectToArray(const SkRect& rect);
     static SkPDFArray* MatrixToArray(const SkMatrix& matrix);
     static void AppendTransform(const SkMatrix& matrix, SkWStream* content);
 
@@ -52,6 +53,7 @@
     static void StrokePath(SkWStream* content);
     static void DrawFormXObject(int objectIndex, SkWStream* content);
     static void ApplyGraphicState(int objectIndex, SkWStream* content);
+    static void ApplyPattern(int objectIndex, SkWStream* content);
 };
 
 #endif