SkDocument base for pdf, xps, etc.

R=scroggo@google.com

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

git-svn-id: http://skia.googlecode.com/svn/trunk@9476 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gyp/core.gypi b/gyp/core.gypi
index 534c888..a88f722 100644
--- a/gyp/core.gypi
+++ b/gyp/core.gypi
@@ -186,6 +186,8 @@
         '<(skia_src_path)/core/SkWriter32.cpp',
         '<(skia_src_path)/core/SkXfermode.cpp',
 
+        '<(skia_src_path)/doc/SkDocument.cpp',
+
         '<(skia_src_path)/image/SkDataPixelRef.cpp',
         '<(skia_src_path)/image/SkImage.cpp',
         '<(skia_src_path)/image/SkImagePriv.cpp',
diff --git a/gyp/pdf.gyp b/gyp/pdf.gyp
index 31b48db..f89df93 100644
--- a/gyp/pdf.gyp
+++ b/gyp/pdf.gyp
@@ -47,6 +47,8 @@
         '../src/pdf/SkPDFUtils.cpp',
         '../src/pdf/SkPDFUtils.h',
         '../src/pdf/SkTSet.h',
+
+        '../src/doc/SkDocument_PDF.cpp',
       ],
       # This section makes all targets that depend on this target
       # #define SK_SUPPORT_PDF and have access to the pdf header files.
diff --git a/gyp/tools.gyp b/gyp/tools.gyp
index 25aa1db..62bbbd0 100644
--- a/gyp/tools.gyp
+++ b/gyp/tools.gyp
@@ -64,6 +64,7 @@
       ],
       'dependencies': [
         'skia_lib.gyp:skia_lib',
+        'pdf.gyp:pdf',
         'flags.gyp:flags',
       ],
     },
