Support multiple PDF rendering backends in the GM

R=epoger@google.com, vandebo@chromium.org

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

git-svn-id: http://skia.googlecode.com/svn/trunk@10841 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/gm.h b/gm/gm.h
index f806973..dbd7f42 100644
--- a/gm/gm.h
+++ b/gm/gm.h
@@ -42,6 +42,7 @@
             kSkip565_Flag               = 1 << 5,
             kSkipScaledReplay_Flag      = 1 << 6,
             kSkipGPU_Flag               = 1 << 7,
+            kSkipPDFRasterization_Flag  = 1 << 8,
         };
 
         void draw(SkCanvas*);
diff --git a/gm/gmmain.cpp b/gm/gmmain.cpp
index 773e1e7..4db3a0d 100644
--- a/gm/gmmain.cpp
+++ b/gm/gmmain.cpp
@@ -30,6 +30,7 @@
 #include "SkImageDecoder.h"
 #include "SkImageEncoder.h"
 #include "SkOSFile.h"
+#include "SkPDFRasterizer.h"
 #include "SkPicture.h"
 #include "SkRefCnt.h"
 #include "SkStream.h"
@@ -83,9 +84,6 @@
 
 #ifdef SK_BUILD_FOR_MAC
     #include "SkCGUtils.h"
-    #define CAN_IMAGE_PDF   1
-#else
-    #define CAN_IMAGE_PDF   0
 #endif
 
 using namespace skiagm;
@@ -160,6 +158,12 @@
     bool                            fRunByDefault;
 };
 
+struct PDFRasterizerData {
+    bool        (*fRasterizerFunction)(SkStream*, SkBitmap*);
+    const char* fName;
+    bool        fRunByDefault;
+};
+
 class BWTextDrawFilter : public SkDrawFilter {
 public:
     virtual bool filter(SkPaint*, Type) SK_OVERRIDE;
@@ -282,14 +286,19 @@
         }
     }
 
