diff options
author | Bo Xu <bo_xu@foxitsoftware.com> | 2014-10-28 23:03:33 -0700 |
---|---|---|
committer | Bo Xu <bo_xu@foxitsoftware.com> | 2014-11-03 11:10:11 -0800 |
commit | fdc00a7042d912aafaabddae4d9c84199921ef23 (patch) | |
tree | 32ab8ac91cc68d2cd15b9168782a71b3f3f5e7b9 /xfa_test/pdf/instance.cc | |
parent | e9b38fa38de2c95d8260be31c57d9272c4d127ed (diff) | |
download | pdfium-fdc00a7042d912aafaabddae4d9c84199921ef23.tar.xz |
Merge XFA to PDFium master at 4dc95e7 on 10/28/2014
Diffstat (limited to 'xfa_test/pdf/instance.cc')
-rw-r--r-- | xfa_test/pdf/instance.cc | 2778 |
1 files changed, 2778 insertions, 0 deletions
diff --git a/xfa_test/pdf/instance.cc b/xfa_test/pdf/instance.cc new file mode 100644 index 0000000000..4bd2f787df --- /dev/null +++ b/xfa_test/pdf/instance.cc @@ -0,0 +1,2778 @@ +// Copyright (c) 2012 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 "pdf/instance.h" + +#include <algorithm> // for min() +#define _USE_MATH_DEFINES // for M_PI +#include <cmath> // for log() and pow() +#include <math.h> +#include <list> + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "chrome/browser/chrome_page_zoom_constants.h" +#include "chrome/common/content_restriction.h" +#include "content/public/common/page_zoom.h" +#include "net/base/escape.h" +#include "pdf/draw_utils.h" +#include "pdf/number_image_generator.h" +#include "pdf/pdf.h" +#include "pdf/resource_consts.h" +#include "ppapi/c/dev/ppb_cursor_control_dev.h" +#include "ppapi/c/pp_errors.h" +#include "ppapi/c/pp_rect.h" +#include "ppapi/c/private/ppp_pdf.h" +#include "ppapi/c/trusted/ppb_url_loader_trusted.h" +#include "ppapi/cpp/core.h" +#include "ppapi/cpp/dev/font_dev.h" +#include "ppapi/cpp/dev/memory_dev.h" +#include "ppapi/cpp/dev/text_input_dev.h" +#include "ppapi/cpp/module.h" +#include "ppapi/cpp/point.h" +#include "ppapi/cpp/private/pdf.h" +#include "ppapi/cpp/rect.h" +#include "ppapi/cpp/resource.h" +#include "ppapi/cpp/url_request_info.h" +#include "ui/events/keycodes/keyboard_codes.h" + +#if defined(OS_MACOSX) +#include "base/mac/mac_util.h" +#endif + +namespace chrome_pdf { + +struct ToolbarButtonInfo { + uint32 id; + Button::ButtonStyle style; + PP_ResourceImage normal; + PP_ResourceImage highlighted; + PP_ResourceImage pressed; +}; + +namespace { + +// Uncomment following #define to enable thumbnails. +// #define ENABLE_THUMBNAILS + +const uint32 kToolbarSplashTimeoutMs = 6000; +const uint32 kMessageTextColor = 0xFF575757; +const uint32 kMessageTextSize = 22; +const uint32 kProgressFadeTimeoutMs = 250; +const uint32 kProgressDelayTimeoutMs = 1000; +const uint32 kAutoScrollTimeoutMs = 50; +const double kAutoScrollFactor = 0.2; + +// Javascript methods. +const char kJSAccessibility[] = "accessibility"; +const char kJSDocumentLoadComplete[] = "documentLoadComplete"; +const char kJSGetHeight[] = "getHeight"; +const char kJSGetHorizontalScrollbarThickness[] = + "getHorizontalScrollbarThickness"; +const char kJSGetPageLocationNormalized[] = "getPageLocationNormalized"; +const char kJSGetSelectedText[] = "getSelectedText"; +const char kJSGetVerticalScrollbarThickness[] = "getVerticalScrollbarThickness"; +const char kJSGetWidth[] = "getWidth"; +const char kJSGetZoomLevel[] = "getZoomLevel"; +const char kJSGoToPage[] = "goToPage"; +const char kJSGrayscale[] = "grayscale"; +const char kJSLoadPreviewPage[] = "loadPreviewPage"; +const char kJSOnLoad[] = "onload"; +const char kJSOnPluginSizeChanged[] = "onPluginSizeChanged"; +const char kJSOnScroll[] = "onScroll"; +const char kJSPageXOffset[] = "pageXOffset"; +const char kJSPageYOffset[] = "pageYOffset"; +const char kJSPrintPreviewPageCount[] = "printPreviewPageCount"; +const char kJSReload[] = "reload"; +const char kJSRemovePrintButton[] = "removePrintButton"; +const char kJSResetPrintPreviewUrl[] = "resetPrintPreviewUrl"; +const char kJSSendKeyEvent[] = "sendKeyEvent"; +const char kJSSetPageNumbers[] = "setPageNumbers"; +const char kJSSetPageXOffset[] = "setPageXOffset"; +const char kJSSetPageYOffset[] = "setPageYOffset"; +const char kJSSetZoomLevel[] = "setZoomLevel"; +const char kJSZoomFitToHeight[] = "fitToHeight"; +const char kJSZoomFitToWidth[] = "fitToWidth"; +const char kJSZoomIn[] = "zoomIn"; +const char kJSZoomOut[] = "zoomOut"; + +// URL reference parameters. +// For more possible parameters, see RFC 3778 and the "PDF Open Parameters" +// document from Adobe. +const char kDelimiters[] = "#&"; +const char kNamedDest[] = "nameddest"; +const char kPage[] = "page"; + +const char kChromePrint[] = "chrome://print/"; + +// Dictionary Value key names for the document accessibility info +const char kAccessibleNumberOfPages[] = "numberOfPages"; +const char kAccessibleLoaded[] = "loaded"; +const char kAccessibleCopyable[] = "copyable"; + +const ToolbarButtonInfo kPDFToolbarButtons[] = { + { kFitToPageButtonId, Button::BUTTON_STATE, + PP_RESOURCEIMAGE_PDF_BUTTON_FTP, + PP_RESOURCEIMAGE_PDF_BUTTON_FTP_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_FTP_PRESSED }, + { kFitToWidthButtonId, Button::BUTTON_STATE, + PP_RESOURCEIMAGE_PDF_BUTTON_FTW, + PP_RESOURCEIMAGE_PDF_BUTTON_FTW_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_FTW_PRESSED }, + { kZoomOutButtonId, Button::BUTTON_CLICKABLE, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_PRESSED }, + { kZoomInButtonId, Button::BUTTON_CLICKABLE, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_PRESSED }, + { kSaveButtonId, Button::BUTTON_CLICKABLE, + PP_RESOURCEIMAGE_PDF_BUTTON_SAVE, + PP_RESOURCEIMAGE_PDF_BUTTON_SAVE_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_SAVE_PRESSED }, + { kPrintButtonId, Button::BUTTON_CLICKABLE, + PP_RESOURCEIMAGE_PDF_BUTTON_PRINT, + PP_RESOURCEIMAGE_PDF_BUTTON_PRINT_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_PRINT_PRESSED }, +}; + +const ToolbarButtonInfo kPDFNoPrintToolbarButtons[] = { + { kFitToPageButtonId, Button::BUTTON_STATE, + PP_RESOURCEIMAGE_PDF_BUTTON_FTP, + PP_RESOURCEIMAGE_PDF_BUTTON_FTP_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_FTP_PRESSED }, + { kFitToWidthButtonId, Button::BUTTON_STATE, + PP_RESOURCEIMAGE_PDF_BUTTON_FTW, + PP_RESOURCEIMAGE_PDF_BUTTON_FTW_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_FTW_PRESSED }, + { kZoomOutButtonId, Button::BUTTON_CLICKABLE, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_PRESSED }, + { kZoomInButtonId, Button::BUTTON_CLICKABLE, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_PRESSED }, + { kSaveButtonId, Button::BUTTON_CLICKABLE, + PP_RESOURCEIMAGE_PDF_BUTTON_SAVE, + PP_RESOURCEIMAGE_PDF_BUTTON_SAVE_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_SAVE_PRESSED }, + { kPrintButtonId, Button::BUTTON_CLICKABLE, + PP_RESOURCEIMAGE_PDF_BUTTON_PRINT_DISABLED, + PP_RESOURCEIMAGE_PDF_BUTTON_PRINT_DISABLED, + PP_RESOURCEIMAGE_PDF_BUTTON_PRINT_DISABLED } +}; + +const ToolbarButtonInfo kPrintPreviewToolbarButtons[] = { + { kFitToPageButtonId, Button::BUTTON_STATE, + PP_RESOURCEIMAGE_PDF_BUTTON_FTP, + PP_RESOURCEIMAGE_PDF_BUTTON_FTP_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_FTP_PRESSED }, + { kFitToWidthButtonId, Button::BUTTON_STATE, + PP_RESOURCEIMAGE_PDF_BUTTON_FTW, + PP_RESOURCEIMAGE_PDF_BUTTON_FTW_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_FTW_PRESSED }, + { kZoomOutButtonId, Button::BUTTON_CLICKABLE, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMOUT_PRESSED }, + { kZoomInButtonId, Button::BUTTON_CLICKABLE, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_END, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_END_HOVER, + PP_RESOURCEIMAGE_PDF_BUTTON_ZOOMIN_END_PRESSED }, +}; + +static const char kPPPPdfInterface[] = PPP_PDF_INTERFACE_1; + +PP_Var GetLinkAtPosition(PP_Instance instance, PP_Point point) { + pp::Var var; + void* object = + pp::Instance::GetPerInstanceObject(instance, kPPPPdfInterface); + if (object) + var = static_cast<Instance*>(object)->GetLinkAtPosition(pp::Point(point)); + return var.Detach(); +} + +void Transform(PP_Instance instance, PP_PrivatePageTransformType type) { + void* object = + pp::Instance::GetPerInstanceObject(instance, kPPPPdfInterface); + if (object) { + Instance* obj_instance = static_cast<Instance*>(object); + switch (type) { + case PP_PRIVATEPAGETRANSFORMTYPE_ROTATE_90_CW: + obj_instance->RotateClockwise(); + break; + case PP_PRIVATEPAGETRANSFORMTYPE_ROTATE_90_CCW: + obj_instance->RotateCounterclockwise(); + break; + } + } +} + +const PPP_Pdf ppp_private = { + &GetLinkAtPosition, + &Transform +}; + +int ExtractPrintPreviewPageIndex(const std::string& src_url) { + // Sample |src_url| format: chrome://print/id/page_index/print.pdf + std::vector<std::string> url_substr; + base::SplitString(src_url.substr(strlen(kChromePrint)), '/', &url_substr); + if (url_substr.size() != 3) + return -1; + + if (url_substr[2] != "print.pdf") + return -1; + + int page_index = 0; + if (!base::StringToInt(url_substr[1], &page_index)) + return -1; + return page_index; +} + +bool IsPrintPreviewUrl(const std::string& url) { + return url.substr(0, strlen(kChromePrint)) == kChromePrint; +} + +void ScalePoint(float scale, pp::Point* point) { + point->set_x(static_cast<int>(point->x() * scale)); + point->set_y(static_cast<int>(point->y() * scale)); +} + +void ScaleRect(float scale, pp::Rect* rect) { + int left = static_cast<int>(floorf(rect->x() * scale)); + int top = static_cast<int>(floorf(rect->y() * scale)); + int right = static_cast<int>(ceilf((rect->x() + rect->width()) * scale)); + int bottom = static_cast<int>(ceilf((rect->y() + rect->height()) * scale)); + rect->SetRect(left, top, right - left, bottom - top); +} + +template<class T> +T ClipToRange(T value, T lower_boundary, T upper_boundary) { + DCHECK(lower_boundary <= upper_boundary); + return std::max<T>(lower_boundary, std::min<T>(value, upper_boundary)); +} + +} // namespace + +Instance::Instance(PP_Instance instance) + : pp::InstancePrivate(instance), + pp::Find_Private(this), + pp::Printing_Dev(this), + pp::Selection_Dev(this), + pp::WidgetClient_Dev(this), + pp::Zoom_Dev(this), + cursor_(PP_CURSORTYPE_POINTER), + timer_pending_(false), + current_timer_id_(0), + zoom_(1.0), + device_scale_(1.0), + printing_enabled_(true), + hidpi_enabled_(false), + full_(IsFullFrame()), + zoom_mode_(full_ ? ZOOM_AUTO : ZOOM_SCALE), + did_call_start_loading_(false), + is_autoscroll_(false), + scrollbar_thickness_(-1), + scrollbar_reserved_thickness_(-1), + current_tb_info_(NULL), + current_tb_info_size_(0), + paint_manager_(this, this, true), + delayed_progress_timer_id_(0), + first_paint_(true), + painted_first_page_(false), + show_page_indicator_(false), + document_load_state_(LOAD_STATE_LOADING), + preview_document_load_state_(LOAD_STATE_COMPLETE), + told_browser_about_unsupported_feature_(false), + print_preview_page_count_(0) { + loader_factory_.Initialize(this); + timer_factory_.Initialize(this); + form_factory_.Initialize(this); + callback_factory_.Initialize(this); + engine_.reset(PDFEngine::Create(this)); + pp::Module::Get()->AddPluginInterface(kPPPPdfInterface, &ppp_private); + AddPerInstanceObject(kPPPPdfInterface, this); + + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_MOUSE); + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_WHEEL); + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_KEYBOARD); + RequestFilteringInputEvents(PP_INPUTEVENT_CLASS_TOUCH); +} + +Instance::~Instance() { + if (timer_pending_) { + timer_factory_.CancelAll(); + timer_pending_ = false; + } + // The engine may try to access this instance during its destruction. + // Make sure this happens early while the instance is still intact. + engine_.reset(); + RemovePerInstanceObject(kPPPPdfInterface, this); +} + +bool Instance::Init(uint32_t argc, const char* argn[], const char* argv[]) { + // For now, we hide HiDPI support behind a flag. + if (pp::PDF::IsFeatureEnabled(this, PP_PDFFEATURE_HIDPI)) + hidpi_enabled_ = true; + + printing_enabled_ = pp::PDF::IsFeatureEnabled(this, PP_PDFFEATURE_PRINTING); + if (printing_enabled_) { + CreateToolbar(kPDFToolbarButtons, arraysize(kPDFToolbarButtons)); + } else { + CreateToolbar(kPDFNoPrintToolbarButtons, + arraysize(kPDFNoPrintToolbarButtons)); + } + + CreateProgressBar(); + + // Load autoscroll anchor image. + autoscroll_anchor_ = + CreateResourceImage(PP_RESOURCEIMAGE_PDF_PAN_SCROLL_ICON); + +#ifdef ENABLE_THUMBNAILS + CreateThumbnails(); +#endif + const char* url = NULL; + for (uint32_t i = 0; i < argc; ++i) { + if (strcmp(argn[i], "src") == 0) { + url = argv[i]; + break; + } + } + + if (!url) + return false; + + CreatePageIndicator(IsPrintPreviewUrl(url)); + + if (!full_) { + // For PDFs embedded in a frame, we don't get the data automatically like we + // do for full-frame loads. Start loading the data manually. + LoadUrl(url); + } else { + DCHECK(!did_call_start_loading_); + pp::PDF::DidStartLoading(this); + did_call_start_loading_ = true; + } + + ZoomLimitsChanged(kMinZoom, kMaxZoom); + + text_input_.reset(new pp::TextInput_Dev(this)); + + url_ = url; + return engine_->New(url); +} + +bool Instance::HandleDocumentLoad(const pp::URLLoader& loader) { + delayed_progress_timer_id_ = ScheduleTimer(kProgressBarId, + kProgressDelayTimeoutMs); + return engine_->HandleDocumentLoad(loader); +} + +bool Instance::HandleInputEvent(const pp::InputEvent& event) { + // To simplify things, convert the event into device coordinates if it is + // a mouse event. + pp::InputEvent event_device_res(event); + { + pp::MouseInputEvent mouse_event(event); + if (!mouse_event.is_null()) { + pp::Point point = mouse_event.GetPosition(); + pp::Point movement = mouse_event.GetMovement(); + ScalePoint(device_scale_, &point); + ScalePoint(device_scale_, &movement); + mouse_event = pp::MouseInputEvent( + this, + event.GetType(), + event.GetTimeStamp(), + event.GetModifiers(), + mouse_event.GetButton(), + point, + mouse_event.GetClickCount(), + movement); + event_device_res = mouse_event; + } + } + + // Check if we need to go to autoscroll mode. + if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE && + (event.GetModifiers() & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN)) { + pp::MouseInputEvent mouse_event(event_device_res); + pp::Point pos = mouse_event.GetPosition(); + EnableAutoscroll(pos); + UpdateCursor(CalculateAutoscroll(pos)); + return true; + } else { + // Quit autoscrolling on any other event. + DisableAutoscroll(); + } + +#ifdef ENABLE_THUMBNAILS + if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSELEAVE) + thumbnails_.SlideOut(); + + if (thumbnails_.HandleEvent(event_device_res)) + return true; +#endif + + if (toolbar_->HandleEvent(event_device_res)) + return true; + +#ifdef ENABLE_THUMBNAILS + if (v_scrollbar_.get() && event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE) { + pp::MouseInputEvent mouse_event(event); + pp::Point pt = mouse_event.GetPosition(); + pp::Rect v_scrollbar_rc; + v_scrollbar_->GetLocation(&v_scrollbar_rc); + // There is a bug (https://bugs.webkit.org/show_bug.cgi?id=45208) + // in the webkit that makes event.u.mouse.button + // equal to PP_INPUTEVENT_MOUSEBUTTON_LEFT, even when no button is pressed. + // To work around this issue we use modifier for now, and will switch + // to button once the bug is fixed and webkit got merged back to our tree. + if (v_scrollbar_rc.Contains(pt) && + (event.GetModifiers() & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN)) { + thumbnails_.SlideIn(); + } + + // When scrollbar is in the scrolling mode we should display thumbnails + // even the mouse is outside the thumbnail and scrollbar areas. + // If mouse is outside plugin area, we are still getting mouse move events + // while scrolling. See bug description for details: + // http://code.google.com/p/chromium/issues/detail?id=56444 + if (!v_scrollbar_rc.Contains(pt) && thumbnails_.visible() && + !(event.GetModifiers() & PP_INPUTEVENT_MODIFIER_LEFTBUTTONDOWN) && + !thumbnails_.rect().Contains(pt)) { + thumbnails_.SlideOut(); + } + } +#endif + + // Need to pass the event to the engine first, since if we're over an edit + // control we want it to get keyboard events (like space) instead of the + // scrollbar. + // TODO: will have to offset the mouse coordinates once we support bidi and + // there could be scrollbars on the left. + pp::InputEvent offset_event(event_device_res); + bool try_engine_first = true; + switch (offset_event.GetType()) { + case PP_INPUTEVENT_TYPE_MOUSEDOWN: + case PP_INPUTEVENT_TYPE_MOUSEUP: + case PP_INPUTEVENT_TYPE_MOUSEMOVE: + case PP_INPUTEVENT_TYPE_MOUSEENTER: + case PP_INPUTEVENT_TYPE_MOUSELEAVE: { + pp::MouseInputEvent mouse_event(event_device_res); + pp::MouseInputEvent mouse_event_dip(event); + pp::Point point = mouse_event.GetPosition(); + point.set_x(point.x() - available_area_.x()); + offset_event = pp::MouseInputEvent( + this, + event.GetType(), + event.GetTimeStamp(), + event.GetModifiers(), + mouse_event.GetButton(), + point, + mouse_event.GetClickCount(), + mouse_event.GetMovement()); + if (!engine_->IsSelecting()) { + if (!IsOverlayScrollbar() && + !available_area_.Contains(mouse_event.GetPosition())) { + try_engine_first = false; + } else if (IsOverlayScrollbar()) { + pp::Rect temp; + if ((v_scrollbar_.get() && v_scrollbar_->GetLocation(&temp) && + temp.Contains(mouse_event_dip.GetPosition())) || + (h_scrollbar_.get() && h_scrollbar_->GetLocation(&temp) && + temp.Contains(mouse_event_dip.GetPosition()))) { + try_engine_first = false; + } + } + } + break; + } + default: + break; + } + if (try_engine_first && engine_->HandleEvent(offset_event)) + return true; + + // Left/Right arrows should scroll to the beginning of the Prev/Next page if + // there is no horizontal scroll bar. + // If fit-to-height, PgDown/PgUp should scroll to the beginning of the + // Prev/Next page. Spacebar / shift+spacebar should do the same. + if (v_scrollbar_.get() && event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN) { + pp::KeyboardInputEvent keyboard_event(event); + bool no_h_scrollbar = !h_scrollbar_.get(); + uint32_t key_code = keyboard_event.GetKeyCode(); + bool page_down = no_h_scrollbar && key_code == ui::VKEY_RIGHT; + bool page_up = no_h_scrollbar && key_code == ui::VKEY_LEFT; + if (zoom_mode_ == ZOOM_FIT_TO_PAGE) { + bool has_shift = + keyboard_event.GetModifiers() & PP_INPUTEVENT_MODIFIER_SHIFTKEY; + bool key_is_space = key_code == ui::VKEY_SPACE; + page_down |= key_is_space || key_code == ui::VKEY_NEXT; + page_up |= (key_is_space && has_shift) || (key_code == ui::VKEY_PRIOR); + } + if (page_down) { + int page = engine_->GetFirstVisiblePage(); + if (page == -1) + return true; + // Engine calculates visible page including delimiter to the page size. + // We need to check here if the page itself is completely out of view and + // scroll to the next one in that case. + if (engine_->GetPageRect(page).bottom() * zoom_ <= + v_scrollbar_->GetValue()) + page++; + ScrollToPage(page + 1); + UpdateCursor(PP_CURSORTYPE_POINTER); + return true; + } else if (page_up) { + int page = engine_->GetFirstVisiblePage(); + if (page == -1) + return true; + if (engine_->GetPageRect(page).y() * zoom_ >= v_scrollbar_->GetValue()) + page--; + ScrollToPage(page); + UpdateCursor(PP_CURSORTYPE_POINTER); + return true; + } + } + + if (v_scrollbar_.get() && v_scrollbar_->HandleEvent(event)) { + UpdateCursor(PP_CURSORTYPE_POINTER); + return true; + } + + if (h_scrollbar_.get() && h_scrollbar_->HandleEvent(event)) { + UpdateCursor(PP_CURSORTYPE_POINTER); + return true; + } + + if (timer_pending_ && + (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP || + event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE)) { + timer_factory_.CancelAll(); + timer_pending_ = false; + } else if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE && + engine_->IsSelecting()) { + bool set_timer = false; + pp::MouseInputEvent mouse_event(event); + if (v_scrollbar_.get() && + (mouse_event.GetPosition().y() <= 0 || + mouse_event.GetPosition().y() >= (plugin_dip_size_.height() - 1))) { + v_scrollbar_->ScrollBy( + PP_SCROLLBY_LINE, mouse_event.GetPosition().y() >= 0 ? 1: -1); + set_timer = true; + } + if (h_scrollbar_.get() && + (mouse_event.GetPosition().x() <= 0 || + mouse_event.GetPosition().x() >= (plugin_dip_size_.width() - 1))) { + h_scrollbar_->ScrollBy(PP_SCROLLBY_LINE, + mouse_event.GetPosition().x() >= 0 ? 1: -1); + set_timer = true; + } + + if (set_timer) { + last_mouse_event_ = pp::MouseInputEvent(event); + + pp::CompletionCallback callback = + timer_factory_.NewCallback(&Instance::OnTimerFired); + pp::Module::Get()->core()->CallOnMainThread(kDragTimerMs, callback); + timer_pending_ = true; + } + } + + if (event.GetType() == PP_INPUTEVENT_TYPE_KEYDOWN) { + pp::KeyboardInputEvent keyboard_event(event); + const uint32 modifier = event.GetModifiers(); + if (modifier & kDefaultKeyModifier) { + switch (keyboard_event.GetKeyCode()) { + case 'A': + engine_->SelectAll(); + return true; + } + } else if (modifier & PP_INPUTEVENT_MODIFIER_CONTROLKEY) { + switch (keyboard_event.GetKeyCode()) { + case ui::VKEY_OEM_4: + // Left bracket. + engine_->RotateCounterclockwise(); + return true; + case ui::VKEY_OEM_6: + // Right bracket. + engine_->RotateClockwise(); + return true; + } + } + } + + // Return true for unhandled clicks so the plugin takes focus. + return (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN); +} + +void Instance::DidChangeView(const pp::View& view) { + pp::Rect view_rect(view.GetRect()); + float device_scale = 1.0f; + float old_device_scale = device_scale_; + if (hidpi_enabled_) + device_scale = view.GetDeviceScale(); + pp::Size view_device_size(view_rect.width() * device_scale, + view_rect.height() * device_scale); + if (view_device_size == plugin_size_ && device_scale == device_scale_) + return; // We don't care about the position, only the size. + + image_data_ = pp::ImageData(); + device_scale_ = device_scale; + plugin_dip_size_ = view_rect.size(); + plugin_size_ = view_device_size; + + paint_manager_.SetSize(view_device_size, device_scale_); + + image_data_ = pp::ImageData(this, + PP_IMAGEDATAFORMAT_BGRA_PREMUL, + plugin_size_, + false); + if (image_data_.is_null()) { + DCHECK(plugin_size_.IsEmpty()); + return; + } + + // View dimensions changed, disable autoscroll (if it was enabled). + DisableAutoscroll(); + + OnGeometryChanged(zoom_, old_device_scale); +} + +pp::Var Instance::GetInstanceObject() { + if (instance_object_.is_undefined()) { + PDFScriptableObject* object = new PDFScriptableObject(this); + // The pp::Var takes ownership of object here. + instance_object_ = pp::VarPrivate(this, object); + } + + return instance_object_; +} + +pp::Var Instance::GetLinkAtPosition(const pp::Point& point) { + pp::Point offset_point(point); + ScalePoint(device_scale_, &offset_point); + offset_point.set_x(offset_point.x() - available_area_.x()); + return engine_->GetLinkAtPosition(offset_point); +} + +pp::Var Instance::GetSelectedText(bool html) { + if (html || !engine_->HasPermission(PDFEngine::PERMISSION_COPY)) + return pp::Var(); + return engine_->GetSelectedText(); +} + +void Instance::InvalidateWidget(pp::Widget_Dev widget, + const pp::Rect& dirty_rect) { + if (v_scrollbar_.get() && *v_scrollbar_ == widget) { + if (!image_data_.is_null()) + v_scrollbar_->Paint(dirty_rect.pp_rect(), &image_data_); + } else if (h_scrollbar_.get() && *h_scrollbar_ == widget) { + if (!image_data_.is_null()) + h_scrollbar_->Paint(dirty_rect.pp_rect(), &image_data_); + } else { + // Possible to hit this condition since sometimes the scrollbar codes posts + // a task to do something later, and we could have deleted our reference in + // the meantime. + return; + } + + pp::Rect dirty_rect_scaled = dirty_rect; + ScaleRect(device_scale_, &dirty_rect_scaled); + paint_manager_.InvalidateRect(dirty_rect_scaled); +} + +void Instance::ScrollbarValueChanged(pp::Scrollbar_Dev scrollbar, + uint32_t value) { + value = GetScaled(value); + if (v_scrollbar_.get() && *v_scrollbar_ == scrollbar) { + engine_->ScrolledToYPosition(value); + pp::Rect rc; + v_scrollbar_->GetLocation(&rc); + int32 doc_height = GetDocumentPixelHeight(); + doc_height -= GetScaled(rc.height()); +#ifdef ENABLE_THUMBNAILS + if (thumbnails_.visible()) { + thumbnails_.SetPosition(value, doc_height, true); + } +#endif + pp::Point origin( + plugin_size_.width() - page_indicator_.rect().width() - + GetScaled(GetScrollbarReservedThickness()), + page_indicator_.GetYPosition(value, doc_height, plugin_size_.height())); + page_indicator_.MoveTo(origin, page_indicator_.visible()); + } else if (h_scrollbar_.get() && *h_scrollbar_ == scrollbar) { + engine_->ScrolledToXPosition(value); + } +} + +void Instance::ScrollbarOverlayChanged(pp::Scrollbar_Dev scrollbar, + bool overlay) { + scrollbar_reserved_thickness_ = overlay ? 0 : scrollbar_thickness_; + OnGeometryChanged(zoom_, device_scale_); +} + +uint32_t Instance::QuerySupportedPrintOutputFormats() { + return engine_->QuerySupportedPrintOutputFormats(); +} + +int32_t Instance::PrintBegin(const PP_PrintSettings_Dev& print_settings) { + // For us num_pages is always equal to the number of pages in the PDF + // document irrespective of the printable area. + int32_t ret = engine_->GetNumberOfPages(); + if (!ret) + return 0; + + uint32_t supported_formats = engine_->QuerySupportedPrintOutputFormats(); + if ((print_settings.format & supported_formats) == 0) + return 0; + + print_settings_.is_printing = true; + print_settings_.pepper_print_settings = print_settings; + engine_->PrintBegin(); + return ret; +} + +pp::Resource Instance::PrintPages( + const PP_PrintPageNumberRange_Dev* page_ranges, + uint32_t page_range_count) { + if (!print_settings_.is_printing) + return pp::Resource(); + + print_settings_.print_pages_called_ = true; + return engine_->PrintPages(page_ranges, page_range_count, + print_settings_.pepper_print_settings); +} + +void Instance::PrintEnd() { + if (print_settings_.print_pages_called_) + UserMetricsRecordAction("PDF.PrintPage"); + print_settings_.Clear(); + engine_->PrintEnd(); +} + +bool Instance::IsPrintScalingDisabled() { + return !engine_->GetPrintScaling(); +} + +bool Instance::StartFind(const std::string& text, bool case_sensitive) { + engine_->StartFind(text.c_str(), case_sensitive); + return true; +} + +void Instance::SelectFindResult(bool forward) { + engine_->SelectFindResult(forward); +} + +void Instance::StopFind() { + engine_->StopFind(); +} + +void Instance::Zoom(double scale, bool text_only) { + UserMetricsRecordAction("PDF.ZoomFromBrowser"); + + // If the zoom level doesn't change it means that this zoom change might have + // been initiated by the plugin. In that case, we don't want to change the + // zoom mode to ZOOM_SCALE as it may have been intentionally set to + // ZOOM_FIT_TO_PAGE or some other value when the zoom was last changed. + if (scale == zoom_) + return; + + SetZoom(ZOOM_SCALE, scale); +} + +void Instance::ZoomChanged(double factor) { + if (full_) + Zoom_Dev::ZoomChanged(factor); +} + +void Instance::OnPaint(const std::vector<pp::Rect>& paint_rects, + std::vector<PaintManager::ReadyRect>* ready, + std::vector<pp::Rect>* pending) { + if (image_data_.is_null()) { + DCHECK(plugin_size_.IsEmpty()); + return; + } + if (first_paint_) { + first_paint_ = false; + pp::Rect rect = pp::Rect(pp::Point(), plugin_size_); + FillRect(rect, kBackgroundColor); + ready->push_back(PaintManager::ReadyRect(rect, image_data_, true)); + *pending = paint_rects; + return; + } + + engine_->PrePaint(); + + for (size_t i = 0; i < paint_rects.size(); i++) { + // Intersect with plugin area since there could be pending invalidates from + // when the plugin area was larger. + pp::Rect rect = + paint_rects[i].Intersect(pp::Rect(pp::Point(), plugin_size_)); + if (rect.IsEmpty()) + continue; + + pp::Rect pdf_rect = available_area_.Intersect(rect); + if (!pdf_rect.IsEmpty()) { + pdf_rect.Offset(available_area_.x() * -1, 0); + + std::vector<pp::Rect> pdf_ready; + std::vector<pp::Rect> pdf_pending; + engine_->Paint(pdf_rect, &image_data_, &pdf_ready, &pdf_pending); + for (size_t j = 0; j < pdf_ready.size(); ++j) { + pdf_ready[j].Offset(available_area_.point()); + ready->push_back( + PaintManager::ReadyRect(pdf_ready[j], image_data_, false)); + } + for (size_t j = 0; j < pdf_pending.size(); ++j) { + pdf_pending[j].Offset(available_area_.point()); + pending->push_back(pdf_pending[j]); + } + } + + for (size_t j = 0; j < background_parts_.size(); ++j) { + pp::Rect intersection = background_parts_[j].location.Intersect(rect); + if (!intersection.IsEmpty()) { + FillRect(intersection, background_parts_[j].color); + ready->push_back( + PaintManager::ReadyRect(intersection, image_data_, false)); + } + } + + if (document_load_state_ == LOAD_STATE_FAILED) { + pp::Point top_center; + top_center.set_x(plugin_size_.width() / 2); + top_center.set_y(plugin_size_.height() / 2); + DrawText(top_center, PP_RESOURCESTRING_PDFLOAD_FAILED); + } + +#ifdef ENABLE_THUMBNAILS + thumbnails_.Paint(&image_data_, rect); +#endif + } + + engine_->PostPaint(); + + // Must paint scrollbars after the background parts, in case we have an + // overlay scrollbar that's over the background. We also do this in a separate + // loop because the scrollbar painting logic uses the signal of whether there + // are pending paints or not to figure out if it should draw right away or + // not. + for (size_t i = 0; i < paint_rects.size(); i++) { + PaintIfWidgetIntersects(h_scrollbar_.get(), paint_rects[i], ready, pending); + PaintIfWidgetIntersects(v_scrollbar_.get(), paint_rects[i], ready, pending); + } + + if (progress_bar_.visible()) + PaintOverlayControl(&progress_bar_, &image_data_, ready); + + if (page_indicator_.visible()) + PaintOverlayControl(&page_indicator_, &image_data_, ready); + + if (toolbar_->current_transparency() != kTransparentAlpha) + PaintOverlayControl(toolbar_.get(), &image_data_, ready); + + // Paint autoscroll anchor if needed. + if (is_autoscroll_) { + size_t limit = ready->size(); + for (size_t i = 0; i < limit; i++) { + pp::Rect anchor_rect = autoscroll_rect_.Intersect((*ready)[i].rect); + if (!anchor_rect.IsEmpty()) { + pp::Rect draw_rc = pp::Rect( + pp::Point(anchor_rect.x() - autoscroll_rect_.x(), + anchor_rect.y() - autoscroll_rect_.y()), + anchor_rect.size()); + // Paint autoscroll anchor. + AlphaBlend(autoscroll_anchor_, draw_rc, + &image_data_, anchor_rect.point(), kOpaqueAlpha); + } + } + } +} + +void Instance::PaintOverlayControl( + Control* ctrl, + pp::ImageData* image_data, + std::vector<PaintManager::ReadyRect>* ready) { + // Make sure that we only paint overlay controls over an area that's ready, + // i.e. not pending. Otherwise we'll mark the control rect as ready and + // it'll overwrite the pdf region. + std::list<pp::Rect> ctrl_rects; + for (size_t i = 0; i < ready->size(); i++) { + pp::Rect rc = ctrl->rect().Intersect((*ready)[i].rect); + if (!rc.IsEmpty()) + ctrl_rects.push_back(rc); + } + + if (!ctrl_rects.empty()) { + ctrl->PaintMultipleRects(image_data, ctrl_rects); + + std::list<pp::Rect>::iterator iter; + for (iter = ctrl_rects.begin(); iter != ctrl_rects.end(); ++iter) { + ready->push_back(PaintManager::ReadyRect(*iter, *image_data, false)); + } + } +} + +void Instance::DidOpen(int32_t result) { + if (result == PP_OK) { + engine_->HandleDocumentLoad(embed_loader_); + } else if (result != PP_ERROR_ABORTED) { // Can happen in tests. + NOTREACHED(); + } +} + +void Instance::DidOpenPreview(int32_t result) { + if (result == PP_OK) { + preview_engine_.reset(PDFEngine::Create(new PreviewModeClient(this))); + preview_engine_->HandleDocumentLoad(embed_preview_loader_); + } else { + NOTREACHED(); + } +} + +void Instance::PaintIfWidgetIntersects( + pp::Widget_Dev* widget, + const pp::Rect& rect, + std::vector<PaintManager::ReadyRect>* ready, + std::vector<pp::Rect>* pending) { + if (!widget) + return; + + pp::Rect location; + if (!widget->GetLocation(&location)) + return; + + ScaleRect(device_scale_, &location); + location = location.Intersect(rect); + if (location.IsEmpty()) + return; + + if (IsOverlayScrollbar()) { + // If we're using overlay scrollbars, and there are pending paints under the + // scrollbar, don't update the scrollbar instantly. While it would be nice, + // we would need to double buffer the plugin area in order to make this + // work. This is because we'd need to always have a copy of what the pdf + // under the scrollbar looks like, and additionally we couldn't paint the + // pdf under the scrollbar if it's ready until we got the preceding flush. + // So in practice, it would make painting slower and introduce extra buffer + // copies for the general case. + for (size_t i = 0; i < pending->size(); ++i) { + if ((*pending)[i].Intersects(location)) + return; + } + + // Even if none of the pending paints are under the scrollbar, we never want + // to paint it if it's over the pdf if there are other pending paints. + // Otherwise different parts of the pdf plugin would display at different + // scroll offsets. + if (!pending->empty() && available_area_.Intersects(rect)) { + pending->push_back(location); + return; + } + } + + pp::Rect location_dip = location; + ScaleRect(1.0f / device_scale_, &location_dip); + + DCHECK(!image_data_.is_null()); + widget->Paint(location_dip, &image_data_); + + ready->push_back(PaintManager::ReadyRect(location, image_data_, true)); +} + +void Instance::OnTimerFired(int32_t) { + HandleInputEvent(last_mouse_event_); +} + +void Instance::OnClientTimerFired(int32_t id) { + engine_->OnCallback(id); +} + +void Instance::OnControlTimerFired(int32_t, + const uint32& control_id, + const uint32& timer_id) { + if (control_id == toolbar_->id()) { + toolbar_->OnTimerFired(timer_id); + } else if (control_id == progress_bar_.id()) { + if (timer_id == delayed_progress_timer_id_) { + if (document_load_state_ == LOAD_STATE_LOADING && + !progress_bar_.visible()) { + progress_bar_.Fade(true, kProgressFadeTimeoutMs); + } + delayed_progress_timer_id_ = 0; + } else { + progress_bar_.OnTimerFired(timer_id); + } + } else if (control_id == kAutoScrollId) { + if (is_autoscroll_) { + if (autoscroll_x_ != 0 && h_scrollbar_.get()) { + h_scrollbar_->ScrollBy(PP_SCROLLBY_PIXEL, autoscroll_x_); + } + if (autoscroll_y_ != 0 && v_scrollbar_.get()) { + v_scrollbar_->ScrollBy(PP_SCROLLBY_PIXEL, autoscroll_y_); + } + + // Reschedule timer. + ScheduleTimer(kAutoScrollId, kAutoScrollTimeoutMs); + } + } else if (control_id == kPageIndicatorId) { + page_indicator_.OnTimerFired(timer_id); + } +#ifdef ENABLE_THUMBNAILS + else if (control_id == thumbnails_.id()) { + thumbnails_.OnTimerFired(timer_id); + } +#endif +} + +void Instance::CalculateBackgroundParts() { + background_parts_.clear(); + int v_scrollbar_thickness = + GetScaled(v_scrollbar_.get() ? GetScrollbarReservedThickness() : 0); + int h_scrollbar_thickness = + GetScaled(h_scrollbar_.get() ? GetScrollbarReservedThickness() : 0); + int width_without_scrollbar = std::max( + plugin_size_.width() - v_scrollbar_thickness, 0); + int height_without_scrollbar = std::max( + plugin_size_.height() - h_scrollbar_thickness, 0); + int left_width = available_area_.x(); + int right_start = available_area_.right(); + int right_width = abs(width_without_scrollbar - available_area_.right()); + int bottom = std::min(available_area_.bottom(), height_without_scrollbar); + + // Add the left, right, and bottom rectangles. Note: we assume only + // horizontal centering. + BackgroundPart part = { + pp::Rect(0, 0, left_width, bottom), + kBackgroundColor + }; + if (!part.location.IsEmpty()) + background_parts_.push_back(part); + part.location = pp::Rect(right_start, 0, right_width, bottom); + if (!part.location.IsEmpty()) + background_parts_.push_back(part); + part.location = pp::Rect( + 0, bottom, width_without_scrollbar, height_without_scrollbar - bottom); + if (!part.location.IsEmpty()) + background_parts_.push_back(part); + + if (h_scrollbar_thickness +#if defined(OS_MACOSX) + || +#else + && +#endif + v_scrollbar_thickness) { + part.color = 0xFFFFFFFF; + part.location = pp::Rect(plugin_size_.width() - v_scrollbar_thickness, + plugin_size_.height() - h_scrollbar_thickness, + h_scrollbar_thickness, + v_scrollbar_thickness); + background_parts_.push_back(part); + } +} + +int Instance::GetDocumentPixelWidth() const { + return static_cast<int>(ceil(document_size_.width() * zoom_ * device_scale_)); +} + +int Instance::GetDocumentPixelHeight() const { + return static_cast<int>(ceil(document_size_.height() * + zoom_ * + device_scale_)); +} + +void Instance::FillRect(const pp::Rect& rect, uint32 color) { + DCHECK(!image_data_.is_null() || rect.IsEmpty()); + uint32* buffer_start = static_cast<uint32*>(image_data_.data()); + int stride = image_data_.stride(); + uint32* ptr = buffer_start + rect.y() * stride / 4 + rect.x(); + int height = rect.height(); + int width = rect.width(); + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) + *(ptr + x) = color; + ptr += stride / 4; + } +} + +void Instance::DocumentSizeUpdated(const pp::Size& size) { + document_size_ = size; + + OnGeometryChanged(zoom_, device_scale_); +} + +void Instance::Invalidate(const pp::Rect& rect) { + pp::Rect offset_rect(rect); + offset_rect.Offset(available_area_.point()); + paint_manager_.InvalidateRect(offset_rect); +} + +void Instance::Scroll(const pp::Point& point) { + pp::Rect scroll_area = available_area_; + if (IsOverlayScrollbar()) { + pp::Rect rc; + if (h_scrollbar_.get()) { + h_scrollbar_->GetLocation(&rc); + ScaleRect(device_scale_, &rc); + if (scroll_area.bottom() > rc.y()) { + scroll_area.set_height(rc.y() - scroll_area.y()); + paint_manager_.InvalidateRect(rc); + } + } + if (v_scrollbar_.get()) { + v_scrollbar_->GetLocation(&rc); + ScaleRect(device_scale_, &rc); + if (scroll_area.right() > rc.x()) { + scroll_area.set_width(rc.x() - scroll_area.x()); + paint_manager_.InvalidateRect(rc); + } + } + } + paint_manager_.ScrollRect(scroll_area, point); + + if (toolbar_->current_transparency() != kTransparentAlpha) + paint_manager_.InvalidateRect(toolbar_->GetControlsRect()); + + if (progress_bar_.visible()) + paint_manager_.InvalidateRect(progress_bar_.rect()); + + if (is_autoscroll_) + paint_manager_.InvalidateRect(autoscroll_rect_); + + if (show_page_indicator_) { + page_indicator_.set_current_page(GetPageNumberToDisplay()); + page_indicator_.Splash(); + } + + if (page_indicator_.visible()) + paint_manager_.InvalidateRect(page_indicator_.rect()); + + // Run the scroll callback asynchronously. This function can be invoked by a + // layout change which should not re-enter into JS synchronously. + pp::CompletionCallback callback = + callback_factory_.NewCallback(&Instance::RunCallback, + on_scroll_callback_); + pp::Module::Get()->core()->CallOnMainThread(0, callback); +} + +void Instance::ScrollToX(int position) { + if (!h_scrollbar_.get()) { + NOTREACHED(); + return; + } + int position_dip = static_cast<int>(position / device_scale_); + h_scrollbar_->SetValue(position_dip); +} + +void Instance::ScrollToY(int position) { + if (!v_scrollbar_.get()) { + NOTREACHED(); + return; + } + int position_dip = static_cast<int>(position / device_scale_); + v_scrollbar_->SetValue(ClipToRange(position_dip, 0, valid_v_range_)); +} + +void Instance::ScrollToPage(int page) { + if (!v_scrollbar_.get()) + return; + + if (engine_->GetNumberOfPages() == 0) + return; + + int index = ClipToRange(page, 0, engine_->GetNumberOfPages() - 1); + pp::Rect rect = engine_->GetPageRect(index); + // If we are trying to scroll pass the last page, + // scroll to the end of the last page. + int position = index < page ? rect.bottom() : rect.y(); + ScrollToY(position * zoom_ * device_scale_); +} + +void Instance::NavigateTo(const std::string& url, bool open_in_new_tab) { + std::string url_copy(url); + + // Empty |url_copy| is ok, and will effectively be a reload. + // Skip the code below so an empty URL does not turn into "http://", which + // will cause GURL to fail a DCHECK. + if (!url_copy.empty()) { + // If |url_copy| starts with '#', then it's for the same URL with a + // different URL fragment. + if (url_copy[0] == '#') { + url_copy = url_ + url_copy; + // Changing the href does not actually do anything when navigating in the + // same tab, so do the actual page scroll here. Then fall through so the + // href gets updated. + if (!open_in_new_tab) { + int page_number = GetInitialPage(url_copy); + if (page_number >= 0) + ScrollToPage(page_number); + } + } + // If there's no scheme, add http. + if (url_copy.find("://") == std::string::npos && + url_copy.find("mailto:") == std::string::npos) { + url_copy = "http://" + url_copy; + } + // Make sure |url_copy| starts with a valid scheme. + if (url_copy.find("http://") != 0 && + url_copy.find("https://") != 0 && + url_copy.find("ftp://") != 0 && + url_copy.find("file://") != 0 && + url_copy.find("mailto:") != 0) { + return; + } + // Make sure |url_copy| is not only a scheme. + if (url_copy == "http://" || + url_copy == "https://" || + url_copy == "ftp://" || + url_copy == "file://" || + url_copy == "mailto:") { + return; + } + } + if (open_in_new_tab) { + GetWindowObject().Call("open", url_copy); + } else { + GetWindowObject().GetProperty("top").GetProperty("location"). + SetProperty("href", url_copy); + } +} + +void Instance::UpdateCursor(PP_CursorType_Dev cursor) { + if (cursor == cursor_) + return; + cursor_ = cursor; + + const PPB_CursorControl_Dev* cursor_interface = + reinterpret_cast<const PPB_CursorControl_Dev*>( + pp::Module::Get()->GetBrowserInterface(PPB_CURSOR_CONTROL_DEV_INTERFACE)); + if (!cursor_interface) { + NOTREACHED(); + return; + } + + cursor_interface->SetCursor( + pp_instance(), cursor_, pp::ImageData().pp_resource(), NULL); +} + +void Instance::UpdateTickMarks(const std::vector<pp::Rect>& tickmarks) { + if (!v_scrollbar_.get()) + return; + + float inverse_scale = 1.0f / device_scale_; + std::vector<pp::Rect> scaled_tickmarks = tickmarks; + for (size_t i = 0; i < scaled_tickmarks.size(); i++) { + ScaleRect(inverse_scale, &scaled_tickmarks[i]); + } + + v_scrollbar_->SetTickMarks( + scaled_tickmarks.empty() ? NULL : &scaled_tickmarks[0], tickmarks.size()); +} + +void Instance::NotifyNumberOfFindResultsChanged(int total, bool final_result) { + NumberOfFindResultsChanged(total, final_result); +} + +void Instance::NotifySelectedFindResultChanged(int current_find_index) { + SelectedFindResultChanged(current_find_index); +} + +void Instance::OnEvent(uint32 control_id, uint32 event_id, void* data) { + if (event_id == Button::EVENT_ID_BUTTON_CLICKED || + event_id == Button::EVENT_ID_BUTTON_STATE_CHANGED) { + switch (control_id) { + case kFitToPageButtonId: + UserMetricsRecordAction("PDF.FitToPageButton"); + SetZoom(ZOOM_FIT_TO_PAGE, 0); + ZoomChanged(zoom_); + break; + case kFitToWidthButtonId: + UserMetricsRecordAction("PDF.FitToWidthButton"); + SetZoom(ZOOM_FIT_TO_WIDTH, 0); + ZoomChanged(zoom_); + break; + case kZoomOutButtonId: + case kZoomInButtonId: + UserMetricsRecordAction(control_id == kZoomOutButtonId ? + "PDF.ZoomOutButton" : "PDF.ZoomInButton"); + SetZoom(ZOOM_SCALE, CalculateZoom(control_id)); + ZoomChanged(zoom_); + break; + case kSaveButtonId: + UserMetricsRecordAction("PDF.SaveButton"); + SaveAs(); + break; + case kPrintButtonId: + UserMetricsRecordAction("PDF.PrintButton"); + Print(); + break; + } + } + if (control_id == kThumbnailsId && + event_id == ThumbnailControl::EVENT_ID_THUMBNAIL_SELECTED) { + int page = *static_cast<int*>(data); + pp::Rect page_rc(engine_->GetPageRect(page)); + ScrollToY(static_cast<int>(page_rc.y() * zoom_ * device_scale_)); + } +} + +void Instance::Invalidate(uint32 control_id, const pp::Rect& rc) { + paint_manager_.InvalidateRect(rc); +} + +uint32 Instance::ScheduleTimer(uint32 control_id, uint32 timeout_ms) { + current_timer_id_++; + pp::CompletionCallback callback = + timer_factory_.NewCallback(&Instance::OnControlTimerFired, + control_id, + current_timer_id_); + pp::Module::Get()->core()->CallOnMainThread(timeout_ms, callback); + return current_timer_id_; +} + +void Instance::SetEventCapture(uint32 control_id, bool set_capture) { + // TODO(gene): set event capture here. +} + +void Instance::SetCursor(uint32 control_id, PP_CursorType_Dev cursor_type) { + UpdateCursor(cursor_type); +} + +pp::Instance* Instance::GetInstance() { + return this; +} + +void Instance::GetDocumentPassword( + pp::CompletionCallbackWithOutput<pp::Var> callback) { + std::string message(GetLocalizedString(PP_RESOURCESTRING_PDFGETPASSWORD)); + pp::Var result = pp::PDF::ModalPromptForPassword(this, message); + *callback.output() = result.pp_var(); + callback.Run(PP_OK); +} + +void Instance::Alert(const std::string& message) { + GetWindowObject().Call("alert", message); +} + +bool Instance::Confirm(const std::string& message) { + pp::Var result = GetWindowObject().Call("confirm", message); + return result.is_bool() ? result.AsBool() : false; +} + +std::string Instance::Prompt(const std::string& question, + const std::string& default_answer) { + pp::Var result = GetWindowObject().Call("prompt", question, default_answer); + return result.is_string() ? result.AsString() : std::string(); +} + +std::string Instance::GetURL() { + return url_; +} + +void Instance::Email(const std::string& to, + const std::string& cc, + const std::string& bcc, + const std::string& subject, + const std::string& body) { + std::string javascript = + "var href = 'mailto:" + net::EscapeUrlEncodedData(to, false) + + "?cc=" + net::EscapeUrlEncodedData(cc, false) + + "&bcc=" + net::EscapeUrlEncodedData(bcc, false) + + "&subject=" + net::EscapeUrlEncodedData(subject, false) + + "&body=" + net::EscapeUrlEncodedData(body, false) + + "';var temp = window.open(href, '_blank', " + + "'width=1,height=1');if(temp) temp.close();"; + ExecuteScript(javascript); +} + +void Instance::Print() { + if (!printing_enabled_ || + (!engine_->HasPermission(PDFEngine::PERMISSION_PRINT_LOW_QUALITY) && + !engine_->HasPermission(PDFEngine::PERMISSION_PRINT_HIGH_QUALITY))) { + return; + } + + pp::CompletionCallback callback = + callback_factory_.NewCallback(&Instance::OnPrint); + pp::Module::Get()->core()->CallOnMainThread(0, callback); +} + +void Instance::OnPrint(int32_t) { + pp::PDF::Print(this); +} + +void Instance::SaveAs() { + pp::PDF::SaveAs(this); +} + +void Instance::SubmitForm(const std::string& url, + const void* data, + int length) { + pp::URLRequestInfo request(this); + request.SetURL(url); + request.SetMethod("POST"); + request.AppendDataToBody(reinterpret_cast<const char*>(data), length); + + pp::CompletionCallback callback = + form_factory_.NewCallback(&Instance::FormDidOpen); + form_loader_ = CreateURLLoaderInternal(); + int rv = form_loader_.Open(request, callback); + if (rv != PP_OK_COMPLETIONPENDING) + callback.Run(rv); +} + +void Instance::FormDidOpen(int32_t result) { + // TODO: inform the user of success/failure. + if (result != PP_OK) { + NOTREACHED(); + } +} + +std::string Instance::ShowFileSelectionDialog() { + // Seems like very low priority to implement, since the pdf has no way to get + // the file data anyways. Javascript doesn't let you do this synchronously. + NOTREACHED(); + return std::string(); +} + +pp::URLLoader Instance::CreateURLLoader() { + if (full_) { + if (!did_call_start_loading_) { + did_call_start_loading_ = true; + pp::PDF::DidStartLoading(this); + } + + // Disable save and print until the document is fully loaded, since they + // would generate an incomplete document. Need to do this each time we + // call DidStartLoading since that resets the content restrictions. + pp::PDF::SetContentRestriction(this, CONTENT_RESTRICTION_SAVE | + CONTENT_RESTRICTION_PRINT); + } + + return CreateURLLoaderInternal(); +} + +void Instance::ScheduleCallback(int id, int delay_in_ms) { + pp::CompletionCallback callback = + timer_factory_.NewCallback(&Instance::OnClientTimerFired); + pp::Module::Get()->core()->CallOnMainThread(delay_in_ms, callback, id); +} + +void Instance::SearchString(const base::char16* string, + const base::char16* term, + bool case_sensitive, + std::vector<SearchStringResult>* results) { + if (!pp::PDF::IsAvailable()) { + NOTREACHED(); + return; + } + + PP_PrivateFindResult* pp_results; + int count = 0; + pp::PDF::SearchString( + this, + reinterpret_cast<const unsigned short*>(string), + reinterpret_cast<const unsigned short*>(term), + case_sensitive, + &pp_results, + &count); + + results->resize(count); + for (int i = 0; i < count; ++i) { + (*results)[i].start_index = pp_results[i].start_index; + (*results)[i].length = pp_results[i].length; + } + + pp::Memory_Dev memory; + memory.MemFree(pp_results); +} + +void Instance::DocumentPaintOccurred() { + if (painted_first_page_) + return; + + painted_first_page_ = true; + UpdateToolbarPosition(false); + toolbar_->Splash(kToolbarSplashTimeoutMs); + + if (engine_->GetNumberOfPages() > 1) + show_page_indicator_ = true; + else + show_page_indicator_ = false; + + if (v_scrollbar_.get() && show_page_indicator_) { + page_indicator_.set_current_page(GetPageNumberToDisplay()); + page_indicator_.Splash(kToolbarSplashTimeoutMs, + kPageIndicatorInitialFadeTimeoutMs); + } +} + +void Instance::DocumentLoadComplete(int page_count) { + // Clear focus state for OSK. + FormTextFieldFocusChange(false); + + // Update progress control. + if (progress_bar_.visible()) + progress_bar_.Fade(false, kProgressFadeTimeoutMs); + + DCHECK(document_load_state_ == LOAD_STATE_LOADING); + document_load_state_ = LOAD_STATE_COMPLETE; + UserMetricsRecordAction("PDF.LoadSuccess"); + + if (did_call_start_loading_) { + pp::PDF::DidStopLoading(this); + did_call_start_loading_ = false; + } + + if (on_load_callback_.is_string()) + ExecuteScript(on_load_callback_); + // Note: If we are in print preview mode on_load_callback_ might call + // ScrollTo{X|Y}() and we don't want to scroll again and override it. + // #page=N is not supported in Print Preview. + if (!IsPrintPreview()) { + int initial_page = GetInitialPage(url_); + if (initial_page >= 0) + ScrollToPage(initial_page); + } + + if (!full_) + return; + if (!pp::PDF::IsAvailable()) + return; + + int content_restrictions = + CONTENT_RESTRICTION_CUT | CONTENT_RESTRICTION_PASTE; + if (!engine_->HasPermission(PDFEngine::PERMISSION_COPY)) + content_restrictions |= CONTENT_RESTRICTION_COPY; + + if (!engine_->HasPermission(PDFEngine::PERMISSION_PRINT_LOW_QUALITY) && + !engine_->HasPermission(PDFEngine::PERMISSION_PRINT_HIGH_QUALITY)) { + printing_enabled_ = false; + if (current_tb_info_ == kPDFToolbarButtons) { + // Remove Print button. + CreateToolbar(kPDFNoPrintToolbarButtons, + arraysize(kPDFNoPrintToolbarButtons)); + UpdateToolbarPosition(false); + Invalidate(pp::Rect(plugin_size_)); + } + } + + pp::PDF::SetContentRestriction(this, content_restrictions); + + pp::PDF::HistogramPDFPageCount(this, page_count); +} + +void Instance::RotateClockwise() { + engine_->RotateClockwise(); +} + +void Instance::RotateCounterclockwise() { + engine_->RotateCounterclockwise(); +} + +void Instance::PreviewDocumentLoadComplete() { + if (preview_document_load_state_ != LOAD_STATE_LOADING || + preview_pages_info_.empty()) { + return; + } + + preview_document_load_state_ = LOAD_STATE_COMPLETE; + + int dest_page_index = preview_pages_info_.front().second; + int src_page_index = + ExtractPrintPreviewPageIndex(preview_pages_info_.front().first); + if (src_page_index > 0 && dest_page_index > -1 && preview_engine_.get()) + engine_->AppendPage(preview_engine_.get(), dest_page_index); + + preview_pages_info_.pop(); + // |print_preview_page_count_| is not updated yet. Do not load any + // other preview pages till we get this information. + if (print_preview_page_count_ == 0) + return; + + if (preview_pages_info_.size()) + LoadAvailablePreviewPage(); +} + +void Instance::DocumentLoadFailed() { + DCHECK(document_load_state_ == LOAD_STATE_LOADING); + UserMetricsRecordAction("PDF.LoadFailure"); + + // Hide progress control. + progress_bar_.Fade(false, kProgressFadeTimeoutMs); + + if (did_call_start_loading_) { + pp::PDF::DidStopLoading(this); + did_call_start_loading_ = false; + } + + document_load_state_ = LOAD_STATE_FAILED; + paint_manager_.InvalidateRect(pp::Rect(pp::Point(), plugin_size_)); +} + +void Instance::PreviewDocumentLoadFailed() { + UserMetricsRecordAction("PDF.PreviewDocumentLoadFailure"); + if (preview_document_load_state_ != LOAD_STATE_LOADING || + preview_pages_info_.empty()) { + return; + } + + preview_document_load_state_ = LOAD_STATE_FAILED; + preview_pages_info_.pop(); + + if (preview_pages_info_.size()) + LoadAvailablePreviewPage(); +} + +pp::Instance* Instance::GetPluginInstance() { + return GetInstance(); +} + +void Instance::DocumentHasUnsupportedFeature(const std::string& feature) { + std::string metric("PDF_Unsupported_"); + metric += feature; + if (!unsupported_features_reported_.count(metric)) { + unsupported_features_reported_.insert(metric); + UserMetricsRecordAction(metric); + } + + // Since we use an info bar, only do this for full frame plugins.. + if (!full_) + return; + + if (told_browser_about_unsupported_feature_) + return; + told_browser_about_unsupported_feature_ = true; + + pp::PDF::HasUnsupportedFeature(this); +} + +void Instance::DocumentLoadProgress(uint32 available, uint32 doc_size) { + double progress = 0.0; + if (doc_size == 0) { + // Document size is unknown. Use heuristics. + // We'll make progress logarithmic from 0 to 100M. + static const double kFactor = log(100000000.0) / 100.0; + if (available > 0) { + progress = log(static_cast<double>(available)) / kFactor; + if (progress > 100.0) + progress = 100.0; + } + } else { + progress = 100.0 * static_cast<double>(available) / doc_size; + } + progress_bar_.SetProgress(progress); +} + +void Instance::FormTextFieldFocusChange(bool in_focus) { + if (!text_input_.get()) + return; + if (in_focus) + text_input_->SetTextInputType(PP_TEXTINPUT_TYPE_DEV_TEXT); + else + text_input_->SetTextInputType(PP_TEXTINPUT_TYPE_DEV_NONE); +} + +// Called by PDFScriptableObject. +bool Instance::HasScriptableMethod(const pp::Var& method, pp::Var* exception) { + std::string method_str = method.AsString(); + return (method_str == kJSAccessibility || + method_str == kJSDocumentLoadComplete || + method_str == kJSGetHeight || + method_str == kJSGetHorizontalScrollbarThickness || + method_str == kJSGetPageLocationNormalized || + method_str == kJSGetSelectedText || + method_str == kJSGetVerticalScrollbarThickness || + method_str == kJSGetWidth || + method_str == kJSGetZoomLevel || + method_str == kJSGoToPage || + method_str == kJSGrayscale || + method_str == kJSLoadPreviewPage || + method_str == kJSOnLoad || + method_str == kJSOnPluginSizeChanged || + method_str == kJSOnScroll || + method_str == kJSPageXOffset || + method_str == kJSPageYOffset || + method_str == kJSPrintPreviewPageCount || + method_str == kJSReload || + method_str == kJSRemovePrintButton || + method_str == kJSResetPrintPreviewUrl || + method_str == kJSSendKeyEvent || + method_str == kJSSetPageNumbers || + method_str == kJSSetPageXOffset || + method_str == kJSSetPageYOffset || + method_str == kJSSetZoomLevel || + method_str == kJSZoomFitToHeight || + method_str == kJSZoomFitToWidth || + method_str == kJSZoomIn || + method_str == kJSZoomOut); +} + +pp::Var Instance::CallScriptableMethod(const pp::Var& method, + const std::vector<pp::Var>& args, + pp::Var* exception) { + std::string method_str = method.AsString(); + if (method_str == kJSGrayscale) { + if (args.size() == 1 && args[0].is_bool()) { + engine_->SetGrayscale(args[0].AsBool()); + // Redraw + paint_manager_.InvalidateRect(pp::Rect(pp::Point(), plugin_size_)); +#ifdef ENABLE_THUMBNAILS + if (thumbnails_.visible()) + thumbnails_.Show(true, true); +#endif + return pp::Var(true); + } + return pp::Var(false); + } + if (method_str == kJSOnLoad) { + if (args.size() == 1 && args[0].is_string()) { + on_load_callback_ = args[0]; + return pp::Var(true); + } + return pp::Var(false); + } + if (method_str == kJSOnScroll) { + if (args.size() == 1 && args[0].is_string()) { + on_scroll_callback_ = args[0]; + return pp::Var(true); + } + return pp::Var(false); + } + if (method_str == kJSOnPluginSizeChanged) { + if (args.size() == 1 && args[0].is_string()) { + on_plugin_size_changed_callback_ = args[0]; + return pp::Var(true); + } + return pp::Var(false); + } + if (method_str == kJSReload) { + document_load_state_ = LOAD_STATE_LOADING; + if (!full_) + LoadUrl(url_); + preview_engine_.reset(); + print_preview_page_count_ = 0; + engine_.reset(PDFEngine::Create(this)); + engine_->New(url_.c_str()); +#ifdef ENABLE_THUMBNAILS + thumbnails_.ResetEngine(engine_.get()); +#endif + return pp::Var(); + } + if (method_str == kJSResetPrintPreviewUrl) { + if (args.size() == 1 && args[0].is_string()) { + url_ = args[0].AsString(); + preview_pages_info_ = std::queue<PreviewPageInfo>(); + preview_document_load_state_ = LOAD_STATE_COMPLETE; + } + return pp::Var(); + } + if (method_str == kJSZoomFitToHeight) { + SetZoom(ZOOM_FIT_TO_PAGE, 0); + return pp::Var(); + } + if (method_str == kJSZoomFitToWidth) { + SetZoom(ZOOM_FIT_TO_WIDTH, 0); + return pp::Var(); + } + if (method_str == kJSZoomIn) { + SetZoom(ZOOM_SCALE, CalculateZoom(kZoomInButtonId)); + return pp::Var(); + } + if (method_str == kJSZoomOut) { + SetZoom(ZOOM_SCALE, CalculateZoom(kZoomOutButtonId)); + return pp::Var(); + } + if (method_str == kJSSetZoomLevel) { + if (args.size() == 1 && args[0].is_number()) + SetZoom(ZOOM_SCALE, args[0].AsDouble()); + return pp::Var(); + } + if (method_str == kJSGetZoomLevel) { + return pp::Var(zoom_); + } + if (method_str == kJSGetHeight) { + return pp::Var(plugin_size_.height()); + } + if (method_str == kJSGetWidth) { + return pp::Var(plugin_size_.width()); + } + if (method_str == kJSGetHorizontalScrollbarThickness) { + return pp::Var( + h_scrollbar_.get() ? GetScrollbarReservedThickness() : 0); + } + if (method_str == kJSGetVerticalScrollbarThickness) { + return pp::Var( + v_scrollbar_.get() ? GetScrollbarReservedThickness() : 0); + } + if (method_str == kJSGetSelectedText) { + return GetSelectedText(false); + } + if (method_str == kJSDocumentLoadComplete) { + return pp::Var((document_load_state_ != LOAD_STATE_LOADING)); + } + if (method_str == kJSPageYOffset) { + return pp::Var(static_cast<int32_t>( + v_scrollbar_.get() ? v_scrollbar_->GetValue() : 0)); + } + if (method_str == kJSSetPageYOffset) { + if (args.size() == 1 && args[0].is_number() && v_scrollbar_.get()) + ScrollToY(GetScaled(args[0].AsInt())); + return pp::Var(); + } + if (method_str == kJSPageXOffset) { + return pp::Var(static_cast<int32_t>( + h_scrollbar_.get() ? h_scrollbar_->GetValue() : 0)); + } + if (method_str == kJSSetPageXOffset) { + if (args.size() == 1 && args[0].is_number() && h_scrollbar_.get()) + ScrollToX(GetScaled(args[0].AsInt())); + return pp::Var(); + } + if (method_str == kJSRemovePrintButton) { + CreateToolbar(kPrintPreviewToolbarButtons, + arraysize(kPrintPreviewToolbarButtons)); + UpdateToolbarPosition(false); + Invalidate(pp::Rect(plugin_size_)); + return pp::Var(); + } + if (method_str == kJSGoToPage) { + if (args.size() == 1 && args[0].is_string()) { + ScrollToPage(atoi(args[0].AsString().c_str())); + } + return pp::Var(); + } + if (method_str == kJSAccessibility) { + if (args.size() == 0) { + base::DictionaryValue node; + node.SetInteger(kAccessibleNumberOfPages, engine_->GetNumberOfPages()); + node.SetBoolean(kAccessibleLoaded, + document_load_state_ != LOAD_STATE_LOADING); + bool has_permissions = + engine_->HasPermission(PDFEngine::PERMISSION_COPY) || + engine_->HasPermission(PDFEngine::PERMISSION_COPY_ACCESSIBLE); + node.SetBoolean(kAccessibleCopyable, has_permissions); + std::string json; + base::JSONWriter::Write(&node, &json); + return pp::Var(json); + } else if (args[0].is_number()) { + return pp::Var(engine_->GetPageAsJSON(args[0].AsInt())); + } + } + if (method_str == kJSPrintPreviewPageCount) { + if (args.size() == 1 && args[0].is_number()) + SetPrintPreviewMode(args[0].AsInt()); + return pp::Var(); + } + if (method_str == kJSLoadPreviewPage) { + if (args.size() == 2 && args[0].is_string() && args[1].is_number()) + ProcessPreviewPageInfo(args[0].AsString(), args[1].AsInt()); + return pp::Var(); + } + if (method_str == kJSGetPageLocationNormalized) { + const size_t kMaxLength = 30; + char location_info[kMaxLength]; + int page_idx = engine_->GetMostVisiblePage(); + if (page_idx < 0) + return pp::Var(std::string()); + pp::Rect rect = engine_->GetPageContentsRect(page_idx); + int v_scrollbar_reserved_thickness = + v_scrollbar_.get() ? GetScaled(GetScrollbarReservedThickness()) : 0; + + rect.set_x(rect.x() + ((plugin_size_.width() - + v_scrollbar_reserved_thickness - available_area_.width()) / 2)); + base::snprintf(location_info, + kMaxLength, + "%0.4f;%0.4f;%0.4f;%0.4f;", + rect.x() / static_cast<float>(plugin_size_.width()), + rect.y() / static_cast<float>(plugin_size_.height()), + rect.width() / static_cast<float>(plugin_size_.width()), + rect.height()/ static_cast<float>(plugin_size_.height())); + return pp::Var(std::string(location_info)); + } + if (method_str == kJSSetPageNumbers) { + if (args.size() != 1 || !args[0].is_string()) + return pp::Var(); + const int num_pages_signed = engine_->GetNumberOfPages(); + if (num_pages_signed <= 0) + return pp::Var(); + scoped_ptr<base::ListValue> page_ranges(static_cast<base::ListValue*>( + base::JSONReader::Read(args[0].AsString(), false))); + const size_t num_pages = static_cast<size_t>(num_pages_signed); + if (!page_ranges.get() || page_ranges->GetSize() != num_pages) + return pp::Var(); + + std::vector<int> print_preview_page_numbers; + for (size_t index = 0; index < num_pages; ++index) { + int page_number = 0; // |page_number| is 1-based. + if (!page_ranges->GetInteger(index, &page_number) || page_number < 1) + return pp::Var(); + print_preview_page_numbers.push_back(page_number); + } + print_preview_page_numbers_ = print_preview_page_numbers; + page_indicator_.set_current_page(GetPageNumberToDisplay()); + return pp::Var(); + } + // This is here to work around https://bugs.webkit.org/show_bug.cgi?id=16735. + // In JS, creating a synthetic keyboard event and dispatching it always + // result in a keycode of 0. + if (method_str == kJSSendKeyEvent) { + if (args.size() == 1 && args[0].is_number()) { + pp::KeyboardInputEvent event( + this, // instance + PP_INPUTEVENT_TYPE_KEYDOWN, // HandleInputEvent only care about this. + 0, // timestamp, not used for kbd events. + 0, // no modifiers. + args[0].AsInt(), // keycode. + pp::Var()); // no char text needed. + HandleInputEvent(event); + } + } + return pp::Var(); +} + +void Instance::OnGeometryChanged(double old_zoom, float old_device_scale) { + bool force_no_horizontal_scrollbar = false; + int scrollbar_thickness = GetScrollbarThickness(); + + if (old_device_scale != device_scale_) { + // Change in device scale forces us to recreate resources + ConfigureNumberImageGenerator(); + + CreateToolbar(current_tb_info_, current_tb_info_size_); + // Load autoscroll anchor image. + autoscroll_anchor_ = + CreateResourceImage(PP_RESOURCEIMAGE_PDF_PAN_SCROLL_ICON); + + ConfigurePageIndicator(); + ConfigureProgressBar(); + + pp::Point scroll_position = engine_->GetScrollPosition(); + ScalePoint(device_scale_ / old_device_scale, &scroll_position); + engine_->SetScrollPosition(scroll_position); + } + + UpdateZoomScale(); + if (zoom_ != old_zoom || device_scale_ != old_device_scale) + engine_->ZoomUpdated(zoom_ * device_scale_); + if (zoom_ != old_zoom) + ZoomChanged(zoom_); + + available_area_ = pp::Rect(plugin_size_); + if (GetDocumentPixelHeight() > plugin_size_.height()) { + CreateVerticalScrollbar(); + } else { + DestroyVerticalScrollbar(); + } + + int v_scrollbar_reserved_thickness = + v_scrollbar_.get() ? GetScaled(GetScrollbarReservedThickness()) : 0; + + if (!force_no_horizontal_scrollbar && + GetDocumentPixelWidth() > + (plugin_size_.width() - v_scrollbar_reserved_thickness)) { + CreateHorizontalScrollbar(); + + // Adding the horizontal scrollbar now might cause us to need vertical + // scrollbars. + if (GetDocumentPixelHeight() > + plugin_size_.height() - GetScaled(GetScrollbarReservedThickness())) { + CreateVerticalScrollbar(); + } + + } else { + DestroyHorizontalScrollbar(); + } + +#ifdef ENABLE_THUMBNAILS + int thumbnails_pos = 0, thumbnails_total = 0; +#endif + if (v_scrollbar_.get()) { + v_scrollbar_->SetScale(device_scale_); + available_area_.set_width( + std::max(0, plugin_size_.width() - v_scrollbar_reserved_thickness)); + +#ifdef ENABLE_THUMBNAILS + int height = plugin_size_.height(); +#endif + int height_dip = plugin_dip_size_.height(); + +#if defined(OS_MACOSX) + // Before Lion, Mac always had the resize at the bottom. After that, it + // never did. + if ((base::mac::IsOSSnowLeopard() && full_) || + (base::mac::IsOSLionOrLater() && h_scrollbar_.get())) { +#else + if (h_scrollbar_.get()) { +#endif // defined(OS_MACOSX) +#ifdef ENABLE_THUMBNAILS + height -= GetScaled(GetScrollbarThickness()); +#endif + height_dip -= GetScrollbarThickness(); + } +#ifdef ENABLE_THUMBNAILS + int32 doc_height = GetDocumentPixelHeight(); +#endif + int32 doc_height_dip = + static_cast<int32>(GetDocumentPixelHeight() / device_scale_); +#if defined(OS_MACOSX) + // On the Mac we always allow room for the resize button (whose width is + // the same as that of the scrollbar) in full mode. However, if there is no + // no horizontal scrollbar, the end of the scrollbar will scroll past the + // end of the document. This is because the scrollbar assumes that its own + // height (in the case of a vscroll bar) is the same as the height of the + // viewport. Since the viewport is actually larger, we compensate by + // adjusting the document height. Similar logic applies below for the + // horizontal scrollbar. + // For example, if the document size is 1000, and the viewport size is 200, + // then the scrollbar position at the end will be 800. In this case the + // viewport is actally 215 (assuming 15 as the scrollbar width) but the + // scrollbar thinks it is 200. We want the scrollbar position at the end to + // be 785. Making the document size 985 achieves this. + if (full_ && !h_scrollbar_.get()) { +#ifdef ENABLE_THUMBNAILS + doc_height -= GetScaled(GetScrollbarThickness()); +#endif + doc_height_dip -= GetScrollbarThickness(); + } +#endif // defined(OS_MACOSX) + + int32 position; + position = v_scrollbar_->GetValue(); + position = static_cast<int>(position * zoom_ / old_zoom); + valid_v_range_ = doc_height_dip - height_dip; + if (position > valid_v_range_) + position = valid_v_range_; + + v_scrollbar_->SetValue(position); + + PP_Rect loc; + loc.point.x = static_cast<int>(available_area_.right() / device_scale_); + if (IsOverlayScrollbar()) + loc.point.x -= scrollbar_thickness; + loc.point.y = 0; + loc.size.width = scrollbar_thickness; + loc.size.height = height_dip; + v_scrollbar_->SetLocation(loc); + v_scrollbar_->SetDocumentSize(doc_height_dip); + +#ifdef ENABLE_THUMBNAILS + thumbnails_pos = position; + thumbnails_total = doc_height - height; +#endif + } + + if (h_scrollbar_.get()) { + h_scrollbar_->SetScale(device_scale_); + available_area_.set_height( + std::max(0, plugin_size_.height() - + GetScaled(GetScrollbarReservedThickness()))); + + int width_dip = plugin_dip_size_.width(); + + // See note above. +#if defined(OS_MACOSX) + if ((base::mac::IsOSSnowLeopard() && full_) || + (base::mac::IsOSLionOrLater() && v_scrollbar_.get())) { +#else + if (v_scrollbar_.get()) { +#endif + width_dip -= GetScrollbarThickness(); + } + int32 doc_width_dip = + static_cast<int32>(GetDocumentPixelWidth() / device_scale_); +#if defined(OS_MACOSX) + // See comment in the above if (v_scrollbar_.get()) block. + if (full_ && !v_scrollbar_.get()) + doc_width_dip -= GetScrollbarThickness(); +#endif // defined(OS_MACOSX) + + int32 position; + position = h_scrollbar_->GetValue(); + position = static_cast<int>(position * zoom_ / old_zoom); + position = std::min(position, doc_width_dip - width_dip); + + h_scrollbar_->SetValue(position); + + PP_Rect loc; + loc.point.x = 0; + loc.point.y = static_cast<int>(available_area_.bottom() / device_scale_); + if (IsOverlayScrollbar()) + loc.point.y -= scrollbar_thickness; + loc.size.width = width_dip; + loc.size.height = scrollbar_thickness; + h_scrollbar_->SetLocation(loc); + h_scrollbar_->SetDocumentSize(doc_width_dip); + } + + int doc_width = GetDocumentPixelWidth(); + if (doc_width < available_area_.width()) { + available_area_.Offset((available_area_.width() - doc_width) / 2, 0); + available_area_.set_width(doc_width); + } + int doc_height = GetDocumentPixelHeight(); + if (doc_height < available_area_.height()) { + available_area_.set_height(doc_height); + } + + // We'll invalidate the entire plugin anyways. + UpdateToolbarPosition(false); + UpdateProgressBarPosition(false); + UpdatePageIndicatorPosition(false); + +#ifdef ENABLE_THUMBNAILS + // Update thumbnail control position. + thumbnails_.SetPosition(thumbnails_pos, thumbnails_total, false); + pp::Rect thumbnails_rc(plugin_size_.width() - GetScaled(kThumbnailsWidth), 0, + GetScaled(kThumbnailsWidth), plugin_size_.height()); + if (v_scrollbar_.get()) + thumbnails_rc.Offset(-v_scrollbar_reserved_thickness, 0); + if (h_scrollbar_.get()) + thumbnails_rc.Inset(0, 0, 0, v_scrollbar_reserved_thickness); + thumbnails_.SetRect(thumbnails_rc, false); +#endif + + CalculateBackgroundParts(); + engine_->PageOffsetUpdated(available_area_.point()); + engine_->PluginSizeUpdated(available_area_.size()); + + if (!document_size_.GetArea()) + return; + paint_manager_.InvalidateRect(pp::Rect(pp::Point(), plugin_size_)); + + // Run the plugin size change callback asynchronously. This function can be + // invoked by a layout change which should not re-enter into JS synchronously. + pp::CompletionCallback callback = + callback_factory_.NewCallback(&Instance::RunCallback, + on_plugin_size_changed_callback_); + pp::Module::Get()->core()->CallOnMainThread(0, callback); +} + +void Instance::RunCallback(int32_t, pp::Var callback) { + if (callback.is_string()) + ExecuteScript(callback); +} + +void Instance::CreateHorizontalScrollbar() { + if (h_scrollbar_.get()) + return; + + h_scrollbar_.reset(new pp::Scrollbar_Dev(this, false)); +} + +void Instance::CreateVerticalScrollbar() { + if (v_scrollbar_.get()) + return; + + v_scrollbar_.reset(new pp::Scrollbar_Dev(this, true)); +} + +void Instance::DestroyHorizontalScrollbar() { + if (!h_scrollbar_.get()) + return; + if (h_scrollbar_->GetValue()) + engine_->ScrolledToXPosition(0); + h_scrollbar_.reset(); +} + +void Instance::DestroyVerticalScrollbar() { + if (!v_scrollbar_.get()) + return; + if (v_scrollbar_->GetValue()) + engine_->ScrolledToYPosition(0); + v_scrollbar_.reset(); + page_indicator_.Show(false, true); +} + +int Instance::GetScrollbarThickness() { + if (scrollbar_thickness_ == -1) { + pp::Scrollbar_Dev temp_scrollbar(this, false); + scrollbar_thickness_ = temp_scrollbar.GetThickness(); + scrollbar_reserved_thickness_ = + temp_scrollbar.IsOverlay() ? 0 : scrollbar_thickness_; + } + + return scrollbar_thickness_; +} + +int Instance::GetScrollbarReservedThickness() { + GetScrollbarThickness(); + return scrollbar_reserved_thickness_; +} + +bool Instance::IsOverlayScrollbar() { + return GetScrollbarReservedThickness() == 0; +} + +void Instance::CreateToolbar(const ToolbarButtonInfo* tb_info, size_t size) { + toolbar_.reset(new FadingControls()); + + DCHECK(tb_info); + DCHECK(size >= 0); + + // Remember the current toolbar information in case we need to recreate the + // images later. + current_tb_info_ = tb_info; + current_tb_info_size_ = size; + + int max_height = 0; + pp::Point origin(kToolbarFadingOffsetLeft, kToolbarFadingOffsetTop); + ScalePoint(device_scale_, &origin); + + std::list<Button*> buttons; + for (size_t i = 0; i < size; i++) { + Button* btn = new Button; + pp::ImageData normal_face = + CreateResourceImage(tb_info[i].normal); + btn->CreateButton(tb_info[i].id, + origin, + true, + toolbar_.get(), + tb_info[i].style, + normal_face, + CreateResourceImage(tb_info[i].highlighted), + CreateResourceImage(tb_info[i].pressed)); + buttons.push_back(btn); + + origin += pp::Point(normal_face.size().width(), 0); + max_height = std::max(max_height, normal_face.size().height()); + } + + pp::Rect rc_toolbar(0, 0, + origin.x() + GetToolbarRightOffset(), + origin.y() + max_height + GetToolbarBottomOffset()); + toolbar_->CreateFadingControls( + kToolbarId, rc_toolbar, false, this, kTransparentAlpha); + + std::list<Button*>::iterator iter; + for (iter = buttons.begin(); iter != buttons.end(); ++iter) { + toolbar_->AddControl(*iter); + } +} + +int Instance::GetToolbarRightOffset() { + int scrollbar_thickness = GetScrollbarThickness(); + return GetScaled(kToolbarFadingOffsetRight) + 2 * scrollbar_thickness; +} + +int Instance::GetToolbarBottomOffset() { + int scrollbar_thickness = GetScrollbarThickness(); + return GetScaled(kToolbarFadingOffsetBottom) + scrollbar_thickness; +} + +std::vector<pp::ImageData> Instance::GetThumbnailResources() { + std::vector<pp::ImageData> num_images(10); + num_images[0] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_0); + num_images[1] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_1); + num_images[2] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_2); + num_images[3] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_3); + num_images[4] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_4); + num_images[5] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_5); + num_images[6] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_6); + num_images[7] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_7); + num_images[8] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_8); + num_images[9] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_9); + return num_images; +} + +std::vector<pp::ImageData> Instance::GetProgressBarResources( + pp::ImageData* background) { + std::vector<pp::ImageData> result(9); + result[0] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_0); + result[1] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_1); + result[2] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_2); + result[3] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_3); + result[4] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_4); + result[5] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_5); + result[6] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_6); + result[7] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_7); + result[8] = CreateResourceImage(PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_8); + *background = CreateResourceImage( + PP_RESOURCEIMAGE_PDF_PROGRESS_BAR_BACKGROUND); + return result; +} + +void Instance::CreatePageIndicator(bool always_visible) { + page_indicator_.CreatePageIndicator(kPageIndicatorId, false, this, + number_image_generator(), always_visible); + ConfigurePageIndicator(); +} + +void Instance::ConfigurePageIndicator() { + pp::ImageData background = + CreateResourceImage(PP_RESOURCEIMAGE_PDF_PAGE_INDICATOR_BACKGROUND); + page_indicator_.Configure(pp::Point(), background); +} + +void Instance::CreateProgressBar() { + pp::ImageData background; + std::vector<pp::ImageData> images = GetProgressBarResources(&background); + std::string text = GetLocalizedString(PP_RESOURCESTRING_PDFPROGRESSLOADING); + progress_bar_.CreateProgressControl(kProgressBarId, false, this, 0.0, + device_scale_, images, background, text); +} + +void Instance::ConfigureProgressBar() { + pp::ImageData background; + std::vector<pp::ImageData> images = GetProgressBarResources(&background); + progress_bar_.Reconfigure(background, images, device_scale_); +} + +void Instance::CreateThumbnails() { + thumbnails_.CreateThumbnailControl( + kThumbnailsId, pp::Rect(), false, this, engine_.get(), + number_image_generator()); +} + +void Instance::LoadUrl(const std::string& url) { + LoadUrlInternal(url, &embed_loader_, &Instance::DidOpen); +} + +void Instance::LoadPreviewUrl(const std::string& url) { + LoadUrlInternal(url, &embed_preview_loader_, &Instance::DidOpenPreview); +} + +void Instance::LoadUrlInternal(const std::string& url, pp::URLLoader* loader, + void (Instance::* method)(int32_t)) { + pp::URLRequestInfo request(this); + request.SetURL(url); + request.SetMethod("GET"); + + *loader = CreateURLLoaderInternal(); + pp::CompletionCallback callback = loader_factory_.NewCallback(method); + int rv = loader->Open(request, callback); + if (rv != PP_OK_COMPLETIONPENDING) + callback.Run(rv); +} + +pp::URLLoader Instance::CreateURLLoaderInternal() { + pp::URLLoader loader(this); + + const PPB_URLLoaderTrusted* trusted_interface = + reinterpret_cast<const PPB_URLLoaderTrusted*>( + pp::Module::Get()->GetBrowserInterface( + PPB_URLLOADERTRUSTED_INTERFACE)); + if (trusted_interface) + trusted_interface->GrantUniversalAccess(loader.pp_resource()); + return loader; +} + +int Instance::GetInitialPage(const std::string& url) { + size_t found_idx = url.find('#'); + if (found_idx == std::string::npos) + return -1; + + const std::string& ref = url.substr(found_idx + 1); + std::vector<std::string> fragments; + Tokenize(ref, kDelimiters, &fragments); + + // Page number to return, zero-based. + int page = -1; + + // Handle the case of http://foo.com/bar#NAMEDDEST. This is not explicitly + // mentioned except by example in the Adobe "PDF Open Parameters" document. + if ((fragments.size() == 1) && (fragments[0].find('=') == std::string::npos)) + return engine_->GetNamedDestinationPage(fragments[0]); + + for (size_t i = 0; i < fragments.size(); ++i) { + std::vector<std::string> key_value; + base::SplitString(fragments[i], '=', &key_value); + if (key_value.size() != 2) + continue; + const std::string& key = key_value[0]; + const std::string& value = key_value[1]; + + if (base::strcasecmp(kPage, key.c_str()) == 0) { + // |page_value| is 1-based. + int page_value = -1; + if (base::StringToInt(value, &page_value) && page_value > 0) + page = page_value - 1; + continue; + } + if (base::strcasecmp(kNamedDest, key.c_str()) == 0) { + // |page_value| is 0-based. + int page_value = engine_->GetNamedDestinationPage(value); + if (page_value >= 0) + page = page_value; + continue; + } + } + return page; +} + +void Instance::UpdateToolbarPosition(bool invalidate) { + pp::Rect ctrl_rc = toolbar_->GetControlsRect(); + int min_toolbar_width = ctrl_rc.width() + GetToolbarRightOffset() + + GetScaled(kToolbarFadingOffsetLeft); + int min_toolbar_height = ctrl_rc.width() + GetToolbarBottomOffset() + + GetScaled(kToolbarFadingOffsetBottom); + + // Update toolbar position + if (plugin_size_.width() < min_toolbar_width || + plugin_size_.height() < min_toolbar_height) { + // Disable toolbar if it does not fit on the screen. + toolbar_->Show(false, invalidate); + } else { + pp::Point offset( + plugin_size_.width() - GetToolbarRightOffset() - ctrl_rc.right(), + plugin_size_.height() - GetToolbarBottomOffset() - ctrl_rc.bottom()); + toolbar_->MoveBy(offset, invalidate); + + int toolbar_width = std::max(plugin_size_.width() / 2, min_toolbar_width); + toolbar_->ExpandLeft(toolbar_width - toolbar_->rect().width()); + toolbar_->Show(painted_first_page_, invalidate); + } +} + +void Instance::UpdateProgressBarPosition(bool invalidate) { + // TODO(gene): verify we don't overlap with toolbar. + int scrollbar_thickness = GetScrollbarThickness(); + pp::Point progress_origin( + scrollbar_thickness + GetScaled(kProgressOffsetLeft), + plugin_size_.height() - progress_bar_.rect().height() - + scrollbar_thickness - GetScaled(kProgressOffsetBottom)); + progress_bar_.MoveTo(progress_origin, invalidate); +} + +void Instance::UpdatePageIndicatorPosition(bool invalidate) { + int32 doc_height = static_cast<int>(document_size_.height() * zoom_); + pp::Point origin( + plugin_size_.width() - page_indicator_.rect().width() - + GetScaled(GetScrollbarReservedThickness()), + page_indicator_.GetYPosition(engine_->GetVerticalScrollbarYPosition(), + doc_height, plugin_size_.height())); + page_indicator_.MoveTo(origin, invalidate); +} + +void Instance::SetZoom(ZoomMode zoom_mode, double scale) { + double old_zoom = zoom_; + + zoom_mode_ = zoom_mode; + if (zoom_mode_ == ZOOM_SCALE) + zoom_ = scale; + UpdateZoomScale(); + + engine_->ZoomUpdated(zoom_ * device_scale_); + OnGeometryChanged(old_zoom, device_scale_); + + // If fit-to-height, snap to the beginning of the most visible page. + if (zoom_mode_ == ZOOM_FIT_TO_PAGE) { + ScrollToPage(engine_->GetMostVisiblePage()); + } + + // Update sticky buttons to the current zoom style. + Button* ftp_btn = static_cast<Button*>( + toolbar_->GetControl(kFitToPageButtonId)); + Button* ftw_btn = static_cast<Button*>( + toolbar_->GetControl(kFitToWidthButtonId)); + switch (zoom_mode_) { + case ZOOM_FIT_TO_PAGE: + ftp_btn->SetPressedState(true); + ftw_btn->SetPressedState(false); + break; + case ZOOM_FIT_TO_WIDTH: + ftw_btn->SetPressedState(true); + ftp_btn->SetPressedState(false); + break; + default: + ftw_btn->SetPressedState(false); + ftp_btn->SetPressedState(false); + } +} + +void Instance::UpdateZoomScale() { + switch (zoom_mode_) { + case ZOOM_SCALE: + break; // Keep current scale. + case ZOOM_FIT_TO_PAGE: { + int page_num = engine_->GetFirstVisiblePage(); + if (page_num == -1) + break; + pp::Rect rc = engine_->GetPageRect(page_num); + if (!rc.height()) + break; + // Calculate fit to width zoom level. + double ftw_zoom = static_cast<double>(plugin_dip_size_.width() - + GetScrollbarReservedThickness()) / document_size_.width(); + // Calculate fit to height zoom level. If document will not fit + // horizontally, adjust zoom level to allow space for horizontal + // scrollbar. + double fth_zoom = + static_cast<double>(plugin_dip_size_.height()) / rc.height(); + if (fth_zoom * document_size_.width() > + plugin_dip_size_.width() - GetScrollbarReservedThickness()) + fth_zoom = static_cast<double>(plugin_dip_size_.height() + - GetScrollbarReservedThickness()) / rc.height(); + zoom_ = std::min(ftw_zoom, fth_zoom); + } break; + case ZOOM_FIT_TO_WIDTH: + case ZOOM_AUTO: + if (!document_size_.width()) + break; + zoom_ = static_cast<double>(plugin_dip_size_.width() - + GetScrollbarReservedThickness()) / document_size_.width(); + if (zoom_mode_ == ZOOM_AUTO && zoom_ > 1.0) + zoom_ = 1.0; + break; + } + zoom_ = ClipToRange(zoom_, kMinZoom, kMaxZoom); +} + +double Instance::CalculateZoom(uint32 control_id) const { + if (control_id == kZoomInButtonId) { + for (size_t i = 0; i < chrome_page_zoom::kPresetZoomFactorsSize; ++i) { + double current_zoom = chrome_page_zoom::kPresetZoomFactors[i]; + if (current_zoom - content::kEpsilon > zoom_) + return current_zoom; + } + } else { + for (size_t i = chrome_page_zoom::kPresetZoomFactorsSize; i > 0; --i) { + double current_zoom = chrome_page_zoom::kPresetZoomFactors[i - 1]; + if (current_zoom + content::kEpsilon < zoom_) + return current_zoom; + } + } + return zoom_; +} + +pp::ImageData Instance::CreateResourceImage(PP_ResourceImage image_id) { + pp::ImageData resource_data; + if (hidpi_enabled_) { + resource_data = + pp::PDF::GetResourceImageForScale(this, image_id, device_scale_); + } + + return resource_data.data() ? resource_data + : pp::PDF::GetResourceImage(this, image_id); +} + +std::string Instance::GetLocalizedString(PP_ResourceString id) { + pp::Var rv(pp::PDF::GetLocalizedString(this, id)); + if (!rv.is_string()) + return std::string(); + + return rv.AsString(); +} + +void Instance::DrawText(const pp::Point& top_center, PP_ResourceString id) { + std::string str(GetLocalizedString(id)); + + pp::FontDescription_Dev description; + description.set_family(PP_FONTFAMILY_SANSSERIF); + description.set_size(kMessageTextSize * device_scale_); + pp::Font_Dev font(this, description); + int length = font.MeasureSimpleText(str); + pp::Point point(top_center); + point.set_x(point.x() - length / 2); + DCHECK(!image_data_.is_null()); + font.DrawSimpleText(&image_data_, str, point, kMessageTextColor); +} + +void Instance::SetPrintPreviewMode(int page_count) { + if (!IsPrintPreview() || page_count <= 0) { + print_preview_page_count_ = 0; + return; + } + + print_preview_page_count_ = page_count; + ScrollToPage(0); + engine_->AppendBlankPages(print_preview_page_count_); + if (preview_pages_info_.size() > 0) + LoadAvailablePreviewPage(); +} + +bool Instance::IsPrintPreview() { + return IsPrintPreviewUrl(url_); +} + +int Instance::GetPageNumberToDisplay() { + int page = engine_->GetMostVisiblePage(); + if (IsPrintPreview() && !print_preview_page_numbers_.empty()) { + page = ClipToRange<int>(page, 0, print_preview_page_numbers_.size() - 1); + return print_preview_page_numbers_[page]; + } + return page + 1; +} + +void Instance::ProcessPreviewPageInfo(const std::string& url, + int dst_page_index) { + if (!IsPrintPreview() || print_preview_page_count_ < 0) + return; + + int src_page_index = ExtractPrintPreviewPageIndex(url); + if (src_page_index < 1) + return; + + preview_pages_info_.push(std::make_pair(url, dst_page_index)); + LoadAvailablePreviewPage(); +} + +void Instance::LoadAvailablePreviewPage() { + if (preview_pages_info_.size() <= 0) + return; + + std::string url = preview_pages_info_.front().first; + int dst_page_index = preview_pages_info_.front().second; + int src_page_index = ExtractPrintPreviewPageIndex(url); + if (src_page_index < 1 || + dst_page_index >= print_preview_page_count_ || + preview_document_load_state_ == LOAD_STATE_LOADING) { + return; + } + + preview_document_load_state_ = LOAD_STATE_LOADING; + LoadPreviewUrl(url); +} + +void Instance::EnableAutoscroll(const pp::Point& origin) { + if (is_autoscroll_) + return; + + pp::Size client_size = plugin_size_; + if (v_scrollbar_.get()) + client_size.Enlarge(-GetScrollbarThickness(), 0); + if (h_scrollbar_.get()) + client_size.Enlarge(0, -GetScrollbarThickness()); + + // Do not allow autoscroll if client area is too small. + if (autoscroll_anchor_.size().width() > client_size.width() || + autoscroll_anchor_.size().height() > client_size.height()) + return; + + autoscroll_rect_ = pp::Rect( + pp::Point(origin.x() - autoscroll_anchor_.size().width() / 2, + origin.y() - autoscroll_anchor_.size().height() / 2), + autoscroll_anchor_.size()); + + // Make sure autoscroll anchor is in the client area. + if (autoscroll_rect_.right() > client_size.width()) { + autoscroll_rect_.set_x( + client_size.width() - autoscroll_anchor_.size().width()); + } + if (autoscroll_rect_.bottom() > client_size.height()) { + autoscroll_rect_.set_y( + client_size.height() - autoscroll_anchor_.size().height()); + } + + if (autoscroll_rect_.x() < 0) + autoscroll_rect_.set_x(0); + if (autoscroll_rect_.y() < 0) + autoscroll_rect_.set_y(0); + + is_autoscroll_ = true; + Invalidate(kAutoScrollId, autoscroll_rect_); + + ScheduleTimer(kAutoScrollId, kAutoScrollTimeoutMs); +} + +void Instance::DisableAutoscroll() { + if (is_autoscroll_) { + is_autoscroll_ = false; + Invalidate(kAutoScrollId, autoscroll_rect_); + } +} + +PP_CursorType_Dev Instance::CalculateAutoscroll(const pp::Point& mouse_pos) { + // Scroll only if mouse pointer is outside of the anchor area. + if (autoscroll_rect_.Contains(mouse_pos)) { + autoscroll_x_ = 0; + autoscroll_y_ = 0; + return PP_CURSORTYPE_MIDDLEPANNING; + } + + // Relative position to the center of anchor area. + pp::Point rel_pos = mouse_pos - autoscroll_rect_.CenterPoint(); + + // Calculate angle from the X axis. Angle is in range from -pi to pi. + double angle = atan2(static_cast<double>(rel_pos.y()), + static_cast<double>(rel_pos.x())); + + autoscroll_x_ = rel_pos.x() * kAutoScrollFactor; + autoscroll_y_ = rel_pos.y() * kAutoScrollFactor; + + // Angle is from -pi to pi. Screen Y is increasing toward bottom, + // so negative angle represent north direction. + if (angle < - (M_PI * 7.0 / 8.0)) { + // going west + return PP_CURSORTYPE_WESTPANNING; + } else if (angle < - (M_PI * 5.0 / 8.0)) { + // going north-west + return PP_CURSORTYPE_NORTHWESTPANNING; + } else if (angle < - (M_PI * 3.0 / 8.0)) { + // going north. + return PP_CURSORTYPE_NORTHPANNING; + } else if (angle < - (M_PI * 1.0 / 8.0)) { + // going north-east + return PP_CURSORTYPE_NORTHEASTPANNING; + } else if (angle < M_PI * 1.0 / 8.0) { + // going east. + return PP_CURSORTYPE_EASTPANNING; + } else if (angle < M_PI * 3.0 / 8.0) { + // going south-east + return PP_CURSORTYPE_SOUTHEASTPANNING; + } else if (angle < M_PI * 5.0 / 8.0) { + // going south. + return PP_CURSORTYPE_SOUTHPANNING; + } else if (angle < M_PI * 7.0 / 8.0) { + // going south-west + return PP_CURSORTYPE_SOUTHWESTPANNING; + } + + // went around the circle, going west again + return PP_CURSORTYPE_WESTPANNING; +} + +void Instance::ConfigureNumberImageGenerator() { + std::vector<pp::ImageData> num_images = GetThumbnailResources(); + pp::ImageData number_background = CreateResourceImage( + PP_RESOURCEIMAGE_PDF_BUTTON_THUMBNAIL_NUM_BACKGROUND); + number_image_generator_->Configure(number_background, + num_images, + device_scale_); +} + +NumberImageGenerator* Instance::number_image_generator() { + if (!number_image_generator_.get()) { + number_image_generator_.reset(new NumberImageGenerator(this)); + ConfigureNumberImageGenerator(); + } + return number_image_generator_.get(); +} + +int Instance::GetScaled(int x) const { + return static_cast<int>(x * device_scale_); +} + +void Instance::UserMetricsRecordAction(const std::string& action) { + pp::PDF::UserMetricsRecordAction(this, pp::Var(action)); +} + +PDFScriptableObject::PDFScriptableObject(Instance* instance) + : instance_(instance) { +} + +PDFScriptableObject::~PDFScriptableObject() { +} + +bool PDFScriptableObject::HasMethod(const pp::Var& name, pp::Var* exception) { + return instance_->HasScriptableMethod(name, exception); +} + +pp::Var PDFScriptableObject::Call(const pp::Var& method, + const std::vector<pp::Var>& args, + pp::Var* exception) { + return instance_->CallScriptableMethod(method, args, exception); +} + +} // namespace chrome_pdf |