// Copyright (c) 2010 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/pdf_ps_metafile_cairo.h" #include #include #include #include #include "base/eintr_wrapper.h" #include "base/file_descriptor_posix.h" #include "base/file_util.h" #include "base/logging.h" #include "printing/units.h" #include "skia/ext/vector_platform_device_linux.h" namespace { const cairo_user_data_key_t kPdfMetafileKey = {0}; // Tests if |surface| is valid. bool IsSurfaceValid(cairo_surface_t* surface) { return cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS; } // Tests if |context| is valid. bool IsContextValid(cairo_t* context) { return cairo_status(context) == CAIRO_STATUS_SUCCESS; } // Destroys and resets |surface|. void CleanUpSurface(cairo_surface_t** surface) { if (*surface) { cairo_surface_destroy(*surface); *surface = NULL; } } // Destroys and resets |context|. void CleanUpContext(cairo_t** context) { if (*context) { cairo_destroy(*context); *context = NULL; } } // Callback function for Cairo to write PDF/PS stream. // |dst_buffer| is actually a pointer of type `std::string*`. cairo_status_t WriteCairoStream(void* dst_buffer, const unsigned char* src_data, unsigned int src_data_length) { DCHECK(dst_buffer); DCHECK(src_data); DCHECK_GT(src_data_length, 0u); std::string* buffer = reinterpret_cast(dst_buffer); buffer->append(reinterpret_cast(src_data), src_data_length); return CAIRO_STATUS_SUCCESS; } void DestroyContextData(void* data) { // Nothing to be done here. } } // namespace namespace printing { PdfPsMetafile::PdfPsMetafile() : format_(PDF), surface_(NULL), context_(NULL) { } PdfPsMetafile::PdfPsMetafile(const FileFormat& format) : format_(format), surface_(NULL), context_(NULL) { } PdfPsMetafile::~PdfPsMetafile() { // Releases all resources if we forgot to do so. CleanUpAll(); } bool PdfPsMetafile::Init() { // We need to check at least these two members to ensure Init() has not been // called before. DCHECK(!context_); DCHECK(data_.empty()); // Creates an 1 by 1 Cairo surface for entire PDF/PS file. // The size for each page will be overwritten later in StartPage(). switch (format_) { case PDF: surface_ = cairo_pdf_surface_create_for_stream(WriteCairoStream, &data_, 1, 1); break; case PS: surface_ = cairo_ps_surface_create_for_stream(WriteCairoStream, &data_, 1, 1); break; default: NOTREACHED(); return false; } // Cairo always returns a valid pointer. // Hence, we have to check if it points to a "nil" object. if (!IsSurfaceValid(surface_)) { DLOG(ERROR) << "Cannot create Cairo surface for PdfPsMetafile!"; CleanUpSurface(&surface_); return false; } // Creates a context. context_ = cairo_create(surface_); if (!IsContextValid(context_)) { DLOG(ERROR) << "Cannot create Cairo context for PdfPsMetafile!"; CleanUpContext(&context_); CleanUpSurface(&surface_); return false; } cairo_set_user_data(context_, &kPdfMetafileKey, this, DestroyContextData); return true; } bool PdfPsMetafile::Init(const void* src_buffer, uint32 src_buffer_size) { // We need to check at least these two members to ensure Init() has not been // called before DCHECK(!context_); DCHECK(data_.empty()); if (src_buffer == NULL || src_buffer_size == 0) return false; data_ = std::string(reinterpret_cast(src_buffer), src_buffer_size); return true; } bool PdfPsMetafile::SetRawData(const void* src_buffer, uint32 src_buffer_size) { if (!context_) { // If Init has not already been called, just call Init() return Init(src_buffer, src_buffer_size); } // If a context has already been created, remember this data in // raw_override_data_ if (src_buffer == NULL || src_buffer_size == 0) return false; raw_override_data_ = std::string(reinterpret_cast(src_buffer), src_buffer_size); return true; } cairo_t* PdfPsMetafile::StartPage(double width_in_points, double height_in_points, double margin_top_in_points, double margin_right_in_points, double margin_bottom_in_points, double margin_left_in_points) { DCHECK(IsSurfaceValid(surface_)); DCHECK(IsContextValid(context_)); // Passing this check implies page_surface_ is NULL, and current_page_ is // empty. DCHECK_GT(width_in_points, 0.); DCHECK_GT(height_in_points, 0.); // We build in extra room for the margins. The Cairo PDF backend will scale // the output to fit a page. double width = width_in_points + margin_left_in_points + margin_right_in_points; double height = height_in_points + margin_top_in_points + margin_bottom_in_points; // Don't let WebKit draw over the margins. cairo_surface_set_device_offset(surface_, margin_left_in_points, margin_top_in_points); switch (format_) { case PDF: cairo_pdf_surface_set_size(surface_, width, height); break; case PS: cairo_ps_surface_set_size(surface_, width, height); break; default: NOTREACHED(); CleanUpAll(); return NULL; } return context_; } bool PdfPsMetafile::FinishPage() { DCHECK(IsSurfaceValid(surface_)); DCHECK(IsContextValid(context_)); // Flushes all rendering for current page. cairo_surface_flush(surface_); cairo_show_page(context_); return true; } void PdfPsMetafile::Close() { DCHECK(IsSurfaceValid(surface_)); DCHECK(IsContextValid(context_)); cairo_surface_finish(surface_); // If we have raw PDF/PS data set use that instead of what was drawn. if (!raw_override_data_.empty()) { data_ = raw_override_data_; raw_override_data_.clear(); } DCHECK(!data_.empty()); // Make sure we did get something. CleanUpContext(&context_); CleanUpSurface(&surface_); } uint32 PdfPsMetafile::GetDataSize() const { // We need to check at least these two members to ensure that either Init() // has been called to initialize |data_|, or metafile has been closed. DCHECK(!context_); DCHECK(!data_.empty()); return data_.size(); } bool PdfPsMetafile::GetData(void* dst_buffer, uint32 dst_buffer_size) const { DCHECK(dst_buffer); DCHECK_GT(dst_buffer_size, 0u); memcpy(dst_buffer, data_.data(), dst_buffer_size); return true; } bool PdfPsMetafile::SaveTo(const base::FileDescriptor& fd) const { // We need to check at least these two members to ensure that either Init() // has been called to initialize |data_|, or metafile has been closed. DCHECK(!context_); DCHECK(!data_.empty()); if (fd.fd < 0) { DLOG(ERROR) << "Invalid file descriptor!"; return false; } bool success = true; if (file_util::WriteFileDescriptor(fd.fd, data_.data(), GetDataSize()) < 0) { DLOG(ERROR) << "Failed to save file with fd " << fd.fd; success = false; } if (fd.auto_close) { if (HANDLE_EINTR(close(fd.fd)) < 0) { DPLOG(WARNING) << "close"; success = false; } } return success; } PdfPsMetafile* PdfPsMetafile::FromCairoContext(cairo_t* context) { return reinterpret_cast( cairo_get_user_data(context, &kPdfMetafileKey)); } void PdfPsMetafile::CleanUpAll() { CleanUpContext(&context_); CleanUpSurface(&surface_); data_.clear(); skia::VectorPlatformDevice::ClearFontCache(); } const double PdfPsMetafile::kTopMarginInInch = 0.25; const double PdfPsMetafile::kBottomMarginInInch = 0.56; const double PdfPsMetafile::kLeftMarginInInch = 0.25; const double PdfPsMetafile::kRightMarginInInch = 0.25; } // namespace printing