pdfviewer: more plumming for soft masks, blend modes and transparency groups

Review URL: https://codereview.chromium.org/21125002

git-svn-id: http://skia.googlecode.com/svn/trunk@10419 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/experimental/PdfViewer/SkPdfBasics.h b/experimental/PdfViewer/SkPdfBasics.h
index 74ab9e6..30c92b6 100644
--- a/experimental/PdfViewer/SkPdfBasics.h
+++ b/experimental/PdfViewer/SkPdfBasics.h
@@ -189,7 +189,8 @@
                                 transparency group XObject (see Section 7.5.5, “Transparency
                                 Group XObjects”). Initial value: Normal.
  */
-    SkXfermode::Mode fBlendMode;
+    SkXfermode::Mode fBlendModes[256];
+    int fBlendModesLength;
 
 /*
 soft mask         dictionary    (PDF 1.4) A soft-mask dictionary (see “Soft-Mask Dictionaries” on
@@ -327,7 +328,8 @@
         fAlphaSource  = false;
         fDashArrayLength = 0;
         fDashPhase    = 0;
-        fBlendMode    = SkXfermode::kSrc_Mode;  // PDF: Normal Blend mode
+        fBlendModesLength = 1;
+        fBlendModes[0] = SkXfermode::kSrc_Mode;  // PDF: Normal Blend mode
     }
 
     // TODO(edisonn): make two functons instead, stroking and non stoking, avoid branching
diff --git a/experimental/PdfViewer/SkPdfFont.cpp b/experimental/PdfViewer/SkPdfFont.cpp
index 0a8420c..ac96f02 100644
--- a/experimental/PdfViewer/SkPdfFont.cpp
+++ b/experimental/PdfViewer/SkPdfFont.cpp
@@ -308,7 +308,7 @@
 static int skstoi(const SkPdfObject* str) {
     // TODO(edisonn): report err of it is not a (hex) string
     int ret = 0;
-    for (unsigned int i = 0 ; i < str->len(); i++) {
+    for (unsigned int i = 0 ; i < str->lenstr(); i++) {
         ret = (ret << 8) + ((unsigned char*)str->c_str())[i];
     }
     // TODO(edisonn): character larger than 0x0000ffff not supported right now.
diff --git a/experimental/PdfViewer/SkPdfFont.h b/experimental/PdfViewer/SkPdfFont.h
index 4929a80..58c91b6 100644
--- a/experimental/PdfViewer/SkPdfFont.h
+++ b/experimental/PdfViewer/SkPdfFont.h
@@ -47,7 +47,7 @@
 public:
     SkUnencodedText(const SkPdfString* obj) {
         text = (void*)obj->c_str();
-        len = obj->len();
+        len = obj->lenstr();
     }
 };
 
diff --git a/experimental/PdfViewer/SkPdfRenderer.cpp b/experimental/PdfViewer/SkPdfRenderer.cpp
index e7bfdb6..e2805c7 100644
--- a/experimental/PdfViewer/SkPdfRenderer.cpp
+++ b/experimental/PdfViewer/SkPdfRenderer.cpp
@@ -668,6 +668,11 @@
     // TODO(edisonn): iterate smart on the stream even if it is compressed, tokenize it as we go.
     // For this PdfContentsTokenizer needs to be extended.
 
+    // This is a group?
+    if (skobj->has_Group()) {
+        //TransparencyGroupDictionary* ...
+    }
+
     SkPdfStream* stream = (SkPdfStream*)skobj;
 
     SkPdfNativeTokenizer* tokenizer =
@@ -1582,20 +1587,112 @@
     skpdfGraphicsStateApplyFontCore(pdfContext, fontAndSize->objAtAIndex(0), fontAndSize->objAtAIndex(1)->numberValue());
 }
 
+SkTDict<SkXfermode::Mode> gPdfBlendModes(20);
+
+class InitBlendModes {
+public:
+    InitBlendModes() {
+        // TODO(edisonn): use the python code generator?
+        // TABLE 7.2 Standard separable blend modes
+        gPdfBlendModes.set("Normal", SkXfermode::kSrc_Mode);
+        gPdfBlendModes.set("Multiply", SkXfermode::kMultiply_Mode);
+        gPdfBlendModes.set("Screen", SkXfermode::kScreen_Mode);
+        gPdfBlendModes.set("Overlay", SkXfermode::kOverlay_Mode);
+        gPdfBlendModes.set("Darken", SkXfermode::kDarken_Mode);
+        gPdfBlendModes.set("Lighten", SkXfermode::kLighten_Mode);
+        gPdfBlendModes.set("ColorDodge", SkXfermode::kColorDodge_Mode);
+        gPdfBlendModes.set("ColorBurn", SkXfermode::kColorBurn_Mode);
+        gPdfBlendModes.set("HardLight", SkXfermode::kHardLight_Mode);
+        gPdfBlendModes.set("SoftLight", SkXfermode::kSoftLight_Mode);
+        gPdfBlendModes.set("Difference", SkXfermode::kDifference_Mode);
+        gPdfBlendModes.set("Exclusion", SkXfermode::kExclusion_Mode);
+
+        // TABLE 7.3 Standard nonseparable blend modes
+        gPdfBlendModes.set("Hue", SkXfermode::kHue_Mode);
+        gPdfBlendModes.set("Saturation", SkXfermode::kSaturation_Mode);
+        gPdfBlendModes.set("Color", SkXfermode::kColor_Mode);
+        gPdfBlendModes.set("Luminosity", SkXfermode::kLuminosity_Mode);
+    }
+};
+
+InitBlendModes _gDummyInniter;
+
+SkXfermode::Mode xferModeFromBlendMode(const char* blendMode, size_t len) {
+    SkXfermode::Mode mode = (SkXfermode::Mode)(SkXfermode::kLastMode + 1);
+    if (gPdfBlendModes.find(blendMode, len, &mode)) {
+        return mode;
+    }
+
+    return (SkXfermode::Mode)(SkXfermode::kLastMode + 1);
+}
+
 void skpdfGraphicsStateApplyBM_name(PdfContext* pdfContext, const std::string& blendMode) {
-    // TODO(edisonn): verify input
+    SkXfermode::Mode mode = xferModeFromBlendMode(blendMode.c_str(), blendMode.length());
+    if (mode <= SkXfermode::kLastMode) {
+        pdfContext->fGraphicsState.fBlendModesLength = 1;
+        pdfContext->fGraphicsState.fBlendModes[0] = mode;
+    } else {
+        // TODO(edisonn): report unknown blend mode
+    }
 }
 
 void skpdfGraphicsStateApplyBM_array(PdfContext* pdfContext, SkPdfArray* blendModes) {
-    // TODO(edisonn): verify input
-}
+    if (!blendModes || blendModes->isArray() || blendModes->size() == 0 || blendModes->size() > 256) {
+        // TODO(edisonn): report error/warning
+        return;
+    }
+    SkXfermode::Mode modes[256];
+    int cnt = blendModes->size();
+    for (int i = 0; i < cnt; i++) {
+        SkPdfObject* name = blendModes->objAtAIndex(i);
+        if (!name->isName()) {
+            // TODO(edisonn): report error/warning
+            return;
+        }
+        SkXfermode::Mode mode = xferModeFromBlendMode(name->c_str(), name->lenstr());
+        if (mode > SkXfermode::kLastMode) {
+            // TODO(edisonn): report error/warning
+            return;
+        }
+    }
 
-void skpdfGraphicsStateApplySMask_name(PdfContext* pdfContext, const std::string& sMask) {
-    // TODO(edisonn): verify input
+    pdfContext->fGraphicsState.fBlendModesLength = cnt;
+    for (int i = 0; i < cnt; i++) {
+        pdfContext->fGraphicsState.fBlendModes[i] = modes[i];
+    }
 }
 
 void skpdfGraphicsStateApplySMask_dict(PdfContext* pdfContext, SkPdfDictionary* sMask) {
     // TODO(edisonn): verify input
+    if (pdfContext->fPdfDoc->mapper()->mapSoftMaskDictionary(sMask)) {
+        //SkPdfSoftMaskDictionary* smd = (SkPdfSoftMaskDictionary*)sMask;
+        // TODO(edisonn): load soft mask
+    } else if (pdfContext->fPdfDoc->mapper()->mapSoftMaskImageDictionary(sMask)) {
+        SkPdfSoftMaskImageDictionary* smid = (SkPdfSoftMaskImageDictionary*)sMask;
+        pdfContext->fGraphicsState.fSMask = getImageFromObject(pdfContext, smid, true);
+    } else {
+        // TODO (edisonn): report error/warning
+    }
+}
+
+void skpdfGraphicsStateApplySMask_name(PdfContext* pdfContext, const std::string& sMask) {
+    //Next, get the ExtGState Dictionary from the Resource Dictionary:
+    SkPdfDictionary* extGStateDictionary = pdfContext->fGraphicsState.fResources->ExtGState(pdfContext->fPdfDoc);
+
+    if (extGStateDictionary == NULL) {
+#ifdef PDF_TRACE
+        printf("ExtGState is NULL!\n");
+#endif
+        // TODO (edisonn): report error/warning
+        return;
+    }
+
+    SkPdfObject* obj = pdfContext->fPdfDoc->resolveReference(extGStateDictionary->get(sMask.c_str()));
+    if (!obj || !obj->isDictionary()) {
+        // TODO (edisonn): report error/warning
+        return;
+    }
+    skpdfGraphicsStateApplySMask_dict(pdfContext, obj->asDictionary());
 }
 
 void skpdfGraphicsStateApplyAIS(PdfContext* pdfContext, bool alphaSource) {
diff --git a/experimental/PdfViewer/pdfparser/native/SkNativeParsedPDF.cpp b/experimental/PdfViewer/pdfparser/native/SkNativeParsedPDF.cpp
index 8f92a0f..8892ee2 100644
--- a/experimental/PdfViewer/pdfparser/native/SkNativeParsedPDF.cpp
+++ b/experimental/PdfViewer/pdfparser/native/SkNativeParsedPDF.cpp
@@ -196,7 +196,7 @@
 
             token.reset();
             current = nextObject(0, current, trailerEnd, &token, NULL, NULL);
-            if (!token.isKeyword() || token.len() != 1 || (*token.c_str() != 'f' && *token.c_str() != 'n')) {
+            if (!token.isKeyword() || token.lenstr() != 1 || (*token.c_str() != 'f' && *token.c_str() != 'n')) {
                 // TODO(edisonn): report/warning
                 return current;
             }
@@ -215,7 +215,7 @@
     const unsigned char* current =
             nextObject(0, trailerStart, trailerEnd, &trailerKeyword, NULL, NULL);
 
-    if (!trailerKeyword.isKeyword() || strlen("trailer") != trailerKeyword.len() ||
+    if (!trailerKeyword.isKeyword() || strlen("trailer") != trailerKeyword.lenstr() ||
         strncmp(trailerKeyword.c_str(), "trailer", strlen("trailer")) != 0) {
         // TODO(edisonn): report warning, rebuild trailer from objects.
         return -1;
diff --git a/experimental/PdfViewer/pdfparser/native/SkPdfNativeTokenizer.cpp b/experimental/PdfViewer/pdfparser/native/SkPdfNativeTokenizer.cpp
index bc3a3fa..41bd92d 100644
--- a/experimental/PdfViewer/pdfparser/native/SkPdfNativeTokenizer.cpp
+++ b/experimental/PdfViewer/pdfparser/native/SkPdfNativeTokenizer.cpp
@@ -927,7 +927,7 @@
     // If it is a keyword, we will only get the pointer of the string
     if (obj.type() == SkPdfObject::kKeyword_PdfObjectType) {
         token->fKeyword = obj.c_str();
-        token->fKeywordLength = obj.len();
+        token->fKeywordLength = obj.lenstr();
         token->fType = kKeyword_TokenType;
     } else {
         SkPdfObject* pobj = fAllocator->allocObject();
@@ -1062,7 +1062,7 @@
         SkPdfObject* key = fAllocator->allocObject();
         fUncompressedStream = nextObject(0, fUncompressedStream, fUncompressedStreamEnd, key, fAllocator, fDoc);
 
-        if (key->isKeyword() && key->len() == 2 && key->c_str()[0] == 'I' && key->c_str()[1] == 'D') { // ID
+        if (key->isKeyword() && key->lenstr() == 2 && key->c_str()[0] == 'I' && key->c_str()[1] == 'D') { // ID
             fUncompressedStream = readInlineImageStream(0, fUncompressedStream, fUncompressedStreamEnd, inlineImage, fDoc);
             return inlineImage;
         } else {
diff --git a/experimental/PdfViewer/pdfparser/native/SkPdfObject.h b/experimental/PdfViewer/pdfparser/native/SkPdfObject.h
index 5525f7b..9df9a23 100644
--- a/experimental/PdfViewer/pdfparser/native/SkPdfObject.h
+++ b/experimental/PdfViewer/pdfparser/native/SkPdfObject.h
@@ -131,7 +131,7 @@
         }
     }
 
-    size_t len() const {
+    size_t lenstr() const {
         switch (fObjectType) {
             case kString_PdfObjectType:
             case kHexString_PdfObjectType:
diff --git a/experimental/PdfViewer/spec2def.py b/experimental/PdfViewer/spec2def.py
index 0f9d684..f1ab8a7 100644
--- a/experimental/PdfViewer/spec2def.py
+++ b/experimental/PdfViewer/spec2def.py
@@ -116,9 +116,9 @@
 'TABLE 6.5': ['Type10HalftoneDictionary', 'Additional entries specific to a type 10 halftone dictionary'],
 'TABLE 6.6': ['Type16HalftoneDictionary', 'Additional entries specific to a type 16 halftone dictionary'],
 'TABLE 6.7': ['Type5HalftoneDictionary', 'Entries in a type 5 halftone dictionary'],
-'TABLE 7.10': ['SoftMaskDictionary', 'Entries in a soft-mask dictionary'],
-'TABLE 7.12': ['SoftMaskImageDictionary', 'Additional entry in a soft-mask image dictionary'],
-'TABLE 7.13': ['TransparencyGroupDictionary', 'Additional entries specific to a transparency group attributes dictionary'],
+'TABLE 7.10': ['SoftMaskDictionary', 'Entries in a soft-mask dictionary', '', {'S': '[datatypes.PdfName(\'Alpha\'), datatypes.PdfName(\'Luminosity\')]'}],
+'TABLE 7.12': ['SoftMaskImageDictionary', 'Additional entry in a soft-mask image dictionary', 'ImageDictionary', {'Subtype': '[datatypes.PdfName(\'Image\')]', 'ColorSpace': '[datatypes.PdfName(\'DeviceGray\'), datatypes.PdfName(\'Gray\')]'}],
+'TABLE 7.13': ['TransparencyGroupDictionary', 'Additional entries specific to a transparency group attributes dictionary', 'XObjectDictionary', {'S': '[datatypes.PdfName(\'Transparency\')]'}],
 'TABLE 8.1': ['ViewerPreferencesDictionary', 'Entries in a viewer preferences dictionary'],
 'TABLE 8.3': ['OutlineDictionary', 'Entries in the outline dictionary'],
 'TABLE 8.4': ['OutlineItemDictionary', 'Entries in an outline item dictionary'],
@@ -222,6 +222,9 @@
 'TABLE 9.49': ['OpiVersionDictionary', 'Entry in an OPI version dictionary'],
 }
 
+classTree = {
+}
+
 def buildKnownDictionaries():
   global tableToClassName
   global knownTypes
@@ -297,6 +300,9 @@
   global emitedDitionaryName
   global table
   global tableToClassName
+  global classTree
+  global tableKey
+  
   
   if columnValues == None:
     return
@@ -392,10 +398,13 @@
     emitedDitionaryName = tableToClassName[tableKey][0]
     comment = fix(tableToClassName[tableKey][1])
     
+    
     if len(tableToClassName[tableKey]) >= 3 and tableToClassName[tableKey][2] != '':
       fspecPy.write('  pdfspec.addClass(\'' + emitedDitionaryName + '\', \'' + tableToClassName[tableKey][2] + '\', \'' + comment + '\')\\\n')
+      classTree[emitedDitionaryName] = [tableToClassName[tableKey][2], {}]
     else:
       fspecPy.write('  pdfspec.addClass(\'' + emitedDitionaryName + '\', \'Dictionary\', \'' + comment + '\')\\\n')
+      classTree[emitedDitionaryName] = ['Dictionary', {}]
 
   if len(tableToClassName[tableKey]) >= 4 and columnValues[0] in tableToClassName[tableKey][3]:
     required = True
@@ -409,6 +418,12 @@
   fspecPy.write('          .name(\'' + columnValues[0] + '\')\\\n')
   fspecPy.write('          .type(\'' + columnValues[1] + '\')\\\n')
   fspecPy.write('          .comment(\'' + columnValues[2] + '\')\\\n')
+  
+  classTree[emitedDitionaryName][1][columnValues[0]] =   '          .field(\'' + columnValues[0] + '\')\\\n' + \
+  '          .name(\'' + columnValues[0] + '\')\\\n' + \
+  '          .type(\'' + columnValues[1] + '\')\\\n' + \
+  '          .comment(\'\')\\\n'
+    
 
   if len(tableToClassName[tableKey]) >= 4 and columnValues[0] in tableToClassName[tableKey][3]:
     fspecPy.write('          .must(' + tableToClassName[tableKey][3][columnValues[0]] + ')\\\n')
@@ -461,11 +476,39 @@
 def stopTable(fspecPy):
   global tableHeaderFound
   global emitedDitionaryName
-
+  global tableKey
+  global classTree
+  
   if not inTable():
     return
   
   commitRow(fspecPy)
+  
+  #print tableKey
+  
+  # TODO(edisonn): iterate on all requited key in the def, and if not on the definition, get definition from parent and export them
+  if len(tableToClassName[tableKey]) >= 4:
+    for field in tableToClassName[tableKey][3]:
+      #print field
+      if not field in classTree[emitedDitionaryName][1]:
+        fieldDef = ''
+        searchKey = classTree[emitedDitionaryName][0]
+        while searchKey != 'Dictionary' and (not field in classTree[searchKey][1]):
+          searchKey = classTree[searchKey][0]
+        
+        if searchKey != 'Dictionary' and (field in classTree[searchKey][1]):
+          #print tableToClassName[tableKey][3][field]
+          #print classTree[searchKey][1][field]
+          # TODO(edisonns): hack - for required fields, they need to be downgraded to only a type
+          classTree[searchKey][1][field] = classTree[searchKey][1][field].replace(' or array', '')
+          classTree[searchKey][1][field] = classTree[searchKey][1][field].replace(' or distionary', '')
+          fspecPy.write('      .required(\'NULL\')\\\n')
+          fspecPy.write(classTree[searchKey][1][field])
+          fspecPy.write('          .must(' + tableToClassName[tableKey][3][field] + ')\\\n')
+          fspecPy.write('          .done().done()\\\n')
+        else:
+          print 'ERROR' + tableKey + '.' + field;
+  
   tableHeaderFound = False
   emitedDitionaryName = ''
   fspecPy.write('      .done()\n')
@@ -569,7 +612,7 @@
         return False
 
     if first != '' and second != '' and third[0] != '(':
-      stopTable()
+      stopTable(fspecPy)
       return False
       
     if first == '' and second != '' and second[0] == ' ':
diff --git a/gyp/pdfviewer.gyp b/gyp/pdfviewer.gyp
index ad6f92f..8552f3b 100644
--- a/gyp/pdfviewer.gyp
+++ b/gyp/pdfviewer.gyp
@@ -55,6 +55,8 @@
           ],
           'outputs': [
             '<(SHARED_INTERMEDIATE_DIR)/native/autogen/SkPdfEnums_autogen.h',
+            '<(SHARED_INTERMEDIATE_DIR)/native/autogen/SkPdfMapper_autogen.h',
+            '<(SHARED_INTERMEDIATE_DIR)/native/autogen/SkPdfHeaders_autogen.h',
             '<(SHARED_INTERMEDIATE_DIR)/native/autogen/SkPdfMapper_autogen.cpp',
             '<(SHARED_INTERMEDIATE_DIR)/native/autogen/SkPdfHeaders_autogen.cpp',
             # TODO(edisonn): ok, there are many more files here, which we should list but since