Upload upstream chromium 67.0.3396
[platform/framework/web/chromium-efl.git] / pdf / pdfium / pdfium_page.cc
1 // Copyright (c) 2010 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_page.h"
6
7 #include <math.h>
8 #include <stddef.h>
9
10 #include <algorithm>
11 #include <memory>
12 #include <utility>
13
14 #include "base/logging.h"
15 #include "base/numerics/safe_math.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "pdf/pdfium/pdfium_api_string_buffer_adapter.h"
20 #include "pdf/pdfium/pdfium_engine.h"
21 #include "printing/units.h"
22 #include "third_party/pdfium/public/fpdf_annot.h"
23
24 using printing::ConvertUnitDouble;
25 using printing::kPointsPerInch;
26 using printing::kPixelsPerInch;
27
28 namespace {
29
30 pp::FloatRect FloatPageRectToPixelRect(FPDF_PAGE page,
31                                        const pp::FloatRect& input) {
32   int output_width = FPDF_GetPageWidth(page);
33   int output_height = FPDF_GetPageHeight(page);
34
35   int min_x;
36   int min_y;
37   int max_x;
38   int max_y;
39   FPDF_PageToDevice(page, 0, 0, output_width, output_height, 0, input.x(),
40                     input.y(), &min_x, &min_y);
41   FPDF_PageToDevice(page, 0, 0, output_width, output_height, 0, input.right(),
42                     input.bottom(), &max_x, &max_y);
43
44   if (max_x < min_x)
45     std::swap(min_x, max_x);
46   if (max_y < min_y)
47     std::swap(min_y, max_y);
48
49   pp::FloatRect output_rect(
50       ConvertUnitDouble(min_x, kPointsPerInch, kPixelsPerInch),
51       ConvertUnitDouble(min_y, kPointsPerInch, kPixelsPerInch),
52       ConvertUnitDouble(max_x - min_x, kPointsPerInch, kPixelsPerInch),
53       ConvertUnitDouble(max_y - min_y, kPointsPerInch, kPixelsPerInch));
54   return output_rect;
55 }
56
57 pp::FloatRect GetFloatCharRectInPixels(FPDF_PAGE page,
58                                        FPDF_TEXTPAGE text_page,
59                                        int index) {
60   double left, right, bottom, top;
61   FPDFText_GetCharBox(text_page, index, &left, &right, &bottom, &top);
62   if (right < left)
63     std::swap(left, right);
64   if (bottom < top)
65     std::swap(top, bottom);
66   pp::FloatRect page_coords(left, top, right - left, bottom - top);
67   return FloatPageRectToPixelRect(page, page_coords);
68 }
69
70 bool OverlapsOnYAxis(const pp::FloatRect& a, const pp::FloatRect& b) {
71   return !(a.IsEmpty() || b.IsEmpty() || a.bottom() < b.y() ||
72            b.bottom() < a.y());
73 }
74
75 }  // namespace
76
77 namespace chrome_pdf {
78
79 PDFiumPage::LinkTarget::LinkTarget() : page(-1) {}
80
81 PDFiumPage::LinkTarget::LinkTarget(const LinkTarget& other) = default;
82
83 PDFiumPage::LinkTarget::~LinkTarget() = default;
84
85 PDFiumPage::PDFiumPage(PDFiumEngine* engine,
86                        int i,
87                        const pp::Rect& r,
88                        bool available)
89     : engine_(engine),
90       page_(nullptr),
91       text_page_(nullptr),
92       index_(i),
93       loading_count_(0),
94       rect_(r),
95       calculated_links_(false),
96       available_(available) {}
97
98 PDFiumPage::PDFiumPage(const PDFiumPage& that) = default;
99
100 PDFiumPage::~PDFiumPage() {
101   DCHECK_EQ(0, loading_count_);
102 }
103
104 void PDFiumPage::Unload() {
105   // Do not unload while in the middle of a load.
106   if (loading_count_)
107     return;
108
109   if (text_page_) {
110     FPDFText_ClosePage(text_page_);
111     text_page_ = nullptr;
112   }
113
114   if (page_) {
115     if (engine_->form()) {
116       FORM_OnBeforeClosePage(page_, engine_->form());
117     }
118     FPDF_ClosePage(page_);
119     page_ = nullptr;
120   }
121 }
122
123 FPDF_PAGE PDFiumPage::GetPage() {
124   ScopedUnsupportedFeature scoped_unsupported_feature(engine_);
125   ScopedSubstFont scoped_subst_font(engine_);
126   if (!available_)
127     return nullptr;
128   if (!page_) {
129     ScopedLoadCounter scoped_load(this);
130     page_ = FPDF_LoadPage(engine_->doc(), index_);
131     if (page_ && engine_->form()) {
132       FORM_OnAfterLoadPage(page_, engine_->form());
133     }
134   }
135   return page_;
136 }
137
138 FPDF_PAGE PDFiumPage::GetPrintPage() {
139   ScopedUnsupportedFeature scoped_unsupported_feature(engine_);
140   ScopedSubstFont scoped_subst_font(engine_);
141   if (!available_)
142     return nullptr;
143   if (!page_) {
144     ScopedLoadCounter scoped_load(this);
145     page_ = FPDF_LoadPage(engine_->doc(), index_);
146   }
147   return page_;
148 }
149
150 void PDFiumPage::ClosePrintPage() {
151   // Do not close |page_| while in the middle of a load.
152   if (loading_count_)
153     return;
154
155   if (page_) {
156     FPDF_ClosePage(page_);
157     page_ = nullptr;
158   }
159 }
160
161 FPDF_TEXTPAGE PDFiumPage::GetTextPage() {
162   if (!available_)
163     return nullptr;
164   if (!text_page_) {
165     ScopedLoadCounter scoped_load(this);
166     text_page_ = FPDFText_LoadPage(GetPage());
167   }
168   return text_page_;
169 }
170
171 void PDFiumPage::GetTextRunInfo(int start_char_index,
172                                 uint32_t* out_len,
173                                 double* out_font_size,
174                                 pp::FloatRect* out_bounds) {
175   FPDF_PAGE page = GetPage();
176   FPDF_TEXTPAGE text_page = GetTextPage();
177   int chars_count = FPDFText_CountChars(text_page);
178   int char_index = start_char_index;
179   while (
180       char_index < chars_count &&
181       base::IsUnicodeWhitespace(FPDFText_GetUnicode(text_page, char_index))) {
182     char_index++;
183   }
184   int text_run_font_size = FPDFText_GetFontSize(text_page, char_index);
185   pp::FloatRect text_run_bounds =
186       GetFloatCharRectInPixels(page, text_page, char_index);
187   if (char_index < chars_count)
188     char_index++;
189   while (char_index < chars_count) {
190     unsigned int character = FPDFText_GetUnicode(text_page, char_index);
191
192     if (!base::IsUnicodeWhitespace(character)) {
193       // TODO(dmazzoni): this assumes horizontal text.
194       // https://crbug.com/580311
195       pp::FloatRect char_rect =
196           GetFloatCharRectInPixels(page, text_page, char_index);
197       if (!char_rect.IsEmpty() && !OverlapsOnYAxis(text_run_bounds, char_rect))
198         break;
199
200       int font_size = FPDFText_GetFontSize(text_page, char_index);
201       if (font_size != text_run_font_size)
202         break;
203
204       // Heuristic: split a text run after a space longer than 3 average
205       // characters.
206       double avg_char_width =
207           text_run_bounds.width() / (char_index - start_char_index);
208       if (char_rect.x() - text_run_bounds.right() > avg_char_width * 3)
209         break;
210
211       text_run_bounds = text_run_bounds.Union(char_rect);
212     }
213
214     char_index++;
215   }
216
217   // Some PDFs have missing or obviously bogus font sizes; substitute the
218   // height of the bounding box in those cases.
219   if (text_run_font_size <= 1 ||
220       text_run_font_size < text_run_bounds.height() / 2 ||
221       text_run_font_size > text_run_bounds.height() * 2) {
222     text_run_font_size = text_run_bounds.height();
223   }
224
225   *out_len = char_index - start_char_index;
226   *out_font_size = text_run_font_size;
227   *out_bounds = text_run_bounds;
228 }
229
230 uint32_t PDFiumPage::GetCharUnicode(int char_index) {
231   FPDF_TEXTPAGE text_page = GetTextPage();
232   return FPDFText_GetUnicode(text_page, char_index);
233 }
234
235 pp::FloatRect PDFiumPage::GetCharBounds(int char_index) {
236   FPDF_PAGE page = GetPage();
237   FPDF_TEXTPAGE text_page = GetTextPage();
238   return GetFloatCharRectInPixels(page, text_page, char_index);
239 }
240
241 PDFiumPage::Area PDFiumPage::GetCharIndex(const pp::Point& point,
242                                           int rotation,
243                                           int* char_index,
244                                           int* form_type,
245                                           LinkTarget* target) {
246   if (!available_)
247     return NONSELECTABLE_AREA;
248   pp::Point point2 = point - rect_.point();
249   double new_x;
250   double new_y;
251   FPDF_DeviceToPage(GetPage(), 0, 0, rect_.width(), rect_.height(), rotation,
252                     point2.x(), point2.y(), &new_x, &new_y);
253
254   // hit detection tolerance, in points.
255   constexpr double kTolerance = 20.0;
256   int rv = FPDFText_GetCharIndexAtPos(GetTextPage(), new_x, new_y, kTolerance,
257                                       kTolerance);
258   *char_index = rv;
259
260   FPDF_LINK link = FPDFLink_GetLinkAtPoint(GetPage(), new_x, new_y);
261   int control =
262       FPDFPage_HasFormFieldAtPoint(engine_->form(), GetPage(), new_x, new_y);
263
264   // If there is a control and link at the same point, figure out their z-order
265   // to determine which is on top.
266   if (link && control > FPDF_FORMFIELD_UNKNOWN) {
267     int control_z_order = FPDFPage_FormFieldZOrderAtPoint(
268         engine_->form(), GetPage(), new_x, new_y);
269     int link_z_order = FPDFLink_GetLinkZOrderAtPoint(GetPage(), new_x, new_y);
270     DCHECK_NE(control_z_order, link_z_order);
271     if (control_z_order > link_z_order) {
272       *form_type = control;
273       return FormTypeToArea(*form_type);
274     }
275
276     // We don't handle all possible link types of the PDF. For example,
277     // launch actions, cross-document links, etc.
278     // In that case, GetLinkTarget() will return NONSELECTABLE_AREA
279     // and we should proceed with area detection.
280     Area area = GetLinkTarget(link, target);
281     if (area != NONSELECTABLE_AREA)
282       return area;
283   } else if (link) {
284     // We don't handle all possible link types of the PDF. For example,
285     // launch actions, cross-document links, etc.
286     // See identical block above.
287     Area area = GetLinkTarget(link, target);
288     if (area != NONSELECTABLE_AREA)
289       return area;
290   } else if (control > FPDF_FORMFIELD_UNKNOWN) {
291     *form_type = control;
292     return FormTypeToArea(*form_type);
293   }
294
295   if (rv < 0)
296     return NONSELECTABLE_AREA;
297
298   return GetLink(*char_index, target) != -1 ? WEBLINK_AREA : TEXT_AREA;
299 }
300
301 // static
302 PDFiumPage::Area PDFiumPage::FormTypeToArea(int form_type) {
303   switch (form_type) {
304     case FPDF_FORMFIELD_COMBOBOX:
305     case FPDF_FORMFIELD_TEXTFIELD:
306 #if defined(PDF_ENABLE_XFA)
307     // TODO(bug_353450): figure out selection and copying for XFA fields.
308     case FPDF_FORMFIELD_XFA_COMBOBOX:
309     case FPDF_FORMFIELD_XFA_TEXTFIELD:
310 #endif
311       return FORM_TEXT_AREA;
312     default:
313       return NONSELECTABLE_AREA;
314   }
315 }
316
317 base::char16 PDFiumPage::GetCharAtIndex(int index) {
318   if (!available_)
319     return L'\0';
320   return static_cast<base::char16>(FPDFText_GetUnicode(GetTextPage(), index));
321 }
322
323 int PDFiumPage::GetCharCount() {
324   if (!available_)
325     return 0;
326   return FPDFText_CountChars(GetTextPage());
327 }
328
329 PDFiumPage::Area PDFiumPage::GetLinkTarget(FPDF_LINK link, LinkTarget* target) {
330   FPDF_DEST dest = FPDFLink_GetDest(engine_->doc(), link);
331   if (dest)
332     return GetDestinationTarget(dest, target);
333
334   FPDF_ACTION action = FPDFLink_GetAction(link);
335   if (!action)
336     return NONSELECTABLE_AREA;
337
338   switch (FPDFAction_GetType(action)) {
339     case PDFACTION_GOTO: {
340       FPDF_DEST dest = FPDFAction_GetDest(engine_->doc(), action);
341       if (dest)
342         return GetDestinationTarget(dest, target);
343       // TODO(crbug.com/55776): We don't fully support all types of the
344       // in-document links.
345       return NONSELECTABLE_AREA;
346     }
347     case PDFACTION_URI:
348       return GetURITarget(action, target);
349     // TODO(crbug.com/767191): Support PDFACTION_LAUNCH.
350     // TODO(crbug.com/142344): Support PDFACTION_REMOTEGOTO.
351     case PDFACTION_LAUNCH:
352     case PDFACTION_REMOTEGOTO:
353     default:
354       return NONSELECTABLE_AREA;
355   }
356 }
357
358 PDFiumPage::Area PDFiumPage::GetDestinationTarget(FPDF_DEST destination,
359                                                   LinkTarget* target) {
360   if (!target)
361     return NONSELECTABLE_AREA;
362
363   int page_index = FPDFDest_GetDestPageIndex(engine_->doc(), destination);
364   if (page_index < 0)
365     return NONSELECTABLE_AREA;
366
367   target->page = page_index;
368
369   base::Optional<gfx::PointF> xy = GetPageXYTarget(destination);
370   if (xy)
371     target->y_in_pixels = TransformPageToScreenXY(xy.value()).y();
372
373   return DOCLINK_AREA;
374 }
375
376 base::Optional<gfx::PointF> PDFiumPage::GetPageXYTarget(FPDF_DEST destination) {
377   if (!available_)
378     return {};
379
380   FPDF_BOOL has_x_coord;
381   FPDF_BOOL has_y_coord;
382   FPDF_BOOL has_zoom;
383   FS_FLOAT x;
384   FS_FLOAT y;
385   FS_FLOAT zoom;
386   FPDF_BOOL success = FPDFDest_GetLocationInPage(
387       destination, &has_x_coord, &has_y_coord, &has_zoom, &x, &y, &zoom);
388
389   if (!success || !has_x_coord || !has_y_coord)
390     return {};
391
392   return {gfx::PointF(x, y)};
393 }
394
395 gfx::PointF PDFiumPage::TransformPageToScreenXY(const gfx::PointF& xy) {
396   if (!available_)
397     return gfx::PointF();
398
399   pp::FloatRect page_rect(xy.x(), xy.y(), 0, 0);
400   pp::FloatRect pixel_rect(FloatPageRectToPixelRect(GetPage(), page_rect));
401   return gfx::PointF(pixel_rect.x(), pixel_rect.y());
402 }
403
404 PDFiumPage::Area PDFiumPage::GetURITarget(FPDF_ACTION uri_action,
405                                           LinkTarget* target) const {
406   if (target) {
407     size_t buffer_size =
408         FPDFAction_GetURIPath(engine_->doc(), uri_action, nullptr, 0);
409     if (buffer_size > 0) {
410       PDFiumAPIStringBufferAdapter<std::string> api_string_adapter(
411           &target->url, buffer_size, true);
412       void* data = api_string_adapter.GetData();
413       size_t bytes_written =
414           FPDFAction_GetURIPath(engine_->doc(), uri_action, data, buffer_size);
415       api_string_adapter.Close(bytes_written);
416     }
417   }
418   return WEBLINK_AREA;
419 }
420
421 int PDFiumPage::GetLink(int char_index, LinkTarget* target) {
422   if (!available_)
423     return -1;
424
425   CalculateLinks();
426
427   // Get the bounding box of the rect again, since it might have moved because
428   // of the tolerance above.
429   double left, right, bottom, top;
430   FPDFText_GetCharBox(GetTextPage(), char_index, &left, &right, &bottom, &top);
431
432   pp::Point origin(
433       PageToScreen(pp::Point(), 1.0, left, top, right, bottom, 0).point());
434   for (size_t i = 0; i < links_.size(); ++i) {
435     for (const auto& rect : links_[i].rects) {
436       if (rect.Contains(origin)) {
437         if (target)
438           target->url = links_[i].url;
439         return i;
440       }
441     }
442   }
443   return -1;
444 }
445
446 std::vector<int> PDFiumPage::GetLinks(pp::Rect text_area,
447                                       std::vector<LinkTarget>* targets) {
448   std::vector<int> links;
449   if (!available_)
450     return links;
451
452   CalculateLinks();
453
454   for (size_t i = 0; i < links_.size(); ++i) {
455     for (const auto& rect : links_[i].rects) {
456       if (rect.Intersects(text_area)) {
457         if (targets) {
458           LinkTarget target;
459           target.url = links_[i].url;
460           targets->push_back(target);
461         }
462         links.push_back(i);
463       }
464     }
465   }
466   return links;
467 }
468
469 void PDFiumPage::CalculateLinks() {
470   if (calculated_links_)
471     return;
472
473   calculated_links_ = true;
474   FPDF_PAGELINK links = FPDFLink_LoadWebLinks(GetTextPage());
475   int count = FPDFLink_CountWebLinks(links);
476   for (int i = 0; i < count; ++i) {
477     base::string16 url;
478     int url_length = FPDFLink_GetURL(links, i, nullptr, 0);
479     if (url_length > 0) {
480       PDFiumAPIStringBufferAdapter<base::string16> api_string_adapter(
481           &url, url_length, true);
482       unsigned short* data =
483           reinterpret_cast<unsigned short*>(api_string_adapter.GetData());
484       int actual_length = FPDFLink_GetURL(links, i, data, url_length);
485       api_string_adapter.Close(actual_length);
486     }
487     Link link;
488     link.url = base::UTF16ToUTF8(url);
489
490     // If the link cannot be converted to a pp::Var, then it is not possible to
491     // pass it to JS. In this case, ignore the link like other PDF viewers.
492     // See http://crbug.com/312882 for an example.
493     pp::Var link_var(link.url);
494     if (!link_var.is_string())
495       continue;
496
497     // Make sure all the characters in the URL are valid per RFC 1738.
498     // http://crbug.com/340326 has a sample bad PDF.
499     // GURL does not work correctly, e.g. it just strips \t \r \n.
500     bool is_invalid_url = false;
501     for (size_t j = 0; j < link.url.length(); ++j) {
502       // Control characters are not allowed.
503       // 0x7F is also a control character.
504       // 0x80 and above are not in US-ASCII.
505       if (link.url[j] < ' ' || link.url[j] >= '\x7F') {
506         is_invalid_url = true;
507         break;
508       }
509     }
510     if (is_invalid_url)
511       continue;
512
513     int rect_count = FPDFLink_CountRects(links, i);
514     for (int j = 0; j < rect_count; ++j) {
515       double left;
516       double top;
517       double right;
518       double bottom;
519       FPDFLink_GetRect(links, i, j, &left, &top, &right, &bottom);
520       pp::Rect rect =
521           PageToScreen(pp::Point(), 1.0, left, top, right, bottom, 0);
522       if (rect.IsEmpty())
523         continue;
524       link.rects.push_back(rect);
525     }
526     links_.push_back(link);
527   }
528   FPDFLink_CloseWebLinks(links);
529 }
530
531 pp::Rect PDFiumPage::PageToScreen(const pp::Point& offset,
532                                   double zoom,
533                                   double left,
534                                   double top,
535                                   double right,
536                                   double bottom,
537                                   int rotation) const {
538   if (!available_)
539     return pp::Rect();
540
541   double start_x = (rect_.x() - offset.x()) * zoom;
542   double start_y = (rect_.y() - offset.y()) * zoom;
543   double size_x = rect_.width() * zoom;
544   double size_y = rect_.height() * zoom;
545   if (!base::IsValueInRangeForNumericType<int>(start_x) ||
546       !base::IsValueInRangeForNumericType<int>(start_y) ||
547       !base::IsValueInRangeForNumericType<int>(size_x) ||
548       !base::IsValueInRangeForNumericType<int>(size_y)) {
549     return pp::Rect();
550   }
551
552   int new_left;
553   int new_top;
554   int new_right;
555   int new_bottom;
556   FPDF_PageToDevice(page_, static_cast<int>(start_x), static_cast<int>(start_y),
557                     static_cast<int>(ceil(size_x)),
558                     static_cast<int>(ceil(size_y)), rotation, left, top,
559                     &new_left, &new_top);
560   FPDF_PageToDevice(page_, static_cast<int>(start_x), static_cast<int>(start_y),
561                     static_cast<int>(ceil(size_x)),
562                     static_cast<int>(ceil(size_y)), rotation, right, bottom,
563                     &new_right, &new_bottom);
564
565   // If the PDF is rotated, the horizontal/vertical coordinates could be
566   // flipped.  See
567   // http://www.netl.doe.gov/publications/proceedings/03/ubc/presentations/Goeckner-pres.pdf
568   if (new_right < new_left)
569     std::swap(new_right, new_left);
570   if (new_bottom < new_top)
571     std::swap(new_bottom, new_top);
572
573   base::CheckedNumeric<int32_t> new_size_x = new_right;
574   new_size_x -= new_left;
575   new_size_x += 1;
576   base::CheckedNumeric<int32_t> new_size_y = new_bottom;
577   new_size_y -= new_top;
578   new_size_y += 1;
579   if (!new_size_x.IsValid() || !new_size_y.IsValid())
580     return pp::Rect();
581
582   return pp::Rect(new_left, new_top, new_size_x.ValueOrDie(),
583                   new_size_y.ValueOrDie());
584 }
585
586 const PDFEngine::PageFeatures* PDFiumPage::GetPageFeatures() {
587   // If page_features_ is cached, return the cached features.
588   if (page_features_.IsInitialized())
589     return &page_features_;
590
591   FPDF_PAGE page = GetPage();
592   if (!page)
593     return nullptr;
594
595   // Initialize and cache page_features_.
596   page_features_.index = index_;
597   int annotation_count = FPDFPage_GetAnnotCount(page);
598   for (int i = 0; i < annotation_count; ++i) {
599     FPDF_ANNOTATION annotation = FPDFPage_GetAnnot(page, i);
600     FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annotation);
601     page_features_.annotation_types.insert(subtype);
602     FPDFPage_CloseAnnot(annotation);
603   }
604
605   return &page_features_;
606 }
607
608 PDFiumPage::ScopedLoadCounter::ScopedLoadCounter(PDFiumPage* page)
609     : page_(page) {
610   page_->loading_count_++;
611 }
612
613 PDFiumPage::ScopedLoadCounter::~ScopedLoadCounter() {
614   page_->loading_count_--;
615 }
616
617 PDFiumPage::Link::Link() = default;
618
619 PDFiumPage::Link::Link(const Link& that) = default;
620
621 PDFiumPage::Link::~Link() = default;
622
623 }  // namespace chrome_pdf