| // 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/data_pipe_utils/data_pipe_utils.h" |
| #include "mojo/environment/scoped_chromium_init.h" |
| #include "mojo/public/c/system/main.h" |
| #include "mojo/public/cpp/application/connect.h" |
| #include "mojo/public/cpp/application/run_application.h" |
| #include "mojo/ui/choreographer.h" |
| #include "mojo/ui/content_viewer_app.h" |
| #include "mojo/ui/ganesh_view.h" |
| #include "mojo/ui/input_handler.h" |
| #include "mojo/ui/view_provider_app.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 { |
| |
| namespace { |
| constexpr uint32_t kContentImageResourceId = 1; |
| constexpr uint32_t kRootNodeId = mojo::gfx::composition::kSceneRootNodeId; |
| } // namespace |
| |
| 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_; } |
| |
| sk_sp<SkImage> DrawPage(int page_index, const mojo::Size& size) { |
| FPDF_PAGE page = FPDF_LoadPage(doc_, page_index); |
| |
| double width_pts = FPDF_GetPageWidth(page); |
| double height_pts = FPDF_GetPageHeight(page); |
| |
| int width, height; |
| if (size.width * height_pts < size.height * width_pts) { |
| width = size.width; |
| height = height_pts * size.width / width_pts; |
| } else { |
| width = width_pts * size.height / height_pts; |
| height = size.height; |
| } |
| |
| int stride = width * 4; |
| sk_sp<SkData> pixels = SkData::MakeUninitialized(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 SkImage::MakeRasterData(info, pixels, 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::ChoreographerDelegate, |
| public mojo::ui::InputListener { |
| public: |
| PDFDocumentView( |
| mojo::InterfaceHandle<mojo::ApplicationConnector> app_connector, |
| mojo::InterfaceRequest<mojo::ui::ViewOwner> view_owner_request, |
| const std::shared_ptr<PDFDocument>& pdf_document) |
| : GaneshView(app_connector.Pass(), |
| view_owner_request.Pass(), |
| "PDFDocumentViewer"), |
| pdf_document_(pdf_document), |
| choreographer_(scene(), this), |
| input_handler_(GetViewServiceProvider(), this) { |
| DCHECK(pdf_document_); |
| } |
| |
| ~PDFDocumentView() override {} |
| |
| private: |
| // |GaneshView|: |
| void OnPropertiesChanged( |
| uint32_t old_scene_version, |
| mojo::ui::ViewPropertiesPtr old_properties) override { |
| Redraw(); |
| } |
| |
| // |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); |
| } |
| |
| // |ChoreographerDelegate|: |
| void OnDraw(const mojo::gfx::composition::FrameInfo& frame_info, |
| const base::TimeDelta& time_delta) override { |
| if (!properties()) |
| return; |
| |
| auto update = mojo::gfx::composition::SceneUpdate::New(); |
| |
| const mojo::Size& size = *properties()->view_layout->size; |
| if (size.width > 0 && size.height > 0) { |
| mojo::RectF bounds; |
| bounds.width = size.width; |
| bounds.height = size.height; |
| |
| 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->hit_test_behavior = |
| mojo::gfx::composition::HitTestBehavior::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()); |
| } else { |
| auto root_node = mojo::gfx::composition::Node::New(); |
| update->nodes.insert(kRootNodeId, root_node.Pass()); |
| } |
| |
| scene()->Update(update.Pass()); |
| |
| auto metadata = mojo::gfx::composition::SceneMetadata::New(); |
| metadata->version = scene_version(); |
| scene()->Publish(metadata.Pass()); |
| } |
| |
| void DrawContent(const mojo::skia::GaneshContext::Scope& ganesh_scope, |
| const mojo::Size& size, |
| SkCanvas* canvas) { |
| if (!cached_image_) { |
| cached_image_ = pdf_document_->DrawPage(page_, size); |
| } |
| |
| if (cached_image_) { |
| canvas->clear(SK_ColorBLACK); |
| canvas->drawImageRect( |
| cached_image_.get(), |
| SkRect::MakeWH(cached_image_->width(), cached_image_->height()), |
| SkRect::MakeXYWH((size.width - cached_image_->width()) / 2, |
| (size.height - cached_image_->height()) / 2, |
| cached_image_->width(), cached_image_->height()), |
| nullptr); |
| } else { |
| canvas->clear(SK_ColorBLUE); |
| } |
| } |
| |
| void GotoNextPage() { |
| if (page_ + 1 < pdf_document_->page_count()) { |
| page_++; |
| Redraw(); |
| } |
| } |
| |
| void GotoPreviousPage() { |
| if (page_ > 0) { |
| page_--; |
| Redraw(); |
| } |
| } |
| |
| void Redraw() { |
| cached_image_.reset(); |
| choreographer_.ScheduleDraw(); |
| } |
| |
| std::shared_ptr<PDFDocument> pdf_document_; |
| uint32_t page_ = 0u; |
| sk_sp<SkImage> cached_image_; |
| |
| mojo::ui::Choreographer choreographer_; |
| 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 {} |
| |
| void CreateView( |
| const std::string& connection_url, |
| mojo::InterfaceRequest<mojo::ui::ViewOwner> view_owner_request, |
| mojo::InterfaceRequest<mojo::ServiceProvider> services) override { |
| new PDFDocumentView(mojo::CreateApplicationConnector(shell()), |
| view_owner_request.Pass(), pdf_document_); |
| } |
| |
| 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::ScopedChromiumInit init; |
| examples::PDFContentViewerApp pdf_content_viewer_app; |
| return mojo::RunApplication(application_request, &pdf_content_viewer_app); |
| } |