Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / pdf_viewer / pdf_viewer.cc
blobf0a89cd230e0bcd19111f7f91c3c77598ba79271
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/bind.h"
6 #include "base/callback.h"
7 #include "base/containers/hash_tables.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "components/mus/public/cpp/types.h"
10 #include "components/mus/public/cpp/view.h"
11 #include "components/mus/public/cpp/view_observer.h"
12 #include "components/mus/public/cpp/view_surface.h"
13 #include "components/mus/public/cpp/view_tree_connection.h"
14 #include "components/mus/public/cpp/view_tree_delegate.h"
15 #include "components/mus/public/interfaces/compositor_frame.mojom.h"
16 #include "components/mus/public/interfaces/gpu.mojom.h"
17 #include "components/mus/public/interfaces/surface_id.mojom.h"
18 #include "gpu/GLES2/gl2chromium.h"
19 #include "gpu/GLES2/gl2extchromium.h"
20 #include "mojo/application/public/cpp/application_connection.h"
21 #include "mojo/application/public/cpp/application_delegate.h"
22 #include "mojo/application/public/cpp/application_impl.h"
23 #include "mojo/application/public/cpp/application_runner.h"
24 #include "mojo/application/public/cpp/connect.h"
25 #include "mojo/application/public/cpp/content_handler_factory.h"
26 #include "mojo/application/public/cpp/interface_factory_impl.h"
27 #include "mojo/application/public/cpp/service_provider_impl.h"
28 #include "mojo/application/public/interfaces/content_handler.mojom.h"
29 #include "mojo/application/public/interfaces/shell.mojom.h"
30 #include "mojo/common/data_pipe_utils.h"
31 #include "mojo/converters/geometry/geometry_type_converters.h"
32 #include "mojo/converters/surfaces/surfaces_type_converters.h"
33 #include "mojo/converters/surfaces/surfaces_utils.h"
34 #include "mojo/public/c/gles2/gles2.h"
35 #include "mojo/public/c/system/main.h"
36 #include "mojo/public/cpp/bindings/binding.h"
37 #include "third_party/pdfium/public/fpdf_ext.h"
38 #include "third_party/pdfium/public/fpdfview.h"
39 #include "ui/gfx/geometry/rect.h"
40 #include "ui/mojo/events/input_events.mojom.h"
41 #include "ui/mojo/events/input_key_codes.mojom.h"
42 #include "ui/mojo/geometry/geometry.mojom.h"
43 #include "ui/mojo/geometry/geometry_util.h"
44 #include "v8/include/v8.h"
46 const uint32_t g_background_color = 0xFF888888;
47 const uint32_t g_transparent_color = 0x00000000;
49 namespace pdf_viewer {
50 namespace {
52 void LostContext(void*) {
53 DCHECK(false);
56 void OnGotContentHandlerID(uint32_t content_handler_id) {}
58 // BitmapUploader is useful if you want to draw a bitmap or color in a View.
59 class BitmapUploader : public mojo::SurfaceClient {
60 public:
61 explicit BitmapUploader(mus::View* view)
62 : view_(view),
63 color_(g_transparent_color),
64 width_(0),
65 height_(0),
66 format_(BGRA),
67 next_resource_id_(1u),
68 id_namespace_(0u),
69 local_id_(0u),
70 returner_binding_(this) {}
71 ~BitmapUploader() override {
72 MojoGLES2DestroyContext(gles2_context_);
75 void Init(mojo::Shell* shell) {
76 surface_ = view_->RequestSurface();
77 surface_->BindToThread();
79 mojo::ServiceProviderPtr gpu_service_provider;
80 mojo::URLRequestPtr request2(mojo::URLRequest::New());
81 request2->url = mojo::String::From("mojo:mus");
82 shell->ConnectToApplication(request2.Pass(),
83 mojo::GetProxy(&gpu_service_provider), nullptr,
84 nullptr, base::Bind(&OnGotContentHandlerID));
85 ConnectToService(gpu_service_provider.get(), &gpu_service_);
87 mojo::CommandBufferPtr gles2_client;
88 gpu_service_->CreateOffscreenGLES2Context(GetProxy(&gles2_client));
89 gles2_context_ = MojoGLES2CreateContext(
90 gles2_client.PassInterface().PassHandle().release().value(),
91 nullptr,
92 &LostContext, NULL, mojo::Environment::GetDefaultAsyncWaiter());
93 MojoGLES2MakeCurrent(gles2_context_);
96 // Sets the color which is RGBA.
97 void SetColor(uint32_t color) {
98 if (color_ == color)
99 return;
100 color_ = color;
101 if (surface_)
102 Upload();
105 enum Format {
106 RGBA, // Pixel layout on Android.
107 BGRA, // Pixel layout everywhere else.
110 // Sets a bitmap.
111 void SetBitmap(int width,
112 int height,
113 scoped_ptr<std::vector<unsigned char>> data,
114 Format format) {
115 width_ = width;
116 height_ = height;
117 bitmap_ = data.Pass();
118 format_ = format;
119 if (surface_)
120 Upload();
123 private:
124 void Upload() {
125 gfx::Rect bounds(view_->bounds().To<gfx::Rect>());
126 mojo::PassPtr pass = mojo::CreateDefaultPass(1, bounds);
127 mojo::CompositorFramePtr frame = mojo::CompositorFrame::New();
129 // TODO(rjkroege): Support device scale factor in PDF viewer
130 mojo::CompositorFrameMetadataPtr meta =
131 mojo::CompositorFrameMetadata::New();
132 meta->device_scale_factor = 1.0f;
133 frame->metadata = meta.Pass();
135 frame->resources.resize(0u);
137 pass->quads.resize(0u);
138 pass->shared_quad_states.push_back(
139 mojo::CreateDefaultSQS(bounds.size()));
141 MojoGLES2MakeCurrent(gles2_context_);
142 if (bitmap_.get()) {
143 mojo::Size bitmap_size;
144 bitmap_size.width = width_;
145 bitmap_size.height = height_;
146 GLuint texture_id = BindTextureForSize(bitmap_size);
147 glTexSubImage2D(GL_TEXTURE_2D,
151 bitmap_size.width,
152 bitmap_size.height,
153 TextureFormat(),
154 GL_UNSIGNED_BYTE,
155 &((*bitmap_)[0]));
157 GLbyte mailbox[GL_MAILBOX_SIZE_CHROMIUM];
158 glGenMailboxCHROMIUM(mailbox);
159 glProduceTextureCHROMIUM(GL_TEXTURE_2D, mailbox);
160 GLuint sync_point = glInsertSyncPointCHROMIUM();
162 mojo::TransferableResourcePtr resource =
163 mojo::TransferableResource::New();
164 resource->id = next_resource_id_++;
165 resource_to_texture_id_map_[resource->id] = texture_id;
166 resource->format = mojo::RESOURCE_FORMAT_RGBA_8888;
167 resource->filter = GL_LINEAR;
168 resource->size = bitmap_size.Clone();
169 mojo::MailboxHolderPtr mailbox_holder = mojo::MailboxHolder::New();
170 mailbox_holder->mailbox = mojo::Mailbox::New();
171 for (int i = 0; i < GL_MAILBOX_SIZE_CHROMIUM; ++i)
172 mailbox_holder->mailbox->name.push_back(mailbox[i]);
173 mailbox_holder->texture_target = GL_TEXTURE_2D;
174 mailbox_holder->sync_point = sync_point;
175 resource->mailbox_holder = mailbox_holder.Pass();
176 resource->is_repeated = false;
177 resource->is_software = false;
179 mojo::QuadPtr quad = mojo::Quad::New();
180 quad->material = mojo::MATERIAL_TEXTURE_CONTENT;
182 mojo::RectPtr rect = mojo::Rect::New();
183 if (width_ <= bounds.width() && height_ <= bounds.height()) {
184 rect->width = width_;
185 rect->height = height_;
186 } else {
187 // The source bitmap is larger than the viewport. Resize it while
188 // maintaining the aspect ratio.
189 float width_ratio = static_cast<float>(width_) / bounds.width();
190 float height_ratio = static_cast<float>(height_) / bounds.height();
191 if (width_ratio > height_ratio) {
192 rect->width = bounds.width();
193 rect->height = height_ / width_ratio;
194 } else {
195 rect->height = bounds.height();
196 rect->width = width_ / height_ratio;
199 quad->rect = rect.Clone();
200 quad->opaque_rect = rect.Clone();
201 quad->visible_rect = rect.Clone();
202 quad->needs_blending = true;
203 quad->shared_quad_state_index = 0u;
205 mojo::TextureQuadStatePtr texture_state = mojo::TextureQuadState::New();
206 texture_state->resource_id = resource->id;
207 texture_state->premultiplied_alpha = true;
208 texture_state->uv_top_left = mojo::PointF::New();
209 texture_state->uv_bottom_right = mojo::PointF::New();
210 texture_state->uv_bottom_right->x = 1.f;
211 texture_state->uv_bottom_right->y = 1.f;
212 texture_state->background_color = mojo::Color::New();
213 texture_state->background_color->rgba = g_transparent_color;
214 for (int i = 0; i < 4; ++i)
215 texture_state->vertex_opacity.push_back(1.f);
216 texture_state->y_flipped = false;
218 frame->resources.push_back(resource.Pass());
219 quad->texture_quad_state = texture_state.Pass();
220 pass->quads.push_back(quad.Pass());
223 if (color_ != g_transparent_color) {
224 mojo::QuadPtr quad = mojo::Quad::New();
225 quad->material = mojo::MATERIAL_SOLID_COLOR;
226 quad->rect = mojo::Rect::From(bounds);
227 quad->opaque_rect = mojo::Rect::New();
228 quad->visible_rect = mojo::Rect::From(bounds);
229 quad->needs_blending = true;
230 quad->shared_quad_state_index = 0u;
232 mojo::SolidColorQuadStatePtr color_state =
233 mojo::SolidColorQuadState::New();
234 color_state->color = mojo::Color::New();
235 color_state->color->rgba = color_;
236 color_state->force_anti_aliasing_off = false;
238 quad->solid_color_quad_state = color_state.Pass();
239 pass->quads.push_back(quad.Pass());
242 frame->passes.push_back(pass.Pass());
244 // TODO(rjkroege, fsamuel): We should throttle frames.
245 surface_->SubmitCompositorFrame(frame.Pass());
248 uint32_t BindTextureForSize(const mojo::Size size) {
249 // TODO(jamesr): Recycle textures.
250 GLuint texture = 0u;
251 glGenTextures(1, &texture);
252 glBindTexture(GL_TEXTURE_2D, texture);
253 glTexImage2D(GL_TEXTURE_2D,
255 TextureFormat(),
256 size.width,
257 size.height,
259 TextureFormat(),
260 GL_UNSIGNED_BYTE,
262 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
263 return texture;
266 uint32_t TextureFormat() {
267 return format_ == BGRA ? GL_BGRA_EXT : GL_RGBA;
270 void SetIdNamespace(uint32_t id_namespace) {
271 id_namespace_ = id_namespace;
272 if (color_ != g_transparent_color || bitmap_.get())
273 Upload();
276 // SurfaceClient implementation.
277 void ReturnResources(
278 mojo::Array<mojo::ReturnedResourcePtr> resources) override {
279 MojoGLES2MakeCurrent(gles2_context_);
280 // TODO(jamesr): Recycle.
281 for (size_t i = 0; i < resources.size(); ++i) {
282 mojo::ReturnedResourcePtr resource = resources[i].Pass();
283 DCHECK_EQ(1, resource->count);
284 glWaitSyncPointCHROMIUM(resource->sync_point);
285 uint32_t texture_id = resource_to_texture_id_map_[resource->id];
286 DCHECK_NE(0u, texture_id);
287 resource_to_texture_id_map_.erase(resource->id);
288 glDeleteTextures(1, &texture_id);
292 mus::View* view_;
293 mojo::GpuPtr gpu_service_;
294 scoped_ptr<mus::ViewSurface> surface_;
295 MojoGLES2Context gles2_context_;
297 mojo::Size size_;
298 uint32_t color_;
299 int width_;
300 int height_;
301 Format format_;
302 scoped_ptr<std::vector<unsigned char>> bitmap_;
303 uint32_t next_resource_id_;
304 uint32_t id_namespace_;
305 uint32_t local_id_;
306 base::hash_map<uint32_t, uint32_t> resource_to_texture_id_map_;
307 mojo::Binding<mojo::SurfaceClient> returner_binding_;
309 DISALLOW_COPY_AND_ASSIGN(BitmapUploader);
312 class EmbedderData {
313 public:
314 EmbedderData(mojo::Shell* shell, mus::View* root) : bitmap_uploader_(root) {
315 bitmap_uploader_.Init(shell);
316 bitmap_uploader_.SetColor(g_background_color);
319 BitmapUploader& bitmap_uploader() { return bitmap_uploader_; }
321 private:
322 BitmapUploader bitmap_uploader_;
324 DISALLOW_COPY_AND_ASSIGN(EmbedderData);
327 class PDFView : public mojo::ApplicationDelegate,
328 public mus::ViewTreeDelegate,
329 public mus::ViewObserver,
330 public mojo::InterfaceFactory<mojo::ViewTreeClient> {
331 public:
332 PDFView(mojo::InterfaceRequest<mojo::Application> request,
333 mojo::URLResponsePtr response)
334 : app_(this, request.Pass(), base::Bind(&PDFView::OnTerminate,
335 base::Unretained(this))),
336 current_page_(0), page_count_(0), doc_(nullptr) {
337 FetchPDF(response.Pass());
340 ~PDFView() override {
341 if (doc_)
342 FPDF_CloseDocument(doc_);
343 for (auto& roots : embedder_for_roots_) {
344 roots.first->RemoveObserver(this);
345 delete roots.second;
349 private:
350 // Overridden from ApplicationDelegate:
351 bool ConfigureIncomingConnection(
352 mojo::ApplicationConnection* connection) override {
353 connection->AddService<mojo::ViewTreeClient>(this);
354 return true;
357 // Overridden from ViewTreeDelegate:
358 void OnEmbed(mus::View* root) override {
359 DCHECK(embedder_for_roots_.find(root) == embedder_for_roots_.end());
360 root->AddObserver(this);
361 EmbedderData* embedder_data = new EmbedderData(app_.shell(), root);
362 embedder_for_roots_[root] = embedder_data;
363 DrawBitmap(embedder_data);
366 void OnConnectionLost(mus::ViewTreeConnection* connection) override {}
368 // Overridden from ViewObserver:
369 void OnViewBoundsChanged(mus::View* view,
370 const mojo::Rect& old_bounds,
371 const mojo::Rect& new_bounds) override {
372 DCHECK(embedder_for_roots_.find(view) != embedder_for_roots_.end());
373 DrawBitmap(embedder_for_roots_[view]);
376 void OnViewInputEvent(mus::View* view, const mojo::EventPtr& event) override {
377 DCHECK(embedder_for_roots_.find(view) != embedder_for_roots_.end());
378 if (event->key_data &&
379 (event->action != mojo::EVENT_TYPE_KEY_PRESSED ||
380 event->key_data->is_char)) {
381 return;
384 // TODO(rjkroege): Make panning and scrolling more performant and
385 // responsive to gesture events.
386 if ((event->key_data &&
387 event->key_data->windows_key_code == mojo::KEYBOARD_CODE_DOWN) ||
388 (event->wheel_data && event->wheel_data &&
389 event->wheel_data->delta_y < 0)) {
390 if (current_page_ < (page_count_ - 1)) {
391 current_page_++;
392 DrawBitmap(embedder_for_roots_[view]);
394 } else if ((event->key_data &&
395 event->key_data->windows_key_code == mojo::KEYBOARD_CODE_UP) ||
396 (event->pointer_data && event->wheel_data &&
397 event->wheel_data->delta_y > 0)) {
398 if (current_page_ > 0) {
399 current_page_--;
400 DrawBitmap(embedder_for_roots_[view]);
405 void OnViewDestroyed(mus::View* view) override {
406 DCHECK(embedder_for_roots_.find(view) != embedder_for_roots_.end());
407 const auto& it = embedder_for_roots_.find(view);
408 DCHECK(it != embedder_for_roots_.end());
409 delete it->second;
410 embedder_for_roots_.erase(it);
411 if (embedder_for_roots_.size() == 0)
412 app_.Quit();
415 // Overridden from mojo::InterfaceFactory<mojo::ViewTreeClient>:
416 void Create(
417 mojo::ApplicationConnection* connection,
418 mojo::InterfaceRequest<mojo::ViewTreeClient> request) override {
419 mus::ViewTreeConnection::Create(this, request.Pass());
422 void DrawBitmap(EmbedderData* embedder_data) {
423 if (!doc_)
424 return;
426 FPDF_PAGE page = FPDF_LoadPage(doc_, current_page_);
427 int width = static_cast<int>(FPDF_GetPageWidth(page));
428 int height = static_cast<int>(FPDF_GetPageHeight(page));
430 scoped_ptr<std::vector<unsigned char>> bitmap;
431 bitmap.reset(new std::vector<unsigned char>);
432 bitmap->resize(width * height * 4);
434 FPDF_BITMAP f_bitmap = FPDFBitmap_CreateEx(width, height, FPDFBitmap_BGRA,
435 &(*bitmap)[0], width * 4);
436 FPDFBitmap_FillRect(f_bitmap, 0, 0, width, height, 0xFFFFFFFF);
437 FPDF_RenderPageBitmap(f_bitmap, page, 0, 0, width, height, 0, 0);
438 FPDFBitmap_Destroy(f_bitmap);
440 FPDF_ClosePage(page);
442 embedder_data->bitmap_uploader().SetBitmap(width, height, bitmap.Pass(),
443 BitmapUploader::BGRA);
446 void FetchPDF(mojo::URLResponsePtr response) {
447 data_.clear();
448 mojo::common::BlockingCopyToString(response->body.Pass(), &data_);
449 if (data_.length() >= static_cast<size_t>(std::numeric_limits<int>::max()))
450 return;
451 doc_ = FPDF_LoadMemDocument(data_.data(), static_cast<int>(data_.length()),
452 nullptr);
453 page_count_ = FPDF_GetPageCount(doc_);
456 // Callback from the quit closure. We key off this rather than
457 // ApplicationDelegate::Quit() as we don't want to shut down the messageloop
458 // when we quit (the messageloop is shared among multiple PDFViews).
459 void OnTerminate() {
460 delete this;
463 mojo::ApplicationImpl app_;
464 std::string data_;
465 int current_page_;
466 int page_count_;
467 FPDF_DOCUMENT doc_;
468 std::map<mus::View*, EmbedderData*> embedder_for_roots_;
470 DISALLOW_COPY_AND_ASSIGN(PDFView);
473 class ContentHandlerImpl : public mojo::ContentHandler {
474 public:
475 ContentHandlerImpl(mojo::InterfaceRequest<ContentHandler> request)
476 : binding_(this, request.Pass()) {}
477 ~ContentHandlerImpl() override {}
479 private:
480 // Overridden from ContentHandler:
481 void StartApplication(mojo::InterfaceRequest<mojo::Application> request,
482 mojo::URLResponsePtr response) override {
483 new PDFView(request.Pass(), response.Pass());
486 mojo::StrongBinding<mojo::ContentHandler> binding_;
488 DISALLOW_COPY_AND_ASSIGN(ContentHandlerImpl);
491 class PDFViewer : public mojo::ApplicationDelegate,
492 public mojo::InterfaceFactory<mojo::ContentHandler> {
493 public:
494 PDFViewer() {
495 v8::V8::InitializeICU();
496 FPDF_InitLibrary();
499 ~PDFViewer() override {
500 FPDF_DestroyLibrary();
503 private:
504 // Overridden from ApplicationDelegate:
505 bool ConfigureIncomingConnection(
506 mojo::ApplicationConnection* connection) override {
507 connection->AddService(this);
508 return true;
511 // Overridden from InterfaceFactory<ContentHandler>:
512 void Create(mojo::ApplicationConnection* connection,
513 mojo::InterfaceRequest<mojo::ContentHandler> request) override {
514 new ContentHandlerImpl(request.Pass());
517 DISALLOW_COPY_AND_ASSIGN(PDFViewer);
520 } // namespace
521 } // namespace pdf_viewer
523 MojoResult MojoMain(MojoHandle application_request) {
524 mojo::ApplicationRunner runner(new pdf_viewer::PDFViewer());
525 return runner.Run(application_request);