1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "pdf/pdfium/pdfium_print.h"
6 
7 #include <algorithm>
8 #include <string>
9 #include <utility>
10 
11 #include "base/strings/string_number_conversions.h"
12 #include "build/chromeos_buildflags.h"
13 #include "pdf/pdf_transform.h"
14 #include "pdf/pdfium/pdfium_engine.h"
15 #include "pdf/pdfium/pdfium_mem_buffer_file_read.h"
16 #include "pdf/pdfium/pdfium_mem_buffer_file_write.h"
17 #include "pdf/ppapi_migration/geometry_conversions.h"
18 #include "ppapi/c/dev/ppp_printing_dev.h"
19 #include "ppapi/c/private/ppp_pdf.h"
20 #include "printing/nup_parameters.h"
21 #include "printing/page_setup.h"
22 #include "printing/units.h"
23 #include "third_party/pdfium/public/fpdf_flatten.h"
24 #include "third_party/pdfium/public/fpdf_ppo.h"
25 #include "third_party/pdfium/public/fpdf_transformpage.h"
26 #include "ui/gfx/codec/jpeg_codec.h"
27 #include "ui/gfx/geometry/point_f.h"
28 #include "ui/gfx/geometry/rect.h"
29 #include "ui/gfx/geometry/size.h"
30 #include "ui/gfx/geometry/size_f.h"
31 
32 using printing::ConvertUnit;
33 using printing::ConvertUnitDouble;
34 using printing::kPointsPerInch;
35 
36 namespace chrome_pdf {
37 
38 namespace {
39 
40 // UI should have done parameter sanity check, when execution
41 // reaches here, |pages_per_sheet| should be a positive integer.
ShouldDoNup(int pages_per_sheet)42 bool ShouldDoNup(int pages_per_sheet) {
43   return pages_per_sheet > 1;
44 }
45 
46 // Returns the valid, positive page count, or 0 on failure.
GetDocumentPageCount(FPDF_DOCUMENT doc)47 int GetDocumentPageCount(FPDF_DOCUMENT doc) {
48   return std::max(FPDF_GetPageCount(doc), 0);
49 }
50 
51 // Set the destination page size and content area in points based on source
52 // page rotation and orientation.
53 //
54 // |rotated| True if source page is rotated 90 degree or 270 degree.
55 // |is_src_page_landscape| is true if the source page orientation is landscape.
56 // |page_size| has the actual destination page size in points.
57 // |content_rect| has the actual destination page printable area values in
58 // points.
SetPageSizeAndContentRect(bool rotated,bool is_src_page_landscape,gfx::Size * page_size,gfx::Rect * content_rect)59 void SetPageSizeAndContentRect(bool rotated,
60                                bool is_src_page_landscape,
61                                gfx::Size* page_size,
62                                gfx::Rect* content_rect) {
63   bool is_dst_page_landscape = page_size->width() > page_size->height();
64   bool page_orientation_mismatched =
65       is_src_page_landscape != is_dst_page_landscape;
66   bool rotate_dst_page = rotated ^ page_orientation_mismatched;
67   if (rotate_dst_page) {
68     page_size->SetSize(page_size->height(), page_size->width());
69     content_rect->SetRect(content_rect->y(), content_rect->x(),
70                           content_rect->height(), content_rect->width());
71   }
72 }
73 
74 // Transform |page| contents to fit in the selected printer paper size.
TransformPDFPageForPrinting(FPDF_PAGE page,float scale_factor,PP_PrintScalingOption_Dev scaling_option,const gfx::Size & paper_size,const gfx::Rect & printable_area)75 void TransformPDFPageForPrinting(FPDF_PAGE page,
76                                  float scale_factor,
77                                  PP_PrintScalingOption_Dev scaling_option,
78                                  const gfx::Size& paper_size,
79                                  const gfx::Rect& printable_area) {
80   // Get the source page width and height in points.
81   gfx::SizeF src_page_size(FPDF_GetPageWidthF(page), FPDF_GetPageHeightF(page));
82   const int src_page_rotation = FPDFPage_GetRotation(page);
83 
84   gfx::Size page_size = paper_size;
85   gfx::Rect content_rect = printable_area;
86   const bool rotated = (src_page_rotation % 2 == 1);
87   SetPageSizeAndContentRect(rotated,
88                             src_page_size.width() > src_page_size.height(),
89                             &page_size, &content_rect);
90 
91   // Compute the screen page width and height in points.
92   const int actual_page_width =
93       rotated ? page_size.height() : page_size.width();
94   const int actual_page_height =
95       rotated ? page_size.width() : page_size.height();
96 
97   gfx::Rect gfx_printed_rect;
98   bool fitted_scaling;
99   switch (scaling_option) {
100     case PP_PRINTSCALINGOPTION_FIT_TO_PRINTABLE_AREA:
101       gfx_printed_rect = gfx::Rect(content_rect.x(), content_rect.y(),
102                                    content_rect.width(), content_rect.height());
103       fitted_scaling = true;
104       break;
105     case PP_PRINTSCALINGOPTION_FIT_TO_PAPER:
106       gfx_printed_rect = gfx::Rect(page_size.width(), page_size.height());
107       fitted_scaling = true;
108       break;
109     default:
110       fitted_scaling = false;
111       break;
112   }
113 
114   if (fitted_scaling) {
115     scale_factor =
116         CalculateScaleFactor(gfx_printed_rect, src_page_size, rotated);
117   }
118 
119   // Calculate positions for the clip box.
120   PdfRectangle media_box;
121   PdfRectangle crop_box;
122   bool has_media_box =
123       !!FPDFPage_GetMediaBox(page, &media_box.left, &media_box.bottom,
124                              &media_box.right, &media_box.top);
125   bool has_crop_box = !!FPDFPage_GetCropBox(
126       page, &crop_box.left, &crop_box.bottom, &crop_box.right, &crop_box.top);
127   CalculateMediaBoxAndCropBox(rotated, has_media_box, has_crop_box, &media_box,
128                               &crop_box);
129   PdfRectangle source_clip_box = CalculateClipBoxBoundary(media_box, crop_box);
130   ScalePdfRectangle(scale_factor, &source_clip_box);
131 
132   // Calculate the translation offset values.
133   gfx::PointF offset =
134       fitted_scaling
135           ? CalculateScaledClipBoxOffset(gfx_printed_rect, source_clip_box)
136           : CalculateNonScaledClipBoxOffset(
137                 src_page_rotation, actual_page_width, actual_page_height,
138                 source_clip_box);
139 
140   // Reset the media box and crop box. When the page has crop box and media box,
141   // the plugin will display the crop box contents and not the entire media box.
142   // If the pages have different crop box values, the plugin will display a
143   // document of multiple page sizes. To give better user experience, we
144   // decided to have same crop box and media box values. Hence, the user will
145   // see a list of uniform pages.
146   FPDFPage_SetMediaBox(page, 0, 0, page_size.width(), page_size.height());
147   FPDFPage_SetCropBox(page, 0, 0, page_size.width(), page_size.height());
148 
149   // Transformation is not required, return. Do this check only after updating
150   // the media box and crop box. For more detailed information, please refer to
151   // the comment block right before FPDF_SetMediaBox and FPDF_GetMediaBox calls.
152   if (scale_factor == 1.0f && offset.IsOrigin())
153     return;
154 
155   // All the positions have been calculated, now manipulate the PDF.
156   const FS_MATRIX matrix = {scale_factor, 0.0f,       0.0f,
157                             scale_factor, offset.x(), offset.y()};
158   const FS_RECTF cliprect = {
159       source_clip_box.left + offset.x(), source_clip_box.top + offset.y(),
160       source_clip_box.right + offset.x(), source_clip_box.bottom + offset.y()};
161   FPDFPage_TransFormWithClip(page, &matrix, &cliprect);
162   FPDFPage_TransformAnnots(page, scale_factor, 0, 0, scale_factor, offset.x(),
163                            offset.y());
164 }
165 
FitContentsToPrintableAreaIfRequired(FPDF_DOCUMENT doc,float scale_factor,PP_PrintScalingOption_Dev scaling_option,const gfx::Size & paper_size,const gfx::Rect & printable_area)166 void FitContentsToPrintableAreaIfRequired(
167     FPDF_DOCUMENT doc,
168     float scale_factor,
169     PP_PrintScalingOption_Dev scaling_option,
170     const gfx::Size& paper_size,
171     const gfx::Rect& printable_area) {
172   // Check to see if we need to fit pdf contents to printer paper size.
173   if (scaling_option == PP_PRINTSCALINGOPTION_SOURCE_SIZE)
174     return;
175 
176   int num_pages = FPDF_GetPageCount(doc);
177   // In-place transformation is more efficient than creating a new
178   // transformed document from the source document. Therefore, transform
179   // every page to fit the contents in the selected printer paper.
180   for (int i = 0; i < num_pages; ++i) {
181     ScopedFPDFPage page(FPDF_LoadPage(doc, i));
182     TransformPDFPageForPrinting(page.get(), scale_factor, scaling_option,
183                                 paper_size, printable_area);
184   }
185 }
186 
187 // Takes the same parameters as PDFiumPrint::CreateNupPdf().
188 // On success, returns the N-up version of |doc|. On failure, returns nullptr.
CreateNupPdfDocument(ScopedFPDFDocument doc,size_t pages_per_sheet,const gfx::Size & page_size,const gfx::Rect & printable_area)189 ScopedFPDFDocument CreateNupPdfDocument(ScopedFPDFDocument doc,
190                                         size_t pages_per_sheet,
191                                         const gfx::Size& page_size,
192                                         const gfx::Rect& printable_area) {
193   DCHECK(doc);
194   DCHECK(ShouldDoNup(pages_per_sheet));
195 
196   int page_size_width = page_size.width();
197   int page_size_height = page_size.height();
198 
199   printing::NupParameters nup_params;
200   bool is_landscape = PDFiumPrint::IsSourcePdfLandscape(doc.get());
201   nup_params.SetParameters(pages_per_sheet, is_landscape);
202   bool paper_is_landscape = page_size_width > page_size_height;
203   if (nup_params.landscape() != paper_is_landscape)
204     std::swap(page_size_width, page_size_height);
205 
206   ScopedFPDFDocument nup_doc(FPDF_ImportNPagesToOne(
207       doc.get(), page_size_width, page_size_height,
208       nup_params.num_pages_on_x_axis(), nup_params.num_pages_on_y_axis()));
209   if (nup_doc) {
210     PDFiumPrint::FitContentsToPrintableArea(nup_doc.get(), page_size,
211                                             printable_area);
212   }
213   return nup_doc;
214 }
215 
ConvertDocToBuffer(ScopedFPDFDocument doc)216 std::vector<uint8_t> ConvertDocToBuffer(ScopedFPDFDocument doc) {
217   DCHECK(doc);
218 
219   std::vector<uint8_t> buffer;
220   PDFiumMemBufferFileWrite output_file_write;
221   if (FPDF_SaveAsCopy(doc.get(), &output_file_write, 0))
222     buffer = output_file_write.TakeBuffer();
223   return buffer;
224 }
225 
GetBlockForJpeg(void * param,unsigned long pos,unsigned char * buf,unsigned long size)226 int GetBlockForJpeg(void* param,
227                     unsigned long pos,
228                     unsigned char* buf,
229                     unsigned long size) {
230   std::vector<uint8_t>* data_vector = static_cast<std::vector<uint8_t>*>(param);
231   if (pos + size < pos || pos + size > data_vector->size())
232     return 0;
233   memcpy(buf, data_vector->data() + pos, size);
234   return 1;
235 }
236 
GetPageRangeStringFromRange(const PP_PrintPageNumberRange_Dev * page_ranges,uint32_t page_range_count)237 std::string GetPageRangeStringFromRange(
238     const PP_PrintPageNumberRange_Dev* page_ranges,
239     uint32_t page_range_count) {
240   DCHECK(page_range_count);
241 
242   std::string page_number_str;
243   for (uint32_t i = 0; i < page_range_count; ++i) {
244     if (!page_number_str.empty())
245       page_number_str.push_back(',');
246     const PP_PrintPageNumberRange_Dev& range = page_ranges[i];
247     page_number_str.append(base::NumberToString(range.first_page_number + 1));
248     if (range.first_page_number != range.last_page_number) {
249       page_number_str.push_back('-');
250       page_number_str.append(base::NumberToString(range.last_page_number + 1));
251     }
252   }
253   return page_number_str;
254 }
255 
FlattenPrintData(FPDF_DOCUMENT doc)256 bool FlattenPrintData(FPDF_DOCUMENT doc) {
257   DCHECK(doc);
258 
259   int page_count = FPDF_GetPageCount(doc);
260   for (int i = 0; i < page_count; ++i) {
261     ScopedFPDFPage page(FPDF_LoadPage(doc, i));
262     DCHECK(page);
263     if (FPDFPage_Flatten(page.get(), FLAT_PRINT) == FLATTEN_FAIL)
264       return false;
265   }
266   return true;
267 }
268 
269 }  // namespace
270 
PDFiumPrint(PDFiumEngine * engine)271 PDFiumPrint::PDFiumPrint(PDFiumEngine* engine) : engine_(engine) {}
272 
273 PDFiumPrint::~PDFiumPrint() = default;
274 
275 #if BUILDFLAG(IS_ASH)
276 // static
CreateFlattenedPdf(ScopedFPDFDocument doc)277 std::vector<uint8_t> PDFiumPrint::CreateFlattenedPdf(ScopedFPDFDocument doc) {
278   if (!FlattenPrintData(doc.get()))
279     return std::vector<uint8_t>();
280   return ConvertDocToBuffer(std::move(doc));
281 }
282 #endif  // BUILDFLAG(IS_ASH)
283 
284 // static
GetPageNumbersFromPrintPageNumberRange(const PP_PrintPageNumberRange_Dev * page_ranges,uint32_t page_range_count)285 std::vector<uint32_t> PDFiumPrint::GetPageNumbersFromPrintPageNumberRange(
286     const PP_PrintPageNumberRange_Dev* page_ranges,
287     uint32_t page_range_count) {
288   DCHECK(page_range_count);
289 
290   std::vector<uint32_t> page_numbers;
291   for (uint32_t i = 0; i < page_range_count; ++i) {
292     for (uint32_t page_number = page_ranges[i].first_page_number;
293          page_number <= page_ranges[i].last_page_number; ++page_number) {
294       page_numbers.push_back(page_number);
295     }
296   }
297   return page_numbers;
298 }
299 
300 // static
CreateNupPdf(ScopedFPDFDocument doc,size_t pages_per_sheet,const gfx::Size & page_size,const gfx::Rect & printable_area)301 std::vector<uint8_t> PDFiumPrint::CreateNupPdf(
302     ScopedFPDFDocument doc,
303     size_t pages_per_sheet,
304     const gfx::Size& page_size,
305     const gfx::Rect& printable_area) {
306   ScopedFPDFDocument nup_doc = CreateNupPdfDocument(
307       std::move(doc), pages_per_sheet, page_size, printable_area);
308   if (!nup_doc)
309     return std::vector<uint8_t>();
310   return ConvertDocToBuffer(std::move(nup_doc));
311 }
312 
313 // static
IsSourcePdfLandscape(FPDF_DOCUMENT doc)314 bool PDFiumPrint::IsSourcePdfLandscape(FPDF_DOCUMENT doc) {
315   DCHECK(doc);
316 
317   ScopedFPDFPage pdf_page(FPDF_LoadPage(doc, 0));
318   DCHECK(pdf_page);
319 
320   bool is_source_landscape =
321       FPDF_GetPageWidthF(pdf_page.get()) > FPDF_GetPageHeightF(pdf_page.get());
322   return is_source_landscape;
323 }
324 
325 // static
FitContentsToPrintableArea(FPDF_DOCUMENT doc,const gfx::Size & page_size,const gfx::Rect & printable_area)326 void PDFiumPrint::FitContentsToPrintableArea(FPDF_DOCUMENT doc,
327                                              const gfx::Size& page_size,
328                                              const gfx::Rect& printable_area) {
329   FitContentsToPrintableAreaIfRequired(
330       doc, /*scale_factor=*/1.0f, PP_PRINTSCALINGOPTION_FIT_TO_PRINTABLE_AREA,
331       page_size, printable_area);
332 }
333 
PrintPagesAsPdf(const PP_PrintPageNumberRange_Dev * page_ranges,uint32_t page_range_count,const PP_PrintSettings_Dev & print_settings,const PP_PdfPrintSettings_Dev & pdf_print_settings,bool raster)334 std::vector<uint8_t> PDFiumPrint::PrintPagesAsPdf(
335     const PP_PrintPageNumberRange_Dev* page_ranges,
336     uint32_t page_range_count,
337     const PP_PrintSettings_Dev& print_settings,
338     const PP_PdfPrintSettings_Dev& pdf_print_settings,
339     bool raster) {
340   std::vector<uint8_t> buffer;
341   ScopedFPDFDocument output_doc = CreatePrintPdf(
342       page_ranges, page_range_count, print_settings, pdf_print_settings);
343   if (raster)
344     output_doc = CreateRasterPdf(std::move(output_doc), print_settings);
345   if (GetDocumentPageCount(output_doc.get()))
346     buffer = ConvertDocToBuffer(std::move(output_doc));
347   return buffer;
348 }
349 
CreatePrintPdf(const PP_PrintPageNumberRange_Dev * page_ranges,uint32_t page_range_count,const PP_PrintSettings_Dev & print_settings,const PP_PdfPrintSettings_Dev & pdf_print_settings)350 ScopedFPDFDocument PDFiumPrint::CreatePrintPdf(
351     const PP_PrintPageNumberRange_Dev* page_ranges,
352     uint32_t page_range_count,
353     const PP_PrintSettings_Dev& print_settings,
354     const PP_PdfPrintSettings_Dev& pdf_print_settings) {
355   ScopedFPDFDocument output_doc(FPDF_CreateNewDocument());
356   DCHECK(output_doc);
357   FPDF_CopyViewerPreferences(output_doc.get(), engine_->doc());
358 
359   std::string page_number_str =
360       GetPageRangeStringFromRange(page_ranges, page_range_count);
361   if (!FPDF_ImportPages(output_doc.get(), engine_->doc(),
362                         page_number_str.c_str(), 0)) {
363     return nullptr;
364   }
365 
366   float scale_factor = pdf_print_settings.scale_factor / 100.0f;
367   FitContentsToPrintableAreaIfRequired(
368       output_doc.get(), scale_factor, print_settings.print_scaling_option,
369       SizeFromPPSize(print_settings.paper_size),
370       RectFromPPRect(print_settings.printable_area));
371   if (!FlattenPrintData(output_doc.get()))
372     return nullptr;
373 
374   uint32_t pages_per_sheet = pdf_print_settings.pages_per_sheet;
375   if (!ShouldDoNup(pages_per_sheet))
376     return output_doc;
377 
378   gfx::Size page_size(print_settings.paper_size.width,
379                       print_settings.paper_size.height);
380   gfx::Rect printable_area(print_settings.printable_area.point.x,
381                            print_settings.printable_area.point.y,
382                            print_settings.printable_area.size.width,
383                            print_settings.printable_area.size.height);
384   gfx::Rect symmetrical_printable_area =
385       printing::PageSetup::GetSymmetricalPrintableArea(page_size,
386                                                        printable_area);
387   if (symmetrical_printable_area.IsEmpty())
388     return nullptr;
389   return CreateNupPdfDocument(std::move(output_doc), pages_per_sheet, page_size,
390                               symmetrical_printable_area);
391 }
392 
CreateRasterPdf(ScopedFPDFDocument doc,const PP_PrintSettings_Dev & print_settings)393 ScopedFPDFDocument PDFiumPrint::CreateRasterPdf(
394     ScopedFPDFDocument doc,
395     const PP_PrintSettings_Dev& print_settings) {
396   int page_count = GetDocumentPageCount(doc.get());
397   if (page_count == 0)
398     return nullptr;
399 
400   ScopedFPDFDocument rasterized_doc(FPDF_CreateNewDocument());
401   DCHECK(rasterized_doc);
402   FPDF_CopyViewerPreferences(rasterized_doc.get(), doc.get());
403 
404   for (int i = 0; i < page_count; ++i) {
405     ScopedFPDFPage pdf_page(FPDF_LoadPage(doc.get(), i));
406     if (!pdf_page)
407       return nullptr;
408 
409     ScopedFPDFDocument temp_doc =
410         CreateSinglePageRasterPdf(pdf_page.get(), print_settings);
411     if (!temp_doc)
412       return nullptr;
413 
414     if (!FPDF_ImportPages(rasterized_doc.get(), temp_doc.get(), "1", i))
415       return nullptr;
416   }
417 
418   return rasterized_doc;
419 }
420 
CreateSinglePageRasterPdf(FPDF_PAGE page_to_print,const PP_PrintSettings_Dev & print_settings)421 ScopedFPDFDocument PDFiumPrint::CreateSinglePageRasterPdf(
422     FPDF_PAGE page_to_print,
423     const PP_PrintSettings_Dev& print_settings) {
424   ScopedFPDFDocument temp_doc(FPDF_CreateNewDocument());
425   DCHECK(temp_doc);
426 
427   float source_page_width = FPDF_GetPageWidthF(page_to_print);
428   float source_page_height = FPDF_GetPageHeightF(page_to_print);
429 
430   // For computing size in pixels, use a square dpi since the source PDF page
431   // has square DPI.
432   int width_in_pixels =
433       ConvertUnit(source_page_width, kPointsPerInch, print_settings.dpi);
434   int height_in_pixels =
435       ConvertUnit(source_page_height, kPointsPerInch, print_settings.dpi);
436 
437   gfx::Size bitmap_size(width_in_pixels, height_in_pixels);
438   ScopedFPDFBitmap bitmap(FPDFBitmap_Create(
439       bitmap_size.width(), bitmap_size.height(), /*alpha=*/false));
440 
441   // Clear the bitmap
442   FPDFBitmap_FillRect(bitmap.get(), 0, 0, bitmap_size.width(),
443                       bitmap_size.height(), 0xFFFFFFFF);
444 
445   FPDF_RenderPageBitmap(bitmap.get(), page_to_print, 0, 0, bitmap_size.width(),
446                         bitmap_size.height(), print_settings.orientation,
447                         FPDF_PRINTING);
448 
449   double ratio_x = ConvertUnitDouble(bitmap_size.width(), print_settings.dpi,
450                                      kPointsPerInch);
451   double ratio_y = ConvertUnitDouble(bitmap_size.height(), print_settings.dpi,
452                                      kPointsPerInch);
453 
454   // Add the bitmap to an image object and add the image object to the output
455   // page.
456   ScopedFPDFPageObject temp_img(FPDFPageObj_NewImageObj(temp_doc.get()));
457 
458   bool encoded = false;
459   std::vector<uint8_t> compressed_bitmap_data;
460   if (!(print_settings.format & PP_PRINTOUTPUTFORMAT_PDF)) {
461     // Use quality = 40 as this does not significantly degrade the printed
462     // document relative to a normal bitmap and provides better compression than
463     // a higher quality setting.
464     constexpr int kQuality = 40;
465     SkImageInfo info = SkImageInfo::Make(
466         FPDFBitmap_GetWidth(bitmap.get()), FPDFBitmap_GetHeight(bitmap.get()),
467         kBGRA_8888_SkColorType, kOpaque_SkAlphaType);
468     SkPixmap src(info, FPDFBitmap_GetBuffer(bitmap.get()),
469                  FPDFBitmap_GetStride(bitmap.get()));
470     encoded = gfx::JPEGCodec::Encode(src, kQuality, &compressed_bitmap_data);
471   }
472 
473   {
474     ScopedFPDFPage temp_page_holder(
475         FPDFPage_New(temp_doc.get(), 0, source_page_width, source_page_height));
476     FPDF_PAGE temp_page = temp_page_holder.get();
477     if (encoded) {
478       FPDF_FILEACCESS file_access = {};
479       file_access.m_FileLen =
480           static_cast<unsigned long>(compressed_bitmap_data.size());
481       file_access.m_GetBlock = &GetBlockForJpeg;
482       file_access.m_Param = &compressed_bitmap_data;
483 
484       FPDFImageObj_LoadJpegFileInline(&temp_page, 1, temp_img.get(),
485                                       &file_access);
486     } else {
487       FPDFImageObj_SetBitmap(&temp_page, 1, temp_img.get(), bitmap.get());
488     }
489 
490     FPDFImageObj_SetMatrix(temp_img.get(), ratio_x, 0, 0, ratio_y, 0, 0);
491     FPDFPage_InsertObject(temp_page, temp_img.release());
492     FPDFPage_GenerateContent(temp_page);
493   }
494 
495   return temp_doc;
496 }
497 
498 }  // namespace chrome_pdf
499