SkPDF: Refactor PDFShader to use ShTHashMap<>

my tests run ~14% faster.

  - Split out gradient shaders from image shaders.  new compilation
    unit: SkPDFGradientShader
  - Common functions InverseTransformBBox and PopulateTilingPatternDict
    moved to SkPDFUtils
  - Split SkPDFShader::State into image and gradient structures.
  - SkPDFCanon is now a simpler structure, with no logic of its own.
    I am considering just moving all of its fields into SkPDFDocument
  - SkPDFShader::State (the image/fallback shader) now is POD, making
    the use of a hashmap for canonicalization straightforward.
    Formerly, we used a linear search.
  - Do not bother trying to canonicalize the falback image shader.
  - SkPDFGradientShader::Key is not POD; comparison of two objects
    requires looking at the contents of two variable-sized arrays.
    We now pre-calculate the hash of the arrays using SkOpts::hash and
    store a hash for the object in the fHash field.
    Using that hash, we can now canonicalize using a hashmap instead
    of a linar search!
  - several static functions renamed to follow style guidelines
  - stop using codeFunction function pointer; I find that less
    clear than it could be.
  - operator==() for SkPDFShader::State and SkPDFGradientShader::Key is
    now much simpler and can now be inlined.
  - SkArrayEqual template in SkPDFUtils.h

No change to PDF output.

BUG=skia:3585
Change-Id: I354ad1b600be6d6749abccb58d13db257370bc0b
Reviewed-on: https://skia-review.googlesource.com/21376
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Hal Canary <halcanary@google.com>
diff --git a/gn/pdf.gni b/gn/pdf.gni
index f7de23d..6b140e0 100644
--- a/gn/pdf.gni
+++ b/gn/pdf.gni
@@ -28,6 +28,8 @@
   "$_src/pdf/SkPDFFont.h",
   "$_src/pdf/SkPDFFormXObject.cpp",
   "$_src/pdf/SkPDFFormXObject.h",
+  "$_src/pdf/SkPDFGradientShader.cpp",
+  "$_src/pdf/SkPDFGradientShader.h",
   "$_src/pdf/SkPDFGraphicState.cpp",
   "$_src/pdf/SkPDFGraphicState.h",
   "$_src/pdf/SkPDFMakeCIDGlyphWidthsArray.cpp",
diff --git a/src/pdf/SkPDFCanon.cpp b/src/pdf/SkPDFCanon.cpp
index 3ecd474..53a0044 100644
--- a/src/pdf/SkPDFCanon.cpp
+++ b/src/pdf/SkPDFCanon.cpp
@@ -10,47 +10,7 @@
 #include "SkPDFCanon.h"
 #include "SkPDFFont.h"
 
-////////////////////////////////////////////////////////////////////////////////
-
 SkPDFCanon::~SkPDFCanon() {}
+SkPDFCanon::SkPDFCanon() {}
 
-////////////////////////////////////////////////////////////////////////////////
 
-template <typename T>
-sk_sp<SkPDFObject> find_shader(const SkTArray<T>& records,
-                               const SkPDFShader::State& state) {
-    for (const T& record : records) {
-        if (record.fShaderState == state) {
-            return record.fShaderObject;
-        }
-    }
-    return nullptr;
-}
-
-sk_sp<SkPDFObject> SkPDFCanon::findFunctionShader(
-        const SkPDFShader::State& state) const {
-    return find_shader(fFunctionShaderRecords, state);
-}
-void SkPDFCanon::addFunctionShader(sk_sp<SkPDFObject> pdfShader,
-                                   SkPDFShader::State state) {
-    fFunctionShaderRecords.emplace_back(ShaderRec{std::move(state), std::move(pdfShader)});
-}
-
-sk_sp<SkPDFObject> SkPDFCanon::findAlphaShader(
-        const SkPDFShader::State& state) const {
-    return find_shader(fAlphaShaderRecords, state);
-}
-void SkPDFCanon::addAlphaShader(sk_sp<SkPDFObject> pdfShader,
-                                SkPDFShader::State state) {
-    fAlphaShaderRecords.emplace_back(ShaderRec{std::move(state), std::move(pdfShader)});
-}
-
-sk_sp<SkPDFObject> SkPDFCanon::findImageShader(
-        const SkPDFShader::State& state) const {
-    return find_shader(fImageShaderRecords, state);
-}
-
-void SkPDFCanon::addImageShader(sk_sp<SkPDFObject> pdfShader,
-                                SkPDFShader::State state) {
-    fImageShaderRecords.emplace_back(ShaderRec{std::move(state), std::move(pdfShader)});
-}
diff --git a/src/pdf/SkPDFCanon.h b/src/pdf/SkPDFCanon.h
index d876443..1085387 100644
--- a/src/pdf/SkPDFCanon.h
+++ b/src/pdf/SkPDFCanon.h
@@ -9,6 +9,7 @@
 
 #include "SkPDFGraphicState.h"
 #include "SkPDFShader.h"
+#include "SkPDFGradientShader.h"
 #include "SkPixelSerializer.h"
 #include "SkTDArray.h"
 #include "SkTHash.h"
@@ -21,18 +22,17 @@
  *  The SkPDFCanon canonicalizes objects across PDF pages
  *  (SkPDFDevices) and across draw calls.
  */
