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, ®ion, &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, ®ion, &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