Chromium Code Reviews| Index: examples/ui/pdf_viewer/pdf_viewer.cc |
| diff --git a/examples/ui/pdf_viewer/pdf_viewer.cc b/examples/ui/pdf_viewer/pdf_viewer.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..6e5c0699cbc26652fbd0d1d0b47ce28563ae425d |
| --- /dev/null |
| +++ b/examples/ui/pdf_viewer/pdf_viewer.cc |
| @@ -0,0 +1,297 @@ |
| +// 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 <memory> |
| +#include <string> |
| + |
| +#include "base/bind.h" |
| +#include "base/macros.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "mojo/application/application_runner_chromium.h" |
| +#include "mojo/data_pipe_utils/data_pipe_utils.h" |
| +#include "mojo/public/c/system/main.h" |
| +#include "mojo/ui/content_viewer_app.h" |
| +#include "mojo/ui/ganesh_view.h" |
| +#include "mojo/ui/input_handler.h" |
| +#include "third_party/pdfium/fpdfsdk/include/fpdf_ext.h" |
| +#include "third_party/pdfium/fpdfsdk/include/fpdfview.h" |
| +#include "third_party/skia/include/core/SkCanvas.h" |
| +#include "third_party/skia/include/core/SkData.h" |
| +#include "third_party/skia/include/core/SkImage.h" |
| +#include "third_party/skia/include/core/SkImageInfo.h" |
| +#include "third_party/skia/include/core/SkSurface.h" |
| +#include "v8/include/v8.h" |
| + |
| +namespace examples { |
| + |
| +constexpr uint32_t kContentImageResourceId = 1; |
| +constexpr uint32_t kRootNodeId = mojo::gfx::composition::kSceneRootNodeId; |
| + |
| +class PDFLibrary; |
| + |
| +class PDFDocument { |
| + public: |
| + PDFDocument(const std::shared_ptr<PDFLibrary>& pdf_library, |
| + FPDF_DOCUMENT doc, |
| + const std::string& data) |
| + : pdf_library_(pdf_library), |
| + doc_(doc), |
| + data_(data), |
| + page_count_(FPDF_GetPageCount(doc_)) {} |
| + |
| + ~PDFDocument() { FPDF_CloseDocument(doc_); } |
| + |
| + uint32_t page_count() { return page_count_; } |
| + |
| + skia::RefPtr<SkImage> DrawPage(int page_index) { |
| + FPDF_PAGE page = FPDF_LoadPage(doc_, page_index); |
| + int width = static_cast<int>(FPDF_GetPageWidth(page)); |
| + int height = static_cast<int>(FPDF_GetPageHeight(page)); |
| + int stride = width * 4; |
| + |
| + skia::RefPtr<SkData> pixels = |
| + skia::AdoptRef(SkData::NewUninitialized(stride * height)); |
| + DCHECK(pixels); |
| + |
| + FPDF_BITMAP bitmap = FPDFBitmap_CreateEx(width, height, FPDFBitmap_BGRA, |
| + pixels->writable_data(), stride); |
| + FPDFBitmap_FillRect(bitmap, 0, 0, width, height, 0xFFFFFFFF); |
| + FPDF_RenderPageBitmap(bitmap, page, 0, 0, width, height, 0, 0); |
| + FPDFBitmap_Destroy(bitmap); |
| + |
| + FPDF_ClosePage(page); |
| + |
| + SkImageInfo info = SkImageInfo::Make(width, height, kBGRA_8888_SkColorType, |
| + kOpaque_SkAlphaType); |
| + return skia::AdoptRef(SkImage::NewRasterData(info, pixels.get(), stride)); |
| + } |
| + |
| + private: |
| + std::shared_ptr<PDFLibrary> pdf_library_; |
| + FPDF_DOCUMENT doc_; |
| + std::string data_; |
| + uint32_t page_count_; |
| +}; |
| + |
| +class PDFLibrary : public std::enable_shared_from_this<PDFLibrary> { |
| + public: |
| + PDFLibrary() { |
| + v8::V8::InitializeICU(); |
| + FPDF_InitLibrary(); |
| + } |
| + |
| + ~PDFLibrary() { FPDF_DestroyLibrary(); } |
| + |
| + std::shared_ptr<PDFDocument> Decode(mojo::URLResponsePtr response) { |
| + std::string data; |
| + mojo::common::BlockingCopyToString(response->body.Pass(), &data); |
| + if (data.length() >= static_cast<size_t>(std::numeric_limits<int>::max())) |
| + return nullptr; |
| + |
| + FPDF_DOCUMENT doc = FPDF_LoadMemDocument( |
| + data.data(), static_cast<int>(data.length()), nullptr); |
| + if (!doc) |
| + return nullptr; |
| + |
| + return std::make_shared<PDFDocument>(shared_from_this(), doc, data); |
| + } |
| +}; |
| + |
| +class PDFDocumentView : public mojo::ui::GaneshView, |
| + public mojo::ui::InputListener { |
| + public: |
| + PDFDocumentView( |
| + mojo::ApplicationImpl* app_impl, |
| + const std::shared_ptr<PDFDocument>& pdf_document, |
| + const mojo::ui::ViewProvider::CreateViewCallback& create_view_callback) |
| + : GaneshView(app_impl, "PDFDocumentViewer", create_view_callback), |
| + pdf_document_(pdf_document), |
| + input_handler_(view_service_provider(), this) { |
| + DCHECK(pdf_document_); |
| + } |
| + |
| + ~PDFDocumentView() override {} |
| + |
| + private: |
| + // |View|: |
| + void OnLayout(mojo::ui::ViewLayoutParamsPtr layout_params, |
| + mojo::Array<uint32_t> children_needing_layout, |
| + const OnLayoutCallback& callback) override { |
| + size_.width = layout_params->constraints->max_width; |
| + size_.height = layout_params->constraints->max_height; |
| + |
| + auto info = mojo::ui::ViewLayoutResult::New(); |
| + info->size = size_.Clone(); |
| + callback.Run(info.Pass()); |
| + |
| + UpdateScene(); |
|
abarth
2016/01/10 23:09:31
I'm surprised there's no token that I get from lay
jeffbrown
2016/01/26 08:14:17
I'll be redoing layout once this gets checked in.
|
| + } |
| + |
| + // |InputListener|: |
| + void OnEvent(mojo::EventPtr event, const OnEventCallback& callback) override { |
| + if (event->key_data && (event->action != mojo::EventType::KEY_PRESSED || |
| + event->key_data->is_char)) { |
| + callback.Run(false); |
| + return; |
| + } |
| + |
| + if ((event->key_data && |
| + event->key_data->windows_key_code == mojo::KeyboardCode::DOWN) || |
| + (event->pointer_data && event->pointer_data->vertical_wheel < 0)) { |
| + GotoNextPage(); |
| + callback.Run(true); |
| + return; |
| + } |
| + |
| + if ((event->key_data && |
| + event->key_data->windows_key_code == mojo::KeyboardCode::UP) || |
| + (event->pointer_data && event->pointer_data->vertical_wheel > 0)) { |
| + GotoPreviousPage(); |
| + callback.Run(true); |
| + return; |
| + } |
| + |
| + callback.Run(false); |
| + } |
| + |
| + void UpdateScene() { |
| + mojo::Rect bounds; |
| + bounds.width = size_.width; |
| + bounds.height = size_.height; |
| + |
| + auto update = mojo::gfx::composition::SceneUpdate::New(); |
| + mojo::gfx::composition::ResourcePtr content_resource = |
| + ganesh_renderer()->DrawCanvas( |
| + size_, |
| + base::Bind(&PDFDocumentView::DrawContent, base::Unretained(this))); |
| + DCHECK(content_resource); |
| + update->resources.insert(kContentImageResourceId, content_resource.Pass()); |
| + |
| + auto root_node = mojo::gfx::composition::Node::New(); |
| + root_node->op = mojo::gfx::composition::NodeOp::New(); |
| + root_node->op->set_image(mojo::gfx::composition::ImageNodeOp::New()); |
| + root_node->op->get_image()->content_rect = bounds.Clone(); |
| + root_node->op->get_image()->image_resource_id = kContentImageResourceId; |
| + update->nodes.insert(kRootNodeId, root_node.Pass()); |
| + |
| + scene()->Update(update.Pass()); |
| + scene()->Publish(nullptr); |
| + } |
| + |
| + void DrawContent(SkCanvas* canvas) { |
| + if (!cached_image_) { |
| + cached_image_ = pdf_document_->DrawPage(page_); |
| + } |
| + |
| + if (cached_image_) { |
| + canvas->clear(SK_ColorBLACK); |
| + |
| + int32_t w, h; |
| + if (size_.width * cached_image_->height() < |
| + size_.height * cached_image_->width()) { |
| + w = size_.width; |
| + h = cached_image_->height() * size_.width / cached_image_->width(); |
| + } else { |
| + w = cached_image_->width() * size_.height / cached_image_->height(); |
| + h = size_.height; |
| + } |
| + canvas->drawImageRect( |
| + cached_image_.get(), |
| + SkRect::MakeWH(cached_image_->width(), cached_image_->height()), |
| + SkRect::MakeXYWH((size_.width - w) / 2, (size_.height - h) / 2, w, h), |
| + nullptr); |
| + } else { |
| + canvas->clear(SK_ColorBLUE); |
| + } |
| + |
| + canvas->flush(); |
| + } |
| + |
| + void GotoNextPage() { |
| + if (page_ + 1 < pdf_document_->page_count()) { |
| + page_++; |
| + Redraw(); |
| + } |
| + } |
| + |
| + void GotoPreviousPage() { |
| + if (page_ > 0) { |
| + page_--; |
| + Redraw(); |
| + } |
| + } |
| + |
| + void Redraw() { |
| + cached_image_.clear(); |
| + UpdateScene(); |
|
abarth
2016/01/10 23:09:31
I was expecting some sort of "invalidate and wait
jeffbrown
2016/01/26 08:14:17
Good point. Let's add that.
|
| + } |
| + |
| + std::shared_ptr<PDFDocument> pdf_document_; |
| + uint32_t page_ = 0u; |
| + skia::RefPtr<SkImage> cached_image_; |
| + |
| + mojo::Size size_; |
| + mojo::ui::InputHandler input_handler_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(PDFDocumentView); |
| +}; |
| + |
| +class PDFContentViewProviderApp : public mojo::ui::ViewProviderApp { |
| + public: |
| + PDFContentViewProviderApp(const std::shared_ptr<PDFLibrary>& pdf_library, |
| + const std::shared_ptr<PDFDocument>& pdf_document) |
| + : pdf_library_(pdf_library), pdf_document_(pdf_document) { |
| + DCHECK(pdf_library_); |
| + DCHECK(pdf_document_); |
| + } |
| + |
| + ~PDFContentViewProviderApp() override {} |
| + |
| + bool CreateView( |
| + const std::string& connection_url, |
| + mojo::InterfaceRequest<mojo::ServiceProvider> services, |
| + mojo::ServiceProviderPtr exposed_services, |
| + const mojo::ui::ViewProvider::CreateViewCallback& callback) override { |
| + new PDFDocumentView(app_impl(), pdf_document_, callback); |
| + return true; |
| + } |
| + |
| + private: |
| + std::shared_ptr<PDFLibrary> pdf_library_; |
| + std::shared_ptr<PDFDocument> pdf_document_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(PDFContentViewProviderApp); |
| +}; |
| + |
| +class PDFContentViewerApp : public mojo::ui::ContentViewerApp { |
| + public: |
| + PDFContentViewerApp() : pdf_library_(std::make_shared<PDFLibrary>()) {} |
| + |
| + ~PDFContentViewerApp() override {} |
| + |
| + mojo::ui::ViewProviderApp* LoadContent( |
| + const std::string& content_handler_url, |
| + mojo::URLResponsePtr response) override { |
| + std::shared_ptr<PDFDocument> pdf_document = |
| + pdf_library_->Decode(response.Pass()); |
| + if (!pdf_document) { |
| + LOG(ERROR) << "Could not decode PDFDocument."; |
| + return nullptr; |
| + } |
| + |
| + return new PDFContentViewProviderApp(pdf_library_, pdf_document); |
| + } |
| + |
| + private: |
| + std::shared_ptr<PDFLibrary> pdf_library_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(PDFContentViewerApp); |
| +}; |
| + |
| +} // namespace examples |
| + |
| +MojoResult MojoMain(MojoHandle application_request) { |
| + mojo::ApplicationRunnerChromium runner(new examples::PDFContentViewerApp()); |
| + return runner.Run(application_request); |
| +} |