some refactoring ideas

  - streamline source initialization so that each
    kind of source only has to mention what's interesting
    to it, with defaults for some obvious fields.

  - have sources return a Result rather than a bool,
    encapsulating Ok/Skip/Fail and any failure message,
    but grab this in a middle draw() layer so each backend
    doesn't have to care about anything but Ok/Skip.

Change-Id: I715278ba5e05892e42c8875bd607160b0cc594b5
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/206763
Auto-Submit: Mike Klein <mtklein@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/tools/fm/fm.cpp b/tools/fm/fm.cpp
index ab7f04a..6e385d4 100644
--- a/tools/fm/fm.cpp
+++ b/tools/fm/fm.cpp
@@ -98,119 +98,106 @@
     exit(1);
 }
 
+struct Result {
+    enum { Ok, Skip, Fail} status;
+    SkString               failure;
+};
+static const Result ok = {Result::Ok,   {}},
+                  skip = {Result::Skip, {}};
+
+template <typename... Args>
+static Result fail(const char* why, Args... args) {
+    return { Result::Fail, SkStringPrintf(why, args...) };
+}
+
+
 struct Source {
     SkString                               name;
     SkISize                                size;
-    std::function<void(GrContextOptions*)> tweak;
-    std::function<bool(SkCanvas*)>         draw;  // true -> ok, false -> skip;
-                                                  // failures should exit_with_failure()
+    std::function<Result(SkCanvas*)>       draw;
+    std::function<void(GrContextOptions*)> tweak = [](GrContextOptions*){};
 };
 
