[PDF] Add support for SrcIn, SrcOut, DstIn, DstOut xfermodes.

This change uses the soft mask (aka soft clip) functionality of PDF to implement the xfermodes.  It has to put existing content (dst) into a form xobject as well as putting the new (src) content into a different form xobject.  It then draws one of them with the other as the soft mask.
To accomplish this, we add a call to finishContentEntry after each call to setUpContentEntry - this is kind of a hack, but I don't see a better way to extract src.
Unfortunately, soft mask is specified in the Graphic State PDF object (and not in the form xobject), so when handling one of these modes, we add a one time GS object to set the soft mask and invoke a simple GS to reset the soft mask when done.

Review URL: http://codereview.appspot.com/4496041

git-svn-id: http://skia.googlecode.com/svn/trunk@1320 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/src/pdf/SkPDFDevice.cpp b/src/pdf/SkPDFDevice.cpp
index 1a3324a..6734c0e 100644
--- a/src/pdf/SkPDFDevice.cpp
+++ b/src/pdf/SkPDFDevice.cpp
@@ -423,9 +423,7 @@
     }
 
     if (state.fGraphicStateIndex != currentEntry()->fGraphicStateIndex) {
-        fContentStream->writeText("/G");
-        fContentStream->writeDecAsText(state.fGraphicStateIndex);
-        fContentStream->writeText(" gs\n");
+        SkPDFUtils::ApplyGraphicState(state.fGraphicStateIndex, fContentStream);
         currentEntry()->fGraphicStateIndex = state.fGraphicStateIndex;
     }
 
@@ -556,6 +554,7 @@
     }
 
     internalDrawPaint(paint);
+    finishContentEntry(paint);
 }
 
 void SkPDFDevice::drawPaint(const SkDraw& d, const SkPaint& paint) {
@@ -566,6 +565,7 @@
     }
 
     internalDrawPaint(newPaint);
+    finishContentEntry(newPaint);
 }
 
 void SkPDFDevice::internalDrawPaint(const SkPaint& paint) {
@@ -651,6 +651,7 @@
         default:
             SkASSERT(false);
     }
+    finishContentEntry(*paint);
 }
 
 void SkPDFDevice::drawRect(const SkDraw& d, const SkRect& r,
@@ -676,6 +677,7 @@
     SkPDFUtils::AppendRectangle(r, &fCurrentContentEntry->fContent);
     SkPDFUtils::PaintPath(paint.getStyle(), SkPath::kWinding_FillType,
                           &fCurrentContentEntry->fContent);
+    finishContentEntry(paint);
 }
 
 void SkPDFDevice::drawPath(const SkDraw& d, const SkPath& path,
@@ -703,6 +705,7 @@
     SkPDFUtils::EmitPath(path, &fCurrentContentEntry->fContent);
     SkPDFUtils::PaintPath(paint.getStyle(), path.getFillType(),
                           &fCurrentContentEntry->fContent);
+    finishContentEntry(paint);
 }
 
 void SkPDFDevice::drawBitmap(const SkDraw& d, const SkBitmap& bitmap,
@@ -798,6 +801,7 @@
             drawRect(d, r, paint);
         }
     }
+    finishContentEntry(textPaint);
 }
 
 void SkPDFDevice::drawPosText(const SkDraw& d, const void* text, size_t len,
@@ -849,6 +853,7 @@
         fCurrentContentEntry->fContent.writeText(" Tj\n");
     }
     fCurrentContentEntry->fContent.writeText("ET\n");
+    finishContentEntry(textPaint);
 }
 
 void SkPDFDevice::drawTextOnPath(const SkDraw& d, const void* text, size_t len,
@@ -889,10 +894,9 @@
     SkPDFDevice* pdfDevice = static_cast<SkPDFDevice*>(device);
     SkPDFFormXObject* xobject = new SkPDFFormXObject(pdfDevice);
     fXObjectResources.push(xobject);  // Transfer reference.
-    fCurrentContentEntry->fContent.writeText("/X");
-    fCurrentContentEntry->fContent.writeDecAsText(
-            fXObjectResources.count() - 1);
-    fCurrentContentEntry->fContent.writeText(" Do\n");
+    SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
+                                &fCurrentContentEntry->fContent);
+    finishContentEntry(paint);
 }
 
 const SkRefPtr<SkPDFDict>& SkPDFDevice::getResourceDict() {
@@ -1038,6 +1042,14 @@
     return result;
 }
 
