Merge perspective feature for PDF.

From Skia's repo:

http://code.google.com/p/skia/source/detail?r=11751
Add SkPDFDeviceFlatenner which extends SkPDFDevice to add support
to flatten the path and the text when we have perspective. prepare
to deprecate SkPDFDevice constructor, and route gm and render_pdfs
to use SkDocument::Create pdf interface instead. - controlled by a
flag add comments where we are supposed to flatten other features
(paint, shaders, ... )

https://code.google.com/p/skia/source/detail?r=11822
Implement perspective for bitmaps in pdf.

https://code.google.com/p/skia/source/detail?r=11937
PDF: support perspective in simple shaders. (this version does not
work well with tilling)

BUG:11384071

Change-Id: I48ea88d231231215349f155d972c6b9e1e127c71
diff --git a/Android.mk b/Android.mk
index 3590893..1e90e52 100644
--- a/Android.mk
+++ b/Android.mk
@@ -351,6 +351,7 @@
 	src/ports/SkTLS_pthread.cpp \
 	src/pdf/SkPDFCatalog.cpp \
 	src/pdf/SkPDFDevice.cpp \
+	src/pdf/SkPDFDeviceFlattener.cpp \
 	src/pdf/SkPDFDocument.cpp \
 	src/pdf/SkPDFFont.cpp \
 	src/pdf/SkPDFFormXObject.cpp \
@@ -571,6 +572,7 @@
 	$(LOCAL_PATH)/src/gpu \
 	$(LOCAL_PATH)/src/image \
 	$(LOCAL_PATH)/src/lazy \
+	$(LOCAL_PATH)/src/pdf \
 	$(LOCAL_PATH)/src/sfnt \
 	$(LOCAL_PATH)/src/utils \
 	external/freetype/include \
diff --git a/gm/gm_error.h b/gm/gm_error.h
index e442f5d..e2274c4 100644
--- a/gm/gm_error.h
+++ b/gm/gm_error.h
@@ -28,6 +28,7 @@
 
         kIntentionallySkipped_ErrorType,
         kRenderModeMismatch_ErrorType,
+        kGeneratePdfFailed_ErrorType,
         kExpectationsMismatch_ErrorType,
         kMissingExpectations_ErrorType,
         kWritingReferenceImage_ErrorType,
@@ -45,6 +46,8 @@
             return "IntentionallySkipped";
         case kRenderModeMismatch_ErrorType:
             return "RenderModeMismatch";
+        case kGeneratePdfFailed_ErrorType:
+            return "GeneratePdfFailed";
         case kExpectationsMismatch_ErrorType:
             return "ExpectationsMismatch";
         case kMissingExpectations_ErrorType:
diff --git a/gm/gmmain.cpp b/gm/gmmain.cpp
index 67259f1..ed7e7a8 100644
--- a/gm/gmmain.cpp
+++ b/gm/gmmain.cpp
@@ -23,6 +23,7 @@
 #include "SkData.h"
 #include "SkDeferredCanvas.h"
 #include "SkDevice.h"
+#include "SkDocument.h"
 #include "SkDrawFilter.h"
 #include "SkForceLinking.h"
 #include "SkGPipe.h"
@@ -32,6 +33,7 @@
 #include "SkOSFile.h"
 #include "SkPicture.h"
 #include "SkRefCnt.h"
+#include "SkScalar.h"
 #include "SkStream.h"
 #include "SkTArray.h"
 #include "SkTDict.h"
@@ -68,6 +70,7 @@
 #define DEBUGFAIL_SEE_STDERR SkDEBUGFAIL("see stderr for message")
 
 extern bool gSkSuppressFontCachePurgeSpew;
+DECLARE_bool(useDocumentInsteadOfDevice);
 
 #ifdef SK_SUPPORT_PDF
     #include "SkPDFDevice.h"
@@ -596,34 +599,51 @@
         }
     }
 