-    static bool write_bitmap(const SkString& path, const SkBitmap& bitmap) {
+    static ErrorCombination write_bitmap(const SkString& path, const SkBitmap& bitmap) {
         // TODO(epoger): Now that we have removed force_all_opaque()
         // from this method, we should be able to get rid of the
         // transformation to 8888 format also.
         SkBitmap copy;
         bitmap.copyTo(&copy, SkBitmap::kARGB_8888_Config);
-        return SkImageEncoder::EncodeFile(path.c_str(), copy,
-                                          SkImageEncoder::kPNG_Type, 100);
+        if (!SkImageEncoder::EncodeFile(path.c_str(), copy,
+                                        SkImageEncoder::kPNG_Type,
+                                        100)) {
+            gm_fprintf(stderr, "FAILED to write bitmap: %s\n", path.c_str());
+            return ErrorCombination(kWritingReferenceImage_ErrorType);
+        }
+        return kEmpty_ErrorCombination;
     }
 
     /**
@@ -410,9 +419,13 @@
         gm_fprintf(stdout, "(results marked with [*] will cause nonzero return value)\n");
     }
 
-    static bool write_document(const SkString& path, SkStreamAsset* asset) {
+    static ErrorCombination write_document(const SkString& path, SkStreamAsset* asset) {
         SkFILEWStream stream(path.c_str());
-        return stream.writeStream(asset, asset->getLength());
+        if (!stream.writeStream(asset, asset->getLength())) {
+            gm_fprintf(stderr, "FAILED to write document: %s\n", path.c_str());
+            return ErrorCombination(kWritingReferenceImage_ErrorType);
+        }
+        return kEmpty_ErrorCombination;
     }
 
     /**
@@ -651,51 +664,6 @@
 #endif
     }
 
-    ErrorCombination write_reference_image(const ConfigData& gRec, const char writePath [],
-                                           const char renderModeDescriptor [],
-                                           const char *shortName,
-                                           const BitmapAndDigest* bitmapAndDigest,
-                                           SkStreamAsset* document) {
-        SkString path;
-        bool success = false;
-        if (gRec.fBackend == kRaster_Backend ||
-            gRec.fBackend == kGPU_Backend ||
-            (gRec.fBackend == kPDF_Backend && CAN_IMAGE_PDF)) {
-
-            path = make_bitmap_filename(writePath, shortName, gRec.fName, renderModeDescriptor,
-                                        bitmapAndDigest->fDigest);
-            success = write_bitmap(path, bitmapAndDigest->fBitmap);
-        }
-        if (kPDF_Backend == gRec.fBackend) {
-            path = make_filename(writePath, shortName, gRec.fName, renderModeDescriptor,
-                                 "pdf");
-            success = write_document(path, document);
-        }
-        if (kXPS_Backend == gRec.fBackend) {
-            path = make_filename(writePath, shortName, gRec.fName, renderModeDescriptor,
-                                 "xps");
-            success = write_document(path, document);
-        }
-        if (success) {
-            return kEmpty_ErrorCombination;
-        } else {
-            gm_fprintf(stderr, "FAILED to write %s\n", path.c_str());
-            ErrorCombination errors(kWritingReferenceImage_ErrorType);
-            // TODO(epoger): Don't call RecordTestResults() here...
-            // Instead, we should make sure to call RecordTestResults
-            // exactly ONCE per test.  (Otherwise, gmmain.fTestsRun
-            // will be incremented twice for this test: once in
-            // compare_test_results_to_stored_expectations() before
-            // that method calls this one, and again here.)
-            //
-            // When we make that change, we should probably add a
-            // WritingReferenceImage test to the gm self-tests.)
-            RecordTestResults(errors, make_shortname_plus_config(shortName, gRec.fName),
-                              renderModeDescriptor);
-            return errors;
-        }
-    }
-
     /**
      * Log more detail about the mistmatch between expectedBitmap and
      * actualBitmap.
@@ -824,7 +792,6 @@
                                     completeName);
             }
         }
-        RecordTestResults(errors, shortNamePlusConfig, renderModeDescriptor);
 
         if (addToJsonSummary) {
             add_actual_results_to_json_summary(completeName, actualBitmapAndDigest.fDigest, errors,
@@ -893,20 +860,14 @@
      *
      * @param gm which test generated the actualBitmap
      * @param gRec
-     * @param writePath unless this is NULL, write out actual images into this
-     *        directory
      * @param actualBitmapAndDigest ptr to bitmap generated by this run, or NULL
      *        if we don't have a usable bitmap representation
-     * @param document pdf or xps representation, if appropriate
      */
     ErrorCombination compare_test_results_to_stored_expectations(
-        GM* gm, const ConfigData& gRec, const char writePath[],
-        const BitmapAndDigest* actualBitmapAndDigest, SkStreamAsset* document) {
+        GM* gm, const ConfigData& gRec,
+        const BitmapAndDigest* actualBitmapAndDigest) {
 
         SkString shortNamePlusConfig = make_shortname_plus_config(gm->shortName(), gRec.fName);
-        SkString nameWithExtension(shortNamePlusConfig);
-        nameWithExtension.append(".");
-        nameWithExtension.append(kPNG_FileExtension);
 
         ErrorCombination errors;
 
@@ -914,18 +875,20 @@
             // Note that we intentionally skipped validating the results for
             // this test, because we don't know how to generate an SkBitmap
             // version of the output.
-            RecordTestResults(ErrorCombination(kIntentionallySkipped_ErrorType),
-                              shortNamePlusConfig, "");
+            errors.add(ErrorCombination(kIntentionallySkipped_ErrorType));
         } else if (!(gRec.fFlags & kWrite_ConfigFlag)) {
             // We don't record the results for this test or compare them
             // against any expectations, because the output image isn't
             // meaningful.
             // See https://code.google.com/p/skia/issues/detail?id=1410 ('some
             // GM result images not available for download from Google Storage')
-            RecordTestResults(ErrorCombination(kIntentionallySkipped_ErrorType),
-                              shortNamePlusConfig, "");
+            errors.add(ErrorCombination(kIntentionallySkipped_ErrorType));
         } else {
             ExpectationsSource *expectationsSource = this->fExpectationsSource.get();
+            SkString nameWithExtension(shortNamePlusConfig);
+            nameWithExtension.append(".");
+            nameWithExtension.append(kPNG_FileExtension);
+
             if (expectationsSource && (gRec.fFlags & kRead_ConfigFlag)) {
                 /*
                  * Get the expected results for this test, as one or more allowed
@@ -950,20 +913,9 @@
                                                    actualBitmapAndDigest->fDigest,
                                                    ErrorCombination(kMissingExpectations_ErrorType),
                                                    false);
-                RecordTestResults(ErrorCombination(kMissingExpectations_ErrorType),
-                                  shortNamePlusConfig, "");
+                errors.add(ErrorCombination(kMissingExpectations_ErrorType));
             }
         }
-
-        // TODO: Consider moving this into compare_to_expectations(),
-        // similar to fMismatchPath... for now, we don't do that, because
-        // we don't want to write out the actual bitmaps for all
-        // renderModes of all tests!  That would be a lot of files.
-        if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) {
-            errors.add(write_reference_image(gRec, writePath, "", gm->shortName(),
-                                             actualBitmapAndDigest, document));
-        }
-
         return errors;
     }
 
@@ -983,8 +935,19 @@
         SkASSERT(referenceBitmap);
         Expectations expectations(*referenceBitmap);
         BitmapAndDigest actualBitmapAndDigest(actualBitmap);
-        return compare_to_expectations(expectations, actualBitmapAndDigest, shortName,
-                                       configName, renderModeDescriptor, false);
+
+        // TODO: Eliminate RecordTestResults from here.
+        // Results recording code for the test_drawing path has been refactored so that
+        // RecordTestResults is only called once, at the topmost level. However, the
+        // other paths have not yet been refactored, and RecordTestResults has been added
+        // here to maintain proper behavior for calls not coming from the test_drawing path.
+        ErrorCombination errors;
+        errors.add(compare_to_expectations(expectations, actualBitmapAndDigest, shortName,
+                                           configName, renderModeDescriptor, false));
+        SkString shortNamePlusConfig = make_shortname_plus_config(shortName, configName);
+        RecordTestResults(errors, shortNamePlusConfig, renderModeDescriptor);
+
+        return errors;
     }
 
     static SkPicture* generate_new_picture(GM* gm, BbhType bbhType, uint32_t recordFlags,
@@ -1037,46 +1000,89 @@
 
     // Test: draw into a bitmap or pdf.
     // Depending on flags, possibly compare to an expected image.
-    ErrorCombination test_drawing(GM* gm,
-                                  const ConfigData& gRec,
+    // If writePath is not NULL, also write images (or documents) to the specified path.
+    ErrorCombination test_drawing(GM* gm, const ConfigData& gRec,
+                                  const SkTDArray<const PDFRasterizerData*> &pdfRasterizers,
                                   const char writePath [],
                                   GrSurface* gpuTarget,
                                   SkBitmap* bitmap) {
+        ErrorCombination errors;
         SkDynamicMemoryWStream document;
+        SkString path;
 
         if (gRec.fBackend == kRaster_Backend ||
             gRec.fBackend == kGPU_Backend) {
             // Early exit if we can't generate the image.
-            ErrorCombination errors = generate_image(gm, gRec, gpuTarget, bitmap, false);
+            errors.add(generate_image(gm, gRec, gpuTarget, bitmap, false));
             if (!errors.isEmpty()) {
                 // TODO: Add a test to exercise what the stdout and
                 // JSON look like if we get an "early error" while
                 // trying to generate the image.
                 return errors;
             }
+            BitmapAndDigest bitmapAndDigest(*bitmap);
+            errors.add(compare_test_results_to_stored_expectations(
+                           gm, gRec, &bitmapAndDigest));
+
+            if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) {
+                path = make_bitmap_filename(writePath, gm->shortName(), gRec.fName,
+                                            "", bitmapAndDigest.fDigest);
+                errors.add(write_bitmap(path, bitmapAndDigest.fBitmap));
+            }
         } 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);
-#else
-            bitmap = NULL;  // we don't generate a bitmap rendering of the PDF file
-#endif
+
+            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;
+                    }
+
+                    BitmapAndDigest bitmapAndDigest(pdfBitmap);
+                    errors.add(compare_test_results_to_stored_expectations(
+                               gm, gRec, &bitmapAndDigest));
+
+                    SkString configName(gRec.fName);
+                    configName.append("_");
+                    configName.append(pdfRasterizers[i]->fName);
+
+                    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 if (gRec.fBackend == kXPS_Backend) {
             generate_xps(gm, document);
-            bitmap = NULL;  // we don't generate a bitmap rendering of the XPS file
-        }
+            SkAutoTUnref<SkStreamAsset> documentStream(document.detachAsStream());
 
-        SkAutoTUnref<SkStreamAsset> documentStream(document.detachAsStream());
-        if (NULL == bitmap) {
-            return compare_test_results_to_stored_expectations(
-                gm, gRec, writePath, NULL, documentStream);
+            errors.add(compare_test_results_to_stored_expectations(
+                           gm, gRec, NULL));
+
+            if (writePath && (gRec.fFlags & kWrite_ConfigFlag)) {
+                path = make_filename(writePath, gm->shortName(), gRec.fName, "", "xps");
+                errors.add(write_document(path, documentStream));
+            }
         } else {
-            BitmapAndDigest bitmapAndDigest(*bitmap);
-            return compare_test_results_to_stored_expectations(
-                gm, gRec, writePath, &bitmapAndDigest, documentStream);
+            SkASSERT(false);
         }
+        return errors;
     }
 
     ErrorCombination test_deferred_drawing(GM* gm,
@@ -1230,11 +1236,6 @@
 static const GLContextType kDontCare_GLContextType = 0;
 #endif
 
-// If the platform does not support writing PNGs of PDFs then there will be no
-// reference images to read. However, we can always write the .pdf files
-static const ConfigFlags kPDFConfigFlags = CAN_IMAGE_PDF ? kRW_ConfigFlag :
-                                                           kWrite_ConfigFlag;
-
 static const ConfigData gRec[] = {
     { SkBitmap::kARGB_8888_Config, kRaster_Backend, kDontCare_GLContextType,                  0, kRW_ConfigFlag,    "8888",         true },
 #if 0   // stop testing this (for now at least) since we want to remove support for it (soon please!!!)
@@ -1266,10 +1267,19 @@
     { SkBitmap::kARGB_8888_Config, kXPS_Backend,    kDontCare_GLContextType,                  0, kWrite_ConfigFlag, "xps",          true },
 #endif // SK_SUPPORT_XPS
 #ifdef SK_SUPPORT_PDF
-    { SkBitmap::kARGB_8888_Config, kPDF_Backend,    kDontCare_GLContextType,                  0, kPDFConfigFlags,   "pdf",          true },
+    { SkBitmap::kARGB_8888_Config, kPDF_Backend,    kDontCare_GLContextType,                  0, kRW_ConfigFlag,    "pdf",          true },
 #endif // SK_SUPPORT_PDF
 };
 
+static const PDFRasterizerData kPDFRasterizers[] = {
+#ifdef SK_BUILD_FOR_MAC
+    { &SkPDFDocumentToBitmap, "mac",     true },
+#endif
+#ifdef SK_BUILD_POPPLER
+    { &SkPopplerRasterizePDF, "poppler", true },
+#endif
+};
+
 static const char kDefaultsConfigStr[] = "defaults";
 static const char kExcludeConfigChar = '~';
 
@@ -1321,6 +1331,32 @@
     return result;
 }
 
+static SkString pdfRasterizerUsage() {
+    SkString result;
+    result.appendf("Space delimited list of which PDF rasterizers to run. Possible options: [");
+    // For this (and further) loops through kPDFRasterizers, there is a typecast to int to avoid
+    // the compiler giving an "comparison of unsigned expression < 0 is always false" warning
+    // and turning it into a build-breaking error.
+    for (int i = 0; i < (int)SK_ARRAY_COUNT(kPDFRasterizers); ++i) {
+        if (i > 0) {
+            result.append(" ");
+        }
+        result.append(kPDFRasterizers[i].fName);
+    }
+    result.append("]\n");
+    result.append("The default value is: \"");
+    for (int i = 0; i < (int)SK_ARRAY_COUNT(kPDFRasterizers); ++i) {
+        if (kPDFRasterizers[i].fRunByDefault) {
+            if (i > 0) {
+                result.append(" ");
+            }
+            result.append(kPDFRasterizers[i].fName);
+        }
+    }
+    result.append("\"");
+    return result;
+}
+
 // Macro magic to convert a numeric preprocessor token into a string.
 // Adapted from http://stackoverflow.com/questions/240353/convert-a-preprocessor-token-to-a-string
 // This should probably be moved into one of our common headers...
@@ -1329,6 +1365,7 @@
 
 // Alphabetized ignoring "no" prefix ("readPath", "noreplay", "resourcePath").
 DEFINE_string(config, "", configUsage().c_str());
+DEFINE_string(pdfRasterizers, "", pdfRasterizerUsage().c_str());
 DEFINE_bool(deferred, true, "Exercise the deferred rendering test pass.");
 DEFINE_string(excludeConfig, "", "Space delimited list of configs to skip.");
 DEFINE_bool(forceBWtext, false, "Disable text anti-aliasing.");
@@ -1417,6 +1454,15 @@
     return -1;
 }
 
+static const PDFRasterizerData* findPDFRasterizer(const char rasterizer[]) {
+    for (int i = 0; i < (int)SK_ARRAY_COUNT(kPDFRasterizers); i++) {
+        if (!strcmp(rasterizer, kPDFRasterizers[i].fName)) {
+            return &kPDFRasterizers[i];
+        }
+    }
+    return NULL;
+}
+
 namespace skiagm {
 #if SK_SUPPORT_GPU
 SkAutoTUnref<GrContext> gGrContext;
@@ -1473,9 +1519,13 @@
  *
  * Returns all errors encountered while doing so.
  */
-ErrorCombination run_multiple_configs(GMMain &gmmain, GM *gm, const SkTDArray<size_t> &configs,
+ErrorCombination run_multiple_configs(GMMain &gmmain, GM *gm,
+                                      const SkTDArray<size_t> &configs,
+                                      const SkTDArray<const PDFRasterizerData*> &pdfRasterizers,
                                       GrContextFactory *grFactory);
-ErrorCombination run_multiple_configs(GMMain &gmmain, GM *gm, const SkTDArray<size_t> &configs,
+ErrorCombination run_multiple_configs(GMMain &gmmain, GM *gm,
+                                      const SkTDArray<size_t> &configs,
+                                      const SkTDArray<const PDFRasterizerData*> &pdfRasterizers,
                                       GrContextFactory *grFactory) {
     const char renderModeDescriptor[] = "";
     ErrorCombination errorsForAllConfigs;
@@ -1566,9 +1616,12 @@
         } else {
             writePath = NULL;
         }
+
         if (errorsForThisConfig.isEmpty()) {
-            errorsForThisConfig.add(gmmain.test_drawing(gm,config, writePath, gpuTarget,
+            errorsForThisConfig.add(gmmain.test_drawing(gm, config, pdfRasterizers,
+                                                        writePath, gpuTarget,
                                                         &comparisonBitmap));
+            gmmain.RecordTestResults(errorsForThisConfig, shortNamePlusConfig, "");
         }
 
         if (FLAGS_deferred && errorsForThisConfig.isEmpty() &&
@@ -1754,10 +1807,9 @@
     return total;
 }
 
-bool prepare_subdirectories(const char *root, bool useFileHierarchy,
-                            const SkTDArray<size_t> &configs);
-bool prepare_subdirectories(const char *root, bool useFileHierarchy,
-                            const SkTDArray<size_t> &configs) {
+static bool prepare_subdirectories(const char *root, bool useFileHierarchy,
+                                   const SkTDArray<size_t> &configs,
+                                   const SkTDArray<const PDFRasterizerData*>& pdfRasterizers) {
     if (!sk_mkdir(root)) {
         return false;
     }
@@ -1769,6 +1821,16 @@
             if (!sk_mkdir(subdir.c_str())) {
                 return false;
             }
+
+            if (config.fBackend == kPDF_Backend) {
+                for (int j = 0; j < pdfRasterizers.count(); j++) {
+                    SkString pdfSubdir = subdir;
+                    pdfSubdir.appendf("_%s", pdfRasterizers[j]->fName);
+                    if (!sk_mkdir(pdfSubdir.c_str())) {
+                        return false;
+                    }
+                }
+            }
         }
     }
     return true;
@@ -1877,6 +1939,53 @@
     return true;
 }
 
+static bool parse_flags_pdf_rasterizers(const SkTDArray<size_t>& configs,
+                                        SkTDArray<const PDFRasterizerData*>* outRasterizers) {
+    // No need to run this check (and display the PDF rasterizers message)
+    // if no PDF backends are in the configs.
+    bool configHasPDF = false;
+    for (int i = 0; i < configs.count(); i++) {
+        if (gRec[configs[i]].fBackend == kPDF_Backend) {
+            configHasPDF = true;
+            break;
+        }
+    }
+    if (!configHasPDF) {
+        return true;
+    }
+
+    for (int i = 0; i < FLAGS_pdfRasterizers.count(); i++) {
+        const char* rasterizer = FLAGS_pdfRasterizers[i];
+        const PDFRasterizerData* rasterizerPtr = findPDFRasterizer(rasterizer);
+
+        if (rasterizerPtr == NULL) {
+            gm_fprintf(stderr, "unrecognized rasterizer %s\n", rasterizer);
+            return false;
+        }
+        appendUnique<const PDFRasterizerData*>(outRasterizers,
+                                               rasterizerPtr);
+    }
+
+    if (outRasterizers->count() == 0) {
+        // if no config is specified by user, add the defaults
+        for (int i = 0; i < (int)SK_ARRAY_COUNT(kPDFRasterizers); ++i) {
+            if (kPDFRasterizers[i].fRunByDefault) {
+                *outRasterizers->append() = &kPDFRasterizers[i];
+            }
+        }
+    }
+
+    // now show the user the set of configs that will be run.
+    SkString configStr("These PDF rasterizers will be run:");
+    // show the user the config that will run.
+    for (int i = 0; i < outRasterizers->count(); ++i) {
+        configStr.appendf(" %s", (*outRasterizers)[i]->fName);
+    }
+    gm_fprintf(stdout, "%s\n", configStr.c_str());
+
+    return true;
+}
+
 static bool parse_flags_ignore_error_types(ErrorCombination* outErrorTypes) {
     if (FLAGS_ignoreErrorTypes.count() > 0) {
         *outErrorTypes = ErrorCombination();
@@ -2019,8 +2128,10 @@
     SkCommandLineFlags::Parse(argc, argv);
 
     SkTDArray<size_t> configs;
+
     int moduloRemainder = -1;
     int moduloDivisor = -1;
+    SkTDArray<const PDFRasterizerData*> pdfRasterizers;
     SkTDArray<SkScalar> tileGridReplayScales;
 #if SK_SUPPORT_GPU
     GrContextFactory* grFactory = new GrContextFactory;
@@ -2039,6 +2150,7 @@
         !parse_flags_match_strs(&matchStrs) ||
         !parse_flags_jpeg_quality() ||
         !parse_flags_configs(&configs, grFactory) ||
+        !parse_flags_pdf_rasterizers(configs, &pdfRasterizers) ||
         !parse_flags_gmmain_paths(&gmmain)) {
         return -1;
     }
@@ -2068,18 +2180,20 @@
 
     // If we will be writing out files, prepare subdirectories.
     if (FLAGS_writePath.count() == 1) {
-        if (!prepare_subdirectories(FLAGS_writePath[0], gmmain.fUseFileHierarchy, configs)) {
+        if (!prepare_subdirectories(FLAGS_writePath[0], gmmain.fUseFileHierarchy,
+                                    configs, pdfRasterizers)) {
             return -1;
         }
     }
     if (NULL != gmmain.fMismatchPath) {
-        if (!prepare_subdirectories(gmmain.fMismatchPath, gmmain.fUseFileHierarchy, configs)) {
+        if (!prepare_subdirectories(gmmain.fMismatchPath, gmmain.fUseFileHierarchy,
+                                    configs, pdfRasterizers)) {
             return -1;
         }
     }
     if (NULL != gmmain.fMissingExpectationsPath) {
         if (!prepare_subdirectories(gmmain.fMissingExpectationsPath, gmmain.fUseFileHierarchy,
-                                    configs)) {
+                                    configs, pdfRasterizers)) {
             return -1;
         }
     }
@@ -2107,7 +2221,7 @@
         gm_fprintf(stdout, "%sdrawing... %s [%d %d]\n", moduloStr.c_str(), shortName,
                    size.width(), size.height());
 
-        run_multiple_configs(gmmain, gm, configs, grFactory);
+        run_multiple_configs(gmmain, gm, configs, pdfRasterizers, grFactory);
 
         SkBitmap comparisonBitmap;
         const ConfigData compareConfig =
diff --git a/gm/mixedxfermodes.cpp b/gm/mixedxfermodes.cpp
index f057672..645143a 100644
--- a/gm/mixedxfermodes.cpp
+++ b/gm/mixedxfermodes.cpp
@@ -125,6 +125,11 @@
         }
     }
 
+    virtual uint32_t onGetFlags() const {
+        // Skip PDF rasterization since rendering this PDF takes forever.
+        return kSkipPDFRasterization_Flag;
+    }
+
 private:
     enum {
         kNumShapes = 100,
diff --git a/gm/tests/outputs/add-config-pdf/output-expected/json-summary.txt b/gm/tests/outputs/add-config-pdf/output-expected/json-summary.txt
index fad1ffe..52eedd4 100644
--- a/gm/tests/outputs/add-config-pdf/output-expected/json-summary.txt
+++ b/gm/tests/outputs/add-config-pdf/output-expected/json-summary.txt
@@ -2,7 +2,9 @@
    "actual-results" : {
       "failed" : null,
       "failure-ignored" : null,
-      "no-comparison" : null,
+      "no-comparison" : {
+         "pdf/selftest1.png" : [ "bitmap-64bitMD5", 1149339852105949057 ]
+      },
       "succeeded" : {
          "565/selftest1.png" : [ "bitmap-64bitMD5", 12927999507540085554 ],
          "8888/selftest1.png" : [ "bitmap-64bitMD5", 1209453360120438698 ]
@@ -20,6 +22,10 @@
             [ "bitmap-64bitMD5", 1209453360120438698 ]
          ],
          "ignore-failure" : false
+      },
+      "pdf/selftest1.png" : {
+         "allowed-digests" : null,
+         "ignore-failure" : false
       }
    }
 }
diff --git a/gm/tests/outputs/add-config-pdf/output-expected/mismatchPath/pdf_poppler/bogusfile b/gm/tests/outputs/add-config-pdf/output-expected/mismatchPath/pdf_poppler/bogusfile
new file mode 100644
index 0000000..d86cd5b
--- /dev/null
+++ b/gm/tests/outputs/add-config-pdf/output-expected/mismatchPath/pdf_poppler/bogusfile
@@ -0,0 +1 @@
+Created additional file to make sure directory isn't empty, because self-test cannot handle empty directories.
diff --git a/gm/tests/outputs/add-config-pdf/output-expected/missingExpectationsPath/pdf/selftest1.png b/gm/tests/outputs/add-config-pdf/output-expected/missingExpectationsPath/pdf/selftest1.png
new file mode 100644
index 0000000..e5584b8
--- /dev/null
+++ b/gm/tests/outputs/add-config-pdf/output-expected/missingExpectationsPath/pdf/selftest1.png
@@ -0,0 +1 @@
+[contents of gm/tests/outputs/add-config-pdf/output-actual/missingExpectationsPath/pdf/selftest1.png]
diff --git a/gm/tests/outputs/add-config-pdf/output-expected/missingExpectationsPath/pdf_poppler/bogusfile b/gm/tests/outputs/add-config-pdf/output-expected/missingExpectationsPath/pdf_poppler/bogusfile
new file mode 100644
index 0000000..d86cd5b
--- /dev/null
+++ b/gm/tests/outputs/add-config-pdf/output-expected/missingExpectationsPath/pdf_poppler/bogusfile
@@ -0,0 +1 @@
+Created additional file to make sure directory isn't empty, because self-test cannot handle empty directories.
diff --git a/gm/tests/outputs/add-config-pdf/output-expected/stdout b/gm/tests/outputs/add-config-pdf/output-expected/stdout
index 4052bbf..0a1bda0 100644
--- a/gm/tests/outputs/add-config-pdf/output-expected/stdout
+++ b/gm/tests/outputs/add-config-pdf/output-expected/stdout
@@ -1,4 +1,5 @@
 GM: These configs will be run: 8888 565 pdf
+GM: These PDF rasterizers will be run: poppler
 GM: reading expectations from JSON summary file gm/tests/inputs/json/identical-bytes.json
 GM: writing to gm/tests/outputs/add-config-pdf/output-actual/writePath
 GM: writing mismatches to gm/tests/outputs/add-config-pdf/output-actual/mismatchPath
@@ -8,11 +9,11 @@
 GM: ... over  3 configs ["8888", "565", "pdf"]
 GM: ...  and  7 modes   ["pipe", "pipe cross-process", "pipe cross-process, shared address", "replay", "rtree", "serialize", "tilegrid"]
 GM: ... so there should be a total of 10 tests.
-GM: Ran 10 tests: NoGpuContext=0 IntentionallySkipped=1 RenderModeMismatch=0 ExpectationsMismatch=0 MissingExpectations=0 WritingReferenceImage=0
+GM: Ran 10 tests: NoGpuContext=0 IntentionallySkipped=0 RenderModeMismatch=0 ExpectationsMismatch=0 MissingExpectations=1 WritingReferenceImage=0
 GM: [*] 0 NoGpuContext:
-GM: [ ] 1 IntentionallySkipped: pdf/selftest1
+GM: [ ] 0 IntentionallySkipped:
 GM: [*] 0 RenderModeMismatch:
 GM: [*] 0 ExpectationsMismatch:
-GM: [ ] 0 MissingExpectations:
+GM: [ ] 1 MissingExpectations: pdf/selftest1
 GM: [*] 0 WritingReferenceImage:
 GM: (results marked with [*] will cause nonzero return value)
diff --git a/gm/tests/outputs/add-config-pdf/output-expected/writePath/pdf_poppler/bogusfile b/gm/tests/outputs/add-config-pdf/output-expected/writePath/pdf_poppler/bogusfile
new file mode 100644
index 0000000..d86cd5b
--- /dev/null
+++ b/gm/tests/outputs/add-config-pdf/output-expected/writePath/pdf_poppler/bogusfile
@@ -0,0 +1 @@
+Created additional file to make sure directory isn't empty, because self-test cannot handle empty directories.
diff --git a/gm/tests/outputs/add-config-pdf/output-expected/writePath/pdf_poppler/selftest1.png b/gm/tests/outputs/add-config-pdf/output-expected/writePath/pdf_poppler/selftest1.png
new file mode 100644
index 0000000..5d008d8
--- /dev/null
+++ b/gm/tests/outputs/add-config-pdf/output-expected/writePath/pdf_poppler/selftest1.png
@@ -0,0 +1 @@
+[contents of gm/tests/outputs/add-config-pdf/output-actual/writePath/pdf_poppler/selftest1.png]
diff --git a/gm/tests/outputs/pipe-playback-failure/output-expected/stderr b/gm/tests/outputs/pipe-playback-failure/output-expected/stderr
index 573600f..150a10b 100644
--- a/gm/tests/outputs/pipe-playback-failure/output-expected/stderr
+++ b/gm/tests/outputs/pipe-playback-failure/output-expected/stderr
@@ -1 +1,2 @@
+GM: FAILED to write bitmap: gm/tests/outputs/pipe-playback-failure/output-actual/mismatchPath/comparison/selftest1-pipe.png
 GM: ---- comparison/selftest1-pipe.png: 60000 (of 60000) differing pixels, max per-channel mismatch R=135 G=246 B=135 A=0