+void SkPDFDevice::createFormXObjectFromDevice(
+        SkRefPtr<SkPDFFormXObject>* xobject) {
+    *xobject = new SkPDFFormXObject(this);
+    (*xobject)->unref();  // SkRefPtr and new both took a reference.
+    cleanUp();  // Reset this device to have no content.
+    init();
+}
+
 bool SkPDFDevice::setUpContentEntry(const SkClipStack* clipStack,
                                     const SkRegion& clipRegion,
                                     const SkMatrix& matrix,
@@ -1081,9 +1093,19 @@
         fCurrentContentEntry = fContentEntries.get();
     }
 
-    // TODO(vandebo) For the following modes, we need to calculate various
-    // operations between the source and destination shape:
-    // SrcIn, DstIn, SrcOut, DstOut, SrcAtop, DestAtop, Xor.
+    // For the following modes, we use both source and destination, but
+    // we use one as a smask for the other, so we have to make form xobjects
+    // out of both of them: SrcIn, DstIn, SrcOut, DstOut.
+    if (xfermode == SkXfermode::kSrcIn_Mode ||
+            xfermode == SkXfermode::kDstIn_Mode ||
+            xfermode == SkXfermode::kSrcOut_Mode ||
+            xfermode == SkXfermode::kDstOut_Mode) {
+        SkASSERT(fDstFormXObject.get() == NULL);
+        createFormXObjectFromDevice(&fDstFormXObject);
+    }
+
+    // TODO(vandebo) Figure out how/if we can handle the following modes:
+    // SrcAtop, DestAtop, Xor.
 
     // These xfer modes don't draw source at all.
     if (xfermode == SkXfermode::kClear_Mode ||
@@ -1135,6 +1157,57 @@
     return setUpContentEntry(clipStack, clipRegion, matrix, paint, true);
 }
 
+void SkPDFDevice::finishContentEntry(const SkPaint& paint) {
+    SkXfermode::Mode xfermode = SkXfermode::kSrcOver_Mode;
+    if (paint.getXfermode()) {
+        paint.getXfermode()->asMode(&xfermode);
+    }
+    if (xfermode != SkXfermode::kSrcIn_Mode &&
+            xfermode != SkXfermode::kDstIn_Mode &&
+            xfermode != SkXfermode::kSrcOut_Mode &&
+            xfermode != SkXfermode::kDstOut_Mode) {
+        SkASSERT(fDstFormXObject.get() == NULL);
+        return;
+    }
+
+    SkRefPtr<SkPDFFormXObject> srcFormXObject;
+    createFormXObjectFromDevice(&srcFormXObject);
+
+    SkMatrix identity;
+    identity.reset();
+    SkPaint stockPaint;
+    setUpContentEntry(&fExistingClipStack, fExistingClipRegion, identity,
+                      stockPaint);
+
+    SkRefPtr<SkPDFGraphicState> sMaskGS;
+    if (xfermode == SkXfermode::kSrcIn_Mode ||
+            xfermode == SkXfermode::kSrcOut_Mode) {
+        sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
+                fDstFormXObject.get(), xfermode == SkXfermode::kSrcOut_Mode);
+        fXObjectResources.push(srcFormXObject.get());
+        srcFormXObject->ref();
+    } else {
+        sMaskGS = SkPDFGraphicState::getSMaskGraphicState(
+                srcFormXObject.get(), xfermode == SkXfermode::kDstOut_Mode);
+        fXObjectResources.push(fDstFormXObject.get());
+        fDstFormXObject->ref();
+    }
+    sMaskGS->unref();  // SkRefPtr and getSMaskGraphicState both took a ref.
+    SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
+                                  &fCurrentContentEntry->fContent);
+
+    SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
+                                &fCurrentContentEntry->fContent);
+
+    sMaskGS = SkPDFGraphicState::getNoSMaskGraphicState();
+    sMaskGS->unref();  // SkRefPtr and getSMaskGraphicState both took a ref.
+    SkPDFUtils::ApplyGraphicState(addGraphicStateResource(sMaskGS.get()),
+                                  &fCurrentContentEntry->fContent);
+
+    fDstFormXObject = NULL;
+    finishContentEntry(stockPaint);
+}
+
 void SkPDFDevice::populateGraphicStateEntryFromPaint(
         const SkMatrix& matrix,
         const SkClipStack& clipStack,
@@ -1212,14 +1285,7 @@
         newGraphicState = SkPDFGraphicState::getGraphicStateForPaint(newPaint);
     }
     newGraphicState->unref();  // getGraphicState and SkRefPtr both took a ref.
