// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chromeos/utils/pdf_conversion.h" #include "base/files/file_path.h" #include "base/logging.h" #include "printing/units.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkData.h" #include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkRect.h" #include "third_party/skia/include/core/SkStream.h" #include "third_party/skia/include/core/SkTypes.h" #include "third_party/skia/include/docs/SkPDFDocument.h" #include "ui/gfx/image/buffer_w_stream.h" namespace chromeos { namespace { // The number of degrees to rotate a PDF image. constexpr int kRotationDegrees = 180; // Creates a new page for the PDF document and adds `image_data` to the page. // `rotate` indicates whether the page should be rotated 180 degrees. // Returns whether the page was successfully created. bool AddPdfPage(sk_sp pdf_doc, const sk_sp& image_data, bool rotate, absl::optional dpi) { const sk_sp image = SkImage::MakeFromEncoded(image_data); if (!image) { LOG(ERROR) << "Unable to generate image from encoded image data."; return false; } // Convert from JPG dimensions in pixels (DPI) to PDF dimensions in points // (1/72 in). int page_width; int page_height; if (dpi.has_value() && dpi.value() > 0) { page_width = printing::ConvertUnit(image->width(), dpi.value(), printing::kPointsPerInch); page_height = printing::ConvertUnit(image->height(), dpi.value(), printing::kPointsPerInch); } else { page_width = image->width(); page_height = image->height(); } SkCanvas* page_canvas = pdf_doc->beginPage(page_width, page_height); if (!page_canvas) { LOG(ERROR) << "Unable to access PDF page canvas."; return false; } // Rotate pages that were flipped by an ADF scanner. if (rotate) { page_canvas->rotate(kRotationDegrees); page_canvas->translate(-page_width, -page_height); } SkRect image_bounds = SkRect::MakeIWH(page_width, page_height); page_canvas->drawImageRect(image, image_bounds, SkSamplingOptions()); pdf_doc->endPage(); return true; } } // namespace bool ConvertJpgImagesToPdf(const std::vector& jpg_images, const base::FilePath& file_path, bool rotate_alternate_pages, absl::optional dpi) { DCHECK(!file_path.empty()); SkFILEWStream pdf_outfile(file_path.value().c_str()); if (!pdf_outfile.isValid()) { LOG(ERROR) << "Unable to open output file."; return false; } sk_sp pdf_doc = SkPDF::MakeDocument(&pdf_outfile); DCHECK(pdf_doc); // Never rotate first page of PDF. bool rotate_current_page = false; for (const auto& jpg_image : jpg_images) { SkDynamicMemoryWStream img_stream; if (!img_stream.write(jpg_image.c_str(), jpg_image.size())) { LOG(ERROR) << "Unable to write image to dynamic memory stream."; return false; } const sk_sp img_data = img_stream.detachAsData(); if (img_data->isEmpty()) { LOG(ERROR) << "Stream data is empty."; return false; } if (!AddPdfPage(pdf_doc, img_data, rotate_current_page, dpi)) { LOG(ERROR) << "Unable to add new PDF page."; return false; } if (rotate_alternate_pages) { rotate_current_page = !rotate_current_page; } } pdf_doc->close(); return true; } bool ConvertJpgImageToPdf(const std::vector& jpg_image, std::vector* output) { gfx::BufferWStream output_stream; sk_sp pdf_doc = SkPDF::MakeDocument(&output_stream); DCHECK(pdf_doc); SkDynamicMemoryWStream img_stream; if (!img_stream.write(jpg_image.data(), jpg_image.size())) { LOG(ERROR) << "Unable to write image to dynamic memory stream."; return false; } const sk_sp img_data = img_stream.detachAsData(); if (img_data->isEmpty()) { LOG(ERROR) << "Stream data is empty."; return false; } if (!AddPdfPage(pdf_doc, img_data, false, absl::nullopt)) { LOG(ERROR) << "Unable to add new PDF page."; return false; } pdf_doc->close(); *output = output_stream.TakeBuffer(); return true; } } // namespace chromeos