blob: 3043c4bed1f39c399a5857acbceeba1a61cb551e [file] [log] [blame]
// 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);
}