-    // newGraphicState has been canonicalized so we can directly compare
-    // pointers.
-    int resourceIndex = fGraphicStateResources.find(newGraphicState.get());
-    if (resourceIndex < 0) {
-        resourceIndex = fGraphicStateResources.count();
-        fGraphicStateResources.push(newGraphicState.get());
-        newGraphicState->ref();
-    }
+    int resourceIndex = addGraphicStateResource(newGraphicState.get());
     entry->fGraphicStateIndex = resourceIndex;
 
     if (hasText) {
@@ -1230,6 +1296,18 @@
     }
 }
 
+int SkPDFDevice::addGraphicStateResource(SkPDFGraphicState* gs) {
+    // Assumes that gs has been canonicalized (so we can directly compare
+    // pointers).
+    int result = fGraphicStateResources.find(gs);
+    if (result < 0) {
+        result = fGraphicStateResources.count();
+        fGraphicStateResources.push(gs);
+        gs->ref();
+    }
+    return result;
+}
+
 void SkPDFDevice::updateFont(const SkPaint& paint, uint16_t glyphID) {
     SkTypeface* typeface = paint.getTypeface();
     if (fCurrentContentEntry->fState.fFont == NULL ||
@@ -1299,8 +1377,7 @@
     }
 
     fXObjectResources.push(image);  // Transfer reference.
-    fCurrentContentEntry->fContent.writeText("/X");
-    fCurrentContentEntry->fContent.writeDecAsText(
-            fXObjectResources.count() - 1);
-    fCurrentContentEntry->fContent.writeText(" Do\n");
+    SkPDFUtils::DrawFormXObject(fXObjectResources.count() - 1,
+                                &fCurrentContentEntry->fContent);
+    finishContentEntry(paint);
 }
diff --git a/src/pdf/SkPDFGraphicState.cpp b/src/pdf/SkPDFGraphicState.cpp
index e5badae..48203f6 100644
--- a/src/pdf/SkPDFGraphicState.cpp
+++ b/src/pdf/SkPDFGraphicState.cpp
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
+#include "SkPDFFormXObject.h"
 #include "SkPDFGraphicState.h"
 #include "SkPDFUtils.h"
 #include "SkStream.h"
 #include "SkTypes.h"
 
