1 // Copyright (c) 2012 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.
4 
5 #include "pdf/pdfium/pdfium_engine.h"
6 
7 #include <math.h>
8 #include <stddef.h>
9 #include <stdint.h>
10 
11 #include <algorithm>
12 #include <limits>
13 #include <memory>
14 #include <set>
15 #include <utility>
16 
17 #include "base/auto_reset.h"
18 #include "base/bind.h"
19 #include "base/logging.h"
20 #include "base/stl_util.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/threading/thread_task_runner_handle.h"
25 #include "build/build_config.h"
26 #include "gin/array_buffer.h"
27 #include "gin/public/gin_embedders.h"
28 #include "gin/public/isolate_holder.h"
29 #include "pdf/document_loader_impl.h"
30 #include "pdf/draw_utils/coordinates.h"
31 #include "pdf/draw_utils/shadow.h"
32 #include "pdf/pdf_transform.h"
33 #include "pdf/pdfium/pdfium_api_string_buffer_adapter.h"
34 #include "pdf/pdfium/pdfium_document.h"
35 #include "pdf/pdfium/pdfium_mem_buffer_file_read.h"
36 #include "pdf/pdfium/pdfium_mem_buffer_file_write.h"
37 #include "pdf/pdfium/pdfium_permissions.h"
38 #include "pdf/pdfium/pdfium_unsupported_features.h"
39 #include "pdf/url_loader_wrapper_impl.h"
40 #include "ppapi/cpp/instance.h"
41 #include "ppapi/cpp/private/pdf.h"
42 #include "ppapi/cpp/var_dictionary.h"
43 #include "printing/units.h"
44 #include "third_party/pdfium/public/cpp/fpdf_scopers.h"
45 #include "third_party/pdfium/public/fpdf_annot.h"
46 #include "third_party/pdfium/public/fpdf_attachment.h"
47 #include "third_party/pdfium/public/fpdf_catalog.h"
48 #include "third_party/pdfium/public/fpdf_ext.h"
49 #include "third_party/pdfium/public/fpdf_ppo.h"
50 #include "third_party/pdfium/public/fpdf_searchex.h"
51 #include "ui/events/keycodes/keyboard_codes.h"
52 #include "ui/gfx/geometry/rect.h"
53 #include "v8/include/v8.h"
54 
55 #if defined(OS_LINUX) || defined(OS_BSD)
56 #include "pdf/pdfium/pdfium_font_linux.h"
57 #endif
58 
59 using printing::ConvertUnit;
60 using printing::ConvertUnitDouble;
61 using printing::kPointsPerInch;
62 using printing::kPixelsPerInch;
63 
64 namespace chrome_pdf {
65 
66 static_assert(static_cast<int>(PDFEngine::FormType::kNone) == FORMTYPE_NONE,
67               "None form types must match");
68 static_assert(static_cast<int>(PDFEngine::FormType::kAcroForm) ==
69                   FORMTYPE_ACRO_FORM,
70               "AcroForm form types must match");
71 static_assert(static_cast<int>(PDFEngine::FormType::kXFAFull) ==
72                   FORMTYPE_XFA_FULL,
73               "XFA full form types must match");
74 static_assert(static_cast<int>(PDFEngine::FormType::kXFAForeground) ==
75                   FORMTYPE_XFA_FOREGROUND,
76               "XFA foreground form types must match");
77 static_assert(static_cast<int>(PDFEngine::FormType::kCount) == FORMTYPE_COUNT,
78               "Form type counts must match");
79 
80 namespace {
81 
82 constexpr int32_t kHighlightColorR = 153;
83 constexpr int32_t kHighlightColorG = 193;
84 constexpr int32_t kHighlightColorB = 218;
85 
86 constexpr uint32_t kPendingPageColor = 0xFFEEEEEE;
87 
88 constexpr uint32_t kFormHighlightColor = 0xFFE4DD;
89 constexpr int32_t kFormHighlightAlpha = 100;
90 
91 constexpr int kMaxPasswordTries = 3;
92 
93 constexpr base::TimeDelta kTouchLongPressTimeout =
94     base::TimeDelta::FromMilliseconds(300);
95 
96 // Windows has native panning capabilities. No need to use our own.
97 #if defined(OS_WIN)
98 constexpr bool kViewerImplementedPanning = false;
99 #else
100 constexpr bool kViewerImplementedPanning = true;
101 #endif
102 
103 constexpr int32_t kLoadingTextVerticalOffset = 50;
104 
105 // The maximum amount of time we'll spend doing a paint before we give back
106 // control of the thread.
107 constexpr base::TimeDelta kMaxProgressivePaintTime =
108     base::TimeDelta::FromMilliseconds(300);
109 
110 // The maximum amount of time we'll spend doing the first paint. This is less
111 // than the above to keep things smooth if the user is scrolling quickly. This
112 // is set to 250 ms to give enough time for most PDFs to render, while avoiding
113 // adding too much latency to the display of the final image when the user
114 // stops scrolling.
115 // Setting a higher value has minimal benefit (scrolling at less than 4 fps will
116 // never be a great experience) and there is some cost, since when the user
117 // stops scrolling the in-progress painting has to complete or timeout before
118 // the final painting can start.
119 // The scrollbar will always be responsive since it is managed by a separate
120 // process.
121 constexpr base::TimeDelta kMaxInitialProgressivePaintTime =
122     base::TimeDelta::FromMilliseconds(250);
123 
124 template <class S>
IsAboveOrDirectlyLeftOf(const S & lhs,const S & rhs)125 bool IsAboveOrDirectlyLeftOf(const S& lhs, const S& rhs) {
126   return lhs.y() < rhs.y() || (lhs.y() == rhs.y() && lhs.x() < rhs.x());
127 }
128 
CalculateCenterForZoom(int center,int length,double zoom)129 int CalculateCenterForZoom(int center, int length, double zoom) {
130   int adjusted_center =
131       static_cast<int>(center * zoom) - static_cast<int>(length * zoom / 2);
132   return std::max(adjusted_center, 0);
133 }
134 
135 // This formats a string with special 0xfffe end-of-line hyphens the same way
136 // as Adobe Reader. When a hyphen is encountered, the next non-CR/LF whitespace
137 // becomes CR+LF and the hyphen is erased. If there is no whitespace between
138 // two hyphens, the latter hyphen is erased and ignored.
FormatStringWithHyphens(base::string16 * text)139 void FormatStringWithHyphens(base::string16* text) {
140   // First pass marks all the hyphen positions.
141   struct HyphenPosition {
142     HyphenPosition() : position(0), next_whitespace_position(0) {}
143     size_t position;
144     size_t next_whitespace_position;  // 0 for none
145   };
146   std::vector<HyphenPosition> hyphen_positions;
147   HyphenPosition current_hyphen_position;
148   bool current_hyphen_position_is_valid = false;
149   constexpr base::char16 kPdfiumHyphenEOL = 0xfffe;
150 
151   for (size_t i = 0; i < text->size(); ++i) {
152     const base::char16& current_char = (*text)[i];
153     if (current_char == kPdfiumHyphenEOL) {
154       if (current_hyphen_position_is_valid)
155         hyphen_positions.push_back(current_hyphen_position);
156       current_hyphen_position = HyphenPosition();
157       current_hyphen_position.position = i;
158       current_hyphen_position_is_valid = true;
159     } else if (base::IsUnicodeWhitespace(current_char)) {
160       if (current_hyphen_position_is_valid) {
161         if (current_char != L'\r' && current_char != L'\n')
162           current_hyphen_position.next_whitespace_position = i;
163         hyphen_positions.push_back(current_hyphen_position);
164         current_hyphen_position_is_valid = false;
165       }
166     }
167   }
168   if (current_hyphen_position_is_valid)
169     hyphen_positions.push_back(current_hyphen_position);
170 
171   // With all the hyphen positions, do the search and replace.
172   while (!hyphen_positions.empty()) {
173     static constexpr base::char16 kCr[] = {L'\r', L'\0'};
174     const HyphenPosition& position = hyphen_positions.back();
175     if (position.next_whitespace_position != 0) {
176       (*text)[position.next_whitespace_position] = L'\n';
177       text->insert(position.next_whitespace_position, kCr);
178     }
179     text->erase(position.position, 1);
180     hyphen_positions.pop_back();
181   }
182 
183   // Adobe Reader also get rid of trailing spaces right before a CRLF.
184   static constexpr base::char16 kSpaceCrCn[] = {L' ', L'\r', L'\n', L'\0'};
185   static constexpr base::char16 kCrCn[] = {L'\r', L'\n', L'\0'};
186   base::ReplaceSubstringsAfterOffset(text, 0, kSpaceCrCn, kCrCn);
187 }
188 
189 // Replace CR/LF with just LF on POSIX.
FormatStringForOS(base::string16 * text)190 void FormatStringForOS(base::string16* text) {
191 #if defined(OS_POSIX)
192   static constexpr base::char16 kCr[] = {L'\r', L'\0'};
193   static constexpr base::char16 kBlank[] = {L'\0'};
194   base::ReplaceChars(*text, kCr, kBlank, text);
195 #elif defined(OS_WIN)
196   // Do nothing
197 #else
198   NOTIMPLEMENTED();
199 #endif
200 }
201 
202 // Returns true if |cur| is a character to break on.
203 // For double clicks, look for work breaks.
204 // For triple clicks, look for line breaks.
205 // The actual algorithm used in Blink is much more complicated, so do a simple
206 // approximation.
FindMultipleClickBoundary(bool is_double_click,base::char16 cur)207 bool FindMultipleClickBoundary(bool is_double_click, base::char16 cur) {
208   if (!is_double_click)
209     return cur == '\n';
210 
211   // Deal with ASCII characters.
212   if (base::IsAsciiAlpha(cur) || base::IsAsciiDigit(cur) || cur == '_')
213     return false;
214   if (cur < 128)
215     return true;
216 
217   if (cur == kZeroWidthSpace)
218     return true;
219 
220   return false;
221 }
222 
223 gin::IsolateHolder* g_isolate_holder = nullptr;
224 
IsV8Initialized()225 bool IsV8Initialized() {
226   return !!g_isolate_holder;
227 }
228 
SetUpV8()229 void SetUpV8() {
230   const char* recommended = FPDF_GetRecommendedV8Flags();
231   v8::V8::SetFlagsFromString(recommended, strlen(recommended));
232   gin::IsolateHolder::Initialize(gin::IsolateHolder::kNonStrictMode,
233                                  gin::ArrayBufferAllocator::SharedInstance());
234   DCHECK(!g_isolate_holder);
235   g_isolate_holder = new gin::IsolateHolder(
236       base::ThreadTaskRunnerHandle::Get(), gin::IsolateHolder::kSingleThread,
237       gin::IsolateHolder::IsolateType::kUtility);
238   g_isolate_holder->isolate()->Enter();
239 }
240 
TearDownV8()241 void TearDownV8() {
242   g_isolate_holder->isolate()->Exit();
243   delete g_isolate_holder;
244   g_isolate_holder = nullptr;
245 }
246 
247 // Returns true if the given |area| and |form_type| combination from
248 // PDFiumEngine::GetCharIndex() indicates it is a form text area.
IsFormTextArea(PDFiumPage::Area area,int form_type)249 bool IsFormTextArea(PDFiumPage::Area area, int form_type) {
250   if (form_type == FPDF_FORMFIELD_UNKNOWN)
251     return false;
252 
253   DCHECK_EQ(area, PDFiumPage::FormTypeToArea(form_type));
254   return area == PDFiumPage::FORM_TEXT_AREA;
255 }
256 
257 // Checks whether or not focus is in an editable form text area given the
258 // form field annotation flags and form type.
CheckIfEditableFormTextArea(int flags,int form_type)259 bool CheckIfEditableFormTextArea(int flags, int form_type) {
260   if (!!(flags & FPDF_FORMFLAG_READONLY))
261     return false;
262   if (form_type == FPDF_FORMFIELD_TEXTFIELD)
263     return true;
264   if (form_type == FPDF_FORMFIELD_COMBOBOX &&
265       (!!(flags & FPDF_FORMFLAG_CHOICE_EDIT))) {
266     return true;
267   }
268   return false;
269 }
270 
IsLinkArea(PDFiumPage::Area area)271 bool IsLinkArea(PDFiumPage::Area area) {
272   return area == PDFiumPage::WEBLINK_AREA || area == PDFiumPage::DOCLINK_AREA;
273 }
274 
275 // Normalize a MouseInputEvent. For Mac, this means transforming ctrl + left
276 // button down events into a right button down events.
NormalizeMouseEvent(pp::Instance * instance,const pp::MouseInputEvent & event)277 pp::MouseInputEvent NormalizeMouseEvent(pp::Instance* instance,
278                                         const pp::MouseInputEvent& event) {
279   pp::MouseInputEvent normalized_event = event;
280 #if defined(OS_MACOSX)
281   uint32_t modifiers = event.GetModifiers();
282   if ((modifiers & PP_INPUTEVENT_MODIFIER_CONTROLKEY) &&
283       event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT &&
284       event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN) {
285     uint32_t new_modifiers = modifiers & ~PP_INPUTEVENT_MODIFIER_CONTROLKEY;
286     normalized_event = pp::MouseInputEvent(
287         instance, PP_INPUTEVENT_TYPE_MOUSEDOWN, event.GetTimeStamp(),
288         new_modifiers, PP_INPUTEVENT_MOUSEBUTTON_RIGHT, event.GetPosition(), 1,
289         event.GetMovement());
290   }
291 #endif
292   return normalized_event;
293 }
294 
295 // These values are intended for the JS to handle, and it doesn't have access
296 // to the PDFDEST_VIEW_* defines.
ConvertViewIntToViewString(unsigned long view_int)297 std::string ConvertViewIntToViewString(unsigned long view_int) {
298   switch (view_int) {
299     case PDFDEST_VIEW_XYZ:
300       return "XYZ";
301     case PDFDEST_VIEW_FIT:
302       return "Fit";
303     case PDFDEST_VIEW_FITH:
304       return "FitH";
305     case PDFDEST_VIEW_FITV:
306       return "FitV";
307     case PDFDEST_VIEW_FITR:
308       return "FitR";
309     case PDFDEST_VIEW_FITB:
310       return "FitB";
311     case PDFDEST_VIEW_FITBH:
312       return "FitBH";
313     case PDFDEST_VIEW_FITBV:
314       return "FitBV";
315     case PDFDEST_VIEW_UNKNOWN_MODE:
316       return "";
317     default:
318       NOTREACHED();
319       return "";
320   }
321 }
322 
323 // Simplify to \" for searching
324 constexpr wchar_t kHebrewPunctuationGershayimCharacter = 0x05F4;
325 constexpr wchar_t kLeftDoubleQuotationMarkCharacter = 0x201C;
326 constexpr wchar_t kRightDoubleQuotationMarkCharacter = 0x201D;
327 
328 // Simplify \' for searching
329 constexpr wchar_t kHebrewPunctuationGereshCharacter = 0x05F3;
330 constexpr wchar_t kLeftSingleQuotationMarkCharacter = 0x2018;
331 constexpr wchar_t kRightSingleQuotationMarkCharacter = 0x2019;
332 
SimplifyForSearch(wchar_t c)333 wchar_t SimplifyForSearch(wchar_t c) {
334   switch (c) {
335     case kHebrewPunctuationGershayimCharacter:
336     case kLeftDoubleQuotationMarkCharacter:
337     case kRightDoubleQuotationMarkCharacter:
338       return L'\"';
339     case kHebrewPunctuationGereshCharacter:
340     case kLeftSingleQuotationMarkCharacter:
341     case kRightSingleQuotationMarkCharacter:
342       return L'\'';
343     default:
344       return c;
345   }
346 }
347 
348 }  // namespace
349 
InitializeSDK(bool enable_v8)350 void InitializeSDK(bool enable_v8) {
351   FPDF_LIBRARY_CONFIG config;
352   config.version = 2;
353   config.m_pUserFontPaths = nullptr;
354 
355   if (enable_v8) {
356     SetUpV8();
357     config.m_pIsolate = v8::Isolate::GetCurrent();
358   } else {
359     config.m_pIsolate = nullptr;
360   }
361   config.m_v8EmbedderSlot = gin::kEmbedderPDFium;
362   FPDF_InitLibraryWithConfig(&config);
363 
364 #if defined(OS_LINUX) || defined(OS_BSD)
365   InitializeLinuxFontMapper();
366 #endif
367 
368   InitializeUnsupportedFeaturesHandler();
369 }
370 
ShutdownSDK()371 void ShutdownSDK() {
372   FPDF_DestroyLibrary();
373   if (IsV8Initialized())
374     TearDownV8();
375 }
376 
377 PDFEngine::AccessibilityTextFieldInfo::AccessibilityTextFieldInfo() = default;
378 
379 PDFEngine::AccessibilityTextFieldInfo::AccessibilityTextFieldInfo(
380     const AccessibilityTextFieldInfo& that) = default;
381 
382 PDFEngine::AccessibilityTextFieldInfo::~AccessibilityTextFieldInfo() = default;
383 
Create(PDFEngine::Client * client,bool enable_javascript)384 std::unique_ptr<PDFEngine> PDFEngine::Create(PDFEngine::Client* client,
385                                              bool enable_javascript) {
386   return std::make_unique<PDFiumEngine>(client, enable_javascript);
387 }
388 
PDFiumEngine(PDFEngine::Client * client,bool enable_javascript)389 PDFiumEngine::PDFiumEngine(PDFEngine::Client* client, bool enable_javascript)
390     : client_(client),
391       form_filler_(this, enable_javascript),
392       mouse_down_state_(PDFiumPage::NONSELECTABLE_AREA,
393                         PDFiumPage::LinkTarget()),
394       print_(this) {
395   if (enable_javascript)
396     DCHECK(IsV8Initialized());
397 
398   find_factory_.Initialize(this);
399   password_factory_.Initialize(this);
400 
401   IFSDK_PAUSE::version = 1;
402   IFSDK_PAUSE::user = nullptr;
403   IFSDK_PAUSE::NeedToPauseNow = Pause_NeedToPauseNow;
404 
405 #if defined(OS_LINUX) || defined(OS_BSD)
406   // PreviewModeClient does not know its pp::Instance.
407   SetLastInstance(client_->GetPluginInstance());
408 #endif
409 }
410 
~PDFiumEngine()411 PDFiumEngine::~PDFiumEngine() {
412   for (auto& page : pages_)
413     page->Unload();
414 
415   if (doc())
416     FORM_DoDocumentAAction(form(), FPDFDOC_AACTION_WC);
417 }
418 
SetDocumentLoaderForTesting(std::unique_ptr<DocumentLoader> loader)419 void PDFiumEngine::SetDocumentLoaderForTesting(
420     std::unique_ptr<DocumentLoader> loader) {
421   DCHECK(loader);
422   DCHECK(!doc_loader_);
423   doc_loader_ = std::move(loader);
424   doc_loader_set_for_testing_ = true;
425 }
426 
New(const char * url,const char * headers)427 bool PDFiumEngine::New(const char* url, const char* headers) {
428   url_ = url;
429   if (headers)
430     headers_ = headers;
431   else
432     headers_.clear();
433   return true;
434 }
435 
PageOffsetUpdated(const pp::Point & page_offset)436 void PDFiumEngine::PageOffsetUpdated(const pp::Point& page_offset) {
437   page_offset_ = page_offset;
438 }
439 
PluginSizeUpdated(const pp::Size & size)440 void PDFiumEngine::PluginSizeUpdated(const pp::Size& size) {
441   CancelPaints();
442 
443   plugin_size_ = size;
444   CalculateVisiblePages();
445 }
446 
ScrolledToXPosition(int position)447 void PDFiumEngine::ScrolledToXPosition(int position) {
448   CancelPaints();
449 
450   int old_x = position_.x();
451   position_.set_x(position);
452   CalculateVisiblePages();
453   client_->DidScroll(pp::Point(old_x - position, 0));
454   OnSelectionPositionChanged();
455 }
456 
ScrolledToYPosition(int position)457 void PDFiumEngine::ScrolledToYPosition(int position) {
458   CancelPaints();
459 
460   int old_y = position_.y();
461   position_.set_y(position);
462   CalculateVisiblePages();
463   client_->DidScroll(pp::Point(0, old_y - position));
464   OnSelectionPositionChanged();
465 }
466 
PrePaint()467 void PDFiumEngine::PrePaint() {
468   for (auto& paint : progressive_paints_)
469     paint.set_painted(false);
470 }
471 
Paint(const pp::Rect & rect,pp::ImageData * image_data,std::vector<pp::Rect> * ready,std::vector<pp::Rect> * pending)472 void PDFiumEngine::Paint(const pp::Rect& rect,
473                          pp::ImageData* image_data,
474                          std::vector<pp::Rect>* ready,
475                          std::vector<pp::Rect>* pending) {
476   DCHECK(image_data);
477   DCHECK(ready);
478   DCHECK(pending);
479 
480   pp::Rect leftover = rect;
481   for (size_t i = 0; i < visible_pages_.size(); ++i) {
482     int index = visible_pages_[i];
483     // Convert the current page's rectangle to screen rectangle.  We do this
484     // instead of the reverse (converting the dirty rectangle from screen to
485     // page coordinates) because then we'd have to convert back to screen
486     // coordinates, and the rounding errors sometime leave pixels dirty or even
487     // move the text up or down a pixel when zoomed.
488     pp::Rect page_rect_in_screen = GetPageScreenRect(index);
489     pp::Rect dirty_in_screen = page_rect_in_screen.Intersect(leftover);
490     if (dirty_in_screen.IsEmpty())
491       continue;
492 
493     // Compute the leftover dirty region. The first page may have blank space
494     // above it, in which case we also need to subtract that space from the
495     // dirty region.
496     // If two-up view is enabled, we don't need to recompute |leftover| since
497     // subtracting |leftover| with a two-up view page won't result in a
498     // rectangle.
499     if (!layout_.options().two_up_view_enabled()) {
500       if (i == 0) {
501         pp::Rect blank_space_in_screen = dirty_in_screen;
502         blank_space_in_screen.set_y(0);
503         blank_space_in_screen.set_height(dirty_in_screen.y());
504         leftover = leftover.Subtract(blank_space_in_screen);
505       }
506 
507       leftover = leftover.Subtract(dirty_in_screen);
508     }
509 
510     if (pages_[index]->available()) {
511       int progressive = GetProgressiveIndex(index);
512       if (progressive != -1) {
513         DCHECK_GE(progressive, 0);
514         DCHECK_LT(static_cast<size_t>(progressive), progressive_paints_.size());
515         if (progressive_paints_[progressive].rect() != dirty_in_screen) {
516           // The PDFium code can only handle one progressive paint at a time, so
517           // queue this up. Previously we used to merge the rects when this
518           // happened, but it made scrolling up on complex PDFs very slow since
519           // there would be a damaged rect at the top (from scroll) and at the
520           // bottom (from toolbar).
521           pending->push_back(dirty_in_screen);
522           continue;
523         }
524       }
525 
526       if (progressive == -1) {
527         progressive = StartPaint(index, dirty_in_screen);
528         progressive_paint_timeout_ = kMaxInitialProgressivePaintTime;
529       } else {
530         progressive_paint_timeout_ = kMaxProgressivePaintTime;
531       }
532 
533       progressive_paints_[progressive].set_painted(true);
534       if (ContinuePaint(progressive, image_data)) {
535         FinishPaint(progressive, image_data);
536         ready->push_back(dirty_in_screen);
537       } else {
538         pending->push_back(dirty_in_screen);
539       }
540     } else {
541       PaintUnavailablePage(index, dirty_in_screen, image_data);
542       ready->push_back(dirty_in_screen);
543     }
544   }
545 }
546 
PostPaint()547 void PDFiumEngine::PostPaint() {
548   for (size_t i = 0; i < progressive_paints_.size(); ++i) {
549     if (progressive_paints_[i].painted())
550       continue;
551 
552     // This rectangle must have been merged with another one, that's why we
553     // weren't asked to paint it. Remove it or otherwise we'll never finish
554     // painting.
555     FPDF_RenderPage_Close(
556         pages_[progressive_paints_[i].page_index()]->GetPage());
557     progressive_paints_.erase(progressive_paints_.begin() + i);
558     --i;
559   }
560 }
561 
HandleDocumentLoad(const pp::URLLoader & loader)562 bool PDFiumEngine::HandleDocumentLoad(const pp::URLLoader& loader) {
563   password_tries_remaining_ = kMaxPasswordTries;
564   process_when_pending_request_complete_ = true;
565 
566   if (!doc_loader_set_for_testing_) {
567     auto loader_wrapper =
568         std::make_unique<URLLoaderWrapperImpl>(GetPluginInstance(), loader);
569     loader_wrapper->SetResponseHeaders(headers_);
570 
571     doc_loader_ = std::make_unique<DocumentLoaderImpl>(this);
572     if (!doc_loader_->Init(std::move(loader_wrapper), url_))
573       return false;
574   }
575   document_ = std::make_unique<PDFiumDocument>(doc_loader_.get());
576 
577   // request initial data.
578   doc_loader_->RequestData(0, 1);
579   return true;
580 }
581 
GetPluginInstance()582 pp::Instance* PDFiumEngine::GetPluginInstance() {
583   return client_->GetPluginInstance();
584 }
585 
CreateURLLoader()586 std::unique_ptr<URLLoaderWrapper> PDFiumEngine::CreateURLLoader() {
587   return std::make_unique<URLLoaderWrapperImpl>(GetPluginInstance(),
588                                                 client_->CreateURLLoader());
589 }
590 
AppendPage(PDFEngine * engine,int index)591 void PDFiumEngine::AppendPage(PDFEngine* engine, int index) {
592   // Unload and delete the blank page before appending.
593   pages_[index]->Unload();
594   pages_[index]->set_calculated_links(false);
595   pp::Size curr_page_size = GetPageSize(index);
596   FPDFPage_Delete(doc(), index);
597   FPDF_ImportPages(doc(), static_cast<PDFiumEngine*>(engine)->doc(), "1",
598                    index);
599   pp::Size new_page_size = GetPageSize(index);
600   if (curr_page_size != new_page_size) {
601     DCHECK(document_loaded_);
602     LoadPageInfo();
603   }
604   client_->Invalidate(GetPageScreenRect(index));
605 }
606 
GetSaveData()607 std::vector<uint8_t> PDFiumEngine::GetSaveData() {
608   PDFiumMemBufferFileWrite output_file_write;
609   if (!FPDF_SaveAsCopy(doc(), &output_file_write, 0))
610     return std::vector<uint8_t>();
611   return output_file_write.TakeBuffer();
612 }
613 
OnPendingRequestComplete()614 void PDFiumEngine::OnPendingRequestComplete() {
615   if (!process_when_pending_request_complete_)
616     return;
617 
618   if (!fpdf_availability()) {
619     document_->file_access().m_FileLen = doc_loader_->GetDocumentSize();
620     document_->CreateFPDFAvailability();
621     DCHECK(fpdf_availability());
622     // Currently engine does not deal efficiently with some non-linearized
623     // files.
624     // See http://code.google.com/p/chromium/issues/detail?id=59400
625     // To improve user experience we download entire file for non-linearized
626     // PDF.
627     if (FPDFAvail_IsLinearized(fpdf_availability()) != PDF_LINEARIZED) {
628       // Wait complete document.
629       process_when_pending_request_complete_ = false;
630       document_->ResetFPDFAvailability();
631       return;
632     }
633   }
634 
635   if (!doc()) {
636     LoadDocument();
637     return;
638   }
639 
640   if (pages_.empty()) {
641     LoadBody();
642     return;
643   }
644 
645   // LoadDocument() will result in |pending_pages_| being reset so there's no
646   // need to run the code below in that case.
647   bool update_pages = false;
648   std::vector<int> still_pending;
649   for (int pending_page : pending_pages_) {
650     if (CheckPageAvailable(pending_page, &still_pending)) {
651       update_pages = true;
652       if (IsPageVisible(pending_page))
653         client_->Invalidate(GetPageScreenRect(pending_page));
654     }
655   }
656   pending_pages_.swap(still_pending);
657   if (update_pages) {
658     DCHECK(document_loaded_);
659     LoadPageInfo();
660   }
661 }
662 
OnNewDataReceived()663 void PDFiumEngine::OnNewDataReceived() {
664   client_->DocumentLoadProgress(doc_loader_->BytesReceived(),
665                                 doc_loader_->GetDocumentSize());
666 }
667 
OnDocumentComplete()668 void PDFiumEngine::OnDocumentComplete() {
669   if (doc())
670     return FinishLoadingDocument();
671 
672   document_->file_access().m_FileLen = doc_loader_->GetDocumentSize();
673   if (!fpdf_availability()) {
674     document_->CreateFPDFAvailability();
675     DCHECK(fpdf_availability());
676   }
677   LoadDocument();
678 }
679 
OnDocumentCanceled()680 void PDFiumEngine::OnDocumentCanceled() {
681   if (visible_pages_.empty())
682     client_->DocumentLoadFailed();
683   else
684     OnDocumentComplete();
685 }
686 
FinishLoadingDocument()687 void PDFiumEngine::FinishLoadingDocument() {
688   DCHECK(doc());
689   DCHECK(doc_loader_->IsDocumentComplete());
690 
691   LoadBody();
692 
693   FX_DOWNLOADHINTS& download_hints = document_->download_hints();
694   bool need_update = false;
695   for (size_t i = 0; i < pages_.size(); ++i) {
696     if (pages_[i]->available())
697       continue;
698 
699     pages_[i]->MarkAvailable();
700     // We still need to call IsPageAvail() even if the whole document is
701     // already downloaded.
702     FPDFAvail_IsPageAvail(fpdf_availability(), i, &download_hints);
703     need_update = true;
704     if (IsPageVisible(i))
705       client_->Invalidate(GetPageScreenRect(i));
706   }
707 
708   // Transition |document_loaded_| to true after finishing any calls to
709   // FPDFAvail_IsPageAvail(), since we no longer need to defer calls to this
710   // function from LoadPageInfo(). Note that LoadBody() calls LoadPageInfo()
711   // indirectly, so we cannot make this transition earlier.
712   document_loaded_ = true;
713 
714   if (need_update)
715     LoadPageInfo();
716 
717   LoadDocumentMetadata();
718 
719   if (called_do_document_action_)
720     return;
721   called_do_document_action_ = true;
722 
723   // These can only be called now, as the JS might end up needing a page.
724   FORM_DoDocumentJSAction(form());
725   FORM_DoDocumentOpenAction(form());
726   if (most_visible_page_ != -1) {
727     FPDF_PAGE new_page = pages_[most_visible_page_]->GetPage();
728     FORM_DoPageAAction(new_page, form(), FPDFPAGE_AACTION_OPEN);
729   }
730 
731   if (doc()) {
732     DocumentFeatures document_features;
733     document_features.page_count = pages_.size();
734     document_features.has_attachments = (FPDFDoc_GetAttachmentCount(doc()) > 0);
735     document_features.is_tagged = FPDFCatalog_IsTagged(doc());
736     document_features.form_type =
737         static_cast<FormType>(FPDF_GetFormType(doc()));
738     client_->DocumentLoadComplete(document_features);
739   }
740 }
741 
UnsupportedFeature(const std::string & feature)742 void PDFiumEngine::UnsupportedFeature(const std::string& feature) {
743   client_->DocumentHasUnsupportedFeature(feature);
744 }
745 
fpdf_availability() const746 FPDF_AVAIL PDFiumEngine::fpdf_availability() const {
747   return document_ ? document_->fpdf_availability() : nullptr;
748 }
749 
doc() const750 FPDF_DOCUMENT PDFiumEngine::doc() const {
751   return document_ ? document_->doc() : nullptr;
752 }
753 
form() const754 FPDF_FORMHANDLE PDFiumEngine::form() const {
755   return document_ ? document_->form() : nullptr;
756 }
757 
ContinueFind(int32_t result)758 void PDFiumEngine::ContinueFind(int32_t result) {
759   StartFind(current_find_text_, result != 0);
760 }
761 
HandleEvent(const pp::InputEvent & event)762 bool PDFiumEngine::HandleEvent(const pp::InputEvent& event) {
763   DCHECK(!defer_page_unload_);
764   defer_page_unload_ = true;
765   bool rv = false;
766   switch (event.GetType()) {
767     case PP_INPUTEVENT_TYPE_MOUSEDOWN:
768       rv = OnMouseDown(pp::MouseInputEvent(event));
769       break;
770     case PP_INPUTEVENT_TYPE_MOUSEUP:
771       rv = OnMouseUp(pp::MouseInputEvent(event));
772       break;
773     case PP_INPUTEVENT_TYPE_MOUSEMOVE:
774       rv = OnMouseMove(pp::MouseInputEvent(event));
775       break;
776     case PP_INPUTEVENT_TYPE_MOUSEENTER:
777       OnMouseEnter(pp::MouseInputEvent(event));
778       break;
779     case PP_INPUTEVENT_TYPE_KEYDOWN:
780       rv = OnKeyDown(pp::KeyboardInputEvent(event));
781       break;
782     case PP_INPUTEVENT_TYPE_KEYUP:
783       rv = OnKeyUp(pp::KeyboardInputEvent(event));
784       break;
785     case PP_INPUTEVENT_TYPE_CHAR:
786       rv = OnChar(pp::KeyboardInputEvent(event));
787       break;
788     case PP_INPUTEVENT_TYPE_TOUCHSTART: {
789       KillTouchTimer();
790 
791       pp::TouchInputEvent touch_event(event);
792       if (touch_event.GetTouchCount(PP_TOUCHLIST_TYPE_TARGETTOUCHES) == 1)
793         ScheduleTouchTimer(touch_event);
794       break;
795     }
796     case PP_INPUTEVENT_TYPE_TOUCHEND:
797       KillTouchTimer();
798       break;
799     case PP_INPUTEVENT_TYPE_TOUCHMOVE:
800       // TODO(dsinclair): This should allow a little bit of movement (up to the
801       // touch radii) to account for finger jiggle.
802       KillTouchTimer();
803       break;
804     default:
805       break;
806   }
807 
808   DCHECK(defer_page_unload_);
809   defer_page_unload_ = false;
810 
811   // Store the pages to unload away because the act of unloading pages can cause
812   // there to be more pages to unload. We leave those extra pages to be unloaded
813   // on the next go around.
814   std::vector<int> pages_to_unload;
815   std::swap(pages_to_unload, deferred_page_unloads_);
816   for (int page_index : pages_to_unload)
817     pages_[page_index]->Unload();
818 
819   return rv;
820 }
821 
QuerySupportedPrintOutputFormats()822 uint32_t PDFiumEngine::QuerySupportedPrintOutputFormats() {
823   if (HasPermission(PERMISSION_PRINT_HIGH_QUALITY))
824     return PP_PRINTOUTPUTFORMAT_PDF | PP_PRINTOUTPUTFORMAT_RASTER;
825   if (HasPermission(PERMISSION_PRINT_LOW_QUALITY))
826     return PP_PRINTOUTPUTFORMAT_RASTER;
827   return 0;
828 }
829 
PrintBegin()830 void PDFiumEngine::PrintBegin() {
831   FORM_DoDocumentAAction(form(), FPDFDOC_AACTION_WP);
832 }
833 
PrintPages(const PP_PrintPageNumberRange_Dev * page_ranges,uint32_t page_range_count,const PP_PrintSettings_Dev & print_settings,const PP_PdfPrintSettings_Dev & pdf_print_settings)834 pp::Resource PDFiumEngine::PrintPages(
835     const PP_PrintPageNumberRange_Dev* page_ranges,
836     uint32_t page_range_count,
837     const PP_PrintSettings_Dev& print_settings,
838     const PP_PdfPrintSettings_Dev& pdf_print_settings) {
839   if (!page_range_count)
840     return pp::Resource();
841 
842   if ((print_settings.format & PP_PRINTOUTPUTFORMAT_PDF) &&
843       HasPermission(PERMISSION_PRINT_HIGH_QUALITY)) {
844     return PrintPagesAsPdf(page_ranges, page_range_count, print_settings,
845                            pdf_print_settings);
846   }
847   if (HasPermission(PERMISSION_PRINT_LOW_QUALITY)) {
848     return PrintPagesAsRasterPdf(page_ranges, page_range_count, print_settings,
849                                  pdf_print_settings);
850   }
851   return pp::Resource();
852 }
853 
PrintPagesAsRasterPdf(const PP_PrintPageNumberRange_Dev * page_ranges,uint32_t page_range_count,const PP_PrintSettings_Dev & print_settings,const PP_PdfPrintSettings_Dev & pdf_print_settings)854 pp::Buffer_Dev PDFiumEngine::PrintPagesAsRasterPdf(
855     const PP_PrintPageNumberRange_Dev* page_ranges,
856     uint32_t page_range_count,
857     const PP_PrintSettings_Dev& print_settings,
858     const PP_PdfPrintSettings_Dev& pdf_print_settings) {
859   DCHECK(page_range_count);
860 
861   // If document is not downloaded yet, disable printing.
862   if (doc() && !doc_loader_->IsDocumentComplete())
863     return pp::Buffer_Dev();
864 
865   KillFormFocus();
866 
867 #if defined(OS_LINUX) || defined(OS_BSD)
868   SetLastInstance(client_->GetPluginInstance());
869 #endif
870 
871   return ConvertPdfToBufferDev(
872       print_.PrintPagesAsPdf(page_ranges, page_range_count, print_settings,
873                              pdf_print_settings, /*raster=*/true));
874 }
875 
PrintPagesAsPdf(const PP_PrintPageNumberRange_Dev * page_ranges,uint32_t page_range_count,const PP_PrintSettings_Dev & print_settings,const PP_PdfPrintSettings_Dev & pdf_print_settings)876 pp::Buffer_Dev PDFiumEngine::PrintPagesAsPdf(
877     const PP_PrintPageNumberRange_Dev* page_ranges,
878     uint32_t page_range_count,
879     const PP_PrintSettings_Dev& print_settings,
880     const PP_PdfPrintSettings_Dev& pdf_print_settings) {
881   DCHECK(page_range_count);
882   DCHECK(doc());
883 
884   KillFormFocus();
885 
886   std::vector<uint32_t> page_numbers =
887       PDFiumPrint::GetPageNumbersFromPrintPageNumberRange(page_ranges,
888                                                           page_range_count);
889   for (uint32_t page_number : page_numbers) {
890     pages_[page_number]->GetPage();
891     if (!IsPageVisible(page_number))
892       pages_[page_number]->Unload();
893   }
894 
895   return ConvertPdfToBufferDev(
896       print_.PrintPagesAsPdf(page_ranges, page_range_count, print_settings,
897                              pdf_print_settings, /*raster=*/false));
898 }
899 
ConvertPdfToBufferDev(const std::vector<uint8_t> & pdf_data)900 pp::Buffer_Dev PDFiumEngine::ConvertPdfToBufferDev(
901     const std::vector<uint8_t>& pdf_data) {
902   pp::Buffer_Dev buffer;
903   if (!pdf_data.empty()) {
904     buffer = pp::Buffer_Dev(GetPluginInstance(), pdf_data.size());
905     if (!buffer.is_null())
906       memcpy(buffer.data(), pdf_data.data(), pdf_data.size());
907   }
908   return buffer;
909 }
910 
KillFormFocus()911 void PDFiumEngine::KillFormFocus() {
912   FORM_ForceToKillFocus(form());
913   SetInFormTextArea(false);
914 }
915 
GetLoadedByteSize()916 uint32_t PDFiumEngine::GetLoadedByteSize() {
917   return doc_loader_->GetDocumentSize();
918 }
919 
ReadLoadedBytes(uint32_t length,void * buffer)920 bool PDFiumEngine::ReadLoadedBytes(uint32_t length, void* buffer) {
921   DCHECK_LE(length, GetLoadedByteSize());
922   return doc_loader_->GetBlock(0, length, buffer);
923 }
924 
SetFormSelectedText(FPDF_FORMHANDLE form_handle,FPDF_PAGE page)925 void PDFiumEngine::SetFormSelectedText(FPDF_FORMHANDLE form_handle,
926                                        FPDF_PAGE page) {
927   unsigned long form_sel_text_len =
928       FORM_GetSelectedText(form_handle, page, nullptr, 0);
929 
930   // If form selected text is empty and there was no previous form text
931   // selection, exit early because nothing has changed. When |form_sel_text_len|
932   // is 2, that represents a wide string with just a NUL-terminator.
933   if (form_sel_text_len <= 2 && selected_form_text_.empty())
934     return;
935 
936   base::string16 selected_form_text16;
937   PDFiumAPIStringBufferSizeInBytesAdapter<base::string16> string_adapter(
938       &selected_form_text16, form_sel_text_len, false);
939   string_adapter.Close(FORM_GetSelectedText(
940       form_handle, page, string_adapter.GetData(), form_sel_text_len));
941 
942   // Update previous and current selections, then compare them to check if
943   // selection has changed. If so, set plugin text selection.
944   std::string selected_form_text = selected_form_text_;
945   selected_form_text_ = base::UTF16ToUTF8(selected_form_text16);
946   if (selected_form_text != selected_form_text_) {
947     DCHECK(in_form_text_area_);
948     pp::PDF::SetSelectedText(GetPluginInstance(), selected_form_text_.c_str());
949   }
950 }
951 
PrintEnd()952 void PDFiumEngine::PrintEnd() {
953   FORM_DoDocumentAAction(form(), FPDFDOC_AACTION_DP);
954 }
955 
GetCharIndex(const pp::Point & point,int * page_index,int * char_index,int * form_type,PDFiumPage::LinkTarget * target)956 PDFiumPage::Area PDFiumEngine::GetCharIndex(const pp::Point& point,
957                                             int* page_index,
958                                             int* char_index,
959                                             int* form_type,
960                                             PDFiumPage::LinkTarget* target) {
961   int page = -1;
962   pp::Point point_in_page(
963       static_cast<int>((point.x() + position_.x()) / current_zoom_),
964       static_cast<int>((point.y() + position_.y()) / current_zoom_));
965   for (int visible_page : visible_pages_) {
966     if (pages_[visible_page]->rect().Contains(point_in_page)) {
967       page = visible_page;
968       break;
969     }
970   }
971   if (page == -1)
972     return PDFiumPage::NONSELECTABLE_AREA;
973 
974   // If the page hasn't finished rendering, calling into the page sometimes
975   // leads to hangs.
976   for (const auto& paint : progressive_paints_) {
977     if (paint.page_index() == page)
978       return PDFiumPage::NONSELECTABLE_AREA;
979   }
980 
981   *page_index = page;
982   PDFiumPage::Area result = pages_[page]->GetCharIndex(
983       point_in_page, layout_.options().default_page_orientation(), char_index,
984       form_type, target);
985   return (client_->IsPrintPreview() && result == PDFiumPage::WEBLINK_AREA)
986              ? PDFiumPage::NONSELECTABLE_AREA
987              : result;
988 }
989 
OnMouseDown(const pp::MouseInputEvent & event)990 bool PDFiumEngine::OnMouseDown(const pp::MouseInputEvent& event) {
991   pp::MouseInputEvent normalized_event =
992       NormalizeMouseEvent(client_->GetPluginInstance(), event);
993   if (normalized_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT)
994     return OnLeftMouseDown(normalized_event);
995   if (normalized_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_MIDDLE)
996     return OnMiddleMouseDown(normalized_event);
997   if (normalized_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_RIGHT)
998     return OnRightMouseDown(normalized_event);
999   return false;
1000 }
1001 
OnSingleClick(int page_index,int char_index)1002 void PDFiumEngine::OnSingleClick(int page_index, int char_index) {
1003   SetSelecting(true);
1004   selection_.push_back(PDFiumRange(pages_[page_index].get(), char_index, 0));
1005 }
1006 
OnMultipleClick(int click_count,int page_index,int char_index)1007 void PDFiumEngine::OnMultipleClick(int click_count,
1008                                    int page_index,
1009                                    int char_index) {
1010   DCHECK_GE(click_count, 2);
1011   bool is_double_click = click_count == 2;
1012 
1013   // It would be more efficient if the SDK could support finding a space, but
1014   // now it doesn't.
1015   int start_index = char_index;
1016   do {
1017     base::char16 cur = pages_[page_index]->GetCharAtIndex(start_index);
1018     if (FindMultipleClickBoundary(is_double_click, cur))
1019       break;
1020   } while (--start_index >= 0);
1021   if (start_index)
1022     start_index++;
1023 
1024   int end_index = char_index;
1025   int total = pages_[page_index]->GetCharCount();
1026   while (end_index++ <= total) {
1027     base::char16 cur = pages_[page_index]->GetCharAtIndex(end_index);
1028     if (FindMultipleClickBoundary(is_double_click, cur))
1029       break;
1030   }
1031 
1032   selection_.push_back(PDFiumRange(pages_[page_index].get(), start_index,
1033                                    end_index - start_index));
1034 }
1035 
OnLeftMouseDown(const pp::MouseInputEvent & event)1036 bool PDFiumEngine::OnLeftMouseDown(const pp::MouseInputEvent& event) {
1037   SetMouseLeftButtonDown(true);
1038 
1039   auto selection_invalidator =
1040       std::make_unique<SelectionChangeInvalidator>(this);
1041   selection_.clear();
1042 
1043   int page_index = -1;
1044   int char_index = -1;
1045   int form_type = FPDF_FORMFIELD_UNKNOWN;
1046   PDFiumPage::LinkTarget target;
1047   pp::Point point = event.GetPosition();
1048   PDFiumPage::Area area =
1049       GetCharIndex(point, &page_index, &char_index, &form_type, &target);
1050   DCHECK_GE(form_type, FPDF_FORMFIELD_UNKNOWN);
1051   mouse_down_state_.Set(area, target);
1052 
1053   // Decide whether to open link or not based on user action in mouse up and
1054   // mouse move events.
1055   if (IsLinkArea(area))
1056     return true;
1057 
1058   if (page_index != -1) {
1059     last_page_mouse_down_ = page_index;
1060     double page_x;
1061     double page_y;
1062     DeviceToPage(page_index, point, &page_x, &page_y);
1063 
1064     bool is_form_text_area = IsFormTextArea(area, form_type);
1065     FPDF_PAGE page = pages_[page_index]->GetPage();
1066     bool is_editable_form_text_area =
1067         is_form_text_area &&
1068         IsPointInEditableFormTextArea(page, page_x, page_y, form_type);
1069 
1070     if (event.GetClickCount() == 1) {
1071       FORM_OnLButtonDown(form(), page, event.GetModifiers(), page_x, page_y);
1072     } else if (event.GetClickCount() == 2) {
1073       FORM_OnLButtonDoubleClick(form(), page, event.GetModifiers(), page_x,
1074                                 page_y);
1075     }
1076     if (form_type != FPDF_FORMFIELD_UNKNOWN) {
1077       // Destroy SelectionChangeInvalidator object before SetInFormTextArea()
1078       // changes plugin's focus to be in form text area. This way, regular text
1079       // selection can be cleared when a user clicks into a form text area
1080       // because the pp::PDF::SetSelectedText() call in
1081       // ~SelectionChangeInvalidator() still goes to the Mimehandler
1082       // (not the Renderer).
1083       selection_invalidator.reset();
1084 
1085       SetInFormTextArea(is_form_text_area);
1086       editable_form_text_area_ = is_editable_form_text_area;
1087       return true;  // Return now before we get into the selection code.
1088     }
1089   }
1090   SetInFormTextArea(false);
1091 
1092   if (area != PDFiumPage::TEXT_AREA)
1093     return true;  // Return true so WebKit doesn't do its own highlighting.
1094 
1095   if (event.GetClickCount() == 1)
1096     OnSingleClick(page_index, char_index);
1097   else if (event.GetClickCount() == 2 || event.GetClickCount() == 3)
1098     OnMultipleClick(event.GetClickCount(), page_index, char_index);
1099 
1100   return true;
1101 }
1102 
OnMiddleMouseDown(const pp::MouseInputEvent & event)1103 bool PDFiumEngine::OnMiddleMouseDown(const pp::MouseInputEvent& event) {
1104   SetMouseLeftButtonDown(false);
1105   mouse_middle_button_down_ = true;
1106   mouse_middle_button_last_position_ = event.GetPosition();
1107 
1108   SelectionChangeInvalidator selection_invalidator(this);
1109   selection_.clear();
1110 
1111   int unused_page_index = -1;
1112   int unused_char_index = -1;
1113   int unused_form_type = FPDF_FORMFIELD_UNKNOWN;
1114   PDFiumPage::LinkTarget target;
1115   PDFiumPage::Area area =
1116       GetCharIndex(event.GetPosition(), &unused_page_index, &unused_char_index,
1117                    &unused_form_type, &target);
1118   mouse_down_state_.Set(area, target);
1119 
1120   // Decide whether to open link or not based on user action in mouse up and
1121   // mouse move events.
1122   if (IsLinkArea(area))
1123     return true;
1124 
1125   if (kViewerImplementedPanning) {
1126     // Switch to hand cursor when panning.
1127     client_->UpdateCursor(PP_CURSORTYPE_HAND);
1128   }
1129 
1130   // Prevent middle mouse button from selecting texts.
1131   return false;
1132 }
1133 
OnRightMouseDown(const pp::MouseInputEvent & event)1134 bool PDFiumEngine::OnRightMouseDown(const pp::MouseInputEvent& event) {
1135   DCHECK_EQ(PP_INPUTEVENT_MOUSEBUTTON_RIGHT, event.GetButton());
1136 
1137   pp::Point point = event.GetPosition();
1138   int page_index = -1;
1139   int char_index = -1;
1140   int form_type = FPDF_FORMFIELD_UNKNOWN;
1141   PDFiumPage::LinkTarget target;
1142   PDFiumPage::Area area =
1143       GetCharIndex(point, &page_index, &char_index, &form_type, &target);
1144   DCHECK_GE(form_type, FPDF_FORMFIELD_UNKNOWN);
1145 
1146   bool is_form_text_area = IsFormTextArea(area, form_type);
1147   bool is_editable_form_text_area = false;
1148 
1149   double page_x = -1;
1150   double page_y = -1;
1151   FPDF_PAGE page = nullptr;
1152   if (is_form_text_area) {
1153     DCHECK_NE(page_index, -1);
1154 
1155     DeviceToPage(page_index, point, &page_x, &page_y);
1156     page = pages_[page_index]->GetPage();
1157     is_editable_form_text_area =
1158         IsPointInEditableFormTextArea(page, page_x, page_y, form_type);
1159   }
1160 
1161   // Handle the case when focus starts inside a form text area.
1162   if (in_form_text_area_) {
1163     if (is_form_text_area) {
1164       FORM_OnFocus(form(), page, 0, page_x, page_y);
1165     } else {
1166       // Transition out of a form text area.
1167       FORM_ForceToKillFocus(form());
1168       SetInFormTextArea(false);
1169     }
1170     return true;
1171   }
1172 
1173   // Handle the case when focus starts outside a form text area and transitions
1174   // into a form text area.
1175   if (is_form_text_area) {
1176     {
1177       SelectionChangeInvalidator selection_invalidator(this);
1178       selection_.clear();
1179     }
1180 
1181     SetInFormTextArea(true);
1182     editable_form_text_area_ = is_editable_form_text_area;
1183     FORM_OnFocus(form(), page, 0, page_x, page_y);
1184     return true;
1185   }
1186 
1187   // Handle the case when focus starts outside a form text area and stays
1188   // outside.
1189   if (selection_.empty())
1190     return false;
1191 
1192   std::vector<pp::Rect> selection_rect_vector =
1193       GetAllScreenRectsUnion(selection_, GetVisibleRect().point());
1194   for (const auto& rect : selection_rect_vector) {
1195     if (rect.Contains(point.x(), point.y()))
1196       return false;
1197   }
1198   SelectionChangeInvalidator selection_invalidator(this);
1199   selection_.clear();
1200   return true;
1201 }
1202 
NavigateToLinkDestination(PDFiumPage::Area area,const PDFiumPage::LinkTarget & target,WindowOpenDisposition disposition)1203 bool PDFiumEngine::NavigateToLinkDestination(
1204     PDFiumPage::Area area,
1205     const PDFiumPage::LinkTarget& target,
1206     WindowOpenDisposition disposition) {
1207   if (area == PDFiumPage::WEBLINK_AREA) {
1208     client_->NavigateTo(target.url, disposition);
1209     SetInFormTextArea(false);
1210     return true;
1211   }
1212   if (area == PDFiumPage::DOCLINK_AREA) {
1213     if (!PageIndexInBounds(target.page))
1214       return true;
1215 
1216     if (disposition == WindowOpenDisposition::CURRENT_TAB) {
1217       client_->NavigateToDestination(
1218           target.page, base::OptionalOrNullptr(target.x_in_pixels),
1219           base::OptionalOrNullptr(target.y_in_pixels),
1220           base::OptionalOrNullptr(target.zoom));
1221     } else {
1222       std::string parameters = base::StringPrintf("#page=%d", target.page + 1);
1223       parameters += base::StringPrintf(
1224           "&zoom=%d,%d,%d", static_cast<int>(target.zoom.value_or(1.0) * 100),
1225           static_cast<int>(target.x_in_pixels.value_or(0)),
1226           static_cast<int>(target.y_in_pixels.value_or(0)));
1227 
1228       client_->NavigateTo(parameters, disposition);
1229     }
1230     SetInFormTextArea(false);
1231     return true;
1232   }
1233   return false;
1234 }
1235 
OnMouseUp(const pp::MouseInputEvent & event)1236 bool PDFiumEngine::OnMouseUp(const pp::MouseInputEvent& event) {
1237   if (event.GetButton() != PP_INPUTEVENT_MOUSEBUTTON_LEFT &&
1238       event.GetButton() != PP_INPUTEVENT_MOUSEBUTTON_MIDDLE) {
1239     return false;
1240   }
1241 
1242   if (event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT)
1243     SetMouseLeftButtonDown(false);
1244   else if (event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_MIDDLE)
1245     mouse_middle_button_down_ = false;
1246 
1247   int page_index = -1;
1248   int char_index = -1;
1249   int form_type = FPDF_FORMFIELD_UNKNOWN;
1250   PDFiumPage::LinkTarget target;
1251   pp::Point point = event.GetPosition();
1252   PDFiumPage::Area area =
1253       GetCharIndex(point, &page_index, &char_index, &form_type, &target);
1254 
1255   // Open link on mouse up for same link for which mouse down happened earlier.
1256   if (mouse_down_state_.Matches(area, target)) {
1257     uint32_t modifiers = event.GetModifiers();
1258     bool middle_button =
1259         !!(modifiers & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN);
1260     bool alt_key = !!(modifiers & PP_INPUTEVENT_MODIFIER_ALTKEY);
1261     bool ctrl_key = !!(modifiers & PP_INPUTEVENT_MODIFIER_CONTROLKEY);
1262     bool meta_key = !!(modifiers & PP_INPUTEVENT_MODIFIER_METAKEY);
1263     bool shift_key = !!(modifiers & PP_INPUTEVENT_MODIFIER_SHIFTKEY);
1264 
1265     WindowOpenDisposition disposition = ui::DispositionFromClick(
1266         middle_button, alt_key, ctrl_key, meta_key, shift_key);
1267 
1268     if (NavigateToLinkDestination(area, target, disposition))
1269       return true;
1270   }
1271 
1272   if (event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_MIDDLE) {
1273     if (kViewerImplementedPanning) {
1274       // Update the cursor when panning stops.
1275       client_->UpdateCursor(DetermineCursorType(area, form_type));
1276     }
1277 
1278     // Prevent middle mouse button from selecting texts.
1279     return false;
1280   }
1281 
1282   if (page_index != -1) {
1283     double page_x;
1284     double page_y;
1285     DeviceToPage(page_index, point, &page_x, &page_y);
1286     FORM_OnLButtonUp(form(), pages_[page_index]->GetPage(),
1287                      event.GetModifiers(), page_x, page_y);
1288   }
1289 
1290   if (!selecting_)
1291     return false;
1292 
1293   SetSelecting(false);
1294   return true;
1295 }
1296 
OnMouseMove(const pp::MouseInputEvent & event)1297 bool PDFiumEngine::OnMouseMove(const pp::MouseInputEvent& event) {
1298   int page_index = -1;
1299   int char_index = -1;
1300   int form_type = FPDF_FORMFIELD_UNKNOWN;
1301   PDFiumPage::LinkTarget target;
1302   pp::Point point = event.GetPosition();
1303   PDFiumPage::Area area =
1304       GetCharIndex(point, &page_index, &char_index, &form_type, &target);
1305 
1306   // Clear |mouse_down_state_| if mouse moves away from where the mouse down
1307   // happened.
1308   if (!mouse_down_state_.Matches(area, target))
1309     mouse_down_state_.Reset();
1310 
1311   if (!selecting_) {
1312     client_->UpdateCursor(DetermineCursorType(area, form_type));
1313 
1314     if (page_index != -1) {
1315       double page_x;
1316       double page_y;
1317       DeviceToPage(page_index, point, &page_x, &page_y);
1318       FORM_OnMouseMove(form(), pages_[page_index]->GetPage(), 0, page_x,
1319                        page_y);
1320     }
1321 
1322     std::string url = GetLinkAtPosition(event.GetPosition());
1323     if (url != link_under_cursor_) {
1324       link_under_cursor_ = url;
1325       pp::PDF::SetLinkUnderCursor(GetPluginInstance(), url.c_str());
1326     }
1327 
1328     // If in form text area while left mouse button is held down, check if form
1329     // text selection needs to be updated.
1330     if (mouse_left_button_down_ && area == PDFiumPage::FORM_TEXT_AREA &&
1331         last_page_mouse_down_ != -1) {
1332       SetFormSelectedText(form(), pages_[last_page_mouse_down_]->GetPage());
1333     }
1334 
1335     if (kViewerImplementedPanning && mouse_middle_button_down_) {
1336       // Subtract (origin - destination) so delta is already the delta for
1337       // moving the page, rather than the delta the mouse moved.
1338       // GetMovement() does not work here, as small mouse movements are
1339       // considered zero.
1340       pp::Point page_position_delta =
1341           mouse_middle_button_last_position_ - event.GetPosition();
1342       if (page_position_delta.x() != 0 || page_position_delta.y() != 0) {
1343         client_->ScrollBy(page_position_delta);
1344         mouse_middle_button_last_position_ = event.GetPosition();
1345       }
1346     }
1347 
1348     // No need to swallow the event, since this might interfere with the
1349     // scrollbars if the user is dragging them.
1350     return false;
1351   }
1352 
1353   // We're selecting but right now we're not over text, so don't change the
1354   // current selection.
1355   if (area != PDFiumPage::TEXT_AREA && !IsLinkArea(area))
1356     return false;
1357 
1358   SelectionChangeInvalidator selection_invalidator(this);
1359   return ExtendSelection(page_index, char_index);
1360 }
1361 
DetermineCursorType(PDFiumPage::Area area,int form_type) const1362 PP_CursorType_Dev PDFiumEngine::DetermineCursorType(PDFiumPage::Area area,
1363                                                     int form_type) const {
1364   if (kViewerImplementedPanning && mouse_middle_button_down_) {
1365     return PP_CURSORTYPE_HAND;
1366   }
1367 
1368   switch (area) {
1369     case PDFiumPage::TEXT_AREA:
1370       return PP_CURSORTYPE_IBEAM;
1371     case PDFiumPage::WEBLINK_AREA:
1372     case PDFiumPage::DOCLINK_AREA:
1373       return PP_CURSORTYPE_HAND;
1374     case PDFiumPage::NONSELECTABLE_AREA:
1375     case PDFiumPage::FORM_TEXT_AREA:
1376     default:
1377       switch (form_type) {
1378         case FPDF_FORMFIELD_PUSHBUTTON:
1379         case FPDF_FORMFIELD_CHECKBOX:
1380         case FPDF_FORMFIELD_RADIOBUTTON:
1381         case FPDF_FORMFIELD_COMBOBOX:
1382         case FPDF_FORMFIELD_LISTBOX:
1383           return PP_CURSORTYPE_HAND;
1384         case FPDF_FORMFIELD_TEXTFIELD:
1385           return PP_CURSORTYPE_IBEAM;
1386 #if defined(PDF_ENABLE_XFA)
1387         case FPDF_FORMFIELD_XFA_CHECKBOX:
1388         case FPDF_FORMFIELD_XFA_COMBOBOX:
1389         case FPDF_FORMFIELD_XFA_IMAGEFIELD:
1390         case FPDF_FORMFIELD_XFA_LISTBOX:
1391         case FPDF_FORMFIELD_XFA_PUSHBUTTON:
1392         case FPDF_FORMFIELD_XFA_SIGNATURE:
1393           return PP_CURSORTYPE_HAND;
1394         case FPDF_FORMFIELD_XFA_TEXTFIELD:
1395           return PP_CURSORTYPE_IBEAM;
1396 #endif
1397         default:
1398           return PP_CURSORTYPE_POINTER;
1399       }
1400   }
1401 }
1402 
OnMouseEnter(const pp::MouseInputEvent & event)1403 void PDFiumEngine::OnMouseEnter(const pp::MouseInputEvent& event) {
1404   if (event.GetModifiers() & PP_INPUTEVENT_MODIFIER_MIDDLEBUTTONDOWN) {
1405     if (!mouse_middle_button_down_) {
1406       mouse_middle_button_down_ = true;
1407       mouse_middle_button_last_position_ = event.GetPosition();
1408     }
1409   } else {
1410     if (mouse_middle_button_down_) {
1411       mouse_middle_button_down_ = false;
1412     }
1413   }
1414 }
1415 
ExtendSelection(int page_index,int char_index)1416 bool PDFiumEngine::ExtendSelection(int page_index, int char_index) {
1417   // Check if the user has decreased their selection area and we need to remove
1418   // pages from |selection_|.
1419   for (size_t i = 0; i < selection_.size(); ++i) {
1420     if (selection_[i].page_index() == page_index) {
1421       // There should be no other pages after this.
1422       selection_.erase(selection_.begin() + i + 1, selection_.end());
1423       break;
1424     }
1425   }
1426   if (selection_.empty())
1427     return false;
1428 
1429   const int last_page_index = selection_.back().page_index();
1430   const int last_char_index = selection_.back().char_index();
1431   if (last_page_index == page_index) {
1432     // Selecting within a page.
1433     int count = char_index - last_char_index;
1434     if (count >= 0) {
1435       // Selecting forward.
1436       ++count;
1437     } else {
1438       --count;
1439     }
1440     selection_.back().SetCharCount(count);
1441   } else if (last_page_index < page_index) {
1442     // Selecting into the next page.
1443 
1444     // Save the current last selection for use below.
1445     // Warning: Do not use references / pointers into |selection_|, as the code
1446     // below can modify |selection_| and invalidate those references / pointers.
1447     const size_t last_selection_index = selection_.size() - 1;
1448 
1449     // First make sure that there are no gaps in selection, i.e. if mousedown on
1450     // page one but we only get mousemove over page three, we want page two.
1451     for (int i = last_page_index + 1; i < page_index; ++i) {
1452       selection_.push_back(
1453           PDFiumRange(pages_[i].get(), 0, pages_[i]->GetCharCount()));
1454     }
1455 
1456     int count = pages_[last_page_index]->GetCharCount();
1457     selection_[last_selection_index].SetCharCount(count - last_char_index);
1458     selection_.push_back(PDFiumRange(pages_[page_index].get(), 0, char_index));
1459   } else {
1460     // Selecting into the previous page.
1461     // The selection's char_index is 0-based, so the character count is one
1462     // more than the index. The character count needs to be negative to
1463     // indicate a backwards selection.
1464     selection_.back().SetCharCount(-last_char_index - 1);
1465 
1466     // First make sure that there are no gaps in selection, i.e. if mousedown on
1467     // page three but we only get mousemove over page one, we want page two.
1468     for (int i = last_page_index - 1; i > page_index; --i) {
1469       selection_.push_back(
1470           PDFiumRange(pages_[i].get(), 0, pages_[i]->GetCharCount()));
1471     }
1472 
1473     int count = pages_[page_index]->GetCharCount();
1474     selection_.push_back(
1475         PDFiumRange(pages_[page_index].get(), count, count - char_index));
1476   }
1477 
1478   return true;
1479 }
1480 
OnKeyDown(const pp::KeyboardInputEvent & event)1481 bool PDFiumEngine::OnKeyDown(const pp::KeyboardInputEvent& event) {
1482   if (last_page_mouse_down_ == -1)
1483     return false;
1484 
1485   bool rv = !!FORM_OnKeyDown(form(), pages_[last_page_mouse_down_]->GetPage(),
1486                              event.GetKeyCode(), event.GetModifiers());
1487 
1488   if (event.GetKeyCode() == ui::VKEY_BACK ||
1489       event.GetKeyCode() == ui::VKEY_ESCAPE) {
1490     // Blink does not send char events for backspace or escape keys, see
1491     // WebKeyboardEvent::IsCharacterKey() and b/961192 for more information.
1492     // So just fake one since PDFium uses it.
1493     std::string str;
1494     str.push_back(event.GetKeyCode());
1495     pp::KeyboardInputEvent synthesized(pp::KeyboardInputEvent(
1496         client_->GetPluginInstance(), PP_INPUTEVENT_TYPE_CHAR,
1497         event.GetTimeStamp(), event.GetModifiers(), event.GetKeyCode(), str));
1498     OnChar(synthesized);
1499   }
1500 
1501   return rv;
1502 }
1503 
OnKeyUp(const pp::KeyboardInputEvent & event)1504 bool PDFiumEngine::OnKeyUp(const pp::KeyboardInputEvent& event) {
1505   if (last_page_mouse_down_ == -1)
1506     return false;
1507 
1508   // Check if form text selection needs to be updated.
1509   FPDF_PAGE page = pages_[last_page_mouse_down_]->GetPage();
1510   if (in_form_text_area_)
1511     SetFormSelectedText(form(), page);
1512 
1513   return !!FORM_OnKeyUp(form(), page, event.GetKeyCode(), event.GetModifiers());
1514 }
1515 
OnChar(const pp::KeyboardInputEvent & event)1516 bool PDFiumEngine::OnChar(const pp::KeyboardInputEvent& event) {
1517   if (last_page_mouse_down_ == -1)
1518     return false;
1519 
1520   base::string16 str = base::UTF8ToUTF16(event.GetCharacterText().AsString());
1521   return !!FORM_OnChar(form(), pages_[last_page_mouse_down_]->GetPage(), str[0],
1522                        event.GetModifiers());
1523 }
1524 
StartFind(const std::string & text,bool case_sensitive)1525 void PDFiumEngine::StartFind(const std::string& text, bool case_sensitive) {
1526   // If the caller asks StartFind() to search for no text, then this is an
1527   // error on the part of the caller. The PPAPI Find_Private interface
1528   // guarantees it is not empty, so this should never happen.
1529   DCHECK(!text.empty());
1530 
1531   // If StartFind() gets called before we have any page information (i.e.
1532   // before the first call to LoadDocument has happened). Handle this case.
1533   if (pages_.empty()) {
1534     client_->NotifyNumberOfFindResultsChanged(0, true);
1535     return;
1536   }
1537 
1538   bool first_search = (current_find_text_ != text);
1539   int character_to_start_searching_from = 0;
1540   if (first_search) {
1541     std::vector<PDFiumRange> old_selection = selection_;
1542     StopFind();
1543     current_find_text_ = text;
1544 
1545     if (old_selection.empty()) {
1546       // Start searching from the beginning of the document.
1547       next_page_to_search_ = 0;
1548       last_page_to_search_ = pages_.size() - 1;
1549       last_character_index_to_search_ = -1;
1550     } else {
1551       // There's a current selection, so start from it.
1552       next_page_to_search_ = old_selection[0].page_index();
1553       last_character_index_to_search_ = old_selection[0].char_index();
1554       character_to_start_searching_from = old_selection[0].char_index();
1555       last_page_to_search_ = next_page_to_search_;
1556     }
1557     search_in_progress_ = true;
1558   }
1559 
1560   int current_page = next_page_to_search_;
1561 
1562   if (pages_[current_page]->available()) {
1563     base::string16 str = base::UTF8ToUTF16(text);
1564     // Don't use PDFium to search for now, since it doesn't support unicode
1565     // text. Leave the code for now to avoid bit-rot, in case it's fixed later.
1566     // The extra parens suppress a -Wunreachable-code warning.
1567     if ((0)) {
1568       SearchUsingPDFium(str, case_sensitive, first_search,
1569                         character_to_start_searching_from, current_page);
1570     } else {
1571       SearchUsingICU(str, case_sensitive, first_search,
1572                      character_to_start_searching_from, current_page);
1573     }
1574 
1575     if (!IsPageVisible(current_page))
1576       pages_[current_page]->Unload();
1577   }
1578 
1579   if (next_page_to_search_ != last_page_to_search_ ||
1580       (first_search && last_character_index_to_search_ != -1)) {
1581     ++next_page_to_search_;
1582   }
1583 
1584   if (next_page_to_search_ == static_cast<int>(pages_.size()))
1585     next_page_to_search_ = 0;
1586   // If there's only one page in the document and we start searching midway,
1587   // then we'll want to search the page one more time.
1588   bool end_of_search =
1589       next_page_to_search_ == last_page_to_search_ &&
1590       // Only one page but didn't start midway.
1591       ((pages_.size() == 1 && last_character_index_to_search_ == -1) ||
1592        // Started midway, but only 1 page and we already looped around.
1593        (pages_.size() == 1 && !first_search) ||
1594        // Started midway, and we've just looped around.
1595        (pages_.size() > 1 && current_page == next_page_to_search_));
1596 
1597   if (end_of_search) {
1598     search_in_progress_ = false;
1599 
1600     // Send the final notification.
1601     client_->NotifyNumberOfFindResultsChanged(find_results_.size(), true);
1602     return;
1603   }
1604 
1605   // In unit tests, PPAPI is not initialized, so just call ContinueFind()
1606   // directly.
1607   if (doc_loader_set_for_testing_) {
1608     ContinueFind(case_sensitive ? 1 : 0);
1609   } else {
1610     pp::CompletionCallback callback =
1611         find_factory_.NewCallback(&PDFiumEngine::ContinueFind);
1612     pp::Module::Get()->core()->CallOnMainThread(0, callback,
1613                                                 case_sensitive ? 1 : 0);
1614   }
1615 }
1616 
SearchUsingPDFium(const base::string16 & term,bool case_sensitive,bool first_search,int character_to_start_searching_from,int current_page)1617 void PDFiumEngine::SearchUsingPDFium(const base::string16& term,
1618                                      bool case_sensitive,
1619                                      bool first_search,
1620                                      int character_to_start_searching_from,
1621                                      int current_page) {
1622   // Find all the matches in the current page.
1623   unsigned long flags = case_sensitive ? FPDF_MATCHCASE : 0;
1624   FPDF_SCHHANDLE find =
1625       FPDFText_FindStart(pages_[current_page]->GetTextPage(),
1626                          reinterpret_cast<const unsigned short*>(term.c_str()),
1627                          flags, character_to_start_searching_from);
1628 
1629   // Note: since we search one page at a time, we don't find matches across
1630   // page boundaries.  We could do this manually ourself, but it seems low
1631   // priority since Reader itself doesn't do it.
1632   while (FPDFText_FindNext(find)) {
1633     PDFiumRange result(pages_[current_page].get(),
1634                        FPDFText_GetSchResultIndex(find),
1635                        FPDFText_GetSchCount(find));
1636 
1637     if (!first_search && last_character_index_to_search_ != -1 &&
1638         result.page_index() == last_page_to_search_ &&
1639         result.char_index() >= last_character_index_to_search_) {
1640       break;
1641     }
1642 
1643     AddFindResult(result);
1644   }
1645 
1646   FPDFText_FindClose(find);
1647 }
1648 
SearchUsingICU(const base::string16 & term,bool case_sensitive,bool first_search,int character_to_start_searching_from,int current_page)1649 void PDFiumEngine::SearchUsingICU(const base::string16& term,
1650                                   bool case_sensitive,
1651                                   bool first_search,
1652                                   int character_to_start_searching_from,
1653                                   int current_page) {
1654   DCHECK(!term.empty());
1655 
1656   // Various types of quotions marks need to be converted to the simple ASCII
1657   // version for searching to get better matching.
1658   base::string16 adjusted_term = term;
1659   for (base::char16& c : adjusted_term)
1660     c = SimplifyForSearch(c);
1661 
1662   const int original_text_length = pages_[current_page]->GetCharCount();
1663   int text_length = original_text_length;
1664   if (character_to_start_searching_from) {
1665     text_length -= character_to_start_searching_from;
1666   } else if (!first_search && last_character_index_to_search_ != -1 &&
1667              current_page == last_page_to_search_) {
1668     text_length = last_character_index_to_search_;
1669   }
1670   if (text_length <= 0)
1671     return;
1672 
1673   base::string16 page_text;
1674   PDFiumAPIStringBufferAdapter<base::string16> api_string_adapter(
1675       &page_text, text_length, false);
1676   unsigned short* data =
1677       reinterpret_cast<unsigned short*>(api_string_adapter.GetData());
1678   int written =
1679       FPDFText_GetText(pages_[current_page]->GetTextPage(),
1680                        character_to_start_searching_from, text_length, data);
1681   api_string_adapter.Close(written);
1682 
1683   base::string16 adjusted_page_text;
1684   adjusted_page_text.reserve(page_text.size());
1685   // Values in |removed_indices| are in the adjusted text index space and
1686   // indicate a character was removed from the page text before the given
1687   // index. If multiple characters are removed in a row then there will be
1688   // multiple entries with the same value.
1689   std::vector<size_t> removed_indices;
1690   // When walking through the page text collapse any whitespace regions,
1691   // including \r and \n, down to a single ' ' character. This code does
1692   // not use base::CollapseWhitespace(), because that function does not
1693   // return where the collapsing occurs, but uses the same underlying list of
1694   // whitespace characters. Calculating where the collapsed regions are after
1695   // the fact is as complex as collapsing them manually.
1696   for (size_t i = 0; i < page_text.size(); i++) {
1697     base::char16 c = page_text[i];
1698     // Collapse whitespace regions by inserting a ' ' into the
1699     // adjusted text and recording any removed whitespace indices as preceding
1700     // it.
1701     if (base::IsUnicodeWhitespace(c)) {
1702       size_t whitespace_region_begin = i;
1703       while (i < page_text.size() && base::IsUnicodeWhitespace(page_text[i]))
1704         ++i;
1705 
1706       size_t count = i - whitespace_region_begin - 1;
1707       removed_indices.insert(removed_indices.end(), count,
1708                              adjusted_page_text.size());
1709       adjusted_page_text.push_back(' ');
1710       if (i >= page_text.size())
1711         break;
1712       c = page_text[i];
1713     }
1714 
1715     if (IsIgnorableCharacter(c))
1716       removed_indices.push_back(adjusted_page_text.size());
1717     else
1718       adjusted_page_text.push_back(SimplifyForSearch(c));
1719   }
1720 
1721   std::vector<PDFEngine::Client::SearchStringResult> results =
1722       client_->SearchString(adjusted_page_text.c_str(), adjusted_term.c_str(),
1723                             case_sensitive);
1724   for (const auto& result : results) {
1725     // Need to convert from adjusted page text start to page text start, by
1726     // incrementing for all the characters adjusted before it in the string.
1727     auto removed_indices_begin = std::upper_bound(
1728         removed_indices.begin(), removed_indices.end(), result.start_index);
1729     size_t page_text_result_start_index =
1730         result.start_index +
1731         std::distance(removed_indices.begin(), removed_indices_begin);
1732 
1733     // Need to convert the adjusted page length into a page text length, since
1734     // the matching range may have adjusted characters within it. This
1735     // conversion only cares about skipped characters in the result interval.
1736     auto removed_indices_end =
1737         std::upper_bound(removed_indices_begin, removed_indices.end(),
1738                          result.start_index + result.length);
1739     int term_removed_count =
1740         std::distance(removed_indices_begin, removed_indices_end);
1741     int page_text_result_length = result.length + term_removed_count;
1742 
1743     // Need to map the indexes from the page text, which may have generated
1744     // characters like space etc, to character indices from the page.
1745     int text_to_start_searching_from = FPDFText_GetTextIndexFromCharIndex(
1746         pages_[current_page]->GetTextPage(), character_to_start_searching_from);
1747     int temp_start =
1748         page_text_result_start_index + text_to_start_searching_from;
1749     int start = FPDFText_GetCharIndexFromTextIndex(
1750         pages_[current_page]->GetTextPage(), temp_start);
1751     int end = FPDFText_GetCharIndexFromTextIndex(
1752         pages_[current_page]->GetTextPage(),
1753         temp_start + page_text_result_length);
1754 
1755     // If |term| occurs at the end of a page, then |end| will be -1 due to the
1756     // index being out of bounds. Compensate for this case so the range
1757     // character count calculation below works out.
1758     if (temp_start + page_text_result_length == original_text_length) {
1759       DCHECK_EQ(-1, end);
1760       end = original_text_length;
1761     }
1762     DCHECK_LT(start, end);
1763     DCHECK_EQ(term.size() + term_removed_count,
1764               static_cast<size_t>(end - start));
1765     AddFindResult(PDFiumRange(pages_[current_page].get(), start, end - start));
1766   }
1767 }
1768 
AddFindResult(const PDFiumRange & result)1769 void PDFiumEngine::AddFindResult(const PDFiumRange& result) {
1770   bool first_result = find_results_.empty();
1771   // Figure out where to insert the new location, since we could have
1772   // started searching midway and now we wrapped.
1773   size_t result_index;
1774   int page_index = result.page_index();
1775   int char_index = result.char_index();
1776   for (result_index = 0; result_index < find_results_.size(); ++result_index) {
1777     if (find_results_[result_index].page_index() > page_index ||
1778         (find_results_[result_index].page_index() == page_index &&
1779          find_results_[result_index].char_index() > char_index)) {
1780       break;
1781     }
1782   }
1783   find_results_.insert(find_results_.begin() + result_index, result);
1784   UpdateTickMarks();
1785   client_->NotifyNumberOfFindResultsChanged(find_results_.size(), false);
1786   if (first_result) {
1787     DCHECK(!resume_find_index_);
1788     DCHECK(!current_find_index_);
1789     SelectFindResult(/*forward=*/true);
1790   }
1791 }
1792 
SelectFindResult(bool forward)1793 bool PDFiumEngine::SelectFindResult(bool forward) {
1794   if (find_results_.empty())
1795     return false;
1796 
1797   SelectionChangeInvalidator selection_invalidator(this);
1798 
1799   // Move back/forward through the search locations we previously found.
1800   size_t new_index;
1801   const size_t last_index = find_results_.size() - 1;
1802 
1803   if (resume_find_index_) {
1804     new_index = resume_find_index_.value();
1805     resume_find_index_.reset();
1806   } else if (current_find_index_) {
1807     size_t current_index = current_find_index_.value();
1808     if ((forward && current_index >= last_index) ||
1809         (!forward && current_index == 0)) {
1810       current_find_index_.reset();
1811       client_->NotifySelectedFindResultChanged(-1);
1812       client_->NotifyNumberOfFindResultsChanged(find_results_.size(), true);
1813       return true;
1814     }
1815     int increment = forward ? 1 : -1;
1816     new_index = current_index + increment;
1817   } else {
1818     new_index = forward ? 0 : last_index;
1819   }
1820   current_find_index_ = new_index;
1821 
1822   // Update the selection before telling the client to scroll, since it could
1823   // paint then.
1824   selection_.clear();
1825   selection_.push_back(find_results_[current_find_index_.value()]);
1826 
1827   // If the result is not in view, scroll to it.
1828   pp::Rect bounding_rect;
1829   pp::Rect visible_rect = GetVisibleRect();
1830   // Use zoom of 1.0 since |visible_rect| is without zoom.
1831   const std::vector<pp::Rect>& rects =
1832       find_results_[current_find_index_.value()].GetScreenRects(
1833           pp::Point(), 1.0, layout_.options().default_page_orientation());
1834   for (const auto& rect : rects)
1835     bounding_rect = bounding_rect.Union(rect);
1836   if (!visible_rect.Contains(bounding_rect)) {
1837     pp::Point center = bounding_rect.CenterPoint();
1838     // Make the page centered.
1839     int new_y = CalculateCenterForZoom(center.y(), visible_rect.height(),
1840                                        current_zoom_);
1841     client_->ScrollToY(new_y, /*compensate_for_toolbar=*/false);
1842 
1843     // Only move horizontally if it's not visible.
1844     if (center.x() < visible_rect.x() || center.x() > visible_rect.right()) {
1845       int new_x = CalculateCenterForZoom(center.x(), visible_rect.width(),
1846                                          current_zoom_);
1847       client_->ScrollToX(new_x);
1848     }
1849   }
1850 
1851   client_->NotifySelectedFindResultChanged(current_find_index_.value());
1852   if (!search_in_progress_)
1853     client_->NotifyNumberOfFindResultsChanged(find_results_.size(), true);
1854   return true;
1855 }
1856 
StopFind()1857 void PDFiumEngine::StopFind() {
1858   SelectionChangeInvalidator selection_invalidator(this);
1859   selection_.clear();
1860   selecting_ = false;
1861 
1862   find_results_.clear();
1863   search_in_progress_ = false;
1864   next_page_to_search_ = -1;
1865   last_page_to_search_ = -1;
1866   last_character_index_to_search_ = -1;
1867   current_find_index_.reset();
1868   current_find_text_.clear();
1869 
1870   UpdateTickMarks();
1871   find_factory_.CancelAll();
1872 }
1873 
GetAllScreenRectsUnion(const std::vector<PDFiumRange> & rect_range,const pp::Point & offset_point) const1874 std::vector<pp::Rect> PDFiumEngine::GetAllScreenRectsUnion(
1875     const std::vector<PDFiumRange>& rect_range,
1876     const pp::Point& offset_point) const {
1877   std::vector<pp::Rect> rect_vector;
1878   rect_vector.reserve(rect_range.size());
1879   for (const auto& range : rect_range) {
1880     pp::Rect result_rect;
1881     const std::vector<pp::Rect>& rects =
1882         range.GetScreenRects(offset_point, current_zoom_,
1883                              layout_.options().default_page_orientation());
1884     for (const auto& rect : rects)
1885       result_rect = result_rect.Union(rect);
1886     rect_vector.push_back(result_rect);
1887   }
1888   return rect_vector;
1889 }
1890 
UpdateTickMarks()1891 void PDFiumEngine::UpdateTickMarks() {
1892   std::vector<pp::Rect> tickmarks =
1893       GetAllScreenRectsUnion(find_results_, pp::Point());
1894   client_->UpdateTickMarks(tickmarks);
1895 }
1896 
ZoomUpdated(double new_zoom_level)1897 void PDFiumEngine::ZoomUpdated(double new_zoom_level) {
1898   CancelPaints();
1899 
1900   current_zoom_ = new_zoom_level;
1901 
1902   CalculateVisiblePages();
1903   UpdateTickMarks();
1904 }
1905 
RotateClockwise()1906 void PDFiumEngine::RotateClockwise() {
1907   desired_layout_options_.RotatePagesClockwise();
1908   ProposeNextDocumentLayout();
1909 }
1910 
RotateCounterclockwise()1911 void PDFiumEngine::RotateCounterclockwise() {
1912   desired_layout_options_.RotatePagesCounterclockwise();
1913   ProposeNextDocumentLayout();
1914 }
1915 
SetTwoUpView(bool enable)1916 void PDFiumEngine::SetTwoUpView(bool enable) {
1917   desired_layout_options_.set_two_up_view_enabled(enable);
1918   ProposeNextDocumentLayout();
1919 }
1920 
InvalidateAllPages()1921 void PDFiumEngine::InvalidateAllPages() {
1922   CancelPaints();
1923   StopFind();
1924   DCHECK(document_loaded_);
1925   RefreshCurrentDocumentLayout();
1926   client_->Invalidate(pp::Rect(plugin_size_));
1927 }
1928 
GetSelectedText()1929 std::string PDFiumEngine::GetSelectedText() {
1930   if (!HasPermission(PDFEngine::PERMISSION_COPY))
1931     return std::string();
1932 
1933   base::string16 result;
1934   for (size_t i = 0; i < selection_.size(); ++i) {
1935     static constexpr base::char16 kNewLineChar = L'\n';
1936     base::string16 current_selection_text = selection_[i].GetText();
1937     if (i != 0) {
1938       if (selection_[i - 1].page_index() > selection_[i].page_index())
1939         std::swap(current_selection_text, result);
1940       result.push_back(kNewLineChar);
1941     }
1942     result.append(current_selection_text);
1943   }
1944 
1945   FormatStringWithHyphens(&result);
1946   FormatStringForOS(&result);
1947   return base::UTF16ToUTF8(result);
1948 }
1949 
CanEditText()1950 bool PDFiumEngine::CanEditText() {
1951   return editable_form_text_area_;
1952 }
1953 
HasEditableText()1954 bool PDFiumEngine::HasEditableText() {
1955   DCHECK(CanEditText());
1956   if (last_page_mouse_down_ == -1)
1957     return false;
1958   FPDF_PAGE page = pages_[last_page_mouse_down_]->GetPage();
1959   // If the return value is 2, that corresponds to "\0\0".
1960   return FORM_GetFocusedText(form(), page, nullptr, 0) > 2;
1961 }
1962 
ReplaceSelection(const std::string & text)1963 void PDFiumEngine::ReplaceSelection(const std::string& text) {
1964   DCHECK(CanEditText());
1965   if (last_page_mouse_down_ != -1) {
1966     base::string16 text_wide = base::UTF8ToUTF16(text);
1967     FPDF_WIDESTRING text_pdf_wide =
1968         reinterpret_cast<FPDF_WIDESTRING>(text_wide.c_str());
1969 
1970     FORM_ReplaceSelection(form(), pages_[last_page_mouse_down_]->GetPage(),
1971                           text_pdf_wide);
1972   }
1973 }
1974 
CanUndo()1975 bool PDFiumEngine::CanUndo() {
1976   if (last_page_mouse_down_ == -1)
1977     return false;
1978   return !!FORM_CanUndo(form(), pages_[last_page_mouse_down_]->GetPage());
1979 }
1980 
CanRedo()1981 bool PDFiumEngine::CanRedo() {
1982   if (last_page_mouse_down_ == -1)
1983     return false;
1984   return !!FORM_CanRedo(form(), pages_[last_page_mouse_down_]->GetPage());
1985 }
1986 
Undo()1987 void PDFiumEngine::Undo() {
1988   if (last_page_mouse_down_ != -1)
1989     FORM_Undo(form(), pages_[last_page_mouse_down_]->GetPage());
1990 }
1991 
Redo()1992 void PDFiumEngine::Redo() {
1993   if (last_page_mouse_down_ != -1)
1994     FORM_Redo(form(), pages_[last_page_mouse_down_]->GetPage());
1995 }
1996 
HandleAccessibilityAction(const PP_PdfAccessibilityActionData & action_data)1997 void PDFiumEngine::HandleAccessibilityAction(
1998     const PP_PdfAccessibilityActionData& action_data) {
1999   switch (action_data.action) {
2000     case PP_PdfAccessibilityAction::PP_PDF_SCROLL_TO_MAKE_VISIBLE: {
2001       ScrollBasedOnScrollAlignment(action_data.target_rect,
2002                                    action_data.horizontal_scroll_alignment,
2003                                    action_data.vertical_scroll_alignment);
2004       break;
2005     }
2006     case PP_PdfAccessibilityAction::PP_PDF_DO_DEFAULT_ACTION: {
2007       if (PageIndexInBounds(action_data.page_index)) {
2008         if (action_data.annotation_type ==
2009             PP_PdfAccessibilityAnnotationType::PP_PDF_LINK) {
2010           PDFiumPage::LinkTarget target;
2011           PDFiumPage::Area area =
2012               pages_[action_data.page_index]->GetLinkTargetAtIndex(
2013                   action_data.annotation_index, &target);
2014           NavigateToLinkDestination(area, target,
2015                                     WindowOpenDisposition::CURRENT_TAB);
2016         }
2017       }
2018       break;
2019     }
2020     case PP_PdfAccessibilityAction::PP_PDF_SCROLL_TO_GLOBAL_POINT: {
2021       ScrollToGlobalPoint(action_data.target_rect, action_data.target_point);
2022       break;
2023     }
2024     case PP_PdfAccessibilityAction::PP_PDF_SET_SELECTION: {
2025       if (IsPageCharacterIndexInBounds(action_data.selection_start_index) &&
2026           IsPageCharacterIndexInBounds(action_data.selection_end_index)) {
2027         SetSelection(action_data.selection_start_index,
2028                      action_data.selection_end_index);
2029       }
2030       break;
2031     }
2032     default:
2033       NOTREACHED();
2034       break;
2035   }
2036 }
2037 
GetLinkAtPosition(const pp::Point & point)2038 std::string PDFiumEngine::GetLinkAtPosition(const pp::Point& point) {
2039   std::string url;
2040   int temp;
2041   int page_index = -1;
2042   int form_type = FPDF_FORMFIELD_UNKNOWN;
2043   PDFiumPage::LinkTarget target;
2044   PDFiumPage::Area area =
2045       GetCharIndex(point, &page_index, &temp, &form_type, &target);
2046   if (area == PDFiumPage::WEBLINK_AREA)
2047     url = target.url;
2048   return url;
2049 }
2050 
HasPermission(DocumentPermission permission) const2051 bool PDFiumEngine::HasPermission(DocumentPermission permission) const {
2052   // No |permissions_| means no restrictions.
2053   if (!permissions_)
2054     return true;
2055   return permissions_->HasPermission(permission);
2056 }
2057 
SelectAll()2058 void PDFiumEngine::SelectAll() {
2059   if (in_form_text_area_)
2060     return;
2061 
2062   SelectionChangeInvalidator selection_invalidator(this);
2063 
2064   selection_.clear();
2065   for (const auto& page : pages_) {
2066     if (page->available())
2067       selection_.push_back(PDFiumRange(page.get(), 0, page->GetCharCount()));
2068   }
2069 }
2070 
GetDocumentMetadata() const2071 const DocumentMetadata& PDFiumEngine::GetDocumentMetadata() const {
2072   DCHECK(document_loaded_);
2073   return doc_metadata_;
2074 }
2075 
GetNumberOfPages()2076 int PDFiumEngine::GetNumberOfPages() {
2077   return pages_.size();
2078 }
2079 
GetBookmarks()2080 pp::VarArray PDFiumEngine::GetBookmarks() {
2081   pp::VarDictionary dict = TraverseBookmarks(nullptr, 0);
2082   // The root bookmark contains no useful information.
2083   return pp::VarArray(dict.Get(pp::Var("children")));
2084 }
2085 
TraverseBookmarks(FPDF_BOOKMARK bookmark,unsigned int depth)2086 pp::VarDictionary PDFiumEngine::TraverseBookmarks(FPDF_BOOKMARK bookmark,
2087                                                   unsigned int depth) {
2088   pp::VarDictionary dict;
2089   base::string16 title;
2090   unsigned long buffer_size = FPDFBookmark_GetTitle(bookmark, nullptr, 0);
2091   if (buffer_size > 0) {
2092     PDFiumAPIStringBufferSizeInBytesAdapter<base::string16> api_string_adapter(
2093         &title, buffer_size, true);
2094     api_string_adapter.Close(FPDFBookmark_GetTitle(
2095         bookmark, api_string_adapter.GetData(), buffer_size));
2096   }
2097   dict.Set(pp::Var("title"), pp::Var(base::UTF16ToUTF8(title)));
2098 
2099   FPDF_DEST dest = FPDFBookmark_GetDest(doc(), bookmark);
2100   // Some bookmarks don't have a page to select.
2101   if (dest) {
2102     int page_index = FPDFDest_GetDestPageIndex(doc(), dest);
2103     if (PageIndexInBounds(page_index)) {
2104       dict.Set(pp::Var("page"), pp::Var(page_index));
2105 
2106       base::Optional<gfx::PointF> xy;
2107       base::Optional<float> zoom;
2108       pages_[page_index]->GetPageDestinationTarget(dest, &xy, &zoom);
2109       if (xy) {
2110         dict.Set(pp::Var("x"), pp::Var(static_cast<int>(xy.value().x())));
2111         dict.Set(pp::Var("y"), pp::Var(static_cast<int>(xy.value().y())));
2112       }
2113       if (zoom) {
2114         dict.Set(pp::Var("zoom"), pp::Var(zoom.value()));
2115       }
2116     }
2117   } else {
2118     // Extract URI for bookmarks linking to an external page.
2119     FPDF_ACTION action = FPDFBookmark_GetAction(bookmark);
2120     buffer_size = FPDFAction_GetURIPath(doc(), action, nullptr, 0);
2121     if (buffer_size > 0) {
2122       std::string uri;
2123       PDFiumAPIStringBufferAdapter<std::string> api_string_adapter(
2124           &uri, buffer_size, true);
2125       api_string_adapter.Close(FPDFAction_GetURIPath(
2126           doc(), action, api_string_adapter.GetData(), buffer_size));
2127       dict.Set(pp::Var("uri"), pp::Var(uri));
2128     }
2129   }
2130 
2131   pp::VarArray children;
2132 
2133   // Don't trust PDFium to handle circular bookmarks.
2134   constexpr unsigned int kMaxDepth = 128;
2135   if (depth < kMaxDepth) {
2136     int child_index = 0;
2137     std::set<FPDF_BOOKMARK> seen_bookmarks;
2138     for (FPDF_BOOKMARK child_bookmark =
2139              FPDFBookmark_GetFirstChild(doc(), bookmark);
2140          child_bookmark;
2141          child_bookmark = FPDFBookmark_GetNextSibling(doc(), child_bookmark)) {
2142       if (base::Contains(seen_bookmarks, child_bookmark))
2143         break;
2144 
2145       seen_bookmarks.insert(child_bookmark);
2146       children.Set(child_index, TraverseBookmarks(child_bookmark, depth + 1));
2147       child_index++;
2148     }
2149   }
2150   dict.Set(pp::Var("children"), children);
2151   return dict;
2152 }
2153 
ScrollBasedOnScrollAlignment(const pp::Rect & scroll_rect,const PP_PdfAccessibilityScrollAlignment & horizontal_scroll_alignment,const PP_PdfAccessibilityScrollAlignment & vertical_scroll_alignment)2154 void PDFiumEngine::ScrollBasedOnScrollAlignment(
2155     const pp::Rect& scroll_rect,
2156     const PP_PdfAccessibilityScrollAlignment& horizontal_scroll_alignment,
2157     const PP_PdfAccessibilityScrollAlignment& vertical_scroll_alignment) {
2158   pp::Point scroll_offset = GetScreenRect(scroll_rect).point();
2159   switch (horizontal_scroll_alignment) {
2160     case PP_PdfAccessibilityScrollAlignment::PP_PDF_SCROLL_ALIGNMENT_RIGHT:
2161       scroll_offset.set_x(scroll_offset.x() - plugin_size_.width());
2162       break;
2163     case PP_PDF_SCROLL_ALIGNMENT_CENTER:
2164       scroll_offset.set_x(scroll_offset.x() - (plugin_size_.width() / 2));
2165       break;
2166     case PP_PDF_SCROLL_ALIGNMENT_CLOSEST_EDGE: {
2167       scroll_offset.set_x((std::abs(scroll_offset.x()) <=
2168                            std::abs(scroll_offset.x() - plugin_size_.width()))
2169                               ? scroll_offset.x()
2170                               : scroll_offset.x() - plugin_size_.width());
2171       break;
2172     }
2173     case PP_PDF_SCROLL_NONE:
2174       scroll_offset.set_x(0);
2175       break;
2176     case PP_PDF_SCROLL_ALIGNMENT_LEFT:
2177     case PP_PDF_SCROLL_ALIGNMENT_TOP:
2178     case PP_PDF_SCROLL_ALIGNMENT_BOTTOM:
2179     default:
2180       break;
2181   }
2182 
2183   switch (vertical_scroll_alignment) {
2184     case PP_PDF_SCROLL_ALIGNMENT_BOTTOM:
2185       scroll_offset.set_y(scroll_offset.y() - plugin_size_.height());
2186       break;
2187     case PP_PDF_SCROLL_ALIGNMENT_CENTER:
2188       scroll_offset.set_y(scroll_offset.y() - (plugin_size_.height() / 2));
2189       break;
2190     case PP_PDF_SCROLL_ALIGNMENT_CLOSEST_EDGE: {
2191       scroll_offset.set_y((std::abs(scroll_offset.y()) <=
2192                            std::abs(scroll_offset.y() - plugin_size_.height()))
2193                               ? scroll_offset.y()
2194                               : scroll_offset.y() - plugin_size_.height());
2195       break;
2196     }
2197     case PP_PDF_SCROLL_NONE:
2198       scroll_offset.set_y(0);
2199       break;
2200     case PP_PDF_SCROLL_ALIGNMENT_TOP:
2201     case PP_PDF_SCROLL_ALIGNMENT_LEFT:
2202     case PP_PDF_SCROLL_ALIGNMENT_RIGHT:
2203     default:
2204       break;
2205   }
2206 
2207   client_->ScrollBy(scroll_offset);
2208 }
2209 
ScrollToGlobalPoint(const pp::Rect & target_rect,const pp::Point & global_point)2210 void PDFiumEngine::ScrollToGlobalPoint(const pp::Rect& target_rect,
2211                                        const pp::Point& global_point) {
2212   pp::Point scroll_offset = GetScreenRect(target_rect).point();
2213   client_->ScrollBy(scroll_offset - global_point);
2214 }
2215 
GetNamedDestination(const std::string & destination)2216 base::Optional<PDFEngine::NamedDestination> PDFiumEngine::GetNamedDestination(
2217     const std::string& destination) {
2218   // Look for the destination.
2219   FPDF_DEST dest = FPDF_GetNamedDestByName(doc(), destination.c_str());
2220   if (!dest) {
2221     // Look for a bookmark with the same name.
2222     base::string16 destination_wide = base::UTF8ToUTF16(destination);
2223     FPDF_WIDESTRING destination_pdf_wide =
2224         reinterpret_cast<FPDF_WIDESTRING>(destination_wide.c_str());
2225     FPDF_BOOKMARK bookmark = FPDFBookmark_Find(doc(), destination_pdf_wide);
2226     if (bookmark)
2227       dest = FPDFBookmark_GetDest(doc(), bookmark);
2228   }
2229 
2230   if (!dest)
2231     return {};
2232 
2233   int page = FPDFDest_GetDestPageIndex(doc(), dest);
2234   if (page < 0)
2235     return {};
2236 
2237   PDFEngine::NamedDestination result;
2238   result.page = page;
2239   unsigned long view_int =
2240       FPDFDest_GetView(dest, &result.num_params, result.params);
2241   result.view = ConvertViewIntToViewString(view_int);
2242   return result;
2243 }
2244 
GetMostVisiblePage()2245 int PDFiumEngine::GetMostVisiblePage() {
2246   if (in_flight_visible_page_)
2247     return *in_flight_visible_page_;
2248 
2249   // We can call GetMostVisiblePage through a callback from PDFium. We have
2250   // to defer the page deletion otherwise we could potentially delete the page
2251   // that originated the calling JS request and destroy the objects that are
2252   // currently being used.
2253   base::AutoReset<bool> defer_page_unload_guard(&defer_page_unload_, true);
2254   CalculateVisiblePages();
2255   return most_visible_page_;
2256 }
2257 
GetPageBoundsRect(int index)2258 pp::Rect PDFiumEngine::GetPageBoundsRect(int index) {
2259   return pages_[index]->rect();
2260 }
2261 
GetPageContentsRect(int index)2262 pp::Rect PDFiumEngine::GetPageContentsRect(int index) {
2263   return GetScreenRect(pages_[index]->rect());
2264 }
2265 
GetVerticalScrollbarYPosition()2266 int PDFiumEngine::GetVerticalScrollbarYPosition() {
2267   return position_.y();
2268 }
2269 
SetGrayscale(bool grayscale)2270 void PDFiumEngine::SetGrayscale(bool grayscale) {
2271   render_grayscale_ = grayscale;
2272 }
2273 
HandleLongPress(const pp::TouchInputEvent & event)2274 void PDFiumEngine::HandleLongPress(const pp::TouchInputEvent& event) {
2275   pp::FloatPoint fp =
2276       event.GetTouchByIndex(PP_TOUCHLIST_TYPE_TARGETTOUCHES, 0).position();
2277   pp::Point point;
2278   point.set_x(fp.x());
2279   point.set_y(fp.y());
2280 
2281   // Send a fake mouse down to trigger the multi-click selection code.
2282   pp::MouseInputEvent mouse_event(
2283       client_->GetPluginInstance(), PP_INPUTEVENT_TYPE_MOUSEDOWN,
2284       event.GetTimeStamp(), event.GetModifiers(),
2285       PP_INPUTEVENT_MOUSEBUTTON_LEFT, point, 2, point);
2286 
2287   OnMouseDown(mouse_event);
2288 }
2289 
GetCharCount(int page_index)2290 int PDFiumEngine::GetCharCount(int page_index) {
2291   DCHECK(PageIndexInBounds(page_index));
2292   return pages_[page_index]->GetCharCount();
2293 }
2294 
GetCharBounds(int page_index,int char_index)2295 pp::FloatRect PDFiumEngine::GetCharBounds(int page_index, int char_index) {
2296   DCHECK(PageIndexInBounds(page_index));
2297   return pages_[page_index]->GetCharBounds(char_index);
2298 }
2299 
GetCharUnicode(int page_index,int char_index)2300 uint32_t PDFiumEngine::GetCharUnicode(int page_index, int char_index) {
2301   DCHECK(PageIndexInBounds(page_index));
2302   return pages_[page_index]->GetCharUnicode(char_index);
2303 }
2304 
2305 base::Optional<pp::PDF::PrivateAccessibilityTextRunInfo>
GetTextRunInfo(int page_index,int start_char_index)2306 PDFiumEngine::GetTextRunInfo(int page_index, int start_char_index) {
2307   DCHECK(PageIndexInBounds(page_index));
2308   return pages_[page_index]->GetTextRunInfo(start_char_index);
2309 }
2310 
GetLinkInfo(int page_index)2311 std::vector<PDFEngine::AccessibilityLinkInfo> PDFiumEngine::GetLinkInfo(
2312     int page_index) {
2313   DCHECK(PageIndexInBounds(page_index));
2314   return pages_[page_index]->GetLinkInfo();
2315 }
2316 
GetImageInfo(int page_index)2317 std::vector<PDFEngine::AccessibilityImageInfo> PDFiumEngine::GetImageInfo(
2318     int page_index) {
2319   DCHECK(PageIndexInBounds(page_index));
2320   return pages_[page_index]->GetImageInfo();
2321 }
2322 
2323 std::vector<PDFEngine::AccessibilityHighlightInfo>
GetHighlightInfo(int page_index)2324 PDFiumEngine::GetHighlightInfo(int page_index) {
2325   DCHECK(PageIndexInBounds(page_index));
2326   return pages_[page_index]->GetHighlightInfo();
2327 }
2328 
2329 std::vector<PDFEngine::AccessibilityTextFieldInfo>
GetTextFieldInfo(int page_index)2330 PDFiumEngine::GetTextFieldInfo(int page_index) {
2331   DCHECK(PageIndexInBounds(page_index));
2332   return pages_[page_index]->GetTextFieldInfo();
2333 }
2334 
GetPrintScaling()2335 bool PDFiumEngine::GetPrintScaling() {
2336   return !!FPDF_VIEWERREF_GetPrintScaling(doc());
2337 }
2338 
GetCopiesToPrint()2339 int PDFiumEngine::GetCopiesToPrint() {
2340   return FPDF_VIEWERREF_GetNumCopies(doc());
2341 }
2342 
GetDuplexType()2343 int PDFiumEngine::GetDuplexType() {
2344   return static_cast<int>(FPDF_VIEWERREF_GetDuplex(doc()));
2345 }
2346 
GetPageSizeAndUniformity(pp::Size * size)2347 bool PDFiumEngine::GetPageSizeAndUniformity(pp::Size* size) {
2348   if (pages_.empty())
2349     return false;
2350 
2351   pp::Size page_size = GetPageSize(0);
2352   for (size_t i = 1; i < pages_.size(); ++i) {
2353     if (page_size != GetPageSize(i))
2354       return false;
2355   }
2356 
2357   // Convert |page_size| back to points.
2358   size->set_width(
2359       ConvertUnit(page_size.width(), kPixelsPerInch, kPointsPerInch));
2360   size->set_height(
2361       ConvertUnit(page_size.height(), kPixelsPerInch, kPointsPerInch));
2362   return true;
2363 }
2364 
AppendBlankPages(size_t num_pages)2365 void PDFiumEngine::AppendBlankPages(size_t num_pages) {
2366   DCHECK_GT(num_pages, 0U);
2367 
2368   if (!doc())
2369     return;
2370 
2371   selection_.clear();
2372   pending_pages_.clear();
2373 
2374   // Delete all pages except the first one.
2375   while (pages_.size() > 1) {
2376     pages_.pop_back();
2377     FPDFPage_Delete(doc(), pages_.size());
2378   }
2379 
2380   // Create blank pages with the same size as the first page.
2381   pp::Size page_0_size = GetPageSize(0);
2382   double page_0_width_in_points =
2383       ConvertUnitDouble(page_0_size.width(), kPixelsPerInch, kPointsPerInch);
2384   double page_0_height_in_points =
2385       ConvertUnitDouble(page_0_size.height(), kPixelsPerInch, kPointsPerInch);
2386 
2387   for (size_t i = 1; i < num_pages; ++i) {
2388     {
2389       // Add a new page to the document, but delete the FPDF_PAGE object.
2390       ScopedFPDFPage temp_page(FPDFPage_New(doc(), i, page_0_width_in_points,
2391                                             page_0_height_in_points));
2392     }
2393 
2394     auto page = std::make_unique<PDFiumPage>(this, i);
2395     page->MarkAvailable();
2396     pages_.push_back(std::move(page));
2397   }
2398 
2399   DCHECK(document_loaded_);
2400   LoadPageInfo();
2401 }
2402 
LoadDocument()2403 void PDFiumEngine::LoadDocument() {
2404   // Check if the document is ready for loading. If it isn't just bail for now,
2405   // we will call LoadDocument() again later.
2406   if (!doc() && !doc_loader_->IsDocumentComplete()) {
2407     FX_DOWNLOADHINTS& download_hints = document_->download_hints();
2408     if (!FPDFAvail_IsDocAvail(fpdf_availability(), &download_hints))
2409       return;
2410   }
2411 
2412   // If we're in the middle of getting a password, just return. We will retry
2413   // loading the document after we get the password anyway.
2414   if (getting_password_)
2415     return;
2416 
2417   ScopedUnsupportedFeature scoped_unsupported_feature(this);
2418   bool needs_password = false;
2419   if (TryLoadingDoc(std::string(), &needs_password)) {
2420     ContinueLoadingDocument(std::string());
2421     return;
2422   }
2423   if (needs_password)
2424     GetPasswordAndLoad();
2425   else
2426     client_->DocumentLoadFailed();
2427 }
2428 
TryLoadingDoc(const std::string & password,bool * needs_password)2429 bool PDFiumEngine::TryLoadingDoc(const std::string& password,
2430                                  bool* needs_password) {
2431   *needs_password = false;
2432   if (doc()) {
2433     // This is probably not necessary, because it should have already been
2434     // called below in the |doc_| initialization path. However, the previous
2435     // call may have failed, so call it again for good measure.
2436     FX_DOWNLOADHINTS& download_hints = document_->download_hints();
2437     FPDFAvail_IsDocAvail(fpdf_availability(), &download_hints);
2438     return true;
2439   }
2440 
2441   if (!password.empty())
2442     password_tries_remaining_--;
2443   document_->LoadDocument(password);
2444   if (!doc()) {
2445     if (FPDF_GetLastError() == FPDF_ERR_PASSWORD)
2446       *needs_password = true;
2447     return false;
2448   }
2449 
2450   // Always call FPDFAvail_IsDocAvail() so PDFium initializes internal data
2451   // structures.
2452   FX_DOWNLOADHINTS& download_hints = document_->download_hints();
2453   FPDFAvail_IsDocAvail(fpdf_availability(), &download_hints);
2454   return true;
2455 }
2456 
GetPasswordAndLoad()2457 void PDFiumEngine::GetPasswordAndLoad() {
2458   getting_password_ = true;
2459   DCHECK(!doc());
2460   DCHECK_EQ(static_cast<unsigned long>(FPDF_ERR_PASSWORD), FPDF_GetLastError());
2461   client_->GetDocumentPassword(password_factory_.NewCallbackWithOutput(
2462       &PDFiumEngine::OnGetPasswordComplete));
2463 }
2464 
OnGetPasswordComplete(int32_t result,const pp::Var & password)2465 void PDFiumEngine::OnGetPasswordComplete(int32_t result,
2466                                          const pp::Var& password) {
2467   getting_password_ = false;
2468 
2469   std::string password_text;
2470   if (result == PP_OK && password.is_string())
2471     password_text = password.AsString();
2472   ContinueLoadingDocument(password_text);
2473 }
2474 
ContinueLoadingDocument(const std::string & password)2475 void PDFiumEngine::ContinueLoadingDocument(const std::string& password) {
2476   ScopedUnsupportedFeature scoped_unsupported_feature(this);
2477 
2478   bool needs_password = false;
2479   bool loaded = TryLoadingDoc(password, &needs_password);
2480   bool password_incorrect = !loaded && needs_password && !password.empty();
2481   if (password_incorrect && password_tries_remaining_ > 0) {
2482     GetPasswordAndLoad();
2483     return;
2484   }
2485 
2486   if (!doc()) {
2487     client_->DocumentLoadFailed();
2488     return;
2489   }
2490 
2491   if (FPDFDoc_GetPageMode(doc()) == PAGEMODE_USEOUTLINES)
2492     client_->DocumentHasUnsupportedFeature("Bookmarks");
2493 
2494   permissions_ = std::make_unique<PDFiumPermissions>(doc());
2495 
2496   LoadBody();
2497 
2498   if (doc_loader_->IsDocumentComplete())
2499     FinishLoadingDocument();
2500 }
2501 
LoadPageInfo()2502 void PDFiumEngine::LoadPageInfo() {
2503   RefreshCurrentDocumentLayout();
2504 
2505   // TODO(crbug.com/1013800): RefreshCurrentDocumentLayout() should send some
2506   // sort of "current layout changed" notification, instead of proposing a new
2507   // layout. Proposals are never rejected currently, so this is OK for now.
2508   ProposeNextDocumentLayout();
2509 }
2510 
RefreshCurrentDocumentLayout()2511 void PDFiumEngine::RefreshCurrentDocumentLayout() {
2512   UpdateDocumentLayout(&layout_);
2513   if (!layout_.dirty())
2514     return;
2515 
2516   DCHECK_EQ(pages_.size(), layout_.page_count());
2517   for (size_t i = 0; i < layout_.page_count(); ++i) {
2518     // TODO(kmoon): This should be the only place that sets |PDFiumPage::rect_|.
2519     pages_[i]->set_rect(layout_.page_bounds_rect(i));
2520   }
2521 
2522   layout_.clear_dirty();
2523 
2524   CalculateVisiblePages();
2525 }
2526 
ProposeNextDocumentLayout()2527 void PDFiumEngine::ProposeNextDocumentLayout() {
2528   DocumentLayout next_layout;
2529   next_layout.SetOptions(desired_layout_options_);
2530   UpdateDocumentLayout(&next_layout);
2531 
2532   // The time windows between proposal and application may overlap, so we must
2533   // always propose a new layout, regardless of the current layout state.
2534   client_->ProposeDocumentLayout(next_layout);
2535 }
2536 
UpdateDocumentLayout(DocumentLayout * layout)2537 void PDFiumEngine::UpdateDocumentLayout(DocumentLayout* layout) {
2538   std::vector<pp::Size> page_sizes = LoadPageSizes(layout->options());
2539   if (page_sizes.empty())
2540     return;
2541 
2542   if (layout->options().two_up_view_enabled()) {
2543     layout->ComputeTwoUpViewLayout(page_sizes);
2544   } else {
2545     layout->ComputeSingleViewLayout(page_sizes);
2546   }
2547 }
2548 
LoadPageSizes(const DocumentLayout::Options & layout_options)2549 std::vector<pp::Size> PDFiumEngine::LoadPageSizes(
2550     const DocumentLayout::Options& layout_options) {
2551   std::vector<pp::Size> page_sizes;
2552   if (!doc_loader_)
2553     return page_sizes;
2554   if (pages_.empty() && document_loaded_)
2555     return page_sizes;
2556 
2557   pending_pages_.clear();
2558   size_t new_page_count = FPDF_GetPageCount(doc());
2559 
2560   bool doc_complete = doc_loader_->IsDocumentComplete();
2561   bool is_linear =
2562       FPDFAvail_IsLinearized(fpdf_availability()) == PDF_LINEARIZED;
2563   for (size_t i = 0; i < new_page_count; ++i) {
2564     // Get page availability. If |document_loaded_| == true and the page is not
2565     // new, then the page has been constructed already. Get page availability
2566     // flag from already existing PDFiumPage object. If |document_loaded_| ==
2567     // false or the page is new, then the page may not be fully loaded yet.
2568     bool page_available;
2569     if (document_loaded_ && i < pages_.size()) {
2570       page_available = pages_[i]->available();
2571     } else if (is_linear) {
2572       FX_DOWNLOADHINTS& download_hints = document_->download_hints();
2573       int linear_page_avail =
2574           FPDFAvail_IsPageAvail(fpdf_availability(), i, &download_hints);
2575       page_available = linear_page_avail == PDF_DATA_AVAIL;
2576     } else {
2577       page_available = doc_complete;
2578     }
2579 
2580     // TODO(crbug.com/1013800): It'd be better if page size were independent of
2581     // layout options, and handled in the layout code.
2582     pp::Size size = page_available ? GetPageSizeForLayout(i, layout_options)
2583                                    : default_page_size_;
2584     EnlargePage(layout_options, i, new_page_count, &size);
2585     page_sizes.push_back(size);
2586   }
2587 
2588   // Add new pages. If |document_loaded_| == false, do not mark page as
2589   // available even if |doc_complete| is true because FPDFAvail_IsPageAvail()
2590   // still has to be called for this page, which will be done in
2591   // FinishLoadingDocument().
2592   for (size_t i = pages_.size(); i < new_page_count; ++i) {
2593     auto page = std::make_unique<PDFiumPage>(this, i);
2594     if (document_loaded_ &&
2595         FPDFAvail_IsPageAvail(fpdf_availability(), i, nullptr))
2596       page->MarkAvailable();
2597     pages_.push_back(std::move(page));
2598   }
2599 
2600   // Remove pages that do not exist anymore.
2601   if (pages_.size() > new_page_count) {
2602     for (size_t i = new_page_count; i < pages_.size(); ++i)
2603       pages_[i]->Unload();
2604 
2605     pages_.resize(new_page_count);
2606   }
2607 
2608   return page_sizes;
2609 }
2610 
LoadBody()2611 void PDFiumEngine::LoadBody() {
2612   DCHECK(doc());
2613   DCHECK(fpdf_availability());
2614   if (doc_loader_->IsDocumentComplete()) {
2615     LoadForm();
2616   } else if (FPDFAvail_IsLinearized(fpdf_availability()) == PDF_LINEARIZED &&
2617              FPDF_GetPageCount(doc()) == 1) {
2618     // If we have only one page we should load form first, bacause it is may be
2619     // XFA document. And after loading form the page count and its contents may
2620     // be changed.
2621     LoadForm();
2622     if (document_->form_status() == PDF_FORM_NOTAVAIL)
2623       return;
2624   }
2625   LoadPages();
2626 }
2627 
LoadPages()2628 void PDFiumEngine::LoadPages() {
2629   if (pages_.empty()) {
2630     if (!doc_loader_->IsDocumentComplete()) {
2631       // Check if the first page is available.  In a linearized PDF, that is not
2632       // always page 0.  Doing this gives us the default page size, since when
2633       // the document is available, the first page is available as well.
2634       CheckPageAvailable(FPDFAvail_GetFirstPageNum(doc()), &pending_pages_);
2635     }
2636     DCHECK(!document_loaded_);
2637     LoadPageInfo();
2638   }
2639 }
2640 
LoadForm()2641 void PDFiumEngine::LoadForm() {
2642   if (form())
2643     return;
2644 
2645   DCHECK(doc());
2646   document_->SetFormStatus();
2647   if (document_->form_status() != PDF_FORM_NOTAVAIL ||
2648       doc_loader_->IsDocumentComplete()) {
2649     document_->InitializeForm(&form_filler_);
2650 #if defined(PDF_ENABLE_XFA)
2651     FPDF_LoadXFA(doc());
2652 #endif
2653 
2654     FPDF_SetFormFieldHighlightColor(form(), FPDF_FORMFIELD_UNKNOWN,
2655                                     kFormHighlightColor);
2656     FPDF_SetFormFieldHighlightAlpha(form(), kFormHighlightAlpha);
2657   }
2658 }
2659 
CalculateVisiblePages()2660 void PDFiumEngine::CalculateVisiblePages() {
2661   // Early return if the PDF isn't being loaded or if we don't have the document
2662   // info yet. The latter is important because otherwise as the PDF is being
2663   // initialized by the renderer there could be races that call this method
2664   // before we get the initial network responses. The document loader depends on
2665   // the list of pending requests to be valid for progressive loading to
2666   // function.
2667   if (!doc_loader_ || pages_.empty())
2668     return;
2669   // Clear pending requests queue, since it may contain requests to the pages
2670   // that are already invisible (after scrolling for example).
2671   pending_pages_.clear();
2672   doc_loader_->ClearPendingRequests();
2673 
2674   visible_pages_.clear();
2675   pp::Rect visible_rect(plugin_size_);
2676   for (int i = 0; i < static_cast<int>(pages_.size()); ++i) {
2677     // Check an entire PageScreenRect, since we might need to repaint side
2678     // borders and shadows even if the page itself is not visible.
2679     // For example, when user use pdf with different page sizes and zoomed in
2680     // outside page area.
2681     if (visible_rect.Intersects(GetPageScreenRect(i))) {
2682       visible_pages_.push_back(i);
2683       CheckPageAvailable(i, &pending_pages_);
2684     } else {
2685       // Need to unload pages when we're not using them, since some PDFs use a
2686       // lot of memory.  See http://crbug.com/48791
2687       if (defer_page_unload_) {
2688         deferred_page_unloads_.push_back(i);
2689       } else {
2690         pages_[i]->Unload();
2691       }
2692 
2693       // If the last mouse down was on a page that's no longer visible, reset
2694       // that variable so that we don't send keyboard events to it (the focus
2695       // will be lost when the page is first closed anyways).
2696       if (static_cast<int>(i) == last_page_mouse_down_)
2697         last_page_mouse_down_ = -1;
2698     }
2699   }
2700 
2701   // Any pending highlighting of form fields will be invalid since these are in
2702   // screen coordinates.
2703   form_highlights_.clear();
2704 
2705   std::vector<draw_utils::IndexedPage> visible_pages_rects;
2706   visible_pages_rects.reserve(visible_pages_.size());
2707   for (int visible_page_index : visible_pages_) {
2708     visible_pages_rects.push_back(
2709         {visible_page_index, pages_[visible_page_index]->rect()});
2710   }
2711 
2712   int most_visible_page =
2713       draw_utils::GetMostVisiblePage(visible_pages_rects, GetVisibleRect());
2714   SetCurrentPage(most_visible_page);
2715 }
2716 
IsPageVisible(int index) const2717 bool PDFiumEngine::IsPageVisible(int index) const {
2718   return base::Contains(visible_pages_, index);
2719 }
2720 
ScrollToPage(int page)2721 void PDFiumEngine::ScrollToPage(int page) {
2722   if (!PageIndexInBounds(page))
2723     return;
2724 
2725   in_flight_visible_page_ = page;
2726   client_->ScrollToPage(page);
2727 }
2728 
CheckPageAvailable(int index,std::vector<int> * pending)2729 bool PDFiumEngine::CheckPageAvailable(int index, std::vector<int>* pending) {
2730   if (!doc())
2731     return false;
2732 
2733   const int num_pages = static_cast<int>(pages_.size());
2734   if (index < num_pages && pages_[index]->available())
2735     return true;
2736 
2737   FX_DOWNLOADHINTS& download_hints = document_->download_hints();
2738   if (!FPDFAvail_IsPageAvail(fpdf_availability(), index, &download_hints)) {
2739     if (!base::Contains(*pending, index))
2740       pending->push_back(index);
2741     return false;
2742   }
2743 
2744   if (index < num_pages)
2745     pages_[index]->MarkAvailable();
2746   if (default_page_size_.IsEmpty())
2747     default_page_size_ = GetPageSize(index);
2748   return true;
2749 }
2750 
GetPageSize(int index)2751 pp::Size PDFiumEngine::GetPageSize(int index) {
2752   return GetPageSizeForLayout(index, layout_.options());
2753 }
2754 
GetPageSizeForLayout(int index,const DocumentLayout::Options & layout_options)2755 pp::Size PDFiumEngine::GetPageSizeForLayout(
2756     int index,
2757     const DocumentLayout::Options& layout_options) {
2758   pp::Size size;
2759   double width_in_points = 0;
2760   double height_in_points = 0;
2761   int rv = FPDF_GetPageSizeByIndex(doc(), index, &width_in_points,
2762                                    &height_in_points);
2763 
2764   if (rv) {
2765     int width_in_pixels = static_cast<int>(
2766         ConvertUnitDouble(width_in_points, kPointsPerInch, kPixelsPerInch));
2767     int height_in_pixels = static_cast<int>(
2768         ConvertUnitDouble(height_in_points, kPointsPerInch, kPixelsPerInch));
2769 
2770     switch (layout_options.default_page_orientation()) {
2771       case PageOrientation::kOriginal:
2772       case PageOrientation::kClockwise180:
2773         // No axis swap needed.
2774         break;
2775       case PageOrientation::kClockwise90:
2776       case PageOrientation::kClockwise270:
2777         // Rotated 90 degrees: swap axes.
2778         std::swap(width_in_pixels, height_in_pixels);
2779         break;
2780     }
2781 
2782     size = pp::Size(width_in_pixels, height_in_pixels);
2783   }
2784   return size;
2785 }
2786 
GetInsetSizes(const DocumentLayout::Options & layout_options,size_t page_index,size_t num_of_pages) const2787 draw_utils::PageInsetSizes PDFiumEngine::GetInsetSizes(
2788     const DocumentLayout::Options& layout_options,
2789     size_t page_index,
2790     size_t num_of_pages) const {
2791   DCHECK_LT(page_index, num_of_pages);
2792 
2793   if (layout_options.two_up_view_enabled()) {
2794     return draw_utils::GetPageInsetsForTwoUpView(
2795         page_index, num_of_pages, DocumentLayout::kSingleViewInsets,
2796         DocumentLayout::kHorizontalSeparator);
2797   }
2798 
2799   return DocumentLayout::kSingleViewInsets;
2800 }
2801 
EnlargePage(const DocumentLayout::Options & layout_options,size_t page_index,size_t num_of_pages,pp::Size * page_size) const2802 void PDFiumEngine::EnlargePage(const DocumentLayout::Options& layout_options,
2803                                size_t page_index,
2804                                size_t num_of_pages,
2805                                pp::Size* page_size) const {
2806   draw_utils::PageInsetSizes inset_sizes =
2807       GetInsetSizes(layout_options, page_index, num_of_pages);
2808   page_size->Enlarge(inset_sizes.left + inset_sizes.right,
2809                      inset_sizes.top + inset_sizes.bottom);
2810 }
2811 
InsetPage(const DocumentLayout::Options & layout_options,size_t page_index,size_t num_of_pages,double multiplier,pp::Rect * rect) const2812 void PDFiumEngine::InsetPage(const DocumentLayout::Options& layout_options,
2813                              size_t page_index,
2814                              size_t num_of_pages,
2815                              double multiplier,
2816                              pp::Rect* rect) const {
2817   draw_utils::PageInsetSizes inset_sizes =
2818       GetInsetSizes(layout_options, page_index, num_of_pages);
2819   rect->Inset(static_cast<int>(ceil(inset_sizes.left * multiplier)),
2820               static_cast<int>(ceil(inset_sizes.top * multiplier)),
2821               static_cast<int>(ceil(inset_sizes.right * multiplier)),
2822               static_cast<int>(ceil(inset_sizes.bottom * multiplier)));
2823 }
2824 
GetAdjacentPageIndexForTwoUpView(size_t page_index,size_t num_of_pages) const2825 base::Optional<size_t> PDFiumEngine::GetAdjacentPageIndexForTwoUpView(
2826     size_t page_index,
2827     size_t num_of_pages) const {
2828   DCHECK_LT(page_index, num_of_pages);
2829 
2830   if (!layout_.options().two_up_view_enabled())
2831     return base::nullopt;
2832 
2833   int adjacent_page_offset = page_index % 2 ? -1 : 1;
2834   size_t adjacent_page_index = page_index + adjacent_page_offset;
2835   if (adjacent_page_index >= num_of_pages)
2836     return base::nullopt;
2837 
2838   return adjacent_page_index;
2839 }
2840 
StartPaint(int page_index,const pp::Rect & dirty)2841 int PDFiumEngine::StartPaint(int page_index, const pp::Rect& dirty) {
2842   // For the first time we hit paint, do nothing and just record the paint for
2843   // the next callback.  This keeps the UI responsive in case the user is doing
2844   // a lot of scrolling.
2845   progressive_paints_.emplace_back(page_index, dirty);
2846   return progressive_paints_.size() - 1;
2847 }
2848 
ContinuePaint(int progressive_index,pp::ImageData * image_data)2849 bool PDFiumEngine::ContinuePaint(int progressive_index,
2850                                  pp::ImageData* image_data) {
2851   DCHECK_GE(progressive_index, 0);
2852   DCHECK_LT(static_cast<size_t>(progressive_index), progressive_paints_.size());
2853   DCHECK(image_data);
2854 
2855   last_progressive_start_time_ = base::Time::Now();
2856 #if defined(OS_LINUX) || defined(OS_BSD)
2857   SetLastInstance(client_->GetPluginInstance());
2858 #endif
2859 
2860   int page_index = progressive_paints_[progressive_index].page_index();
2861   DCHECK(PageIndexInBounds(page_index));
2862 
2863   int rv;
2864   FPDF_PAGE page = pages_[page_index]->GetPage();
2865   if (progressive_paints_[progressive_index].bitmap()) {
2866     rv = FPDF_RenderPage_Continue(page, this);
2867   } else {
2868     int start_x;
2869     int start_y;
2870     int size_x;
2871     int size_y;
2872     pp::Rect dirty = progressive_paints_[progressive_index].rect();
2873     GetPDFiumRect(page_index, dirty, &start_x, &start_y, &size_x, &size_y);
2874 
2875     ScopedFPDFBitmap new_bitmap = CreateBitmap(dirty, image_data);
2876     FPDFBitmap_FillRect(new_bitmap.get(), start_x, start_y, size_x, size_y,
2877                         0xFFFFFFFF);
2878     rv = FPDF_RenderPageBitmap_Start(
2879         new_bitmap.get(), page, start_x, start_y, size_x, size_y,
2880         ToPDFiumRotation(layout_.options().default_page_orientation()),
2881         GetRenderingFlags(), this);
2882     progressive_paints_[progressive_index].SetBitmapAndImageData(
2883         std::move(new_bitmap), *image_data);
2884   }
2885   return rv != FPDF_RENDER_TOBECONTINUED;
2886 }
2887 
FinishPaint(int progressive_index,pp::ImageData * image_data)2888 void PDFiumEngine::FinishPaint(int progressive_index,
2889                                pp::ImageData* image_data) {
2890   DCHECK_GE(progressive_index, 0);
2891   DCHECK_LT(static_cast<size_t>(progressive_index), progressive_paints_.size());
2892   DCHECK(image_data);
2893 
2894   int page_index = progressive_paints_[progressive_index].page_index();
2895   const pp::Rect& dirty_in_screen =
2896       progressive_paints_[progressive_index].rect();
2897 
2898   int start_x;
2899   int start_y;
2900   int size_x;
2901   int size_y;
2902   FPDF_BITMAP bitmap = progressive_paints_[progressive_index].bitmap();
2903   GetPDFiumRect(page_index, dirty_in_screen, &start_x, &start_y, &size_x,
2904                 &size_y);
2905 
2906   // Draw the forms.
2907   FPDF_FFLDraw(form(), bitmap, pages_[page_index]->GetPage(), start_x, start_y,
2908                size_x, size_y,
2909                ToPDFiumRotation(layout_.options().default_page_orientation()),
2910                GetRenderingFlags());
2911 
2912   FillPageSides(progressive_index);
2913 
2914   // Paint the page shadows.
2915   PaintPageShadow(progressive_index, image_data);
2916 
2917   DrawSelections(progressive_index, image_data);
2918   form_highlights_.clear();
2919 
2920   FPDF_RenderPage_Close(pages_[page_index]->GetPage());
2921   progressive_paints_.erase(progressive_paints_.begin() + progressive_index);
2922 }
2923 
CancelPaints()2924 void PDFiumEngine::CancelPaints() {
2925   for (const auto& paint : progressive_paints_)
2926     FPDF_RenderPage_Close(pages_[paint.page_index()]->GetPage());
2927 
2928   progressive_paints_.clear();
2929 }
2930 
FillPageSides(int progressive_index)2931 void PDFiumEngine::FillPageSides(int progressive_index) {
2932   DCHECK_GE(progressive_index, 0);
2933   DCHECK_LT(static_cast<size_t>(progressive_index), progressive_paints_.size());
2934 
2935   int page_index = progressive_paints_[progressive_index].page_index();
2936   const pp::Rect& dirty_in_screen =
2937       progressive_paints_[progressive_index].rect();
2938   FPDF_BITMAP bitmap = progressive_paints_[progressive_index].bitmap();
2939   draw_utils::PageInsetSizes inset_sizes =
2940       GetInsetSizes(layout_.options(), page_index, pages_.size());
2941 
2942   pp::Rect page_rect = pages_[page_index]->rect();
2943   const bool is_two_up_view = layout_.options().two_up_view_enabled();
2944   if (page_rect.x() > 0 && (!is_two_up_view || page_index % 2 == 0)) {
2945     // If in two-up view, only need to draw the left empty space for left pages
2946     // since the gap between the left and right page will be drawn by the left
2947     // page.
2948     pp::Rect left_in_screen = GetScreenRect(draw_utils::GetLeftFillRect(
2949         page_rect, inset_sizes, DocumentLayout::kBottomSeparator));
2950     left_in_screen = left_in_screen.Intersect(dirty_in_screen);
2951 
2952     FPDFBitmap_FillRect(bitmap, left_in_screen.x() - dirty_in_screen.x(),
2953                         left_in_screen.y() - dirty_in_screen.y(),
2954                         left_in_screen.width(), left_in_screen.height(),
2955                         client_->GetBackgroundColor());
2956   }
2957 
2958   if (page_rect.right() < layout_.size().width()) {
2959     pp::Rect right_in_screen = GetScreenRect(draw_utils::GetRightFillRect(
2960         page_rect, inset_sizes, layout_.size().width(),
2961         DocumentLayout::kBottomSeparator));
2962     right_in_screen = right_in_screen.Intersect(dirty_in_screen);
2963 
2964     FPDFBitmap_FillRect(bitmap, right_in_screen.x() - dirty_in_screen.x(),
2965                         right_in_screen.y() - dirty_in_screen.y(),
2966                         right_in_screen.width(), right_in_screen.height(),
2967                         client_->GetBackgroundColor());
2968   }
2969 
2970   pp::Rect bottom_in_screen;
2971   if (is_two_up_view) {
2972     pp::Rect page_in_screen = GetScreenRect(page_rect);
2973     bottom_in_screen = draw_utils::GetBottomGapBetweenRects(
2974         page_in_screen.bottom(), dirty_in_screen);
2975 
2976     if (page_index % 2 == 1) {
2977       draw_utils::AdjustBottomGapForRightSidePage(page_in_screen.x(),
2978                                                   &bottom_in_screen);
2979     }
2980 
2981     bottom_in_screen = bottom_in_screen.Intersect(dirty_in_screen);
2982   } else {
2983     bottom_in_screen = GetScreenRect(draw_utils::GetBottomFillRect(
2984         page_rect, inset_sizes, DocumentLayout::kBottomSeparator));
2985     bottom_in_screen = bottom_in_screen.Intersect(dirty_in_screen);
2986   }
2987 
2988   FPDFBitmap_FillRect(bitmap, bottom_in_screen.x() - dirty_in_screen.x(),
2989                       bottom_in_screen.y() - dirty_in_screen.y(),
2990                       bottom_in_screen.width(), bottom_in_screen.height(),
2991                       client_->GetBackgroundColor());
2992 }
2993 
PaintPageShadow(int progressive_index,pp::ImageData * image_data)2994 void PDFiumEngine::PaintPageShadow(int progressive_index,
2995                                    pp::ImageData* image_data) {
2996   DCHECK_GE(progressive_index, 0);
2997   DCHECK_LT(static_cast<size_t>(progressive_index), progressive_paints_.size());
2998   DCHECK(image_data);
2999 
3000   int page_index = progressive_paints_[progressive_index].page_index();
3001   const pp::Rect& dirty_in_screen =
3002       progressive_paints_[progressive_index].rect();
3003   pp::Rect page_rect = pages_[page_index]->rect();
3004   pp::Rect shadow_rect(page_rect);
3005   InsetPage(layout_.options(), page_index, pages_.size(), /*multiplier=*/-1,
3006             &shadow_rect);
3007 
3008   // Due to the rounding errors of the GetScreenRect it is possible to get
3009   // different size shadows on the left and right sides even they are defined
3010   // the same. To fix this issue let's calculate shadow rect and then shrink
3011   // it by the size of the shadows.
3012   shadow_rect = GetScreenRect(shadow_rect);
3013   page_rect = shadow_rect;
3014   InsetPage(layout_.options(), page_index, pages_.size(),
3015             /*multiplier=*/current_zoom_, &page_rect);
3016 
3017   DrawPageShadow(page_rect, shadow_rect, dirty_in_screen, image_data);
3018 }
3019 
DrawSelections(int progressive_index,pp::ImageData * image_data) const3020 void PDFiumEngine::DrawSelections(int progressive_index,
3021                                   pp::ImageData* image_data) const {
3022   DCHECK_GE(progressive_index, 0);
3023   DCHECK_LT(static_cast<size_t>(progressive_index), progressive_paints_.size());
3024   DCHECK(image_data);
3025 
3026   int page_index = progressive_paints_[progressive_index].page_index();
3027   const pp::Rect& dirty_in_screen =
3028       progressive_paints_[progressive_index].rect();
3029 
3030   void* region = nullptr;
3031   int stride;
3032   GetRegion(dirty_in_screen.point(), image_data, &region, &stride);
3033 
3034   std::vector<pp::Rect> highlighted_rects;
3035   pp::Rect visible_rect = GetVisibleRect();
3036   for (const auto& range : selection_) {
3037     if (range.page_index() != page_index)
3038       continue;
3039 
3040     const std::vector<pp::Rect>& rects =
3041         range.GetScreenRects(visible_rect.point(), current_zoom_,
3042                              layout_.options().default_page_orientation());
3043     for (const auto& rect : rects) {
3044       pp::Rect visible_selection = rect.Intersect(dirty_in_screen);
3045       if (visible_selection.IsEmpty())
3046         continue;
3047 
3048       visible_selection.Offset(-dirty_in_screen.point().x(),
3049                                -dirty_in_screen.point().y());
3050       Highlight(region, stride, visible_selection, kHighlightColorR,
3051                 kHighlightColorG, kHighlightColorB, &highlighted_rects);
3052     }
3053   }
3054 
3055   for (const auto& highlight : form_highlights_) {
3056     pp::Rect visible_selection = highlight.Intersect(dirty_in_screen);
3057     if (visible_selection.IsEmpty())
3058       continue;
3059 
3060     visible_selection.Offset(-dirty_in_screen.point().x(),
3061                              -dirty_in_screen.point().y());
3062     Highlight(region, stride, visible_selection, kHighlightColorR,
3063               kHighlightColorG, kHighlightColorB, &highlighted_rects);
3064   }
3065 }
3066 
PaintUnavailablePage(int page_index,const pp::Rect & dirty,pp::ImageData * image_data)3067 void PDFiumEngine::PaintUnavailablePage(int page_index,
3068                                         const pp::Rect& dirty,
3069                                         pp::ImageData* image_data) {
3070   int start_x;
3071   int start_y;
3072   int size_x;
3073   int size_y;
3074   GetPDFiumRect(page_index, dirty, &start_x, &start_y, &size_x, &size_y);
3075   ScopedFPDFBitmap bitmap(CreateBitmap(dirty, image_data));
3076   FPDFBitmap_FillRect(bitmap.get(), start_x, start_y, size_x, size_y,
3077                       kPendingPageColor);
3078 
3079   pp::Rect loading_text_in_screen(
3080       pages_[page_index]->rect().width() / 2,
3081       pages_[page_index]->rect().y() + kLoadingTextVerticalOffset, 0, 0);
3082   loading_text_in_screen = GetScreenRect(loading_text_in_screen);
3083 }
3084 
GetProgressiveIndex(int page_index) const3085 int PDFiumEngine::GetProgressiveIndex(int page_index) const {
3086   for (size_t i = 0; i < progressive_paints_.size(); ++i) {
3087     if (progressive_paints_[i].page_index() == page_index)
3088       return i;
3089   }
3090   return -1;
3091 }
3092 
CreateBitmap(const pp::Rect & rect,pp::ImageData * image_data) const3093 ScopedFPDFBitmap PDFiumEngine::CreateBitmap(const pp::Rect& rect,
3094                                             pp::ImageData* image_data) const {
3095   void* region;
3096   int stride;
3097   GetRegion(rect.point(), image_data, &region, &stride);
3098   if (!region)
3099     return nullptr;
3100   return ScopedFPDFBitmap(FPDFBitmap_CreateEx(rect.width(), rect.height(),
3101                                               FPDFBitmap_BGRx, region, stride));
3102 }
3103 
GetPDFiumRect(int page_index,const pp::Rect & rect,int * start_x,int * start_y,int * size_x,int * size_y) const3104 void PDFiumEngine::GetPDFiumRect(int page_index,
3105                                  const pp::Rect& rect,
3106                                  int* start_x,
3107                                  int* start_y,
3108                                  int* size_x,
3109                                  int* size_y) const {
3110   pp::Rect page_rect = GetScreenRect(pages_[page_index]->rect());
3111   page_rect.Offset(-rect.x(), -rect.y());
3112 
3113   *start_x = page_rect.x();
3114   *start_y = page_rect.y();
3115   *size_x = page_rect.width();
3116   *size_y = page_rect.height();
3117 }
3118 
GetRenderingFlags() const3119 int PDFiumEngine::GetRenderingFlags() const {
3120   int flags = FPDF_LCD_TEXT;
3121   if (render_grayscale_)
3122     flags |= FPDF_GRAYSCALE;
3123   if (client_->IsPrintPreview())
3124     flags |= FPDF_PRINTING;
3125   if (render_annots_)
3126     flags |= FPDF_ANNOT;
3127   return flags;
3128 }
3129 
GetVisibleRect() const3130 pp::Rect PDFiumEngine::GetVisibleRect() const {
3131   pp::Rect rv;
3132   rv.set_x(static_cast<int>(position_.x() / current_zoom_));
3133   rv.set_y(static_cast<int>(position_.y() / current_zoom_));
3134   rv.set_width(static_cast<int>(ceil(plugin_size_.width() / current_zoom_)));
3135   rv.set_height(static_cast<int>(ceil(plugin_size_.height() / current_zoom_)));
3136   return rv;
3137 }
3138 
GetPageScreenRect(int page_index) const3139 pp::Rect PDFiumEngine::GetPageScreenRect(int page_index) const {
3140   const pp::Rect& page_rect = pages_[page_index]->rect();
3141   draw_utils::PageInsetSizes inset_sizes =
3142       GetInsetSizes(layout_.options(), page_index, pages_.size());
3143 
3144   int max_page_height = page_rect.height();
3145   base::Optional<size_t> adjacent_page_index =
3146       GetAdjacentPageIndexForTwoUpView(page_index, pages_.size());
3147   if (adjacent_page_index.has_value()) {
3148     max_page_height = std::max(
3149         max_page_height, pages_[adjacent_page_index.value()]->rect().height());
3150   }
3151 
3152   return GetScreenRect(draw_utils::GetSurroundingRect(
3153       page_rect.y(), max_page_height, inset_sizes, layout_.size().width(),
3154       DocumentLayout::kBottomSeparator));
3155 }
3156 
GetScreenRect(const pp::Rect & rect) const3157 pp::Rect PDFiumEngine::GetScreenRect(const pp::Rect& rect) const {
3158   return draw_utils::GetScreenRect(rect, position_, current_zoom_);
3159 }
3160 
Highlight(void * buffer,int stride,const pp::Rect & rect,int color_red,int color_green,int color_blue,std::vector<pp::Rect> * highlighted_rects) const3161 void PDFiumEngine::Highlight(void* buffer,
3162                              int stride,
3163                              const pp::Rect& rect,
3164                              int color_red,
3165                              int color_green,
3166                              int color_blue,
3167                              std::vector<pp::Rect>* highlighted_rects) const {
3168   if (!buffer)
3169     return;
3170 
3171   pp::Rect new_rect = rect;
3172   for (const auto& highlighted : *highlighted_rects)
3173     new_rect = new_rect.Subtract(highlighted);
3174   if (new_rect.IsEmpty())
3175     return;
3176 
3177   std::vector<size_t> overlapping_rect_indices;
3178   for (size_t i = 0; i < highlighted_rects->size(); ++i) {
3179     if (new_rect.Intersects((*highlighted_rects)[i]))
3180       overlapping_rect_indices.push_back(i);
3181   }
3182 
3183   highlighted_rects->push_back(new_rect);
3184   int l = new_rect.x();
3185   int t = new_rect.y();
3186   int w = new_rect.width();
3187   int h = new_rect.height();
3188 
3189   for (int y = t; y < t + h; ++y) {
3190     for (int x = l; x < l + w; ++x) {
3191       bool overlaps = false;
3192       for (size_t i : overlapping_rect_indices) {
3193         const auto& highlighted = (*highlighted_rects)[i];
3194         if (highlighted.Contains(x, y)) {
3195           overlaps = true;
3196           break;
3197         }
3198       }
3199       if (overlaps)
3200         continue;
3201 
3202       uint8_t* pixel = static_cast<uint8_t*>(buffer) + y * stride + x * 4;
3203       pixel[0] = static_cast<uint8_t>(pixel[0] * (color_blue / 255.0));
3204       pixel[1] = static_cast<uint8_t>(pixel[1] * (color_green / 255.0));
3205       pixel[2] = static_cast<uint8_t>(pixel[2] * (color_red / 255.0));
3206     }
3207   }
3208 }
3209 
SelectionChangeInvalidator(PDFiumEngine * engine)3210 PDFiumEngine::SelectionChangeInvalidator::SelectionChangeInvalidator(
3211     PDFiumEngine* engine)
3212     : engine_(engine),
3213       previous_origin_(engine_->GetVisibleRect().point()),
3214       old_selections_(GetVisibleSelections()) {}
3215 
~SelectionChangeInvalidator()3216 PDFiumEngine::SelectionChangeInvalidator::~SelectionChangeInvalidator() {
3217   // Offset the old selections if the document scrolled since we recorded them.
3218   pp::Point offset = previous_origin_ - engine_->GetVisibleRect().point();
3219   for (auto& old_selection : old_selections_)
3220     old_selection.Offset(offset);
3221 
3222   std::vector<pp::Rect> new_selections = GetVisibleSelections();
3223   for (auto& new_selection : new_selections) {
3224     for (auto& old_selection : old_selections_) {
3225       if (!old_selection.IsEmpty() && new_selection == old_selection) {
3226         // Rectangle was selected before and after, so no need to invalidate it.
3227         // Mark the rectangles by setting them to empty.
3228         new_selection = old_selection = pp::Rect();
3229         break;
3230       }
3231     }
3232   }
3233 
3234   bool selection_changed = false;
3235   for (const auto& old_selection : old_selections_) {
3236     if (!old_selection.IsEmpty()) {
3237       Invalidate(old_selection);
3238       selection_changed = true;
3239     }
3240   }
3241   for (const auto& new_selection : new_selections) {
3242     if (!new_selection.IsEmpty()) {
3243       Invalidate(new_selection);
3244       selection_changed = true;
3245     }
3246   }
3247 
3248   if (selection_changed) {
3249     engine_->OnSelectionTextChanged();
3250     engine_->OnSelectionPositionChanged();
3251   }
3252 }
3253 
3254 std::vector<pp::Rect>
GetVisibleSelections() const3255 PDFiumEngine::SelectionChangeInvalidator::GetVisibleSelections() const {
3256   std::vector<pp::Rect> rects;
3257   pp::Point visible_point = engine_->GetVisibleRect().point();
3258   for (const auto& range : engine_->selection_) {
3259     // Exclude selections on pages that's not currently visible.
3260     if (!engine_->IsPageVisible(range.page_index()))
3261       continue;
3262 
3263     const std::vector<pp::Rect>& selection_rects = range.GetScreenRects(
3264         visible_point, engine_->current_zoom_,
3265         engine_->layout_.options().default_page_orientation());
3266     rects.insert(rects.end(), selection_rects.begin(), selection_rects.end());
3267   }
3268   return rects;
3269 }
3270 
Invalidate(const pp::Rect & selection)3271 void PDFiumEngine::SelectionChangeInvalidator::Invalidate(
3272     const pp::Rect& selection) {
3273   pp::Rect expanded_selection = selection;
3274   expanded_selection.Inset(-1, -1);
3275   engine_->client_->Invalidate(expanded_selection);
3276 }
3277 
MouseDownState(const PDFiumPage::Area & area,const PDFiumPage::LinkTarget & target)3278 PDFiumEngine::MouseDownState::MouseDownState(
3279     const PDFiumPage::Area& area,
3280     const PDFiumPage::LinkTarget& target)
3281     : area_(area), target_(target) {}
3282 
3283 PDFiumEngine::MouseDownState::~MouseDownState() = default;
3284 
Set(const PDFiumPage::Area & area,const PDFiumPage::LinkTarget & target)3285 void PDFiumEngine::MouseDownState::Set(const PDFiumPage::Area& area,
3286                                        const PDFiumPage::LinkTarget& target) {
3287   area_ = area;
3288   target_ = target;
3289 }
3290 
Reset()3291 void PDFiumEngine::MouseDownState::Reset() {
3292   area_ = PDFiumPage::NONSELECTABLE_AREA;
3293   target_ = PDFiumPage::LinkTarget();
3294 }
3295 
Matches(const PDFiumPage::Area & area,const PDFiumPage::LinkTarget & target) const3296 bool PDFiumEngine::MouseDownState::Matches(
3297     const PDFiumPage::Area& area,
3298     const PDFiumPage::LinkTarget& target) const {
3299   if (area_ != area)
3300     return false;
3301 
3302   if (area == PDFiumPage::WEBLINK_AREA)
3303     return target_.url == target.url;
3304 
3305   if (area == PDFiumPage::DOCLINK_AREA)
3306     return target_.page == target.page;
3307 
3308   return true;
3309 }
3310 
DeviceToPage(int page_index,const pp::Point & device_point,double * page_x,double * page_y)3311 void PDFiumEngine::DeviceToPage(int page_index,
3312                                 const pp::Point& device_point,
3313                                 double* page_x,
3314                                 double* page_y) {
3315   *page_x = *page_y = 0;
3316   float device_x = device_point.x();
3317   float device_y = device_point.y();
3318   int temp_x = static_cast<int>((device_x + position_.x()) / current_zoom_ -
3319                                 pages_[page_index]->rect().x());
3320   int temp_y = static_cast<int>((device_y + position_.y()) / current_zoom_ -
3321                                 pages_[page_index]->rect().y());
3322   FPDF_BOOL ret = FPDF_DeviceToPage(
3323       pages_[page_index]->GetPage(), 0, 0, pages_[page_index]->rect().width(),
3324       pages_[page_index]->rect().height(),
3325       ToPDFiumRotation(layout_.options().default_page_orientation()), temp_x,
3326       temp_y, page_x, page_y);
3327   DCHECK(ret);
3328 }
3329 
GetVisiblePageIndex(FPDF_PAGE page)3330 int PDFiumEngine::GetVisiblePageIndex(FPDF_PAGE page) {
3331   // Copy |visible_pages_| since it can change as a result of loading the page
3332   // in GetPage(). See https://crbug.com/822091.
3333   std::vector<int> visible_pages_copy(visible_pages_);
3334   for (int page_index : visible_pages_copy) {
3335     if (pages_[page_index]->GetPage() == page)
3336       return page_index;
3337   }
3338   return -1;
3339 }
3340 
SetCurrentPage(int index)3341 void PDFiumEngine::SetCurrentPage(int index) {
3342   in_flight_visible_page_.reset();
3343 
3344   if (index == most_visible_page_ || !form())
3345     return;
3346 
3347   if (most_visible_page_ != -1 && called_do_document_action_) {
3348     FPDF_PAGE old_page = pages_[most_visible_page_]->GetPage();
3349     FORM_DoPageAAction(old_page, form(), FPDFPAGE_AACTION_CLOSE);
3350   }
3351   most_visible_page_ = index;
3352 #if defined(OS_LINUX) || defined(OS_BSD)
3353   SetLastInstance(client_->GetPluginInstance());
3354 #endif
3355   if (most_visible_page_ != -1 && called_do_document_action_) {
3356     FPDF_PAGE new_page = pages_[most_visible_page_]->GetPage();
3357     FORM_DoPageAAction(new_page, form(), FPDFPAGE_AACTION_OPEN);
3358   }
3359 }
3360 
DrawPageShadow(const pp::Rect & page_rc,const pp::Rect & shadow_rc,const pp::Rect & clip_rc,pp::ImageData * image_data)3361 void PDFiumEngine::DrawPageShadow(const pp::Rect& page_rc,
3362                                   const pp::Rect& shadow_rc,
3363                                   const pp::Rect& clip_rc,
3364                                   pp::ImageData* image_data) {
3365   pp::Rect page_rect(page_rc);
3366   page_rect.Offset(page_offset_);
3367 
3368   pp::Rect shadow_rect(shadow_rc);
3369   shadow_rect.Offset(page_offset_);
3370 
3371   pp::Rect clip_rect(clip_rc);
3372   clip_rect.Offset(page_offset_);
3373 
3374   // Page drop shadow parameters.
3375   constexpr double factor = 0.5;
3376   uint32_t depth = std::max({page_rect.x() - shadow_rect.x(),
3377                              page_rect.y() - shadow_rect.y(),
3378                              shadow_rect.right() - page_rect.right(),
3379                              shadow_rect.bottom() - page_rect.bottom()});
3380   depth = static_cast<uint32_t>(depth * 1.5) + 1;
3381 
3382   // We need to check depth only to verify our copy of shadow matrix is correct.
3383   if (!page_shadow_ || page_shadow_->depth() != depth) {
3384     page_shadow_ = std::make_unique<draw_utils::ShadowMatrix>(
3385         depth, factor, client_->GetBackgroundColor());
3386   }
3387 
3388   DCHECK(!image_data->is_null());
3389   DrawShadow(image_data, shadow_rect, page_rect, clip_rect, *page_shadow_);
3390 }
3391 
GetRegion(const pp::Point & location,pp::ImageData * image_data,void ** region,int * stride) const3392 void PDFiumEngine::GetRegion(const pp::Point& location,
3393                              pp::ImageData* image_data,
3394                              void** region,
3395                              int* stride) const {
3396   if (image_data->is_null()) {
3397     DCHECK(plugin_size_.IsEmpty());
3398     *stride = 0;
3399     *region = nullptr;
3400     return;
3401   }
3402   char* buffer = static_cast<char*>(image_data->data());
3403   *stride = image_data->stride();
3404 
3405   pp::Point offset_location = location + page_offset_;
3406   // TODO: update this when we support BIDI and scrollbars can be on the left.
3407   if (!buffer ||
3408       !pp::Rect(page_offset_, plugin_size_).Contains(offset_location)) {
3409     *region = nullptr;
3410     return;
3411   }
3412 
3413   buffer += location.y() * (*stride);
3414   buffer += (location.x() + page_offset_.x()) * 4;
3415   *region = buffer;
3416 }
3417 
OnSelectionTextChanged()3418 void PDFiumEngine::OnSelectionTextChanged() {
3419   DCHECK(!in_form_text_area_);
3420   pp::PDF::SetSelectedText(GetPluginInstance(), GetSelectedText().c_str());
3421 }
3422 
OnSelectionPositionChanged()3423 void PDFiumEngine::OnSelectionPositionChanged() {
3424   // We need to determine the top-left and bottom-right points of the selection
3425   // in order to report those to the embedder. This code assumes that the
3426   // selection list is out of order.
3427   pp::Rect left(std::numeric_limits<int32_t>::max(),
3428                 std::numeric_limits<int32_t>::max(), 0, 0);
3429   pp::Rect right;
3430   for (const auto& sel : selection_) {
3431     const std::vector<pp::Rect>& screen_rects =
3432         sel.GetScreenRects(GetVisibleRect().point(), current_zoom_,
3433                            layout_.options().default_page_orientation());
3434     for (const auto& rect : screen_rects) {
3435       if (IsAboveOrDirectlyLeftOf(rect, left))
3436         left = rect;
3437       if (IsAboveOrDirectlyLeftOf(right, rect))
3438         right = rect;
3439     }
3440   }
3441   right.set_x(right.x() + right.width());
3442   if (left.IsEmpty()) {
3443     left.set_x(0);
3444     left.set_y(0);
3445   }
3446   client_->SelectionChanged(left, right);
3447 }
3448 
ApplyDocumentLayout(const DocumentLayout::Options & options)3449 pp::Size PDFiumEngine::ApplyDocumentLayout(
3450     const DocumentLayout::Options& options) {
3451   // We need to return early if the layout would not change, otherwise calling
3452   // client_->ScrollToPage() would send another "viewport" message, triggering
3453   // an infinite loop.
3454   //
3455   // TODO(crbug.com/1013800): The current implementation computes layout twice
3456   // (here, and in InvalidateAllPages()). This shouldn't be too expensive at
3457   // realistic page counts, but could be avoided.
3458   layout_.SetOptions(options);
3459   UpdateDocumentLayout(&layout_);
3460   if (!layout_.dirty())
3461     return layout_.size();
3462 
3463   // Store the current find index so that we can resume finding at that
3464   // particular index after we have recomputed the find results.
3465   std::string current_find_text = current_find_text_;
3466   resume_find_index_ = current_find_index_;
3467 
3468   // Save the current page.
3469   int most_visible_page = most_visible_page_;
3470 
3471   InvalidateAllPages();
3472 
3473   // Restore find results.
3474   if (!current_find_text.empty()) {
3475     // Clear the UI.
3476     client_->NotifyNumberOfFindResultsChanged(0, false);
3477     StartFind(current_find_text, false);
3478   }
3479 
3480   // Restore current page. After a rotation, the page heights have changed but
3481   // the scroll position has not. Re-adjust.
3482   // TODO(thestig): It would be better to also restore the position on the page.
3483   client_->ScrollToPage(most_visible_page);
3484 
3485   return layout_.size();
3486 }
3487 
SetSelecting(bool selecting)3488 void PDFiumEngine::SetSelecting(bool selecting) {
3489   bool was_selecting = selecting_;
3490   selecting_ = selecting;
3491   if (selecting_ != was_selecting)
3492     client_->IsSelectingChanged(selecting);
3493 }
3494 
SetEditMode(bool edit_mode)3495 void PDFiumEngine::SetEditMode(bool edit_mode) {
3496   if (edit_mode_ == edit_mode)
3497     return;
3498 
3499   edit_mode_ = edit_mode;
3500   client_->IsEditModeChanged(edit_mode_);
3501 }
3502 
SetInFormTextArea(bool in_form_text_area)3503 void PDFiumEngine::SetInFormTextArea(bool in_form_text_area) {
3504   // If focus was previously in form text area, clear form text selection.
3505   // Clearing needs to be done before changing focus to ensure the correct
3506   // observer is notified of the change in selection. When |in_form_text_area_|
3507   // is true, this is the Renderer. After it flips, the MimeHandler is notified.
3508   if (in_form_text_area_)
3509     pp::PDF::SetSelectedText(GetPluginInstance(), "");
3510 
3511   client_->FormTextFieldFocusChange(in_form_text_area);
3512   in_form_text_area_ = in_form_text_area;
3513 
3514   // Clear |editable_form_text_area_| when focus no longer in form text area.
3515   if (!in_form_text_area_)
3516     editable_form_text_area_ = false;
3517 }
3518 
SetMouseLeftButtonDown(bool is_mouse_left_button_down)3519 void PDFiumEngine::SetMouseLeftButtonDown(bool is_mouse_left_button_down) {
3520   mouse_left_button_down_ = is_mouse_left_button_down;
3521 }
3522 
IsPointInEditableFormTextArea(FPDF_PAGE page,double page_x,double page_y,int form_type)3523 bool PDFiumEngine::IsPointInEditableFormTextArea(FPDF_PAGE page,
3524                                                  double page_x,
3525                                                  double page_y,
3526                                                  int form_type) {
3527 #if defined(PDF_ENABLE_XFA)
3528   if (IS_XFA_FORMFIELD(form_type))
3529     return form_type == FPDF_FORMFIELD_XFA_TEXTFIELD ||
3530            form_type == FPDF_FORMFIELD_XFA_COMBOBOX;
3531 #endif  // defined(PDF_ENABLE_XFA)
3532 
3533   const FS_POINTF point = {page_x, page_y};
3534   ScopedFPDFAnnotation annot(
3535       FPDFAnnot_GetFormFieldAtPoint(form(), page, &point));
3536   if (!annot)
3537     return false;
3538 
3539   int flags = FPDFAnnot_GetFormFieldFlags(form(), annot.get());
3540   return CheckIfEditableFormTextArea(flags, form_type);
3541 }
3542 
ScheduleTouchTimer(const pp::TouchInputEvent & evt)3543 void PDFiumEngine::ScheduleTouchTimer(const pp::TouchInputEvent& evt) {
3544   touch_timer_.Start(FROM_HERE, kTouchLongPressTimeout,
3545                      base::BindOnce(&PDFiumEngine::HandleLongPress,
3546                                     base::Unretained(this), evt));
3547 }
3548 
KillTouchTimer()3549 void PDFiumEngine::KillTouchTimer() {
3550   touch_timer_.Stop();
3551 }
3552 
PageIndexInBounds(int index) const3553 bool PDFiumEngine::PageIndexInBounds(int index) const {
3554   return index >= 0 && index < static_cast<int>(pages_.size());
3555 }
3556 
IsPageCharacterIndexInBounds(const PP_PdfPageCharacterIndex & index) const3557 bool PDFiumEngine::IsPageCharacterIndexInBounds(
3558     const PP_PdfPageCharacterIndex& index) const {
3559   return PageIndexInBounds(index.page_index) &&
3560          pages_[index.page_index]->IsCharIndexInBounds(index.char_index);
3561 }
3562 
GetToolbarHeightInScreenCoords()3563 float PDFiumEngine::GetToolbarHeightInScreenCoords() {
3564   return client_->GetToolbarHeightInScreenCoords();
3565 }
3566 
Pause_NeedToPauseNow(IFSDK_PAUSE * param)3567 FPDF_BOOL PDFiumEngine::Pause_NeedToPauseNow(IFSDK_PAUSE* param) {
3568   PDFiumEngine* engine = static_cast<PDFiumEngine*>(param);
3569   return base::Time::Now() - engine->last_progressive_start_time_ >
3570          engine->progressive_paint_timeout_;
3571 }
3572 
SetSelection(const PP_PdfPageCharacterIndex & selection_start_index,const PP_PdfPageCharacterIndex & selection_end_index)3573 void PDFiumEngine::SetSelection(
3574     const PP_PdfPageCharacterIndex& selection_start_index,
3575     const PP_PdfPageCharacterIndex& selection_end_index) {
3576   SelectionChangeInvalidator selection_invalidator(this);
3577   selection_.clear();
3578 
3579   PP_PdfPageCharacterIndex sel_start_index = selection_start_index;
3580   PP_PdfPageCharacterIndex sel_end_index = selection_end_index;
3581   if (sel_end_index.page_index < sel_start_index.page_index) {
3582     std::swap(sel_end_index.page_index, sel_start_index.page_index);
3583     std::swap(sel_end_index.char_index, sel_start_index.char_index);
3584   }
3585 
3586   if (sel_end_index.page_index == sel_start_index.page_index &&
3587       sel_end_index.char_index < sel_start_index.char_index) {
3588     std::swap(sel_end_index.char_index, sel_start_index.char_index);
3589   }
3590 
3591   for (uint32_t i = sel_start_index.page_index; i <= sel_end_index.page_index;
3592        ++i) {
3593     int32_t char_count = pages_[i]->GetCharCount();
3594     if (char_count <= 0)
3595       continue;
3596     int32_t start_char_index = 0;
3597     int32_t end_char_index = char_count;
3598     if (i == sel_start_index.page_index)
3599       start_char_index = sel_start_index.char_index;
3600     if (i == sel_end_index.page_index)
3601       end_char_index = sel_end_index.char_index;
3602     selection_.push_back(PDFiumRange(pages_[i].get(), start_char_index,
3603                                      end_char_index - start_char_index));
3604   }
3605 }
3606 
ScrollIntoView(const pp::Rect & rect)3607 void PDFiumEngine::ScrollIntoView(const pp::Rect& rect) {
3608   pp::Rect visible_rect = GetVisibleRect();
3609   if (visible_rect.Contains(rect))
3610     return;
3611   // Since the focus rect is not already in the visible area, scrolling
3612   // horizontally and/or vertically is required.
3613   if (rect.y() < visible_rect.y() || rect.bottom() > visible_rect.bottom()) {
3614     // Scroll the viewport vertically to align the top of focus rect to
3615     // centre.
3616     client_->ScrollToY(rect.y() * current_zoom_ - plugin_size_.height() / 2,
3617                        /*compensate_for_toolbar=*/false);
3618   }
3619   if (rect.x() < visible_rect.x() || rect.right() > visible_rect.right()) {
3620     // Scroll the viewport horizontally to align the left of focus rect to
3621     // centre.
3622     client_->ScrollToX(rect.x() * current_zoom_ - plugin_size_.width() / 2);
3623   }
3624 }
3625 
SetCaretPosition(const pp::Point & position)3626 void PDFiumEngine::SetCaretPosition(const pp::Point& position) {
3627   // TODO(dsinclair): Handle caret position ...
3628 }
3629 
MoveRangeSelectionExtent(const pp::Point & extent)3630 void PDFiumEngine::MoveRangeSelectionExtent(const pp::Point& extent) {
3631   int page_index = -1;
3632   int char_index = -1;
3633   int form_type = FPDF_FORMFIELD_UNKNOWN;
3634   PDFiumPage::LinkTarget target;
3635   GetCharIndex(extent, &page_index, &char_index, &form_type, &target);
3636   if (page_index < 0 || char_index < 0)
3637     return;
3638 
3639   SelectionChangeInvalidator selection_invalidator(this);
3640   if (range_selection_direction_ == RangeSelectionDirection::Right) {
3641     ExtendSelection(page_index, char_index);
3642     return;
3643   }
3644 
3645   // For a left selection we clear the current selection and set a new starting
3646   // point based on the new left position. We then extend that selection out to
3647   // the previously provided base location.
3648   selection_.clear();
3649   selection_.push_back(PDFiumRange(pages_[page_index].get(), char_index, 0));
3650 
3651   // This should always succeeed because the range selection base should have
3652   // already been selected.
3653   GetCharIndex(range_selection_base_, &page_index, &char_index, &form_type,
3654                &target);
3655   ExtendSelection(page_index, char_index);
3656 }
3657 
SetSelectionBounds(const pp::Point & base,const pp::Point & extent)3658 void PDFiumEngine::SetSelectionBounds(const pp::Point& base,
3659                                       const pp::Point& extent) {
3660   range_selection_base_ = base;
3661   range_selection_direction_ = IsAboveOrDirectlyLeftOf(base, extent)
3662                                    ? RangeSelectionDirection::Left
3663                                    : RangeSelectionDirection::Right;
3664 }
3665 
GetSelection(uint32_t * selection_start_page_index,uint32_t * selection_start_char_index,uint32_t * selection_end_page_index,uint32_t * selection_end_char_index)3666 void PDFiumEngine::GetSelection(uint32_t* selection_start_page_index,
3667                                 uint32_t* selection_start_char_index,
3668                                 uint32_t* selection_end_page_index,
3669                                 uint32_t* selection_end_char_index) {
3670   size_t len = selection_.size();
3671   if (len == 0) {
3672     *selection_start_page_index = 0;
3673     *selection_start_char_index = 0;
3674     *selection_end_page_index = 0;
3675     *selection_end_char_index = 0;
3676     return;
3677   }
3678 
3679   *selection_start_page_index = selection_[0].page_index();
3680   *selection_start_char_index = selection_[0].char_index();
3681   *selection_end_page_index = selection_[len - 1].page_index();
3682 
3683   // If the selection is all within one page, the end index is the
3684   // start index plus the char count. But if the selection spans
3685   // multiple pages, the selection starts at the beginning of the
3686   // last page in |selection_| and goes to the char count.
3687   if (len == 1) {
3688     *selection_end_char_index =
3689         selection_[0].char_index() + selection_[0].char_count();
3690   } else {
3691     *selection_end_char_index = selection_[len - 1].char_count();
3692   }
3693 }
3694 
LoadDocumentMetadata()3695 void PDFiumEngine::LoadDocumentMetadata() {
3696   DCHECK(document_loaded_);
3697 
3698   // Document information dictionary entries
3699   doc_metadata_.version = GetDocumentVersion();
3700   doc_metadata_.title = GetMetadataByField("Title");
3701   doc_metadata_.author = GetMetadataByField("Author");
3702   doc_metadata_.subject = GetMetadataByField("Subject");
3703   doc_metadata_.creator = GetMetadataByField("Creator");
3704   doc_metadata_.producer = GetMetadataByField("Producer");
3705 }
3706 
GetMetadataByField(FPDF_BYTESTRING field) const3707 std::string PDFiumEngine::GetMetadataByField(FPDF_BYTESTRING field) const {
3708   DCHECK(doc());
3709 
3710   size_t size =
3711       FPDF_GetMetaText(doc(), field, /*buffer=*/nullptr, /*buflen=*/0);
3712   if (size == 0)
3713     return std::string();
3714 
3715   base::string16 value;
3716   PDFiumAPIStringBufferSizeInBytesAdapter<base::string16> string_adapter(
3717       &value, size, /*check_expected_size=*/false);
3718   string_adapter.Close(
3719       FPDF_GetMetaText(doc(), field, string_adapter.GetData(), size));
3720   return base::UTF16ToUTF8(value);
3721 }
3722 
GetDocumentVersion() const3723 PdfVersion PDFiumEngine::GetDocumentVersion() const {
3724   DCHECK(doc());
3725 
3726   int version;
3727   if (!FPDF_GetFileVersion(doc(), &version))
3728     return PdfVersion::kUnknown;
3729 
3730   switch (version) {
3731     case 10:
3732       return PdfVersion::k1_0;
3733     case 11:
3734       return PdfVersion::k1_1;
3735     case 12:
3736       return PdfVersion::k1_2;
3737     case 13:
3738       return PdfVersion::k1_3;
3739     case 14:
3740       return PdfVersion::k1_4;
3741     case 15:
3742       return PdfVersion::k1_5;
3743     case 16:
3744       return PdfVersion::k1_6;
3745     case 17:
3746       return PdfVersion::k1_7;
3747     case 18:
3748       return PdfVersion::k1_8;
3749     case 20:
3750       return PdfVersion::k2_0;
3751     default:
3752       return PdfVersion::kUnknown;
3753   }
3754 }
3755 
3756 #if defined(PDF_ENABLE_XFA)
UpdatePageCount()3757 void PDFiumEngine::UpdatePageCount() {
3758   InvalidateAllPages();
3759 }
3760 #endif  // defined(PDF_ENABLE_XFA)
3761 
ProgressivePaint(int index,const pp::Rect & rect)3762 PDFiumEngine::ProgressivePaint::ProgressivePaint(int index,
3763                                                  const pp::Rect& rect)
3764     : page_index_(index), rect_(rect) {}
3765 
3766 PDFiumEngine::ProgressivePaint::ProgressivePaint(ProgressivePaint&& that) =
3767     default;
3768 
3769 PDFiumEngine::ProgressivePaint::~ProgressivePaint() = default;
3770 
3771 PDFiumEngine::ProgressivePaint& PDFiumEngine::ProgressivePaint::operator=(
3772     ProgressivePaint&& that) = default;
3773 
SetBitmapAndImageData(ScopedFPDFBitmap bitmap,pp::ImageData image_data)3774 void PDFiumEngine::ProgressivePaint::SetBitmapAndImageData(
3775     ScopedFPDFBitmap bitmap,
3776     pp::ImageData image_data) {
3777   bitmap_ = std::move(bitmap);
3778   image_data_ = std::move(image_data);
3779 }
3780 
3781 }  // namespace chrome_pdf
3782