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.
6 #include "base/callback.h"
7 #include "base/containers/hash_tables.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "components/view_manager/public/cpp/types.h"
10 #include "components/view_manager/public/cpp/view.h"
11 #include "components/view_manager/public/cpp/view_observer.h"
12 #include "components/view_manager/public/cpp/view_tree_connection.h"
13 #include "components/view_manager/public/cpp/view_tree_delegate.h"
14 #include "components/view_manager/public/interfaces/gpu.mojom.h"
15 #include "components/view_manager/public/interfaces/surface_id.mojom.h"
16 #include "components/view_manager/public/interfaces/surfaces.mojom.h"
17 #include "gpu/GLES2/gl2chromium.h"
18 #include "gpu/GLES2/gl2extchromium.h"
19 #include "mojo/application/public/cpp/application_connection.h"
20 #include "mojo/application/public/cpp/application_delegate.h"
21 #include "mojo/application/public/cpp/application_impl.h"
22 #include "mojo/application/public/cpp/application_runner.h"
23 #include "mojo/application/public/cpp/connect.h"
24 #include "mojo/application/public/cpp/content_handler_factory.h"
25 #include "mojo/application/public/cpp/interface_factory_impl.h"
26 #include "mojo/application/public/cpp/service_provider_impl.h"
27 #include "mojo/application/public/interfaces/content_handler.mojom.h"
28 #include "mojo/application/public/interfaces/shell.mojom.h"
29 #include "mojo/common/data_pipe_utils.h"
30 #include "mojo/converters/geometry/geometry_type_converters.h"
31 #include "mojo/converters/surfaces/surfaces_utils.h"
32 #include "mojo/public/c/gles2/gles2.h"
33 #include "mojo/public/c/system/main.h"
34 #include "mojo/public/cpp/bindings/binding.h"
35 #include "third_party/pdfium/public/fpdf_ext.h"
36 #include "third_party/pdfium/public/fpdfview.h"
37 #include "ui/gfx/geometry/rect.h"
38 #include "ui/mojo/events/input_events.mojom.h"
39 #include "ui/mojo/events/input_key_codes.mojom.h"
40 #include "ui/mojo/geometry/geometry.mojom.h"
41 #include "ui/mojo/geometry/geometry_util.h"
42 #include "v8/include/v8.h"
44 const uint32_t g_background_color
= 0xFF888888;
45 const uint32_t g_transparent_color
= 0x00000000;
47 namespace pdf_viewer
{
50 void LostContext(void*) {
54 // BitmapUploader is useful if you want to draw a bitmap or color in a View.
55 class BitmapUploader
: public mojo::ResourceReturner
{
57 explicit BitmapUploader(mojo::View
* view
)
59 color_(g_transparent_color
),
63 next_resource_id_(1u),
66 returner_binding_(this) {
68 ~BitmapUploader() override
{
69 MojoGLES2DestroyContext(gles2_context_
);
72 void Init(mojo::Shell
* shell
) {
73 mojo::ServiceProviderPtr surfaces_service_provider
;
74 mojo::URLRequestPtr
request(mojo::URLRequest::New());
75 request
->url
= mojo::String::From("mojo:view_manager");
76 shell
->ConnectToApplication(request
.Pass(),
77 mojo::GetProxy(&surfaces_service_provider
),
79 ConnectToService(surfaces_service_provider
.get(), &surface_
);
80 surface_
->GetIdNamespace(
81 base::Bind(&BitmapUploader::SetIdNamespace
, base::Unretained(this)));
82 mojo::ResourceReturnerPtr returner_ptr
;
83 returner_binding_
.Bind(GetProxy(&returner_ptr
));
84 surface_
->SetResourceReturner(returner_ptr
.Pass());
86 mojo::ServiceProviderPtr gpu_service_provider
;
87 mojo::URLRequestPtr
request2(mojo::URLRequest::New());
88 request2
->url
= mojo::String::From("mojo:view_manager");
89 shell
->ConnectToApplication(request2
.Pass(),
90 mojo::GetProxy(&gpu_service_provider
), nullptr,
92 ConnectToService(gpu_service_provider
.get(), &gpu_service_
);
94 mojo::CommandBufferPtr gles2_client
;
95 gpu_service_
->CreateOffscreenGLES2Context(GetProxy(&gles2_client
));
96 gles2_context_
= MojoGLES2CreateContext(
97 gles2_client
.PassInterface().PassHandle().release().value(),
98 &LostContext
, NULL
, mojo::Environment::GetDefaultAsyncWaiter());
99 MojoGLES2MakeCurrent(gles2_context_
);
102 // Sets the color which is RGBA.
103 void SetColor(uint32_t color
) {
112 RGBA
, // Pixel layout on Android.
113 BGRA
, // Pixel layout everywhere else.
117 void SetBitmap(int width
,
119 scoped_ptr
<std::vector
<unsigned char>> data
,
123 bitmap_
= data
.Pass();
132 size
.width
= view_
->bounds().width
;
133 size
.height
= view_
->bounds().height
;
134 if (!size
.width
|| !size
.height
) {
135 view_
->SetSurfaceId(mojo::SurfaceId::New());
139 if (id_namespace_
== 0u) // Can't generate a qualified ID yet.
142 if (size
!= surface_size_
) {
143 if (local_id_
!= 0u) {
144 surface_
->DestroySurface(local_id_
);
147 surface_
->CreateSurface(local_id_
);
148 surface_size_
= size
;
149 auto qualified_id
= mojo::SurfaceId::New();
150 qualified_id
->id_namespace
= id_namespace_
;
151 qualified_id
->local
= local_id_
;
152 view_
->SetSurfaceId(qualified_id
.Pass());
155 gfx::Rect
bounds(size
.width
, size
.height
);
156 mojo::PassPtr pass
= mojo::CreateDefaultPass(1, bounds
);
157 mojo::CompositorFramePtr frame
= mojo::CompositorFrame::New();
158 frame
->resources
.resize(0u);
160 pass
->quads
.resize(0u);
161 pass
->shared_quad_states
.push_back(
162 mojo::CreateDefaultSQS(size
.To
<gfx::Size
>()));
164 MojoGLES2MakeCurrent(gles2_context_
);
166 mojo::Size bitmap_size
;
167 bitmap_size
.width
= width_
;
168 bitmap_size
.height
= height_
;
169 GLuint texture_id
= BindTextureForSize(bitmap_size
);
170 glTexSubImage2D(GL_TEXTURE_2D
,
180 GLbyte mailbox
[GL_MAILBOX_SIZE_CHROMIUM
];
181 glGenMailboxCHROMIUM(mailbox
);
182 glProduceTextureCHROMIUM(GL_TEXTURE_2D
, mailbox
);
183 GLuint sync_point
= glInsertSyncPointCHROMIUM();
185 mojo::TransferableResourcePtr resource
=
186 mojo::TransferableResource::New();
187 resource
->id
= next_resource_id_
++;
188 resource_to_texture_id_map_
[resource
->id
] = texture_id
;
189 resource
->format
= mojo::RESOURCE_FORMAT_RGBA_8888
;
190 resource
->filter
= GL_LINEAR
;
191 resource
->size
= bitmap_size
.Clone();
192 mojo::MailboxHolderPtr mailbox_holder
= mojo::MailboxHolder::New();
193 mailbox_holder
->mailbox
= mojo::Mailbox::New();
194 for (int i
= 0; i
< GL_MAILBOX_SIZE_CHROMIUM
; ++i
)
195 mailbox_holder
->mailbox
->name
.push_back(mailbox
[i
]);
196 mailbox_holder
->texture_target
= GL_TEXTURE_2D
;
197 mailbox_holder
->sync_point
= sync_point
;
198 resource
->mailbox_holder
= mailbox_holder
.Pass();
199 resource
->is_repeated
= false;
200 resource
->is_software
= false;
202 mojo::QuadPtr quad
= mojo::Quad::New();
203 quad
->material
= mojo::MATERIAL_TEXTURE_CONTENT
;
205 mojo::RectPtr rect
= mojo::Rect::New();
206 if (width_
<= size
.width
&& height_
<= size
.height
) {
207 rect
->width
= width_
;
208 rect
->height
= height_
;
210 // The source bitmap is larger than the viewport. Resize it while
211 // maintaining the aspect ratio.
212 float width_ratio
= static_cast<float>(width_
) / size
.width
;
213 float height_ratio
= static_cast<float>(height_
) / size
.height
;
214 if (width_ratio
> height_ratio
) {
215 rect
->width
= size
.width
;
216 rect
->height
= height_
/ width_ratio
;
218 rect
->height
= size
.height
;
219 rect
->width
= width_
/ height_ratio
;
222 quad
->rect
= rect
.Clone();
223 quad
->opaque_rect
= rect
.Clone();
224 quad
->visible_rect
= rect
.Clone();
225 quad
->needs_blending
= true;
226 quad
->shared_quad_state_index
= 0u;
228 mojo::TextureQuadStatePtr texture_state
= mojo::TextureQuadState::New();
229 texture_state
->resource_id
= resource
->id
;
230 texture_state
->premultiplied_alpha
= true;
231 texture_state
->uv_top_left
= mojo::PointF::New();
232 texture_state
->uv_bottom_right
= mojo::PointF::New();
233 texture_state
->uv_bottom_right
->x
= 1.f
;
234 texture_state
->uv_bottom_right
->y
= 1.f
;
235 texture_state
->background_color
= mojo::Color::New();
236 texture_state
->background_color
->rgba
= g_transparent_color
;
237 for (int i
= 0; i
< 4; ++i
)
238 texture_state
->vertex_opacity
.push_back(1.f
);
239 texture_state
->y_flipped
= false;
241 frame
->resources
.push_back(resource
.Pass());
242 quad
->texture_quad_state
= texture_state
.Pass();
243 pass
->quads
.push_back(quad
.Pass());
246 if (color_
!= g_transparent_color
) {
247 mojo::QuadPtr quad
= mojo::Quad::New();
248 quad
->material
= mojo::MATERIAL_SOLID_COLOR
;
249 quad
->rect
= mojo::Rect::From(bounds
);
250 quad
->opaque_rect
= mojo::Rect::New();
251 quad
->visible_rect
= mojo::Rect::From(bounds
);
252 quad
->needs_blending
= true;
253 quad
->shared_quad_state_index
= 0u;
255 mojo::SolidColorQuadStatePtr color_state
=
256 mojo::SolidColorQuadState::New();
257 color_state
->color
= mojo::Color::New();
258 color_state
->color
->rgba
= color_
;
259 color_state
->force_anti_aliasing_off
= false;
261 quad
->solid_color_quad_state
= color_state
.Pass();
262 pass
->quads
.push_back(quad
.Pass());
265 frame
->passes
.push_back(pass
.Pass());
267 surface_
->SubmitCompositorFrame(local_id_
, frame
.Pass(), mojo::Closure());
270 uint32_t BindTextureForSize(const mojo::Size size
) {
271 // TODO(jamesr): Recycle textures.
273 glGenTextures(1, &texture
);
274 glBindTexture(GL_TEXTURE_2D
, texture
);
275 glTexImage2D(GL_TEXTURE_2D
,
284 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, GL_LINEAR
);
288 uint32_t TextureFormat() {
289 return format_
== BGRA
? GL_BGRA_EXT
: GL_RGBA
;
292 void SetIdNamespace(uint32_t id_namespace
) {
293 id_namespace_
= id_namespace
;
294 if (color_
!= g_transparent_color
|| bitmap_
.get())
298 // ResourceReturner implementation.
299 void ReturnResources(
300 mojo::Array
<mojo::ReturnedResourcePtr
> resources
) override
{
301 MojoGLES2MakeCurrent(gles2_context_
);
302 // TODO(jamesr): Recycle.
303 for (size_t i
= 0; i
< resources
.size(); ++i
) {
304 mojo::ReturnedResourcePtr resource
= resources
[i
].Pass();
305 DCHECK_EQ(1, resource
->count
);
306 glWaitSyncPointCHROMIUM(resource
->sync_point
);
307 uint32_t texture_id
= resource_to_texture_id_map_
[resource
->id
];
308 DCHECK_NE(0u, texture_id
);
309 resource_to_texture_id_map_
.erase(resource
->id
);
310 glDeleteTextures(1, &texture_id
);
315 mojo::GpuPtr gpu_service_
;
316 MojoGLES2Context gles2_context_
;
323 scoped_ptr
<std::vector
<unsigned char>> bitmap_
;
324 mojo::SurfacePtr surface_
;
325 mojo::Size surface_size_
;
326 uint32_t next_resource_id_
;
327 uint32_t id_namespace_
;
329 base::hash_map
<uint32_t, uint32_t> resource_to_texture_id_map_
;
330 mojo::Binding
<mojo::ResourceReturner
> returner_binding_
;
332 DISALLOW_COPY_AND_ASSIGN(BitmapUploader
);
337 EmbedderData(mojo::Shell
* shell
, mojo::View
* root
) : bitmap_uploader_(root
) {
338 bitmap_uploader_
.Init(shell
);
339 bitmap_uploader_
.SetColor(g_background_color
);
342 BitmapUploader
& bitmap_uploader() { return bitmap_uploader_
; }
345 BitmapUploader bitmap_uploader_
;
347 DISALLOW_COPY_AND_ASSIGN(EmbedderData
);
350 class PDFView
: public mojo::ApplicationDelegate
,
351 public mojo::ViewTreeDelegate
,
352 public mojo::ViewObserver
,
353 public mojo::InterfaceFactory
<mojo::ViewTreeClient
> {
355 PDFView(mojo::InterfaceRequest
<mojo::Application
> request
,
356 mojo::URLResponsePtr response
)
357 : app_(this, request
.Pass(), base::Bind(&PDFView::OnTerminate
,
358 base::Unretained(this))),
359 current_page_(0), page_count_(0), doc_(nullptr) {
360 FetchPDF(response
.Pass());
363 ~PDFView() override
{
365 FPDF_CloseDocument(doc_
);
366 for (auto& roots
: embedder_for_roots_
) {
367 roots
.first
->RemoveObserver(this);
373 // Overridden from ApplicationDelegate:
374 bool ConfigureIncomingConnection(
375 mojo::ApplicationConnection
* connection
) override
{
376 connection
->AddService
<mojo::ViewTreeClient
>(this);
380 // Overridden from ViewTreeDelegate:
381 void OnEmbed(mojo::View
* root
) override
{
382 DCHECK(embedder_for_roots_
.find(root
) == embedder_for_roots_
.end());
383 root
->AddObserver(this);
384 EmbedderData
* embedder_data
= new EmbedderData(app_
.shell(), root
);
385 embedder_for_roots_
[root
] = embedder_data
;
386 DrawBitmap(embedder_data
);
389 void OnConnectionLost(mojo::ViewTreeConnection
* connection
) override
{}
391 // Overridden from ViewObserver:
392 void OnViewBoundsChanged(mojo::View
* view
,
393 const mojo::Rect
& old_bounds
,
394 const mojo::Rect
& new_bounds
) override
{
395 DCHECK(embedder_for_roots_
.find(view
) != embedder_for_roots_
.end());
396 DrawBitmap(embedder_for_roots_
[view
]);
399 void OnViewInputEvent(mojo::View
* view
,
400 const mojo::EventPtr
& event
) override
{
401 DCHECK(embedder_for_roots_
.find(view
) != embedder_for_roots_
.end());
402 if (event
->key_data
&&
403 (event
->action
!= mojo::EVENT_TYPE_KEY_PRESSED
||
404 event
->key_data
->is_char
)) {
408 if ((event
->key_data
&&
409 event
->key_data
->windows_key_code
== mojo::KEYBOARD_CODE_DOWN
) ||
410 (event
->pointer_data
&& event
->pointer_data
->vertical_wheel
< 0)) {
411 if (current_page_
< (page_count_
- 1)) {
413 DrawBitmap(embedder_for_roots_
[view
]);
415 } else if ((event
->key_data
&&
416 event
->key_data
->windows_key_code
== mojo::KEYBOARD_CODE_UP
) ||
417 (event
->pointer_data
&&
418 event
->pointer_data
->vertical_wheel
> 0)) {
419 if (current_page_
> 0) {
421 DrawBitmap(embedder_for_roots_
[view
]);
426 void OnViewDestroyed(mojo::View
* view
) override
{
427 DCHECK(embedder_for_roots_
.find(view
) != embedder_for_roots_
.end());
428 const auto& it
= embedder_for_roots_
.find(view
);
429 DCHECK(it
!= embedder_for_roots_
.end());
431 embedder_for_roots_
.erase(it
);
432 if (embedder_for_roots_
.size() == 0)
436 // Overridden from mojo::InterfaceFactory<mojo::ViewTreeClient>:
438 mojo::ApplicationConnection
* connection
,
439 mojo::InterfaceRequest
<mojo::ViewTreeClient
> request
) override
{
440 mojo::ViewTreeConnection::Create(this, request
.Pass());
443 void DrawBitmap(EmbedderData
* embedder_data
) {
447 FPDF_PAGE page
= FPDF_LoadPage(doc_
, current_page_
);
448 int width
= static_cast<int>(FPDF_GetPageWidth(page
));
449 int height
= static_cast<int>(FPDF_GetPageHeight(page
));
451 scoped_ptr
<std::vector
<unsigned char>> bitmap
;
452 bitmap
.reset(new std::vector
<unsigned char>);
453 bitmap
->resize(width
* height
* 4);
455 FPDF_BITMAP f_bitmap
= FPDFBitmap_CreateEx(width
, height
, FPDFBitmap_BGRA
,
456 &(*bitmap
)[0], width
* 4);
457 FPDFBitmap_FillRect(f_bitmap
, 0, 0, width
, height
, 0xFFFFFFFF);
458 FPDF_RenderPageBitmap(f_bitmap
, page
, 0, 0, width
, height
, 0, 0);
459 FPDFBitmap_Destroy(f_bitmap
);
461 FPDF_ClosePage(page
);
463 embedder_data
->bitmap_uploader().SetBitmap(width
, height
, bitmap
.Pass(),
464 BitmapUploader::BGRA
);
467 void FetchPDF(mojo::URLResponsePtr response
) {
469 mojo::common::BlockingCopyToString(response
->body
.Pass(), &data_
);
470 if (data_
.length() >= static_cast<size_t>(std::numeric_limits
<int>::max()))
472 doc_
= FPDF_LoadMemDocument(data_
.data(), static_cast<int>(data_
.length()),
474 page_count_
= FPDF_GetPageCount(doc_
);
477 // Callback from the quit closure. We key off this rather than
478 // ApplicationDelegate::Quit() as we don't want to shut down the messageloop
479 // when we quit (the messageloop is shared among multiple PDFViews).
484 mojo::ApplicationImpl app_
;
489 std::map
<mojo::View
*, EmbedderData
*> embedder_for_roots_
;
491 DISALLOW_COPY_AND_ASSIGN(PDFView
);
494 class ContentHandlerImpl
: public mojo::ContentHandler
{
496 ContentHandlerImpl(mojo::InterfaceRequest
<ContentHandler
> request
)
497 : binding_(this, request
.Pass()) {}
498 ~ContentHandlerImpl() override
{}
501 // Overridden from ContentHandler:
502 void StartApplication(mojo::InterfaceRequest
<mojo::Application
> request
,
503 mojo::URLResponsePtr response
) override
{
504 new PDFView(request
.Pass(), response
.Pass());
507 mojo::StrongBinding
<mojo::ContentHandler
> binding_
;
509 DISALLOW_COPY_AND_ASSIGN(ContentHandlerImpl
);
512 class PDFViewer
: public mojo::ApplicationDelegate
,
513 public mojo::InterfaceFactory
<mojo::ContentHandler
> {
516 v8::V8::InitializeICU();
520 ~PDFViewer() override
{
521 FPDF_DestroyLibrary();
525 // Overridden from ApplicationDelegate:
526 bool ConfigureIncomingConnection(
527 mojo::ApplicationConnection
* connection
) override
{
528 connection
->AddService(this);
532 // Overridden from InterfaceFactory<ContentHandler>:
533 void Create(mojo::ApplicationConnection
* connection
,
534 mojo::InterfaceRequest
<mojo::ContentHandler
> request
) override
{
535 new ContentHandlerImpl(request
.Pass());
538 DISALLOW_COPY_AND_ASSIGN(PDFViewer
);
542 } // namespace pdf_viewer
544 MojoResult
MojoMain(MojoHandle application_request
) {
545 mojo::ApplicationRunner
runner(new pdf_viewer::PDFViewer());
546 return runner
.Run(application_request
);