-namespace {
-
-const char* blendModeFromXfermode(SkXfermode::Mode mode) {
+static const char* blend_mode_from_xfermode(SkXfermode::Mode mode) {
     switch (mode) {
         case SkXfermode::kSrcOver_Mode:    return "Normal";
         case SkXfermode::kMultiply_Mode:   return "Multiply";
@@ -41,13 +40,13 @@
         case SkXfermode::kSrc_Mode:
         case SkXfermode::kDst_Mode:
         case SkXfermode::kDstOver_Mode:
-            return "Normal";
-
-        // TODO(vandebo) Figure out if we can support more of these modes.
         case SkXfermode::kSrcIn_Mode:
         case SkXfermode::kDstIn_Mode:
         case SkXfermode::kSrcOut_Mode:
         case SkXfermode::kDstOut_Mode:
+            return "Normal";
+
+        // TODO(vandebo) Figure out if we can support more of these modes.
         case SkXfermode::kSrcATop_Mode:
         case SkXfermode::kDstATop_Mode:
         case SkXfermode::kXor_Mode:
@@ -57,13 +56,23 @@
     return NULL;
 }
 
-}
-
 SkPDFGraphicState::~SkPDFGraphicState() {
     SkAutoMutexAcquire lock(canonicalPaintsMutex());
-    int index = find(fPaint);
-    SkASSERT(index >= 0);
-    canonicalPaints().removeShuffle(index);
+    if (!fSMask) {
+        int index = find(fPaint);
+        SkASSERT(index >= 0);
+        canonicalPaints().removeShuffle(index);
+    }
+    fResources.unrefAll();
+}
+
+void SkPDFGraphicState::getResources(SkTDArray<SkPDFObject*>* resourceList) {
+    resourceList->setReserve(resourceList->count() + fResources.count());
+    for (int i = 0; i < fResources.count(); i++) {
+        resourceList->push(fResources[i]);
+        fResources[i]->ref();
+        fResources[i]->getResources(resourceList);
+    }
 }
 
 void SkPDFGraphicState::emitObject(SkWStream* stream, SkPDFCatalog* catalog,
@@ -108,14 +117,82 @@
 }
 
 // static
+SkPDFGraphicState* SkPDFGraphicState::getSMaskGraphicState(
+        SkPDFFormXObject* sMask, bool invert) {
+    // The practical chances of using the same mask more than once are unlikely
+    // enough that it's not worth canonicalizing.
+    SkAutoMutexAcquire lock(canonicalPaintsMutex());
+
+    SkRefPtr<SkPDFDict> sMaskDict = new SkPDFDict("Mask");
+    sMaskDict->unref();  // SkRefPtr and new both took a reference.
+    sMaskDict->insert("S", new SkPDFName("Alpha"))->unref();
+    sMaskDict->insert("G", new SkPDFObjRef(sMask))->unref();
+
+    SkPDFGraphicState* result = new SkPDFGraphicState;
+    result->fPopulated = true;
+    result->fSMask = true;
+    result->insert("Type", new SkPDFName("ExtGState"))->unref();
+    result->insert("SMask", sMaskDict.get());
+    result->fResources.push(sMask);
+    sMask->ref();
+
+    if (invert) {
+        // Acrobat crashes if we use a type 0 function, kpdf crashes if we use
+        // a type 2 function, so we use a type 4 function.
+        SkRefPtr<SkPDFArray> domainAndRange = new SkPDFArray;
+        domainAndRange->unref();  // SkRefPtr and new both took a reference.
+        domainAndRange->reserve(2);
+        domainAndRange->append(new SkPDFInt(0))->unref();
+        domainAndRange->append(new SkPDFInt(1))->unref();
+
+        static const char psInvert[] = "{1 exch sub}";
+        SkRefPtr<SkMemoryStream> psInvertStream =
+            new SkMemoryStream(&psInvert, strlen(psInvert), true);
+        psInvertStream->unref();  // SkRefPtr and new both took a reference.
+
+        SkRefPtr<SkPDFStream> invertFunc =
+            new SkPDFStream(psInvertStream.get());
+        result->fResources.push(invertFunc.get());  // Pass the ref from new.
+        invertFunc->insert("FunctionType", new SkPDFInt(4))->unref();
+        invertFunc->insert("Domain", domainAndRange.get());
+        invertFunc->insert("Range", domainAndRange.get());
+
+        sMaskDict->insert("TR", new SkPDFObjRef(invertFunc.get()))->unref();
+    }
+
+    return result;
+}
+
+// static
+SkPDFGraphicState* SkPDFGraphicState::getNoSMaskGraphicState() {
+    SkAutoMutexAcquire lock(canonicalPaintsMutex());
+    static SkPDFGraphicState* noSMaskGS = NULL;
+    if (!noSMaskGS) {
+        noSMaskGS = new SkPDFGraphicState;
+        noSMaskGS->fPopulated = true;
+        noSMaskGS->fSMask = true;
+        noSMaskGS->insert("Type", new SkPDFName("ExtGState"))->unref();
+        noSMaskGS->insert("SMask", new SkPDFName("None"))->unref();
+    }
+    noSMaskGS->ref();
+    return noSMaskGS;
+}
+
+// static
 int SkPDFGraphicState::find(const SkPaint& paint) {
     GSCanonicalEntry search(&paint);
     return canonicalPaints().find(search);
 }
 
+SkPDFGraphicState::SkPDFGraphicState()
+    : fPopulated(false),
+      fSMask(false) {
+}
+
 SkPDFGraphicState::SkPDFGraphicState(const SkPaint& paint)
     : fPaint(paint),
-      fPopulated(false) {
+      fPopulated(false),
+      fSMask(false) {
 }
 
 // populateDict and operator== have to stay in sync with each other.
@@ -154,11 +231,12 @@
             fPaint.getXfermode()->asMode(&xfermode);
         // If we don't support the mode, just use kSrcOver_Mode.
         if (xfermode < 0 || xfermode > SkXfermode::kLastMode ||
-                blendModeFromXfermode(xfermode) == NULL) {
+                blend_mode_from_xfermode(xfermode) == NULL) {
             xfermode = SkXfermode::kSrcOver_Mode;
             NOT_IMPLEMENTED("unsupported xfermode", false);
         }
-        insert("BM", new SkPDFName(blendModeFromXfermode(xfermode)))->unref();
+        insert("BM",
+               new SkPDFName(blend_mode_from_xfermode(xfermode)))->unref();
     }
 }
 
@@ -185,10 +263,10 @@
         aXfermode->asMode(&aXfermodeName);
     }
     if (aXfermodeName < 0 || aXfermodeName > SkXfermode::kLastMode ||
-            blendModeFromXfermode(aXfermodeName) == NULL) {
+            blend_mode_from_xfermode(aXfermodeName) == NULL) {
         aXfermodeName = SkXfermode::kSrcOver_Mode;
     }
