普通文本  |  679行  |  21.85 KB

// 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;
}

SkBaseDevice* 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