diff options
author | sverrir@google.com <sverrir@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-06 15:25:50 +0000 |
---|---|---|
committer | sverrir@google.com <sverrir@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-07-06 15:25:50 +0000 |
commit | 0e0fca32b226a29a774728b642848bfdd732f791 (patch) | |
tree | 70547f886163e2a364ed47a8d5c85765d48f0e12 /printing | |
parent | d09e2889c21d0a420b70680291906e4a17fb8503 (diff) | |
download | chromium_src-0e0fca32b226a29a774728b642848bfdd732f791.zip chromium_src-0e0fca32b226a29a774728b642848bfdd732f791.tar.gz chromium_src-0e0fca32b226a29a774728b642848bfdd732f791.tar.bz2 |
Move Emf class to the printing library. Also creates a platform agnostic NativeMetafile definition to ease platform porting.
BUG=none
TEST=none (No functional change)
Review URL: http://codereview.chromium.org/149181
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19943 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'printing')
-rw-r--r-- | printing/emf_win.cc | 317 | ||||
-rw-r--r-- | printing/emf_win.h | 181 | ||||
-rw-r--r-- | printing/emf_win_unittest.cc | 117 | ||||
-rw-r--r-- | printing/native_metafile.h | 36 | ||||
-rw-r--r-- | printing/printing.gyp | 15 |
5 files changed, 666 insertions, 0 deletions
diff --git a/printing/emf_win.cc b/printing/emf_win.cc new file mode 100644 index 0000000..42380443 --- /dev/null +++ b/printing/emf_win.cc @@ -0,0 +1,317 @@ +// Copyright (c) 2006-2008 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/emf_win.h" + +#include "base/gfx/rect.h" +#include "base/logging.h" + +namespace printing { + +Emf::Emf() : emf_(NULL), hdc_(NULL) { +} + +Emf::~Emf() { + CloseEmf(); + DCHECK(!emf_ && !hdc_); +} + +bool Emf::CreateDc(HDC sibling, const RECT* rect) { + DCHECK(!emf_ && !hdc_); + hdc_ = CreateEnhMetaFile(sibling, NULL, rect, NULL); + DCHECK(hdc_); + return hdc_ != NULL; +} + +bool Emf::CreateFromData(const void* buffer, size_t size) { + DCHECK(!emf_ && !hdc_); + emf_ = SetEnhMetaFileBits(static_cast<unsigned>(size), + reinterpret_cast<const BYTE*>(buffer)); + DCHECK(emf_); + return emf_ != NULL; +} + +bool Emf::CloseDc() { + DCHECK(!emf_ && hdc_); + emf_ = CloseEnhMetaFile(hdc_); + DCHECK(emf_); + hdc_ = NULL; + return emf_ != NULL; +} + +void Emf::CloseEmf() { + DCHECK(!hdc_); + if (emf_) { + DeleteEnhMetaFile(emf_); + emf_ = NULL; + } +} + +bool Emf::Playback(HDC hdc, const RECT* rect) const { + DCHECK(emf_ && !hdc_); + RECT bounds; + if (!rect) { + // Get the natural bounds of the EMF buffer. + bounds = GetBounds().ToRECT(); + rect = &bounds; + } + return PlayEnhMetaFile(hdc, emf_, rect) != 0; +} + +bool Emf::SafePlayback(HDC context) const { + DCHECK(emf_ && !hdc_); + XFORM base_matrix; + if (!GetWorldTransform(context, &base_matrix)) { + NOTREACHED(); + return false; + } + + return EnumEnhMetaFile(context, + emf_, + &Emf::SafePlaybackProc, + reinterpret_cast<void*>(&base_matrix), + &GetBounds().ToRECT()) != 0; +} + +gfx::Rect Emf::GetBounds() const { + DCHECK(emf_ && !hdc_); + ENHMETAHEADER header; + if (GetEnhMetaFileHeader(emf_, sizeof(header), &header) != sizeof(header)) { + NOTREACHED(); + return gfx::Rect(); + } + if (header.rclBounds.left == 0 && + header.rclBounds.top == 0 && + header.rclBounds.right == -1 && + header.rclBounds.bottom == -1) { + // A freshly created EMF buffer that has no drawing operation has invalid + // bounds. Instead of having an (0,0) size, it has a (-1,-1) size. Detect + // this special case and returns an empty Rect instead of an invalid one. + return gfx::Rect(); + } + return gfx::Rect(header.rclBounds.left, + header.rclBounds.top, + header.rclBounds.right - header.rclBounds.left, + header.rclBounds.bottom - header.rclBounds.top); +} + +unsigned Emf::GetDataSize() const { + DCHECK(emf_ && !hdc_); + return GetEnhMetaFileBits(emf_, 0, NULL); +} + +bool Emf::GetData(void* buffer, size_t size) const { + DCHECK(emf_ && !hdc_); + DCHECK(buffer && size); + unsigned size2 = GetEnhMetaFileBits(emf_, static_cast<unsigned>(size), + reinterpret_cast<BYTE*>(buffer)); + DCHECK(size2 == size); + return size2 == size && size2 != 0; +} + +bool Emf::GetData(std::vector<uint8>* buffer) const { + unsigned size = GetDataSize(); + if (!size) + return false; + + buffer->resize(size); + if (!GetData(&buffer->front(), size)) + return false; + return true; +} + +bool Emf::SaveTo(const std::wstring& filename) const { + HANDLE file = CreateFile(filename.c_str(), GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, + CREATE_ALWAYS, 0, NULL); + if (file == INVALID_HANDLE_VALUE) + return false; + + bool success = false; + std::vector<uint8> buffer; + if (GetData(&buffer)) { + DWORD written = 0; + if (WriteFile(file, &*buffer.begin(), static_cast<DWORD>(buffer.size()), + &written, NULL) && + written == buffer.size()) { + success = true; + } + } + CloseHandle(file); + return success; +} + +int CALLBACK Emf::SafePlaybackProc(HDC hdc, + HANDLETABLE* handle_table, + const ENHMETARECORD* record, + int objects_count, + LPARAM param) { + const XFORM* base_matrix = reinterpret_cast<const XFORM*>(param); + EnumerationContext context; + context.handle_table = handle_table; + context.objects_count = objects_count; + context.hdc = hdc; + Record record_instance(&context, record); + bool success = record_instance.SafePlayback(base_matrix); + DCHECK(success); + return 1; +} + +Emf::Record::Record() { +} + +Emf::Record::Record(const EnumerationContext* context, + const ENHMETARECORD* record) + : record_(record), + context_(context) { + DCHECK(record_); +} + +bool Emf::Record::Play() const { + return 0 != PlayEnhMetaFileRecord(context_->hdc, + context_->handle_table, + record_, + context_->objects_count); +} + +bool Emf::Record::SafePlayback(const XFORM* base_matrix) const { + // For EMF field description, see [MS-EMF] Enhanced Metafile Format + // Specification. + // + // This is the second major EMF breakage I get; the first one being + // SetDCBrushColor/SetDCPenColor/DC_PEN/DC_BRUSH being silently ignored. + // + // This function is the guts of the fix for bug 1186598. Some printer drivers + // somehow choke on certain EMF records, but calling the corresponding + // function directly on the printer HDC is fine. Still, playing the EMF record + // fails. Go figure. + // + // The main issue is that SetLayout is totally unsupported on these printers + // (HP 4500/4700). I used to call SetLayout and I stopped. I found out this is + // not sufficient because GDI32!PlayEnhMetaFile internally calls SetLayout(!) + // Damn. + // + // So I resorted to manually parse the EMF records and play them one by one. + // The issue with this method compared to using PlayEnhMetaFile to play back + // an EMF buffer is that the later silently fixes the matrix to take in + // account the matrix currently loaded at the time of the call. + // The matrix magic is done transparently when using PlayEnhMetaFile but since + // I'm processing one field at a time, I need to do the fixup myself. Note + // that PlayEnhMetaFileRecord doesn't fix the matrix correctly even when + // called inside an EnumEnhMetaFile loop. Go figure (bis). + // + // So when I see a EMR_SETWORLDTRANSFORM and EMR_MODIFYWORLDTRANSFORM, I need + // to fix the matrix according to the matrix previously loaded before playing + // back the buffer. Otherwise, the previously loaded matrix would be ignored + // and the EMF buffer would always be played back at its native resolution. + // Duh. + // + // I also use this opportunity to skip over eventual EMR_SETLAYOUT record that + // could remain. + // + // Note: I should probably care about view ports and clipping, eventually. + bool res; + switch (record()->iType) { + case EMR_SETWORLDTRANSFORM: { + DCHECK_EQ(record()->nSize, sizeof(DWORD) * 2 + sizeof(XFORM)); + const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm); + HDC hdc = context_->hdc; + if (base_matrix) { + res = 0 != SetWorldTransform(hdc, base_matrix) && + ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY); + } else { + res = 0 != SetWorldTransform(hdc, xform); + } + break; + } + case EMR_MODIFYWORLDTRANSFORM: { + DCHECK_EQ(record()->nSize, + sizeof(DWORD) * 2 + sizeof(XFORM) + sizeof(DWORD)); + const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm); + const DWORD* option = reinterpret_cast<const DWORD*>(xform + 1); + HDC hdc = context_->hdc; + switch (*option) { + case MWT_IDENTITY: + if (base_matrix) { + res = 0 != SetWorldTransform(hdc, base_matrix); + } else { + res = 0 != ModifyWorldTransform(hdc, xform, MWT_IDENTITY); + } + break; + case MWT_LEFTMULTIPLY: + case MWT_RIGHTMULTIPLY: + res = 0 != ModifyWorldTransform(hdc, xform, *option); + break; + case 4: // MWT_SET + if (base_matrix) { + res = 0 != SetWorldTransform(hdc, base_matrix) && + ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY); + } else { + res = 0 != SetWorldTransform(hdc, xform); + } + break; + default: + res = false; + break; + } + break; + } + case EMR_SETLAYOUT: + // Ignore it. + res = true; + break; + default: { + res = Play(); + break; + } + } + return res; +} + +Emf::Enumerator::Enumerator(const Emf& emf, HDC context, const RECT* rect) { + context_.handle_table = NULL; + context_.objects_count = 0; + context_.hdc = NULL; + items_.clear(); + if (!EnumEnhMetaFile(context, + emf.emf(), + &Emf::Enumerator::EnhMetaFileProc, + reinterpret_cast<void*>(this), + rect)) { + NOTREACHED(); + items_.clear(); + } + DCHECK_EQ(context_.hdc, context); +} + +Emf::Enumerator::const_iterator Emf::Enumerator::begin() const { + return items_.begin(); +} + +Emf::Enumerator::const_iterator Emf::Enumerator::end() const { + return items_.end(); +} + +int CALLBACK Emf::Enumerator::EnhMetaFileProc(HDC hdc, + HANDLETABLE* handle_table, + const ENHMETARECORD* record, + int objects_count, + LPARAM param) { + Enumerator& emf = *reinterpret_cast<Enumerator*>(param); + if (!emf.context_.handle_table) { + DCHECK(!emf.context_.handle_table); + DCHECK(!emf.context_.objects_count); + emf.context_.handle_table = handle_table; + emf.context_.objects_count = objects_count; + emf.context_.hdc = hdc; + } else { + DCHECK_EQ(emf.context_.handle_table, handle_table); + DCHECK_EQ(emf.context_.objects_count, objects_count); + DCHECK_EQ(emf.context_.hdc, hdc); + } + emf.items_.push_back(Record(&emf.context_, record)); + return 1; +} + +} // namespace printing diff --git a/printing/emf_win.h b/printing/emf_win.h new file mode 100644 index 0000000..7be747a --- /dev/null +++ b/printing/emf_win.h @@ -0,0 +1,181 @@ +// Copyright (c) 2006-2008 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_EMF_WIN_H__ +#define PRINTING_EMF_WIN_H__ + +#include <windows.h> +#include <vector> + +#include "base/basictypes.h" + +namespace gfx { +class Rect; +} + +namespace printing { + +// Simple wrapper class that manage an EMF data stream and its virtual HDC. +class Emf { + public: + class Record; + class Enumerator; + struct EnumerationContext; + + Emf(); + ~Emf(); + + // Generates a virtual HDC that will record every GDI commands and compile it + // in a EMF data stream. + // hdc is used to setup the default DPI and color settings. hdc is optional. + // rect specifies the dimensions (in .01-millimeter units) of the EMF. rect is + // optional. + bool CreateDc(HDC sibling, const RECT* rect); + + // Load a EMF data stream. buffer contains EMF data. + bool CreateFromData(const void* buffer, size_t size); + + // TODO(maruel): CreateFromFile(). If ever used. Maybe users would like to + // have the ability to save web pages to an EMF file? Afterward, it is easy to + // convert to PDF or PS. + + // Closes the HDC created by CreateDc() and generates the compiled EMF + // data. + bool CloseDc(); + + // Closes the EMF data handle when it is not needed anymore. + void CloseEmf(); + + // "Plays" the EMF buffer in a HDC. It is the same effect as calling the + // original GDI function that were called when recording the EMF. |rect| is in + // "logical units" and is optional. If |rect| is NULL, the natural EMF bounds + // are used. + // Note: Windows has been known to have stack buffer overflow in its GDI + // functions, whether used directly or indirectly through precompiled EMF + // data. We have to accept the risk here. Since it is used only for printing, + // it requires user intervention. + bool Playback(HDC hdc, const RECT* rect) const; + + // The slow version of Playback(). It enumerates all the records and play them + // back in the HDC. The trick is that it skip over the records known to have + // issue with some printers. See Emf::Record::SafePlayback implementation for + // details. + bool SafePlayback(HDC hdc) const; + + // Retrieves the bounds of the painted area by this EMF buffer. This value + // should be passed to Playback to keep the exact same size. + gfx::Rect GetBounds() const; + + // Retrieves the EMF stream size. + unsigned GetDataSize() const; + + // Retrieves the EMF stream. + bool GetData(void* buffer, size_t size) const; + + // Retrieves the EMF stream. It is an helper function. + bool GetData(std::vector<uint8>* buffer) const; + + HENHMETAFILE emf() const { + return emf_; + } + + HDC hdc() const { + return hdc_; + } + + // Saves the EMF data to a file as-is. It is recommended to use the .emf file + // extension but it is not enforced. This function synchronously writes to the + // file. For testing only. + bool SaveTo(const std::wstring& filename) const; + + private: + // Playbacks safely one EMF record. + static int CALLBACK SafePlaybackProc(HDC hdc, + HANDLETABLE* handle_table, + const ENHMETARECORD* record, + int objects_count, + LPARAM param); + + // Compiled EMF data handle. + HENHMETAFILE emf_; + + // Valid when generating EMF data through a virtual HDC. + HDC hdc_; + + DISALLOW_EVIL_CONSTRUCTORS(Emf); +}; + +struct Emf::EnumerationContext { + HANDLETABLE* handle_table; + int objects_count; + HDC hdc; +}; + +// One EMF record. It keeps pointers to the EMF buffer held by Emf::emf_. +// The entries become invalid once Emf::CloseEmf() is called. +class Emf::Record { + public: + Record(); + + // Plays the record. + bool Play() const; + + // Plays the record working around quirks with SetLayout, + // SetWorldTransform and ModifyWorldTransform. See implementation for details. + bool SafePlayback(const XFORM* base_matrix) const; + + // Access the underlying EMF record. + const ENHMETARECORD* record() const { return record_; } + + protected: + Record(const EnumerationContext* context, + const ENHMETARECORD* record); + + private: + friend class Emf; + friend class Enumerator; + const ENHMETARECORD* record_; + const EnumerationContext* context_; +}; + +// Retrieves individual records out of a Emf buffer. The main use is to skip +// over records that are unsupported on a specific printer or to play back +// only a part of an EMF buffer. +class Emf::Enumerator { + public: + // Iterator type used for iterating the records. + typedef std::vector<Record>::const_iterator const_iterator; + + // Enumerates the records at construction time. |hdc| and |rect| are + // both optional at the same time or must both be valid. + // Warning: |emf| must be kept valid for the time this object is alive. + Enumerator(const Emf& emf, HDC hdc, const RECT* rect); + + // Retrieves the first Record. + const_iterator begin() const; + + // Retrieves the end of the array. + const_iterator end() const; + + private: + // Processes one EMF record and saves it in the items_ array. + static int CALLBACK EnhMetaFileProc(HDC hdc, + HANDLETABLE* handle_table, + const ENHMETARECORD* record, + int objects_count, + LPARAM param); + + // The collection of every EMF records in the currently loaded EMF buffer. + // Initialized by Enumerate(). It keeps pointers to the EMF buffer held by + // Emf::emf_. The entries become invalid once Emf::CloseEmf() is called. + std::vector<Record> items_; + + EnumerationContext context_; + + DISALLOW_EVIL_CONSTRUCTORS(Enumerator); +}; + +} // namespace printing + +#endif // PRINTING_EMF_WIN_H__ diff --git a/printing/emf_win_unittest.cc b/printing/emf_win_unittest.cc new file mode 100644 index 0000000..ab817db --- /dev/null +++ b/printing/emf_win_unittest.cc @@ -0,0 +1,117 @@ +// Copyright (c) 2006-2008 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/emf_win.h" + +// For quick access. +#include <wingdi.h> + +#include "base/basictypes.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// This test is automatically disabled if no printer named "UnitTest Printer" is +// available. +class EmfPrintingTest : public testing::Test { + public: + typedef testing::Test Parent; + static bool IsTestCaseDisabled() { + // It is assumed this printer is a HP Color LaserJet 4550 PCL or 4700. + HDC hdc = CreateDC(L"WINSPOOL", L"UnitTest Printer", NULL, NULL); + if (!hdc) + return true; + DeleteDC(hdc); + return false; + } +}; + +} // namespace + +TEST(EmfTest, DC) { + static const int EMF_HEADER_SIZE = 128; + + // Simplest use case. + printing::Emf emf; + RECT rect = {100, 100, 200, 200}; + HDC hdc = CreateCompatibleDC(NULL); + EXPECT_TRUE(hdc != NULL); + EXPECT_TRUE(emf.CreateDc(hdc, &rect)); + EXPECT_TRUE(emf.hdc() != NULL); + // In theory, you'd use the HDC with GDI functions here. + EXPECT_TRUE(emf.CloseDc()); + unsigned size = emf.GetDataSize(); + EXPECT_EQ(size, EMF_HEADER_SIZE); + std::vector<BYTE> data; + EXPECT_TRUE(emf.GetData(&data)); + EXPECT_EQ(data.size(), size); + emf.CloseEmf(); + EXPECT_TRUE(DeleteDC(hdc)); + + // Playback the data. + hdc = CreateCompatibleDC(NULL); + EXPECT_TRUE(hdc); + EXPECT_TRUE(emf.CreateFromData(&data.front(), size)); + RECT output_rect = {0, 0, 10, 10}; + EXPECT_TRUE(emf.Playback(hdc, &output_rect)); + EXPECT_TRUE(DeleteDC(hdc)); +} + +// TODO(sverrir): Re-enable after win_printing_context has been moved here. +/* +#include "chrome/browser/printing/win_printing_context.h" +#include "chrome/common/chrome_paths.h" + +// Disabled if no "UnitTest printer" exist. Useful to reproduce bug 1186598. +TEST_F(EmfPrintingTest, Enumerate) { + if (IsTestCaseDisabled()) + return; + + printing::PrintSettings settings; + + // My test case is a HP Color LaserJet 4550 PCL. + settings.set_device_name(L"UnitTest Printer"); + + // Initialize it. + printing::PrintingContext context; + EXPECT_EQ(context.InitWithSettings(settings), printing::PrintingContext::OK); + + std::wstring test_file; + PathService::Get(chrome::DIR_TEST_DATA, &test_file); + + // Load any EMF with an image. + printing::Emf emf; + file_util::AppendToPath(&test_file, L"printing"); + file_util::AppendToPath(&test_file, L"test4.emf"); + std::string emf_data; + file_util::ReadFileToString(test_file, &emf_data); + ASSERT_TRUE(emf_data.size()); + EXPECT_TRUE(emf.CreateFromData(&emf_data[0], emf_data.size())); + + // This will print to file. The reason is that when running inside a + // unit_test, printing::PrintingContext automatically dumps its files to the + // current directory. + // TODO(maruel): Clean the .PRN file generated in current directory. + context.NewDocument(L"EmfTest.Enumerate"); + context.NewPage(); + // Process one at a time. + printing::Emf::Enumerator emf_enum(emf, context.context(), + &emf.GetBounds().ToRECT()); + for (printing::Emf::Enumerator::const_iterator itr = emf_enum.begin(); + itr != emf_enum.end(); + ++itr) { + // To help debugging. + ptrdiff_t index = itr - emf_enum.begin(); + // If you get this assert, you need to lookup iType in wingdi.h. It starts + // with EMR_HEADER. + EMR_HEADER; + EXPECT_TRUE(itr->SafePlayback(NULL)) << + " index: " << index << " type: " << itr->record()->iType; + } + context.PageDone(); + context.DocumentDone(); +} +*/ diff --git a/printing/native_metafile.h b/printing/native_metafile.h new file mode 100644 index 0000000..6963089 --- /dev/null +++ b/printing/native_metafile.h @@ -0,0 +1,36 @@ +// 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_NATIVE_METAFILE_H__ +#define PRINTING_NATIVE_METAFILE_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 +// platforms that have the same interface as Emf or if we change Emf to support +// multiple platforms (and rename to NativeMetafile). + + +#if defined(OS_WIN) + +#include "printing/emf_win.h" + +namespace printing { + +typedef Emf NativeMetafile; + +} // namespace printing + +#elif defined(OS_MACOSX) + +// TODO(port): Printing using PDF? + +#elif defined(OS_LINUX) + +// TODO(port): Printing using PostScript? + +#endif + + +#endif // PRINTING_NATIVE_METAFILE_H__ diff --git a/printing/printing.gyp b/printing/printing.gyp index 1980417..899bded 100644 --- a/printing/printing.gyp +++ b/printing/printing.gyp @@ -15,12 +15,17 @@ 'type': '<(library)', 'dependencies': [ '../base/base.gyp:base', + '../base/base.gyp:base_gfx', + ], 'msvs_guid': '9E5416B9-B91B-4029-93F4-102C1AD5CAF4', 'include_dirs': [ '..', ], 'sources': [ + 'emf_win.cc', + 'emf_win.h', + 'native_metafile.h', 'units.cc', 'units.h', ], @@ -49,8 +54,18 @@ '../testing/gtest.gyp:gtestmain', ], 'sources': [ + 'emf_win_unittest.cc', 'units_unittest.cc', ], + 'conditions': [ + ['OS!="linux"', {'sources/': [['exclude', '_linux_unittest\\.cc$']]}], + ['OS!="mac"', {'sources/': [['exclude', '_mac_unittest\\.(cc|mm?)$']]}], + ['OS!="win"', { + 'sources/': [['exclude', '_win_unittest\\.cc$']] + }, { # else: OS=="win" + 'sources/': [['exclude', '_posix_unittest\\.cc$']] + }], + ], }, ], } |