-class SkPDFCanon : SkNoncopyable {
+class SkPDFCanon {
 public:
     ~SkPDFCanon();
+    SkPDFCanon();
+    SkPDFCanon(const SkPDFCanon&) = delete;
+    SkPDFCanon& operator=(const SkPDFCanon&) = delete;
 
-    sk_sp<SkPDFObject> findFunctionShader(const SkPDFShader::State&) const;
-    void addFunctionShader(sk_sp<SkPDFObject>, SkPDFShader::State);
+    SkTHashMap<SkPDFShader::State, sk_sp<SkPDFObject>> fImageShaderMap;
 
-    sk_sp<SkPDFObject> findAlphaShader(const SkPDFShader::State&) const;
-    void addAlphaShader(sk_sp<SkPDFObject>, SkPDFShader::State);
-
-    sk_sp<SkPDFObject> findImageShader(const SkPDFShader::State&) const;
-    void addImageShader(sk_sp<SkPDFObject>, SkPDFShader::State);
+    SkPDFGradientShader::HashMap fAlphaGradientMap;
+    SkPDFGradientShader::HashMap fOpaqueGradientMap;
 
     SkTHashMap<SkBitmapKey, sk_sp<SkPDFObject>> fPDFBitmapMap;
 
@@ -47,14 +47,5 @@
     sk_sp<SkPDFStream> fInvertFunction;
     sk_sp<SkPDFDict> fNoSmaskGraphicState;
     sk_sp<SkPDFArray> fRangeObject;
-
-private:
-    struct ShaderRec {
-        SkPDFShader::State fShaderState;
-        sk_sp<SkPDFObject> fShaderObject;
-    };
-    SkTArray<ShaderRec> fFunctionShaderRecords;
-    SkTArray<ShaderRec> fAlphaShaderRecords;
-    SkTArray<ShaderRec> fImageShaderRecords;
 };
 #endif  // SkPDFCanon_DEFINED
diff --git a/src/pdf/SkPDFGradientShader.cpp b/src/pdf/SkPDFGradientShader.cpp
new file mode 100644
index 0000000..0287678
--- /dev/null
+++ b/src/pdf/SkPDFGradientShader.cpp
@@ -0,0 +1,955 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPDFGradientShader.h"
+
+#include "SkOpts.h"
+#include "SkPDFDocument.h"
+#include "SkPDFFormXObject.h"
+#include "SkPDFResourceDict.h"
+#include "SkPDFUtils.h"
+
+static uint32_t hash(const SkShader::GradientInfo& v) {
+    uint32_t buffer[] = {
+        (uint32_t)v.fColorCount,
+        SkOpts::hash(v.fColors, v.fColorCount * sizeof(SkColor)),
+        SkOpts::hash(v.fColorOffsets, v.fColorCount * sizeof(SkScalar)),
+        SkOpts::hash(v.fPoint, 2 * sizeof(SkPoint)),
+        SkOpts::hash(v.fRadius, 2 * sizeof(SkScalar)),
+        (uint32_t)v.fTileMode,
+        v.fGradientFlags,
+    };
+    return SkOpts::hash(buffer, sizeof(buffer));
+}
+
+static uint32_t hash(const SkPDFGradientShader::Key& k) {
+    uint32_t buffer[] = {
+        (uint32_t)k.fType,
+        hash(k.fInfo),
+        SkOpts::hash(&k.fCanvasTransform, sizeof(SkMatrix)),
+        SkOpts::hash(&k.fShaderTransform, sizeof(SkMatrix)),
+        SkOpts::hash(&k.fBBox, sizeof(SkIRect))
+    };
+    return SkOpts::hash(buffer, sizeof(buffer));
+}
+
+static void unit_to_points_matrix(const SkPoint pts[2], SkMatrix* matrix) {
+    SkVector    vec = pts[1] - pts[0];
+    SkScalar    mag = vec.length();
+    SkScalar    inv = mag ? SkScalarInvert(mag) : 0;
+
+    vec.scale(inv);
+    matrix->setSinCos(vec.fY, vec.fX);
+    matrix->preScale(mag, mag);
+    matrix->postTranslate(pts[0].fX, pts[0].fY);
+}
+
+static const int kColorComponents = 3;
+typedef uint8_t ColorTuple[kColorComponents];
+
+/* Assumes t + startOffset is on the stack and does a linear interpolation on t
+   between startOffset and endOffset from prevColor to curColor (for each color
+   component), leaving the result in component order on the stack. It assumes
+   there are always 3 components per color.
+   @param range                  endOffset - startOffset
+   @param curColor[components]   The current color components.
+   @param prevColor[components]  The previous color components.
+   @param result                 The result ps function.
+ */
+static void interpolate_color_code(SkScalar range, const ColorTuple& curColor,
+                                   const ColorTuple& prevColor,
+                                   SkDynamicMemoryWStream* result) {
+    SkASSERT(range != SkIntToScalar(0));
+
+    // Figure out how to scale each color component.
+    SkScalar multiplier[kColorComponents];
+    for (int i = 0; i < kColorComponents; i++) {
+        static const SkScalar kColorScale = SkScalarInvert(255);
+        multiplier[i] = kColorScale * (curColor[i] - prevColor[i]) / range;
+    }
+
+    // Calculate when we no longer need to keep a copy of the input parameter t.
+    // If the last component to use t is i, then dupInput[0..i - 1] = true
+    // and dupInput[i .. components] = false.
+    bool dupInput[kColorComponents];
+    dupInput[kColorComponents - 1] = false;
+    for (int i = kColorComponents - 2; i >= 0; i--) {
+        dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0;
+    }
+
+    if (!dupInput[0] && multiplier[0] == 0) {
+        result->writeText("pop ");
+    }
+
+    for (int i = 0; i < kColorComponents; i++) {
+        // If the next components needs t and this component will consume a
+        // copy, make another copy.
+        if (dupInput[i] && multiplier[i] != 0) {
+            result->writeText("dup ");
+        }
+
+        if (multiplier[i] == 0) {
+            SkPDFUtils::AppendColorComponent(prevColor[i], result);
+            result->writeText(" ");
+        } else {
+            if (multiplier[i] != 1) {
+                SkPDFUtils::AppendScalar(multiplier[i], result);
+                result->writeText(" mul ");
+            }
+            if (prevColor[i] != 0) {
+                SkPDFUtils::AppendColorComponent(prevColor[i], result);
+                result->writeText(" add ");
+            }
+        }
+
+        if (dupInput[i]) {
+            result->writeText("exch\n");
+        }
+    }
+}
+
+/* Generate Type 4 function code to map t=[0,1) to the passed gradient,
+   clamping at the edges of the range.  The generated code will be of the form:
+       if (t < 0) {
+           return colorData[0][r,g,b];
+       } else {
+           if (t < info.fColorOffsets[1]) {
+               return linearinterpolation(colorData[0][r,g,b],
+                                          colorData[1][r,g,b]);
+           } else {
+               if (t < info.fColorOffsets[2]) {
+                   return linearinterpolation(colorData[1][r,g,b],
+                                              colorData[2][r,g,b]);
+               } else {
+
+                ...    } else {
+                           return colorData[info.fColorCount - 1][r,g,b];
+                       }
+                ...
+           }
+       }
+ */
+static void gradient_function_code(const SkShader::GradientInfo& info,
+                                 SkDynamicMemoryWStream* result) {
+    /* We want to linearly interpolate from the previous color to the next.
+       Scale the colors from 0..255 to 0..1 and determine the multipliers
+       for interpolation.
+       C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}.
+     */
+
+    SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount);
+    ColorTuple *colorData = colorDataAlloc.get();
+    for (int i = 0; i < info.fColorCount; i++) {
+        colorData[i][0] = SkColorGetR(info.fColors[i]);
+        colorData[i][1] = SkColorGetG(info.fColors[i]);
+        colorData[i][2] = SkColorGetB(info.fColors[i]);
+    }
+
+    // Clamp the initial color.
+    result->writeText("dup 0 le {pop ");
+    SkPDFUtils::AppendColorComponent(colorData[0][0], result);
+    result->writeText(" ");
+    SkPDFUtils::AppendColorComponent(colorData[0][1], result);
+    result->writeText(" ");
+    SkPDFUtils::AppendColorComponent(colorData[0][2], result);
+    result->writeText(" }\n");
+
+    // The gradient colors.
+    int gradients = 0;
+    for (int i = 1 ; i < info.fColorCount; i++) {
+        if (info.fColorOffsets[i] == info.fColorOffsets[i - 1]) {
+            continue;
+        }
+        gradients++;
+
+        result->writeText("{dup ");
+        SkPDFUtils::AppendScalar(info.fColorOffsets[i], result);
+        result->writeText(" le {");
+        if (info.fColorOffsets[i - 1] != 0) {
+            SkPDFUtils::AppendScalar(info.fColorOffsets[i - 1], result);
+            result->writeText(" sub\n");
+        }
+
+        interpolate_color_code(info.fColorOffsets[i] - info.fColorOffsets[i - 1],
+                             colorData[i], colorData[i - 1], result);
+        result->writeText("}\n");
+    }
+
+    // Clamp the final color.
+    result->writeText("{pop ");
+    SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][0], result);
+    result->writeText(" ");
+    SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][1], result);
+    result->writeText(" ");
+    SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][2], result);
+
+    for (int i = 0 ; i < gradients + 1; i++) {
+        result->writeText("} ifelse\n");
+    }
+}
+
+static sk_sp<SkPDFDict> createInterpolationFunction(const ColorTuple& color1,
+                                                    const ColorTuple& color2) {
+    auto retval = sk_make_sp<SkPDFDict>();
+
+    auto c0 = sk_make_sp<SkPDFArray>();
+    c0->appendColorComponent(color1[0]);
+    c0->appendColorComponent(color1[1]);
+    c0->appendColorComponent(color1[2]);
+    retval->insertObject("C0", std::move(c0));
+
+    auto c1 = sk_make_sp<SkPDFArray>();
+    c1->appendColorComponent(color2[0]);
+    c1->appendColorComponent(color2[1]);
+    c1->appendColorComponent(color2[2]);
+    retval->insertObject("C1", std::move(c1));
+
+    auto domain = sk_make_sp<SkPDFArray>();
+    domain->appendScalar(0);
+    domain->appendScalar(1.0f);
+    retval->insertObject("Domain", std::move(domain));
+
+    retval->insertInt("FunctionType", 2);
+    retval->insertScalar("N", 1.0f);
+
+    return retval;
+}
+
+static sk_sp<SkPDFDict> gradientStitchCode(const SkShader::GradientInfo& info) {
+    auto retval = sk_make_sp<SkPDFDict>();
+
+    // normalize color stops
+    int colorCount = info.fColorCount;
+    SkTDArray<SkColor>    colors(info.fColors, colorCount);
+    SkTDArray<SkScalar>   colorOffsets(info.fColorOffsets, colorCount);
+
+    int i = 1;
+    while (i < colorCount - 1) {
+        // ensure stops are in order
+        if (colorOffsets[i - 1] > colorOffsets[i]) {
+            colorOffsets[i] = colorOffsets[i - 1];
+        }
+
+        // remove points that are between 2 coincident points
+        if ((colorOffsets[i - 1] == colorOffsets[i]) && (colorOffsets[i] == colorOffsets[i + 1])) {
+            colorCount -= 1;
+            colors.remove(i);
+            colorOffsets.remove(i);
+        } else {
+            i++;
+        }
+    }
+    // find coincident points and slightly move them over
+    for (i = 1; i < colorCount - 1; i++) {
+        if (colorOffsets[i - 1] == colorOffsets[i]) {
+            colorOffsets[i] += 0.00001f;
+        }
+    }
+    // check if last 2 stops coincide
+    if (colorOffsets[i - 1] == colorOffsets[i]) {
+        colorOffsets[i - 1] -= 0.00001f;
+    }
+
+    SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(colorCount);
+    ColorTuple *colorData = colorDataAlloc.get();
+    for (int i = 0; i < colorCount; i++) {
+        colorData[i][0] = SkColorGetR(colors[i]);
+        colorData[i][1] = SkColorGetG(colors[i]);
+        colorData[i][2] = SkColorGetB(colors[i]);
+    }
+
+    // no need for a stitch function if there are only 2 stops.
+    if (colorCount == 2)
+        return createInterpolationFunction(colorData[0], colorData[1]);
+
+    auto encode = sk_make_sp<SkPDFArray>();
+    auto bounds = sk_make_sp<SkPDFArray>();
+    auto functions = sk_make_sp<SkPDFArray>();
+
+    auto domain = sk_make_sp<SkPDFArray>();
+    domain->appendScalar(0);
+    domain->appendScalar(1.0f);
+    retval->insertObject("Domain", std::move(domain));
+    retval->insertInt("FunctionType", 3);
+
+    for (int i = 1; i < colorCount; i++) {
+        if (i > 1) {
+            bounds->appendScalar(colorOffsets[i-1]);
+        }
+
+        encode->appendScalar(0);
+        encode->appendScalar(1.0f);
+
+        functions->appendObject(createInterpolationFunction(colorData[i-1], colorData[i]));
+    }
+
+    retval->insertObject("Encode", std::move(encode));
+    retval->insertObject("Bounds", std::move(bounds));
+    retval->insertObject("Functions", std::move(functions));
+
+    return retval;
+}
+
+/* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */
+static void tileModeCode(SkShader::TileMode mode,
+                         SkDynamicMemoryWStream* result) {
+    if (mode == SkShader::kRepeat_TileMode) {
+        result->writeText("dup truncate sub\n");  // Get the fractional part.
+        result->writeText("dup 0 le {1 add} if\n");  // Map (-1,0) => (0,1)
+        return;
+    }
+
+    if (mode == SkShader::kMirror_TileMode) {
+        // Map t mod 2 into [0, 1, 1, 0].
+        //               Code                     Stack
+        result->writeText("abs "                 // Map negative to positive.
+                          "dup "                 // t.s t.s
+                          "truncate "            // t.s t
+                          "dup "                 // t.s t t
+                          "cvi "                 // t.s t T
+                          "2 mod "               // t.s t (i mod 2)
+                          "1 eq "                // t.s t true|false
+                          "3 1 roll "            // true|false t.s t
+                          "sub "                 // true|false 0.s
+                          "exch "                // 0.s true|false
+                          "{1 exch sub} if\n");  // 1 - 0.s|0.s
+    }
+}
+
+/**
+ *  Returns PS function code that applies inverse perspective
+ *  to a x, y point.
+ *  The function assumes that the stack has at least two elements,
+ *  and that the top 2 elements are numeric values.
+ *  After executing this code on a PS stack, the last 2 elements are updated
+ *  while the rest of the stack is preserved intact.
+ *  inversePerspectiveMatrix is the inverse perspective matrix.
+ */
+static void apply_perspective_to_coordinates(const SkMatrix& inversePerspectiveMatrix,
+                                             SkDynamicMemoryWStream* code) {
+    if (!inversePerspectiveMatrix.hasPerspective()) {
+        return;
+    }
+
+    // Perspective matrix should be:
+    // 1   0  0
+    // 0   1  0
+    // p0 p1 p2
+
+    const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0];
+    const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1];
+    const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2];
+
+    // y = y / (p2 + p0 x + p1 y)
+    // x = x / (p2 + p0 x + p1 y)
+
+    // Input on stack: x y
+    code->writeText(" dup ");             // x y y
+    SkPDFUtils::AppendScalar(p1, code);   // x y y p1
+    code->writeText(" mul "               // x y y*p1
+                    " 2 index ");         // x y y*p1 x
+    SkPDFUtils::AppendScalar(p0, code);   // x y y p1 x p0
+    code->writeText(" mul ");             // x y y*p1 x*p0
+    SkPDFUtils::AppendScalar(p2, code);   // x y y p1 x*p0 p2
+    code->writeText(" add "               // x y y*p1 x*p0+p2
+                    "add "                // x y y*p1+x*p0+p2
+                    "3 1 roll "           // y*p1+x*p0+p2 x y
+                    "2 index "            // z x y y*p1+x*p0+p2
+                    "div "                // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2)
+                    "3 1 roll "           // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x
+                    "exch "               // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2
+                    "div "                // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2)
+                    "exch\n");            // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2)
+}
+
+static void linearCode(const SkShader::GradientInfo& info,
+                       const SkMatrix& perspectiveRemover,
+                       SkDynamicMemoryWStream* function) {
+    function->writeText("{");
+
+    apply_perspective_to_coordinates(perspectiveRemover, function);
+
+    function->writeText("pop\n");  // Just ditch the y value.
+    tileModeCode(info.fTileMode, function);
+    gradient_function_code(info, function);
+    function->writeText("}");
+}
+
+static void radialCode(const SkShader::GradientInfo& info,
+                       const SkMatrix& perspectiveRemover,
+                       SkDynamicMemoryWStream* function) {
+    function->writeText("{");
+
+    apply_perspective_to_coordinates(perspectiveRemover, function);
+
+    // Find the distance from the origin.
+    function->writeText("dup "      // x y y
+                    "mul "      // x y^2
+                    "exch "     // y^2 x
+                    "dup "      // y^2 x x
+                    "mul "      // y^2 x^2
+                    "add "      // y^2+x^2
+                    "sqrt\n");  // sqrt(y^2+x^2)
+
+    tileModeCode(info.fTileMode, function);
+    gradient_function_code(info, function);
+    function->writeText("}");
+}
+
+/* Conical gradient shader, based on the Canvas spec for radial gradients
+   See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient
+ */
+static void twoPointConicalCode(const SkShader::GradientInfo& info,
+                                const SkMatrix& perspectiveRemover,
+                                SkDynamicMemoryWStream* function) {
+    SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX;
+    SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY;
+    SkScalar r0 = info.fRadius[0];
+    SkScalar dr = info.fRadius[1] - info.fRadius[0];
+    SkScalar a = dx * dx + dy * dy - dr * dr;
+
+    // First compute t, if the pixel falls outside the cone, then we'll end
+    // with 'false' on the stack, otherwise we'll push 'true' with t below it
+
+    // We start with a stack of (x y), copy it and then consume one copy in
+    // order to calculate b and the other to calculate c.
+    function->writeText("{");
+
+    apply_perspective_to_coordinates(perspectiveRemover, function);
+
+    function->writeText("2 copy ");
+
+    // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr).
+    SkPDFUtils::AppendScalar(dy, function);
+    function->writeText(" mul exch ");
+    SkPDFUtils::AppendScalar(dx, function);
+    function->writeText(" mul add ");
+    SkPDFUtils::AppendScalar(r0 * dr, function);
+    function->writeText(" add -2 mul dup dup mul\n");
+
+    // c = x^2 + y^2 + radius0^2
+    function->writeText("4 2 roll dup mul exch dup mul add ");
+    SkPDFUtils::AppendScalar(r0 * r0, function);
+    function->writeText(" sub dup 4 1 roll\n");
+
+    // Contents of the stack at this point: c, b, b^2, c
+
+    // if a = 0, then we collapse to a simpler linear case
+    if (a == 0) {
+
+        // t = -c/b
+        function->writeText("pop pop div neg dup ");
+
+        // compute radius(t)
+        SkPDFUtils::AppendScalar(dr, function);
+        function->writeText(" mul ");
+        SkPDFUtils::AppendScalar(r0, function);
+        function->writeText(" add\n");
+
+        // if r(t) < 0, then it's outside the cone
+        function->writeText("0 lt {pop false} {true} ifelse\n");
+
+    } else {
+
+        // quadratic case: the Canvas spec wants the largest
+        // root t for which radius(t) > 0
+
+        // compute the discriminant (b^2 - 4ac)
+        SkPDFUtils::AppendScalar(a * 4, function);
+        function->writeText(" mul sub dup\n");
+
+        // if d >= 0, proceed
+        function->writeText("0 ge {\n");
+
+        // an intermediate value we'll use to compute the roots:
+        // q = -0.5 * (b +/- sqrt(d))
+        function->writeText("sqrt exch dup 0 lt {exch -1 mul} if");
+        function->writeText(" add -0.5 mul dup\n");
+
+        // first root = q / a
+        SkPDFUtils::AppendScalar(a, function);
+        function->writeText(" div\n");
+
+        // second root = c / q
+        function->writeText("3 1 roll div\n");
+
+        // put the larger root on top of the stack
+        function->writeText("2 copy gt {exch} if\n");
+
+        // compute radius(t) for larger root
+        function->writeText("dup ");
+        SkPDFUtils::AppendScalar(dr, function);
+        function->writeText(" mul ");
+        SkPDFUtils::AppendScalar(r0, function);
+        function->writeText(" add\n");
+
+        // if r(t) > 0, we have our t, pop off the smaller root and we're done
+        function->writeText(" 0 gt {exch pop true}\n");
+
+        // otherwise, throw out the larger one and try the smaller root
+        function->writeText("{pop dup\n");
+        SkPDFUtils::AppendScalar(dr, function);
+        function->writeText(" mul ");
+        SkPDFUtils::AppendScalar(r0, function);
+        function->writeText(" add\n");
+
+        // if r(t) < 0, push false, otherwise the smaller root is our t
+        function->writeText("0 le {pop false} {true} ifelse\n");
+        function->writeText("} ifelse\n");
+
+        // d < 0, clear the stack and push false
+        function->writeText("} {pop pop pop false} ifelse\n");
+    }
+
+    // if the pixel is in the cone, proceed to compute a color
+    function->writeText("{");
+    tileModeCode(info.fTileMode, function);
+    gradient_function_code(info, function);
+
+    // otherwise, just write black
+    function->writeText("} {0 0 0} ifelse }");
+}
+
+static void sweepCode(const SkShader::GradientInfo& info,
+                          const SkMatrix& perspectiveRemover,
+                          SkDynamicMemoryWStream* function) {
+    function->writeText("{exch atan 360 div\n");
+    tileModeCode(info.fTileMode, function);
+    gradient_function_code(info, function);
+    function->writeText("}");
+}
+
+
+// catch cases where the inner just touches the outer circle
+// and make the inner circle just inside the outer one to match raster
+static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) {
+    // detect touching circles
+    SkScalar distance = SkPoint::Distance(p1, p2);
+    SkScalar subtractRadii = fabs(r1 - r2);
+    if (fabs(distance - subtractRadii) < 0.002f) {
+        if (r1 > r2) {
+            r1 += 0.002f;
+        } else {
+            r2 += 0.002f;
+        }
+    }
+}
+
+// Finds affine and persp such that in = affine * persp.
+// but it returns the inverse of perspective matrix.
+static bool split_perspective(const SkMatrix in, SkMatrix* affine,
+                              SkMatrix* perspectiveInverse) {
+    const SkScalar p2 = in[SkMatrix::kMPersp2];
+
+    if (SkScalarNearlyZero(p2)) {
+        return false;
+    }
+
+    const SkScalar zero = SkIntToScalar(0);
+    const SkScalar one = SkIntToScalar(1);
+
+    const SkScalar sx = in[SkMatrix::kMScaleX];
+    const SkScalar kx = in[SkMatrix::kMSkewX];
+    const SkScalar tx = in[SkMatrix::kMTransX];
+    const SkScalar ky = in[SkMatrix::kMSkewY];
+    const SkScalar sy = in[SkMatrix::kMScaleY];
+    const SkScalar ty = in[SkMatrix::kMTransY];
+    const SkScalar p0 = in[SkMatrix::kMPersp0];
+    const SkScalar p1 = in[SkMatrix::kMPersp1];
+
+    // Perspective matrix would be:
+    // 1  0  0
+    // 0  1  0
+    // p0 p1 p2
+    // But we need the inverse of persp.
+    perspectiveInverse->setAll(one,          zero,       zero,
+                               zero,         one,        zero,
+                               -p0/p2,     -p1/p2,     1/p2);
+
+    affine->setAll(sx - p0 * tx / p2,       kx - p1 * tx / p2,      tx / p2,
+                   ky - p0 * ty / p2,       sy - p1 * ty / p2,      ty / p2,
+                   zero,                    zero,                   one);
+
+    return true;
+}
+
+static sk_sp<SkPDFArray> make_range_object() {
+    auto range = sk_make_sp<SkPDFArray>();
+    range->reserve(6);
+    range->appendInt(0);
+    range->appendInt(1);
+    range->appendInt(0);
+    range->appendInt(1);
+    range->appendInt(0);
+    range->appendInt(1);
+    return range;
+}
+
+static sk_sp<SkPDFStream> make_ps_function(
+        std::unique_ptr<SkStreamAsset> psCode,
+        sk_sp<SkPDFArray> domain,
+        sk_sp<SkPDFObject> range) {
+    auto result = sk_make_sp<SkPDFStream>(std::move(psCode));
+    result->dict()->insertInt("FunctionType", 4);
+    result->dict()->insertObject("Domain", std::move(domain));
+    result->dict()->insertObject("Range", std::move(range));
+    return result;
+}
+
+
+static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon,
+                                             const SkPDFGradientShader::Key& state) {
+    SkPoint transformPoints[2];
+    const SkShader::GradientInfo& info = state.fInfo;
+    SkMatrix finalMatrix = state.fCanvasTransform;
+    finalMatrix.preConcat(state.fShaderTransform);
+
+    bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType ||
+                              state.fType == SkShader::kRadial_GradientType ||
+                              state.fType == SkShader::kConical_GradientType) &&
+                             info.fTileMode == SkShader::kClamp_TileMode &&
+                             !finalMatrix.hasPerspective();
+
+    auto domain = sk_make_sp<SkPDFArray>();
+
+    int32_t shadingType = 1;
+    auto pdfShader = sk_make_sp<SkPDFDict>();
+    // The two point radial gradient further references
+    // state.fInfo
+    // in translating from x, y coordinates to the t parameter. So, we have
+    // to transform the points and radii according to the calculated matrix.
+    if (doStitchFunctions) {
+        pdfShader->insertObject("Function", gradientStitchCode(info));
+        shadingType = (state.fType == SkShader::kLinear_GradientType) ? 2 : 3;
+
+        auto extend = sk_make_sp<SkPDFArray>();
+        extend->reserve(2);
+        extend->appendBool(true);
+        extend->appendBool(true);
+        pdfShader->insertObject("Extend", std::move(extend));
+
+        auto coords = sk_make_sp<SkPDFArray>();
+        if (state.fType == SkShader::kConical_GradientType) {
+            coords->reserve(6);
+            SkScalar r1 = info.fRadius[0];
+            SkScalar r2 = info.fRadius[1];
+            SkPoint pt1 = info.fPoint[0];
+            SkPoint pt2 = info.fPoint[1];
+            FixUpRadius(pt1, r1, pt2, r2);
+
+            coords->appendScalar(pt1.fX);
+            coords->appendScalar(pt1.fY);
+            coords->appendScalar(r1);
+
+            coords->appendScalar(pt2.fX);
+            coords->appendScalar(pt2.fY);
+            coords->appendScalar(r2);
+        } else if (state.fType == SkShader::kRadial_GradientType) {
+            coords->reserve(6);
+            const SkPoint& pt1 = info.fPoint[0];
+
+            coords->appendScalar(pt1.fX);
+            coords->appendScalar(pt1.fY);
+            coords->appendScalar(0);
+
+            coords->appendScalar(pt1.fX);
+            coords->appendScalar(pt1.fY);
+            coords->appendScalar(info.fRadius[0]);
+        } else {
+            coords->reserve(4);
+            const SkPoint& pt1 = info.fPoint[0];
+            const SkPoint& pt2 = info.fPoint[1];
+
+            coords->appendScalar(pt1.fX);
+            coords->appendScalar(pt1.fY);
+
+            coords->appendScalar(pt2.fX);
+            coords->appendScalar(pt2.fY);
+        }
+
+        pdfShader->insertObject("Coords", std::move(coords));
+    } else {
+        // Depending on the type of the gradient, we want to transform the
+        // coordinate space in different ways.
+        transformPoints[0] = info.fPoint[0];
+        transformPoints[1] = info.fPoint[1];
+        switch (state.fType) {
+            case SkShader::kLinear_GradientType:
+                break;
+            case SkShader::kRadial_GradientType:
+                transformPoints[1] = transformPoints[0];
+                transformPoints[1].fX += info.fRadius[0];
+                break;
+            case SkShader::kConical_GradientType: {
+                transformPoints[1] = transformPoints[0];
+                transformPoints[1].fX += SK_Scalar1;
+                break;
+            }
+            case SkShader::kSweep_GradientType:
+                transformPoints[1] = transformPoints[0];
+                transformPoints[1].fX += SK_Scalar1;
+                break;
+            case SkShader::kColor_GradientType:
+            case SkShader::kNone_GradientType:
+            default:
+                return nullptr;
+        }
+
+        // Move any scaling (assuming a unit gradient) or translation
+        // (and rotation for linear gradient), of the final gradient from
+        // info.fPoints to the matrix (updating bbox appropriately).  Now
+        // the gradient can be drawn on on the unit segment.
+        SkMatrix mapperMatrix;
+        unit_to_points_matrix(transformPoints, &mapperMatrix);
+
+        finalMatrix.preConcat(mapperMatrix);
+
+        // Preserves as much as posible in the final matrix, and only removes
+        // the perspective. The inverse of the perspective is stored in
+        // perspectiveInverseOnly matrix and has 3 useful numbers
+        // (p0, p1, p2), while everything else is either 0 or 1.
+        // In this way the shader will handle it eficiently, with minimal code.
+        SkMatrix perspectiveInverseOnly = SkMatrix::I();
+        if (finalMatrix.hasPerspective()) {
+            if (!split_perspective(finalMatrix,
+                                   &finalMatrix, &perspectiveInverseOnly)) {
+                return nullptr;
+            }
+        }
+
+        SkRect bbox;
+        bbox.set(state.fBBox);
+        if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &bbox)) {
+            return nullptr;
+        }
+        domain->reserve(4);
+        domain->appendScalar(bbox.fLeft);
+        domain->appendScalar(bbox.fRight);
+        domain->appendScalar(bbox.fTop);
+        domain->appendScalar(bbox.fBottom);
+
+        SkDynamicMemoryWStream functionCode;
+
+        SkShader::GradientInfo infoCopy = info;
+
+        if (state.fType == SkShader::kConical_GradientType) {
+            SkMatrix inverseMapperMatrix;
+            if (!mapperMatrix.invert(&inverseMapperMatrix)) {
+                return nullptr;
+            }
+            inverseMapperMatrix.mapPoints(infoCopy.fPoint, 2);
+            infoCopy.fRadius[0] = inverseMapperMatrix.mapRadius(info.fRadius[0]);
+            infoCopy.fRadius[1] = inverseMapperMatrix.mapRadius(info.fRadius[1]);
+        }
+        switch (state.fType) {
+            case SkShader::kLinear_GradientType:
+                linearCode(infoCopy, perspectiveInverseOnly, &functionCode);
+                break;
+            case SkShader::kRadial_GradientType:
+                radialCode(infoCopy, perspectiveInverseOnly, &functionCode);
+                break;
+            case SkShader::kConical_GradientType:
+                twoPointConicalCode(infoCopy, perspectiveInverseOnly, &functionCode);
+                break;
+            case SkShader::kSweep_GradientType:
+                sweepCode(infoCopy, perspectiveInverseOnly, &functionCode);
+                break;
+            default:
+                SkASSERT(false);
+        }
+        pdfShader->insertObject("Domain", domain);
+
+        sk_sp<SkPDFArray>& rangeObject = canon->fRangeObject;
+        if (!rangeObject) {
+            rangeObject = make_range_object();
+        }
+        pdfShader->insertObjRef("Function",
+                                make_ps_function(functionCode.detachAsStream(), std::move(domain),
+                                                 rangeObject));
+    }
+
+    pdfShader->insertInt("ShadingType", shadingType);
+    pdfShader->insertName("ColorSpace", "DeviceRGB");
+
+    auto pdfFunctionShader = sk_make_sp<SkPDFDict>("Pattern");
+    pdfFunctionShader->insertInt("PatternType", 2);
+    pdfFunctionShader->insertObject("Matrix", SkPDFUtils::MatrixToArray(finalMatrix));
+    pdfFunctionShader->insertObject("Shading", std::move(pdfShader));
+
+    return pdfFunctionShader;
+}
+
+
+static sk_sp<SkPDFObject> find_function_shader(SkPDFDocument* doc,
+                                               SkPDFGradientShader::Key key) {
+    SkPDFCanon* canon = doc->canon();
+    if (sk_sp<SkPDFObject>* ptr = canon->fOpaqueGradientMap.find(key)) {
+        return *ptr;
+    }
+    sk_sp<SkPDFObject> pdfShader = make_function_shader(doc->canon(), key);
+    canon->fOpaqueGradientMap.set(std::move(key), pdfShader);
+    return pdfShader;
+}
+
+static sk_sp<SkPDFDict> get_gradient_resource_dict(SkPDFObject* functionShader,
+                                                   SkPDFObject* gState) {
+    SkTDArray<SkPDFObject*> patterns;
+    if (functionShader) {
+        patterns.push(functionShader);
+    }
+    SkTDArray<SkPDFObject*> graphicStates;
+    if (gState) {
+        graphicStates.push(gState);
+    }
+    return SkPDFResourceDict::Make(&graphicStates, &patterns, nullptr, nullptr);
+}
+
+// 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 std::unique_ptr<SkStreamAsset> 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();
+}
+
+static bool gradient_has_alpha(const SkPDFGradientShader::Key& key) {
+    SkASSERT(key.fType != SkShader::kNone_GradientType);
+    for (int i = 0; i < key.fInfo.fColorCount; i++) {
+        if ((SkAlpha)SkColorGetA(key.fInfo.fColors[i]) != SK_AlphaOPAQUE) {
+            return true;
+        }
+    }
+    return false;
+}
+
+// warning: does not set fHash on new key.  (Both callers need to change fields.)
+static SkPDFGradientShader::Key clone_key(const SkPDFGradientShader::Key& k) {
+    SkPDFGradientShader::Key clone = {
+        k.fType,
+        k.fInfo,  // change pointers later.
+        std::unique_ptr<SkColor[]>(new SkColor[k.fInfo.fColorCount]),
+        std::unique_ptr<SkScalar[]>(new SkScalar[k.fInfo.fColorCount]),
+        k.fCanvasTransform,
+        k.fShaderTransform,
+        k.fBBox, 0};
+    clone.fInfo.fColors = clone.fColors.get();
+    clone.fInfo.fColorOffsets = clone.fStops.get();
+    for (int i = 0; i < clone.fInfo.fColorCount; i++) {
+        clone.fInfo.fColorOffsets[i] = k.fInfo.fColorOffsets[i];
+        clone.fInfo.fColors[i] = k.fInfo.fColors[i];
+    }
+    return clone;
+}
+
+static sk_sp<SkPDFObject> create_smask_graphic_state(SkPDFDocument* doc,
+                                                     const SkPDFGradientShader::Key& state) {
+    SkASSERT(state.fType != SkShader::kNone_GradientType);
+    SkPDFGradientShader::Key luminosityState = clone_key(state);
+    for (int i = 0; i < luminosityState.fInfo.fColorCount; i++) {
+        SkAlpha alpha = SkColorGetA(luminosityState.fInfo.fColors[i]);
+        luminosityState.fInfo.fColors[i] = SkColorSetARGB(255, alpha, alpha, alpha);
+    }
+    luminosityState.fHash = hash(luminosityState);
+
+    SkASSERT(!gradient_has_alpha(luminosityState));
+    sk_sp<SkPDFObject> luminosityShader = find_function_shader(doc, std::move(luminosityState));
+    sk_sp<SkPDFDict> resources = get_gradient_resource_dict(luminosityShader.get(), nullptr);
+    SkRect bbox = SkRect::Make(state.fBBox);
+    sk_sp<SkPDFObject> alphaMask = SkPDFMakeFormXObject(create_pattern_fill_content(-1, bbox),
+                                                        SkPDFUtils::RectToArray(bbox),
+                                                        std::move(resources),
+                                                        SkMatrix::I(),
+                                                        "DeviceRGB");
+    return SkPDFGraphicState::GetSMaskGraphicState(
+            std::move(alphaMask), false,
+            SkPDFGraphicState::kLuminosity_SMaskMode, doc->canon());
+}
+
+static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc,
+                                                     const SkPDFGradientShader::Key& state) {
+    SkASSERT(state.fType != SkShader::kNone_GradientType);
+    SkPDFGradientShader::Key opaqueState = clone_key(state);
+    for (int i = 0; i < opaqueState.fInfo.fColorCount; i++) {
+        opaqueState.fInfo.fColors[i] = SkColorSetA(opaqueState.fInfo.fColors[i], SK_AlphaOPAQUE);
+    }
+    opaqueState.fHash = hash(opaqueState);
+
+    SkASSERT(!gradient_has_alpha(opaqueState));
+    SkRect bbox = SkRect::Make(state.fBBox);
+    sk_sp<SkPDFObject> colorShader = find_function_shader(doc, std::move(opaqueState));
+    if (!colorShader) {
+        return nullptr;
+    }
+
+    // Create resource dict with alpha graphics state as G0 and
+    // pattern shader as P0, then write content stream.
+    sk_sp<SkPDFObject> alphaGs = create_smask_graphic_state(doc, state);
+
+    sk_sp<SkPDFDict> resourceDict =
+            get_gradient_resource_dict(colorShader.get(), alphaGs.get());
+
+    std::unique_ptr<SkStreamAsset> colorStream(create_pattern_fill_content(0, bbox));
+    auto alphaFunctionShader = sk_make_sp<SkPDFStream>(std::move(colorStream));
+
+    SkPDFUtils::PopulateTilingPatternDict(alphaFunctionShader->dict(), bbox,
+                                 std::move(resourceDict), SkMatrix::I());
+    return alphaFunctionShader;
+}
+
+
+
+static SkPDFGradientShader::Key make_key(const SkShader* shader,
+                                         const SkMatrix& canvasTransform,
+                                         const SkIRect& bbox) {
+    SkPDFGradientShader::Key key = {
+         SkShader::kNone_GradientType,
+         {0, nullptr, nullptr, {{0, 0}, {0, 0}}, {0, 0}, SkShader::kClamp_TileMode, 0},
+         nullptr,
+         nullptr,
+         canvasTransform,
+         SkPDFUtils::GetShaderLocalMatrix(shader),
+         bbox, 0};
+    key.fType = shader->asAGradient(&key.fInfo);
+    SkASSERT(SkShader::kNone_GradientType != key.fType);
+    SkASSERT(key.fInfo.fColorCount > 0);
+    key.fColors.reset(new SkColor[key.fInfo.fColorCount]);
+    key.fStops.reset(new SkScalar[key.fInfo.fColorCount]);
+    key.fInfo.fColors = key.fColors.get();
+    key.fInfo.fColorOffsets = key.fStops.get();
+    (void)shader->asAGradient(&key.fInfo);
+    key.fHash = hash(key);
+    return key;
+}
+
+sk_sp<SkPDFObject> SkPDFGradientShader::Make(SkPDFDocument* doc,
+                                             SkShader* shader,
+                                             const SkMatrix& canvasTransform,
+                                             const SkIRect& bbox) {
+    SkASSERT(shader);
+    SkASSERT(SkShader::kNone_GradientType != shader->asAGradient(nullptr));
+    SkPDFGradientShader::Key key = make_key(shader, canvasTransform, bbox);
+    // TODO(halcanary): measure to see if one hashmap is as fast as two.
+    if (gradient_has_alpha(key)) {
+        SkPDFCanon* canon = doc->canon();
+        if (sk_sp<SkPDFObject>* ptr = canon->fAlphaGradientMap.find(key)) {
+            return *ptr;
+        }
+        sk_sp<SkPDFObject> pdfShader = make_alpha_function_shader(doc, key);
+        canon->fAlphaGradientMap.set(std::move(key), pdfShader);
+        return pdfShader;
+    } else {
+        return find_function_shader(doc, std::move(key));
+    }
+}
+
+
diff --git a/src/pdf/SkPDFGradientShader.h b/src/pdf/SkPDFGradientShader.h
new file mode 100644
index 0000000..0cc059c
--- /dev/null
+++ b/src/pdf/SkPDFGradientShader.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef SkPDFGradientShader_DEFINED
+#define SkPDFGradientShader_DEFINED
+
+#include "SkPDFTypes.h"
+#include "SkPDFUtils.h"
+#include "SkShader.h"
+
+class SkMatrix;
+class SkPDFDocument;
+struct SkIRect;
+
+namespace SkPDFGradientShader {
+
+sk_sp<SkPDFObject> Make(SkPDFDocument* doc,
+                        SkShader* shader,
+                        const SkMatrix& matrix,
+                        const SkIRect& surfaceBBox);
+
+struct Key {
+    SkShader::GradientType fType;
+    SkShader::GradientInfo fInfo;
+    std::unique_ptr<SkColor[]> fColors;
+    std::unique_ptr<SkScalar[]> fStops;
+    SkMatrix fCanvasTransform;
+    SkMatrix fShaderTransform;
+    SkIRect fBBox;
+    uint32_t fHash;
+};
+
+struct KeyHash {
+    uint32_t operator()(const Key& k) const { return k.fHash; }
+};
+
+using HashMap = SkTHashMap<Key, sk_sp<SkPDFObject>, KeyHash>;
+
+inline bool operator==(const SkShader::GradientInfo& u, const SkShader::GradientInfo& v) {
+    return u.fColorCount    == v.fColorCount
+        && u.fPoint[0]      == v.fPoint[0]
+        && u.fPoint[1]      == v.fPoint[1]
+        && u.fRadius[0]     == v.fRadius[0]
+        && u.fRadius[1]     == v.fRadius[1]
+        && u.fTileMode      == v.fTileMode
+        && u.fGradientFlags == v.fGradientFlags
+        && SkPackedArrayEqual(u.fColors, v.fColors, u.fColorCount)
+        && SkPackedArrayEqual(u.fColorOffsets, v.fColorOffsets, u.fColorCount);
+}
+
+inline bool operator==(const Key& u, const Key& v) {
+    SkASSERT(u.fInfo.fColors       == u.fColors.get());
+    SkASSERT(u.fInfo.fColorOffsets == u.fStops.get());
+    SkASSERT(v.fInfo.fColors       == v.fColors.get());
+    SkASSERT(v.fInfo.fColorOffsets == v.fStops.get());
+    return u.fType            == v.fType
+        && u.fInfo            == v.fInfo
+        && u.fCanvasTransform == v.fCanvasTransform
+        && u.fShaderTransform == v.fShaderTransform
+        && u.fBBox            == v.fBBox;
+}
+inline bool operator!=(const Key& u, const Key& v) { return !(u == v); }
+
+}  // namespace SkPDFGradientShader
+#endif  // SkPDFGradientShader_DEFINED
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index 7369abf..44cc27b 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -13,6 +13,7 @@
 #include "SkPDFDevice.h"
 #include "SkPDFDocument.h"
 #include "SkPDFFormXObject.h"