-static Source gm_source(std::shared_ptr<skiagm::GM> gm) {
-    return {
-        SkString{gm->getName()},
-        gm->getISize(),
-        [gm](GrContextOptions* options) { gm->modifyGrContextOptions(options); },
-        [gm](SkCanvas* canvas) {
-            SkString err;
-            switch (gm->draw(canvas, &err)) {
-                case skiagm::DrawResult::kOk:   return true;
-                case skiagm::DrawResult::kSkip: break;
-                case skiagm::DrawResult::kFail:
-                    fprintf(stderr, "Drawing GM %s failed: %s\n", gm->getName(), err.c_str());
-                    exit_with_failure();
-            }
-            return false;
-        },
+static void init(Source* source, std::shared_ptr<skiagm::GM> gm) {
+    source->size  = gm->getISize();
+    source->tweak = [gm](GrContextOptions* options) { gm->modifyGrContextOptions(options); };
+    source->draw  = [gm](SkCanvas* canvas) {
+        SkString err;
+        switch (gm->draw(canvas, &err)) {
+            case skiagm::DrawResult::kOk:   break;
+            case skiagm::DrawResult::kSkip: return skip;
+            case skiagm::DrawResult::kFail: return fail(err.c_str());
+        }
+        return ok;
     };
 }
 
-static Source picture_source(SkString name, sk_sp<SkPicture> pic) {
-    return {
-        name,
-        pic->cullRect().roundOut().size(),
-        [](GrContextOptions*) {},
-        [pic](SkCanvas* canvas) {
-            canvas->drawPicture(pic);
-            return true;
-        },
+static void init(Source* source, sk_sp<SkPicture> pic) {
+    source->size = pic->cullRect().roundOut().size();
+    source->draw = [pic](SkCanvas* canvas) {
+        canvas->drawPicture(pic);
+        return ok;
     };
 }
 
-static Source codec_source(SkString name, std::shared_ptr<SkCodec> codec) {
-    return {
-        name,
-        codec->dimensions(),
-        [](GrContextOptions*) {},
-        [codec](SkCanvas* canvas) {
-            SkImageInfo info = codec->getInfo();
-            if (FLAGS_decodeToDst) {
-                info = canvas->imageInfo().makeWH(info.width(),
-                                                  info.height());
-            }
+static void init(Source* source, std::shared_ptr<SkCodec> codec) {
+    source->size = codec->dimensions();
+    source->draw = [codec](SkCanvas* canvas) {
+        SkImageInfo info = codec->getInfo();
+        if (FLAGS_decodeToDst) {
+            info = canvas->imageInfo().makeWH(info.width(),
+                                              info.height());
+        }
 
-            SkBitmap bm;
-            bm.allocPixels(info);
-
-            switch (auto result = codec->getPixels(info, bm.getPixels(), bm.rowBytes())) {
-                case SkCodec::kSuccess:
-                case SkCodec::kErrorInInput:
-                case SkCodec::kIncompleteInput:
-                    canvas->drawBitmap(bm, 0,0);
-                    return true;
-                default:
-                    fprintf(stderr, "SkCodec::getPixels failed: %d.", result);
-                    exit_with_failure();
-            }
-            return false;
-        },
+        SkBitmap bm;
+        bm.allocPixels(info);
+        switch (SkCodec::Result result = codec->getPixels(info, bm.getPixels(), bm.rowBytes())) {
+            case SkCodec::kSuccess:
+            case SkCodec::kErrorInInput:
+            case SkCodec::kIncompleteInput: canvas->drawBitmap(bm, 0,0);
+                                            break;
+            default: return fail("codec->getPixels() failed: %d\n", result);
+        }
+        return ok;
     };
 }
 
-static Source svg_source(SkString name, sk_sp<SkSVGDOM> svg) {
-    return {
-        name,
-        svg->containerSize().isEmpty() ? SkISize{1000,1000}
-                                       : svg->containerSize().toCeil(),
-        [](GrContextOptions*) {},
-        [svg](SkCanvas* canvas) {
-            svg->render(canvas);
-            return true;
-        },
+static void init(Source* source, sk_sp<SkSVGDOM> svg) {
+    source->size = svg->containerSize().isEmpty() ? SkISize{1000,1000}
+                                                  : svg->containerSize().toCeil();
+    source->draw = [svg](SkCanvas* canvas) {
+        svg->render(canvas);
+        return ok;
     };
 }
 
-static Source skottie_source(SkString name, sk_sp<skottie::Animation> animation) {
-    return {
-        name,
-        {1000,1000},
-        [](GrContextOptions*) {},
-        [animation](SkCanvas* canvas) {
-            canvas->clear(SK_ColorWHITE);
+static void init(Source* source, sk_sp<skottie::Animation> animation) {
+    source->size = {1000,1000};
+    source->draw = [animation](SkCanvas* canvas) {
+        canvas->clear(SK_ColorWHITE);
 
-            // Draw frames in a shuffled order to exercise nonlinear frame progression.
-            // The film strip will still be in time order, just drawn out of order.
-            const int order[] = { 4, 0, 3, 1, 2 };
-            const int tiles = SK_ARRAY_COUNT(order);
-            const float dim = 1000.0f / tiles;
+        // Draw frames in a shuffled order to exercise nonlinear frame progression.
+        // The film strip will still be in time order, just drawn out of order.
+        const int order[] = { 4, 0, 3, 1, 2 };
+        const int tiles = SK_ARRAY_COUNT(order);
+        const float dim = 1000.0f / tiles;
 
-            const float dt = 1.0f / (tiles*tiles - 1);
+        const float dt = 1.0f / (tiles*tiles - 1);
 
-            for (int y : order)
-            for (int x : order) {
-                SkRect dst = {x*dim, y*dim, (x+1)*dim, (y+1)*dim};
+        for (int y : order)
+        for (int x : order) {
+            SkRect dst = {x*dim, y*dim, (x+1)*dim, (y+1)*dim};
 
-                SkAutoCanvasRestore _(canvas, true/*save now*/);
-                canvas->clipRect(dst, /*aa=*/true);
-                canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(animation->size()),
-                                                        dst,
-                                                        SkMatrix::kCenter_ScaleToFit));
-                float t = (y*tiles + x) * dt;
-                animation->seek(t);
-                animation->render(canvas);
-            }
-            return true;
-        },
+            SkAutoCanvasRestore _(canvas, true/*save now*/);
+            canvas->clipRect(dst, /*aa=*/true);
+            canvas->concat(SkMatrix::MakeRectToRect(SkRect::MakeSize(animation->size()),
+                                                    dst,
+                                                    SkMatrix::kCenter_ScaleToFit));
+            float t = (y*tiles + x) * dt;
+            animation->seek(t);
+            animation->render(canvas);
+        }
+        return ok;
     };
 }
 
@@ -390,42 +377,45 @@
     }
 
     SkTArray<Source> sources;
-    for (const SkString& source : FLAGS_sources) {
-        if (skiagm::GMFactory* factory = gm_factories.find(source)) {
+    for (const SkString& name : FLAGS_sources) {
+        Source* source = &sources.push_back();
+
+        if (skiagm::GMFactory* factory = gm_factories.find(name)) {
             std::shared_ptr<skiagm::GM> gm{(*factory)(nullptr)};
-            sources.push_back(gm_source(gm));
+            source->name = name;
+            init(source, gm);
             continue;
         }
 
-        if (sk_sp<SkData> blob = SkData::MakeFromFileName(source.c_str())) {
-            const SkString dir  = SkOSPath::Dirname (source.c_str()),
-                           name = SkOSPath::Basename(source.c_str());
+        if (sk_sp<SkData> blob = SkData::MakeFromFileName(name.c_str())) {
+            source->name = SkOSPath::Basename(name.c_str());
 
             if (name.endsWith(".skp")) {
                 if (sk_sp<SkPicture> pic = SkPicture::MakeFromData(blob.get())) {
-                    sources.push_back(picture_source(name, pic));
+                    init(source, pic);
                     continue;
                 }
             } else if (name.endsWith(".svg")) {
                 SkMemoryStream stream{blob};
                 if (sk_sp<SkSVGDOM> svg = SkSVGDOM::MakeFromStream(stream)) {
-                    sources.push_back(svg_source(name, svg));
+                    init(source, svg);
                     continue;
                 }
             } else if (name.endsWith(".json")) {
+                const SkString dir  = SkOSPath::Dirname(name.c_str());
                 if (sk_sp<skottie::Animation> animation = skottie::Animation::Builder()
                         .setResourceProvider(skottie_utils::FileResourceProvider::Make(dir))
                         .make((const char*)blob->data(), blob->size())) {
-                    sources.push_back(skottie_source(name, animation));
+                    init(source, animation);
                     continue;
                 }
             } else if (std::shared_ptr<SkCodec> codec = SkCodec::MakeFromData(blob)) {
-                sources.push_back(codec_source(name, codec));
+                init(source, codec);
                 continue;
             }
         }
 
-        fprintf(stderr, "Don't understand source '%s'... bailing out.\n", source.c_str());
+        fprintf(stderr, "Don't understand source '%s'... bailing out.\n", name.c_str());
         return 1;
     }
 
@@ -509,6 +499,18 @@
         const SkImageInfo info = unsized_info.makeWH(source.size.width(),
                                                      source.size.height());
 
+        auto draw = [&source](SkCanvas* canvas) {
+            Result result = source.draw(canvas);
+            switch (result.status) {
+                case Result::Ok:   break;
+                case Result::Skip: return false;
+                case Result::Fail:
+                    fprintf(stderr, "%s failed: %s\n", source.name.c_str(), result.failure.c_str());
+                    exit_with_failure();
+            }
+            return true;
+        };
+
         GrContextOptions options = baseOptions;
         source.tweak(&options);
         GrContextFactory factory(options);  // N.B. factory must outlive image
@@ -518,19 +520,18 @@
         const char*    ext = ".png";
         switch (backend) {
             case kCPU_Backend:
-                image = draw_with_cpu(source.draw, info);
+                image = draw_with_cpu(draw, info);
                 break;
             case kSKP_Backend:
-                blob = draw_as_skp(source.draw, info);
+                blob = draw_as_skp(draw, info);
                 ext  = ".skp";
                 break;
             case kPDF_Backend:
-                blob = draw_as_pdf(source.draw, info, source.name);
+                blob = draw_as_pdf(draw, info, source.name);
                 ext  = ".pdf";
                 break;
             default:
-                image = draw_with_gpu(source.draw, info,
-                                      (GrContextFactory::ContextType)backend, &factory);
+                image = draw_with_gpu(draw, info, (GrContextFactory::ContextType)backend, &factory);
                 break;
         }