Add PDF viewer example.

R=ben@chromium.org

Review URL: https://codereview.chromium.org/690583002
diff --git a/DEPS b/DEPS
index b92a671..c471a15 100644
--- a/DEPS
+++ b/DEPS
@@ -28,6 +28,7 @@
   'libcxxabi_revision': '4ad1009ab3a59fa7a6896d74d5e4de5885697f95',
   'chromium_git': 'https://chromium.googlesource.com',
   'skia_git': 'https://skia.googlesource.com',
+  'pdfium_git': 'https://pdfium.googlesource.com',
   'boringssl_git': 'https://boringssl.googlesource.com',
   'sfntly_revision': '1bdaae8fc788a5ac8936d68bf24f37d977a13dac',
   'skia_revision': '32501a1bd4964576264795ada7e9c3bf572285b3',
@@ -45,6 +46,10 @@
   # and whatever else without interference from each other.
   'buildtools_revision': '56bc51aff4175d3fa27dcd0faa2c345ab046c8a5',
   # Three lines of non-changing comments so that
+  # the commit queue can handle CLs rolling PDFium
+  # and whatever else without interference from each other.
+  'pdfium_revision': '767aebbef641a89498deebc29369a078207b4dcc',
+  # Three lines of non-changing comments so that
   # the commit queue can handle CLs rolling BoringSSL
   # and whatever else without interference from each other.
   'boringssl_revision': '751e889b1d791d2b91c1437248454fa1d4e101a5',
@@ -123,6 +128,9 @@
   'src/third_party/mesa/src':
    Var('chromium_git') + '/chromium/deps/mesa.git' + '@' + '9731cb962963bca8a05f3d0f6ea40c3a781f0537',
 
+  'src/third_party/pdfium':
+   'https://pdfium.googlesource.com/pdfium.git' + '@' +  Var('pdfium_revision'),
+
   'src/third_party/boringssl/src':
    'https://boringssl.googlesource.com/boringssl.git' + '@' +  Var('boringssl_revision'),
 }
diff --git a/examples/BUILD.gn b/examples/BUILD.gn
index 8bdb389..0f03011 100644
--- a/examples/BUILD.gn
+++ b/examples/BUILD.gn
@@ -12,6 +12,7 @@
     "//examples/content_handler_demo",
     "//examples/echo",
     "//examples/http_server",
+    "//examples/pdf_viewer",
     "//examples/png_viewer",
     "//examples/sample_app",
     "//examples/surfaces_app",
diff --git a/examples/bitmap_uploader/BUILD.gn b/examples/bitmap_uploader/BUILD.gn
index e7cdeb5..5ecd768 100644
--- a/examples/bitmap_uploader/BUILD.gn
+++ b/examples/bitmap_uploader/BUILD.gn
@@ -11,6 +11,7 @@
   public_deps = [
     "//base",
     "//mojo/public/c/gles2",
+    "//mojo/public/gles2:for_shared_library",
     "//mojo/services/public/interfaces/gpu",
     "//mojo/services/public/interfaces/surfaces",
   ]
