From 3cfa15ac25e64425a9550fa8b0f741219fe68006 Mon Sep 17 00:00:00 2001 From: "tony@chromium.org" Date: Tue, 25 Aug 2009 17:36:37 +0000 Subject: Implement native metafile for printing on Linux. The metafile class stores the resulting PDF/PS stream in memory. BUG=9847 Original patch by Min-yu Huang at http://codereview.chromium.org/174042 Review URL: http://codereview.chromium.org/174405 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@24243 0039d316-1c4b-4281-b951-d872f2087c98 --- printing/native_metafile.h | 12 +- printing/pdf_ps_metafile_linux.cc | 288 +++++++++++++++++++++++++++++ printing/pdf_ps_metafile_linux.h | 107 +++++++++++ printing/pdf_ps_metafile_linux_unittest.cc | 122 ++++++++++++ printing/printing.gyp | 13 +- 5 files changed, 538 insertions(+), 4 deletions(-) create mode 100644 printing/pdf_ps_metafile_linux.cc create mode 100644 printing/pdf_ps_metafile_linux.h create mode 100644 printing/pdf_ps_metafile_linux_unittest.cc (limited to 'printing') diff --git a/printing/native_metafile.h b/printing/native_metafile.h index 284212c..be79d5b 100644 --- a/printing/native_metafile.h +++ b/printing/native_metafile.h @@ -5,6 +5,8 @@ #ifndef PRINTING_NATIVE_METAFILE_H_ #define PRINTING_NATIVE_METAFILE_H_ +#include "build/build_config.h" + // Define a metafile format for the current platform. We use this platform // independent define so we can define interfaces in platform agnostic manner. // It is still an outstanding design issue whether we create classes on all @@ -30,9 +32,13 @@ class NativeMetafile {}; #elif defined(OS_LINUX) -// TODO(port): Printing using PostScript? -// The mock class is here so we can compile. -class NativeMetafile {}; +#include "printing/pdf_ps_metafile_linux.h" + +namespace printing { + +typedef PdfPsMetafile NativeMetafile; + +} // namespace printing #endif diff --git a/printing/pdf_ps_metafile_linux.cc b/printing/pdf_ps_metafile_linux.cc new file mode 100644 index 0000000..83c17a9 --- /dev/null +++ b/printing/pdf_ps_metafile_linux.cc @@ -0,0 +1,288 @@ +// Copyright (c) 2009 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_linux.h" + +#include + +#include +#include + +#include "base/file_util.h" +#include "base/logging.h" + +namespace printing { + +PdfPsMetafile::PdfPsMetafile(const FileFormat& format) + : format_(format), + surface_(NULL), context_(NULL), + page_surface_(NULL), page_context_(NULL) { + + // Create 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, + &all_pages_, 1, 1); + } + break; + + case PS: { + surface_ = cairo_ps_surface_create_for_stream(WriteCairoStream, + &all_pages_, 1, 1); + } + break; + + default: + NOTREACHED(); + return; + } + + // 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!"; + cairo_surface_destroy(surface_); + return; + } + + // Create a context. + context_ = cairo_create(surface_); + if (!IsContextValid(context_)) { + DLOG(ERROR) << "Cannot create Cairo context for PdfPsMetafile!"; + cairo_destroy(context_); + cairo_surface_destroy(surface_); + return; + } + + // Since |context_| will keep a reference of |surface_|, we can decreace + // surface's reference count by one safely. + cairo_surface_destroy(surface_); +} + +PdfPsMetafile::PdfPsMetafile(const FileFormat& format, + const void* src_buffer, + size_t src_buffer_size) + : format_(format), + surface_(NULL), context_(NULL), + page_surface_(NULL), page_context_(NULL) { + + all_pages_ = std::string(reinterpret_cast(src_buffer), + src_buffer_size); +} + +PdfPsMetafile::~PdfPsMetafile() { + // Releases resources if we forgot to do so. + Close(); +} + +bool PdfPsMetafile::StartPage(double width_in_points, double height_in_points) { + DCHECK(IsSurfaceValid(surface_)); + DCHECK(IsContextValid(context_)); + DCHECK(!page_surface_ && !page_context_); + DCHECK_GT(width_in_points, 0.); + DCHECK_GT(height_in_points, 0.); + + // Create a target surface for the new page. + // Cairo 1.6.0 does NOT allow the first argument be NULL, + // but some newer versions do support NULL pointer. + switch (format_) { + case PDF: { + page_surface_ = cairo_pdf_surface_create_for_stream(WriteCairoStream, + ¤t_page_, + width_in_points, + height_in_points); + } + break; + + case PS: { + page_surface_ = cairo_ps_surface_create_for_stream(WriteCairoStream, + ¤t_page_, + width_in_points, + height_in_points); + } + 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(page_surface_)) { + DLOG(ERROR) << "Cannot create Cairo surface for PdfPsMetafile!"; + cairo_surface_destroy(page_surface_); + return false; + } + + // Create a context. + page_context_ = cairo_create(page_surface_); + if (!IsContextValid(page_context_)) { + DLOG(ERROR) << "Cannot create Cairo context for PdfPsMetafile!"; + cairo_destroy(page_context_); + cairo_surface_destroy(page_surface_); + return false; + } + + // Since |page_context_| will keep a reference of |page_surface_|, we can + // decreace surface's reference count by one safely. + cairo_surface_destroy(page_surface_); + + return true; +} + +void PdfPsMetafile::FinishPage(float shrink) { + DCHECK(IsSurfaceValid(surface_)); + DCHECK(IsContextValid(context_)); + DCHECK(IsSurfaceValid(page_surface_)); + DCHECK(IsContextValid(page_context_)); + + // Flush all rendering for current page. + cairo_surface_flush(page_surface_); + + // TODO(myhuang): Use real page settings. + // We hard-coded page settings here for testing purpose. + // The paper size is US Letter (8.5 in. by 11 in.). + // The default margins are: + // Left = 0.25 in. + // Right = 0.25 in. + // Top = 0.25 in. + // Bottom = 0.56 in. + const double kDPI = 72.0; // Dots (points) per inch. + const double kWidthInInch = 8.5; + const double kHeightInInch = 11.0; + const double kWidthInPoint = kWidthInInch * kDPI; + const double kHeightInPoint = kHeightInInch * kDPI; + switch (format_) { + case PDF: { + cairo_pdf_surface_set_size(surface_, kWidthInPoint, kHeightInPoint); + } + break; + + case PS: { + cairo_ps_surface_set_size(surface_, kWidthInPoint, kHeightInPoint); + } + break; + + default: + NOTREACHED(); + return; + } + + // Save context's states. + cairo_save(context_); + // Copy current page onto the surface of final result. + // Margins are done by coordinates transformation. + // Please NOTE that we have to call cairo_scale() before we call + // cairo_set_source_surface(). + const double scale_factor = 1. / shrink; + cairo_scale(context_, scale_factor, scale_factor); + const double kLeftMarginInInch = 0.25; + const double kTopMarginInInch = 0.25; + const double kLeftMarginInPoint = kLeftMarginInInch * kDPI; + const double kTopMarginInPoint = kTopMarginInInch * kDPI; + const double kScaledLeftMarginInPoint = kLeftMarginInPoint * shrink; + const double kScaledTopMarginInPoint = kTopMarginInPoint * shrink; + cairo_set_source_surface(context_, + page_surface_, + kScaledLeftMarginInPoint, + kScaledTopMarginInPoint); + // In Cairo 1.6.0, if we use the following API, either the renderer will + // crash, or we will get an empty page. This might be a bug in Cairo. + // cairo_set_operator(context_, CAIRO_OPERATOR_SOURCE); + const double kRightMarginInInch = 0.25; + const double kBottomMarginInInch = 0.56; + const double kPrintableWidthInInch = + kWidthInInch - kLeftMarginInInch - kRightMarginInInch; + const double kPrintableHeightInInch = + kHeightInInch - kTopMarginInInch - kBottomMarginInInch; + const double kScaledPrintableWidthInPoint = + kPrintableWidthInInch * kDPI * shrink; + const double kScaledPrintableHeightInPoint = + kPrintableHeightInInch * kDPI * shrink; + cairo_rectangle(context_, + kScaledLeftMarginInPoint, + kScaledTopMarginInPoint, + kScaledPrintableWidthInPoint, + kScaledPrintableHeightInPoint); + cairo_fill(context_); + + // Finishing the duplication of current page. + cairo_show_page(context_); + cairo_surface_flush(surface_); + + // Destroy resoreces for current page. + cairo_destroy(page_context_); + page_context_ = NULL; + page_surface_ = NULL; + current_page_.clear(); + + // Restore context's states. + cairo_restore(context_); +} + +void PdfPsMetafile::Close() { + if (surface_ != NULL && IsSurfaceValid(surface_)) { + cairo_surface_finish(surface_); + surface_ = NULL; + } + if (context_ != NULL && IsContextValid(context_)) { + cairo_destroy(context_); + context_ = NULL; + } +} + +unsigned int PdfPsMetafile::GetDataSize() const { + DCHECK(!surface_ && !context_); + + return all_pages_.size(); +} + +void PdfPsMetafile::GetData(void* dst_buffer, size_t dst_buffer_size) const { + DCHECK(!surface_ && !context_); + DCHECK(dst_buffer); + + size_t data_size = GetDataSize(); + if (data_size < dst_buffer_size) + dst_buffer_size = data_size; + memcpy(dst_buffer, all_pages_.data(), dst_buffer_size); +} + +bool PdfPsMetafile::SaveTo(const FilePath& filename) const { + DCHECK(!surface_ && !context_); + + const unsigned int data_size = GetDataSize(); + const unsigned int bytes_written = + file_util::WriteFile(filename, all_pages_.data(), data_size); + if (bytes_written != data_size) { + DLOG(ERROR) << "Failed to save file: " << filename.value(); + return false; + } + + return true; +} + +cairo_status_t PdfPsMetafile::WriteCairoStream(void* dst_buffer, + const unsigned char* src_data, + unsigned int src_data_length) { + DCHECK(dst_buffer); + DCHECK(src_data); + + std::string* buffer = reinterpret_cast(dst_buffer); + buffer->append(reinterpret_cast(src_data), src_data_length); + + return CAIRO_STATUS_SUCCESS; +} + +bool PdfPsMetafile::IsSurfaceValid(cairo_surface_t* surface) const { + return cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS; +} + +bool PdfPsMetafile::IsContextValid(cairo_t* context) const { + return cairo_status(context) == CAIRO_STATUS_SUCCESS; +} + +} // namespace printing + diff --git a/printing/pdf_ps_metafile_linux.h b/printing/pdf_ps_metafile_linux.h new file mode 100644 index 0000000..a49f635 --- /dev/null +++ b/printing/pdf_ps_metafile_linux.h @@ -0,0 +1,107 @@ +// Copyright (c) 2009 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. + +#ifndef PRINTING_PDF_PS_METAFILE_LINUX_H_ +#define PRINTING_PDF_PS_METAFILE_LINUX_H_ + +#include + +#include + +#include "base/basictypes.h" + +class FilePath; + +namespace printing { + +// This class uses Cairo graphics library to generate PostScript/PDF stream +// and stores rendering results in a string buffer. +class PdfPsMetafile { + public: + enum FileFormat { + PDF, + PS, + }; + + // The constructor we should use in the renderer process. + explicit PdfPsMetafile(const FileFormat& format); + + // The constructor we should use in the browser process. + // |src_buffer| should point to the shared memory which stores PDF/PS + // contents generated in the renderer. + PdfPsMetafile(const FileFormat& format, + const void* src_buffer, + size_t src_buffer_size); + + ~PdfPsMetafile(); + + FileFormat GetFileFormat() { return format_; } + + // Prepares a new cairo surface/context for rendering a new page. + bool StartPage(double width, double height); // The unit is pt (=1/72 in). + + // Returns the Cairo context for rendering current page. + cairo_t* GetPageContext() const { return page_context_; } + + // Destroys the surface and the context used in rendering current page. + // The results of current page will be appended into buffer |all_pages_|. + // TODO(myhuang): I plan to also do page setup here (margins, the header + // and the footer). At this moment, only pre-defined margins for US letter + // paper are hard-coded here. + // |shrink| decides the scaling factor to fit raw printing results into + // printable area. + void FinishPage(float shrink); + + // Closes resulting PDF/PS file. No further rendering is allowed. + void Close(); + + // Returns size of PDF/PS contents stored in buffer |all_pages_|. + // This function should ONLY be called after PDF/PS file is closed. + unsigned int GetDataSize() const; + + // Copies PDF/PS contents stored in buffer |all_pages_| into |dst_buffer|. + // This function should ONLY be called after PDF/PS file is closed. + void GetData(void* dst_buffer, size_t dst_buffer_size) const; + + // Saves PDF/PS contents stored in buffer |all_pages_| into |filename| on + // the disk. + // This function should ONLY be called after PDF/PS file is closed. + bool SaveTo(const FilePath& filename) const; + + private: + // Callback function for Cairo to write PDF/PS stream. + // |dst_buffer| is actually a pointer of type `std::string*`. + static cairo_status_t WriteCairoStream(void* dst_buffer, + const unsigned char* src_data, + unsigned int src_data_length); + + // Convenient function to test if |surface| is valid. + bool IsSurfaceValid(cairo_surface_t* surface) const; + + // Convenient function to test if |context| is valid. + bool IsContextValid(cairo_t* context) const; + + FileFormat format_; + + // Cairo surface and context for entire PDF/PS file. + cairo_surface_t* surface_; + cairo_t* context_; + + // Cairo surface and context for current page only. + cairo_surface_t* page_surface_; + cairo_t* page_context_; + + // Buffer stores PDF/PS contents for entire PDF/PS file. + std::string all_pages_; + + // Buffer stores PDF/PS contents for current page only. + std::string current_page_; + + DISALLOW_COPY_AND_ASSIGN(PdfPsMetafile); +}; + +} // namespace printing + +#endif // PRINTING_PDF_PS_METAFILE_LINUX_H_ + diff --git a/printing/pdf_ps_metafile_linux_unittest.cc b/printing/pdf_ps_metafile_linux_unittest.cc new file mode 100644 index 0000000..8728ba3 --- /dev/null +++ b/printing/pdf_ps_metafile_linux_unittest.cc @@ -0,0 +1,122 @@ +// Copyright (c) 2009 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_linux.h" + +#include +#include + +#include "base/file_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +typedef struct _cairo cairo_t; + +TEST(PdfTest, Basic) { + // Tests in-renderer constructor. + printing::PdfPsMetafile pdf(printing::PdfPsMetafile::PDF); + cairo_t* context = pdf.GetPageContext(); + EXPECT_TRUE(context == NULL); + + // Renders page 1. + EXPECT_TRUE(pdf.StartPage(72, 72)); + context = pdf.GetPageContext(); + EXPECT_TRUE(context != NULL); + // In theory, we should use Cairo to draw something on |context|. + pdf.FinishPage(1.5); + context = pdf.GetPageContext(); + EXPECT_TRUE(context == NULL); + + // Renders page 2. + EXPECT_TRUE(pdf.StartPage(64, 64)); + context = pdf.GetPageContext(); + EXPECT_TRUE(context != NULL); + // In theory, we should use Cairo to draw something on |context|. + pdf.FinishPage(0.5); + context = pdf.GetPageContext(); + EXPECT_TRUE(context == NULL); + + // Closes the file. + pdf.Close(); + context = pdf.GetPageContext(); + EXPECT_TRUE(context == NULL); + + // Checks data size. + unsigned int size = pdf.GetDataSize(); + EXPECT_GT(size, 0u); + + // Gets resulting data. + std::vector buffer(size, 0x00); + pdf.GetData(&buffer.front(), size); + + // Tests another constructor. + printing::PdfPsMetafile pdf2(printing::PdfPsMetafile::PDF, + &buffer.front(), + size); + + // Tries to get the first 4 characters from pdf2. + std::vector buffer2(4, 0x00); + pdf2.GetData(&buffer2.front(), 4); + + // Tests if the header begins with "%PDF". + std::string header(&buffer2.front(), 4); + EXPECT_EQ(header.find("%PDF", 0), 0u); + + // Tests if we can save data. + EXPECT_TRUE(pdf.SaveTo(FilePath("/dev/null"))); +} + +TEST(PsTest, Basic) { + // Tests in-renderer constructor. + printing::PdfPsMetafile ps(printing::PdfPsMetafile::PS); + cairo_t* context = ps.GetPageContext(); + EXPECT_TRUE(context == NULL); + + // Renders page 1. + EXPECT_TRUE(ps.StartPage(72, 72)); + context = ps.GetPageContext(); + EXPECT_TRUE(context != NULL); + // In theory, we should use Cairo to draw something on |context|. + ps.FinishPage(1.5); + context = ps.GetPageContext(); + EXPECT_TRUE(context == NULL); + + // Renders page 2. + EXPECT_TRUE(ps.StartPage(64, 64)); + context = ps.GetPageContext(); + EXPECT_TRUE(context != NULL); + // In theory, we should use Cairo to draw something on |context|. + ps.FinishPage(0.5); + context = ps.GetPageContext(); + EXPECT_TRUE(context == NULL); + + // Closes the file. + ps.Close(); + context = ps.GetPageContext(); + EXPECT_TRUE(context == NULL); + + // Checks data size. + unsigned int size = ps.GetDataSize(); + EXPECT_GT(size, 0u); + + // Gets resulting data. + std::vector buffer(size, 0x00); + ps.GetData(&buffer.front(), size); + + // Tests another constructor. + printing::PdfPsMetafile ps2(printing::PdfPsMetafile::PS, + &buffer.front(), + size); + + // Tries to get the first 4 characters from ps2. + std::vector buffer2(4, 0x00); + ps2.GetData(&buffer2.front(), 4); + + // Tests if the header begins with "%!PS". + std::string header(&buffer2.front(), 4); + EXPECT_EQ(header.find("%!PS", 0), 0u); + + // Tests if we can save data. + EXPECT_TRUE(ps.SaveTo(FilePath("/dev/null"))); +} + diff --git a/printing/printing.gyp b/printing/printing.gyp index e199de8..3e40dd1 100644 --- a/printing/printing.gyp +++ b/printing/printing.gyp @@ -40,6 +40,8 @@ 'page_range.h', 'page_setup.cc', 'page_setup.h', + 'pdf_ps_metafile_linux.h', + 'pdf_ps_metafile_linux.cc', 'print_settings.cc', 'print_settings.h', 'printed_document.cc', @@ -90,6 +92,7 @@ 'page_overlays_unittest.cc', 'page_range_unittest.cc', 'page_setup_unittest.cc', + 'pdf_ps_metafile_linux_unittest.cc', 'printing_context_win_unittest.cc', 'run_all_unittests.cc', 'units_unittest.cc', @@ -110,7 +113,15 @@ }, { # else: OS=="win" 'sources/': [['exclude', '_posix_unittest\\.cc$']] }], + ['OS=="linux"', { + 'dependencies': [ + '../base/base.gyp:base_gfx', + ], + 'sources/': [ + ['include', 'pdf_ps_metafile_linux_unittest.cc'], + ] + }], ], }, ], -} \ No newline at end of file +} -- cgit v1.1