blob: 26739acdd27bae3cc1f405d15a7398064ba86978 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/printing/pwg_raster_converter.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <utility>
#include "base/cancelable_callback.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "chrome/browser/printing/printing_service.h"
#include "chrome/services/printing/public/mojom/pdf_to_pwg_raster_converter.mojom.h"
#include "chrome/services/printing/public/mojom/printing_service.mojom.h"
#include "components/cloud_devices/common/cloud_device_description.h"
#include "components/cloud_devices/common/printer_description.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "printing/mojom/print.mojom.h"
#include "printing/pdf_render_settings.h"
#include "printing/pwg_raster_settings.h"
#include "printing/units.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace printing {
namespace {
using content::BrowserThread;
// Converts PDF into PWG raster. Class lives on the UI thread.
class PwgRasterConverterHelper
: public base::RefCounted<PwgRasterConverterHelper> {
public:
PwgRasterConverterHelper(const PdfRenderSettings& settings,
const PwgRasterSettings& bitmap_settings);
PwgRasterConverterHelper(const PwgRasterConverterHelper&) = delete;
PwgRasterConverterHelper& operator=(const PwgRasterConverterHelper&) = delete;
void Convert(const std::optional<bool>& use_skia,
const base::RefCountedMemory* data,
PwgRasterConverter::ResultCallback callback);
private:
friend class base::RefCounted<PwgRasterConverterHelper>;
~PwgRasterConverterHelper();
void RunCallback(base::ReadOnlySharedMemoryRegion region,
uint32_t page_count);
PdfRenderSettings settings_;
PwgRasterSettings bitmap_settings_;
mojo::Remote<printing::mojom::PdfToPwgRasterConverter>
pdf_to_pwg_raster_converter_remote_;
PwgRasterConverter::ResultCallback callback_;
};
PwgRasterConverterHelper::PwgRasterConverterHelper(
const PdfRenderSettings& settings,
const PwgRasterSettings& bitmap_settings)
: settings_(settings), bitmap_settings_(bitmap_settings) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
PwgRasterConverterHelper::~PwgRasterConverterHelper() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
void PwgRasterConverterHelper::Convert(
const std::optional<bool>& use_skia,
const base::RefCountedMemory* data,
PwgRasterConverter::ResultCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
callback_ = std::move(callback);
GetPrintingService()->BindPdfToPwgRasterConverter(
pdf_to_pwg_raster_converter_remote_.BindNewPipeAndPassReceiver());
pdf_to_pwg_raster_converter_remote_.set_disconnect_handler(
base::BindOnce(&PwgRasterConverterHelper::RunCallback, this,
base::ReadOnlySharedMemoryRegion(), /*page_count=*/0));
base::MappedReadOnlyRegion memory =
base::ReadOnlySharedMemoryRegion::Create(data->size());
if (!memory.IsValid()) {
RunCallback(base::ReadOnlySharedMemoryRegion(), /*page_count=*/0);
return;
}
if (use_skia) {
pdf_to_pwg_raster_converter_remote_->SetUseSkiaRendererPolicy(*use_skia);
}
// TODO(thestig): Write `data` into shared memory in the first place, to avoid
// this copy.
memory.mapping.GetMemoryAsSpan<uint8_t>().copy_prefix_from(*data);
pdf_to_pwg_raster_converter_remote_->Convert(
std::move(memory.region), settings_, bitmap_settings_,
base::BindOnce(&PwgRasterConverterHelper::RunCallback, this));
}
void PwgRasterConverterHelper::RunCallback(
base::ReadOnlySharedMemoryRegion region,
uint32_t page_count) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (callback_) {
if (region.IsValid() && page_count > 0) {
size_t average_page_size_in_kb = region.GetSize() / 1024;
average_page_size_in_kb /= page_count;
UMA_HISTOGRAM_MEMORY_KB("Printing.ConversionSize.Pwg",
average_page_size_in_kb);
std::move(callback_).Run(std::move(region));
} else {
// TODO(thestig): Consider adding UMA to track failure rates.
std::move(callback_).Run(base::ReadOnlySharedMemoryRegion());
}
}
pdf_to_pwg_raster_converter_remote_.reset();
}
class PwgRasterConverterImpl : public PwgRasterConverter {
public:
PwgRasterConverterImpl();
PwgRasterConverterImpl(const PwgRasterConverterImpl&) = delete;
PwgRasterConverterImpl& operator=(const PwgRasterConverterImpl&) = delete;
~PwgRasterConverterImpl() override;
void Start(const std::optional<bool>& use_skia,
const base::RefCountedMemory* data,
const PdfRenderSettings& conversion_settings,
const PwgRasterSettings& bitmap_settings,
ResultCallback callback) override;
private:
scoped_refptr<PwgRasterConverterHelper> utility_client_;
// Cancelable version of PwgRasterConverter::ResultCallback.
base::CancelableOnceCallback<void(base::ReadOnlySharedMemoryRegion)>
cancelable_callback_;
};
PwgRasterConverterImpl::PwgRasterConverterImpl() = default;
PwgRasterConverterImpl::~PwgRasterConverterImpl() = default;
void PwgRasterConverterImpl::Start(const std::optional<bool>& use_skia,
const base::RefCountedMemory* data,
const PdfRenderSettings& conversion_settings,
const PwgRasterSettings& bitmap_settings,
ResultCallback callback) {
cancelable_callback_.Reset(std::move(callback));
utility_client_ = base::MakeRefCounted<PwgRasterConverterHelper>(
conversion_settings, bitmap_settings);
utility_client_->Convert(use_skia, data, cancelable_callback_.callback());
}
} // namespace
// static
std::unique_ptr<PwgRasterConverter> PwgRasterConverter::CreateDefault() {
return std::make_unique<PwgRasterConverterImpl>();
}
// static
PdfRenderSettings PwgRasterConverter::GetConversionSettings(
const cloud_devices::CloudDeviceDescription& printer_capabilities,
const gfx::Size& page_size,
bool use_color) {
gfx::Size dpi = gfx::Size(kDefaultPdfDpi, kDefaultPdfDpi);
cloud_devices::printer::DpiCapability dpis;
if (dpis.LoadFrom(printer_capabilities))
dpi = gfx::Size(dpis.GetDefault().horizontal, dpis.GetDefault().vertical);
bool page_is_landscape =
static_cast<double>(page_size.width()) / dpi.width() >
static_cast<double>(page_size.height()) / dpi.height();
// Pdfium assumes that page width is given in dpi.width(), and height in
// dpi.height(). If we rotate the page, we need to also swap the DPIs.
gfx::Size final_page_size = page_size;
if (page_is_landscape) {
final_page_size = gfx::Size(page_size.height(), page_size.width());
dpi = gfx::Size(dpi.height(), dpi.width());
}
double scale_x = static_cast<double>(dpi.width()) / kPointsPerInch;
double scale_y = static_cast<double>(dpi.height()) / kPointsPerInch;
// Make vertical rectangle to optimize streaming to printer. Fix orientation
// by autorotate.
gfx::Rect area(final_page_size.width() * scale_x,
final_page_size.height() * scale_y);
return PdfRenderSettings(area, gfx::Point(0, 0), dpi,
/*autorotate=*/true, use_color,
PdfRenderSettings::Mode::NORMAL);
}
// static
PwgRasterSettings PwgRasterConverter::GetBitmapSettings(
const cloud_devices::CloudDeviceDescription& printer_capabilities,
const cloud_devices::CloudDeviceDescription& ticket) {
cloud_devices::printer::DuplexTicketItem duplex_item;
cloud_devices::printer::DuplexType duplex_value =
cloud_devices::printer::DuplexType::NO_DUPLEX;
if (duplex_item.LoadFrom(ticket))
duplex_value = duplex_item.value();
// This assumes `ticket` contains a color ticket item. In case it does not, or
// the color is invalid, `color_value` will default to AUTO_COLOR, which works
// just fine. With AUTO_COLOR, it may be possible to better determine the
// value for `use_color` based on `printer_capabilities`, rather than just
// defaulting to the safe value of true. Parsing `printer_capabilities`
// requires work, which this method is avoiding on purpose.
cloud_devices::printer::Color color_value;
cloud_devices::printer::ColorTicketItem color_item;
if (color_item.LoadFrom(ticket) && color_item.IsValid())
color_value = color_item.value();
DCHECK(color_value.IsValid());
bool use_color;
switch (color_value.type) {
case cloud_devices::printer::ColorType::STANDARD_MONOCHROME:
case cloud_devices::printer::ColorType::CUSTOM_MONOCHROME:
use_color = false;
break;
case cloud_devices::printer::ColorType::STANDARD_COLOR:
case cloud_devices::printer::ColorType::CUSTOM_COLOR:
case cloud_devices::printer::ColorType::AUTO_COLOR:
use_color = true;
break;
default:
NOTREACHED();
}
cloud_devices::printer::PwgRasterConfigCapability raster_capability;
// If the raster capability fails to load, `raster_capability` will contain
// the default value.
raster_capability.LoadFrom(printer_capabilities);
cloud_devices::printer::DocumentSheetBack document_sheet_back =
raster_capability.value().document_sheet_back;
PwgRasterSettings result;
switch (duplex_value) {
case cloud_devices::printer::DuplexType::NO_DUPLEX:
result.duplex_mode = mojom::DuplexMode::kSimplex;
result.odd_page_transform = TRANSFORM_NORMAL;
break;
case cloud_devices::printer::DuplexType::LONG_EDGE:
result.duplex_mode = mojom::DuplexMode::kLongEdge;
if (document_sheet_back ==
cloud_devices::printer::DocumentSheetBack::ROTATED) {
result.odd_page_transform = TRANSFORM_ROTATE_180;
} else if (document_sheet_back ==
cloud_devices::printer::DocumentSheetBack::FLIPPED) {
result.odd_page_transform = TRANSFORM_FLIP_VERTICAL;
} else {
result.odd_page_transform = TRANSFORM_NORMAL;
}
break;
case cloud_devices::printer::DuplexType::SHORT_EDGE:
result.duplex_mode = mojom::DuplexMode::kShortEdge;
if (document_sheet_back ==
cloud_devices::printer::DocumentSheetBack::MANUAL_TUMBLE) {
result.odd_page_transform = TRANSFORM_ROTATE_180;
} else if (document_sheet_back ==
cloud_devices::printer::DocumentSheetBack::FLIPPED) {
result.odd_page_transform = TRANSFORM_FLIP_HORIZONTAL;
} else {
result.odd_page_transform = TRANSFORM_NORMAL;
}
break;
}
result.rotate_all_pages = raster_capability.value().rotate_all_pages;
result.reverse_page_order = raster_capability.value().reverse_order_streaming;
// No need to check for SRGB_8 support in `types`. CDD spec says:
// "any printer that doesn't support SGRAY_8 must be able to perform
// conversion from RGB to grayscale... "
const auto& types = raster_capability.value().document_types_supported;
result.use_color =
use_color ||
!base::Contains(
types, cloud_devices::printer::PwgDocumentTypeSupported::SGRAY_8);
return result;
}
} // namespace printing