| // Copyright 2011 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "printing/pdf_metafile_cg_mac.h" |
| |
| #include <CoreGraphics/CoreGraphics.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| #include "base/files/file_util.h" |
| #include "base/hash/sha1.h" |
| #include "base/path_service.h" |
| #include "printing/mojom/print.mojom.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gfx/codec/png_codec.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| namespace printing { |
| |
| namespace { |
| |
| base::FilePath GetPdfTestData(const base::FilePath::StringType& filename) { |
| base::FilePath root_path; |
| if (!base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &root_path)) { |
| return base::FilePath(); |
| } |
| return root_path.Append("pdf").Append("test").Append("data").Append(filename); |
| } |
| |
| base::FilePath GetPrintingTestData(const base::FilePath::StringType& filename) { |
| base::FilePath root_path; |
| if (!base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &root_path)) { |
| return base::FilePath(); |
| } |
| return root_path.Append("printing") |
| .Append("test") |
| .Append("data") |
| .Append("pdf_cg") |
| .Append(filename); |
| } |
| |
| std::unique_ptr<PdfMetafileCg> GetPdfMetafile( |
| const base::FilePath::StringType& pdf_filename) { |
| // Get test data. |
| base::FilePath pdf_file = GetPdfTestData(pdf_filename); |
| if (pdf_file.empty()) |
| return nullptr; |
| |
| std::string pdf_data; |
| if (!base::ReadFileToString(pdf_file, &pdf_data)) |
| return nullptr; |
| |
| // Initialize and check metafile. |
| auto pdf_cg = std::make_unique<PdfMetafileCg>(); |
| if (!pdf_cg->InitFromData(base::as_bytes(base::make_span(pdf_data)))) |
| return nullptr; |
| return pdf_cg; |
| } |
| |
| void RenderedPdfSha1(const base::FilePath::StringType& pdf_filename, |
| size_t page_number, |
| const gfx::Rect& expected_page_bounds, |
| const gfx::Size& dest_size, |
| bool autorotate, |
| bool fit_to_page, |
| base::SHA1Digest* rendered_hash) { |
| // Initialize and verify the metafile. |
| std::unique_ptr<PdfMetafileCg> pdf_cg = GetPdfMetafile(pdf_filename); |
| ASSERT_TRUE(pdf_cg); |
| ASSERT_LE(page_number, pdf_cg->GetPageCount()); |
| const gfx::Rect bounds = pdf_cg->GetPageBounds(page_number); |
| ASSERT_EQ(expected_page_bounds, bounds); |
| |
| // Set up rendering context. |
| constexpr size_t kBitsPerComponent = 8; |
| constexpr size_t kBytesPerPixel = 4; |
| const size_t kStride = dest_size.width() * kBytesPerPixel; |
| std::vector<uint8_t> rendered_bitmap(dest_size.height() * kStride); |
| base::apple::ScopedCFTypeRef<CGColorSpaceRef> color_space( |
| CGColorSpaceCreateDeviceRGB()); |
| base::apple::ScopedCFTypeRef<CGContextRef> context(CGBitmapContextCreate( |
| rendered_bitmap.data(), dest_size.width(), dest_size.height(), |
| kBitsPerComponent, kStride, color_space.get(), |
| uint32_t{kCGImageAlphaPremultipliedFirst} | kCGBitmapByteOrder32Little)); |
| |
| // Render using metafile and calculate the output hash. |
| ASSERT_TRUE(pdf_cg->RenderPage(page_number, context.get(), |
| gfx::Rect(dest_size).ToCGRect(), autorotate, |
| fit_to_page)); |
| *rendered_hash = base::SHA1HashSpan(rendered_bitmap); |
| } |
| |
| void ExpectedPngSha1(const base::FilePath::StringType& expected_png_filename, |
| const gfx::Size& expected_png_size, |
| base::SHA1Digest* expected_hash) { |
| base::FilePath expected_png_file = GetPrintingTestData(expected_png_filename); |
| ASSERT_FALSE(expected_png_file.empty()); |
| std::string expected_png_data; |
| ASSERT_TRUE(base::ReadFileToString(expected_png_file, &expected_png_data)); |
| |
| // Decode expected PNG and calculate the output hash. |
| std::vector<uint8_t> expected_png_bitmap; |
| int png_width; |
| int png_height; |
| ASSERT_TRUE(gfx::PNGCodec::Decode( |
| reinterpret_cast<const uint8_t*>(expected_png_data.data()), |
| expected_png_data.size(), gfx::PNGCodec::FORMAT_BGRA, |
| &expected_png_bitmap, &png_width, &png_height)); |
| ASSERT_EQ(expected_png_size.width(), png_width); |
| ASSERT_EQ(expected_png_size.height(), png_height); |
| *expected_hash = base::SHA1HashSpan(expected_png_bitmap); |
| } |
| |
| void TestRenderPageWithTransformParams( |
| const base::FilePath::StringType& pdf_filename, |
| size_t page_number, |
| const gfx::Rect& expected_page_bounds, |
| const base::FilePath::StringType& expected_png_filename, |
| const gfx::Size& dest_size, |
| bool autorotate, |
| bool fit_to_page) { |
| base::SHA1Digest rendered_hash; |
| RenderedPdfSha1(pdf_filename, page_number, expected_page_bounds, dest_size, |
| autorotate, fit_to_page, &rendered_hash); |
| base::SHA1Digest expected_hash; |
| ExpectedPngSha1(expected_png_filename, dest_size, &expected_hash); |
| |
| // Make sure the hashes match. |
| EXPECT_EQ(expected_hash, rendered_hash); |
| } |
| |
| void TestRenderPage(const base::FilePath::StringType& pdf_filename, |
| size_t page_number, |
| const gfx::Rect& expected_page_bounds, |
| const base::FilePath::StringType& expected_png_filename, |
| const gfx::Size& dest_size) { |
| TestRenderPageWithTransformParams( |
| pdf_filename, page_number, expected_page_bounds, expected_png_filename, |
| dest_size, /*autorotate=*/true, /*fit_to_page=*/false); |
| } |
| |
| } // namespace |
| |
| TEST(PdfMetafileCgTest, Pdf) { |
| // Test in-renderer constructor. |
| PdfMetafileCg pdf; |
| EXPECT_TRUE(pdf.Init()); |
| EXPECT_TRUE(pdf.context()); |
| |
| // Render page 1. |
| constexpr gfx::Rect kRect1(10, 10, 520, 700); |
| constexpr gfx::Size kSize1(540, 720); |
| pdf.StartPage(kSize1, kRect1, 1.25, mojom::PageOrientation::kUpright); |
| pdf.FinishPage(); |
| |
| // Render page 2. |
| constexpr gfx::Rect kRect2(10, 10, 520, 700); |
| constexpr gfx::Size kSize2(720, 540); |
| pdf.StartPage(kSize2, kRect2, 2.0, mojom::PageOrientation::kUpright); |
| pdf.FinishPage(); |
| |
| pdf.FinishDocument(); |
| |
| // Check data size. |
| const uint32_t size = pdf.GetDataSize(); |
| EXPECT_GT(size, 0U); |
| |
| // Get resulting data. |
| std::vector<char> buffer(size, 0); |
| pdf.GetData(&buffer.front(), size); |
| |
| // Test browser-side constructor. |
| PdfMetafileCg pdf2; |
| // TODO(thestig): Make `buffer` uint8_t and avoid the base::as_bytes() call. |
| EXPECT_TRUE(pdf2.InitFromData(base::as_bytes(base::make_span(buffer)))); |
| |
| // Get the first 4 characters from pdf2. |
| std::vector<char> buffer2(4, 0); |
| pdf2.GetData(&buffer2.front(), 4); |
| |
| // Test that the header begins with "%PDF". |
| std::string header(&buffer2.front(), 4); |
| EXPECT_EQ(0U, header.find("%PDF", 0)); |
| |
| // Test that the PDF is correctly reconstructed. |
| EXPECT_EQ(2U, pdf2.GetPageCount()); |
| gfx::Size page_size = pdf2.GetPageBounds(1).size(); |
| EXPECT_EQ(540, page_size.width()); |
| EXPECT_EQ(720, page_size.height()); |
| page_size = pdf2.GetPageBounds(2).size(); |
| EXPECT_EQ(720, page_size.width()); |
| EXPECT_EQ(540, page_size.height()); |
| } |
| |
| TEST(PdfMetafileCgTest, GetPageBounds) { |
| // Get test data. |
| base::FilePath pdf_file = GetPdfTestData("rectangles_multi_pages.pdf"); |
| ASSERT_FALSE(pdf_file.empty()); |
| std::string pdf_data; |
| ASSERT_TRUE(base::ReadFileToString(pdf_file, &pdf_data)); |
| |
| // Initialize and check metafile. |
| PdfMetafileCg pdf_cg; |
| ASSERT_TRUE(pdf_cg.InitFromData(base::as_bytes(base::make_span(pdf_data)))); |
| ASSERT_EQ(5u, pdf_cg.GetPageCount()); |
| |
| // Since the input into GetPageBounds() is a 1-indexed page number, 0 and 6 |
| // are out of bounds. |
| gfx::Rect bounds; |
| for (size_t i : {0, 6}) { |
| bounds = pdf_cg.GetPageBounds(i); |
| EXPECT_EQ(0, bounds.x()); |
| EXPECT_EQ(0, bounds.y()); |
| EXPECT_EQ(0, bounds.width()); |
| EXPECT_EQ(0, bounds.height()); |
| } |
| |
| // Whereas 1-5 are in bounds. |
| for (size_t i = 1; i < 6; ++i) { |
| bounds = pdf_cg.GetPageBounds(i); |
| EXPECT_EQ(0, bounds.x()); |
| EXPECT_EQ(0, bounds.y()); |
| EXPECT_EQ(200, bounds.width()); |
| EXPECT_EQ(250, bounds.height()); |
| } |
| } |
| |
| TEST(PdfMetafileCgTest, RenderPortraitRectangles) { |
| constexpr gfx::Rect kPageBounds(200, 300); |
| constexpr gfx::Size kDestinationSize(200, 300); |
| TestRenderPage("rectangles.pdf", /*page_number=*/1, kPageBounds, |
| "render_portrait_rectangles_expected.0.png", kDestinationSize); |
| } |
| |
| TEST(PdfMetafileCgTest, RenderAutorotatedPortraitRectangles) { |
| constexpr gfx::Rect kPageBounds(200, 300); |
| constexpr gfx::Size kDestinationSize(300, 200); |
| TestRenderPage("rectangles.pdf", /*page_number=*/1, kPageBounds, |
| "render_autorotated_portrait_rectangles_expected.0.png", |
| kDestinationSize); |
| } |
| |
| TEST(PdfMetafileCgTest, RenderLargePortraitRectangles) { |
| constexpr gfx::Rect kPageBounds(200, 300); |
| constexpr gfx::Size kDestinationSize(100, 120); |
| TestRenderPage("rectangles.pdf", /*page_number=*/1, kPageBounds, |
| "render_large_portrait_rectangles_expected.0.png", |
| kDestinationSize); |
| } |
| |
| TEST(PdfMetafileCgTest, RenderSmallPortraitRectangles) { |
| constexpr gfx::Rect kPageBounds(200, 300); |
| constexpr gfx::Size kDestinationSize(300, 450); |
| TestRenderPage("rectangles.pdf", /*page_number=*/1, kPageBounds, |
| "render_small_portrait_rectangles_expected.0.png", |
| kDestinationSize); |
| } |
| |
| TEST(PdfMetafileCgTest, RenderLandscapeRectangles) { |
| constexpr gfx::Rect kPageBounds(800, 500); |
| constexpr gfx::Size kDestinationSize(400, 600); |
| TestRenderPage("landscape_rectangles.pdf", /*page_number=*/1, kPageBounds, |
| "render_landscape_rectangles_expected.0.png", |
| kDestinationSize); |
| } |
| |
| TEST(PdfMetafileCgTest, RenderRotatedRectangles) { |
| constexpr gfx::Rect kPageBounds(800, 500); |
| constexpr gfx::Size kLandscapeDestinationSize(600, 400); |
| constexpr gfx::Size kPortraitDestinationSize(400, 600); |
| |
| TestRenderPage("rotated_rectangles.pdf", /*page_number=*/1, kPageBounds, |
| "render_rotated_rectangles_expected.0.png", |
| kLandscapeDestinationSize); |
| TestRenderPage("rotated_rectangles.pdf", /*page_number=*/2, kPageBounds, |
| "render_rotated_rectangles_expected.1.png", |
| kPortraitDestinationSize); |
| TestRenderPage("rotated_rectangles.pdf", /*page_number=*/3, kPageBounds, |
| "render_rotated_rectangles_expected.2.png", |
| kLandscapeDestinationSize); |
| TestRenderPage("rotated_rectangles.pdf", /*page_number=*/4, kPageBounds, |
| "render_rotated_rectangles_expected.3.png", |
| kPortraitDestinationSize); |
| |
| TestRenderPage("rotated_rectangles.pdf", /*page_number=*/1, kPageBounds, |
| "render_autorotated_rotated_rectangles_expected.0.png", |
| kPortraitDestinationSize); |
| TestRenderPage("rotated_rectangles.pdf", /*page_number=*/2, kPageBounds, |
| "render_autorotated_rotated_rectangles_expected.1.png", |
| kLandscapeDestinationSize); |
| TestRenderPage("rotated_rectangles.pdf", /*page_number=*/3, kPageBounds, |
| "render_autorotated_rotated_rectangles_expected.2.png", |
| kPortraitDestinationSize); |
| TestRenderPage("rotated_rectangles.pdf", /*page_number=*/4, kPageBounds, |
| "render_autorotated_rotated_rectangles_expected.3.png", |
| kLandscapeDestinationSize); |
| } |
| |
| TEST(PdfMetafileCgTest, RenderLargeLandscapeRectangles) { |
| constexpr gfx::Rect kPageBounds(800, 500); |
| constexpr gfx::Size kDestinationSize(200, 300); |
| TestRenderPage("landscape_rectangles.pdf", /*page_number=*/1, kPageBounds, |
| "render_large_landscape_rectangles_expected.0.png", |
| kDestinationSize); |
| } |
| |
| TEST(PdfMetafileCgTest, RenderSmallLandscapeRectangles) { |
| constexpr gfx::Rect kPageBounds(800, 500); |
| constexpr gfx::Size kDestinationSize(600, 900); |
| TestRenderPage("landscape_rectangles.pdf", /*page_number=*/1, kPageBounds, |
| "render_small_landscape_rectangles_expected.0.png", |
| kDestinationSize); |
| } |
| |
| TEST(PdfMetafileCgTest, RenderScaledLargeLandscapeRectangles) { |
| constexpr gfx::Rect kPageBounds(800, 500); |
| constexpr gfx::Size kDestinationSize(300, 450); |
| TestRenderPageWithTransformParams( |
| "landscape_rectangles.pdf", /*page_number=*/1, kPageBounds, |
| "render_scaled_large_landscape_rectangles_expected.0.png", |
| kDestinationSize, |
| /*autorotate=*/true, /*fit_to_page=*/true); |
| } |
| |
| TEST(PdfMetafileCgTest, RenderScaledSmallLandscapeRectangles) { |
| constexpr gfx::Rect kPageBounds(800, 500); |
| constexpr gfx::Size kDestinationSize(600, 900); |
| TestRenderPageWithTransformParams( |
| "landscape_rectangles.pdf", /*page_number=*/1, kPageBounds, |
| "render_scaled_small_landscape_rectangles_expected.0.png", |
| kDestinationSize, |
| /*autorotate=*/true, /*fit_to_page=*/true); |
| } |
| |
| } // namespace printing |