+#include "SkPDFGradientShader.h"
 #include "SkPDFGraphicState.h"
 #include "SkPDFResourceDict.h"
 #include "SkPDFUtils.h"
@@ -20,936 +21,13 @@
 #include "SkStream.h"
 #include "SkTemplates.h"
 
-static bool inverse_transform_bbox(const SkMatrix& matrix, SkRect* bbox) {
-    SkMatrix inverse;
-    if (!matrix.invert(&inverse)) {
-        return false;
-    }
-    inverse.mapRect(bbox);
-    return true;
-}
 
-static void unitToPointsMatrix(const SkPoint pts[2], SkMatrix* matrix) {
-    SkVector    vec = pts[1] - pts[0];
-    SkScalar    mag = vec.length();
-    SkScalar    inv = mag ? SkScalarInvert(mag) : 0;
-
-    vec.scale(inv);
-    matrix->setSinCos(vec.fY, vec.fX);
-    matrix->preScale(mag, mag);
-    matrix->postTranslate(pts[0].fX, pts[0].fY);
-}
-
-static const int kColorComponents = 3;
-typedef uint8_t ColorTuple[kColorComponents];
-
-/* Assumes t + startOffset is on the stack and does a linear interpolation on t
-   between startOffset and endOffset from prevColor to curColor (for each color
-   component), leaving the result in component order on the stack. It assumes
-   there are always 3 components per color.
-   @param range                  endOffset - startOffset
-   @param curColor[components]   The current color components.
-   @param prevColor[components]  The previous color components.
-   @param result                 The result ps function.
- */
-static void interpolateColorCode(SkScalar range, const ColorTuple& curColor,
-                                 const ColorTuple& prevColor,
-                                 SkDynamicMemoryWStream* result) {
-    SkASSERT(range != SkIntToScalar(0));
-
-    // Figure out how to scale each color component.
-    SkScalar multiplier[kColorComponents];
-    for (int i = 0; i < kColorComponents; i++) {
-        static const SkScalar kColorScale = SkScalarInvert(255);
-        multiplier[i] = kColorScale * (curColor[i] - prevColor[i]) / range;
-    }
-
-    // Calculate when we no longer need to keep a copy of the input parameter t.
-    // If the last component to use t is i, then dupInput[0..i - 1] = true
-    // and dupInput[i .. components] = false.
-    bool dupInput[kColorComponents];
-    dupInput[kColorComponents - 1] = false;
-    for (int i = kColorComponents - 2; i >= 0; i--) {
-        dupInput[i] = dupInput[i + 1] || multiplier[i + 1] != 0;
-    }
-
-    if (!dupInput[0] && multiplier[0] == 0) {
-        result->writeText("pop ");
-    }
-
-    for (int i = 0; i < kColorComponents; i++) {
-        // If the next components needs t and this component will consume a
-        // copy, make another copy.
-        if (dupInput[i] && multiplier[i] != 0) {
-            result->writeText("dup ");
-        }
-
-        if (multiplier[i] == 0) {
-            SkPDFUtils::AppendColorComponent(prevColor[i], result);
-            result->writeText(" ");
-        } else {
-            if (multiplier[i] != 1) {
-                SkPDFUtils::AppendScalar(multiplier[i], result);
-                result->writeText(" mul ");
-            }
-            if (prevColor[i] != 0) {
-                SkPDFUtils::AppendColorComponent(prevColor[i], result);
-                result->writeText(" add ");
-            }
-        }
-
-        if (dupInput[i]) {
-            result->writeText("exch\n");
-        }
-    }
-}
-
-/* Generate Type 4 function code to map t=[0,1) to the passed gradient,
-   clamping at the edges of the range.  The generated code will be of the form:
-       if (t < 0) {
-           return colorData[0][r,g,b];
-       } else {
-           if (t < info.fColorOffsets[1]) {
-               return linearinterpolation(colorData[0][r,g,b],
-                                          colorData[1][r,g,b]);
-           } else {
-               if (t < info.fColorOffsets[2]) {
-                   return linearinterpolation(colorData[1][r,g,b],
-                                              colorData[2][r,g,b]);
-               } else {
-
-                ...    } else {
-                           return colorData[info.fColorCount - 1][r,g,b];
-                       }
-                ...
-           }
-       }
- */
-static void gradientFunctionCode(const SkShader::GradientInfo& info,
-                                 SkDynamicMemoryWStream* result) {
-    /* We want to linearly interpolate from the previous color to the next.
-       Scale the colors from 0..255 to 0..1 and determine the multipliers
-       for interpolation.
-       C{r,g,b}(t, section) = t - offset_(section-1) + t * Multiplier{r,g,b}.
-     */
-
-    SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(info.fColorCount);
-    ColorTuple *colorData = colorDataAlloc.get();
-    for (int i = 0; i < info.fColorCount; i++) {
-        colorData[i][0] = SkColorGetR(info.fColors[i]);
-        colorData[i][1] = SkColorGetG(info.fColors[i]);
-        colorData[i][2] = SkColorGetB(info.fColors[i]);
-    }
-
-    // Clamp the initial color.
-    result->writeText("dup 0 le {pop ");
-    SkPDFUtils::AppendColorComponent(colorData[0][0], result);
-    result->writeText(" ");
-    SkPDFUtils::AppendColorComponent(colorData[0][1], result);
-    result->writeText(" ");
-    SkPDFUtils::AppendColorComponent(colorData[0][2], result);
-    result->writeText(" }\n");
-
-    // The gradient colors.
-    int gradients = 0;
-    for (int i = 1 ; i < info.fColorCount; i++) {
-        if (info.fColorOffsets[i] == info.fColorOffsets[i - 1]) {
-            continue;
-        }
-        gradients++;
-
-        result->writeText("{dup ");
-        SkPDFUtils::AppendScalar(info.fColorOffsets[i], result);
-        result->writeText(" le {");
-        if (info.fColorOffsets[i - 1] != 0) {
-            SkPDFUtils::AppendScalar(info.fColorOffsets[i - 1], result);
-            result->writeText(" sub\n");
-        }
-
-        interpolateColorCode(info.fColorOffsets[i] - info.fColorOffsets[i - 1],
-                             colorData[i], colorData[i - 1], result);
-        result->writeText("}\n");
-    }
-
-    // Clamp the final color.
-    result->writeText("{pop ");
-    SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][0], result);
-    result->writeText(" ");
-    SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][1], result);
-    result->writeText(" ");
-    SkPDFUtils::AppendColorComponent(colorData[info.fColorCount - 1][2], result);
-
-    for (int i = 0 ; i < gradients + 1; i++) {
-        result->writeText("} ifelse\n");
-    }
-}
-
-static sk_sp<SkPDFDict> createInterpolationFunction(const ColorTuple& color1,
-                                                    const ColorTuple& color2) {
-    auto retval = sk_make_sp<SkPDFDict>();
-
-    auto c0 = sk_make_sp<SkPDFArray>();
-    c0->appendColorComponent(color1[0]);
-    c0->appendColorComponent(color1[1]);
-    c0->appendColorComponent(color1[2]);
-    retval->insertObject("C0", std::move(c0));
-
-    auto c1 = sk_make_sp<SkPDFArray>();
-    c1->appendColorComponent(color2[0]);
-    c1->appendColorComponent(color2[1]);
-    c1->appendColorComponent(color2[2]);
-    retval->insertObject("C1", std::move(c1));
-
-    auto domain = sk_make_sp<SkPDFArray>();
-    domain->appendScalar(0);
-    domain->appendScalar(1.0f);
-    retval->insertObject("Domain", std::move(domain));
-
-    retval->insertInt("FunctionType", 2);
-    retval->insertScalar("N", 1.0f);
-
-    return retval;
-}
-
-static sk_sp<SkPDFDict> gradientStitchCode(const SkShader::GradientInfo& info) {
-    auto retval = sk_make_sp<SkPDFDict>();
-
-    // normalize color stops
-    int colorCount = info.fColorCount;
-    SkTDArray<SkColor>    colors(info.fColors, colorCount);
-    SkTDArray<SkScalar>   colorOffsets(info.fColorOffsets, colorCount);
-
-    int i = 1;
-    while (i < colorCount - 1) {
-        // ensure stops are in order
-        if (colorOffsets[i - 1] > colorOffsets[i]) {
-            colorOffsets[i] = colorOffsets[i - 1];
-        }
-
-        // remove points that are between 2 coincident points
-        if ((colorOffsets[i - 1] == colorOffsets[i]) && (colorOffsets[i] == colorOffsets[i + 1])) {
-            colorCount -= 1;
-            colors.remove(i);
-            colorOffsets.remove(i);
-        } else {
-            i++;
-        }
-    }
-    // find coincident points and slightly move them over
-    for (i = 1; i < colorCount - 1; i++) {
-        if (colorOffsets[i - 1] == colorOffsets[i]) {
-            colorOffsets[i] += 0.00001f;
-        }
-    }
-    // check if last 2 stops coincide
-    if (colorOffsets[i - 1] == colorOffsets[i]) {
-        colorOffsets[i - 1] -= 0.00001f;
-    }
-
-    SkAutoSTMalloc<4, ColorTuple> colorDataAlloc(colorCount);
-    ColorTuple *colorData = colorDataAlloc.get();
-    for (int i = 0; i < colorCount; i++) {
-        colorData[i][0] = SkColorGetR(colors[i]);
-        colorData[i][1] = SkColorGetG(colors[i]);
-        colorData[i][2] = SkColorGetB(colors[i]);
-    }
-
-    // no need for a stitch function if there are only 2 stops.
-    if (colorCount == 2)
-        return createInterpolationFunction(colorData[0], colorData[1]);
-
-    auto encode = sk_make_sp<SkPDFArray>();
-    auto bounds = sk_make_sp<SkPDFArray>();
-    auto functions = sk_make_sp<SkPDFArray>();
-
-    auto domain = sk_make_sp<SkPDFArray>();
-    domain->appendScalar(0);
-    domain->appendScalar(1.0f);
-    retval->insertObject("Domain", std::move(domain));
-    retval->insertInt("FunctionType", 3);
-
-    for (int i = 1; i < colorCount; i++) {
-        if (i > 1) {
-            bounds->appendScalar(colorOffsets[i-1]);
-        }
-
-        encode->appendScalar(0);
-        encode->appendScalar(1.0f);
-
-        functions->appendObject(createInterpolationFunction(colorData[i-1], colorData[i]));
-    }
-
-    retval->insertObject("Encode", std::move(encode));
-    retval->insertObject("Bounds", std::move(bounds));
-    retval->insertObject("Functions", std::move(functions));
-
-    return retval;
-}
-
-/* Map a value of t on the stack into [0, 1) for Repeat or Mirror tile mode. */
-static void tileModeCode(SkShader::TileMode mode,
-                         SkDynamicMemoryWStream* result) {
-    if (mode == SkShader::kRepeat_TileMode) {
-        result->writeText("dup truncate sub\n");  // Get the fractional part.
-        result->writeText("dup 0 le {1 add} if\n");  // Map (-1,0) => (0,1)
-        return;
-    }
-
-    if (mode == SkShader::kMirror_TileMode) {
-        // Map t mod 2 into [0, 1, 1, 0].
-        //               Code                     Stack
-        result->writeText("abs "                 // Map negative to positive.
-                          "dup "                 // t.s t.s
-                          "truncate "            // t.s t
-                          "dup "                 // t.s t t
-                          "cvi "                 // t.s t T
-                          "2 mod "               // t.s t (i mod 2)
-                          "1 eq "                // t.s t true|false
-                          "3 1 roll "            // true|false t.s t
-                          "sub "                 // true|false 0.s
-                          "exch "                // 0.s true|false
-                          "{1 exch sub} if\n");  // 1 - 0.s|0.s
-    }
-}
-
-/**
- *  Returns PS function code that applies inverse perspective
- *  to a x, y point.
- *  The function assumes that the stack has at least two elements,
- *  and that the top 2 elements are numeric values.
- *  After executing this code on a PS stack, the last 2 elements are updated
- *  while the rest of the stack is preserved intact.
- *  inversePerspectiveMatrix is the inverse perspective matrix.
- */
-static void apply_perspective_to_coordinates(
-        const SkMatrix& inversePerspectiveMatrix,
-        SkDynamicMemoryWStream* code) {
-    if (!inversePerspectiveMatrix.hasPerspective()) {
-        return;
-    }
-
-    // Perspective matrix should be:
-    // 1   0  0
-    // 0   1  0
-    // p0 p1 p2
-
-    const SkScalar p0 = inversePerspectiveMatrix[SkMatrix::kMPersp0];
-    const SkScalar p1 = inversePerspectiveMatrix[SkMatrix::kMPersp1];
-    const SkScalar p2 = inversePerspectiveMatrix[SkMatrix::kMPersp2];
-
-    // y = y / (p2 + p0 x + p1 y)
-    // x = x / (p2 + p0 x + p1 y)
-
-    // Input on stack: x y
-    code->writeText(" dup ");             // x y y
-    SkPDFUtils::AppendScalar(p1, code);   // x y y p1
-    code->writeText(" mul "               // x y y*p1
-                    " 2 index ");         // x y y*p1 x
-    SkPDFUtils::AppendScalar(p0, code);   // x y y p1 x p0
-    code->writeText(" mul ");             // x y y*p1 x*p0
-    SkPDFUtils::AppendScalar(p2, code);   // x y y p1 x*p0 p2
-    code->writeText(" add "               // x y y*p1 x*p0+p2
-                    "add "                // x y y*p1+x*p0+p2
-                    "3 1 roll "           // y*p1+x*p0+p2 x y
-                    "2 index "            // z x y y*p1+x*p0+p2
-                    "div "                // y*p1+x*p0+p2 x y/(y*p1+x*p0+p2)
-                    "3 1 roll "           // y/(y*p1+x*p0+p2) y*p1+x*p0+p2 x
-                    "exch "               // y/(y*p1+x*p0+p2) x y*p1+x*p0+p2
-                    "div "                // y/(y*p1+x*p0+p2) x/(y*p1+x*p0+p2)
-                    "exch\n");            // x/(y*p1+x*p0+p2) y/(y*p1+x*p0+p2)
-}
-
-static void linearCode(const SkShader::GradientInfo& info,
-                       const SkMatrix& perspectiveRemover,
-                       SkDynamicMemoryWStream* function) {
-    function->writeText("{");
-
-    apply_perspective_to_coordinates(perspectiveRemover, function);
-
-    function->writeText("pop\n");  // Just ditch the y value.
-    tileModeCode(info.fTileMode, function);
-    gradientFunctionCode(info, function);
-    function->writeText("}");
-}
-
-static void radialCode(const SkShader::GradientInfo& info,
-                       const SkMatrix& perspectiveRemover,
-                       SkDynamicMemoryWStream* function) {
-    function->writeText("{");
-
-    apply_perspective_to_coordinates(perspectiveRemover, function);
-
-    // Find the distance from the origin.
-    function->writeText("dup "      // x y y
-                    "mul "      // x y^2
-                    "exch "     // y^2 x
-                    "dup "      // y^2 x x
-                    "mul "      // y^2 x^2
-                    "add "      // y^2+x^2
-                    "sqrt\n");  // sqrt(y^2+x^2)
-
-    tileModeCode(info.fTileMode, function);
-    gradientFunctionCode(info, function);
-    function->writeText("}");
-}
-
-/* Conical gradient shader, based on the Canvas spec for radial gradients
-   See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient
- */
-static void twoPointConicalCode(const SkShader::GradientInfo& info,
-                                const SkMatrix& perspectiveRemover,
-                                SkDynamicMemoryWStream* function) {
-    SkScalar dx = info.fPoint[1].fX - info.fPoint[0].fX;
-    SkScalar dy = info.fPoint[1].fY - info.fPoint[0].fY;
-    SkScalar r0 = info.fRadius[0];
-    SkScalar dr = info.fRadius[1] - info.fRadius[0];
-    SkScalar a = dx * dx + dy * dy - dr * dr;
-
-    // First compute t, if the pixel falls outside the cone, then we'll end
-    // with 'false' on the stack, otherwise we'll push 'true' with t below it
-
-    // We start with a stack of (x y), copy it and then consume one copy in
-    // order to calculate b and the other to calculate c.
-    function->writeText("{");
-
-    apply_perspective_to_coordinates(perspectiveRemover, function);
-
-    function->writeText("2 copy ");
-
-    // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr).
-    SkPDFUtils::AppendScalar(dy, function);
-    function->writeText(" mul exch ");
-    SkPDFUtils::AppendScalar(dx, function);
-    function->writeText(" mul add ");
-    SkPDFUtils::AppendScalar(r0 * dr, function);
-    function->writeText(" add -2 mul dup dup mul\n");
-
-    // c = x^2 + y^2 + radius0^2
-    function->writeText("4 2 roll dup mul exch dup mul add ");
-    SkPDFUtils::AppendScalar(r0 * r0, function);
-    function->writeText(" sub dup 4 1 roll\n");
-
-    // Contents of the stack at this point: c, b, b^2, c
-
-    // if a = 0, then we collapse to a simpler linear case
-    if (a == 0) {
-
-        // t = -c/b
-        function->writeText("pop pop div neg dup ");
-
-        // compute radius(t)
-        SkPDFUtils::AppendScalar(dr, function);
-        function->writeText(" mul ");
-        SkPDFUtils::AppendScalar(r0, function);
-        function->writeText(" add\n");
-
-        // if r(t) < 0, then it's outside the cone
-        function->writeText("0 lt {pop false} {true} ifelse\n");
-
-    } else {
-
-        // quadratic case: the Canvas spec wants the largest
-        // root t for which radius(t) > 0
-
-        // compute the discriminant (b^2 - 4ac)
-        SkPDFUtils::AppendScalar(a * 4, function);
-        function->writeText(" mul sub dup\n");
-
-        // if d >= 0, proceed
-        function->writeText("0 ge {\n");
-
-        // an intermediate value we'll use to compute the roots:
-        // q = -0.5 * (b +/- sqrt(d))
-        function->writeText("sqrt exch dup 0 lt {exch -1 mul} if");
-        function->writeText(" add -0.5 mul dup\n");
-
-        // first root = q / a
-        SkPDFUtils::AppendScalar(a, function);
-        function->writeText(" div\n");
-
-        // second root = c / q
-        function->writeText("3 1 roll div\n");
-
-        // put the larger root on top of the stack
-        function->writeText("2 copy gt {exch} if\n");
-
-        // compute radius(t) for larger root
-        function->writeText("dup ");
-        SkPDFUtils::AppendScalar(dr, function);
-        function->writeText(" mul ");
-        SkPDFUtils::AppendScalar(r0, function);
-        function->writeText(" add\n");
-
-        // if r(t) > 0, we have our t, pop off the smaller root and we're done
-        function->writeText(" 0 gt {exch pop true}\n");
-
-        // otherwise, throw out the larger one and try the smaller root
-        function->writeText("{pop dup\n");
-        SkPDFUtils::AppendScalar(dr, function);
-        function->writeText(" mul ");
-        SkPDFUtils::AppendScalar(r0, function);
-        function->writeText(" add\n");
-
-        // if r(t) < 0, push false, otherwise the smaller root is our t
-        function->writeText("0 le {pop false} {true} ifelse\n");
-        function->writeText("} ifelse\n");
-
-        // d < 0, clear the stack and push false
-        function->writeText("} {pop pop pop false} ifelse\n");
-    }
-
-    // if the pixel is in the cone, proceed to compute a color
-    function->writeText("{");
-    tileModeCode(info.fTileMode, function);
-    gradientFunctionCode(info, function);
-
-    // otherwise, just write black
-    function->writeText("} {0 0 0} ifelse }");
-}
-
-static void sweepCode(const SkShader::GradientInfo& info,
-                          const SkMatrix& perspectiveRemover,
-                          SkDynamicMemoryWStream* function) {
-    function->writeText("{exch atan 360 div\n");
-    tileModeCode(info.fTileMode, function);
-    gradientFunctionCode(info, function);
-    function->writeText("}");
-}
-
-static void drawBitmapMatrix(SkCanvas* canvas, const SkBitmap& bm, const SkMatrix& matrix) {
+static void draw_bitmap_matrix(SkCanvas* canvas, const SkBitmap& bm, const SkMatrix& matrix) {
     SkAutoCanvasRestore acr(canvas, true);
     canvas->concat(matrix);
     canvas->drawBitmap(bm, 0, 0);
 }
 
-////////////////////////////////////////////////////////////////////////////////
-
-static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc,
-                                                     const SkPDFShader::State& state);
-static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon,
-                                             const SkPDFShader::State& state);
-
-static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc,
-                                            const SkPDFShader::State& state,
-                                            SkBitmap image);
-
-static sk_sp<SkPDFObject> get_pdf_shader_by_state(
-        SkPDFDocument* doc,
-        SkPDFShader::State state,
-        SkBitmap image) {
-    SkPDFCanon* canon = doc->canon();
-    if (state.fType == SkShader::kNone_GradientType && image.isNull()) {
-        // TODO(vandebo) This drops SKComposeShader on the floor.  We could
-        // handle compose shader by pulling things up to a layer, drawing with
-        // the first shader, applying the xfer mode and drawing again with the
-        // second shader, then applying the layer to the original drawing.
-        return nullptr;
-    } else if (state.fType == SkShader::kNone_GradientType) {
-        sk_sp<SkPDFObject> shader = canon->findImageShader(state);
-        if (!shader) {
-            shader = make_image_shader(doc, state, std::move(image));
-            canon->addImageShader(shader, std::move(state));
-        }
-        return shader;
-    } else if (state.GradientHasAlpha()) {
-        sk_sp<SkPDFObject> shader = canon->findAlphaShader(state);
-        if (!shader) {
-            shader = make_alpha_function_shader(doc, state);
-            canon->addAlphaShader(shader, std::move(state));
-        }
-        return shader;
-    } else {
-        sk_sp<SkPDFObject> shader = canon->findFunctionShader(state);
-        if (!shader) {
-            shader = make_function_shader(canon, state);
-            canon->addFunctionShader(shader, std::move(state));
-        }
-        return shader;
-    }
-}
-
-sk_sp<SkPDFObject> SkPDFShader::GetPDFShader(SkPDFDocument* doc,
-                                             SkShader* shader,
-                                             const SkMatrix& matrix,
-                                             const SkIRect& surfaceBBox) {
-    if (surfaceBBox.isEmpty()) {
-        return nullptr;
-    }
-    SkScalar rasterDpi = doc->rasterDpi();
-    SkScalar rasterScale = SkIntToScalar(rasterDpi) / SkPDFUtils::kDpiForRasterScaleOne;
-    SkBitmap image;
-    State state(shader, matrix, surfaceBBox, rasterScale, &image);
-    return get_pdf_shader_by_state(doc, std::move(state), std::move(image));
-}
-
-static sk_sp<SkPDFDict> get_gradient_resource_dict(
-        SkPDFObject* functionShader,
-        SkPDFObject* gState) {
-    SkTDArray<SkPDFObject*> patterns;
-    if (functionShader) {
-        patterns.push(functionShader);
-    }
-    SkTDArray<SkPDFObject*> graphicStates;
-    if (gState) {
-        graphicStates.push(gState);
-    }
-    return SkPDFResourceDict::Make(&graphicStates, &patterns, nullptr, nullptr);
-}
-
-static void populate_tiling_pattern_dict(SkPDFDict* pattern,
-                                         SkRect& bbox,
-                                         sk_sp<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->insertObject("BBox", SkPDFUtils::RectToArray(bbox));
-    pattern->insertScalar("XStep", bbox.width());
-    pattern->insertScalar("YStep", bbox.height());
-    pattern->insertObject("Resources", std::move(resources));
-    if (!matrix.isIdentity()) {
-        pattern->insertObject("Matrix", SkPDFUtils::MatrixToArray(matrix));
-    }
-}
-
-/**
- * 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 std::unique_ptr<SkStreamAsset> 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 std::unique_ptr<SkStreamAsset>(content.detachAsStream());
-}
-
-/**
- * Creates a ExtGState with the SMask set to the luminosityShader in
- * luminosity mode. The shader pattern extends to the bbox.
- */
-static sk_sp<SkPDFObject> create_smask_graphic_state(SkPDFDocument* doc,
-                                                     const SkPDFShader::State& state) {
-    SkRect bbox;
-    bbox.set(state.fBBox);
-
-    sk_sp<SkPDFObject> luminosityShader(
-            get_pdf_shader_by_state(doc, state.MakeAlphaToLuminosityState(), SkBitmap()));
-
-    std::unique_ptr<SkStreamAsset> alphaStream(create_pattern_fill_content(-1, bbox));
-
-    sk_sp<SkPDFDict> resources =
-        get_gradient_resource_dict(luminosityShader.get(), nullptr);
-
-    sk_sp<SkPDFObject> alphaMask =
-        SkPDFMakeFormXObject(std::move(alphaStream),
-                             SkPDFUtils::RectToArray(bbox),
-                             std::move(resources),
-                             SkMatrix::I(),
-                             "DeviceRGB");
-    return SkPDFGraphicState::GetSMaskGraphicState(
-            std::move(alphaMask), false,
-            SkPDFGraphicState::kLuminosity_SMaskMode, doc->canon());
-}
-
-static sk_sp<SkPDFStream> make_alpha_function_shader(SkPDFDocument* doc,
-                                                     const SkPDFShader::State& state) {
-    SkRect bbox;
-    bbox.set(state.fBBox);
-
-    SkPDFShader::State opaqueState(state.MakeOpaqueState());
-
-    sk_sp<SkPDFObject> colorShader(
-            get_pdf_shader_by_state(doc, std::move(opaqueState), SkBitmap()));
-    if (!colorShader) {
-        return nullptr;
-    }
-
-    // Create resource dict with alpha graphics state as G0 and
-    // pattern shader as P0, then write content stream.
-    sk_sp<SkPDFObject> alphaGs = create_smask_graphic_state(doc, state);
-
-    sk_sp<SkPDFDict> resourceDict =
-            get_gradient_resource_dict(colorShader.get(), alphaGs.get());
-
-    std::unique_ptr<SkStreamAsset> colorStream(
-            create_pattern_fill_content(0, bbox));
-    auto alphaFunctionShader = sk_make_sp<SkPDFStream>(std::move(colorStream));
-
-    populate_tiling_pattern_dict(alphaFunctionShader->dict(), bbox,
-                                 std::move(resourceDict), SkMatrix::I());
-    return alphaFunctionShader;
-}
-
-// Finds affine and persp such that in = affine * persp.
-// but it returns the inverse of perspective matrix.
-static bool split_perspective(const SkMatrix in, SkMatrix* affine,
-                              SkMatrix* perspectiveInverse) {
-    const SkScalar p2 = in[SkMatrix::kMPersp2];
-
-    if (SkScalarNearlyZero(p2)) {
-        return false;
-    }
-
-    const SkScalar zero = SkIntToScalar(0);
-    const SkScalar one = SkIntToScalar(1);
-
-    const SkScalar sx = in[SkMatrix::kMScaleX];
-    const SkScalar kx = in[SkMatrix::kMSkewX];
-    const SkScalar tx = in[SkMatrix::kMTransX];
-    const SkScalar ky = in[SkMatrix::kMSkewY];
-    const SkScalar sy = in[SkMatrix::kMScaleY];
-    const SkScalar ty = in[SkMatrix::kMTransY];
-    const SkScalar p0 = in[SkMatrix::kMPersp0];
-    const SkScalar p1 = in[SkMatrix::kMPersp1];
-
-    // Perspective matrix would be:
-    // 1  0  0
-    // 0  1  0
-    // p0 p1 p2
-    // But we need the inverse of persp.
-    perspectiveInverse->setAll(one,          zero,       zero,
-                               zero,         one,        zero,
-                               -p0/p2,     -p1/p2,     1/p2);
-
-    affine->setAll(sx - p0 * tx / p2,       kx - p1 * tx / p2,      tx / p2,
-                   ky - p0 * ty / p2,       sy - p1 * ty / p2,      ty / p2,
-                   zero,                    zero,                   one);
-
-    return true;
-}
-
-static sk_sp<SkPDFArray> make_range_object() {
-    auto range = sk_make_sp<SkPDFArray>();
-    range->reserve(6);
-    range->appendInt(0);
-    range->appendInt(1);
-    range->appendInt(0);
-    range->appendInt(1);
-    range->appendInt(0);
-    range->appendInt(1);
-    return range;
-}
-
-static sk_sp<SkPDFStream> make_ps_function(
-        std::unique_ptr<SkStreamAsset> psCode,
-        sk_sp<SkPDFArray> domain,
-        sk_sp<SkPDFObject> range) {
-    auto result = sk_make_sp<SkPDFStream>(std::move(psCode));
-    result->dict()->insertInt("FunctionType", 4);
-    result->dict()->insertObject("Domain", std::move(domain));
-    result->dict()->insertObject("Range", std::move(range));
-    return result;
-}
-
-// catch cases where the inner just touches the outer circle
-// and make the inner circle just inside the outer one to match raster
-static void FixUpRadius(const SkPoint& p1, SkScalar& r1, const SkPoint& p2, SkScalar& r2) {
-    // detect touching circles
-    SkScalar distance = SkPoint::Distance(p1, p2);
-    SkScalar subtractRadii = fabs(r1 - r2);
-    if (fabs(distance - subtractRadii) < 0.002f) {
-        if (r1 > r2) {
-            r1 += 0.002f;
-        } else {
-            r2 += 0.002f;
-        }
-    }
-}
-
-static sk_sp<SkPDFDict> make_function_shader(SkPDFCanon* canon,
-                                             const SkPDFShader::State& state) {
-    void (*codeFunction)(const SkShader::GradientInfo& info,
-                         const SkMatrix& perspectiveRemover,
-                         SkDynamicMemoryWStream* function) = nullptr;
-    SkPoint transformPoints[2];
-    const SkShader::GradientInfo* info = &state.fInfo;
-    SkMatrix finalMatrix = state.fCanvasTransform;
-    finalMatrix.preConcat(state.fShaderTransform);
-
-    bool doStitchFunctions = (state.fType == SkShader::kLinear_GradientType ||
-                              state.fType == SkShader::kRadial_GradientType ||
-                              state.fType == SkShader::kConical_GradientType) &&
-                             info->fTileMode == SkShader::kClamp_TileMode &&
-                             !finalMatrix.hasPerspective();
-
-    auto domain = sk_make_sp<SkPDFArray>();
-
-    int32_t shadingType = 1;
-    auto pdfShader = sk_make_sp<SkPDFDict>();
-    // The two point radial gradient further references
-    // state.fInfo
-    // in translating from x, y coordinates to the t parameter. So, we have
-    // to transform the points and radii according to the calculated matrix.
-    if (doStitchFunctions) {
-        pdfShader->insertObject("Function", gradientStitchCode(*info));
-        shadingType = (state.fType == SkShader::kLinear_GradientType) ? 2 : 3;
-
-        auto extend = sk_make_sp<SkPDFArray>();
-        extend->reserve(2);
-        extend->appendBool(true);
-        extend->appendBool(true);
-        pdfShader->insertObject("Extend", std::move(extend));
-
-        auto coords = sk_make_sp<SkPDFArray>();
-        if (state.fType == SkShader::kConical_GradientType) {
-            coords->reserve(6);
-            SkScalar r1 = info->fRadius[0];
-            SkScalar r2 = info->fRadius[1];
-            SkPoint pt1 = info->fPoint[0];
-            SkPoint pt2 = info->fPoint[1];
-            FixUpRadius(pt1, r1, pt2, r2);
-
-            coords->appendScalar(pt1.fX);
-            coords->appendScalar(pt1.fY);
-            coords->appendScalar(r1);
-
-            coords->appendScalar(pt2.fX);
-            coords->appendScalar(pt2.fY);
-            coords->appendScalar(r2);
-        } else if (state.fType == SkShader::kRadial_GradientType) {
-            coords->reserve(6);
-            const SkPoint& pt1 = info->fPoint[0];
-
-            coords->appendScalar(pt1.fX);
-            coords->appendScalar(pt1.fY);
-            coords->appendScalar(0);
-
-            coords->appendScalar(pt1.fX);
-            coords->appendScalar(pt1.fY);
-            coords->appendScalar(info->fRadius[0]);
-        } else {
-            coords->reserve(4);
-            const SkPoint& pt1 = info->fPoint[0];
-            const SkPoint& pt2 = info->fPoint[1];
-
-            coords->appendScalar(pt1.fX);
-            coords->appendScalar(pt1.fY);
-
-            coords->appendScalar(pt2.fX);
-            coords->appendScalar(pt2.fY);
-        }
-
-        pdfShader->insertObject("Coords", std::move(coords));
-    } else {
-        // Depending on the type of the gradient, we want to transform the
-        // coordinate space in different ways.
-        transformPoints[0] = info->fPoint[0];
-        transformPoints[1] = info->fPoint[1];
-        switch (state.fType) {
-            case SkShader::kLinear_GradientType:
-                codeFunction = &linearCode;
-                break;
-            case SkShader::kRadial_GradientType:
-                transformPoints[1] = transformPoints[0];
-                transformPoints[1].fX += info->fRadius[0];
-                codeFunction = &radialCode;
-                break;
-            case SkShader::kConical_GradientType: {
-                transformPoints[1] = transformPoints[0];
-                transformPoints[1].fX += SK_Scalar1;
-                codeFunction = &twoPointConicalCode;
-                break;
-            }
-            case SkShader::kSweep_GradientType:
-                transformPoints[1] = transformPoints[0];
-                transformPoints[1].fX += SK_Scalar1;
-                codeFunction = &sweepCode;
-                break;
-            case SkShader::kColor_GradientType:
-            case SkShader::kNone_GradientType:
-            default:
-                return nullptr;
-        }
-
-        // Move any scaling (assuming a unit gradient) or translation
-        // (and rotation for linear gradient), of the final gradient from
-        // info->fPoints to the matrix (updating bbox appropriately).  Now
-        // the gradient can be drawn on on the unit segment.
-        SkMatrix mapperMatrix;
-        unitToPointsMatrix(transformPoints, &mapperMatrix);
-
-        finalMatrix.preConcat(mapperMatrix);
-
-        // Preserves as much as posible in the final matrix, and only removes
-        // the perspective. The inverse of the perspective is stored in
-        // perspectiveInverseOnly matrix and has 3 useful numbers
-        // (p0, p1, p2), while everything else is either 0 or 1.
-        // In this way the shader will handle it eficiently, with minimal code.
-        SkMatrix perspectiveInverseOnly = SkMatrix::I();
-        if (finalMatrix.hasPerspective()) {
-            if (!split_perspective(finalMatrix,
-                                   &finalMatrix, &perspectiveInverseOnly)) {
-                return nullptr;
-            }
-        }
-
-        SkRect bbox;
-        bbox.set(state.fBBox);
-        if (!inverse_transform_bbox(finalMatrix, &bbox)) {
-            return nullptr;
-        }
-        domain->reserve(4);
-        domain->appendScalar(bbox.fLeft);
-        domain->appendScalar(bbox.fRight);
-        domain->appendScalar(bbox.fTop);
-        domain->appendScalar(bbox.fBottom);
-
-        SkDynamicMemoryWStream functionCode;
-
-        if (state.fType == SkShader::kConical_GradientType) {
-            SkShader::GradientInfo twoPointRadialInfo = *info;
-            SkMatrix inverseMapperMatrix;
-            if (!mapperMatrix.invert(&inverseMapperMatrix)) {
-                return nullptr;
-            }
-            inverseMapperMatrix.mapPoints(twoPointRadialInfo.fPoint, 2);
-            twoPointRadialInfo.fRadius[0] =
-                inverseMapperMatrix.mapRadius(info->fRadius[0]);
-            twoPointRadialInfo.fRadius[1] =
-                inverseMapperMatrix.mapRadius(info->fRadius[1]);
-            codeFunction(twoPointRadialInfo, perspectiveInverseOnly, &functionCode);
-        } else {
-            codeFunction(*info, perspectiveInverseOnly, &functionCode);
-        }
-
-        pdfShader->insertObject("Domain", domain);
-
-        std::unique_ptr<SkStreamAsset> functionStream(functionCode.detachAsStream());
-
-        sk_sp<SkPDFArray>& rangeObject = canon->fRangeObject;
-        if (!rangeObject) {
-            rangeObject = make_range_object();
-        }
-        sk_sp<SkPDFStream> function = make_ps_function(std::move(functionStream), std::move(domain),
-                                                       rangeObject);
-        pdfShader->insertObjRef("Function", std::move(function));
-    }
-
-    pdfShader->insertInt("ShadingType", shadingType);
-    pdfShader->insertName("ColorSpace", "DeviceRGB");
-
-    auto pdfFunctionShader = sk_make_sp<SkPDFDict>("Pattern");
-    pdfFunctionShader->insertInt("PatternType", 2);
-    pdfFunctionShader->insertObject("Matrix",
-                                    SkPDFUtils::MatrixToArray(finalMatrix));
-    pdfFunctionShader->insertObject("Shading", std::move(pdfShader));
-
-    return pdfFunctionShader;
-}
-
 static sk_sp<SkPDFStream> make_image_shader(SkPDFDocument* doc,
                                             const SkPDFShader::State& state,
                                             SkBitmap image) {
@@ -966,7 +44,7 @@
     finalMatrix.preConcat(state.fShaderTransform);
     SkRect deviceBounds;
     deviceBounds.set(state.fBBox);
-    if (!inverse_transform_bbox(finalMatrix, &deviceBounds)) {
+    if (!SkPDFUtils::InverseTransformBBox(finalMatrix, &deviceBounds)) {
         return nullptr;
     }
 
@@ -1012,14 +90,14 @@
         SkMatrix xMirror;
         xMirror.setScale(-1, 1);
         xMirror.postTranslate(2 * width, 0);
-        drawBitmapMatrix(&canvas, image, xMirror);
+        draw_bitmap_matrix(&canvas, image, xMirror);
         patternBBox.fRight += width;
     }
     if (tileModes[1] == SkShader::kMirror_TileMode) {
         SkMatrix yMirror;
         yMirror.setScale(SK_Scalar1, -SK_Scalar1);
         yMirror.postTranslate(0, 2 * height);
-        drawBitmapMatrix(&canvas, image, yMirror);
+        draw_bitmap_matrix(&canvas, image, yMirror);
         patternBBox.fBottom += height;
     }
     if (tileModes[0] == SkShader::kMirror_TileMode &&
@@ -1027,7 +105,7 @@
         SkMatrix mirror;
         mirror.setScale(-1, -1);
         mirror.postTranslate(2 * width, 2 * height);
-        drawBitmapMatrix(&canvas, image, mirror);
+        draw_bitmap_matrix(&canvas, image, mirror);
     }
 
     // Then handle Clamping, which requires expanding the pattern canvas to
@@ -1078,12 +156,12 @@
             SkMatrix leftMatrix;
             leftMatrix.setScale(-deviceBounds.left(), 1);
             leftMatrix.postTranslate(deviceBounds.left(), 0);
-            drawBitmapMatrix(&canvas, left, leftMatrix);
+            draw_bitmap_matrix(&canvas, left, leftMatrix);
 
             if (tileModes[1] == SkShader::kMirror_TileMode) {
                 leftMatrix.postScale(SK_Scalar1, -SK_Scalar1);
                 leftMatrix.postTranslate(0, 2 * height);
-                drawBitmapMatrix(&canvas, left, leftMatrix);
+                draw_bitmap_matrix(&canvas, left, leftMatrix);
             }
             patternBBox.fLeft = 0;
         }
