blob: 32851bf0769f91e7ccc1b22a97f7f5f3b85d00e4 [file] [log] [blame]
// Copyright 2016 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 "printing/printing_context_chromeos.h"
#include <cups/cups.h>
#include <stdint.h>
#include <unicode/ulocdata.h>
#include <memory>
#include <utility>
#include <vector>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "printing/backend/cups_connection.h"
#include "printing/backend/cups_ipp_util.h"
#include "printing/backend/cups_printer.h"
#include "printing/metafile.h"
#include "printing/print_job_constants.h"
#include "printing/print_settings.h"
#include "printing/printing_context_no_system_dialog.h"
#include "printing/units.h"
namespace printing {
namespace {
using ScopedCupsOption = std::unique_ptr<cups_option_t, OptionDeleter>;
// convert from a ColorMode setting to a print-color-mode value from PWG 5100.13
const char* GetColorModelForMode(int color_mode) {
const char* mode_string;
switch (color_mode) {
case COLOR:
case CMYK:
case CMY:
case KCMY:
case CMY_K:
case RGB:
case RGB16:
case RGBA:
case COLORMODE_COLOR:
case BROTHER_CUPS_COLOR:
case BROTHER_BRSCRIPT3_COLOR:
case HP_COLOR_COLOR:
case PRINTOUTMODE_NORMAL:
case PROCESSCOLORMODEL_CMYK:
case PROCESSCOLORMODEL_RGB:
mode_string = CUPS_PRINT_COLOR_MODE_COLOR;
break;
case GRAY:
case BLACK:
case GRAYSCALE:
case COLORMODE_MONOCHROME:
case BROTHER_CUPS_MONO:
case BROTHER_BRSCRIPT3_BLACK:
case HP_COLOR_BLACK:
case PRINTOUTMODE_NORMAL_GRAY:
case PROCESSCOLORMODEL_GREYSCALE:
mode_string = CUPS_PRINT_COLOR_MODE_MONOCHROME;
break;
default:
mode_string = nullptr;
LOG(WARNING) << "Unrecognized color mode";
break;
}
return mode_string;
}
// Returns a new char buffer which is a null-terminated copy of |value|. The
// caller owns the returned string.
char* DuplicateString(const base::StringPiece value) {
char* dst = new char[value.size() + 1];
value.copy(dst, value.size());
dst[value.size()] = '\0';
return dst;
}
ScopedCupsOption ConstructOption(const base::StringPiece name,
const base::StringPiece value) {
// ScopedCupsOption frees the name and value buffers on deletion
ScopedCupsOption option = ScopedCupsOption(new cups_option_t);
option->name = DuplicateString(name);
option->value = DuplicateString(value);
return option;
}
base::StringPiece GetCollateString(bool collate) {
return collate ? kCollated : kUncollated;
}
std::vector<ScopedCupsOption> SettingsToCupsOptions(
const PrintSettings& settings) {
const char* sides = nullptr;
switch (settings.duplex_mode()) {
case SIMPLEX:
sides = CUPS_SIDES_ONE_SIDED;
break;
case LONG_EDGE:
sides = CUPS_SIDES_TWO_SIDED_PORTRAIT;
break;
case SHORT_EDGE:
sides = CUPS_SIDES_TWO_SIDED_LANDSCAPE;
break;
default:
NOTREACHED();
}
std::vector<ScopedCupsOption> options;
options.push_back(
ConstructOption(kIppColor,
GetColorModelForMode(settings.color()))); // color
options.push_back(ConstructOption(kIppDuplex, sides)); // duplexing
options.push_back(
ConstructOption(kIppMedia,
settings.requested_media().vendor_id)); // paper size
options.push_back(
ConstructOption(kIppCopies,
base::IntToString(settings.copies()))); // copies
options.push_back(
ConstructOption(kIppCollate,
GetCollateString(settings.collate()))); // collate
return options;
}
void SetPrintableArea(PrintSettings* settings,
const PrintSettings::RequestedMedia& media,
bool flip) {
if (!media.size_microns.IsEmpty()) {
float deviceMicronsPerDeviceUnit =
(kHundrethsMMPerInch * 10.0f) / settings->device_units_per_inch();
gfx::Size paper_size =
gfx::Size(media.size_microns.width() / deviceMicronsPerDeviceUnit,
media.size_microns.height() / deviceMicronsPerDeviceUnit);
gfx::Rect paper_rect(0, 0, paper_size.width(), paper_size.height());
settings->SetPrinterPrintableArea(paper_size, paper_rect, flip);
}
}
} // namespace
// static
std::unique_ptr<PrintingContext> PrintingContext::Create(Delegate* delegate) {
if (PrintBackend::GetNativeCupsEnabled())
return base::MakeUnique<PrintingContextChromeos>(delegate);
return base::MakeUnique<PrintingContextNoSystemDialog>(delegate);
}
PrintingContextChromeos::PrintingContextChromeos(Delegate* delegate)
: PrintingContext(delegate),
connection_(GURL(), HTTP_ENCRYPT_NEVER, true) {}
PrintingContextChromeos::~PrintingContextChromeos() {
ReleaseContext();
}
void PrintingContextChromeos::AskUserForSettings(
int max_pages,
bool has_selection,
bool is_scripted,
const PrintSettingsCallback& callback) {
// We don't want to bring up a dialog here. Ever. This should not be called.
NOTREACHED();
}
PrintingContext::Result PrintingContextChromeos::UseDefaultSettings() {
DCHECK(!in_print_job_);
ResetSettings();
std::string device_name = base::UTF16ToUTF8(settings_.device_name());
if (device_name.empty())
return OnError();
// TODO(skau): https://crbug.com/613779. See UpdatePrinterSettings for more
// info.
if (settings_.dpi() == 0) {
DVLOG(1) << "Using Default DPI";
settings_.set_dpi(kDefaultPdfDpi);
}
// Retrieve device information and set it
if (InitializeDevice(device_name) != OK) {
LOG(ERROR) << "Could not initialize printer";
return OnError();
}
// Set printable area
DCHECK(printer_);
PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_);
PrintSettings::RequestedMedia media;
media.vendor_id = paper.vendor_id;
media.size_microns = paper.size_um;
settings_.set_requested_media(media);
SetPrintableArea(&settings_, media, true /* flip landscape */);
return OK;
}
gfx::Size PrintingContextChromeos::GetPdfPaperSizeDeviceUnits() {
int32_t width = 0;
int32_t height = 0;
UErrorCode error = U_ZERO_ERROR;
ulocdata_getPaperSize(delegate_->GetAppLocale().c_str(), &height, &width,
&error);
if (error > U_ZERO_ERROR) {
// If the call failed, assume a paper size of 8.5 x 11 inches.
LOG(WARNING) << "ulocdata_getPaperSize failed, using 8.5 x 11, error: "
<< error;
width =
static_cast<int>(kLetterWidthInch * settings_.device_units_per_inch());
height =
static_cast<int>(kLetterHeightInch * settings_.device_units_per_inch());
} else {
// ulocdata_getPaperSize returns the width and height in mm.
// Convert this to pixels based on the dpi.
float multiplier = 100 * settings_.device_units_per_inch();
multiplier /= kHundrethsMMPerInch;
width *= multiplier;
height *= multiplier;
}
return gfx::Size(width, height);
}
PrintingContext::Result PrintingContextChromeos::UpdatePrinterSettings(
bool external_preview,
bool show_system_dialog,
int page_count) {
DCHECK(!show_system_dialog);
if (InitializeDevice(base::UTF16ToUTF8(settings_.device_name())) != OK)
return OnError();
// TODO(skau): Convert to DCHECK when https://crbug.com/613779 is resolved
// Print quality suffers when this is set to the resolution reported by the
// printer but print quality is fine at this resolution. UseDefaultSettings
// exhibits the same problem.
if (settings_.dpi() == 0) {
DVLOG(1) << "Using Default DPI";
settings_.set_dpi(kDefaultPdfDpi);
}
// compute paper size
PrintSettings::RequestedMedia media = settings_.requested_media();
if (media.IsDefault()) {
DCHECK(printer_);
PrinterSemanticCapsAndDefaults::Paper paper = DefaultPaper(*printer_);
media.vendor_id = paper.vendor_id;
media.size_microns = paper.size_um;
settings_.set_requested_media(media);
}
SetPrintableArea(&settings_, media, true);
return OK;
}
PrintingContext::Result PrintingContextChromeos::InitializeDevice(
const std::string& device) {
DCHECK(!in_print_job_);
std::unique_ptr<CupsPrinter> printer = connection_.GetPrinter(device);
if (!printer) {
LOG(WARNING) << "Could not initialize device";
return OnError();
}
printer_ = std::move(printer);
return OK;
}
PrintingContext::Result PrintingContextChromeos::NewDocument(
const base::string16& document_name) {
DCHECK(!in_print_job_);
in_print_job_ = true;
std::string converted_name = base::UTF16ToUTF8(document_name);
std::string title = base::UTF16ToUTF8(settings_.title());
std::vector<ScopedCupsOption> cups_options = SettingsToCupsOptions(settings_);
std::vector<cups_option_t> options;
for (const ScopedCupsOption& option : cups_options) {
if (printer_->CheckOptionSupported(option->name, option->value)) {
options.push_back(*(option.get()));
} else {
DVLOG(1) << "Unsupported option skipped " << option->name << ", "
<< option->value;
}
}
ipp_status_t create_status = printer_->CreateJob(&job_id_, title, options);
if (job_id_ == 0) {
DLOG(WARNING) << "Creating cups job failed"
<< ippErrorString(create_status);
return OnError();
}
// we only send one document, so it's always the last one
if (!printer_->StartDocument(job_id_, converted_name, true, options)) {
LOG(ERROR) << "Starting document failed";
return OnError();
}
return OK;
}
PrintingContext::Result PrintingContextChromeos::NewPage() {
if (abort_printing_)
return CANCEL;
DCHECK(in_print_job_);
// Intentional No-op.
return OK;
}
PrintingContext::Result PrintingContextChromeos::PageDone() {
if (abort_printing_)
return CANCEL;
DCHECK(in_print_job_);
// Intentional No-op.
return OK;
}
PrintingContext::Result PrintingContextChromeos::DocumentDone() {
if (abort_printing_)
return CANCEL;
DCHECK(in_print_job_);
if (!printer_->FinishDocument()) {
LOG(WARNING) << "Finishing document failed";
return OnError();
}
ipp_status_t job_status = printer_->CloseJob(job_id_);
job_id_ = 0;
if (job_status != IPP_STATUS_OK) {
LOG(WARNING) << "Closing job failed";
return OnError();
}
ResetSettings();
return OK;
}
void PrintingContextChromeos::Cancel() {
abort_printing_ = true;
in_print_job_ = false;
}
void PrintingContextChromeos::ReleaseContext() {
printer_.reset();
}
skia::NativeDrawingContext PrintingContextChromeos::context() const {
// Intentional No-op.
return nullptr;
}
PrintingContext::Result PrintingContextChromeos::StreamData(
const std::vector<char>& buffer) {
if (abort_printing_)
return CANCEL;
DCHECK(in_print_job_);
DCHECK(printer_);
if (!printer_->StreamData(buffer))
return OnError();
return OK;
}
} // namespace printing