summaryrefslogtreecommitdiffstats
path: root/printing
diff options
context:
space:
mode:
authortony@chromium.org <tony@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-25 17:36:37 +0000
committertony@chromium.org <tony@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-25 17:36:37 +0000
commit3cfa15ac25e64425a9550fa8b0f741219fe68006 (patch)
treebcfa22ea62dd1ecd06c7b0ff11ddebb9bea28514 /printing
parent9db9a8ed63bf7d194e687151d86f47eab7292206 (diff)
downloadchromium_src-3cfa15ac25e64425a9550fa8b0f741219fe68006.zip
chromium_src-3cfa15ac25e64425a9550fa8b0f741219fe68006.tar.gz
chromium_src-3cfa15ac25e64425a9550fa8b0f741219fe68006.tar.bz2
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 <minyu.huang@gmail.com> 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
Diffstat (limited to 'printing')
-rw-r--r--printing/native_metafile.h12
-rw-r--r--printing/pdf_ps_metafile_linux.cc288
-rw-r--r--printing/pdf_ps_metafile_linux.h107
-rw-r--r--printing/pdf_ps_metafile_linux_unittest.cc122
-rw-r--r--printing/printing.gyp13
5 files changed, 538 insertions, 4 deletions
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 <stdio.h>
+
+#include <cairo-pdf.h>
+#include <cairo-ps.h>
+
+#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<const char*>(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,
+ &current_page_,
+ width_in_points,
+ height_in_points);
+ }
+ break;
+
+ case PS: {
+ page_surface_ = cairo_ps_surface_create_for_stream(WriteCairoStream,
+ &current_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<std::string* >(dst_buffer);
+ buffer->append(reinterpret_cast<const char*>(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 <cairo.h>
+
+#include <string>
+
+#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 <string>
+#include <vector>
+
+#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<char> 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<char> 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<char> 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<char> 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
+}