@@ -1096,12 +174,12 @@
             SkMatrix rightMatrix;
             rightMatrix.setScale(deviceBounds.right() - width, 1);
             rightMatrix.postTranslate(width, 0);
-            drawBitmapMatrix(&canvas, right, rightMatrix);
+            draw_bitmap_matrix(&canvas, right, rightMatrix);
 
             if (tileModes[1] == SkShader::kMirror_TileMode) {
                 rightMatrix.postScale(SK_Scalar1, -SK_Scalar1);
                 rightMatrix.postTranslate(0, 2 * height);
-                drawBitmapMatrix(&canvas, right, rightMatrix);
+                draw_bitmap_matrix(&canvas, right, rightMatrix);
             }
             patternBBox.fRight = deviceBounds.width();
         }
@@ -1116,12 +194,12 @@
             SkMatrix topMatrix;
             topMatrix.setScale(SK_Scalar1, -deviceBounds.top());
             topMatrix.postTranslate(0, deviceBounds.top());
-            drawBitmapMatrix(&canvas, top, topMatrix);
+            draw_bitmap_matrix(&canvas, top, topMatrix);
 
             if (tileModes[0] == SkShader::kMirror_TileMode) {
                 topMatrix.postScale(-1, 1);
                 topMatrix.postTranslate(2 * width, 0);
-                drawBitmapMatrix(&canvas, top, topMatrix);
+                draw_bitmap_matrix(&canvas, top, topMatrix);
             }
             patternBBox.fTop = 0;
         }