-    static void generate_pdf(GM* gm, SkDynamicMemoryWStream& pdf) {
+    static bool generate_pdf(GM* gm, SkDynamicMemoryWStream& pdf) {
 #ifdef SK_SUPPORT_PDF
         SkMatrix initialTransform = gm->getInitialTransform();
-        SkISize pageSize = gm->getISize();
-        SkPDFDevice* dev = NULL;
-        if (initialTransform.isIdentity()) {
-            dev = new SkPDFDevice(pageSize, pageSize, initialTransform);
+        if (FLAGS_useDocumentInsteadOfDevice) {
+            SkISize pageISize = gm->getISize();
+            SkAutoTUnref<SkDocument> pdfDoc(SkDocument::CreatePDF(&pdf, NULL, encode_to_dct_data));
+
+            if (!pdfDoc.get()) {
+                return false;
+            }
+
+            SkCanvas* canvas = NULL;
+            canvas = pdfDoc->beginPage(SkIntToScalar(pageISize.width()),
+                                       SkIntToScalar(pageISize.height()));
+            canvas->concat(initialTransform);
+
+            invokeGM(gm, canvas, true, false);
+
+            return pdfDoc->close();
         } else {
-            SkRect content = SkRect::MakeWH(SkIntToScalar(pageSize.width()),
-                                            SkIntToScalar(pageSize.height()));
-            initialTransform.mapRect(&content);
-            content.intersect(0, 0, SkIntToScalar(pageSize.width()),
-                              SkIntToScalar(pageSize.height()));
-            SkISize contentSize =
-                SkISize::Make(SkScalarRoundToInt(content.width()),
-                              SkScalarRoundToInt(content.height()));
-            dev = new SkPDFDevice(pageSize, contentSize, initialTransform);
+            SkISize pageSize = gm->getISize();
+            SkPDFDevice* dev = NULL;
+            if (initialTransform.isIdentity()) {
+                dev = new SkPDFDevice(pageSize, pageSize, initialTransform);
+            } else {
+                SkRect content = SkRect::MakeWH(SkIntToScalar(pageSize.width()),
+                                                SkIntToScalar(pageSize.height()));
+                initialTransform.mapRect(&content);
+                content.intersect(0, 0, SkIntToScalar(pageSize.width()),
+                                  SkIntToScalar(pageSize.height()));
+                SkISize contentSize =
+                    SkISize::Make(SkScalarRoundToInt(content.width()),
+                                  SkScalarRoundToInt(content.height()));
+                dev = new SkPDFDevice(pageSize, contentSize, initialTransform);
+            }
+            dev->setDCTEncoder(encode_to_dct_data);
+            SkAutoUnref aur(dev);
+            SkCanvas c(dev);
+            invokeGM(gm, &c, true, false);
+            SkPDFDocument doc;
+            doc.appendPage(dev);
+            doc.emitPDF(&pdf);
         }
-        dev->setDCTEncoder(encode_to_dct_stream);
-        SkAutoUnref aur(dev);
-
-        SkCanvas c(dev);
-        invokeGM(gm, &c, true, false);
-
-        SkPDFDocument doc;
-        doc.appendPage(dev);
-        doc.emitPDF(&pdf);
-#endif
+#endif  // SK_SUPPORT_PDF
+        return true; // Do not report failure if pdf is not supported.
     }
 
     static void generate_xps(GM* gm, SkDynamicMemoryWStream& xps) {
@@ -1042,11 +1062,49 @@
                 return errors;
             }
         } else if (gRec.fBackend == kPDF_Backend) {
-            generate_pdf(gm, document);
 #if CAN_IMAGE_PDF
-            SkAutoDataUnref data(document.copyToData());
-            SkMemoryStream stream(data->data(), data->size());
-            SkPDFDocumentToBitmap(&stream, bitmap);
+            if (!generate_pdf(gm, document)) {
+                errors.add(kGeneratePdfFailed_ErrorType);
+            } else {
+                SkAutoTUnref<SkStreamAsset> documentStream(document.detachAsStream());
+                if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) {
+                    path = make_filename(writePath, gm->shortName(), gRec.fName, "", "pdf");
+                    errors.add(write_document(path, documentStream));
+                }
+
+                if (!(gm->getFlags() & GM::kSkipPDFRasterization_Flag)) {
+                    for (int i = 0; i < pdfRasterizers.count(); i++) {
+                        SkBitmap pdfBitmap;
+                        SkASSERT(documentStream->rewind());
+                        bool success = (*pdfRasterizers[i]->fRasterizerFunction)(
+                                documentStream.get(), &pdfBitmap);
+                        if (!success) {
+                            gm_fprintf(stderr, "FAILED to render PDF for %s using renderer %s\n",
+                                       gm->shortName(),
+                                       pdfRasterizers[i]->fName);
+                            continue;
+                        }
+
+                        SkString configName(gRec.fName);
+                        configName.append("-");
+                        configName.append(pdfRasterizers[i]->fName);
+
+                        BitmapAndDigest bitmapAndDigest(pdfBitmap);
+                        errors.add(compare_test_results_to_stored_expectations(
+                                   gm, gRec, configName.c_str(), &bitmapAndDigest));
+
+                        if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) {
+                            path = make_bitmap_filename(writePath, gm->shortName(),
+                                                        configName.c_str(),
+                                                        "", bitmapAndDigest.fDigest);
+                            errors.add(write_bitmap(path, bitmapAndDigest.fBitmap));
+                        }
+                    }
+                } else {
+                    errors.add(kIntentionallySkipped_ErrorType);
+                }
+            }
+
 #else
             bitmap = NULL;  // we don't generate a bitmap rendering of the PDF file
 #endif
@@ -1364,6 +1422,7 @@
 DEFINE_int32(pdfJpegQuality, -1, "Encodes images in JPEG at quality level N, "
              "which can be in range 0-100). N = -1 will disable JPEG compression. "
              "Default is N = 100, maximum quality.");
