diff options
Diffstat (limited to 'chromium/printing/emf_win.cc')
-rw-r--r-- | chromium/printing/emf_win.cc | 678 |
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 |