diff --git a/include/core/SkDocument.h b/include/core/SkDocument.h
new file mode 100644
index 0000000..5ca0a5c
--- /dev/null
+++ b/include/core/SkDocument.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkDocument_DEFINED
+#define SkDocument_DEFINED
+
+#include "SkRect.h"
+#include "SkRefCnt.h"
+
+class SkCanvas;
+class SkWStream;
+
+/**
+ *  High-level API for creating a document-based canvas. To use..
+ *
+ *  1. Create a document, specifying a stream to store the output.
+ *  2. For each "page" of content:
+ *      a. canvas = doc->beginPage(...)
+ *      b. draw_my_content(canvas);
+ *      c. doc->endPage();
+ *  3. Close the document with doc->close().
+ */
+class SkDocument : public SkRefCnt {
+public:
+    /**
+     *  Create a PDF-backed document, writing the results into a file.
+     *  If there is an error trying to create the doc, returns NULL.
+     */
+    static SkDocument* CreatePDF(const char filename[]);
+
+    /**
+     *  Create a PDF-backed document, writing the results into a stream.
+     *  If there is an error trying to create the doc, returns NULL.
+     *
+     *  The document may write to the stream at anytime during its lifetime,
+     *  until either close() is called or the document is deleted. Once close()
+     *  has been called, and all of the data has been written to the stream,
+     *  if there is a Done proc provided, it will be called with the stream.
+     *  The proc can delete the stream, or whatever it needs to do.
+     */
+    static SkDocument* CreatePDF(SkWStream*, void (*Done)(SkWStream*) = NULL);
+
+    /**
+     *  Begin a new page for the document, returning the canvas that will draw
+     *  into the page. The document owns this canvas, and it will go out of
+     *  scope when endPage() or close() is called, or the document is deleted.
+     */
+    SkCanvas* beginPage(SkScalar width, SkScalar height,
+                        const SkRect* content = NULL);
+
+    /**
+     *  Call endPage() when the content for the current page has been drawn
+     *  (into the canvas returned by beginPage()). After this call the canvas
+     *  returned by beginPage() will be out-of-scope.
+     */
+    void endPage();
+
+    /**
+     *  Call close() when all pages have been drawn. This will close the file
+     *  or stream holding the document's contents. After close() the document
+     *  can no longer add new pages. Deleting the document will automatically
+     *  call close() if need be.
+     */
+    void close();
+
+protected:
+    SkDocument(SkWStream*, void (*)(SkWStream*));
+    // note: subclasses must call close() in their destructor, as the base class
+    // cannot do this for them.
+    virtual ~SkDocument();
+
+    virtual SkCanvas* onBeginPage(SkScalar width, SkScalar height,
+                                  const SkRect& content) = 0;
+    virtual void onEndPage() = 0;
+    virtual void onClose(SkWStream*) = 0;
+
+    enum State {
+        kBetweenPages_State,
+        kInPage_State,
+        kClosed_State
+    };
+    State getState() const { return fState; }
+
+private:
+    SkWStream* fStream;
+    void       (*fDoneProc)(SkWStream*);
+    State      fState;    
+};
+
+#endif
diff --git a/src/doc/SkDocument.cpp b/src/doc/SkDocument.cpp
new file mode 100644
index 0000000..4298b3f
--- /dev/null
+++ b/src/doc/SkDocument.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDocument.h"
+#include "SkStream.h"
+
+SkDocument::SkDocument(SkWStream* stream, void (*doneProc)(SkWStream*)) {
+    fStream = stream;   // we do not own this object.
+    fDoneProc = doneProc;
+    fState = kBetweenPages_State;
+}
+
+SkDocument::~SkDocument() {
+    this->close();
+}
+
+SkCanvas* SkDocument::beginPage(SkScalar width, SkScalar height,
+                                const SkRect* content) {
+    if (width <= 0 || height <= 0) {
+        return NULL;
+    }
+
+    SkRect outer = SkRect::MakeWH(width, height);
+    SkRect inner;
+    if (content) {
+        inner = *content;
+        if (!inner.intersect(outer)) {
+            return NULL;
+        }
+    } else {
+        inner = outer;
+    }
+
+    for (;;) {
+        switch (fState) {
+            case kBetweenPages_State:
+                fState = kInPage_State;
+                return this->onBeginPage(width, height, inner);
+            case kInPage_State:
+                this->endPage();
+                break;
+            case kClosed_State:
+                return NULL;
+        }
+    }
+    SkASSERT(!"never get here");
+    return NULL;
+}
+
+void SkDocument::endPage() {
+    if (kInPage_State == fState) {
+        fState = kBetweenPages_State;
+        this->onEndPage();
+    }
+}
+
+void SkDocument::close() {
+    for (;;) {
+        switch (fState) {
+            case kBetweenPages_State:
+                fState = kClosed_State;
+                this->onClose(fStream);
+
+                if (fDoneProc) {
+                    fDoneProc(fStream);
+                }
+                // we don't own the stream, but we mark it NULL since we can
+                // no longer write to it.
+                fStream = NULL;
+                return;
+            case kInPage_State:
+                this->endPage();
+                break;
+            case kClosed_State:
+                return;
+        }
+    }
+}
+
diff --git a/src/doc/SkDocument_PDF.cpp b/src/doc/SkDocument_PDF.cpp
new file mode 100644
index 0000000..3d9c463
--- /dev/null
+++ b/src/doc/SkDocument_PDF.cpp
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkDocument.h"
+#include "SkPDFDocument.h"
+#include "SkPDFDevice.h"
+
+class SkDocument_PDF : public SkDocument {
+public:
+    SkDocument_PDF(SkWStream* stream, void (*doneProc)(SkWStream*))
+            : SkDocument(stream, doneProc) {
+        fDoc = SkNEW(SkPDFDocument);
+        fCanvas = NULL;
+        fDevice = NULL;
+    }
+    
+    virtual ~SkDocument_PDF() {
+        // subclasses must call close() in their destructors
+        this->close();
+    }
+
+protected:
+    virtual SkCanvas* onBeginPage(SkScalar width, SkScalar height,
+                                  const SkRect& content) SK_OVERRIDE {
+        SkASSERT(NULL == fCanvas);
+        SkASSERT(NULL == fDevice);
+
+        SkISize pageS, contentS;
+        SkMatrix matrix;
+
+        pageS.set(SkScalarRoundToInt(width), SkScalarRoundToInt(height));
+        contentS.set(SkScalarRoundToInt(content.width()),
+                     SkScalarRoundToInt(content.height()));
+        matrix.setTranslate(content.fLeft, content.fTop);
+
+        fDevice = SkNEW_ARGS(SkPDFDevice, (pageS, contentS, matrix));
+        fCanvas = SkNEW_ARGS(SkCanvas, (fDevice));
+        return fCanvas;
+    }
+
+    virtual void onEndPage() SK_OVERRIDE {
+        SkASSERT(fCanvas);
+        SkASSERT(fDevice);
+
+        fCanvas->flush();
+        fDoc->appendPage(fDevice);
+
+        fCanvas->unref();
+        fDevice->unref();
+
+        fCanvas = NULL;
+        fDevice = NULL;
+    }
+
+    virtual void onClose(SkWStream* stream) SK_OVERRIDE {
+        SkASSERT(NULL == fCanvas);
+        SkASSERT(NULL == fDevice);
+
+        fDoc->emitPDF(stream);
+        SkDELETE(fDoc);
+        fDoc = NULL;
+    }
+
+private:
+    SkPDFDocument*  fDoc;
+    SkPDFDevice*    fDevice;
+    SkCanvas*       fCanvas;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkDocument* SkDocument::CreatePDF(SkWStream* stream, void (*done)(SkWStream*)) {
+    return stream ? SkNEW_ARGS(SkDocument_PDF, (stream, done)) : NULL;
+}
+
+static void delete_wstream(SkWStream* stream) {
+    SkDELETE(stream);
+}
+
+SkDocument* SkDocument::CreatePDF(const char path[]) {
+    SkFILEWStream* stream = SkNEW_ARGS(SkFILEWStream, (path));
+    if (!stream->isValid()) {
+        SkDELETE(stream);
+        return NULL;
+    }
+    return SkNEW_ARGS(SkDocument_PDF, (stream, delete_wstream));
+}
+
diff --git a/tools/skhello.cpp b/tools/skhello.cpp
index 668c3a9..036b08a 100644
--- a/tools/skhello.cpp
+++ b/tools/skhello.cpp
@@ -8,22 +8,62 @@
 #include "SkCanvas.h"
 #include "SkCommandLineFlags.h"
 #include "SkData.h"
+#include "SkDocument.h"
 #include "SkGraphics.h"
 #include "SkSurface.h"
 #include "SkImage.h"
 #include "SkStream.h"
 #include "SkString.h"
 
-DEFINE_string2(outFile, o, "skhello.png", "The filename to write the image.");
+DEFINE_string2(outFile, o, "skhello", "The filename to write the image.");
 DEFINE_string2(text, t, "Hello", "The string to write.");
 
+static void doDraw(SkCanvas* canvas, const SkPaint& paint, const char text[]) {
+    SkRect bounds;
+    canvas->getClipBounds(&bounds);
+
+    canvas->drawColor(SK_ColorWHITE);
+    canvas->drawText(text, strlen(text),
+                     bounds.centerX(), bounds.centerY(),
+                     paint);
+}
+
+static bool do_surface(int w, int h, const char path[], const char text[],
+                       const SkPaint& paint) {
+    SkImage::Info info = {
+        w, h, SkImage::kPMColor_ColorType, SkImage::kPremul_AlphaType
+    };
+    SkAutoTUnref<SkSurface> surface(SkSurface::NewRaster(info));
+    doDraw(surface->getCanvas(), paint, text);
+    
+    SkAutoTUnref<SkImage> image(surface->newImageSnapshot());
+    SkAutoDataUnref data(image->encode());
+    if (NULL == data.get()) {
+        return false;
+    }
+    SkFILEWStream stream(path);
+    return stream.write(data->data(), data->size());
+}
+
+static bool do_document(int w, int h, const char path[], const char text[],
+                        const SkPaint& paint) {
+    SkAutoTUnref<SkDocument> doc(SkDocument::CreatePDF(path));
+    if (doc.get()) {
+        SkScalar width = SkIntToScalar(w);
+        SkScalar height = SkIntToScalar(h);
+        doDraw(doc->beginPage(width, height, NULL), paint, text);
+        return true;
+    }
+    return false;
+}
+
 int tool_main(int argc, char** argv);
 int tool_main(int argc, char** argv) {
     SkCommandLineFlags::SetUsage("");
     SkCommandLineFlags::Parse(argc, argv);
 
     SkAutoGraphics ag;
-    SkString path("skhello.png");
+    SkString path("skhello");
     SkString text("Hello");
 
     if (!FLAGS_outFile.isEmpty()) {
@@ -44,24 +84,23 @@
     int w = SkScalarRound(width) + 30;
     int h = SkScalarRound(spacing) + 30;
 
-    SkImage::Info info = {
-        w, h, SkImage::kPMColor_ColorType, SkImage::kPremul_AlphaType
+    static const struct {
+        bool (*fProc)(int w, int h, const char path[], const char text[],
+                      const SkPaint&);
+        const char* fSuffix;
+    } gRec[] = {
+        { do_surface, ".png" },
+        { do_document, ".pdf" },
     };
-    SkAutoTUnref<SkSurface> surface(SkSurface::NewRaster(info));
-    SkCanvas* canvas = surface->getCanvas();
-
-    canvas->drawColor(SK_ColorWHITE);
-    canvas->drawText(text.c_str(), text.size(),
-                     SkIntToScalar(w)/2, SkIntToScalar(h)*2/3,
-                     paint);
-
-    SkAutoTUnref<SkImage> image(surface->newImageSnapshot());
-    SkAutoDataUnref data(image->encode());
-    if (NULL == data.get()) {
-        return -1;
+    
+    for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); ++i) {
+        SkString file;
+        file.printf("%s%s", path.c_str(), gRec[i].fSuffix);
+        if (!gRec[i].fProc(w, h, file.c_str(), text.c_str(), paint)) {
+            return -1;
+        }
     }
-    SkFILEWStream stream(path.c_str());
-    return stream.write(data->data(), data->size());
+    return 0;
 }
 
 #if !defined SK_BUILD_FOR_IOS