+DEFINE_bool(useDocumentInsteadOfDevice, false, "Use SkDocument::CreateFoo instead of SkFooDevice.");
 
 static bool encode_to_dct_stream(SkWStream* stream, const SkBitmap& bitmap, const SkIRect& rect) {
     // Filter output of warnings that JPEG is not available for the image.
diff --git a/gyp/pdf.gyp b/gyp/pdf.gyp
index ce4c59f..695ff47 100644
--- a/gyp/pdf.gyp
+++ b/gyp/pdf.gyp
@@ -15,6 +15,7 @@
       'include_dirs': [
         '../include/pdf',
         '../src/core', # needed to get SkGlyphCache.h and SkTextFormatParams.h
+        '../src/pdf',
         '../src/utils', # needed to get SkBitSet.h
       ],
       'sources': [
diff --git a/gyp/pdf.gypi b/gyp/pdf.gypi
index ae4a032..d2650a1 100644
--- a/gyp/pdf.gypi
+++ b/gyp/pdf.gypi
@@ -13,6 +13,8 @@
         '<(skia_src_path)/pdf/SkPDFCatalog.cpp',
         '<(skia_src_path)/pdf/SkPDFCatalog.h',
         '<(skia_src_path)/pdf/SkPDFDevice.cpp',
+        '<(skia_src_path)/pdf/SkPDFDeviceFlattener.cpp',
+        '<(skia_src_path)/pdf/SkPDFDeviceFlattener.h',
         '<(skia_src_path)/pdf/SkPDFDocument.cpp',
         '<(skia_src_path)/pdf/SkPDFFont.cpp',
         '<(skia_src_path)/pdf/SkPDFFont.h',
diff --git a/include/core/SkDraw.h b/include/core/SkDraw.h
index 8642f0a..66c82c9 100644
--- a/include/core/SkDraw.h
+++ b/include/core/SkDraw.h
@@ -97,16 +97,16 @@
     static RectType ComputeRectType(const SkPaint&, const SkMatrix&,
                                     SkPoint* strokeSize);
 
-private:
     void    drawText_asPaths(const char text[], size_t byteLength,
                              SkScalar x, SkScalar y, const SkPaint&) const;
-    void    drawDevMask(const SkMask& mask, const SkPaint&) const;
-    void    drawBitmapAsMask(const SkBitmap&, const SkPaint&) const;
-
     void    drawPosText_asPaths(const char text[], size_t byteLength,
                                 const SkScalar pos[], SkScalar constY,
                                 int scalarsPerPosition, const SkPaint&) const;
 
+private:
+    void    drawDevMask(const SkMask& mask, const SkPaint&) const;
+    void    drawBitmapAsMask(const SkBitmap&, const SkPaint&) const;
+
     /**
      *  Return the current clip bounds, in local coordinates, with slop to account
      *  for antialiasing or hairlines (i.e. device-bounds outset by 1, and then
diff --git a/include/pdf/SkPDFDevice.h b/include/pdf/SkPDFDevice.h
index 207b2b2..a8c6186 100644
--- a/include/pdf/SkPDFDevice.h
+++ b/include/pdf/SkPDFDevice.h
@@ -10,6 +10,7 @@
 #ifndef SkPDFDevice_DEFINED
 #define SkPDFDevice_DEFINED
 
+#include "SkBitmap.h"
 #include "SkCanvas.h"
 #include "SkDevice.h"
 #include "SkPaint.h"
@@ -64,7 +65,7 @@
      *         inverse scale+translate to accommodate the one that SkPDFDevice
      *         always does.
      */
-    // TODO(vandebo): The sizes should be SkSize and not SkISize.
+    // Deprecated, please use SkDocument::CreatePdf() instead.
     SK_API SkPDFDevice(const SkISize& pageSize, const SkISize& contentSize,
                        const SkMatrix& initialTransform);
     SK_API virtual ~SkPDFDevice();
diff --git a/src/doc/SkDocument_PDF.cpp b/src/doc/SkDocument_PDF.cpp
index 27cf2e8..f484881 100644
--- a/src/doc/SkDocument_PDF.cpp
+++ b/src/doc/SkDocument_PDF.cpp
@@ -7,7 +7,7 @@
 
 #include "SkDocument.h"
 #include "SkPDFDocument.h"
-#include "SkPDFDevice.h"
+#include "SkPDFDeviceFlattener.h"
 
 class SkDocument_PDF : public SkDocument {
 public:
@@ -25,19 +25,14 @@
 
 protected:
     virtual SkCanvas* onBeginPage(SkScalar width, SkScalar height,
-                                  const SkRect& content) SK_OVERRIDE {
+                                  const SkRect& trimBox) SK_OVERRIDE {
         SkASSERT(NULL == fCanvas);
         SkASSERT(NULL == fDevice);
 
-        SkISize pageS, contentS;
-        SkMatrix matrix;
+        SkSize mediaBoxSize;
+        mediaBoxSize.set(width, height);
 
-        pageS.set(SkScalarRoundToInt(width), SkScalarRoundToInt(height));
-        contentS.set(SkScalarRoundToInt(content.width()),
-                     SkScalarRoundToInt(content.height()));
-        matrix.setTranslate(content.fLeft, content.fTop);
-
-        fDevice = SkNEW_ARGS(SkPDFDevice, (pageS, contentS, matrix));
+        fDevice = SkNEW_ARGS(SkPDFDeviceFlattener, (mediaBoxSize, &trimBox));
         fCanvas = SkNEW_ARGS(SkCanvas, (fDevice));
         return fCanvas;
     }
@@ -67,7 +62,7 @@
 
 private:
     SkPDFDocument*  fDoc;
-    SkPDFDevice*    fDevice;
+    SkPDFDeviceFlattener* fDevice;
     SkCanvas*       fCanvas;
 };
 
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 6e5e115..2467d9b 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -648,6 +648,11 @@
     void init(const SkClipStack* clipStack, const SkRegion& clipRegion,
               const SkMatrix& matrix, const SkPaint& paint, bool hasText) {
         fDstFormXObject = NULL;
+        // Shape has to be flatten before we get here.
+        if (matrix.hasPerspective()) {
+            NOT_IMPLEMENTED(!matrix.hasPerspective(), false);
+            return;
+        }
         if (paint.getXfermode()) {
             paint.getXfermode()->asMode(&fXfermode);
         }
@@ -2005,22 +2010,101 @@
     return resourceIndex;
 }
 
-void SkPDFDevice::internalDrawBitmap(const SkMatrix& matrix,
+void SkPDFDevice::internalDrawBitmap(const SkMatrix& origMatrix,
                                      const SkClipStack* clipStack,
-                                     const SkRegion& clipRegion,
-                                     const SkBitmap& bitmap,
+                                     const SkRegion& origClipRegion,
+                                     const SkBitmap& origBitmap,
                                      const SkIRect* srcRect,
                                      const SkPaint& paint) {
+    SkMatrix matrix = origMatrix;
+    SkRegion perspectiveBounds;
+    const SkRegion* clipRegion = &origClipRegion;
+    SkBitmap perspectiveBitmap;
+    const SkBitmap* bitmap = &origBitmap;
+    SkBitmap tmpSubsetBitmap;
+
+    // Rasterize the bitmap using perspective in a new bitmap.
+    if (origMatrix.hasPerspective()) {
+        SkBitmap* subsetBitmap;
+        if (srcRect) {
+            if (!origBitmap.extractSubset(&tmpSubsetBitmap, *srcRect)) {
+               return;
+            }
+            subsetBitmap = &tmpSubsetBitmap;
+        } else {
+            subsetBitmap = &tmpSubsetBitmap;
+            *subsetBitmap = origBitmap;
+        }
+        srcRect = NULL;
+
+        // Transform the bitmap in the new space.
+        SkPath perspectiveOutline;
+        perspectiveOutline.addRect(
+                SkRect::MakeWH(SkIntToScalar(subsetBitmap->width()),
+                               SkIntToScalar(subsetBitmap->height())));
+        perspectiveOutline.transform(origMatrix);
+
+        // TODO(edisonn): perf - use current clip too.
+        // Retrieve the bounds of the new shape.
+        SkRect bounds = perspectiveOutline.getBounds();
+
+        // TODO(edisonn): add DPI settings. Currently 1 pixel/point, which does
+        // not look great, but it is not producing large PDFs.
+
+        // TODO(edisonn): A better approach would be to use a bitmap shader
+        // (in clamp mode) and draw a rect over the entire bounding box. Then
+        // intersect perspectiveOutline to the clip. That will avoid introducing
+        // alpha to the image while still giving good behavior at the edge of
+        // the image.  Avoiding alpha will reduce the pdf size and generation
+        // CPU time some.
+
+        perspectiveBitmap.setConfig(SkBitmap::kARGB_8888_Config,
+                                    SkScalarCeilToInt(bounds.width()),
+                                    SkScalarCeilToInt(bounds.height()));
+        perspectiveBitmap.allocPixels();
+        perspectiveBitmap.eraseColor(SK_ColorTRANSPARENT);
+
+        // FIXME: Once we merge to latest Skia, this should be an SkBitmapDevice.
+        SkDevice device(perspectiveBitmap);
+        SkCanvas canvas(&device);
+
+        SkScalar deltaX = bounds.left();
+        SkScalar deltaY = bounds.top();
+
+        SkMatrix offsetMatrix = origMatrix;
+        offsetMatrix.postTranslate(-deltaX, -deltaY);
+
+        // Translate the draw in the new canvas, so we perfectly fit the
+        // shape in the bitmap.
+        canvas.setMatrix(offsetMatrix);
+
+        canvas.drawBitmap(*subsetBitmap, SkIntToScalar(0), SkIntToScalar(0));
+
+        // Make sure the final bits are in the bitmap.
+        canvas.flush();
+
+        // In the new space, we use the identity matrix translated.
+        matrix.setTranslate(deltaX, deltaY);
+        perspectiveBounds.setRect(
+                SkIRect::MakeXYWH(SkScalarFloorToInt(bounds.x()),
+                                  SkScalarFloorToInt(bounds.y()),
+                                  SkScalarCeilToInt(bounds.width()),
+                                  SkScalarCeilToInt(bounds.height())));
+        clipRegion = &perspectiveBounds;
+        srcRect = NULL;
+        bitmap = &perspectiveBitmap;
+    }
+
     SkMatrix scaled;
     // Adjust for origin flip.
     scaled.setScale(SK_Scalar1, -SK_Scalar1);
     scaled.postTranslate(0, SK_Scalar1);
     // Scale the image up from 1x1 to WxH.
-    SkIRect subset = SkIRect::MakeWH(bitmap.width(), bitmap.height());
+    SkIRect subset = SkIRect::MakeWH(bitmap->width(), bitmap->height());
     scaled.postScale(SkIntToScalar(subset.width()),
                      SkIntToScalar(subset.height()));
     scaled.postConcat(matrix);
-    ScopedContentEntry content(this, clipStack, clipRegion, scaled, paint);
+    ScopedContentEntry content(this, clipStack, *clipRegion, scaled, paint);
     if (!content.entry()) {
         return;
     }
@@ -2029,7 +2113,7 @@
         return;
     }
 
-    SkPDFImage* image = SkPDFImage::CreateImage(bitmap, subset, fEncoder);
+    SkPDFImage* image = SkPDFImage::CreateImage(*bitmap, subset, fEncoder);
     if (!image) {
         return;
     }
diff --git a/src/pdf/SkPDFDeviceFlattener.cpp b/src/pdf/SkPDFDeviceFlattener.cpp
new file mode 100644
index 0000000..67e075c
--- /dev/null
+++ b/src/pdf/SkPDFDeviceFlattener.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkPDFDeviceFlattener.h"
+
+#include "SkDraw.h"
+#include "SkShader.h"
+
+static SkISize SkSizeToISize(const SkSize& size) {
+    return SkISize::Make(SkScalarRoundToInt(size.width()), SkScalarRoundToInt(size.height()));
+}
+
+SkPDFDeviceFlattener::SkPDFDeviceFlattener(const SkSize& pageSize, const SkRect* trimBox)
+            : SkPDFDevice(SkSizeToISize(pageSize),
+                          SkSizeToISize(pageSize),
+                          SkMatrix::I()) {
+    // TODO(edisonn): store the trimbox on emit.
+}
+
+SkPDFDeviceFlattener::~SkPDFDeviceFlattener() {
+}
+
+static void flattenPaint(const SkDraw& d, SkPaint* paint) {
+    if (paint->getShader()) {
+        SkMatrix local = paint->getShader()->getLocalMatrix();
+        local.preConcat(*d.fMatrix);
+        paint->getShader()->setLocalMatrix(local);
+    }
+}
+
+void SkPDFDeviceFlattener::drawPoints(const SkDraw& d, SkCanvas::PointMode mode,
+                                      size_t count, const SkPoint points[],
+                                      const SkPaint& paint) {
+    if (!mustFlatten(d)) {
+        INHERITED::drawPoints(d, mode, count, points, paint);
+        return;
+    }
+
+    SkPaint paintFlatten(paint);
+    flattenPaint(d, &paintFlatten);
+
+    SkPoint* flattenedPoints = SkNEW_ARRAY(SkPoint, count);
+    d.fMatrix->mapPoints(flattenedPoints, points, count);
+    SkDraw draw(d);
+    SkMatrix identity = SkMatrix::I();
+    draw.fMatrix = &identity;
+    INHERITED::drawPoints(draw, mode, count, flattenedPoints, paintFlatten);
+    SkDELETE_ARRAY(flattenedPoints);
+}
+
+void SkPDFDeviceFlattener::drawRect(const SkDraw& d, const SkRect& r, const SkPaint& paint) {
+    if (!mustFlatten(d)) {
+        INHERITED::drawRect(d, r, paint);
+        return;
+    }
+
+    SkPath path;
+    path.addRect(r);
+    path.transform(*d.fMatrix);
+    SkDraw draw(d);
+    SkMatrix matrix = SkMatrix::I();
+    draw.fMatrix = &matrix;
+
+    SkPaint paintFlatten(paint);
+    flattenPaint(d, &paintFlatten);
+
+    INHERITED::drawPath(draw, path, paintFlatten, NULL, true);
+}
+
+void SkPDFDeviceFlattener::drawPath(const SkDraw& d, const SkPath& origPath,
+                                    const SkPaint& paint, const SkMatrix* prePathMatrix,
+                                    bool pathIsMutable) {
+    if (!mustFlatten(d) && !(prePathMatrix && prePathMatrix->hasPerspective())) {
+        INHERITED::drawPath(d, origPath, paint, prePathMatrix, pathIsMutable);
+        return;
+    }
+
+    SkPath* pathPtr = (SkPath*)&origPath;
+    SkPath tmpPath;
+
+    if (!pathIsMutable) {
+        tmpPath = origPath;
+        pathPtr = &tmpPath;
+    }
+
+    if (prePathMatrix) {
+        pathPtr->transform(*prePathMatrix);
+    }
+
+    SkPaint paintFlatten(paint);
+    flattenPaint(d, &paintFlatten);
+
+    bool fill = paintFlatten.getFillPath(*pathPtr, &tmpPath);
+    SkDEBUGCODE(pathPtr = (SkPath*)0x12345678);  // Don't use pathPtr after this point.
+
+    paintFlatten.setPathEffect(NULL);
+    if (fill) {
+        paintFlatten.setStyle(SkPaint::kFill_Style);
+    } else {
+        paintFlatten.setStyle(SkPaint::kStroke_Style);
+        paintFlatten.setStrokeWidth(0);
+    }
+
+    tmpPath.transform(*d.fMatrix);
+
+    SkDraw draw(d);
+    SkMatrix matrix = SkMatrix::I();
+    draw.fMatrix = &matrix;
+
+    INHERITED::drawPath(draw, tmpPath, paintFlatten, NULL, true);
+}
+
+void SkPDFDeviceFlattener::drawText(const SkDraw& d, const void* text, size_t len,
+                                    SkScalar x, SkScalar y, const SkPaint& paint) {
+    if (mustPathText(d, paint)) {
+        d.drawText_asPaths((const char*)text, len, x, y, paint);
+        return;
+    }
+
+    INHERITED::drawText(d, text, len, x, y, paint);
+}
+
+void SkPDFDeviceFlattener::drawPosText(const SkDraw& d, const void* text, size_t len,
+                                       const SkScalar pos[], SkScalar constY,
+                                       int scalarsPerPos, const SkPaint& paint) {
+    if (mustPathText(d, paint)) {
+        d.drawPosText_asPaths((const char*)text, len, pos, constY, scalarsPerPos, paint);
+        return;
+    }
+    INHERITED::drawPosText(d, text, len, pos, constY,scalarsPerPos, paint);
+}
+
+void SkPDFDeviceFlattener::drawTextOnPath(const SkDraw& d, const void* text, size_t len,
+                                          const SkPath& path, const SkMatrix* matrix,
+                                          const SkPaint& paint) {
+    if (mustPathText(d, paint) || (matrix && matrix->hasPerspective())) {
+        d.drawTextOnPath((const char*)text, len, path, matrix, paint);
+        return;
+    }
+    INHERITED::drawTextOnPath(d, text, len, path, matrix, paint);
+}
+
+bool SkPDFDeviceFlattener::mustFlatten(const SkDraw& d) const {
+    // TODO(edisonn): testability, add flag to force return true.
+    return d.fMatrix->hasPerspective();
+}
+
+bool SkPDFDeviceFlattener::mustPathText(const SkDraw& d, const SkPaint&) {
+    // TODO(edisonn): testability, add flag to force return true.
+    // TODO(edisonn): TBD: How to flatten MaskFilter.
+    return d.fMatrix->hasPerspective();
+}
diff --git a/src/pdf/SkPDFDeviceFlattener.h b/src/pdf/SkPDFDeviceFlattener.h
new file mode 100644
index 0000000..f1047db
--- /dev/null
+++ b/src/pdf/SkPDFDeviceFlattener.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkPDFDeviceFlattener_DEFINED
+#define SkPDFDeviceFlattener_DEFINED
+
+#include "SkPDFDevice.h"
+
+
+/** \class SkPDFDeviceFlattener
+
+    The PDF Device Flattener is used to flatten features without native support in PDF.
+    For now, the only one implemented is Perspective.
+
+    TODO(edisonn): Rename the class once we know all the things it will do.
+*/
+class SkPDFDeviceFlattener : public SkPDFDevice {
+private:
+    typedef SkPDFDevice INHERITED;
+
+    SK_API SkPDFDeviceFlattener(const SkSize& pageSize, const SkRect* trimBox = NULL);
+
+public:
+    SK_API virtual ~SkPDFDeviceFlattener();
+
+    virtual void drawPoints(const SkDraw&, SkCanvas::PointMode mode,
+                            size_t count, const SkPoint[],
+                            const SkPaint& paint) SK_OVERRIDE;
+    virtual void drawRect(const SkDraw&, const SkRect& r, const SkPaint& paint);
+    virtual void drawPath(const SkDraw&, const SkPath& origpath,
+                          const SkPaint& paint, const SkMatrix* prePathMatrix,
+                          bool pathIsMutable) SK_OVERRIDE;
+    virtual void drawText(const SkDraw&, const void* text, size_t len,
+                          SkScalar x, SkScalar y, const SkPaint&) SK_OVERRIDE;
+    virtual void drawPosText(const SkDraw&, const void* text, size_t len,
+                             const SkScalar pos[], SkScalar constY,
+                             int scalarsPerPos, const SkPaint&) SK_OVERRIDE;
+    virtual void drawTextOnPath(const SkDraw&, const void* text, size_t len,
+                                const SkPath& path, const SkMatrix* matrix,
+                                const SkPaint& paint) SK_OVERRIDE;
+private:
+
+    bool mustFlatten(const SkDraw& d) const;
+    bool mustPathText(const SkDraw& d, const SkPaint& paint);
+
+    friend class SkDocument_PDF;
+};
+
+#endif  // SkPDFDeviceFlattener_DEFINED
diff --git a/src/pdf/SkPDFShader.cpp b/src/pdf/SkPDFShader.cpp
index 9394f1b..c5d4a5d 100644
--- a/src/pdf/SkPDFShader.cpp
+++ b/src/pdf/SkPDFShader.cpp
@@ -9,7 +9,6 @@
 
 #include "SkPDFShader.h"
 
-#include "SkCanvas.h"
 #include "SkData.h"
 #include "SkPDFCatalog.h"
 #include "SkPDFDevice.h"
@@ -204,16 +203,73 @@
     }
 }
 
-static SkString linearCode(const SkShader::GradientInfo& info) {
-    SkString function("{pop\n");  // Just ditch the y value.
+/**
+ *  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 SkString apply_perspective_to_coordinates(
+        const SkMatrix& inversePerspectiveMatrix) {
+    SkString code;
+    if (!inversePerspectiveMatrix.hasPerspective()) {
+        return code;
+    }
+
+    // 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.append(" dup ");               // x y y
+    code.appendScalar(p1);              // x y y p1
+    code.append(" mul "                 // x y y*p1
+                " 2 index ");           // x y y*p1 x
+    code.appendScalar(p0);              // x y y p1 x p0
+    code.append(" mul ");               // x y y*p1 x*p0
+    code.appendScalar(p2);              // x y y p1 x*p0 p2
+    code.append(" 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)
+    return code;
+}
+
+static SkString linearCode(const SkShader::GradientInfo& info,
+                           const SkMatrix& perspectiveRemover) {
+    SkString function("{");
+
+    function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
+    function.append("pop\n");  // Just ditch the y value.
     tileModeCode(info.fTileMode, &function);
     gradientFunctionCode(info, &function);
     function.append("}");
     return function;
 }
 
-static SkString radialCode(const SkShader::GradientInfo& info) {
+static SkString radialCode(const SkShader::GradientInfo& info,
+                           const SkMatrix& perspectiveRemover) {
     SkString function("{");
+
+    function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
     // Find the distance from the origin.
     function.append("dup "      // x y y
                     "mul "      // x y^2
@@ -233,7 +289,8 @@
    with one simplification, the coordinate space has been scaled so that
    Dr = 1.  This means we don't need to scale the entire equation by 1/Dr^2.
  */
-static SkString twoPointRadialCode(const SkShader::GradientInfo& info) {
+static SkString twoPointRadialCode(const SkShader::GradientInfo& info,
+                                   const SkMatrix& perspectiveRemover) {
     SkScalar dx = info.fPoint[0].fX - info.fPoint[1].fX;
     SkScalar dy = info.fPoint[0].fY - info.fPoint[1].fY;
     SkScalar sr = info.fRadius[0];
@@ -243,6 +300,9 @@
     // 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.
     SkString function("{");
+
+    function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
     function.append("2 copy ");
 
     // Calculate -b and b^2.
@@ -280,7 +340,8 @@
 /* Conical gradient shader, based on the Canvas spec for radial gradients
    See: http://www.w3.org/TR/2dcontext/#dom-context-2d-createradialgradient
  */
-static SkString twoPointConicalCode(const SkShader::GradientInfo& info) {
+static SkString twoPointConicalCode(const SkShader::GradientInfo& info,
+                                    const SkMatrix& perspectiveRemover) {
     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];
@@ -294,6 +355,9 @@
     // 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.
     SkString function("{");
+
+    function.append(apply_perspective_to_coordinates(perspectiveRemover));
+
     function.append("2 copy ");
 
     // Calculate b and b^2; b = -2 * (y * dy + x * dx + r0 * dr).
@@ -389,7 +453,8 @@
     return function;
 }
 
-static SkString sweepCode(const SkShader::GradientInfo& info) {
+static SkString sweepCode(const SkShader::GradientInfo& info,
+                          const SkMatrix& perspectiveRemover) {
     SkString function("{exch atan 360 div\n");
     tileModeCode(info.fTileMode, &function);
     gradientFunctionCode(info, &function);
@@ -719,10 +784,49 @@
                                  SkMatrix::I());
 }
 
+// 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;
+}
+
 SkPDFFunctionShader::SkPDFFunctionShader(SkPDFShader::State* state)
         : SkPDFDict("Pattern"),
           fState(state) {
-    SkString (*codeFunction)(const SkShader::GradientInfo& info) = NULL;
+    SkString (*codeFunction)(const SkShader::GradientInfo& info,
+                             const SkMatrix& perspectiveRemover) = NULL;
     SkPoint transformPoints[2];
 
     // Depending on the type of the gradient, we want to transform the
@@ -774,10 +878,24 @@
     // the gradient can be drawn on on the unit segment.
     SkMatrix mapperMatrix;
     unitToPointsMatrix(transformPoints, &mapperMatrix);
+
     SkMatrix finalMatrix = fState.get()->fCanvasTransform;
     finalMatrix.preConcat(fState.get()->fShaderTransform);
     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;
+        }
+    }
+
     SkRect bbox;
     bbox.set(fState.get()->fBBox);
     if (!transformBBox(finalMatrix, &bbox)) {
@@ -806,9 +924,9 @@
             inverseMapperMatrix.mapRadius(info->fRadius[0]);
         twoPointRadialInfo.fRadius[1] =
             inverseMapperMatrix.mapRadius(info->fRadius[1]);
-        functionCode = codeFunction(twoPointRadialInfo);
+        functionCode = codeFunction(twoPointRadialInfo, perspectiveInverseOnly);
     } else {
-        functionCode = codeFunction(*info);
+        functionCode = codeFunction(*info, perspectiveInverseOnly);
     }
 
     SkAutoTUnref<SkPDFDict> pdfShader(new SkPDFDict);
diff --git a/tools/PdfRenderer.cpp b/tools/PdfRenderer.cpp
index 704cbea..890abde 100644
--- a/tools/PdfRenderer.cpp
+++ b/tools/PdfRenderer.cpp
@@ -13,7 +13,7 @@
 
 namespace sk_tools {
 
-void PdfRenderer::init(SkPicture* pict) {
+void PdfRenderer::init(SkPicture* pict, SkWStream* stream) {
     SkASSERT(NULL == fPicture);
     SkASSERT(NULL == fCanvas.get());
     if (fPicture != NULL || NULL != fCanvas.get()) {
@@ -26,44 +26,35 @@
     }
 
     fPicture = pict;
-    fCanvas.reset(this->setupCanvas());
+    fCanvas.reset(this->setupCanvas(stream, pict->width(), pict->height()));
 }
 
-SkCanvas* PdfRenderer::setupCanvas() {
-    return this->setupCanvas(fPicture->width(), fPicture->height());
-}
+SkCanvas* PdfRenderer::setupCanvas(SkWStream* stream, int width, int height) {
+    fPdfDoc.reset(SkDocument::CreatePDF(stream, NULL, fEncoder));
 
-SkCanvas* PdfRenderer::setupCanvas(int width, int height) {
-    SkISize pageSize = SkISize::Make(width, height);
-    fPDFDevice = SkNEW_ARGS(SkPDFDevice, (pageSize, pageSize, SkMatrix::I()));
-    fPDFDevice->setDCTEncoder(fEncoder);
-    return SkNEW_ARGS(SkCanvas, (fPDFDevice));
+    SkCanvas* canvas = fPdfDoc->beginPage(SkIntToScalar(width), SkIntToScalar(height));
+    canvas->ref();
+
+    return canvas;
 }
 
 void PdfRenderer::end() {
     fPicture = NULL;
     fCanvas.reset(NULL);
-    if (fPDFDevice) {
-        SkDELETE(fPDFDevice);
-        fPDFDevice = NULL;
-    }
+    fPdfDoc.reset(NULL);
 }
 
-void PdfRenderer::write(SkWStream* stream) const {
-    SkPDFDocument doc;
-    doc.appendPage(fPDFDevice);
-    doc.emitPDF(stream);
-}
-
-void SimplePdfRenderer::render() {
+bool SimplePdfRenderer::render() {
     SkASSERT(fCanvas.get() != NULL);
     SkASSERT(fPicture != NULL);
     if (NULL == fCanvas.get() || NULL == fPicture) {
-        return;
+        return false;
     }
 
     fCanvas->drawPicture(*fPicture);
     fCanvas->flush();
+
+    return fPdfDoc->close();
 }
 
 }
diff --git a/tools/PdfRenderer.h b/tools/PdfRenderer.h
index d2d1a5c..2262dbe 100644
--- a/tools/PdfRenderer.h
+++ b/tools/PdfRenderer.h
@@ -13,8 +13,8 @@
 // An SkPicture can be built manually, or read from an SKP file.
 //
 
+#include "SkDocument.h"
 #include "SkMath.h"
-#include "SkPDFDevice.h"
 #include "SkPicture.h"
 #include "SkTypes.h"
 #include "SkTDArray.h"
@@ -23,32 +23,32 @@
 
 class SkBitmap;
 class SkCanvas;
+class SkWStream;
 
 namespace sk_tools {
 
 class PdfRenderer : public SkRefCnt {
 public:
-    virtual void init(SkPicture* pict);
+    virtual void init(SkPicture* pict, SkWStream* stream);
     virtual void setup() {}
-    virtual void render() = 0;
+    virtual bool render() = 0;
     virtual void end();
 
     PdfRenderer(EncodeToDCTStream encoder)
         : fPicture(NULL)
-        , fPDFDevice(NULL)
         , fEncoder(encoder)
+        , fPDFDoc(NULL)
         {}
 
     void write(SkWStream* stream) const;
 
 protected:
-    SkCanvas* setupCanvas();
-    SkCanvas* setupCanvas(int width, int height);
+    SkCanvas* setupCanvas(SkWStream* stream, int width, int height);
 
     SkAutoTUnref<SkCanvas> fCanvas;
     SkPicture* fPicture;
-    SkPDFDevice* fPDFDevice;
     EncodeToDCTStream fEncoder;
+    SkAutoTUnref<SkDocument> fPdfDoc;
 
 private:
     typedef SkRefCnt INHERITED;
@@ -58,7 +58,7 @@
 public:
     SimplePdfRenderer(EncodeToDCTStream encoder)
         : PdfRenderer(encoder) {}
-    virtual void render() SK_OVERRIDE;
+    virtual bool render() SK_OVERRIDE;
 
 private:
     typedef PdfRenderer INHERITED;
diff --git a/tools/render_pdfs_main.cpp b/tools/render_pdfs_main.cpp
index f443502..785f7b2 100644
--- a/tools/render_pdfs_main.cpp
+++ b/tools/render_pdfs_main.cpp
@@ -127,28 +127,24 @@
  * @param inputFilename The skp file that was read.
  * @param renderer The object responsible to write the pdf file.
  */
-static bool write_output(const SkString& outputDir,
-                         const SkString& inputFilename,
-                         const sk_tools::PdfRenderer& renderer) {
+static SkWStream* open_stream(const SkString& outputDir,
+                              const SkString& inputFilename) {
     if (outputDir.isEmpty()) {
-        SkDynamicMemoryWStream stream;
-        renderer.write(&stream);
-        return true;
+        return SkNEW(SkDynamicMemoryWStream);
     }
 
     SkString outputPath;
     if (!make_output_filepath(&outputPath, outputDir, inputFilename)) {
-        return false;
+        return NULL;
     }
 
-    SkFILEWStream stream(outputPath.c_str());
-    if (!stream.isValid()) {
+    SkFILEWStream* stream = SkNEW_ARGS(SkFILEWStream, (outputPath.c_str()));
+    if (!stream->isValid()) {
         SkDebugf("Could not write to file %s\n", outputPath.c_str());
-        return false;
+        return NULL;
     }
-    renderer.write(&stream);
 
-    return true;
+    return stream;
 }
 
 /** Reads an skp file, renders it to pdf and writes the output to a pdf file
@@ -178,13 +174,19 @@
     SkDebugf("exporting... [%i %i] %s\n", picture->width(), picture->height(),
              inputPath.c_str());
 
-    renderer.init(picture);
+    SkWStream* stream(open_stream(outputDir, inputFilename));
 
-    renderer.render();
+    if (!stream) {
+        return false;
+    }
 
-    bool success = write_output(outputDir, inputFilename, renderer);
+    renderer.init(picture, stream);
+
+    bool success = renderer.render();
+    SkDELETE(stream);
 
     renderer.end();
+
     return success;
 }