[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);
 }