diff --git a/pdf/pdf.cc b/pdf/pdf.cc index 208e5e781ee939..d824a06deef5dd 100644 --- a/pdf/pdf.cc +++ b/pdf/pdf.cc @@ -6,6 +6,8 @@ #include +#include + #if defined(OS_WIN) #include #endif @@ -143,4 +145,37 @@ bool RenderPDFPageToBitmap(const void* pdf_buffer, pdf_buffer, pdf_buffer_size, page_number, settings, bitmap_buffer); } +bool ConvertPdfPagesToNupPdf( + std::vector> input_buffers, + size_t pages_per_sheet, + size_t page_size_width, + size_t page_size_height, + void** dest_pdf_buffer, + size_t* dest_pdf_buffer_size) { + ScopedSdkInitializer scoped_sdk_initializer; + if (!scoped_sdk_initializer.Init()) + return false; + + PDFEngineExports* engine_exports = PDFEngineExports::Get(); + return engine_exports->ConvertPdfPagesToNupPdf( + std::move(input_buffers), pages_per_sheet, page_size_width, + page_size_height, dest_pdf_buffer, dest_pdf_buffer_size); +} + +bool ConvertPdfDocumentToNupPdf(base::span input_buffer, + size_t pages_per_sheet, + size_t page_size_width, + size_t page_size_height, + void** dest_pdf_buffer, + size_t* dest_pdf_buffer_size) { + ScopedSdkInitializer scoped_sdk_initializer; + if (!scoped_sdk_initializer.Init()) + return false; + + PDFEngineExports* engine_exports = PDFEngineExports::Get(); + return engine_exports->ConvertPdfDocumentToNupPdf( + input_buffer, pages_per_sheet, page_size_width, page_size_height, + dest_pdf_buffer, dest_pdf_buffer_size); +} + } // namespace chrome_pdf diff --git a/pdf/pdf.h b/pdf/pdf.h index ddd286d1998f94..bc36bb51db8630 100644 --- a/pdf/pdf.h +++ b/pdf/pdf.h @@ -5,6 +5,9 @@ #ifndef PDF_PDF_H_ #define PDF_PDF_H_ +#include + +#include "base/containers/span.h" #include "build/build_config.h" #if defined(OS_WIN) @@ -126,6 +129,59 @@ bool RenderPDFPageToBitmap(const void* pdf_buffer, bool autorotate, bool use_color); +// Convert multiple PDF pages into a N-up PDF. +// |input_buffers| is the vector of buffers with each buffer contains a PDF. +// If any of the PDFs contains multiple pages, only the first page of the +// document is used. +// |pages_per_sheet| is the number of pages to put on one sheet. +// |page_size_width| is the width of the output page size, measured in PDF +// "user space" units. +// |page_size_height| is the height of the output page size, measured in PDF +// "user space" units. +// |dest_pdf_buffer| is the output N-up PDF page. Caller takes ownership, and +// needs to free the memory. +// |dest_pdf_buffer_size| is the size of output N-up PDF page. +// +// |page_size_width| and |page_size_height| are the print media size. The page +// size of the output N-up PDF is determined by the |pages_per_sheet|, the +// orientation of the PDF pages contained in the |input_buffers|, and the media +// page size |page_size_width| and |page_size_height|. For example, when +// |page_size_width| = 512, |page_size_height| = 792, |pages_per_sheet| = 2, and +// the orientation of |input_buffers| = portrait, the output N-up PDF will have +// |page_size_width| = 792, and |page_size_height| = 512. +// See printing::NupParameters for more details on how the output page +// orientation is determined, to understand why |page_size_width| and +// |page_size_height| may be swapped in some cases. +bool ConvertPdfPagesToNupPdf( + std::vector> input_buffers, + size_t pages_per_sheet, + size_t page_size_width, + size_t page_size_height, + void** dest_pdf_buffer, + size_t* dest_pdf_buffer_size); + +// Convert a PDF document to a N-up PDF document. +// |input_buffer| is the buffer that contains the entire PDF document to be +// converted to a N-up PDF document. +// |pages_per_sheet| is the number of pages to put on one sheet. +// |page_size_width| is the width of the media page size, measured in PDF +// "user space" units. +// |page_size_height| is the height of the media page size, measured in PDF +// "user space" units. +// |dest_pdf_buffer| is the output N-up PDF page. Caller takes ownership, and +// needs to free the memory. +// |dest_pdf_buffer_size| is the size of output N-up PDF document. +// +// Refer to the description of ConvertPdfPagesToNupPdf to understand how the +// output page size |page_size_width| and |page_size_height| will be calculated. +// The algorithm used to determine the output page size is the same. +bool ConvertPdfDocumentToNupPdf(base::span input_buffer, + size_t pages_per_sheet, + size_t page_size_width, + size_t page_size_height, + void** dest_pdf_buffer, + size_t* dest_pdf_buffer_size); + } // namespace chrome_pdf #endif // PDF_PDF_H_ diff --git a/pdf/pdf_engine.h b/pdf/pdf_engine.h index b8be4541a16f08..1e4dc14e809dae 100644 --- a/pdf/pdf_engine.h +++ b/pdf/pdf_engine.h @@ -18,6 +18,7 @@ #include #include +#include "base/containers/span.h" #include "base/optional.h" #include "base/strings/string16.h" #include "base/time/time.h" @@ -464,6 +465,24 @@ class PDFEngineExports { const RenderingSettings& settings, void* bitmap_buffer) = 0; + // See the definition of ConvertPdfPagesToNupPdf in pdf.cc for details. + virtual bool ConvertPdfPagesToNupPdf( + std::vector> input_buffers, + size_t pages_per_sheet, + size_t page_size_width, + size_t page_size_height, + void** dest_pdf_buffer, + size_t* dest_pdf_buffer_size) = 0; + + // See the definition of ConvertPdfDocumentToNupPdf in pdf.cc for details. + virtual bool ConvertPdfDocumentToNupPdf( + base::span input_buffer, + size_t pages_per_sheet, + size_t page_size_width, + size_t page_size_height, + void** dest_pdf_buffer, + size_t* dest_pdf_buffer_size) = 0; + virtual bool GetPDFDocInfo(const void* pdf_buffer, int buffer_size, int* page_count, diff --git a/pdf/pdfium/pdfium_engine_exports.cc b/pdf/pdfium/pdfium_engine_exports.cc index e6c94fb3adf833..2dcfc55f8214f0 100644 --- a/pdf/pdfium/pdfium_engine_exports.cc +++ b/pdf/pdfium/pdfium_engine_exports.cc @@ -8,8 +8,13 @@ #include #include "base/no_destructor.h" +#include "base/numerics/safe_conversions.h" +#include "pdf/pdfium/pdfium_mem_buffer_file_write.h" +#include "pdf/pdfium/pdfium_print.h" +#include "printing/nup_parameters.h" #include "printing/units.h" #include "third_party/pdfium/public/cpp/fpdf_scopers.h" +#include "third_party/pdfium/public/fpdf_ppo.h" #include "third_party/pdfium/public/fpdfview.h" using printing::ConvertUnitDouble; @@ -89,6 +94,66 @@ int CalculatePosition(FPDF_PAGE page, return rotate; } +ScopedFPDFDocument LoadPdfData(base::span pdf_buffer) { + ScopedFPDFDocument doc; + if (base::IsValueInRangeForNumericType(pdf_buffer.size())) { + doc.reset( + FPDF_LoadMemDocument(pdf_buffer.data(), pdf_buffer.size(), nullptr)); + } + return doc; +} + +ScopedFPDFDocument CreatePdfDoc( + std::vector> input_buffers) { + ScopedFPDFDocument doc(FPDF_CreateNewDocument()); + + size_t index = 0; + for (auto input_buffer : input_buffers) { + ScopedFPDFDocument single_page_doc = LoadPdfData(input_buffer); + if (!FPDF_ImportPages(doc.get(), single_page_doc.get(), "1", index++)) { + return nullptr; + } + } + + return doc; +} + +bool CreateNupPdfDocument(FPDF_DOCUMENT doc, + size_t pages_per_sheet, + size_t page_size_width, + size_t page_size_height, + void** dest_pdf_buffer, + size_t* dest_pdf_buffer_size) { + if (!dest_pdf_buffer || !dest_pdf_buffer_size) + return false; + + printing::NupParameters nup_params; + bool is_landscape = PDFiumPrint::IsSourcePdfLandscape(doc); + nup_params.SetParameters(pages_per_sheet, is_landscape); + bool paper_is_landscape = page_size_width > page_size_height; + if (nup_params.landscape() != paper_is_landscape) + std::swap(page_size_width, page_size_height); + + ScopedFPDFDocument output_doc_nup(FPDF_ImportNPagesToOne( + doc, page_size_width, page_size_height, nup_params.num_pages_on_x_axis(), + nup_params.num_pages_on_y_axis())); + + if (!output_doc_nup) + return false; + + // Copy data to the |dest_pdf_buffer| here. + // TODO(thestig): Avoid this copy. + PDFiumMemBufferFileWrite output_file_write; + if (!FPDF_SaveAsCopy(output_doc_nup.get(), &output_file_write, 0)) { + return false; + } + *dest_pdf_buffer = malloc(output_file_write.size()); + memcpy(*dest_pdf_buffer, output_file_write.buffer().c_str(), + output_file_write.size()); + *dest_pdf_buffer_size = output_file_write.size(); + return true; +} + } // namespace PDFEngineExports::RenderingSettings::RenderingSettings(int dpi_x, @@ -245,6 +310,38 @@ bool PDFiumEngineExports::RenderPDFPageToBitmap( return true; } +bool PDFiumEngineExports::ConvertPdfPagesToNupPdf( + std::vector> input_buffers, + size_t pages_per_sheet, + size_t page_size_width, + size_t page_size_height, + void** dest_pdf_buffer, + size_t* dest_pdf_buffer_size) { + ScopedFPDFDocument doc = CreatePdfDoc(std::move(input_buffers)); + if (!doc) + return false; + + return CreateNupPdfDocument(doc.get(), pages_per_sheet, page_size_width, + page_size_height, dest_pdf_buffer, + dest_pdf_buffer_size); +} + +bool PDFiumEngineExports::ConvertPdfDocumentToNupPdf( + base::span input_buffer, + size_t pages_per_sheet, + size_t page_size_width, + size_t page_size_height, + void** dest_pdf_buffer, + size_t* dest_pdf_buffer_size) { + ScopedFPDFDocument doc = LoadPdfData(input_buffer); + if (!doc) + return false; + + return CreateNupPdfDocument(doc.get(), pages_per_sheet, page_size_width, + page_size_height, dest_pdf_buffer, + dest_pdf_buffer_size); +} + bool PDFiumEngineExports::GetPDFDocInfo(const void* pdf_buffer, int buffer_size, int* page_count, diff --git a/pdf/pdfium/pdfium_engine_exports.h b/pdf/pdfium/pdfium_engine_exports.h index b37de82875a411..3a23d857a471fe 100644 --- a/pdf/pdfium/pdfium_engine_exports.h +++ b/pdf/pdfium/pdfium_engine_exports.h @@ -8,6 +8,7 @@ #include #include +#include "base/containers/span.h" #include "build/build_config.h" #include "pdf/pdf_engine.h" @@ -36,6 +37,19 @@ class PDFiumEngineExports : public PDFEngineExports { int page_number, const RenderingSettings& settings, void* bitmap_buffer) override; + bool ConvertPdfPagesToNupPdf( + std::vector> input_buffers, + size_t pages_per_sheet, + size_t page_size_width, + size_t page_size_height, + void** dest_pdf_buffer, + size_t* dest_pdf_buffer_size) override; + bool ConvertPdfDocumentToNupPdf(base::span input_buffer, + size_t pages_per_sheet, + size_t page_size_width, + size_t page_size_height, + void** dest_pdf_buffer, + size_t* dest_pdf_buffer_size) override; bool GetPDFDocInfo(const void* pdf_buffer, int buffer_size, int* page_count, diff --git a/pdf/pdfium/pdfium_engine_exports_unittest.cc b/pdf/pdfium/pdfium_engine_exports_unittest.cc index fe83add7857ff8..818dfc6c93af9c 100644 --- a/pdf/pdfium/pdfium_engine_exports_unittest.cc +++ b/pdf/pdfium/pdfium_engine_exports_unittest.cc @@ -98,4 +98,74 @@ TEST_F(PDFiumEngineExportsTest, GetPDFPageSizeByIndex) { } } +TEST_F(PDFiumEngineExportsTest, ConvertPdfPagesToNupPdf) { + base::FilePath pdf_path = + pdf_data_dir().Append(FILE_PATH_LITERAL("rectangles.pdf")); + std::string pdf_data; + ASSERT_TRUE(base::ReadFileToString(pdf_path, &pdf_data)); + + std::vector> pdf_buffers; + + EXPECT_FALSE( + ConvertPdfPagesToNupPdf(pdf_buffers, 1, 512, 792, nullptr, nullptr)); + + pdf_buffers.push_back(base::as_bytes(base::make_span(pdf_data))); + pdf_buffers.push_back(base::as_bytes(base::make_span(pdf_data))); + + void* output_pdf_buffer; + size_t output_pdf_buffer_size; + ASSERT_TRUE(ConvertPdfPagesToNupPdf( + pdf_buffers, 2, 512, 792, &output_pdf_buffer, &output_pdf_buffer_size)); + ASSERT_GT(output_pdf_buffer_size, 0U); + ASSERT_NE(output_pdf_buffer, nullptr); + int page_count; + ASSERT_TRUE(GetPDFDocInfo(output_pdf_buffer, output_pdf_buffer_size, + &page_count, nullptr)); + ASSERT_EQ(1, page_count); + + double width; + double height; + ASSERT_TRUE(GetPDFPageSizeByIndex(output_pdf_buffer, output_pdf_buffer_size, + 0, &width, &height)); + EXPECT_DOUBLE_EQ(792.0, width); + EXPECT_DOUBLE_EQ(512.0, height); + + free(output_pdf_buffer); +} + +TEST_F(PDFiumEngineExportsTest, ConvertPdfDocumentToNupPdf) { + base::FilePath pdf_path = + pdf_data_dir().Append(FILE_PATH_LITERAL("rectangles_multi_pages.pdf")); + std::string pdf_data; + ASSERT_TRUE(base::ReadFileToString(pdf_path, &pdf_data)); + + base::span pdf_buffer; + + EXPECT_FALSE( + ConvertPdfDocumentToNupPdf(pdf_buffer, 1, 512, 792, nullptr, nullptr)); + + pdf_buffer = base::as_bytes(base::make_span(pdf_data)); + + void* output_pdf_buffer; + size_t output_pdf_buffer_size; + ASSERT_TRUE(ConvertPdfDocumentToNupPdf( + pdf_buffer, 4, 512, 792, &output_pdf_buffer, &output_pdf_buffer_size)); + ASSERT_GT(output_pdf_buffer_size, 0U); + ASSERT_NE(output_pdf_buffer, nullptr); + int page_count; + ASSERT_TRUE(GetPDFDocInfo(output_pdf_buffer, output_pdf_buffer_size, + &page_count, nullptr)); + ASSERT_EQ(2, page_count); + for (int page_number = 0; page_number < page_count; ++page_number) { + double width; + double height; + ASSERT_TRUE(GetPDFPageSizeByIndex(output_pdf_buffer, output_pdf_buffer_size, + page_number, &width, &height)); + EXPECT_DOUBLE_EQ(512.0, width); + EXPECT_DOUBLE_EQ(792.0, height); + } + + free(output_pdf_buffer); +} + } // namespace chrome_pdf diff --git a/pdf/pdfium/pdfium_print.cc b/pdf/pdfium/pdfium_print.cc index ea3e2f372f2be6..9dc4709901ec62 100644 --- a/pdf/pdfium/pdfium_print.cc +++ b/pdf/pdfium/pdfium_print.cc @@ -37,24 +37,6 @@ bool ShouldDoNup(int pages_per_sheet) { return pages_per_sheet > 1; } -// Check the source doc orientation. Returns true if the doc is landscape. -// For now the orientation of the doc is determined by its first page's -// orientation. Improvement can be added in the future to better determine the -// orientation of the source docs that have mixed orientation. -// TODO(xlou): rotate pages if the source doc has mixed orientation. So that -// the orientation of all pages of the doc are uniform. Pages of square size -// will not be rotated. -bool IsSourcePdfLandscape(FPDF_DOCUMENT doc) { - DCHECK(doc); - - ScopedFPDFPage pdf_page(FPDF_LoadPage(doc, 0)); - DCHECK(pdf_page); - - bool is_source_landscape = - FPDF_GetPageWidth(pdf_page.get()) > FPDF_GetPageHeight(pdf_page.get()); - return is_source_landscape; -} - // Set the destination page size and content area in points based on source // page rotation and orientation. // @@ -235,6 +217,17 @@ std::vector PDFiumPrint::GetPageNumbersFromPrintPageNumberRange( return page_numbers; } +bool PDFiumPrint::IsSourcePdfLandscape(FPDF_DOCUMENT doc) { + DCHECK(doc); + + ScopedFPDFPage pdf_page(FPDF_LoadPage(doc, 0)); + DCHECK(pdf_page); + + bool is_source_landscape = + FPDF_GetPageWidth(pdf_page.get()) > FPDF_GetPageHeight(pdf_page.get()); + return is_source_landscape; +} + pp::Buffer_Dev PDFiumPrint::PrintPagesAsRasterPDF( const PP_PrintPageNumberRange_Dev* page_ranges, uint32_t page_range_count, diff --git a/pdf/pdfium/pdfium_print.h b/pdf/pdfium/pdfium_print.h index c0df7d99e4476f..5405a1fc28212b 100644 --- a/pdf/pdfium/pdfium_print.h +++ b/pdf/pdfium/pdfium_print.h @@ -40,6 +40,15 @@ class PDFiumPrint { const PP_PrintSettings_Dev& print_settings, const PP_PdfPrintSettings_Dev& pdf_print_settings); + // Check the source doc orientation. Returns true if the doc is landscape. + // For now the orientation of the doc is determined by its first page's + // orientation. Improvement can be added in the future to better determine + // the orientation of the source docs that have mixed orientation. + // TODO(xlou): rotate pages if the source doc has mixed orientation. So that + // the orientation of all pages of the doc are uniform. Pages of square size + // will not be rotated. + static bool IsSourcePdfLandscape(FPDF_DOCUMENT doc); + private: FPDF_DOCUMENT CreateSinglePageRasterPdf( double source_page_width, diff --git a/pdf/test/data/rectangles.pdf b/pdf/test/data/rectangles.pdf new file mode 100644 index 00000000000000..7bad251ba7e08e Binary files /dev/null and b/pdf/test/data/rectangles.pdf differ diff --git a/pdf/test/data/rectangles_multi_pages.pdf b/pdf/test/data/rectangles_multi_pages.pdf new file mode 100644 index 00000000000000..22d44a4fdfe045 Binary files /dev/null and b/pdf/test/data/rectangles_multi_pages.pdf differ