summaryrefslogtreecommitdiff
path: root/chromium/printing/emf_win.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/printing/emf_win.cc')
-rw-r--r--chromium/printing/emf_win.cc678
1 files changed, 678 insertions, 0 deletions
diff --git a/chromium/printing/emf_win.cc b/chromium/printing/emf_win.cc
new file mode 100644
index 00000000000..98c8f8ffa6f
--- /dev/null
+++ b/chromium/printing/emf_win.cc
@@ -0,0 +1,678 @@
+// Copyright (c) 2012 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/files/file_path.h"
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/win/scoped_gdi_object.h"
+#include "base/win/scoped_hdc.h"
+#include "base/win/scoped_select_object.h"
+#include "skia/ext/vector_platform_device_emf_win.h"
+#include "third_party/skia/include/core/SkBitmap.h"
+#include "ui/gfx/codec/jpeg_codec.h"
+#include "ui/gfx/codec/png_codec.h"
+#include "ui/gfx/gdi_util.h"
+#include "ui/gfx/rect.h"
+#include "ui/gfx/size.h"
+
+namespace {
+
+const int kCustomGdiCommentSignature = 0xdeadbabe;
+struct PageBreakRecord {
+ int signature;
+ enum PageBreakType {
+ START_PAGE,
+ END_PAGE,
+ } type;
+ explicit PageBreakRecord(PageBreakType type_in)
+ : signature(kCustomGdiCommentSignature), type(type_in) {
+ }
+ bool IsValid() const {
+ return (signature == kCustomGdiCommentSignature) &&
+ (type >= START_PAGE) && (type <= END_PAGE);
+ }
+};
+
+int CALLBACK IsAlphaBlendUsedEnumProc(HDC,
+ HANDLETABLE*,
+ const ENHMETARECORD *record,
+ int,
+ LPARAM data) {
+ bool* result = reinterpret_cast<bool*>(data);
+ if (!result)
+ return 0;
+ switch (record->iType) {
+ case EMR_ALPHABLEND: {
+ *result = true;
+ return 0;
+ break;
+ }
+ }
+ return 1;
+}
+
+int CALLBACK RasterizeAlphaBlendProc(HDC metafile_dc,
+ HANDLETABLE* handle_table,
+ const ENHMETARECORD *record,
+ int num_objects,
+ LPARAM data) {
+ HDC bitmap_dc = *reinterpret_cast<HDC*>(data);
+ // Play this command to the bitmap DC.
+ ::PlayEnhMetaFileRecord(bitmap_dc, handle_table, record, num_objects);
+ switch (record->iType) {
+ case EMR_ALPHABLEND: {
+ const EMRALPHABLEND* alpha_blend =
+ reinterpret_cast<const EMRALPHABLEND*>(record);
+ // Don't modify transformation here.
+ // Old implementation did reset transformations for DC to identity matrix.
+ // That was not correct and cause some bugs, like unexpected cropping.
+ // EMRALPHABLEND is rendered into bitmap and metafile contexts with
+ // current transformation. If we don't touch them here BitBlt will copy
+ // same areas.
+ ::BitBlt(metafile_dc,
+ alpha_blend->xDest,
+ alpha_blend->yDest,
+ alpha_blend->cxDest,
+ alpha_blend->cyDest,
+ bitmap_dc,
+ alpha_blend->xDest,
+ alpha_blend->yDest,
+ SRCCOPY);
+ break;
+ }
+ case EMR_CREATEBRUSHINDIRECT:
+ case EMR_CREATECOLORSPACE:
+ case EMR_CREATECOLORSPACEW:
+ case EMR_CREATEDIBPATTERNBRUSHPT:
+ case EMR_CREATEMONOBRUSH:
+ case EMR_CREATEPALETTE:
+ case EMR_CREATEPEN:
+ case EMR_DELETECOLORSPACE:
+ case EMR_DELETEOBJECT:
+ case EMR_EXTCREATEFONTINDIRECTW:
+ // Play object creation command only once.
+ break;
+
+ default:
+ // Play this command to the metafile DC.
+ ::PlayEnhMetaFileRecord(metafile_dc, handle_table, record, num_objects);
+ break;
+ }
+ return 1; // Continue enumeration
+}
+
+// Bitmapt for rasterization.
+class RasterBitmap {
+ public:
+ explicit RasterBitmap(const gfx::Size& raster_size)
+ : saved_object_(NULL) {
+ context_.Set(::CreateCompatibleDC(NULL));
+ if (!context_) {
+ NOTREACHED() << "Bitmap DC creation failed";
+ return;
+ }
+ ::SetGraphicsMode(context_, GM_ADVANCED);
+ void* bits = NULL;
+ gfx::Rect bitmap_rect(raster_size);
+ gfx::CreateBitmapHeader(raster_size.width(), raster_size.height(),
+ &header_.bmiHeader);
+ bitmap_.Set(::CreateDIBSection(context_, &header_, DIB_RGB_COLORS, &bits,
+ NULL, 0));
+ if (!bitmap_)
+ NOTREACHED() << "Raster bitmap creation for printing failed";
+
+ saved_object_ = ::SelectObject(context_, bitmap_);
+ RECT rect = bitmap_rect.ToRECT();
+ ::FillRect(context_, &rect,
+ static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH)));
+
+ }
+
+ ~RasterBitmap() {
+ ::SelectObject(context_, saved_object_);
+ }
+
+ HDC context() const {
+ return context_;
+ }
+
+ base::win::ScopedCreateDC context_;
+ BITMAPINFO header_;
+ base::win::ScopedBitmap bitmap_;
+ HGDIOBJ saved_object_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RasterBitmap);
+};
+
+
+
+} // namespace
+
+namespace printing {
+
+bool DIBFormatNativelySupported(HDC dc, uint32 escape, const BYTE* bits,
+ int size) {
+ BOOL supported = FALSE;
+ if (ExtEscape(dc, QUERYESCSUPPORT, sizeof(escape),
+ reinterpret_cast<LPCSTR>(&escape), 0, 0) > 0) {
+ ExtEscape(dc, escape, size, reinterpret_cast<LPCSTR>(bits),
+ sizeof(supported), reinterpret_cast<LPSTR>(&supported));
+ }
+ return !!supported;
+}
+
+Emf::Emf() : emf_(NULL), hdc_(NULL), page_count_(0) {
+}
+
+Emf::~Emf() {
+ DCHECK(!hdc_);
+ if (emf_)
+ DeleteEnhMetaFile(emf_);
+}
+
+bool Emf::InitToFile(const base::FilePath& metafile_path) {
+ DCHECK(!emf_ && !hdc_);
+ hdc_ = CreateEnhMetaFile(NULL, metafile_path.value().c_str(), NULL, NULL);
+ DCHECK(hdc_);
+ return hdc_ != NULL;
+}
+
+bool Emf::InitFromFile(const base::FilePath& metafile_path) {
+ DCHECK(!emf_ && !hdc_);
+ emf_ = GetEnhMetaFile(metafile_path.value().c_str());
+ DCHECK(emf_);
+ return emf_ != NULL;
+}
+
+bool Emf::Init() {
+ DCHECK(!emf_ && !hdc_);
+ hdc_ = CreateEnhMetaFile(NULL, NULL, NULL, NULL);
+ DCHECK(hdc_);
+ return hdc_ != NULL;
+}
+
+bool Emf::InitFromData(const void* src_buffer, uint32 src_buffer_size) {
+ DCHECK(!emf_ && !hdc_);
+ emf_ = SetEnhMetaFileBits(src_buffer_size,
+ reinterpret_cast<const BYTE*>(src_buffer));
+ return emf_ != NULL;
+}
+
+bool Emf::FinishDocument() {
+ DCHECK(!emf_ && hdc_);
+ emf_ = CloseEnhMetaFile(hdc_);
+ DCHECK(emf_);
+ hdc_ = NULL;
+ return 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 = GetPageBounds(1).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;
+ }
+ Emf::EnumerationContext playback_context;
+ playback_context.base_matrix = &base_matrix;
+ RECT rect = GetPageBounds(1).ToRECT();
+ return EnumEnhMetaFile(context,
+ emf_,
+ &Emf::SafePlaybackProc,
+ reinterpret_cast<void*>(&playback_context),
+ &rect) != 0;
+}
+
+gfx::Rect Emf::GetPageBounds(unsigned int page_number) const {
+ DCHECK(emf_ && !hdc_);
+ DCHECK_EQ(1U, page_number);
+ ENHMETAHEADER header;
+ if (GetEnhMetaFileHeader(emf_, sizeof(header), &header) != sizeof(header)) {
+ NOTREACHED();
+ return gfx::Rect();
+ }
+ // Add 1 to right and bottom because it's inclusive rectangle.
+ // See ENHMETAHEADER.
+ return gfx::Rect(header.rclBounds.left,
+ header.rclBounds.top,
+ header.rclBounds.right - header.rclBounds.left + 1,
+ header.rclBounds.bottom - header.rclBounds.top + 1);
+}
+
+uint32 Emf::GetDataSize() const {
+ DCHECK(emf_ && !hdc_);
+ return GetEnhMetaFileBits(emf_, 0, NULL);
+}
+
+bool Emf::GetData(void* buffer, uint32 size) const {
+ DCHECK(emf_ && !hdc_);
+ DCHECK(buffer && size);
+ uint32 size2 =
+ GetEnhMetaFileBits(emf_, size, reinterpret_cast<BYTE*>(buffer));
+ DCHECK(size2 == size);
+ return size2 == size && size2 != 0;
+}
+
+bool Emf::GetDataAsVector(std::vector<uint8>* buffer) const {
+ uint32 size = GetDataSize();
+ if (!size)
+ return false;
+
+ buffer->resize(size);
+ if (!GetData(&buffer->front(), size))
+ return false;
+ return true;
+}
+
+bool Emf::SaveTo(const base::FilePath& file_path) const {
+ HANDLE file = CreateFile(file_path.value().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 (GetDataAsVector(&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) {
+ Emf::EnumerationContext* context =
+ reinterpret_cast<Emf::EnumerationContext*>(param);
+ context->handle_table = handle_table;
+ context->objects_count = objects_count;
+ context->hdc = hdc;
+ Record record_instance(record);
+ bool success = record_instance.SafePlayback(context);
+ DCHECK(success);
+ return 1;
+}
+
+Emf::EnumerationContext::EnumerationContext() {
+ memset(this, 0, sizeof(*this));
+}
+
+Emf::Record::Record(const ENHMETARECORD* record)
+ : record_(record) {
+ DCHECK(record_);
+}
+
+bool Emf::Record::Play(Emf::EnumerationContext* context) const {
+ return 0 != PlayEnhMetaFileRecord(context->hdc,
+ context->handle_table,
+ record_,
+ context->objects_count);
+}
+
+bool Emf::Record::SafePlayback(Emf::EnumerationContext* context) 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.
+ //
+ // Another tweak we make is for JPEGs/PNGs in calls to StretchDIBits.
+ // (Our Pepper plugin code uses a JPEG). If the printer does not support
+ // JPEGs/PNGs natively we decompress the JPEG/PNG and then set it to the
+ // device.
+ // TODO(sanjeevr): We should also add JPEG/PNG support for SetSIBitsToDevice
+ //
+ // We also process any custom EMR_GDICOMMENT records which are our
+ // placeholders for StartPage and EndPage.
+ // Note: I should probably care about view ports and clipping, eventually.
+ bool res = false;
+ const XFORM* base_matrix = context->base_matrix;
+ switch (record()->iType) {
+ case EMR_STRETCHDIBITS: {
+ const EMRSTRETCHDIBITS * sdib_record =
+ reinterpret_cast<const EMRSTRETCHDIBITS*>(record());
+ const BYTE* record_start = reinterpret_cast<const BYTE *>(record());
+ const BITMAPINFOHEADER *bmih =
+ reinterpret_cast<const BITMAPINFOHEADER *>(record_start +
+ sdib_record->offBmiSrc);
+ const BYTE* bits = record_start + sdib_record->offBitsSrc;
+ bool play_normally = true;
+ res = false;
+ HDC hdc = context->hdc;
+ scoped_ptr<SkBitmap> bitmap;
+ if (bmih->biCompression == BI_JPEG) {
+ if (!DIBFormatNativelySupported(hdc, CHECKJPEGFORMAT, bits,
+ bmih->biSizeImage)) {
+ play_normally = false;
+ bitmap.reset(gfx::JPEGCodec::Decode(bits, bmih->biSizeImage));
+ }
+ } else if (bmih->biCompression == BI_PNG) {
+ if (!DIBFormatNativelySupported(hdc, CHECKPNGFORMAT, bits,
+ bmih->biSizeImage)) {
+ play_normally = false;
+ bitmap.reset(new SkBitmap());
+ gfx::PNGCodec::Decode(bits, bmih->biSizeImage, bitmap.get());
+ }
+ }
+ if (!play_normally) {
+ DCHECK(bitmap.get());
+ if (bitmap.get()) {
+ SkAutoLockPixels lock(*bitmap.get());
+ DCHECK_EQ(bitmap->config(), SkBitmap::kARGB_8888_Config);
+ const uint32_t* pixels =
+ static_cast<const uint32_t*>(bitmap->getPixels());
+ if (pixels == NULL) {
+ NOTREACHED();
+ return false;
+ }
+ BITMAPINFOHEADER bmi = {0};
+ gfx::CreateBitmapHeader(bitmap->width(), bitmap->height(), &bmi);
+ res = (0 != StretchDIBits(hdc, sdib_record->xDest, sdib_record->yDest,
+ sdib_record->cxDest,
+ sdib_record->cyDest, sdib_record->xSrc,
+ sdib_record->ySrc,
+ sdib_record->cxSrc, sdib_record->cySrc,
+ pixels,
+ reinterpret_cast<const BITMAPINFO *>(&bmi),
+ sdib_record->iUsageSrc,
+ sdib_record->dwRop));
+ }
+ } else {
+ res = Play(context);
+ }
+ break;
+ }
+ 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;
+ case EMR_GDICOMMENT: {
+ const EMRGDICOMMENT* comment_record =
+ reinterpret_cast<const EMRGDICOMMENT*>(record());
+ if (comment_record->cbData == sizeof(PageBreakRecord)) {
+ const PageBreakRecord* page_break_record =
+ reinterpret_cast<const PageBreakRecord*>(comment_record->Data);
+ if (page_break_record && page_break_record->IsValid()) {
+ if (page_break_record->type == PageBreakRecord::START_PAGE) {
+ res = !!::StartPage(context->hdc);
+ DCHECK_EQ(0, context->dc_on_page_start);
+ context->dc_on_page_start = ::SaveDC(context->hdc);
+ } else if (page_break_record->type == PageBreakRecord::END_PAGE) {
+ DCHECK_NE(0, context->dc_on_page_start);
+ ::RestoreDC(context->hdc, context->dc_on_page_start);
+ context->dc_on_page_start = 0;
+ res = !!::EndPage(context->hdc);
+ } else {
+ res = false;
+ NOTREACHED();
+ }
+ } else {
+ res = Play(context);
+ }
+ } else {
+ res = true;
+ }
+ break;
+ }
+ default: {
+ res = Play(context);
+ break;
+ }
+ }
+ return res;
+}
+
+SkDevice* Emf::StartPageForVectorCanvas(
+ const gfx::Size& page_size, const gfx::Rect& content_area,
+ const float& scale_factor) {
+ if (!StartPage(page_size, content_area, scale_factor))
+ return NULL;
+
+ return skia::VectorPlatformDeviceEmf::CreateDevice(page_size.width(),
+ page_size.height(),
+ true, hdc_);
+}
+
+bool Emf::StartPage(const gfx::Size& /*page_size*/,
+ const gfx::Rect& /*content_area*/,
+ const float& /*scale_factor*/) {
+ DCHECK(hdc_);
+ if (!hdc_)
+ return false;
+ page_count_++;
+ PageBreakRecord record(PageBreakRecord::START_PAGE);
+ return !!GdiComment(hdc_, sizeof(record),
+ reinterpret_cast<const BYTE *>(&record));
+}
+
+bool Emf::FinishPage() {
+ DCHECK(hdc_);
+ if (!hdc_)
+ return false;
+ PageBreakRecord record(PageBreakRecord::END_PAGE);
+ return !!GdiComment(hdc_, sizeof(record),
+ reinterpret_cast<const BYTE *>(&record));
+}
+
+Emf::Enumerator::Enumerator(const Emf& emf, HDC context, const RECT* rect) {
+ 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(record));
+ return 1;
+}
+
+bool Emf::IsAlphaBlendUsed() const {
+ bool result = false;
+ ::EnumEnhMetaFile(NULL,
+ emf(),
+ &IsAlphaBlendUsedEnumProc,
+ &result,
+ NULL);
+ return result;
+}
+
+Emf* Emf::RasterizeMetafile(int raster_area_in_pixels) const {
+ gfx::Rect page_bounds = GetPageBounds(1);
+ gfx::Size page_size(page_bounds.size());
+ if (page_size.GetArea() <= 0) {
+ NOTREACHED() << "Metafile is empty";
+ page_bounds = gfx::Rect(1, 1);
+ }
+
+ float scale = sqrt(float(raster_area_in_pixels) / page_size.GetArea());
+ page_size.set_width(std::max<int>(1, page_size.width() * scale));
+ page_size.set_height(std::max<int>(1, page_size.height() * scale));
+
+
+ RasterBitmap bitmap(page_size);
+
+ gfx::Rect bitmap_rect(page_size);
+ RECT rect = bitmap_rect.ToRECT();
+ Playback(bitmap.context(), &rect);
+
+ scoped_ptr<Emf> result(new Emf);
+ result->Init();
+ HDC hdc = result->context();
+ DCHECK(hdc);
+ skia::InitializeDC(hdc);
+
+ // Params are ignored.
+ result->StartPage(page_bounds.size(), page_bounds, 1);
+
+ ::ModifyWorldTransform(hdc, NULL, MWT_IDENTITY);
+ XFORM xform = {
+ float(page_bounds.width()) / bitmap_rect.width(), 0,
+ 0, float(page_bounds.height()) / bitmap_rect.height(),
+ page_bounds.x(),
+ page_bounds.y(),
+ };
+ ::SetWorldTransform(hdc, &xform);
+ ::BitBlt(hdc, 0, 0, bitmap_rect.width(), bitmap_rect.height(),
+ bitmap.context(), bitmap_rect.x(), bitmap_rect.y(), SRCCOPY);
+
+ result->FinishPage();
+ result->FinishDocument();
+
+ return result.release();
+}
+
+Emf* Emf::RasterizeAlphaBlend() const {
+ gfx::Rect page_bounds = GetPageBounds(1);
+ if (page_bounds.size().GetArea() <= 0) {
+ NOTREACHED() << "Metafile is empty";
+ page_bounds = gfx::Rect(1, 1);
+ }
+
+ RasterBitmap bitmap(page_bounds.size());
+
+ // Map metafile page_bounds.x(), page_bounds.y() to bitmap 0, 0.
+ XFORM xform = { 1, 0, 0, 1, -page_bounds.x(), -page_bounds.y()};
+ ::SetWorldTransform(bitmap.context(), &xform);
+
+ scoped_ptr<Emf> result(new Emf);
+ result->Init();
+ HDC hdc = result->context();
+ DCHECK(hdc);
+ skia::InitializeDC(hdc);
+
+ HDC bitmap_dc = bitmap.context();
+ RECT rect = page_bounds.ToRECT();
+ ::EnumEnhMetaFile(hdc, emf(), &RasterizeAlphaBlendProc, &bitmap_dc, &rect);
+
+ result->FinishDocument();
+
+ return result.release();
+}
+
+
+} // namespace printing