pdfium_page_unittest.cc 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. // Copyright 2019 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 <utility>
  6. #include <vector>
  7. #include "base/check.h"
  8. #include "base/files/file_path.h"
  9. #include "base/strings/string_util.h"
  10. #include "base/strings/stringprintf.h"
  11. #include "base/test/scoped_feature_list.h"
  12. #include "pdf/accessibility_structs.h"
  13. #include "pdf/pdfium/pdfium_engine.h"
  14. #include "pdf/pdfium/pdfium_test_base.h"
  15. #include "pdf/test/test_client.h"
  16. #include "pdf/test/test_helpers.h"
  17. #include "pdf/ui/thumbnail.h"
  18. #include "testing/gtest/include/gtest/gtest.h"
  19. #include "third_party/abseil-cpp/absl/types/optional.h"
  20. #include "third_party/pdfium/public/fpdf_formfill.h"
  21. #include "third_party/skia/include/core/SkImage.h"
  22. #include "third_party/skia/include/core/SkImageInfo.h"
  23. #include "third_party/skia/include/core/SkPixmap.h"
  24. #include "third_party/skia/include/core/SkRefCnt.h"
  25. #include "ui/accessibility/accessibility_features.h"
  26. #include "ui/gfx/geometry/rect.h"
  27. #include "ui/gfx/geometry/rect_f.h"
  28. #include "ui/gfx/geometry/size_f.h"
  29. #include "ui/gfx/geometry/skia_conversions.h"
  30. #include "ui/gfx/geometry/test/geometry_util.h"
  31. #include "ui/gfx/range/range.h"
  32. namespace chrome_pdf {
  33. namespace {
  34. TEST(PDFiumPageHelperTest, ToPDFiumRotation) {
  35. EXPECT_EQ(ToPDFiumRotation(PageOrientation::kOriginal), 0);
  36. EXPECT_EQ(ToPDFiumRotation(PageOrientation::kClockwise90), 1);
  37. EXPECT_EQ(ToPDFiumRotation(PageOrientation::kClockwise180), 2);
  38. EXPECT_EQ(ToPDFiumRotation(PageOrientation::kClockwise270), 3);
  39. }
  40. void CompareTextRuns(const AccessibilityTextRunInfo& expected_text_run,
  41. const AccessibilityTextRunInfo& actual_text_run) {
  42. EXPECT_EQ(expected_text_run.len, actual_text_run.len);
  43. EXPECT_RECTF_EQ(expected_text_run.bounds, actual_text_run.bounds);
  44. EXPECT_EQ(expected_text_run.direction, actual_text_run.direction);
  45. const AccessibilityTextStyleInfo& expected_style = expected_text_run.style;
  46. const AccessibilityTextStyleInfo& actual_style = actual_text_run.style;
  47. EXPECT_EQ(expected_style.font_name, actual_style.font_name);
  48. EXPECT_EQ(expected_style.font_weight, actual_style.font_weight);
  49. EXPECT_EQ(expected_style.render_mode, actual_style.render_mode);
  50. EXPECT_FLOAT_EQ(expected_style.font_size, actual_style.font_size);
  51. EXPECT_EQ(expected_style.fill_color, actual_style.fill_color);
  52. EXPECT_EQ(expected_style.stroke_color, actual_style.stroke_color);
  53. EXPECT_EQ(expected_style.is_italic, actual_style.is_italic);
  54. EXPECT_EQ(expected_style.is_bold, actual_style.is_bold);
  55. }
  56. template <typename T>
  57. void PopulateTextObjects(const std::vector<gfx::Range>& ranges,
  58. std::vector<T>* text_objects) {
  59. text_objects->resize(ranges.size());
  60. for (size_t i = 0; i < ranges.size(); ++i) {
  61. (*text_objects)[i].start_char_index = ranges[i].start();
  62. (*text_objects)[i].char_count = ranges[i].end() - ranges[i].start();
  63. }
  64. }
  65. // Returns the page size for a `PDFiumPage`. The caller must make sure that
  66. // `pdfium_page` is available.
  67. gfx::SizeF GetPageSizeHelper(PDFiumPage& pdfium_page) {
  68. FPDF_PAGE page = pdfium_page.GetPage();
  69. return gfx::SizeF(FPDF_GetPageWidthF(page), FPDF_GetPageHeightF(page));
  70. }
  71. base::FilePath GetThumbnailTestData(const std::string& expectation_file_prefix,
  72. size_t page_index,
  73. float device_pixel_ratio) {
  74. std::string file_dir = base::StringPrintf("%.1fx", device_pixel_ratio);
  75. std::string file_name = base::StringPrintf(
  76. "%s_expected.pdf.%zu.png", expectation_file_prefix.c_str(), page_index);
  77. return base::FilePath(FILE_PATH_LITERAL("thumbnail"))
  78. .AppendASCII(file_dir)
  79. .AppendASCII(file_name);
  80. }
  81. } // namespace
  82. using PDFiumPageTest = PDFiumTestBase;
  83. TEST_F(PDFiumPageTest, Constructor) {
  84. PDFiumPage page(/*engine=*/nullptr, 2);
  85. EXPECT_EQ(page.index(), 2);
  86. EXPECT_TRUE(page.rect().IsEmpty());
  87. EXPECT_FALSE(page.available());
  88. }
  89. TEST_F(PDFiumPageTest, IsCharInPageBounds) {
  90. TestClient client;
  91. std::unique_ptr<PDFiumEngine> engine =
  92. InitializeEngine(&client, FILE_PATH_LITERAL("hello_world_cropped.pdf"));
  93. ASSERT_TRUE(engine);
  94. PDFiumPage page(engine.get(), 0);
  95. EXPECT_FALSE(page.available());
  96. EXPECT_EQ(page.GetCharCount(), 0);
  97. page.MarkAvailable();
  98. EXPECT_TRUE(page.available());
  99. EXPECT_EQ(page.GetCharCount(), 30);
  100. const gfx::RectF page_bounds = page.GetCroppedRect();
  101. EXPECT_EQ(page_bounds, gfx::RectF(193.33333f, 129.33333f));
  102. EXPECT_EQ(page.GetCharAtIndex(0), 'H');
  103. EXPECT_FALSE(page.IsCharInPageBounds(0, page_bounds));
  104. EXPECT_EQ(page.GetCharAtIndex(12), '!');
  105. EXPECT_TRUE(page.IsCharInPageBounds(12, page_bounds));
  106. EXPECT_EQ(page.GetCharAtIndex(13), '\r');
  107. EXPECT_TRUE(page.IsCharInPageBounds(13, page_bounds));
  108. EXPECT_EQ(page.GetCharAtIndex(14), '\n');
  109. EXPECT_TRUE(page.IsCharInPageBounds(14, page_bounds));
  110. EXPECT_EQ(page.GetCharAtIndex(15), 'G');
  111. EXPECT_FALSE(page.IsCharInPageBounds(15, page_bounds));
  112. EXPECT_EQ(page.GetCharAtIndex(29), '!');
  113. EXPECT_FALSE(page.IsCharInPageBounds(29, page_bounds));
  114. }
  115. class PDFiumPageLinkTest : public PDFiumTestBase {
  116. public:
  117. PDFiumPageLinkTest() = default;
  118. PDFiumPageLinkTest(const PDFiumPageLinkTest&) = delete;
  119. PDFiumPageLinkTest& operator=(const PDFiumPageLinkTest&) = delete;
  120. ~PDFiumPageLinkTest() override = default;
  121. const std::vector<PDFiumPage::Link>& GetLinks(PDFiumEngine& engine,
  122. int page_index) {
  123. PDFiumPage& page = GetPDFiumPageForTest(engine, page_index);
  124. page.CalculateLinks();
  125. return page.links_;
  126. }
  127. };
  128. TEST_F(PDFiumPageLinkTest, LinkGeneration) {
  129. TestClient client;
  130. std::unique_ptr<PDFiumEngine> engine =
  131. InitializeEngine(&client, FILE_PATH_LITERAL("weblinks.pdf"));
  132. ASSERT_TRUE(engine);
  133. ASSERT_EQ(1, engine->GetNumberOfPages());
  134. bool using_test_fonts = UsingTestFonts();
  135. const std::vector<PDFiumPage::Link>& links = GetLinks(*engine, 0);
  136. ASSERT_EQ(3u, links.size());
  137. const PDFiumPage::Link& link = links[0];
  138. EXPECT_EQ("http://yahoo.com", link.target.url);
  139. EXPECT_EQ(7, link.start_char_index);
  140. EXPECT_EQ(16, link.char_count);
  141. ASSERT_EQ(1u, link.bounding_rects.size());
  142. if (using_test_fonts) {
  143. EXPECT_EQ(gfx::Rect(75, 192, 110, 15), link.bounding_rects[0]);
  144. } else {
  145. EXPECT_EQ(gfx::Rect(75, 191, 110, 16), link.bounding_rects[0]);
  146. }
  147. const PDFiumPage::Link& second_link = links[1];
  148. EXPECT_EQ("http://bing.com", second_link.target.url);
  149. EXPECT_EQ(52, second_link.start_char_index);
  150. EXPECT_EQ(15, second_link.char_count);
  151. ASSERT_EQ(1u, second_link.bounding_rects.size());
  152. if (using_test_fonts) {
  153. EXPECT_EQ(gfx::Rect(131, 120, 138, 22), second_link.bounding_rects[0]);
  154. } else {
  155. EXPECT_EQ(gfx::Rect(131, 121, 138, 20), second_link.bounding_rects[0]);
  156. }
  157. const PDFiumPage::Link& third_link = links[2];
  158. EXPECT_EQ("http://google.com", third_link.target.url);
  159. EXPECT_EQ(92, third_link.start_char_index);
  160. EXPECT_EQ(17, third_link.char_count);
  161. ASSERT_EQ(1u, third_link.bounding_rects.size());
  162. EXPECT_EQ(gfx::Rect(82, 67, 161, 21), third_link.bounding_rects[0]);
  163. }
  164. TEST_F(PDFiumPageLinkTest, AnnotLinkGeneration) {
  165. struct ExpectedLink {
  166. int32_t start_char_index;
  167. int32_t char_count;
  168. std::vector<gfx::Rect> bounding_rects;
  169. std::string url;
  170. int page;
  171. float y_in_pixels;
  172. };
  173. static ExpectedLink expected_links[] = {
  174. {144, 38, {{99, 436, 236, 13}}, "https://pdfium.googlesource.com/pdfium"},
  175. {27, 38, {{112, 215, 617, 28}}, "", 1, 89.333336},
  176. {65, 27, {{93, 334, 174, 21}}, "https://www.adobe.com"},
  177. {253,
  178. 18,
  179. {{242, 455, 1, 18}, {242, 472, 1, 15}},
  180. "https://cs.chromium.org"},
  181. {-1, 0, {{58, 926, 28, 27}}, "https://www.google.com"}};
  182. if (UsingTestFonts()) {
  183. expected_links[0].bounding_rects[0] = {99, 436, 236, 14};
  184. }
  185. static constexpr size_t kExpectedLinkCount = std::size(expected_links);
  186. TestClient client;
  187. std::unique_ptr<PDFiumEngine> engine =
  188. InitializeEngine(&client, FILE_PATH_LITERAL("link_annots.pdf"));
  189. ASSERT_TRUE(engine);
  190. ASSERT_EQ(2, engine->GetNumberOfPages());
  191. const std::vector<PDFiumPage::Link>& links = GetLinks(*engine, 0);
  192. ASSERT_EQ(kExpectedLinkCount, links.size());
  193. for (size_t i = 0; i < kExpectedLinkCount; ++i) {
  194. const PDFiumPage::Link& actual_current_link = links[i];
  195. const ExpectedLink& expected_current_link = expected_links[i];
  196. EXPECT_EQ(expected_current_link.start_char_index,
  197. actual_current_link.start_char_index);
  198. EXPECT_EQ(expected_current_link.char_count, actual_current_link.char_count);
  199. size_t bounds_size = actual_current_link.bounding_rects.size();
  200. ASSERT_EQ(expected_current_link.bounding_rects.size(), bounds_size);
  201. for (size_t bounds_index = 0; bounds_index < bounds_size; ++bounds_index) {
  202. EXPECT_EQ(expected_current_link.bounding_rects[bounds_index],
  203. actual_current_link.bounding_rects[bounds_index]);
  204. }
  205. EXPECT_EQ(expected_current_link.url, actual_current_link.target.url);
  206. if (actual_current_link.target.url.empty()) {
  207. EXPECT_EQ(expected_current_link.page, actual_current_link.target.page);
  208. ASSERT_TRUE(actual_current_link.target.y_in_pixels.has_value());
  209. EXPECT_FLOAT_EQ(expected_current_link.y_in_pixels,
  210. actual_current_link.target.y_in_pixels.value());
  211. }
  212. }
  213. }
  214. TEST_F(PDFiumPageLinkTest, GetLinkTarget) {
  215. TestClient client;
  216. std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
  217. &client, FILE_PATH_LITERAL("in_doc_link_with_various_page_sizes.pdf"));
  218. ASSERT_EQ(3, engine->GetNumberOfPages());
  219. const std::vector<PDFiumPage::Link>& links = GetLinks(*engine, 0);
  220. ASSERT_EQ(1u, links.size());
  221. // Get the destination link that exists in the first page.
  222. PDFiumPage& first_page = GetPDFiumPageForTest(*engine, 0);
  223. FPDF_LINK link = FPDFLink_GetLinkAtPoint(first_page.GetPage(), 70, 740);
  224. ASSERT_TRUE(link);
  225. FPDF_DEST dest_link = FPDFLink_GetDest(engine->doc(), link);
  226. ASSERT_TRUE(dest_link);
  227. PDFiumPage::LinkTarget target;
  228. PDFiumPage::Area area = first_page.GetLinkTarget(link, &target);
  229. EXPECT_EQ(PDFiumPage::Area::DOCLINK_AREA, area);
  230. EXPECT_EQ(1, target.page);
  231. // Make sure the target page's size is different from the first page's. This
  232. // guarantees that the in-screen coordinates are calculated based on the
  233. // target page's dimension.
  234. PDFiumPage& target_page = GetPDFiumPageForTest(*engine, target.page);
  235. ASSERT_TRUE(target_page.available());
  236. ASSERT_TRUE(first_page.available());
  237. EXPECT_NE(GetPageSizeHelper(first_page), GetPageSizeHelper(target_page));
  238. EXPECT_FLOAT_EQ(74.666664f, target.x_in_pixels.value());
  239. EXPECT_FLOAT_EQ(120.f, target.y_in_pixels.value());
  240. EXPECT_FALSE(target.zoom);
  241. }
  242. using PDFiumPageImageTest = PDFiumTestBase;
  243. TEST_F(PDFiumPageImageTest, CalculateImages) {
  244. TestClient client;
  245. std::unique_ptr<PDFiumEngine> engine =
  246. InitializeEngine(&client, FILE_PATH_LITERAL("image_alt_text.pdf"));
  247. ASSERT_TRUE(engine);
  248. ASSERT_EQ(1, engine->GetNumberOfPages());
  249. PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
  250. page.CalculateImages();
  251. ASSERT_EQ(3u, page.images_.size());
  252. EXPECT_EQ(gfx::Rect(380, 78, 67, 68), page.images_[0].bounding_rect);
  253. EXPECT_EQ("Image 1", page.images_[0].alt_text);
  254. EXPECT_EQ(gfx::Rect(380, 385, 27, 28), page.images_[1].bounding_rect);
  255. EXPECT_EQ("Image 2", page.images_[1].alt_text);
  256. EXPECT_EQ(gfx::Rect(380, 678, 1, 1), page.images_[2].bounding_rect);
  257. EXPECT_EQ("Image 3", page.images_[2].alt_text);
  258. }
  259. TEST_F(PDFiumPageImageTest, ImageAltText) {
  260. TestClient client;
  261. std::unique_ptr<PDFiumEngine> engine =
  262. InitializeEngine(&client, FILE_PATH_LITERAL("text_with_image.pdf"));
  263. ASSERT_TRUE(engine);
  264. ASSERT_EQ(1, engine->GetNumberOfPages());
  265. PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
  266. page.CalculateImages();
  267. ASSERT_EQ(3u, page.images_.size());
  268. EXPECT_EQ(gfx::Rect(380, 78, 67, 68), page.images_[0].bounding_rect);
  269. EXPECT_EQ("Image 1", page.images_[0].alt_text);
  270. EXPECT_EQ(gfx::Rect(380, 385, 27, 28), page.images_[1].bounding_rect);
  271. EXPECT_EQ("", page.images_[1].alt_text);
  272. EXPECT_EQ(gfx::Rect(380, 678, 1, 1), page.images_[2].bounding_rect);
  273. EXPECT_EQ("", page.images_[2].alt_text);
  274. }
  275. class PDFiumPageImageDataTest : public PDFiumPageImageTest {
  276. public:
  277. PDFiumPageImageDataTest() : enable_pdf_ocr_({features::kPdfOcr}) {}
  278. PDFiumPageImageDataTest(const PDFiumPageImageDataTest&) = delete;
  279. PDFiumPageImageDataTest& operator=(const PDFiumPageImageDataTest&) = delete;
  280. ~PDFiumPageImageDataTest() override = default;
  281. private:
  282. base::test::ScopedFeatureList enable_pdf_ocr_;
  283. };
  284. TEST_F(PDFiumPageImageDataTest, ImageData) {
  285. TestClient client;
  286. std::unique_ptr<PDFiumEngine> engine =
  287. InitializeEngine(&client, FILE_PATH_LITERAL("text_with_image.pdf"));
  288. ASSERT_TRUE(engine);
  289. ASSERT_EQ(1, engine->GetNumberOfPages());
  290. PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
  291. page.CalculateImages();
  292. ASSERT_EQ(3u, page.images_.size());
  293. ASSERT_FALSE(page.images_[0].alt_text.empty());
  294. EXPECT_TRUE(page.images_[0].image_data.drawsNothing());
  295. EXPECT_EQ(page.images_[0].image_data.width(), 0);
  296. EXPECT_EQ(page.images_[0].image_data.height(), 0);
  297. ASSERT_TRUE(page.images_[2].alt_text.empty());
  298. EXPECT_EQ(page.images_[1].image_data.width(), 20);
  299. EXPECT_EQ(page.images_[1].image_data.height(), 20);
  300. }
  301. using PDFiumPageTextTest = PDFiumTestBase;
  302. TEST_F(PDFiumPageTextTest, TextRunBounds) {
  303. TestClient client;
  304. std::unique_ptr<PDFiumEngine> engine = InitializeEngine(
  305. &client, FILE_PATH_LITERAL("leading_trailing_spaces_per_text_run.pdf"));
  306. ASSERT_TRUE(engine);
  307. constexpr int kFirstRunStartIndex = 0;
  308. constexpr int kFirstRunEndIndex = 20;
  309. constexpr int kPageIndex = 0;
  310. absl::optional<AccessibilityTextRunInfo> text_run_info_1 =
  311. engine->GetTextRunInfo(kPageIndex, kFirstRunStartIndex);
  312. ASSERT_TRUE(text_run_info_1.has_value());
  313. const auto& actual_text_run_1 = text_run_info_1.value();
  314. EXPECT_EQ(21u, actual_text_run_1.len);
  315. EXPECT_TRUE(base::IsUnicodeWhitespace(
  316. engine->GetCharUnicode(kPageIndex, kFirstRunStartIndex)));
  317. gfx::RectF text_run_bounds = actual_text_run_1.bounds;
  318. EXPECT_TRUE(text_run_bounds.Contains(
  319. engine->GetCharBounds(kPageIndex, kFirstRunStartIndex)));
  320. // Last non-space character should fall in the bounding box of the text run.
  321. // Text run looks like this:
  322. // " Hello, world! \r\n "<17 characters><first Tj>
  323. // " \r\n "<4 characters><second Tj>
  324. // " "<1 character><third Tj starting spaces>
  325. // Finally generated text run: " Hello, world! \r\n \r\n "
  326. constexpr int kFirstRunLastNonSpaceCharIndex = 13;
  327. EXPECT_FALSE(base::IsUnicodeWhitespace(
  328. engine->GetCharUnicode(kPageIndex, kFirstRunLastNonSpaceCharIndex)));
  329. EXPECT_TRUE(text_run_bounds.Contains(
  330. engine->GetCharBounds(kPageIndex, kFirstRunLastNonSpaceCharIndex)));
  331. EXPECT_TRUE(base::IsUnicodeWhitespace(
  332. engine->GetCharUnicode(kPageIndex, kFirstRunEndIndex)));
  333. gfx::RectF end_char_rect =
  334. engine->GetCharBounds(kPageIndex, kFirstRunEndIndex);
  335. EXPECT_FALSE(text_run_bounds.Contains(end_char_rect));
  336. // Equals to the length of the previous text run.
  337. constexpr int kSecondRunStartIndex = 21;
  338. constexpr int kSecondRunEndIndex = 36;
  339. // Test the properties of second text run.
  340. // Note: The leading spaces in second text run are accounted for in the end
  341. // of first text run. Hence we won't see a space leading the second text run.
  342. absl::optional<AccessibilityTextRunInfo> text_run_info_2 =
  343. engine->GetTextRunInfo(kPageIndex, kSecondRunStartIndex);
  344. ASSERT_TRUE(text_run_info_2.has_value());
  345. const auto& actual_text_run_2 = text_run_info_2.value();
  346. EXPECT_EQ(16u, actual_text_run_2.len);
  347. EXPECT_FALSE(base::IsUnicodeWhitespace(
  348. engine->GetCharUnicode(kPageIndex, kSecondRunStartIndex)));
  349. text_run_bounds = actual_text_run_2.bounds;
  350. EXPECT_TRUE(text_run_bounds.Contains(
  351. engine->GetCharBounds(kPageIndex, kSecondRunStartIndex)));
  352. // Last non-space character should fall in the bounding box of the text run.
  353. // Text run looks like this:
  354. // "Goodbye, world! "<19 characters><first Tj>
  355. // Finally generated text run: "Goodbye, world! "
  356. constexpr int kSecondRunLastNonSpaceCharIndex = 35;
  357. EXPECT_FALSE(base::IsUnicodeWhitespace(
  358. engine->GetCharUnicode(kPageIndex, kSecondRunLastNonSpaceCharIndex)));
  359. EXPECT_TRUE(text_run_bounds.Contains(
  360. engine->GetCharBounds(kPageIndex, kSecondRunLastNonSpaceCharIndex)));
  361. EXPECT_TRUE(base::IsUnicodeWhitespace(
  362. engine->GetCharUnicode(kPageIndex, kSecondRunEndIndex)));
  363. EXPECT_FALSE(text_run_bounds.Contains(
  364. engine->GetCharBounds(kPageIndex, kSecondRunEndIndex)));
  365. }
  366. TEST_F(PDFiumPageTextTest, GetTextRunInfo) {
  367. TestClient client;
  368. std::unique_ptr<PDFiumEngine> engine =
  369. InitializeEngine(&client, FILE_PATH_LITERAL("weblinks.pdf"));
  370. ASSERT_TRUE(engine);
  371. int current_char_index = 0;
  372. AccessibilityTextStyleInfo expected_style_1 = {
  373. "Times-Roman",
  374. 0,
  375. AccessibilityTextRenderMode::kFill,
  376. 12,
  377. 0xff000000,
  378. 0xff000000,
  379. false,
  380. false};
  381. AccessibilityTextStyleInfo expected_style_2 = {
  382. "Helvetica", 0, AccessibilityTextRenderMode::kFill,
  383. 16, 0xff000000, 0xff000000,
  384. false, false};
  385. // The links span from [7, 22], [52, 66] and [92, 108] with 16, 15 and 17
  386. // text run lengths respectively. There are text runs preceding and
  387. // succeeding them.
  388. AccessibilityTextRunInfo expected_text_runs[] = {
  389. {7, gfx::RectF(26.666666f, 189.333333f, 38.666672f, 13.333344f),
  390. AccessibilityTextDirection::kLeftToRight, expected_style_1},
  391. {16, gfx::RectF(70.666664f, 189.333333f, 108.0f, 14.666672f),
  392. AccessibilityTextDirection::kLeftToRight, expected_style_1},
  393. {20, gfx::RectF(181.333333f, 189.333333f, 117.333333f, 14.666672f),
  394. AccessibilityTextDirection::kLeftToRight, expected_style_1},
  395. {9, gfx::RectF(28.0f, 117.33334f, 89.333328f, 20.0f),
  396. AccessibilityTextDirection::kLeftToRight, expected_style_2},
  397. {15, gfx::RectF(126.66666f, 117.33334f, 137.33334f, 20.0f),
  398. AccessibilityTextDirection::kLeftToRight, expected_style_2},
  399. {20, gfx::RectF(266.66666f, 118.66666f, 169.33334f, 18.666664f),
  400. AccessibilityTextDirection::kLeftToRight, expected_style_2},
  401. {5, gfx::RectF(28.0f, 65.333336f, 40.0f, 18.666664f),
  402. AccessibilityTextDirection::kLeftToRight, expected_style_2},
  403. {17, gfx::RectF(77.333336f, 64.0f, 160.0f, 20.0f),
  404. AccessibilityTextDirection::kLeftToRight, expected_style_2}};
  405. if (UsingTestFonts()) {
  406. expected_text_runs[4].bounds =
  407. gfx::RectF(126.66666f, 117.33334f, 137.33334f, 21.33334f);
  408. expected_text_runs[5].bounds =
  409. gfx::RectF(266.66666f, 118.66666f, 170.66666f, 20.0f);
  410. expected_text_runs[7].bounds =
  411. gfx::RectF(77.333336f, 64.0f, 160.0f, 21.33333f);
  412. }
  413. // Test negative char index returns nullopt
  414. absl::optional<AccessibilityTextRunInfo> text_run_info_result =
  415. engine->GetTextRunInfo(0, -1);
  416. ASSERT_FALSE(text_run_info_result.has_value());
  417. // Test valid char index returns expected text run info and expected text
  418. // style info
  419. for (const auto& expected_text_run : expected_text_runs) {
  420. text_run_info_result = engine->GetTextRunInfo(0, current_char_index);
  421. ASSERT_TRUE(text_run_info_result.has_value());
  422. const auto& actual_text_run = text_run_info_result.value();
  423. CompareTextRuns(expected_text_run, actual_text_run);
  424. current_char_index += actual_text_run.len;
  425. }
  426. // Test char index outside char range returns nullopt
  427. PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
  428. EXPECT_EQ(page.GetCharCount(), current_char_index);
  429. text_run_info_result = engine->GetTextRunInfo(0, current_char_index);
  430. ASSERT_FALSE(text_run_info_result.has_value());
  431. }
  432. TEST_F(PDFiumPageTextTest, HighlightTextRunInfo) {
  433. TestClient client;
  434. std::unique_ptr<PDFiumEngine> engine =
  435. InitializeEngine(&client, FILE_PATH_LITERAL("highlights.pdf"));
  436. ASSERT_TRUE(engine);
  437. ASSERT_EQ(1, engine->GetNumberOfPages());
  438. // Highlights span across text run indices 0, 2 and 3.
  439. static const AccessibilityTextStyleInfo kExpectedStyle = {
  440. "Helvetica", 0, AccessibilityTextRenderMode::kFill,
  441. 16, 0xff000000, 0xff000000,
  442. false, false};
  443. AccessibilityTextRunInfo expected_text_runs[] = {
  444. {5, gfx::RectF(1.3333334f, 198.66667f, 46.666668f, 14.666672f),
  445. AccessibilityTextDirection::kLeftToRight, kExpectedStyle},
  446. {7, gfx::RectF(50.666668f, 198.66667f, 47.999996f, 17.333328f),
  447. AccessibilityTextDirection::kLeftToRight, kExpectedStyle},
  448. {7, gfx::RectF(106.66666f, 198.66667f, 73.333336f, 18.666672f),
  449. AccessibilityTextDirection::kLeftToRight, kExpectedStyle},
  450. {2, gfx::RectF(181.33333f, 202.66667f, 16.0f, 14.66667f),
  451. AccessibilityTextDirection::kNone, kExpectedStyle},
  452. {2, gfx::RectF(198.66667f, 202.66667f, 21.333328f, 10.666672f),
  453. AccessibilityTextDirection::kLeftToRight, kExpectedStyle}};
  454. if (UsingTestFonts()) {
  455. expected_text_runs[2].bounds =
  456. gfx::RectF(106.66666f, 198.66667f, 73.333336f, 19.999985f);
  457. expected_text_runs[4].bounds =
  458. gfx::RectF(198.66667f, 201.33333f, 21.333328f, 12.000015f);
  459. }
  460. int current_char_index = 0;
  461. for (const auto& expected_text_run : expected_text_runs) {
  462. absl::optional<AccessibilityTextRunInfo> text_run_info_result =
  463. engine->GetTextRunInfo(0, current_char_index);
  464. ASSERT_TRUE(text_run_info_result.has_value());
  465. const auto& actual_text_run = text_run_info_result.value();
  466. CompareTextRuns(expected_text_run, actual_text_run);
  467. current_char_index += actual_text_run.len;
  468. }
  469. }
  470. using PDFiumPageHighlightTest = PDFiumTestBase;
  471. TEST_F(PDFiumPageHighlightTest, PopulateHighlights) {
  472. struct ExpectedHighlight {
  473. int32_t start_char_index;
  474. int32_t char_count;
  475. gfx::Rect bounding_rect;
  476. uint32_t color;
  477. };
  478. constexpr uint32_t kHighlightDefaultColor = MakeARGB(255, 255, 255, 0);
  479. constexpr uint32_t kHighlightRedColor = MakeARGB(102, 230, 0, 0);
  480. constexpr uint32_t kHighlightNoColor = MakeARGB(0, 0, 0, 0);
  481. static const ExpectedHighlight kExpectedHighlights[] = {
  482. {0, 5, {5, 196, 49, 26}, kHighlightDefaultColor},
  483. {12, 7, {110, 196, 77, 26}, kHighlightRedColor},
  484. {20, 1, {192, 196, 13, 26}, kHighlightNoColor}};
  485. TestClient client;
  486. std::unique_ptr<PDFiumEngine> engine =
  487. InitializeEngine(&client, FILE_PATH_LITERAL("highlights.pdf"));
  488. ASSERT_TRUE(engine);
  489. ASSERT_EQ(1, engine->GetNumberOfPages());
  490. PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
  491. page.PopulateAnnotations();
  492. ASSERT_EQ(std::size(kExpectedHighlights), page.highlights_.size());
  493. for (size_t i = 0; i < page.highlights_.size(); ++i) {
  494. ASSERT_EQ(kExpectedHighlights[i].start_char_index,
  495. page.highlights_[i].start_char_index);
  496. ASSERT_EQ(kExpectedHighlights[i].char_count,
  497. page.highlights_[i].char_count);
  498. EXPECT_EQ(kExpectedHighlights[i].bounding_rect,
  499. page.highlights_[i].bounding_rect);
  500. ASSERT_EQ(kExpectedHighlights[i].color, page.highlights_[i].color);
  501. }
  502. }
  503. using PDFiumPageTextFieldTest = PDFiumTestBase;
  504. TEST_F(PDFiumPageTextFieldTest, PopulateTextFields) {
  505. struct ExpectedTextField {
  506. const char* name;
  507. const char* value;
  508. gfx::Rect bounding_rect;
  509. int flags;
  510. };
  511. static const ExpectedTextField kExpectedTextFields[] = {
  512. {"Text Box", "Text", {138, 230, 135, 41}, 0},
  513. {"ReadOnly", "Elephant", {138, 163, 135, 41}, 1},
  514. {"Required", "Required Field", {138, 303, 135, 34}, 2},
  515. {"Password", "", {138, 356, 135, 35}, 8192}};
  516. TestClient client;
  517. std::unique_ptr<PDFiumEngine> engine =
  518. InitializeEngine(&client, FILE_PATH_LITERAL("form_text_fields.pdf"));
  519. ASSERT_TRUE(engine);
  520. ASSERT_EQ(1, engine->GetNumberOfPages());
  521. PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
  522. page.PopulateAnnotations();
  523. size_t text_fields_count = page.text_fields_.size();
  524. ASSERT_EQ(std::size(kExpectedTextFields), text_fields_count);
  525. for (size_t i = 0; i < text_fields_count; ++i) {
  526. EXPECT_EQ(kExpectedTextFields[i].name, page.text_fields_[i].name);
  527. EXPECT_EQ(kExpectedTextFields[i].value, page.text_fields_[i].value);
  528. EXPECT_EQ(kExpectedTextFields[i].bounding_rect,
  529. page.text_fields_[i].bounding_rect);
  530. EXPECT_EQ(kExpectedTextFields[i].flags, page.text_fields_[i].flags);
  531. }
  532. }
  533. using PDFiumPageChoiceFieldTest = PDFiumTestBase;
  534. TEST_F(PDFiumPageChoiceFieldTest, PopulateChoiceFields) {
  535. struct ExpectedChoiceFieldOption {
  536. const char* name;
  537. bool is_selected;
  538. };
  539. struct ExpectedChoiceField {
  540. const char* name;
  541. std::vector<struct ExpectedChoiceFieldOption> options;
  542. gfx::Rect bounding_rect;
  543. int flags;
  544. };
  545. static const ExpectedChoiceField kExpectedChoiceFields[] = {
  546. {"Listbox_SingleSelect",
  547. {{"Foo", false}, {"Bar", false}, {"Qux", false}},
  548. {138, 296, 135, 41},
  549. 0},
  550. {"Combo1",
  551. {{"Apple", false}, {"Banana", true}, {"Cherry", false}},
  552. {138, 230, 135, 41},
  553. 131072},
  554. {"Listbox_ReadOnly",
  555. {{"Dog", false}, {"Elephant", false}, {"Frog", false}},
  556. {138, 96, 135, 41},
  557. 1},
  558. {"Listbox_MultiSelectMultipleIndices",
  559. {
  560. {"Albania", false},
  561. {"Belgium", true},
  562. {"Croatia", false},
  563. {"Denmark", true},
  564. {"Estonia", false},
  565. },
  566. {138, 430, 135, 41},
  567. 2097152},
  568. {"Listbox_MultiSelectMultipleValues",
  569. {
  570. {"Alpha", false},
  571. {"Beta", false},
  572. {"Gamma", true},
  573. {"Delta", false},
  574. {"Epsilon", true},
  575. },
  576. {138, 496, 135, 41},
  577. 2097152},
  578. {"Listbox_MultiSelectMultipleMismatch",
  579. {
  580. {"Alligator", true},
  581. {"Bear", false},
  582. {"Cougar", true},
  583. {"Deer", false},
  584. {"Echidna", false},
  585. },
  586. {138, 563, 135, 41},
  587. 2097152}};
  588. TestClient client;
  589. std::unique_ptr<PDFiumEngine> engine =
  590. InitializeEngine(&client, FILE_PATH_LITERAL("form_choice_fields.pdf"));
  591. ASSERT_TRUE(engine);
  592. ASSERT_EQ(1, engine->GetNumberOfPages());
  593. PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
  594. page.PopulateAnnotations();
  595. size_t choice_fields_count = page.choice_fields_.size();
  596. ASSERT_EQ(std::size(kExpectedChoiceFields), choice_fields_count);
  597. for (size_t i = 0; i < choice_fields_count; ++i) {
  598. EXPECT_EQ(kExpectedChoiceFields[i].name, page.choice_fields_[i].name);
  599. size_t choice_field_options_count = page.choice_fields_[i].options.size();
  600. ASSERT_EQ(std::size(kExpectedChoiceFields[i].options),
  601. choice_field_options_count);
  602. for (size_t j = 0; j < choice_field_options_count; ++j) {
  603. EXPECT_EQ(kExpectedChoiceFields[i].options[j].name,
  604. page.choice_fields_[i].options[j].name);
  605. EXPECT_EQ(kExpectedChoiceFields[i].options[j].is_selected,
  606. page.choice_fields_[i].options[j].is_selected);
  607. }
  608. EXPECT_EQ(kExpectedChoiceFields[i].bounding_rect,
  609. page.choice_fields_[i].bounding_rect);
  610. EXPECT_EQ(kExpectedChoiceFields[i].flags, page.choice_fields_[i].flags);
  611. }
  612. }
  613. using PDFiumPageButtonTest = PDFiumTestBase;
  614. TEST_F(PDFiumPageButtonTest, PopulateButtons) {
  615. struct ExpectedButton {
  616. const char* name;
  617. const char* value;
  618. int type;
  619. int flags;
  620. bool is_checked;
  621. uint32_t control_count;
  622. int control_index;
  623. gfx::Rect bounding_rect;
  624. };
  625. static const ExpectedButton kExpectedButtons[] = {{"readOnlyCheckbox",
  626. "Yes",
  627. FPDF_FORMFIELD_CHECKBOX,
  628. 1,
  629. true,
  630. 1,
  631. 0,
  632. {185, 43, 28, 28}},
  633. {"checkbox",
  634. "Yes",
  635. FPDF_FORMFIELD_CHECKBOX,
  636. 2,
  637. false,
  638. 1,
  639. 0,
  640. {185, 96, 28, 28}},
  641. {"RadioButton",
  642. "value1",
  643. FPDF_FORMFIELD_RADIOBUTTON,
  644. 49154,
  645. false,
  646. 2,
  647. 0,
  648. {185, 243, 28, 28}},
  649. {"RadioButton",
  650. "value2",
  651. FPDF_FORMFIELD_RADIOBUTTON,
  652. 49154,
  653. true,
  654. 2,
  655. 1,
  656. {252, 243, 27, 28}},
  657. {"PushButton",
  658. "",
  659. FPDF_FORMFIELD_PUSHBUTTON,
  660. 65536,
  661. false,
  662. 0,
  663. -1,
  664. {118, 270, 55, 67}}};
  665. TestClient client;
  666. std::unique_ptr<PDFiumEngine> engine =
  667. InitializeEngine(&client, FILE_PATH_LITERAL("form_buttons.pdf"));
  668. ASSERT_TRUE(engine);
  669. ASSERT_EQ(1, engine->GetNumberOfPages());
  670. PDFiumPage& page = GetPDFiumPageForTest(*engine, 0);
  671. page.PopulateAnnotations();
  672. size_t buttons_count = page.buttons_.size();
  673. ASSERT_EQ(std::size(kExpectedButtons), buttons_count);
  674. for (size_t i = 0; i < buttons_count; ++i) {
  675. EXPECT_EQ(kExpectedButtons[i].name, page.buttons_[i].name);
  676. EXPECT_EQ(kExpectedButtons[i].value, page.buttons_[i].value);
  677. EXPECT_EQ(kExpectedButtons[i].type, page.buttons_[i].type);
  678. EXPECT_EQ(kExpectedButtons[i].flags, page.buttons_[i].flags);
  679. EXPECT_EQ(kExpectedButtons[i].is_checked, page.buttons_[i].is_checked);
  680. EXPECT_EQ(kExpectedButtons[i].control_count,
  681. page.buttons_[i].control_count);
  682. EXPECT_EQ(kExpectedButtons[i].control_index,
  683. page.buttons_[i].control_index);
  684. EXPECT_EQ(kExpectedButtons[i].bounding_rect,
  685. page.buttons_[i].bounding_rect);
  686. }
  687. }
  688. class PDFiumPageThumbnailTest : public PDFiumTestBase {
  689. public:
  690. PDFiumPageThumbnailTest() = default;
  691. PDFiumPageThumbnailTest(const PDFiumPageThumbnailTest&) = delete;
  692. PDFiumPageThumbnailTest& operator=(const PDFiumPageThumbnailTest&) = delete;
  693. ~PDFiumPageThumbnailTest() override = default;
  694. void TestGenerateThumbnail(PDFiumEngine& engine,
  695. size_t page_index,
  696. float device_pixel_ratio,
  697. const gfx::Size& expected_thumbnail_size,
  698. const std::string& expectation_file_prefix) {
  699. PDFiumPage& page = GetPDFiumPageForTest(engine, page_index);
  700. Thumbnail thumbnail = page.GenerateThumbnail(device_pixel_ratio);
  701. EXPECT_EQ(expected_thumbnail_size, thumbnail.image_size());
  702. EXPECT_EQ(device_pixel_ratio, thumbnail.device_pixel_ratio());
  703. auto image_info =
  704. SkImageInfo::Make(gfx::SizeToSkISize(thumbnail.image_size()),
  705. kRGBA_8888_SkColorType, kPremul_SkAlphaType);
  706. int stride = thumbnail.stride();
  707. ASSERT_GT(stride, 0);
  708. ASSERT_EQ(image_info.minRowBytes(), static_cast<size_t>(stride));
  709. std::vector<uint8_t> data = thumbnail.TakeData();
  710. sk_sp<SkImage> image = SkImage::MakeRasterCopy(
  711. SkPixmap(image_info, data.data(), image_info.minRowBytes()));
  712. ASSERT_TRUE(image);
  713. base::FilePath expectation_png_file_path = GetThumbnailTestData(
  714. expectation_file_prefix, page_index, device_pixel_ratio);
  715. EXPECT_TRUE(MatchesPngFile(image.get(), expectation_png_file_path));
  716. }
  717. };
  718. TEST_F(PDFiumPageThumbnailTest, GenerateThumbnail) {
  719. TestClient client;
  720. std::unique_ptr<PDFiumEngine> engine =
  721. InitializeEngine(&client, FILE_PATH_LITERAL("variable_page_sizes.pdf"));
  722. ASSERT_EQ(7, engine->GetNumberOfPages());
  723. static constexpr struct {
  724. size_t page_index;
  725. float device_pixel_ratio;
  726. gfx::Size expected_thumbnail_size;
  727. } kGenerateThumbnailTestParams[] = {
  728. {0, 1, {108, 140}}, // ANSI Letter
  729. {1, 1, {108, 152}}, // ISO 216 A4
  730. {2, 1, {140, 140}}, // Square
  731. {3, 1, {540, 108}}, // Wide
  732. {4, 1, {108, 540}}, // Tall
  733. {5, 1, {1399, 46}}, // Super wide
  734. {6, 1, {46, 1399}}, // Super tall
  735. {0, 2, {216, 280}}, // ANSI Letter
  736. {1, 2, {214, 303}}, // ISO 216 A4
  737. {2, 2, {255, 255}}, // Square
  738. {3, 2, {571, 114}}, // Wide
  739. {4, 2, {114, 571}}, // Tall
  740. {5, 2, {1399, 46}}, // Super wide
  741. {6, 2, {46, 1399}}, // Super tall
  742. };
  743. for (const auto& params : kGenerateThumbnailTestParams) {
  744. TestGenerateThumbnail(*engine, params.page_index, params.device_pixel_ratio,
  745. params.expected_thumbnail_size,
  746. "variable_page_sizes");
  747. }
  748. }
  749. // For crbug.com/1248455
  750. TEST_F(PDFiumPageThumbnailTest, GenerateThumbnailForAnnotation) {
  751. TestClient client;
  752. std::unique_ptr<PDFiumEngine> engine =
  753. InitializeEngine(&client, FILE_PATH_LITERAL("signature_widget.pdf"));
  754. TestGenerateThumbnail(*engine, /*page_index=*/0, /*device_pixel_ratio=*/1,
  755. /*expected_thumbnail_size=*/{140, 140},
  756. "signature_widget");
  757. TestGenerateThumbnail(*engine, /*page_index=*/0, /*device_pixel_ratio=*/2,
  758. /*expected_thumbnail_size=*/{255, 255},
  759. "signature_widget");
  760. }
  761. } // namespace chrome_pdf