diff --git a/examples/pdf_viewer/BUILD.gn b/examples/pdf_viewer/BUILD.gn
new file mode 100644
index 0000000..6ba591d
--- /dev/null
+++ b/examples/pdf_viewer/BUILD.gn
@@ -0,0 +1,21 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+shared_library("pdf_viewer") {
+  sources = [
+    "pdf_viewer.cc"
+  ]
+
+  deps = [
+    "//examples/bitmap_uploader",
+    "//mojo/application",
+    "//mojo/public/c/system:for_shared_library",
+    "//mojo/public/cpp/bindings",
+    "//mojo/public/cpp/utility",
+    "//mojo/services/public/cpp/view_manager",
+    "//mojo/services/public/interfaces/content_handler",
+    "//mojo/services/public/interfaces/network",
+    "//third_party/pdfium",
+  ]
+}
diff --git a/examples/pdf_viewer/DEPS b/examples/pdf_viewer/DEPS
new file mode 100644
index 0000000..51c59e2
--- /dev/null
+++ b/examples/pdf_viewer/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+third_party/pdfium",
+  "+v8",
+]
diff --git a/examples/pdf_viewer/pdf_viewer.cc b/examples/pdf_viewer/pdf_viewer.cc
new file mode 100644
index 0000000..c4f0c04
--- /dev/null
+++ b/examples/pdf_viewer/pdf_viewer.cc
@@ -0,0 +1,261 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/strings/string_tokenizer.h"
+#include "examples/bitmap_uploader/bitmap_uploader.h"
+#include "mojo/application/application_runner_chromium.h"
+#include "mojo/public/c/system/main.h"
+#include "mojo/public/cpp/application/application_connection.h"
+#include "mojo/public/cpp/application/application_delegate.h"
+#include "mojo/public/cpp/application/application_impl.h"
+#include "mojo/public/cpp/application/interface_factory_impl.h"
+#include "mojo/public/cpp/application/service_provider_impl.h"
+#include "mojo/services/public/cpp/view_manager/types.h"
+#include "mojo/services/public/cpp/view_manager/view.h"
+#include "mojo/services/public/cpp/view_manager/view_manager.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_client_factory.h"
+#include "mojo/services/public/cpp/view_manager/view_manager_delegate.h"
+#include "mojo/services/public/cpp/view_manager/view_observer.h"
+#include "mojo/services/public/interfaces/content_handler/content_handler.mojom.h"
+#include "mojo/services/public/interfaces/input_events/input_events.mojom.h"
+#include "mojo/services/public/interfaces/input_events/input_key_codes.mojom.h"
+#include "third_party/pdfium/fpdfsdk/include/fpdf_ext.h"
+#include "third_party/pdfium/fpdfsdk/include/fpdfformfill.h"
+#include "third_party/pdfium/fpdfsdk/include/fpdftext.h"
+#include "third_party/pdfium/fpdfsdk/include/fpdfview.h"
+#include "v8/include/v8.h"
+
+#define BACKGROUND_COLOR 0xFF888888
+
+namespace mojo {
+namespace examples {
+
+class PDFViewer;
+
+class PDFView : public ViewManagerDelegate, public ViewObserver {
+ public:
+  static void Spawn(URLResponsePtr response,
+                    ServiceProviderImpl* exported_services,
+                    scoped_ptr<ServiceProvider> imported_services,
+                    Shell* shell) {
+    // PDFView deletes itself when its View is destroyed.
+    new PDFView(
+        response.Pass(), exported_services, imported_services.Pass(), shell);
+  }
+
+ private:
+  PDFView(URLResponsePtr response,
+          ServiceProviderImpl* exported_services,
+          scoped_ptr<ServiceProvider> imported_services,
+          Shell* shell)
+      : current_page_(0),
+        page_count_(0),
+        doc_(NULL),
+        imported_services_(imported_services.Pass()),
+        shell_(shell),
+        root_(nullptr),
+        view_manager_client_factory_(shell, this) {
+    exported_services->AddService(&view_manager_client_factory_);
+    FetchPDF(response.Pass());
+  }
+
+  virtual ~PDFView() {
+    if (doc_)
+      FPDF_CloseDocument(doc_);
+    if (root_)
+      root_->RemoveObserver(this);
+  }
+
+  // Overridden from ViewManagerDelegate:
+  virtual void OnEmbed(ViewManager* view_manager,
+                       View* root,
+                       ServiceProviderImpl* exported_services,
+                       scoped_ptr<ServiceProvider> imported_services) override {
+    root_ = root;
+    root_->AddObserver(this);
+    bitmap_uploader_.reset(new BitmapUploader(root_));
+    bitmap_uploader_->Init(shell_);
+    bitmap_uploader_->SetColor(BACKGROUND_COLOR);
+    DrawBitmap();
+  }
+
+  virtual void OnViewManagerDisconnected(ViewManager* view_manager) override {
+  }
+
+  // Overridden from ViewObserver:
+  virtual void OnViewBoundsChanged(View* view,
+                                   const Rect& old_bounds,
+                                   const Rect& new_bounds) override {
+    DCHECK_EQ(view, root_);
+    DrawBitmap();
+  }
+
+  virtual void OnViewInputEvent(View* view, const EventPtr& event) override {
+    if (event->key_data &&
+        (event->action != EVENT_TYPE_KEY_PRESSED ||
+         event->key_data->is_char)) {
+      return;
+    }
+    if ((event->key_data &&
+         event->key_data->windows_key_code == KEYBOARD_CODE_DOWN) ||
+        (event->wheel_data && event->wheel_data->y_offset < 0)) {
+      if (current_page_ < (page_count_ - 1)) {
+        current_page_++;
+        DrawBitmap();
+      }
+    } else if ((event->key_data &&
+                event->key_data->windows_key_code == KEYBOARD_CODE_UP) ||
+               (event->wheel_data && event->wheel_data->y_offset > 0)) {
+      if (current_page_ > 0) {
+        current_page_--;
+        DrawBitmap();
+      }
+    }
+  }
+
+  virtual void OnViewDestroyed(View* view) override {
+    DCHECK_EQ(view, root_);
+    delete this;
+  }
+
+  void DrawBitmap() {
+    if (!root_ || !doc_)
+      return;
+
+    FPDF_PAGE page = FPDF_LoadPage(doc_, current_page_);
+    FPDF_TEXTPAGE text_page = FPDFText_LoadPage(page);
+    int width = static_cast<int>(FPDF_GetPageWidth(page));
+    int height = static_cast<int>(FPDF_GetPageHeight(page));
+
+    scoped_ptr<std::vector<unsigned char>> bitmap;
+    bitmap.reset(new std::vector<unsigned char>);
+    bitmap->resize(width * height * 4);
+
+    FPDF_BITMAP f_bitmap = FPDFBitmap_CreateEx(
+        width, height, FPDFBitmap_BGRA, &(*bitmap)[0], width * 4);
+    FPDFBitmap_FillRect(f_bitmap, 0, 0, width, height, 0xFFFFFFFF);
+    FPDF_RenderPageBitmap(f_bitmap, page, 0, 0, width, height, 0, 0);
+    FPDFBitmap_Destroy(f_bitmap);
+
+    FPDFText_ClosePage(text_page);
+    FPDF_ClosePage(page);
+
+    bitmap_uploader_->SetBitmap(width, height, bitmap.Pass(),
+                                BitmapUploader::BGRA);
+  }
+
+  void FetchPDF(URLResponsePtr response) {
+    int content_length = GetContentLength(response->headers);
+    scoped_ptr<unsigned char[]> data;
+    data_.reset(new unsigned char[content_length]);
+    unsigned char* buf = data_.get();
+    uint32_t bytes_remaining = content_length;
+    uint32_t num_bytes = bytes_remaining;
+    while (bytes_remaining > 0) {
+      MojoResult result = ReadDataRaw(
+          response->body.get(), buf, &num_bytes, MOJO_READ_DATA_FLAG_NONE);
+      if (result == MOJO_RESULT_SHOULD_WAIT) {
+        Wait(response->body.get(),
+             MOJO_HANDLE_SIGNAL_READABLE,
+             MOJO_DEADLINE_INDEFINITE);
+      } else if (result == MOJO_RESULT_OK) {
+        buf += num_bytes;
+        num_bytes = bytes_remaining -= num_bytes;
+      } else {
+        break;
+      }
+    }
+
+    doc_ = FPDF_LoadMemDocument(data_.get(), content_length, NULL);
+    page_count_ = FPDF_GetPageCount(doc_);
+  }
+
+  int GetContentLength(const Array<String>& headers) {
+    for (size_t i = 0; i < headers.size(); ++i) {
+      base::StringTokenizer t(headers[i], ": ;=");
+      while (t.GetNext()) {
+        if (!t.token_is_delim() && t.token() == "Content-Length") {
+          while (t.GetNext()) {
+            if (!t.token_is_delim())
+              return atoi(t.token().c_str());
+          }
+        }
+      }
+    }
+    return 0;
+  }
+
+  scoped_ptr<unsigned char[]> data_;
+  int current_page_;
+  int page_count_;
+  FPDF_DOCUMENT doc_;
+  scoped_ptr<ServiceProvider> imported_services_;
+  Shell* shell_;
+  View* root_;
+  ViewManagerClientFactory view_manager_client_factory_;
+  scoped_ptr<BitmapUploader> bitmap_uploader_;
+
+  DISALLOW_COPY_AND_ASSIGN(PDFView);
+};
+
+class ContentHandlerImpl : public InterfaceImpl<ContentHandler> {
+ public:
+  explicit ContentHandlerImpl(Shell* shell) : shell_(shell) {}
+  virtual ~ContentHandlerImpl() {}
+
+ private:
+  // Overridden from ContentHandler:
+  virtual void OnConnect(
+      const mojo::String& requestor_url,
+      URLResponsePtr response,
+      InterfaceRequest<ServiceProvider> service_provider) override {
+    ServiceProviderImpl* exported_services = new ServiceProviderImpl();
+    BindToRequest(exported_services, &service_provider);
+    scoped_ptr<ServiceProvider> remote(
+        exported_services->CreateRemoteServiceProvider());
+    PDFView::Spawn(response.Pass(), exported_services, remote.Pass(), shell_);
+  }
+
+  Shell* shell_;
+
+  DISALLOW_COPY_AND_ASSIGN(ContentHandlerImpl);
+};
+
+class PDFViewer : public ApplicationDelegate {
+ public:
+  PDFViewer() {}
+  virtual ~PDFViewer() {
+    FPDF_DestroyLibrary();
+  }
+ private:
+  // Overridden from ApplicationDelegate:
+  virtual void Initialize(ApplicationImpl* app) override {
+    content_handler_factory_.reset(
+        new InterfaceFactoryImplWithContext<ContentHandlerImpl, Shell>(
+            app->shell()));
+
+    v8::V8::InitializeICU();
+    FPDF_InitLibrary(NULL);
+  }
+
+  // Overridden from ApplicationDelegate:
+  virtual bool ConfigureIncomingConnection(
+      ApplicationConnection* connection) override {
+    connection->AddService(content_handler_factory_.get());
+    return true;
+  }
+
+  scoped_ptr<InterfaceFactoryImplWithContext<ContentHandlerImpl, Shell> >
+      content_handler_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(PDFViewer);
+};
+
+}  // namespace examples
+}  // namespace mojo
+
+MojoResult MojoMain(MojoHandle shell_handle) {
+  mojo::ApplicationRunnerChromium runner(new mojo::examples::PDFViewer);
+  return runner.Run(shell_handle);
+}
diff --git a/examples/png_viewer/BUILD.gn b/examples/png_viewer/BUILD.gn
index 85910cb..4dce21a 100644
--- a/examples/png_viewer/BUILD.gn
+++ b/examples/png_viewer/BUILD.gn
@@ -14,7 +14,6 @@
     "//mojo/public/c/system:for_shared_library",
     "//mojo/public/cpp/bindings",
     "//mojo/public/cpp/utility",
-    "//mojo/public/gles2:for_shared_library",
     "//mojo/services/public/cpp/view_manager",
     "//mojo/services/public/interfaces/content_handler",
     "//mojo/services/public/interfaces/network",
diff --git a/mojo/shell/context.cc b/mojo/shell/context.cc
index 215ed7b..d7f8e30 100644
--- a/mojo/shell/context.cc
+++ b/mojo/shell/context.cc
@@ -69,6 +69,7 @@
 void InitContentHandlers(DynamicApplicationLoader* loader,
                          base::CommandLine* command_line) {
   // Default content handlers.
+  loader->RegisterContentHandler("application/pdf", GURL("mojo://pdf_viewer/"));
   loader->RegisterContentHandler("image/png", GURL("mojo://png_viewer/"));
   loader->RegisterContentHandler("text/html", GURL("mojo://html_viewer/"));