pdfium_page.cc 55 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624
  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. #include "pdf/pdfium/pdfium_page.h"
  5. #include <math.h>
  6. #include <stddef.h>
  7. #include <algorithm>
  8. #include <memory>
  9. #include <utility>
  10. #include "base/bind.h"
  11. #include "base/callback.h"
  12. #include "base/check_op.h"
  13. #include "base/cxx17_backports.h"
  14. #include "base/metrics/histogram_functions.h"
  15. #include "base/numerics/math_constants.h"
  16. #include "base/numerics/safe_math.h"
  17. #include "base/strings/string_number_conversions.h"
  18. #include "base/strings/string_util.h"
  19. #include "base/strings/utf_string_conversions.h"
  20. #include "pdf/accessibility_helper.h"
  21. #include "pdf/accessibility_structs.h"
  22. #include "pdf/pdfium/pdfium_api_string_buffer_adapter.h"
  23. #include "pdf/pdfium/pdfium_engine.h"
  24. #include "pdf/pdfium/pdfium_unsupported_features.h"
  25. #include "pdf/ui/thumbnail.h"
  26. #include "printing/units.h"
  27. #include "third_party/pdfium/public/cpp/fpdf_scopers.h"
  28. #include "third_party/pdfium/public/fpdf_annot.h"
  29. #include "third_party/pdfium/public/fpdf_catalog.h"
  30. #include "third_party/skia/include/core/SkImageInfo.h"
  31. #include "third_party/skia/include/core/SkPixmap.h"
  32. #include "ui/accessibility/accessibility_features.h"
  33. #include "ui/gfx/geometry/point.h"
  34. #include "ui/gfx/geometry/point_f.h"
  35. #include "ui/gfx/geometry/rect.h"
  36. #include "ui/gfx/geometry/rect_f.h"
  37. #include "ui/gfx/geometry/size_f.h"
  38. #include "ui/gfx/geometry/vector2d.h"
  39. #include "ui/gfx/geometry/vector2d_f.h"
  40. #include "ui/gfx/range/range.h"
  41. using printing::ConvertUnitFloat;
  42. using printing::kPixelsPerInch;
  43. using printing::kPointsPerInch;
  44. namespace chrome_pdf {
  45. namespace {
  46. constexpr float k45DegreesInRadians = base::kPiFloat / 4;
  47. constexpr float k90DegreesInRadians = base::kPiFloat / 2;
  48. constexpr float k180DegreesInRadians = base::kPiFloat;
  49. constexpr float k270DegreesInRadians = 3 * base::kPiFloat / 2;
  50. constexpr float k360DegreesInRadians = 2 * base::kPiFloat;
  51. gfx::RectF FloatPageRectToPixelRect(FPDF_PAGE page, const gfx::RectF& input) {
  52. int output_width = FPDF_GetPageWidthF(page);
  53. int output_height = FPDF_GetPageHeightF(page);
  54. int min_x;
  55. int min_y;
  56. int max_x;
  57. int max_y;
  58. if (!FPDF_PageToDevice(page, 0, 0, output_width, output_height, 0, input.x(),
  59. input.y(), &min_x, &min_y)) {
  60. return gfx::RectF();
  61. }
  62. if (!FPDF_PageToDevice(page, 0, 0, output_width, output_height, 0,
  63. input.right(), input.bottom(), &max_x, &max_y)) {
  64. return gfx::RectF();
  65. }
  66. if (max_x < min_x)
  67. std::swap(min_x, max_x);
  68. if (max_y < min_y)
  69. std::swap(min_y, max_y);
  70. // Make sure small but non-zero dimensions for `input` does not get rounded
  71. // down to 0.
  72. int width = max_x - min_x;
  73. int height = max_y - min_y;
  74. if (width == 0 && input.width())
  75. width = 1;
  76. if (height == 0 && input.height())
  77. height = 1;
  78. gfx::RectF output_rect(
  79. ConvertUnitFloat(min_x, kPointsPerInch, kPixelsPerInch),
  80. ConvertUnitFloat(min_y, kPointsPerInch, kPixelsPerInch),
  81. ConvertUnitFloat(width, kPointsPerInch, kPixelsPerInch),
  82. ConvertUnitFloat(height, kPointsPerInch, kPixelsPerInch));
  83. return output_rect;
  84. }
  85. gfx::RectF GetFloatCharRectInPixels(FPDF_PAGE page,
  86. FPDF_TEXTPAGE text_page,
  87. int index) {
  88. double left;
  89. double right;
  90. double bottom;
  91. double top;
  92. if (!FPDFText_GetCharBox(text_page, index, &left, &right, &bottom, &top))
  93. return gfx::RectF();
  94. if (right < left)
  95. std::swap(left, right);
  96. if (bottom < top)
  97. std::swap(top, bottom);
  98. gfx::RectF page_coords(left, top, right - left, bottom - top);
  99. return FloatPageRectToPixelRect(page, page_coords);
  100. }
  101. int GetFirstNonUnicodeWhiteSpaceCharIndex(FPDF_TEXTPAGE text_page,
  102. int start_char_index,
  103. int chars_count) {
  104. int i = start_char_index;
  105. while (i < chars_count &&
  106. base::IsUnicodeWhitespace(FPDFText_GetUnicode(text_page, i))) {
  107. i++;
  108. }
  109. return i;
  110. }
  111. AccessibilityTextDirection GetDirectionFromAngle(float angle) {
  112. // Rotating the angle by 45 degrees to simplify the conditions statements.
  113. // It's like if we rotated the whole cartesian coordinate system like below.
  114. // X X
  115. // X IV X
  116. // X X
  117. // X X
  118. // X X
  119. // III X I
  120. // X X
  121. // X X
  122. // X X
  123. // X II X
  124. // X X
  125. angle = fmodf(angle + k45DegreesInRadians, k360DegreesInRadians);
  126. // Quadrant I.
  127. if (angle >= 0 && angle <= k90DegreesInRadians)
  128. return AccessibilityTextDirection::kLeftToRight;
  129. // Quadrant II.
  130. if (angle > k90DegreesInRadians && angle <= k180DegreesInRadians)
  131. return AccessibilityTextDirection::kTopToBottom;
  132. // Quadrant III.
  133. if (angle > k180DegreesInRadians && angle <= k270DegreesInRadians)
  134. return AccessibilityTextDirection::kRightToLeft;
  135. // Quadrant IV.
  136. return AccessibilityTextDirection::kBottomToTop;
  137. }
  138. void AddCharSizeToAverageCharSize(gfx::SizeF new_size,
  139. gfx::SizeF* avg_size,
  140. int* count) {
  141. // Some characters sometimes have a bogus empty bounding box. We don't want
  142. // them to impact the average.
  143. if (!new_size.IsEmpty()) {
  144. avg_size->set_width((avg_size->width() * *count + new_size.width()) /
  145. (*count + 1));
  146. avg_size->set_height((avg_size->height() * *count + new_size.height()) /
  147. (*count + 1));
  148. (*count)++;
  149. }
  150. }
  151. float GetRotatedCharWidth(float angle, const gfx::SizeF& size) {
  152. return fabsf(cosf(angle) * size.width()) + fabsf(sinf(angle) * size.height());
  153. }
  154. float GetAngleOfVector(const gfx::Vector2dF& v) {
  155. float angle = atan2f(v.y(), v.x());
  156. if (angle < 0)
  157. angle += k360DegreesInRadians;
  158. return angle;
  159. }
  160. float GetAngleDifference(float a, float b) {
  161. // This is either the difference or (360 - difference).
  162. float x = fmodf(fabsf(b - a), k360DegreesInRadians);
  163. return x > k180DegreesInRadians ? k360DegreesInRadians - x : x;
  164. }
  165. bool FloatEquals(float f1, float f2) {
  166. // The idea behind this is to use this fraction of the larger of the
  167. // two numbers as the limit of the difference. This breaks down near
  168. // zero, so we reuse this as the minimum absolute size we will use
  169. // for the base of the scale too.
  170. static constexpr float kEpsilonScale = 0.00001f;
  171. return fabsf(f1 - f2) <
  172. kEpsilonScale * fmaxf(fmaxf(fabsf(f1), fabsf(f2)), kEpsilonScale);
  173. }
  174. bool IsRadioButtonOrCheckBox(int button_type) {
  175. return button_type == FPDF_FORMFIELD_CHECKBOX ||
  176. button_type == FPDF_FORMFIELD_RADIOBUTTON;
  177. }
  178. template <typename T>
  179. bool CompareTextRuns(const T& a, const T& b) {
  180. return a.text_range.index < b.text_range.index;
  181. }
  182. // Set text run style information based on a character of the text run.
  183. AccessibilityTextStyleInfo CalculateTextRunStyleInfo(FPDF_TEXTPAGE text_page,
  184. int char_index) {
  185. AccessibilityTextStyleInfo style_info;
  186. style_info.font_size = FPDFText_GetFontSize(text_page, char_index);
  187. int flags = 0;
  188. size_t buffer_size =
  189. FPDFText_GetFontInfo(text_page, char_index, nullptr, 0, &flags);
  190. if (buffer_size > 0) {
  191. PDFiumAPIStringBufferAdapter<std::string> api_string_adapter(
  192. &style_info.font_name, buffer_size, true);
  193. void* data = api_string_adapter.GetData();
  194. size_t bytes_written =
  195. FPDFText_GetFontInfo(text_page, char_index, data, buffer_size, nullptr);
  196. // Trim the null character.
  197. api_string_adapter.Close(bytes_written);
  198. }
  199. style_info.font_weight = FPDFText_GetFontWeight(text_page, char_index);
  200. // As defined in PDF 1.7 table 5.20.
  201. constexpr int kFlagItalic = (1 << 6);
  202. // Bold text is considered bold when greater than or equal to 700.
  203. constexpr int kStandardBoldValue = 700;
  204. style_info.is_italic = (flags & kFlagItalic);
  205. style_info.is_bold = style_info.font_weight >= kStandardBoldValue;
  206. unsigned int fill_r;
  207. unsigned int fill_g;
  208. unsigned int fill_b;
  209. unsigned int fill_a;
  210. if (FPDFText_GetFillColor(text_page, char_index, &fill_r, &fill_g, &fill_b,
  211. &fill_a)) {
  212. style_info.fill_color = MakeARGB(fill_a, fill_r, fill_g, fill_b);
  213. } else {
  214. style_info.fill_color = MakeARGB(0xff, 0, 0, 0);
  215. }
  216. unsigned int stroke_r;
  217. unsigned int stroke_g;
  218. unsigned int stroke_b;
  219. unsigned int stroke_a;
  220. if (FPDFText_GetStrokeColor(text_page, char_index, &stroke_r, &stroke_g,
  221. &stroke_b, &stroke_a)) {
  222. style_info.stroke_color = MakeARGB(stroke_a, stroke_r, stroke_g, stroke_b);
  223. } else {
  224. style_info.stroke_color = MakeARGB(0xff, 0, 0, 0);
  225. }
  226. int render_mode = FPDFText_GetTextRenderMode(text_page, char_index);
  227. DCHECK_GE(render_mode,
  228. static_cast<int>(AccessibilityTextRenderMode::kUnknown));
  229. DCHECK_LE(render_mode,
  230. static_cast<int>(AccessibilityTextRenderMode::kMaxValue));
  231. style_info.render_mode =
  232. static_cast<AccessibilityTextRenderMode>(render_mode);
  233. return style_info;
  234. }
  235. // Returns true if the character at index `char_index` in `text_page` has the
  236. // same text style as the text run.
  237. bool AreTextStyleEqual(FPDF_TEXTPAGE text_page,
  238. int char_index,
  239. const AccessibilityTextStyleInfo& style) {
  240. AccessibilityTextStyleInfo char_style =
  241. CalculateTextRunStyleInfo(text_page, char_index);
  242. return char_style.font_name == style.font_name &&
  243. char_style.font_weight == style.font_weight &&
  244. char_style.render_mode == style.render_mode &&
  245. FloatEquals(char_style.font_size, style.font_size) &&
  246. char_style.fill_color == style.fill_color &&
  247. char_style.stroke_color == style.stroke_color &&
  248. char_style.is_italic == style.is_italic &&
  249. char_style.is_bold == style.is_bold;
  250. }
  251. } // namespace
  252. PDFiumPage::LinkTarget::LinkTarget() : page(-1) {}
  253. PDFiumPage::LinkTarget::LinkTarget(const LinkTarget& other) = default;
  254. PDFiumPage::LinkTarget::~LinkTarget() = default;
  255. PDFiumPage::PDFiumPage(PDFiumEngine* engine, int i)
  256. : engine_(engine), index_(i), available_(false) {}
  257. PDFiumPage::PDFiumPage(PDFiumPage&& that) = default;
  258. PDFiumPage::~PDFiumPage() {
  259. DCHECK_EQ(0, preventing_unload_count_);
  260. }
  261. void PDFiumPage::Unload() {
  262. // Do not unload while in the middle of a load.
  263. if (preventing_unload_count_)
  264. return;
  265. text_page_.reset();
  266. if (page_) {
  267. if (engine_->form()) {
  268. FORM_OnBeforeClosePage(page(), engine_->form());
  269. }
  270. page_.reset();
  271. }
  272. }
  273. FPDF_PAGE PDFiumPage::GetPage() {
  274. ScopedUnsupportedFeature scoped_unsupported_feature(engine_);
  275. if (!available_)
  276. return nullptr;
  277. if (!page_) {
  278. ScopedUnloadPreventer scoped_unload_preventer(this);
  279. page_.reset(FPDF_LoadPage(engine_->doc(), index_));
  280. if (page_ && engine_->form()) {
  281. FORM_OnAfterLoadPage(page(), engine_->form());
  282. }
  283. }
  284. return page();
  285. }
  286. FPDF_TEXTPAGE PDFiumPage::GetTextPage() {
  287. if (!available_)
  288. return nullptr;
  289. if (!text_page_) {
  290. ScopedUnloadPreventer scoped_unload_preventer(this);
  291. text_page_.reset(FPDFText_LoadPage(GetPage()));
  292. }
  293. return text_page();
  294. }
  295. void PDFiumPage::CalculatePageObjectTextRunBreaks() {
  296. if (calculated_page_object_text_run_breaks_)
  297. return;
  298. calculated_page_object_text_run_breaks_ = true;
  299. int chars_count = FPDFText_CountChars(GetTextPage());
  300. if (chars_count == 0)
  301. return;
  302. CalculateLinks();
  303. for (const auto& link : links_) {
  304. if (link.start_char_index >= 0 && link.start_char_index < chars_count) {
  305. page_object_text_run_breaks_.insert(link.start_char_index);
  306. int next_text_run_break_index = link.start_char_index + link.char_count;
  307. // Don't insert a break if the link is at the end of the page text.
  308. if (next_text_run_break_index < chars_count) {
  309. page_object_text_run_breaks_.insert(next_text_run_break_index);
  310. }
  311. }
  312. }
  313. PopulateAnnotations();
  314. for (const auto& highlight : highlights_) {
  315. if (highlight.start_char_index >= 0 &&
  316. highlight.start_char_index < chars_count) {
  317. page_object_text_run_breaks_.insert(highlight.start_char_index);
  318. int next_text_run_break_index =
  319. highlight.start_char_index + highlight.char_count;
  320. // Don't insert a break if the highlight is at the end of the page text.
  321. if (next_text_run_break_index < chars_count) {
  322. page_object_text_run_breaks_.insert(next_text_run_break_index);
  323. }
  324. }
  325. }
  326. }
  327. absl::optional<AccessibilityTextRunInfo> PDFiumPage::GetTextRunInfo(
  328. int start_char_index) {
  329. FPDF_PAGE page = GetPage();
  330. FPDF_TEXTPAGE text_page = GetTextPage();
  331. int chars_count = FPDFText_CountChars(text_page);
  332. // Check to make sure `start_char_index` is within bounds.
  333. if (start_char_index < 0 || start_char_index >= chars_count)
  334. return absl::nullopt;
  335. int actual_start_char_index = GetFirstNonUnicodeWhiteSpaceCharIndex(
  336. text_page, start_char_index, chars_count);
  337. // Check to see if GetFirstNonUnicodeWhiteSpaceCharIndex() iterated through
  338. // all the characters.
  339. if (actual_start_char_index >= chars_count) {
  340. // If so, `info.len` needs to take the number of characters
  341. // iterated into account.
  342. DCHECK_GT(actual_start_char_index, start_char_index);
  343. AccessibilityTextRunInfo info;
  344. info.len = chars_count - start_char_index;
  345. return info;
  346. }
  347. // If the first character in a text run is a space, we need to start
  348. // `text_run_bounds` from the space character instead of the first
  349. // non-space unicode character.
  350. gfx::RectF text_run_bounds =
  351. actual_start_char_index > start_char_index
  352. ? GetFloatCharRectInPixels(page, text_page, start_char_index)
  353. : gfx::RectF();
  354. // Pdfium trims more than 1 consecutive spaces to 1 space.
  355. DCHECK_LE(actual_start_char_index - start_char_index, 1);
  356. int char_index = actual_start_char_index;
  357. // Set text run's style info from the first character of the text run.
  358. AccessibilityTextRunInfo info;
  359. info.style = CalculateTextRunStyleInfo(text_page, char_index);
  360. gfx::RectF start_char_rect =
  361. GetFloatCharRectInPixels(page, text_page, char_index);
  362. float text_run_font_size = info.style.font_size;
  363. // Heuristic: Initialize the average character size to one-third of the font
  364. // size to avoid having the first few characters misrepresent the average.
  365. // Without it, if a text run starts with a '.', its small bounding box could
  366. // lead to a break in the text run after only one space. Ex: ". Hello World"
  367. // would be split in two runs: "." and "Hello World".
  368. double font_size_minimum = FPDFText_GetFontSize(text_page, char_index) / 3.0;
  369. gfx::SizeF avg_char_size(font_size_minimum, font_size_minimum);
  370. int non_whitespace_chars_count = 1;
  371. AddCharSizeToAverageCharSize(start_char_rect.size(), &avg_char_size,
  372. &non_whitespace_chars_count);
  373. // Add first non-space char to text run.
  374. text_run_bounds.Union(start_char_rect);
  375. AccessibilityTextDirection char_direction =
  376. GetDirectionFromAngle(FPDFText_GetCharAngle(text_page, char_index));
  377. if (char_index < chars_count)
  378. char_index++;
  379. gfx::RectF prev_char_rect = start_char_rect;
  380. float estimated_font_size =
  381. std::max(start_char_rect.width(), start_char_rect.height());
  382. // The angle of the vector starting at the first character center-point and
  383. // ending at the current last character center-point.
  384. float text_run_angle = 0;
  385. CalculatePageObjectTextRunBreaks();
  386. const auto breakpoint_iter =
  387. std::lower_bound(page_object_text_run_breaks_.begin(),
  388. page_object_text_run_breaks_.end(), char_index);
  389. int breakpoint_index = breakpoint_iter != page_object_text_run_breaks_.end()
  390. ? *breakpoint_iter
  391. : -1;
  392. // Continue adding characters until heuristics indicate we should end the text
  393. // run.
  394. while (char_index < chars_count) {
  395. // Split a text run when it encounters a page object like links or images.
  396. if (char_index == breakpoint_index)
  397. break;
  398. unsigned int character = FPDFText_GetUnicode(text_page, char_index);
  399. gfx::RectF char_rect =
  400. GetFloatCharRectInPixels(page, text_page, char_index);
  401. if (!base::IsUnicodeWhitespace(character)) {
  402. // Heuristic: End the text run if the text style of the current character
  403. // is different from the text run's style.
  404. if (!AreTextStyleEqual(text_page, char_index, info.style))
  405. break;
  406. // Heuristic: End text run if character isn't going in the same direction.
  407. if (char_direction !=
  408. GetDirectionFromAngle(FPDFText_GetCharAngle(text_page, char_index)))
  409. break;
  410. // Heuristic: End the text run if the difference between the text run
  411. // angle and the angle between the center-points of the previous and
  412. // current characters is greater than 90 degrees.
  413. float current_angle = GetAngleOfVector(char_rect.CenterPoint() -
  414. prev_char_rect.CenterPoint());
  415. if (start_char_rect != prev_char_rect) {
  416. text_run_angle = GetAngleOfVector(prev_char_rect.CenterPoint() -
  417. start_char_rect.CenterPoint());
  418. if (GetAngleDifference(text_run_angle, current_angle) >
  419. k90DegreesInRadians) {
  420. break;
  421. }
  422. }
  423. // Heuristic: End the text run if the center-point distance to the
  424. // previous character is less than 2.5x the average character size.
  425. AddCharSizeToAverageCharSize(char_rect.size(), &avg_char_size,
  426. &non_whitespace_chars_count);
  427. float avg_char_width = GetRotatedCharWidth(current_angle, avg_char_size);
  428. float distance =
  429. (char_rect.CenterPoint() - prev_char_rect.CenterPoint()).Length() -
  430. GetRotatedCharWidth(current_angle, char_rect.size()) / 2 -
  431. GetRotatedCharWidth(current_angle, prev_char_rect.size()) / 2;
  432. if (distance > 2.5f * avg_char_width)
  433. break;
  434. text_run_bounds.Union(char_rect);
  435. prev_char_rect = char_rect;
  436. }
  437. if (!char_rect.IsEmpty()) {
  438. // Update the estimated font size if needed.
  439. float char_largest_side = std::max(char_rect.height(), char_rect.width());
  440. estimated_font_size = std::max(char_largest_side, estimated_font_size);
  441. }
  442. char_index++;
  443. }
  444. // Some PDFs have missing or obviously bogus font sizes; substitute the
  445. // font size by the width or height (whichever's the largest) of the bigger
  446. // character in the current text run.
  447. if (text_run_font_size <= 1 || text_run_font_size < estimated_font_size / 2 ||
  448. text_run_font_size > estimated_font_size * 2) {
  449. text_run_font_size = estimated_font_size;
  450. }
  451. info.len = char_index - start_char_index;
  452. info.style.font_size = text_run_font_size;
  453. info.bounds = text_run_bounds;
  454. // Infer text direction from first and last character of the text run. We
  455. // can't base our decision on the character direction, since a character of a
  456. // RTL language will have an angle of 0 when not rotated, just like a
  457. // character in a LTR language.
  458. info.direction = char_index - actual_start_char_index > 1
  459. ? GetDirectionFromAngle(text_run_angle)
  460. : AccessibilityTextDirection::kNone;
  461. return info;
  462. }
  463. uint32_t PDFiumPage::GetCharUnicode(int char_index) {
  464. FPDF_TEXTPAGE text_page = GetTextPage();
  465. return FPDFText_GetUnicode(text_page, char_index);
  466. }
  467. gfx::RectF PDFiumPage::GetCharBounds(int char_index) {
  468. FPDF_PAGE page = GetPage();
  469. FPDF_TEXTPAGE text_page = GetTextPage();
  470. return GetFloatCharRectInPixels(page, text_page, char_index);
  471. }
  472. gfx::RectF PDFiumPage::GetCroppedRect() {
  473. FPDF_PAGE page = GetPage();
  474. FS_RECTF raw_rect;
  475. if (!FPDF_GetPageBoundingBox(page, &raw_rect))
  476. return gfx::RectF();
  477. if (raw_rect.right < raw_rect.left)
  478. std::swap(raw_rect.right, raw_rect.left);
  479. if (raw_rect.bottom > raw_rect.top)
  480. std::swap(raw_rect.bottom, raw_rect.top);
  481. gfx::RectF rect(raw_rect.left, raw_rect.bottom,
  482. raw_rect.right - raw_rect.left,
  483. raw_rect.top - raw_rect.bottom);
  484. return FloatPageRectToPixelRect(page, rect);
  485. }
  486. bool PDFiumPage::IsCharInPageBounds(int char_index,
  487. const gfx::RectF& page_bounds) {
  488. gfx::RectF char_bounds = GetCharBounds(char_index);
  489. // Make sure `char_bounds` has a minimum size so Intersects() works correctly.
  490. if (char_bounds.IsEmpty()) {
  491. static constexpr gfx::SizeF kMinimumSize(0.0001f, 0.0001f);
  492. char_bounds.set_size(kMinimumSize);
  493. }
  494. return page_bounds.Intersects(char_bounds);
  495. }
  496. std::vector<AccessibilityLinkInfo> PDFiumPage::GetLinkInfo(
  497. const std::vector<AccessibilityTextRunInfo>& text_runs) {
  498. std::vector<AccessibilityLinkInfo> link_info;
  499. if (!available_)
  500. return link_info;
  501. CalculateLinks();
  502. link_info.reserve(links_.size());
  503. for (size_t i = 0; i < links_.size(); ++i) {
  504. const Link& link = links_[i];
  505. AccessibilityLinkInfo cur_info;
  506. cur_info.url = link.target.url;
  507. cur_info.index_in_page = i;
  508. cur_info.text_range = GetEnclosingTextRunRangeForCharRange(
  509. text_runs, link.start_char_index, link.char_count);
  510. gfx::Rect link_rect;
  511. for (const auto& rect : link.bounding_rects)
  512. link_rect.Union(rect);
  513. cur_info.bounds = gfx::RectF(link_rect.x(), link_rect.y(),
  514. link_rect.width(), link_rect.height());
  515. link_info.push_back(std::move(cur_info));
  516. }
  517. std::sort(link_info.begin(), link_info.end(),
  518. CompareTextRuns<AccessibilityLinkInfo>);
  519. return link_info;
  520. }
  521. std::vector<AccessibilityImageInfo> PDFiumPage::GetImageInfo(
  522. uint32_t text_run_count) {
  523. std::vector<AccessibilityImageInfo> image_info;
  524. if (!available_)
  525. return image_info;
  526. CalculateImages();
  527. image_info.reserve(images_.size());
  528. for (const Image& image : images_) {
  529. AccessibilityImageInfo cur_info;
  530. cur_info.alt_text = image.alt_text;
  531. // TODO(mohitb): Update text run index to nearest text run to image bounds.
  532. cur_info.text_run_index = text_run_count;
  533. cur_info.bounds =
  534. gfx::RectF(image.bounding_rect.x(), image.bounding_rect.y(),
  535. image.bounding_rect.width(), image.bounding_rect.height());
  536. cur_info.image_data = image.image_data;
  537. image_info.push_back(std::move(cur_info));
  538. }
  539. return image_info;
  540. }
  541. std::vector<AccessibilityHighlightInfo> PDFiumPage::GetHighlightInfo(
  542. const std::vector<AccessibilityTextRunInfo>& text_runs) {
  543. std::vector<AccessibilityHighlightInfo> highlight_info;
  544. if (!available_)
  545. return highlight_info;
  546. PopulateAnnotations();
  547. highlight_info.reserve(highlights_.size());
  548. for (size_t i = 0; i < highlights_.size(); ++i) {
  549. const Highlight& highlight = highlights_[i];
  550. AccessibilityHighlightInfo cur_info;
  551. cur_info.index_in_page = i;
  552. cur_info.text_range = GetEnclosingTextRunRangeForCharRange(
  553. text_runs, highlight.start_char_index, highlight.char_count);
  554. cur_info.bounds = gfx::RectF(
  555. highlight.bounding_rect.x(), highlight.bounding_rect.y(),
  556. highlight.bounding_rect.width(), highlight.bounding_rect.height());
  557. cur_info.color = highlight.color;
  558. cur_info.note_text = highlight.note_text;
  559. highlight_info.push_back(std::move(cur_info));
  560. }
  561. std::sort(highlight_info.begin(), highlight_info.end(),
  562. CompareTextRuns<AccessibilityHighlightInfo>);
  563. return highlight_info;
  564. }
  565. std::vector<AccessibilityTextFieldInfo> PDFiumPage::GetTextFieldInfo(
  566. uint32_t text_run_count) {
  567. std::vector<AccessibilityTextFieldInfo> text_field_info;
  568. if (!available_)
  569. return text_field_info;
  570. PopulateAnnotations();
  571. text_field_info.reserve(text_fields_.size());
  572. for (size_t i = 0; i < text_fields_.size(); ++i) {
  573. const TextField& text_field = text_fields_[i];
  574. AccessibilityTextFieldInfo cur_info;
  575. cur_info.name = text_field.name;
  576. cur_info.value = text_field.value;
  577. cur_info.index_in_page = i;
  578. cur_info.is_read_only = !!(text_field.flags & FPDF_FORMFLAG_READONLY);
  579. cur_info.is_required = !!(text_field.flags & FPDF_FORMFLAG_REQUIRED);
  580. cur_info.is_password = !!(text_field.flags & FPDF_FORMFLAG_TEXT_PASSWORD);
  581. // TODO(crbug.com/1030242): Update text run index to nearest text run to
  582. // text field bounds.
  583. cur_info.text_run_index = text_run_count;
  584. cur_info.bounds = gfx::RectF(
  585. text_field.bounding_rect.x(), text_field.bounding_rect.y(),
  586. text_field.bounding_rect.width(), text_field.bounding_rect.height());
  587. text_field_info.push_back(std::move(cur_info));
  588. }
  589. return text_field_info;
  590. }
  591. PDFiumPage::Area PDFiumPage::GetLinkTargetAtIndex(int link_index,
  592. LinkTarget* target) {
  593. if (!available_ || link_index < 0)
  594. return NONSELECTABLE_AREA;
  595. CalculateLinks();
  596. if (link_index >= static_cast<int>(links_.size()))
  597. return NONSELECTABLE_AREA;
  598. *target = links_[link_index].target;
  599. return target->url.empty() ? DOCLINK_AREA : WEBLINK_AREA;
  600. }
  601. PDFiumPage::Area PDFiumPage::GetLinkTarget(FPDF_LINK link, LinkTarget* target) {
  602. FPDF_DEST dest_link = FPDFLink_GetDest(engine_->doc(), link);
  603. if (dest_link)
  604. return GetDestinationTarget(dest_link, target);
  605. FPDF_ACTION action = FPDFLink_GetAction(link);
  606. if (!action)
  607. return NONSELECTABLE_AREA;
  608. switch (FPDFAction_GetType(action)) {
  609. case PDFACTION_GOTO: {
  610. FPDF_DEST dest_action = FPDFAction_GetDest(engine_->doc(), action);
  611. if (dest_action)
  612. return GetDestinationTarget(dest_action, target);
  613. // TODO(crbug.com/55776): We don't fully support all types of the
  614. // in-document links.
  615. return NONSELECTABLE_AREA;
  616. }
  617. case PDFACTION_URI:
  618. return GetURITarget(action, target);
  619. // TODO(crbug.com/767191): Support PDFACTION_LAUNCH.
  620. // TODO(crbug.com/142344): Support PDFACTION_REMOTEGOTO.
  621. case PDFACTION_LAUNCH:
  622. case PDFACTION_REMOTEGOTO:
  623. default:
  624. return NONSELECTABLE_AREA;
  625. }
  626. }
  627. PDFiumPage::Area PDFiumPage::GetCharIndex(const gfx::Point& point,
  628. PageOrientation orientation,
  629. int* char_index,
  630. int* form_type,
  631. LinkTarget* target) {
  632. if (!available_)
  633. return NONSELECTABLE_AREA;
  634. gfx::Point device_point = point - rect_.OffsetFromOrigin();
  635. double new_x;
  636. double new_y;
  637. if (!FPDF_DeviceToPage(GetPage(), 0, 0, rect_.width(), rect_.height(),
  638. ToPDFiumRotation(orientation), device_point.x(),
  639. device_point.y(), &new_x, &new_y)) {
  640. return NONSELECTABLE_AREA;
  641. }
  642. // hit detection tolerance, in points.
  643. constexpr double kTolerance = 20.0;
  644. int rv = FPDFText_GetCharIndexAtPos(GetTextPage(), new_x, new_y, kTolerance,
  645. kTolerance);
  646. *char_index = rv;
  647. FPDF_LINK link = FPDFLink_GetLinkAtPoint(GetPage(), new_x, new_y);
  648. int control =
  649. FPDFPage_HasFormFieldAtPoint(engine_->form(), GetPage(), new_x, new_y);
  650. // If there is a control and link at the same point, figure out their z-order
  651. // to determine which is on top.
  652. if (link && control > FPDF_FORMFIELD_UNKNOWN) {
  653. int control_z_order = FPDFPage_FormFieldZOrderAtPoint(
  654. engine_->form(), GetPage(), new_x, new_y);
  655. int link_z_order = FPDFLink_GetLinkZOrderAtPoint(GetPage(), new_x, new_y);
  656. DCHECK_NE(control_z_order, link_z_order);
  657. if (control_z_order > link_z_order) {
  658. *form_type = control;
  659. return FormTypeToArea(*form_type);
  660. }
  661. // We don't handle all possible link types of the PDF. For example,
  662. // launch actions, cross-document links, etc.
  663. // In that case, GetLinkTarget() will return NONSELECTABLE_AREA
  664. // and we should proceed with area detection.
  665. Area area = GetLinkTarget(link, target);
  666. if (area != NONSELECTABLE_AREA)
  667. return area;
  668. } else if (link) {
  669. // We don't handle all possible link types of the PDF. For example,
  670. // launch actions, cross-document links, etc.
  671. // See identical block above.
  672. Area area = GetLinkTarget(link, target);
  673. if (area != NONSELECTABLE_AREA)
  674. return area;
  675. } else if (control > FPDF_FORMFIELD_UNKNOWN) {
  676. *form_type = control;
  677. return FormTypeToArea(*form_type);
  678. }
  679. if (rv < 0)
  680. return NONSELECTABLE_AREA;
  681. return GetLink(*char_index, target) != -1 ? WEBLINK_AREA : TEXT_AREA;
  682. }
  683. // static
  684. PDFiumPage::Area PDFiumPage::FormTypeToArea(int form_type) {
  685. switch (form_type) {
  686. case FPDF_FORMFIELD_COMBOBOX:
  687. case FPDF_FORMFIELD_TEXTFIELD:
  688. #if defined(PDF_ENABLE_XFA)
  689. // TODO(bug_353450): figure out selection and copying for XFA fields.
  690. case FPDF_FORMFIELD_XFA_COMBOBOX:
  691. case FPDF_FORMFIELD_XFA_TEXTFIELD:
  692. #endif
  693. return FORM_TEXT_AREA;
  694. default:
  695. return NONSELECTABLE_AREA;
  696. }
  697. }
  698. char16_t PDFiumPage::GetCharAtIndex(int index) {
  699. if (!available_)
  700. return u'\0';
  701. return static_cast<char16_t>(FPDFText_GetUnicode(GetTextPage(), index));
  702. }
  703. int PDFiumPage::GetCharCount() {
  704. if (!available_)
  705. return 0;
  706. return FPDFText_CountChars(GetTextPage());
  707. }
  708. bool PDFiumPage::IsCharIndexInBounds(int index) {
  709. return index >= 0 && index < GetCharCount();
  710. }
  711. PDFiumPage::Area PDFiumPage::GetDestinationTarget(FPDF_DEST destination,
  712. LinkTarget* target) {
  713. if (!target)
  714. return NONSELECTABLE_AREA;
  715. const int page_index = FPDFDest_GetDestPageIndex(engine_->doc(), destination);
  716. if (page_index < 0)
  717. return NONSELECTABLE_AREA;
  718. target->page = page_index;
  719. absl::optional<float> x;
  720. absl::optional<float> y;
  721. GetPageDestinationTarget(destination, &x, &y, &target->zoom);
  722. // The page where a destination exists can be different from the page that it
  723. // targets. Calculating the in-page coordinates should be based on the target
  724. // page's size.
  725. PDFiumPage* target_page = engine_->GetPage(target->page);
  726. if (!target_page)
  727. return NONSELECTABLE_AREA;
  728. if (x) {
  729. target->x_in_pixels =
  730. target_page->PreProcessAndTransformInPageCoordX(x.value());
  731. }
  732. if (y) {
  733. target->y_in_pixels =
  734. target_page->PreProcessAndTransformInPageCoordY(y.value());
  735. }
  736. return DOCLINK_AREA;
  737. }
  738. void PDFiumPage::GetPageDestinationTarget(FPDF_DEST destination,
  739. absl::optional<float>* dest_x,
  740. absl::optional<float>* dest_y,
  741. absl::optional<float>* zoom_value) {
  742. *dest_x = absl::nullopt;
  743. *dest_y = absl::nullopt;
  744. *zoom_value = absl::nullopt;
  745. if (!available_)
  746. return;
  747. FPDF_BOOL has_x_coord;
  748. FPDF_BOOL has_y_coord;
  749. FPDF_BOOL has_zoom;
  750. FS_FLOAT x;
  751. FS_FLOAT y;
  752. FS_FLOAT zoom;
  753. FPDF_BOOL success = FPDFDest_GetLocationInPage(
  754. destination, &has_x_coord, &has_y_coord, &has_zoom, &x, &y, &zoom);
  755. if (!success)
  756. return;
  757. if (has_x_coord)
  758. *dest_x = x;
  759. if (has_y_coord)
  760. *dest_y = y;
  761. if (has_zoom)
  762. *zoom_value = zoom;
  763. }
  764. float PDFiumPage::PreProcessAndTransformInPageCoordX(float x) {
  765. // If `x` < 0, scroll to the left side of the page.
  766. // If `x` > page width, scroll to the right side of the page.
  767. return TransformPageToScreenX(
  768. base::clamp(x, 0.0f, FPDF_GetPageWidthF(GetPage())));
  769. }
  770. float PDFiumPage::PreProcessAndTransformInPageCoordY(float y) {
  771. // If `y` < 0, it is a valid input, no extra handling is needed.
  772. // If `y` > page height, scroll to the top of the page.
  773. return TransformPageToScreenY(std::min(y, FPDF_GetPageHeightF(GetPage())));
  774. }
  775. gfx::PointF PDFiumPage::TransformPageToScreenXY(const gfx::PointF& xy) {
  776. if (!available_)
  777. return gfx::PointF();
  778. gfx::RectF page_rect(xy.x(), xy.y(), 0, 0);
  779. gfx::RectF pixel_rect(FloatPageRectToPixelRect(GetPage(), page_rect));
  780. return gfx::PointF(pixel_rect.x(), pixel_rect.y());
  781. }
  782. float PDFiumPage::TransformPageToScreenX(float x) {
  783. return TransformPageToScreenXY(gfx::PointF(x, 0)).x();
  784. }
  785. float PDFiumPage::TransformPageToScreenY(float y) {
  786. return TransformPageToScreenXY(gfx::PointF(0, y)).y();
  787. }
  788. PDFiumPage::Area PDFiumPage::GetURITarget(FPDF_ACTION uri_action,
  789. LinkTarget* target) const {
  790. if (target) {
  791. std::string url = CallPDFiumStringBufferApi(
  792. base::BindRepeating(&FPDFAction_GetURIPath, engine_->doc(), uri_action),
  793. /*check_expected_size=*/true);
  794. if (!url.empty())
  795. target->url = url;
  796. }
  797. return WEBLINK_AREA;
  798. }
  799. int PDFiumPage::GetLink(int char_index, LinkTarget* target) {
  800. if (!available_)
  801. return -1;
  802. CalculateLinks();
  803. // Get the bounding box of the rect again, since it might have moved because
  804. // of the tolerance above.
  805. double left;
  806. double right;
  807. double bottom;
  808. double top;
  809. if (!FPDFText_GetCharBox(GetTextPage(), char_index, &left, &right, &bottom,
  810. &top)) {
  811. return -1;
  812. }
  813. gfx::Point origin = PageToScreen(gfx::Point(), 1.0, left, top, right, bottom,
  814. PageOrientation::kOriginal)
  815. .origin();
  816. for (size_t i = 0; i < links_.size(); ++i) {
  817. for (const auto& rect : links_[i].bounding_rects) {
  818. if (rect.Contains(origin)) {
  819. if (target)
  820. target->url = links_[i].target.url;
  821. return i;
  822. }
  823. }
  824. }
  825. return -1;
  826. }
  827. void PDFiumPage::CalculateLinks() {
  828. if (calculated_links_)
  829. return;
  830. calculated_links_ = true;
  831. PopulateWebLinks();
  832. PopulateAnnotationLinks();
  833. }
  834. void PDFiumPage::PopulateWebLinks() {
  835. ScopedFPDFPageLink links(FPDFLink_LoadWebLinks(GetTextPage()));
  836. int count = FPDFLink_CountWebLinks(links.get());
  837. for (int i = 0; i < count; ++i) {
  838. // WARNING: FPDFLink_GetURL() is not compatible with
  839. // CallPDFiumWideStringBufferApi().
  840. std::u16string url;
  841. int url_length = FPDFLink_GetURL(links.get(), i, nullptr, 0);
  842. if (url_length > 0) {
  843. PDFiumAPIStringBufferAdapter<std::u16string> api_string_adapter(
  844. &url, url_length, true);
  845. unsigned short* data =
  846. reinterpret_cast<unsigned short*>(api_string_adapter.GetData());
  847. int actual_length = FPDFLink_GetURL(links.get(), i, data, url_length);
  848. api_string_adapter.Close(actual_length);
  849. }
  850. Link link;
  851. link.target.url = base::UTF16ToUTF8(url);
  852. if (!engine_->IsValidLink(link.target.url))
  853. continue;
  854. // Make sure all the characters in the URL are valid per RFC 1738.
  855. // http://crbug.com/340326 has a sample bad PDF.
  856. // GURL does not work correctly, e.g. it just strips \t \r \n.
  857. bool is_invalid_url = false;
  858. for (size_t j = 0; j < link.target.url.length(); ++j) {
  859. // Control characters are not allowed.
  860. // 0x7F is also a control character.
  861. // 0x80 and above are not in US-ASCII.
  862. if (link.target.url[j] < ' ' || link.target.url[j] >= '\x7F') {
  863. is_invalid_url = true;
  864. break;
  865. }
  866. }
  867. if (is_invalid_url)
  868. continue;
  869. int rect_count = FPDFLink_CountRects(links.get(), i);
  870. for (int j = 0; j < rect_count; ++j) {
  871. double left;
  872. double top;
  873. double right;
  874. double bottom;
  875. if (!FPDFLink_GetRect(links.get(), i, j, &left, &top, &right, &bottom))
  876. continue;
  877. gfx::Rect rect = PageToScreen(gfx::Point(), 1.0, left, top, right, bottom,
  878. PageOrientation::kOriginal);
  879. if (rect.IsEmpty())
  880. continue;
  881. link.bounding_rects.push_back(rect);
  882. }
  883. FPDF_BOOL is_link_over_text = FPDFLink_GetTextRange(
  884. links.get(), i, &link.start_char_index, &link.char_count);
  885. DCHECK(is_link_over_text);
  886. links_.push_back(link);
  887. }
  888. }
  889. void PDFiumPage::PopulateAnnotationLinks() {
  890. int start_pos = 0;
  891. FPDF_LINK link_annot;
  892. FPDF_PAGE page = GetPage();
  893. while (FPDFLink_Enumerate(page, &start_pos, &link_annot)) {
  894. Link link;
  895. Area area = GetLinkTarget(link_annot, &link.target);
  896. if (area == NONSELECTABLE_AREA)
  897. continue;
  898. FS_RECTF link_rect;
  899. if (!FPDFLink_GetAnnotRect(link_annot, &link_rect))
  900. continue;
  901. // The horizontal/vertical coordinates in PDF Links could be
  902. // flipped. Swap the coordinates before further processing.
  903. if (link_rect.right < link_rect.left)
  904. std::swap(link_rect.right, link_rect.left);
  905. if (link_rect.bottom > link_rect.top)
  906. std::swap(link_rect.bottom, link_rect.top);
  907. int quad_point_count = FPDFLink_CountQuadPoints(link_annot);
  908. // Calculate the bounds of link using the quad points data.
  909. // If quad points for link is not present then use
  910. // `link_rect` to calculate the bounds instead.
  911. if (quad_point_count > 0) {
  912. for (int i = 0; i < quad_point_count; ++i) {
  913. FS_QUADPOINTSF point;
  914. if (FPDFLink_GetQuadPoints(link_annot, i, &point)) {
  915. // PDF Specifications: Quadpoints start from bottom left (x1, y1) and
  916. // runs counter clockwise.
  917. link.bounding_rects.push_back(
  918. PageToScreen(gfx::Point(), 1.0, point.x4, point.y4, point.x2,
  919. point.y2, PageOrientation::kOriginal));
  920. }
  921. }
  922. } else {
  923. link.bounding_rects.push_back(PageToScreen(
  924. gfx::Point(), 1.0, link_rect.left, link_rect.top, link_rect.right,
  925. link_rect.bottom, PageOrientation::kOriginal));
  926. }
  927. // Calculate underlying text range of link.
  928. GetUnderlyingTextRangeForRect(
  929. gfx::RectF(link_rect.left, link_rect.bottom,
  930. std::abs(link_rect.right - link_rect.left),
  931. std::abs(link_rect.bottom - link_rect.top)),
  932. &link.start_char_index, &link.char_count);
  933. links_.emplace_back(link);
  934. }
  935. }
  936. void PDFiumPage::CalculateImages() {
  937. if (calculated_images_)
  938. return;
  939. calculated_images_ = true;
  940. FPDF_PAGE page = GetPage();
  941. int page_object_count = FPDFPage_CountObjects(page);
  942. MarkedContentIdToImageMap marked_content_id_image_map;
  943. bool is_tagged = FPDFCatalog_IsTagged(engine_->doc());
  944. for (int i = 0; i < page_object_count; ++i) {
  945. FPDF_PAGEOBJECT page_object = FPDFPage_GetObject(page, i);
  946. if (FPDFPageObj_GetType(page_object) != FPDF_PAGEOBJ_IMAGE)
  947. continue;
  948. float left;
  949. float top;
  950. float right;
  951. float bottom;
  952. if (!FPDFPageObj_GetBounds(page_object, &left, &bottom, &right, &top))
  953. continue;
  954. Image image;
  955. image.page_object_index = i;
  956. image.bounding_rect = PageToScreen(gfx::Point(), 1.0, left, top, right,
  957. bottom, PageOrientation::kOriginal);
  958. if (is_tagged) {
  959. // Collect all marked content IDs for image objects so that they can
  960. // later be used to retrieve alt text from struct tree for the page.
  961. FPDF_IMAGEOBJ_METADATA image_metadata;
  962. if (FPDFImageObj_GetImageMetadata(page_object, page, &image_metadata)) {
  963. int marked_content_id = image_metadata.marked_content_id;
  964. if (marked_content_id >= 0) {
  965. // If `marked_content_id` is already present, ignore the one being
  966. // inserted.
  967. marked_content_id_image_map.insert(
  968. {marked_content_id, images_.size()});
  969. }
  970. }
  971. }
  972. images_.push_back(image);
  973. }
  974. if (!marked_content_id_image_map.empty())
  975. PopulateImageAltText(marked_content_id_image_map);
  976. if (!features::IsPdfOcrEnabled())
  977. return;
  978. // If requested by the user, we store the raw image data so that the OCR
  979. // service can try and retrieve textual and layout information from the image.
  980. // This is because alt text might be empty, or the PDF might simply be
  981. // untagged for accessibility.
  982. for (Image& image : images_) {
  983. if (!image.alt_text.empty())
  984. continue;
  985. FPDF_PAGEOBJECT page_object =
  986. FPDFPage_GetObject(page, image.page_object_index);
  987. ScopedFPDFBitmap bitmap(
  988. FPDFImageObj_GetRenderedBitmap(engine_->doc(), page, page_object));
  989. if (!bitmap)
  990. continue;
  991. SkImageInfo info = SkImageInfo::Make(
  992. FPDFBitmap_GetWidth(bitmap.get()), FPDFBitmap_GetHeight(bitmap.get()),
  993. kBGRA_8888_SkColorType, kOpaque_SkAlphaType);
  994. const size_t row_bytes = FPDFBitmap_GetStride(bitmap.get());
  995. SkPixmap pixels(info, FPDFBitmap_GetBuffer(bitmap.get()), row_bytes);
  996. if (image.image_data.tryAllocPixels(info, row_bytes))
  997. image.image_data.writePixels(pixels);
  998. }
  999. }
  1000. void PDFiumPage::PopulateImageAltText(
  1001. const MarkedContentIdToImageMap& marked_content_id_image_map) {
  1002. ScopedFPDFStructTree struct_tree(FPDF_StructTree_GetForPage(GetPage()));
  1003. if (!struct_tree)
  1004. return;
  1005. std::set<FPDF_STRUCTELEMENT> visited_elements;
  1006. int tree_children_count = FPDF_StructTree_CountChildren(struct_tree.get());
  1007. for (int i = 0; i < tree_children_count; ++i) {
  1008. FPDF_STRUCTELEMENT current_element =
  1009. FPDF_StructTree_GetChildAtIndex(struct_tree.get(), i);
  1010. PopulateImageAltTextForStructElement(marked_content_id_image_map,
  1011. current_element, &visited_elements);
  1012. }
  1013. }
  1014. void PDFiumPage::PopulateImageAltTextForStructElement(
  1015. const MarkedContentIdToImageMap& marked_content_id_image_map,
  1016. FPDF_STRUCTELEMENT current_element,
  1017. std::set<FPDF_STRUCTELEMENT>* visited_elements) {
  1018. if (!current_element)
  1019. return;
  1020. bool inserted = visited_elements->insert(current_element).second;
  1021. if (!inserted)
  1022. return;
  1023. int marked_content_id =
  1024. FPDF_StructElement_GetMarkedContentID(current_element);
  1025. if (marked_content_id >= 0) {
  1026. auto it = marked_content_id_image_map.find(marked_content_id);
  1027. if (it != marked_content_id_image_map.end() &&
  1028. images_[it->second].alt_text.empty()) {
  1029. images_[it->second].alt_text =
  1030. base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(
  1031. base::BindRepeating(&FPDF_StructElement_GetAltText,
  1032. current_element),
  1033. /*check_expected_size=*/true));
  1034. }
  1035. }
  1036. int children_count = FPDF_StructElement_CountChildren(current_element);
  1037. for (int i = 0; i < children_count; ++i) {
  1038. FPDF_STRUCTELEMENT child =
  1039. FPDF_StructElement_GetChildAtIndex(current_element, i);
  1040. PopulateImageAltTextForStructElement(marked_content_id_image_map, child,
  1041. visited_elements);
  1042. }
  1043. }
  1044. void PDFiumPage::PopulateAnnotations() {
  1045. if (calculated_annotations_)
  1046. return;
  1047. FPDF_PAGE page = GetPage();
  1048. if (!page)
  1049. return;
  1050. int annotation_count = FPDFPage_GetAnnotCount(page);
  1051. for (int i = 0; i < annotation_count; ++i) {
  1052. ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(page, i));
  1053. DCHECK(annot);
  1054. FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annot.get());
  1055. switch (subtype) {
  1056. case FPDF_ANNOT_HIGHLIGHT: {
  1057. PopulateHighlight(annot.get());
  1058. break;
  1059. }
  1060. case FPDF_ANNOT_WIDGET: {
  1061. PopulateFormField(annot.get());
  1062. break;
  1063. }
  1064. default:
  1065. break;
  1066. }
  1067. }
  1068. calculated_annotations_ = true;
  1069. }
  1070. void PDFiumPage::PopulateHighlight(FPDF_ANNOTATION annot) {
  1071. DCHECK(annot);
  1072. DCHECK_EQ(FPDFAnnot_GetSubtype(annot), FPDF_ANNOT_HIGHLIGHT);
  1073. FS_RECTF rect;
  1074. if (!FPDFAnnot_GetRect(annot, &rect))
  1075. return;
  1076. Highlight highlight;
  1077. // We use the bounding box of the highlight as the bounding rect.
  1078. highlight.bounding_rect =
  1079. PageToScreen(gfx::Point(), 1.0, rect.left, rect.top, rect.right,
  1080. rect.bottom, PageOrientation::kOriginal);
  1081. GetUnderlyingTextRangeForRect(
  1082. gfx::RectF(rect.left, rect.bottom, std::abs(rect.right - rect.left),
  1083. std::abs(rect.bottom - rect.top)),
  1084. &highlight.start_char_index, &highlight.char_count);
  1085. // Retrieve the color of the highlight.
  1086. unsigned int color_r;
  1087. unsigned int color_g;
  1088. unsigned int color_b;
  1089. unsigned int color_a;
  1090. FPDF_PAGEOBJECT page_object = FPDFAnnot_GetObject(annot, 0);
  1091. if (FPDFPageObj_GetFillColor(page_object, &color_r, &color_g, &color_b,
  1092. &color_a)) {
  1093. highlight.color = MakeARGB(color_a, color_r, color_g, color_b);
  1094. } else {
  1095. // Set the same default color as in pdfium. See calls to
  1096. // GetColorStringWithDefault() in CPVT_GenerateAP::Generate*AP() in
  1097. // pdfium.
  1098. highlight.color = MakeARGB(255, 255, 255, 0);
  1099. }
  1100. // Retrieve the contents of the popup note associated with highlight. See
  1101. // table 164 in ISO 32000-1:2008 spec for more details around "Contents" key
  1102. // in a highlight annotation.
  1103. static constexpr char kContents[] = "Contents";
  1104. highlight.note_text = base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(
  1105. base::BindRepeating(&FPDFAnnot_GetStringValue, annot, kContents),
  1106. /*check_expected_size=*/true));
  1107. highlights_.push_back(std::move(highlight));
  1108. }
  1109. void PDFiumPage::PopulateTextField(FPDF_ANNOTATION annot) {
  1110. DCHECK(annot);
  1111. FPDF_FORMHANDLE form_handle = engine_->form();
  1112. DCHECK_EQ(FPDFAnnot_GetFormFieldType(form_handle, annot),
  1113. FPDF_FORMFIELD_TEXTFIELD);
  1114. TextField text_field;
  1115. if (!PopulateFormFieldProperties(annot, &text_field))
  1116. return;
  1117. text_field.value = base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(
  1118. base::BindRepeating(&FPDFAnnot_GetFormFieldValue, form_handle, annot),
  1119. /*check_expected_size=*/true));
  1120. text_fields_.push_back(std::move(text_field));
  1121. }
  1122. void PDFiumPage::PopulateChoiceField(FPDF_ANNOTATION annot) {
  1123. DCHECK(annot);
  1124. FPDF_FORMHANDLE form_handle = engine_->form();
  1125. int form_field_type = FPDFAnnot_GetFormFieldType(form_handle, annot);
  1126. DCHECK(form_field_type == FPDF_FORMFIELD_LISTBOX ||
  1127. form_field_type == FPDF_FORMFIELD_COMBOBOX);
  1128. ChoiceField choice_field;
  1129. if (!PopulateFormFieldProperties(annot, &choice_field))
  1130. return;
  1131. int options_count = FPDFAnnot_GetOptionCount(form_handle, annot);
  1132. if (options_count < 0)
  1133. return;
  1134. choice_field.options.resize(options_count);
  1135. for (int i = 0; i < options_count; ++i) {
  1136. choice_field.options[i].name =
  1137. base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(
  1138. base::BindRepeating(&FPDFAnnot_GetOptionLabel, form_handle, annot,
  1139. i),
  1140. /*check_expected_size=*/true));
  1141. choice_field.options[i].is_selected =
  1142. FPDFAnnot_IsOptionSelected(form_handle, annot, i);
  1143. }
  1144. choice_fields_.push_back(std::move(choice_field));
  1145. }
  1146. void PDFiumPage::PopulateButton(FPDF_ANNOTATION annot) {
  1147. DCHECK(annot);
  1148. FPDF_FORMHANDLE form_handle = engine_->form();
  1149. int button_type = FPDFAnnot_GetFormFieldType(form_handle, annot);
  1150. DCHECK(button_type == FPDF_FORMFIELD_PUSHBUTTON ||
  1151. IsRadioButtonOrCheckBox(button_type));
  1152. Button button;
  1153. if (!PopulateFormFieldProperties(annot, &button))
  1154. return;
  1155. button.type = button_type;
  1156. if (IsRadioButtonOrCheckBox(button_type)) {
  1157. button.control_count = FPDFAnnot_GetFormControlCount(form_handle, annot);
  1158. if (button.control_count <= 0)
  1159. return;
  1160. button.control_index = FPDFAnnot_GetFormControlIndex(form_handle, annot);
  1161. button.value = base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(
  1162. base::BindRepeating(&FPDFAnnot_GetFormFieldExportValue, form_handle,
  1163. annot),
  1164. /*check_expected_size=*/true));
  1165. button.is_checked = FPDFAnnot_IsChecked(form_handle, annot);
  1166. }
  1167. buttons_.push_back(std::move(button));
  1168. }
  1169. void PDFiumPage::PopulateFormField(FPDF_ANNOTATION annot) {
  1170. DCHECK_EQ(FPDFAnnot_GetSubtype(annot), FPDF_ANNOT_WIDGET);
  1171. int form_field_type = FPDFAnnot_GetFormFieldType(engine_->form(), annot);
  1172. // TODO(crbug.com/1030242): Populate other types of form fields too.
  1173. switch (form_field_type) {
  1174. case FPDF_FORMFIELD_PUSHBUTTON:
  1175. case FPDF_FORMFIELD_CHECKBOX:
  1176. case FPDF_FORMFIELD_RADIOBUTTON: {
  1177. PopulateButton(annot);
  1178. break;
  1179. }
  1180. case FPDF_FORMFIELD_COMBOBOX:
  1181. case FPDF_FORMFIELD_LISTBOX: {
  1182. PopulateChoiceField(annot);
  1183. break;
  1184. }
  1185. case FPDF_FORMFIELD_TEXTFIELD: {
  1186. PopulateTextField(annot);
  1187. break;
  1188. }
  1189. default:
  1190. break;
  1191. }
  1192. }
  1193. bool PDFiumPage::PopulateFormFieldProperties(FPDF_ANNOTATION annot,
  1194. FormField* form_field) {
  1195. DCHECK(annot);
  1196. FS_RECTF rect;
  1197. if (!FPDFAnnot_GetRect(annot, &rect))
  1198. return false;
  1199. // We use the bounding box of the form field as the bounding rect.
  1200. form_field->bounding_rect =
  1201. PageToScreen(gfx::Point(), 1.0, rect.left, rect.top, rect.right,
  1202. rect.bottom, PageOrientation::kOriginal);
  1203. FPDF_FORMHANDLE form_handle = engine_->form();
  1204. form_field->name = base::UTF16ToUTF8(CallPDFiumWideStringBufferApi(
  1205. base::BindRepeating(&FPDFAnnot_GetFormFieldName, form_handle, annot),
  1206. /*check_expected_size=*/true));
  1207. form_field->flags = FPDFAnnot_GetFormFieldFlags(form_handle, annot);
  1208. return true;
  1209. }
  1210. bool PDFiumPage::GetUnderlyingTextRangeForRect(const gfx::RectF& rect,
  1211. int* start_index,
  1212. int* char_len) {
  1213. if (!available_)
  1214. return false;
  1215. FPDF_TEXTPAGE text_page = GetTextPage();
  1216. int char_count = FPDFText_CountChars(text_page);
  1217. if (char_count <= 0)
  1218. return false;
  1219. int start_char_index = -1;
  1220. int cur_char_count = 0;
  1221. // Iterate over page text to find such continuous characters whose mid-points
  1222. // lie inside the rectangle.
  1223. for (int i = 0; i < char_count; ++i) {
  1224. double char_left;
  1225. double char_right;
  1226. double char_bottom;
  1227. double char_top;
  1228. if (!FPDFText_GetCharBox(text_page, i, &char_left, &char_right,
  1229. &char_bottom, &char_top)) {
  1230. break;
  1231. }
  1232. float xmid = (char_left + char_right) / 2;
  1233. float ymid = (char_top + char_bottom) / 2;
  1234. if (rect.Contains(xmid, ymid)) {
  1235. if (start_char_index == -1)
  1236. start_char_index = i;
  1237. ++cur_char_count;
  1238. } else if (start_char_index != -1) {
  1239. break;
  1240. }
  1241. }
  1242. if (cur_char_count == 0)
  1243. return false;
  1244. *char_len = cur_char_count;
  1245. *start_index = start_char_index;
  1246. return true;
  1247. }
  1248. gfx::Rect PDFiumPage::PageToScreen(const gfx::Point& page_point,
  1249. double zoom,
  1250. double left,
  1251. double top,
  1252. double right,
  1253. double bottom,
  1254. PageOrientation orientation) const {
  1255. if (!available_)
  1256. return gfx::Rect();
  1257. double start_x = (rect_.x() - page_point.x()) * zoom;
  1258. double start_y = (rect_.y() - page_point.y()) * zoom;
  1259. double size_x = rect_.width() * zoom;
  1260. double size_y = rect_.height() * zoom;
  1261. if (!base::IsValueInRangeForNumericType<int>(start_x) ||
  1262. !base::IsValueInRangeForNumericType<int>(start_y) ||
  1263. !base::IsValueInRangeForNumericType<int>(size_x) ||
  1264. !base::IsValueInRangeForNumericType<int>(size_y)) {
  1265. return gfx::Rect();
  1266. }
  1267. int new_left;
  1268. int new_top;
  1269. if (!FPDF_PageToDevice(
  1270. page(), static_cast<int>(start_x), static_cast<int>(start_y),
  1271. static_cast<int>(ceil(size_x)), static_cast<int>(ceil(size_y)),
  1272. ToPDFiumRotation(orientation), left, top, &new_left, &new_top)) {
  1273. return gfx::Rect();
  1274. }
  1275. int new_right;
  1276. int new_bottom;
  1277. if (!FPDF_PageToDevice(
  1278. page(), static_cast<int>(start_x), static_cast<int>(start_y),
  1279. static_cast<int>(ceil(size_x)), static_cast<int>(ceil(size_y)),
  1280. ToPDFiumRotation(orientation), right, bottom, &new_right,
  1281. &new_bottom)) {
  1282. return gfx::Rect();
  1283. }
  1284. // If the PDF is rotated, the horizontal/vertical coordinates could be
  1285. // flipped. See
  1286. // http://www.netl.doe.gov/publications/proceedings/03/ubc/presentations/Goeckner-pres.pdf
  1287. if (new_right < new_left)
  1288. std::swap(new_right, new_left);
  1289. if (new_bottom < new_top)
  1290. std::swap(new_bottom, new_top);
  1291. base::CheckedNumeric<int32_t> new_size_x = new_right;
  1292. new_size_x -= new_left;
  1293. new_size_x += 1;
  1294. base::CheckedNumeric<int32_t> new_size_y = new_bottom;
  1295. new_size_y -= new_top;
  1296. new_size_y += 1;
  1297. if (!new_size_x.IsValid() || !new_size_y.IsValid())
  1298. return gfx::Rect();
  1299. return gfx::Rect(new_left, new_top, new_size_x.ValueOrDie(),
  1300. new_size_y.ValueOrDie());
  1301. }
  1302. void PDFiumPage::RequestThumbnail(float device_pixel_ratio,
  1303. SendThumbnailCallback send_callback) {
  1304. DCHECK(!thumbnail_callback_);
  1305. if (available()) {
  1306. GenerateAndSendThumbnail(device_pixel_ratio, std::move(send_callback));
  1307. return;
  1308. }
  1309. // It is safe to use base::Unretained(this) because the callback is only used
  1310. // by `this`.
  1311. thumbnail_callback_ = base::BindOnce(
  1312. &PDFiumPage::GenerateAndSendThumbnail, base::Unretained(this),
  1313. device_pixel_ratio, std::move(send_callback));
  1314. }
  1315. Thumbnail PDFiumPage::GenerateThumbnail(float device_pixel_ratio) {
  1316. DCHECK(available());
  1317. FPDF_PAGE page = GetPage();
  1318. gfx::Size page_size(base::saturated_cast<int>(FPDF_GetPageWidthF(page)),
  1319. base::saturated_cast<int>(FPDF_GetPageHeightF(page)));
  1320. Thumbnail thumbnail(page_size, device_pixel_ratio);
  1321. const gfx::Size& image_size = thumbnail.image_size();
  1322. ScopedFPDFBitmap fpdf_bitmap(FPDFBitmap_CreateEx(
  1323. image_size.width(), image_size.height(), FPDFBitmap_BGRA,
  1324. thumbnail.GetImageData().data(), thumbnail.stride()));
  1325. // Clear the bitmap.
  1326. FPDFBitmap_FillRect(fpdf_bitmap.get(), /*left=*/0, /*top=*/0,
  1327. image_size.width(), image_size.height(),
  1328. /*color=*/0xFFFFFFFF);
  1329. // The combination of the `FPDF_REVERSE_BYTE_ORDER` rendering flag and the
  1330. // `FPDFBitmap_BGRA` format when initializing `fpdf_bitmap` results in an RGBA
  1331. // rendering, which is the format required by HTML <canvas>.
  1332. constexpr int kRenderingFlags = FPDF_ANNOT | FPDF_REVERSE_BYTE_ORDER;
  1333. FPDF_RenderPageBitmap(fpdf_bitmap.get(), GetPage(), /*start_x=*/0,
  1334. /*start_y=*/0, image_size.width(), image_size.height(),
  1335. ToPDFiumRotation(PageOrientation::kOriginal),
  1336. kRenderingFlags);
  1337. // Draw the forms.
  1338. FPDF_FFLDraw(engine_->form(), fpdf_bitmap.get(), GetPage(), /*start_x=*/0,
  1339. /*start_y=*/0, image_size.width(), image_size.height(),
  1340. ToPDFiumRotation(PageOrientation::kOriginal), kRenderingFlags);
  1341. return thumbnail;
  1342. }
  1343. void PDFiumPage::GenerateAndSendThumbnail(float device_pixel_ratio,
  1344. SendThumbnailCallback send_callback) {
  1345. std::move(send_callback).Run(GenerateThumbnail(device_pixel_ratio));
  1346. }
  1347. void PDFiumPage::MarkAvailable() {
  1348. available_ = true;
  1349. // Fulfill pending thumbnail request.
  1350. if (thumbnail_callback_)
  1351. std::move(thumbnail_callback_).Run();
  1352. }
  1353. PDFiumPage::ScopedUnloadPreventer::ScopedUnloadPreventer(PDFiumPage* page)
  1354. : page_(page) {
  1355. page_->preventing_unload_count_++;
  1356. }
  1357. PDFiumPage::ScopedUnloadPreventer::~ScopedUnloadPreventer() {
  1358. page_->preventing_unload_count_--;
  1359. }
  1360. PDFiumPage::Link::Link() = default;
  1361. PDFiumPage::Link::Link(const Link& that) = default;
  1362. PDFiumPage::Link::~Link() = default;
  1363. PDFiumPage::Image::Image() = default;
  1364. PDFiumPage::Image::Image(const Image& that) = default;
  1365. PDFiumPage::Image::~Image() = default;
  1366. PDFiumPage::Highlight::Highlight() = default;
  1367. PDFiumPage::Highlight::Highlight(const Highlight& that) = default;
  1368. PDFiumPage::Highlight::~Highlight() = default;
  1369. PDFiumPage::FormField::FormField() = default;
  1370. PDFiumPage::FormField::FormField(const FormField& that) = default;
  1371. PDFiumPage::FormField::~FormField() = default;
  1372. PDFiumPage::TextField::TextField() = default;
  1373. PDFiumPage::TextField::TextField(const TextField& that) = default;
  1374. PDFiumPage::TextField::~TextField() = default;
  1375. PDFiumPage::ChoiceFieldOption::ChoiceFieldOption() = default;
  1376. PDFiumPage::ChoiceFieldOption::ChoiceFieldOption(
  1377. const ChoiceFieldOption& that) = default;
  1378. PDFiumPage::ChoiceFieldOption::~ChoiceFieldOption() = default;
  1379. PDFiumPage::ChoiceField::ChoiceField() = default;
  1380. PDFiumPage::ChoiceField::ChoiceField(const ChoiceField& that) = default;
  1381. PDFiumPage::ChoiceField::~ChoiceField() = default;
  1382. PDFiumPage::Button::Button() = default;
  1383. PDFiumPage::Button::Button(const Button& that) = default;
  1384. PDFiumPage::Button::~Button() = default;
  1385. } // namespace chrome_pdf