| // 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<SkDocument> pdf_doc, |
| const sk_sp<SkData>& image_data, |
| bool rotate, |
| absl::optional<int> dpi) { |
| const sk_sp<SkImage> 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(-image->width(), -image->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<std::string>& jpg_images, |
| const base::FilePath& file_path, |
| bool rotate_alternate_pages, |
| absl::optional<int> 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<SkDocument> 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<SkData> 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<uint8_t>& jpg_image, |
| std::vector<uint8_t>* output) { |
| gfx::BufferWStream output_stream; |
| sk_sp<SkDocument> 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<SkData> 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 |