-    const char* aXfermodeString = blendModeFromXfermode(aXfermodeName);
+    const char* aXfermodeString = blend_mode_from_xfermode(aXfermodeName);
     SkASSERT(aXfermodeString != NULL);
 
     SkXfermode::Mode bXfermodeName = SkXfermode::kSrcOver_Mode;
@@ -197,10 +275,10 @@
         bXfermode->asMode(&bXfermodeName);
     }
     if (bXfermodeName < 0 || bXfermodeName > SkXfermode::kLastMode ||
-            blendModeFromXfermode(bXfermodeName) == NULL) {
+            blend_mode_from_xfermode(bXfermodeName) == NULL) {
         bXfermodeName = SkXfermode::kSrcOver_Mode;
     }
-    const char* bXfermodeString = blendModeFromXfermode(bXfermodeName);
+    const char* bXfermodeString = blend_mode_from_xfermode(bXfermodeName);
     SkASSERT(bXfermodeString != NULL);
 
     return strcmp(aXfermodeString, bXfermodeString) == 0;
diff --git a/src/pdf/SkPDFUtils.cpp b/src/pdf/SkPDFUtils.cpp
index 2935f1e..a838427 100644
--- a/src/pdf/SkPDFUtils.cpp
+++ b/src/pdf/SkPDFUtils.cpp
@@ -171,3 +171,17 @@
     SkPDFUtils::PaintPath(
         SkPaint::kStroke_Style, SkPath::kWinding_FillType, content);
 }
+
+// static
+void SkPDFUtils::DrawFormXObject(int objectIndex, SkWStream* content) {
+    content->writeText("/X");
+    content->writeDecAsText(objectIndex);
+    content->writeText(" Do\n");
+}
+
+// static
+void SkPDFUtils::ApplyGraphicState(int objectIndex, SkWStream* content) {
+    content->writeText("/G");
+    content->writeDecAsText(objectIndex);
+    content->writeText(" gs\n");
+}