@@ -1134,229 +212,122 @@
             SkMatrix bottomMatrix;
             bottomMatrix.setScale(SK_Scalar1, deviceBounds.bottom() - height);
             bottomMatrix.postTranslate(0, height);
-            drawBitmapMatrix(&canvas, bottom, bottomMatrix);
+            draw_bitmap_matrix(&canvas, bottom, bottomMatrix);
 
             if (tileModes[0] == SkShader::kMirror_TileMode) {
                 bottomMatrix.postScale(-1, 1);
                 bottomMatrix.postTranslate(2 * width, 0);
-                drawBitmapMatrix(&canvas, bottom, bottomMatrix);
+                draw_bitmap_matrix(&canvas, bottom, bottomMatrix);
             }
             patternBBox.fBottom = deviceBounds.height();
         }
     }
 
     auto imageShader = sk_make_sp<SkPDFStream>(patternDevice->content());
-    populate_tiling_pattern_dict(imageShader->dict(), patternBBox,
+    SkPDFUtils::PopulateTilingPatternDict(imageShader->dict(), patternBBox,
                                  patternDevice->makeResourceDict(), finalMatrix);
     return imageShader;
 }
 
-bool SkPDFShader::State::operator==(const SkPDFShader::State& b) const {
-    if (fType != b.fType ||
-            fCanvasTransform != b.fCanvasTransform ||
-            fShaderTransform != b.fShaderTransform ||
-            fBBox != b.fBBox) {
-        return false;
-    }
+// Generic fallback for unsupported shaders:
+//  * allocate a surfaceBBox-sized bitmap
+//  * shade the whole area
+//  * use the result as a bitmap shader
+static sk_sp<SkPDFObject> make_fallback_shader(SkPDFDocument* doc,
+                                               SkShader* shader,
+                                               const SkMatrix& canvasTransform,
+                                               const SkIRect& surfaceBBox) {
+    // TODO(vandebo) This drops SKComposeShader on the floor.  We could
+    // handle compose shader by pulling things up to a layer, drawing with
+    // the first shader, applying the xfer mode and drawing again with the
+    // second shader, then applying the layer to the original drawing.
+    SkPDFShader::State state = {
+        canvasTransform,
+        SkMatrix::I(),
+        surfaceBBox,
+        {{0, 0, 0, 0}, 0},
+        {SkShader::kClamp_TileMode, SkShader::kClamp_TileMode}};
 
-    if (fType == SkShader::kNone_GradientType) {
-        if (fBitmapKey != b.fBitmapKey ||
-                fBitmapKey.fID == 0 ||
-                fImageTileModes[0] != b.fImageTileModes[0] ||
-                fImageTileModes[1] != b.fImageTileModes[1]) {
-            return false;
-        }
-    } else {
-        if (fInfo.fColorCount != b.fInfo.fColorCount ||
-                memcmp(fInfo.fColors, b.fInfo.fColors,
-                       sizeof(SkColor) * fInfo.fColorCount) != 0 ||
-                memcmp(fInfo.fColorOffsets, b.fInfo.fColorOffsets,
-                       sizeof(SkScalar) * fInfo.fColorCount) != 0 ||
-                fInfo.fPoint[0] != b.fInfo.fPoint[0] ||
-                fInfo.fTileMode != b.fInfo.fTileMode) {
-            return false;
-        }
+    state.fShaderTransform = shader->getLocalMatrix();
 
-        switch (fType) {
-            case SkShader::kLinear_GradientType:
-                if (fInfo.fPoint[1] != b.fInfo.fPoint[1]) {
-                    return false;
-                }
-                break;
-            case SkShader::kRadial_GradientType:
-                if (fInfo.fRadius[0] != b.fInfo.fRadius[0]) {
-                    return false;
-                }
-                break;
-            case SkShader::kConical_GradientType:
-                if (fInfo.fPoint[1] != b.fInfo.fPoint[1] ||
-                        fInfo.fRadius[0] != b.fInfo.fRadius[0] ||
-                        fInfo.fRadius[1] != b.fInfo.fRadius[1]) {
-                    return false;
-                }
-                break;
-            case SkShader::kSweep_GradientType:
-            case SkShader::kNone_GradientType:
-            case SkShader::kColor_GradientType:
-                break;
-        }
-    }
-    return true;
-}
-
-SkPDFShader::State::State(SkShader* shader, const SkMatrix& canvasTransform,
-                          const SkIRect& bbox, SkScalar rasterScale,
-                          SkBitmap* imageDst)
-        : fType(SkShader::kNone_GradientType)
-        , fInfo{0, nullptr, nullptr, {{0.0f, 0.0f}, {0.0f, 0.0f}},
-                {0.0f, 0.0f}, SkShader::kClamp_TileMode, 0}
-        , fCanvasTransform(canvasTransform)
-        , fShaderTransform{SkMatrix::I()}
-        , fBBox(bbox)
-        , fBitmapKey{{0, 0, 0, 0}, 0}
-        , fImageTileModes{SkShader::kClamp_TileMode,
-                          SkShader::kClamp_TileMode} {
-    SkASSERT(imageDst);
-    fInfo.fColorCount = 0;
-    fInfo.fColors = nullptr;
-    fInfo.fColorOffsets = nullptr;
-    fImageTileModes[0] = fImageTileModes[1] = SkShader::kClamp_TileMode;
-    fType = shader->asAGradient(&fInfo);
-
-    if (fType != SkShader::kNone_GradientType) {
-        fBitmapKey = SkBitmapKey{{0, 0, 0, 0}, 0};
-        fShaderTransform = SkPDFUtils::GetShaderLocalMatrix(shader);
-        this->allocateGradientInfoStorage();
-        shader->asAGradient(&fInfo);
-        return;
-    }
-    if (SkImage* skimg = shader->isAImage(&fShaderTransform, fImageTileModes)) {
-        // TODO(halcanary): delay converting to bitmap.
-        if (skimg->asLegacyBitmap(imageDst, SkImage::kRO_LegacyBitmapMode)) {
-            fBitmapKey = SkBitmapKey{imageDst->getSubset(), imageDst->getGenerationID()};
-            return;
-        }
-    }
-    fShaderTransform = shader->getLocalMatrix();
-    // Generic fallback for unsupported shaders:
-    //  * allocate a bbox-sized bitmap
-    //  * shade the whole area
-    //  * use the result as a bitmap shader
-
-    // bbox is in device space. While that's exactly what we
+    // surfaceBBox is in device space. While that's exactly what we
     // want for sizing our bitmap, we need to map it into
     // shader space for adjustments (to match
     // MakeImageShader's behavior).
-    SkRect shaderRect = SkRect::Make(bbox);
-    if (!inverse_transform_bbox(canvasTransform, &shaderRect)) {
-        imageDst->reset();
-        return;
+    SkRect shaderRect = SkRect::Make(surfaceBBox);
+    if (!SkPDFUtils::InverseTransformBBox(canvasTransform, &shaderRect)) {
+        return nullptr;
     }
-
     // Clamp the bitmap size to about 1M pixels
     static const SkScalar kMaxBitmapArea = 1024 * 1024;
-    SkScalar bitmapArea = rasterScale * bbox.width() * rasterScale * bbox.height();
+    SkScalar rasterScale = SkIntToScalar(doc->rasterDpi()) / SkPDFUtils::kDpiForRasterScaleOne;
+    SkScalar bitmapArea = rasterScale * surfaceBBox.width() * rasterScale * surfaceBBox.height();
     if (bitmapArea > kMaxBitmapArea) {
         rasterScale *= SkScalarSqrt(kMaxBitmapArea / bitmapArea);
     }
 
-    SkISize size = {SkScalarRoundToInt(rasterScale * bbox.width()),
-                    SkScalarRoundToInt(rasterScale * bbox.height())};
+    SkISize size = {SkScalarRoundToInt(rasterScale * surfaceBBox.width()),
+                    SkScalarRoundToInt(rasterScale * surfaceBBox.height())};
     SkSize scale = {SkIntToScalar(size.width()) / shaderRect.width(),
                     SkIntToScalar(size.height()) / shaderRect.height()};
 
-    imageDst->allocN32Pixels(size.width(), size.height());
-    imageDst->eraseColor(SK_ColorTRANSPARENT);
+    SkBitmap image;
+    image.allocN32Pixels(size.width(), size.height());
+    image.eraseColor(SK_ColorTRANSPARENT);
 
     SkPaint p;
     p.setShader(sk_ref_sp(shader));
 
-    SkCanvas canvas(*imageDst);
+    SkCanvas canvas(image);
     canvas.scale(scale.width(), scale.height());
     canvas.translate(-shaderRect.x(), -shaderRect.y());
     canvas.drawPaint(p);
 
-    fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y());
-    fShaderTransform.preScale(1 / scale.width(), 1 / scale.height());
-    fBitmapKey = SkBitmapKey{imageDst->getSubset(), imageDst->getGenerationID()};
+    state.fShaderTransform.setTranslate(shaderRect.x(), shaderRect.y());
+    state.fShaderTransform.preScale(1 / scale.width(), 1 / scale.height());
+    state.fBitmapKey = SkBitmapKey{image.getSubset(), image.getGenerationID()};
+    SkASSERT (!image.isNull());
+    return make_image_shader(doc, state, std::move(image));
 }
 
-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);
+sk_sp<SkPDFObject> SkPDFShader::GetPDFShader(SkPDFDocument* doc,
+                                             SkShader* shader,
+                                             const SkMatrix& canvasTransform,
+                                             const SkIRect& surfaceBBox) {
+    SkASSERT(shader);
+    SkASSERT(doc);
+    if (SkShader::kNone_GradientType != shader->asAGradient(nullptr)) {
+        return SkPDFGradientShader::Make(doc, shader, canvasTransform, surfaceBBox);
+    }
+    if (surfaceBBox.isEmpty()) {
+        return nullptr;
+    }
+    SkBitmap image;
+    SkPDFShader::State state = {
+        canvasTransform,
+        SkMatrix::I(),
+        surfaceBBox,
+        {{0, 0, 0, 0}, 0},
+        {SkShader::kClamp_TileMode, SkShader::kClamp_TileMode}};
 
-    if (fType != SkShader::kNone_GradientType) {
-        fInfo = other.fInfo;
-
-        this->allocateGradientInfoStorage();
-        for (int i = 0; i < fInfo.fColorCount; i++) {
-            fInfo.fColors[i] = other.fInfo.fColors[i];
-            fInfo.fColorOffsets[i] = other.fInfo.fColorOffsets[i];
+    SkASSERT(shader->asAGradient(nullptr) == SkShader::kNone_GradientType) ;
+    SkImage* skimg;
+    if ((skimg = shader->isAImage(&state.fShaderTransform, state.fImageTileModes))
+            && skimg->asLegacyBitmap(&image, SkImage::kRO_LegacyBitmapMode)) {
+        // TODO(halcanary): delay converting to bitmap.
+        state.fBitmapKey = SkBitmapKey{image.getSubset(), image.getGenerationID()};
+        if (image.isNull()) {
+            return nullptr;
         }
-    }
-}
-
-/**
- * Create a copy of this gradient state with alpha assigned to RGB luminousity.
- * Only valid for gradient states.
- */
-SkPDFShader::State SkPDFShader::State::MakeAlphaToLuminosityState() const {
-    SkASSERT(fBitmapKey == (SkBitmapKey{{0, 0, 0, 0}, 0}));
-    SkASSERT(fType != SkShader::kNone_GradientType);
-
-    SkPDFShader::State newState(*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::MakeOpaqueState() const {
-    SkASSERT(fBitmapKey == (SkBitmapKey{{0, 0, 0, 0}, 0}));
-    SkASSERT(fType != SkShader::kNone_GradientType);
-
-    SkPDFShader::State newState(*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;
+        SkPDFCanon* canon = doc->canon();
+        sk_sp<SkPDFObject>* shaderPtr = canon->fImageShaderMap.find(state);
+        if (shaderPtr) {
+            return *shaderPtr;
         }
+        sk_sp<SkPDFObject> pdfShader = make_image_shader(doc, state, std::move(image));
+        canon->fImageShaderMap.set(std::move(state), pdfShader);
+        return pdfShader;
     }
-    return false;
-}
-
-void SkPDFShader::State::allocateGradientInfoStorage() {
-    fColors.reset(new SkColor[fInfo.fColorCount]);
-    fStops.reset(new SkScalar[fInfo.fColorCount]);
-    fInfo.fColors = fColors.get();
-    fInfo.fColorOffsets = fStops.get();
+    // Don't bother to de-dup fallback shader.
+    return make_fallback_shader(doc, shader, canvasTransform, surfaceBBox);
 }
diff --git a/src/pdf/SkPDFShader.h b/src/pdf/SkPDFShader.h
index fe561b6..062aaa6 100644
--- a/src/pdf/SkPDFShader.h
+++ b/src/pdf/SkPDFShader.h
@@ -45,38 +45,26 @@
 
     static sk_sp<SkPDFArray> MakeRangeObject();
 
-    class State {
-    public:
-        SkShader::GradientType fType;
-        SkShader::GradientInfo fInfo;
-        std::unique_ptr<SkColor[]> fColors;
-        std::unique_ptr<SkScalar[]> fStops;
+    SK_BEGIN_REQUIRE_DENSE
+    struct State {
         SkMatrix fCanvasTransform;
         SkMatrix fShaderTransform;
         SkIRect fBBox;
-
         SkBitmapKey fBitmapKey;
         SkShader::TileMode fImageTileModes[2];
-
-        State(SkShader* shader, const SkMatrix& canvasTransform,
-              const SkIRect& bbox, SkScalar rasterScale,
-              SkBitmap* dstImage);
-
-        bool operator==(const State& b) const;
-
-        State MakeAlphaToLuminosityState() const;
-        State MakeOpaqueState() const;
-
-        bool GradientHasAlpha() const;
-
-        State(State&&) = default;
-        State& operator=(State&&) = default;
-
-    private:
-        State(const State& other);
-        State& operator=(const State& rhs);
-        void allocateGradientInfoStorage();
     };
+    SK_END_REQUIRE_DENSE
 };
 
+inline bool operator==(const SkPDFShader::State& a, const SkPDFShader::State& b) {
+    SkASSERT(a.fBitmapKey.fID != 0);
+    SkASSERT(b.fBitmapKey.fID != 0);
+    return a.fCanvasTransform   == b.fCanvasTransform
+        && a.fShaderTransform   == b.fShaderTransform
+        && a.fBBox              == b.fBBox
+        && a.fBitmapKey         == b.fBitmapKey
+        && a.fImageTileModes[0] == b.fImageTileModes[0]
+        && a.fImageTileModes[1] == b.fImageTileModes[1];
+}
+
 #endif
diff --git a/src/pdf/SkPDFUtils.cpp b/src/pdf/SkPDFUtils.cpp
index 3cbf038..510be6c 100644
--- a/src/pdf/SkPDFUtils.cpp
+++ b/src/pdf/SkPDFUtils.cpp
@@ -486,3 +486,33 @@
         wStream->writeText(">");
     }
 }
+
+bool SkPDFUtils::InverseTransformBBox(const SkMatrix& matrix, SkRect* bbox) {
+    SkMatrix inverse;
+    if (!matrix.invert(&inverse)) {
+        return false;
+    }
+    inverse.mapRect(bbox);
+    return true;
+}
+
+void SkPDFUtils::PopulateTilingPatternDict(SkPDFDict* pattern,
+                                           SkRect& bbox,
+                                           sk_sp<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->insertObject("BBox", SkPDFUtils::RectToArray(bbox));
+    pattern->insertScalar("XStep", bbox.width());
+    pattern->insertScalar("YStep", bbox.height());
+    pattern->insertObject("Resources", std::move(resources));
+    if (!matrix.isIdentity()) {
+        pattern->insertObject("Matrix", SkPDFUtils::MatrixToArray(matrix));
+    }
+}
diff --git a/src/pdf/SkPDFUtils.h b/src/pdf/SkPDFUtils.h
index dfc10b3..93509fe 100644
--- a/src/pdf/SkPDFUtils.h
+++ b/src/pdf/SkPDFUtils.h
@@ -19,6 +19,13 @@
 class SkPDFArray;
 struct SkRect;
 
+template <typename T>
+bool SkPackedArrayEqual(T* u, T* v, size_t n) {
+    SkASSERT(u);
+    SkASSERT(v);
+    return 0 == memcmp(u, v, n * sizeof(T));
+}
+
 #if 0
 #define PRINT_NOT_IMPL(str) fprintf(stderr, str)
 #else
@@ -112,6 +119,11 @@
     }
     return shader->getLocalMatrix();
 }
+bool InverseTransformBBox(const SkMatrix& matrix, SkRect* bbox);
+void PopulateTilingPatternDict(SkPDFDict* pattern,
+                               SkRect& bbox,
+                               sk_sp<SkPDFDict> resources,
+                               const SkMatrix& matrix);
 }  // namespace SkPDFUtils
 
 #endif