Home | History | Annotate | Download | only in printing
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "printing/emf_win.h"
      6 
      7 #include "base/files/file.h"
      8 #include "base/files/file_path.h"
      9 #include "base/logging.h"
     10 #include "base/memory/scoped_ptr.h"
     11 #include "base/win/scoped_gdi_object.h"
     12 #include "base/win/scoped_hdc.h"
     13 #include "base/win/scoped_select_object.h"
     14 #include "skia/ext/vector_platform_device_emf_win.h"
     15 #include "third_party/skia/include/core/SkBitmap.h"
     16 #include "ui/gfx/codec/jpeg_codec.h"
     17 #include "ui/gfx/codec/png_codec.h"
     18 #include "ui/gfx/gdi_util.h"
     19 #include "ui/gfx/rect.h"
     20 #include "ui/gfx/size.h"
     21 
     22 namespace {
     23 
     24 int CALLBACK IsAlphaBlendUsedEnumProc(HDC,
     25                                       HANDLETABLE*,
     26                                       const ENHMETARECORD *record,
     27                                       int,
     28                                       LPARAM data) {
     29   bool* result = reinterpret_cast<bool*>(data);
     30   if (!result)
     31     return 0;
     32   switch (record->iType) {
     33     case EMR_ALPHABLEND: {
     34       *result = true;
     35       return 0;
     36       break;
     37     }
     38   }
     39   return 1;
     40 }
     41 
     42 int CALLBACK RasterizeAlphaBlendProc(HDC metafile_dc,
     43                                      HANDLETABLE* handle_table,
     44                                      const ENHMETARECORD *record,
     45                                      int num_objects,
     46                                      LPARAM data) {
     47     HDC bitmap_dc = *reinterpret_cast<HDC*>(data);
     48     // Play this command to the bitmap DC.
     49     ::PlayEnhMetaFileRecord(bitmap_dc, handle_table, record, num_objects);
     50     switch (record->iType) {
     51     case EMR_ALPHABLEND: {
     52       const EMRALPHABLEND* alpha_blend =
     53           reinterpret_cast<const EMRALPHABLEND*>(record);
     54       // Don't modify transformation here.
     55       // Old implementation did reset transformations for DC to identity matrix.
     56       // That was not correct and cause some bugs, like unexpected cropping.
     57       // EMRALPHABLEND is rendered into bitmap and metafile contexts with
     58       // current transformation. If we don't touch them here BitBlt will copy
     59       // same areas.
     60       ::BitBlt(metafile_dc,
     61                alpha_blend->xDest,
     62                alpha_blend->yDest,
     63                alpha_blend->cxDest,
     64                alpha_blend->cyDest,
     65                bitmap_dc,
     66                alpha_blend->xDest,
     67                alpha_blend->yDest,
     68                SRCCOPY);
     69       break;
     70     }
     71     case EMR_CREATEBRUSHINDIRECT:
     72     case EMR_CREATECOLORSPACE:
     73     case EMR_CREATECOLORSPACEW:
     74     case EMR_CREATEDIBPATTERNBRUSHPT:
     75     case EMR_CREATEMONOBRUSH:
     76     case EMR_CREATEPALETTE:
     77     case EMR_CREATEPEN:
     78     case EMR_DELETECOLORSPACE:
     79     case EMR_DELETEOBJECT:
     80     case EMR_EXTCREATEFONTINDIRECTW:
     81       // Play object creation command only once.
     82       break;
     83 
     84     default:
     85       // Play this command to the metafile DC.
     86       ::PlayEnhMetaFileRecord(metafile_dc, handle_table, record, num_objects);
     87       break;
     88     }
     89     return 1;  // Continue enumeration
     90 }
     91 
     92 // Bitmapt for rasterization.
     93 class RasterBitmap {
     94  public:
     95   explicit RasterBitmap(const gfx::Size& raster_size)
     96       : saved_object_(NULL) {
     97     context_.Set(::CreateCompatibleDC(NULL));
     98     if (!context_.IsValid()) {
     99       NOTREACHED() << "Bitmap DC creation failed";
    100       return;
    101     }
    102     ::SetGraphicsMode(context_.Get(), GM_ADVANCED);
    103     void* bits = NULL;
    104     gfx::Rect bitmap_rect(raster_size);
    105     gfx::CreateBitmapHeader(raster_size.width(), raster_size.height(),
    106                             &header_.bmiHeader);
    107     bitmap_.Set(::CreateDIBSection(context_.Get(), &header_, DIB_RGB_COLORS,
    108                                    &bits, NULL, 0));
    109     if (!bitmap_)
    110       NOTREACHED() << "Raster bitmap creation for printing failed";
    111 
    112     saved_object_ = ::SelectObject(context_.Get(), bitmap_);
    113     RECT rect = bitmap_rect.ToRECT();
    114     ::FillRect(context_.Get(), &rect,
    115                static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH)));
    116 
    117   }
    118 
    119   ~RasterBitmap() {
    120     ::SelectObject(context_.Get(), saved_object_);
    121   }
    122 
    123   HDC context() const {
    124     return context_.Get();
    125   }
    126 
    127   base::win::ScopedCreateDC context_;
    128   BITMAPINFO header_;
    129   base::win::ScopedBitmap bitmap_;
    130   HGDIOBJ saved_object_;
    131 
    132  private:
    133   DISALLOW_COPY_AND_ASSIGN(RasterBitmap);
    134 };
    135 
    136 
    137 
    138 }  // namespace
    139 
    140 namespace printing {
    141 
    142 bool DIBFormatNativelySupported(HDC dc, uint32 escape, const BYTE* bits,
    143                                 int size) {
    144   BOOL supported = FALSE;
    145   if (ExtEscape(dc, QUERYESCSUPPORT, sizeof(escape),
    146                 reinterpret_cast<LPCSTR>(&escape), 0, 0) > 0) {
    147     ExtEscape(dc, escape, size, reinterpret_cast<LPCSTR>(bits),
    148               sizeof(supported), reinterpret_cast<LPSTR>(&supported));
    149   }
    150   return !!supported;
    151 }
    152 
    153 Emf::Emf() : emf_(NULL), hdc_(NULL) {
    154 }
    155 
    156 Emf::~Emf() {
    157   Close();
    158 }
    159 
    160 void Emf::Close() {
    161   DCHECK(!hdc_);
    162   if (emf_)
    163     DeleteEnhMetaFile(emf_);
    164   emf_ = NULL;
    165 }
    166 
    167 bool Emf::InitToFile(const base::FilePath& metafile_path) {
    168   DCHECK(!emf_ && !hdc_);
    169   hdc_ = CreateEnhMetaFile(NULL, metafile_path.value().c_str(), NULL, NULL);
    170   DCHECK(hdc_);
    171   return hdc_ != NULL;
    172 }
    173 
    174 bool Emf::InitFromFile(const base::FilePath& metafile_path) {
    175   DCHECK(!emf_ && !hdc_);
    176   emf_ = GetEnhMetaFile(metafile_path.value().c_str());
    177   DCHECK(emf_);
    178   return emf_ != NULL;
    179 }
    180 
    181 bool Emf::Init() {
    182   DCHECK(!emf_ && !hdc_);
    183   hdc_ = CreateEnhMetaFile(NULL, NULL, NULL, NULL);
    184   DCHECK(hdc_);
    185   return hdc_ != NULL;
    186 }
    187 
    188 bool Emf::InitFromData(const void* src_buffer, uint32 src_buffer_size) {
    189   DCHECK(!emf_ && !hdc_);
    190   emf_ = SetEnhMetaFileBits(src_buffer_size,
    191                             reinterpret_cast<const BYTE*>(src_buffer));
    192   return emf_ != NULL;
    193 }
    194 
    195 bool Emf::FinishDocument() {
    196   DCHECK(!emf_ && hdc_);
    197   emf_ = CloseEnhMetaFile(hdc_);
    198   DCHECK(emf_);
    199   hdc_ = NULL;
    200   return emf_ != NULL;
    201 }
    202 
    203 bool Emf::Playback(HDC hdc, const RECT* rect) const {
    204   DCHECK(emf_ && !hdc_);
    205   RECT bounds;
    206   if (!rect) {
    207     // Get the natural bounds of the EMF buffer.
    208     bounds = GetPageBounds(1).ToRECT();
    209     rect = &bounds;
    210   }
    211   return PlayEnhMetaFile(hdc, emf_, rect) != 0;
    212 }
    213 
    214 bool Emf::SafePlayback(HDC context) const {
    215   DCHECK(emf_ && !hdc_);
    216   XFORM base_matrix;
    217   if (!GetWorldTransform(context, &base_matrix)) {
    218     NOTREACHED();
    219     return false;
    220   }
    221   Emf::EnumerationContext playback_context;
    222   playback_context.base_matrix = &base_matrix;
    223   gfx::Rect bound = GetPageBounds(1);
    224   RECT rect = bound.ToRECT();
    225   return bound.IsEmpty() ||
    226          EnumEnhMetaFile(context,
    227                          emf_,
    228                          &Emf::SafePlaybackProc,
    229                          reinterpret_cast<void*>(&playback_context),
    230                          &rect) != 0;
    231 }
    232 
    233 gfx::Rect Emf::GetPageBounds(unsigned int page_number) const {
    234   DCHECK(emf_ && !hdc_);
    235   DCHECK_EQ(1U, page_number);
    236   ENHMETAHEADER header;
    237   if (GetEnhMetaFileHeader(emf_, sizeof(header), &header) != sizeof(header)) {
    238     NOTREACHED();
    239     return gfx::Rect();
    240   }
    241   // Add 1 to right and bottom because it's inclusive rectangle.
    242   // See ENHMETAHEADER.
    243   return gfx::Rect(header.rclBounds.left,
    244                    header.rclBounds.top,
    245                    header.rclBounds.right - header.rclBounds.left + 1,
    246                    header.rclBounds.bottom - header.rclBounds.top + 1);
    247 }
    248 
    249 uint32 Emf::GetDataSize() const {
    250   DCHECK(emf_ && !hdc_);
    251   return GetEnhMetaFileBits(emf_, 0, NULL);
    252 }
    253 
    254 bool Emf::GetData(void* buffer, uint32 size) const {
    255   DCHECK(emf_ && !hdc_);
    256   DCHECK(buffer && size);
    257   uint32 size2 =
    258       GetEnhMetaFileBits(emf_, size, reinterpret_cast<BYTE*>(buffer));
    259   DCHECK(size2 == size);
    260   return size2 == size && size2 != 0;
    261 }
    262 
    263 int CALLBACK Emf::SafePlaybackProc(HDC hdc,
    264                                    HANDLETABLE* handle_table,
    265                                    const ENHMETARECORD* record,
    266                                    int objects_count,
    267                                    LPARAM param) {
    268   Emf::EnumerationContext* context =
    269       reinterpret_cast<Emf::EnumerationContext*>(param);
    270   context->handle_table = handle_table;
    271   context->objects_count = objects_count;
    272   context->hdc = hdc;
    273   Record record_instance(record);
    274   bool success = record_instance.SafePlayback(context);
    275   DCHECK(success);
    276   return 1;
    277 }
    278 
    279 Emf::EnumerationContext::EnumerationContext() {
    280   memset(this, 0, sizeof(*this));
    281 }
    282 
    283 Emf::Record::Record(const ENHMETARECORD* record)
    284     : record_(record) {
    285   DCHECK(record_);
    286 }
    287 
    288 bool Emf::Record::Play(Emf::EnumerationContext* context) const {
    289   return 0 != PlayEnhMetaFileRecord(context->hdc,
    290                                     context->handle_table,
    291                                     record_,
    292                                     context->objects_count);
    293 }
    294 
    295 bool Emf::Record::SafePlayback(Emf::EnumerationContext* context) const {
    296   // For EMF field description, see [MS-EMF] Enhanced Metafile Format
    297   // Specification.
    298   //
    299   // This is the second major EMF breakage I get; the first one being
    300   // SetDCBrushColor/SetDCPenColor/DC_PEN/DC_BRUSH being silently ignored.
    301   //
    302   // This function is the guts of the fix for bug 1186598. Some printer drivers
    303   // somehow choke on certain EMF records, but calling the corresponding
    304   // function directly on the printer HDC is fine. Still, playing the EMF record
    305   // fails. Go figure.
    306   //
    307   // The main issue is that SetLayout is totally unsupported on these printers
    308   // (HP 4500/4700). I used to call SetLayout and I stopped. I found out this is
    309   // not sufficient because GDI32!PlayEnhMetaFile internally calls SetLayout(!)
    310   // Damn.
    311   //
    312   // So I resorted to manually parse the EMF records and play them one by one.
    313   // The issue with this method compared to using PlayEnhMetaFile to play back
    314   // an EMF buffer is that the later silently fixes the matrix to take in
    315   // account the matrix currently loaded at the time of the call.
    316   // The matrix magic is done transparently when using PlayEnhMetaFile but since
    317   // I'm processing one field at a time, I need to do the fixup myself. Note
    318   // that PlayEnhMetaFileRecord doesn't fix the matrix correctly even when
    319   // called inside an EnumEnhMetaFile loop. Go figure (bis).
    320   //
    321   // So when I see a EMR_SETWORLDTRANSFORM and EMR_MODIFYWORLDTRANSFORM, I need
    322   // to fix the matrix according to the matrix previously loaded before playing
    323   // back the buffer. Otherwise, the previously loaded matrix would be ignored
    324   // and the EMF buffer would always be played back at its native resolution.
    325   // Duh.
    326   //
    327   // I also use this opportunity to skip over eventual EMR_SETLAYOUT record that
    328   // could remain.
    329   //
    330   // Another tweak we make is for JPEGs/PNGs in calls to StretchDIBits.
    331   // (Our Pepper plugin code uses a JPEG). If the printer does not support
    332   // JPEGs/PNGs natively we decompress the JPEG/PNG and then set it to the
    333   // device.
    334   // TODO(sanjeevr): We should also add JPEG/PNG support for SetSIBitsToDevice
    335   //
    336   // We also process any custom EMR_GDICOMMENT records which are our
    337   // placeholders for StartPage and EndPage.
    338   // Note: I should probably care about view ports and clipping, eventually.
    339   bool res = false;
    340   const XFORM* base_matrix = context->base_matrix;
    341   switch (record()->iType) {
    342     case EMR_STRETCHDIBITS: {
    343       const EMRSTRETCHDIBITS * sdib_record =
    344           reinterpret_cast<const EMRSTRETCHDIBITS*>(record());
    345       const BYTE* record_start = reinterpret_cast<const BYTE *>(record());
    346       const BITMAPINFOHEADER *bmih =
    347           reinterpret_cast<const BITMAPINFOHEADER *>(record_start +
    348                                                      sdib_record->offBmiSrc);
    349       const BYTE* bits = record_start + sdib_record->offBitsSrc;
    350       bool play_normally = true;
    351       res = false;
    352       HDC hdc = context->hdc;
    353       scoped_ptr<SkBitmap> bitmap;
    354       if (bmih->biCompression == BI_JPEG) {
    355         if (!DIBFormatNativelySupported(hdc, CHECKJPEGFORMAT, bits,
    356                                         bmih->biSizeImage)) {
    357           play_normally = false;
    358           bitmap.reset(gfx::JPEGCodec::Decode(bits, bmih->biSizeImage));
    359         }
    360       } else if (bmih->biCompression == BI_PNG) {
    361         if (!DIBFormatNativelySupported(hdc, CHECKPNGFORMAT, bits,
    362                                         bmih->biSizeImage)) {
    363           play_normally = false;
    364           bitmap.reset(new SkBitmap());
    365           gfx::PNGCodec::Decode(bits, bmih->biSizeImage, bitmap.get());
    366         }
    367       }
    368       if (!play_normally) {
    369         DCHECK(bitmap.get());
    370         if (bitmap.get()) {
    371           SkAutoLockPixels lock(*bitmap.get());
    372           DCHECK_EQ(bitmap->colorType(), kN32_SkColorType);
    373           const uint32_t* pixels =
    374               static_cast<const uint32_t*>(bitmap->getPixels());
    375           if (pixels == NULL) {
    376             NOTREACHED();
    377             return false;
    378           }
    379           BITMAPINFOHEADER bmi = {0};
    380           gfx::CreateBitmapHeader(bitmap->width(), bitmap->height(), &bmi);
    381           res = (0 != StretchDIBits(hdc, sdib_record->xDest, sdib_record->yDest,
    382                                     sdib_record->cxDest,
    383                                     sdib_record->cyDest, sdib_record->xSrc,
    384                                     sdib_record->ySrc,
    385                                     sdib_record->cxSrc, sdib_record->cySrc,
    386                                     pixels,
    387                                     reinterpret_cast<const BITMAPINFO *>(&bmi),
    388                                     sdib_record->iUsageSrc,
    389                                     sdib_record->dwRop));
    390         }
    391       } else {
    392         res = Play(context);
    393       }
    394       break;
    395     }
    396     case EMR_SETWORLDTRANSFORM: {
    397       DCHECK_EQ(record()->nSize, sizeof(DWORD) * 2 + sizeof(XFORM));
    398       const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm);
    399       HDC hdc = context->hdc;
    400       if (base_matrix) {
    401         res = 0 != SetWorldTransform(hdc, base_matrix) &&
    402                    ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY);
    403       } else {
    404         res = 0 != SetWorldTransform(hdc, xform);
    405       }
    406       break;
    407     }
    408     case EMR_MODIFYWORLDTRANSFORM: {
    409       DCHECK_EQ(record()->nSize,
    410                 sizeof(DWORD) * 2 + sizeof(XFORM) + sizeof(DWORD));
    411       const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm);
    412       const DWORD* option = reinterpret_cast<const DWORD*>(xform + 1);
    413       HDC hdc = context->hdc;
    414       switch (*option) {
    415         case MWT_IDENTITY:
    416           if (base_matrix) {
    417             res = 0 != SetWorldTransform(hdc, base_matrix);
    418           } else {
    419             res = 0 != ModifyWorldTransform(hdc, xform, MWT_IDENTITY);
    420           }
    421           break;
    422         case MWT_LEFTMULTIPLY:
    423         case MWT_RIGHTMULTIPLY:
    424           res = 0 != ModifyWorldTransform(hdc, xform, *option);
    425           break;
    426         case 4:  // MWT_SET
    427           if (base_matrix) {
    428             res = 0 != SetWorldTransform(hdc, base_matrix) &&
    429                        ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY);
    430           } else {
    431             res = 0 != SetWorldTransform(hdc, xform);
    432           }
    433           break;
    434         default:
    435           res = false;
    436           break;
    437       }
    438       break;
    439     }
    440     case EMR_SETLAYOUT:
    441       // Ignore it.
    442       res = true;
    443       break;
    444     default: {
    445       res = Play(context);
    446       break;
    447     }
    448   }
    449   return res;
    450 }
    451 
    452 SkBaseDevice* Emf::StartPageForVectorCanvas(
    453     const gfx::Size& page_size, const gfx::Rect& content_area,
    454     const float& scale_factor) {
    455   if (!StartPage(page_size, content_area, scale_factor))
    456     return NULL;
    457 
    458   return skia::VectorPlatformDeviceEmf::CreateDevice(page_size.width(),
    459                                                      page_size.height(),
    460                                                      true, hdc_);
    461 }
    462 
    463 bool Emf::StartPage(const gfx::Size& /*page_size*/,
    464                     const gfx::Rect& /*content_area*/,
    465                     const float& /*scale_factor*/) {
    466   return true;
    467 }
    468 
    469 bool Emf::FinishPage() {
    470   return true;
    471 }
    472 
    473 Emf::Enumerator::Enumerator(const Emf& emf, HDC context, const RECT* rect) {
    474   items_.clear();
    475   if (!EnumEnhMetaFile(context,
    476                        emf.emf(),
    477                        &Emf::Enumerator::EnhMetaFileProc,
    478                        reinterpret_cast<void*>(this),
    479                        rect)) {
    480     NOTREACHED();
    481     items_.clear();
    482   }
    483   DCHECK_EQ(context_.hdc, context);
    484 }
    485 
    486 Emf::Enumerator::const_iterator Emf::Enumerator::begin() const {
    487   return items_.begin();
    488 }
    489 
    490 Emf::Enumerator::const_iterator Emf::Enumerator::end() const {
    491   return items_.end();
    492 }
    493 
    494 int CALLBACK Emf::Enumerator::EnhMetaFileProc(HDC hdc,
    495                                               HANDLETABLE* handle_table,
    496                                               const ENHMETARECORD* record,
    497                                               int objects_count,
    498                                               LPARAM param) {
    499   Enumerator& emf = *reinterpret_cast<Enumerator*>(param);
    500   if (!emf.context_.handle_table) {
    501     DCHECK(!emf.context_.handle_table);
    502     DCHECK(!emf.context_.objects_count);
    503     emf.context_.handle_table = handle_table;
    504     emf.context_.objects_count = objects_count;
    505     emf.context_.hdc = hdc;
    506   } else {
    507     DCHECK_EQ(emf.context_.handle_table, handle_table);
    508     DCHECK_EQ(emf.context_.objects_count, objects_count);
    509     DCHECK_EQ(emf.context_.hdc, hdc);
    510   }
    511   emf.items_.push_back(Record(record));
    512   return 1;
    513 }
    514 
    515 bool Emf::IsAlphaBlendUsed() const {
    516   bool result = false;
    517   ::EnumEnhMetaFile(NULL,
    518                     emf(),
    519                     &IsAlphaBlendUsedEnumProc,
    520                     &result,
    521                     NULL);
    522   return result;
    523 }
    524 
    525 scoped_ptr<Emf> Emf::RasterizeMetafile(int raster_area_in_pixels) const {
    526   gfx::Rect page_bounds = GetPageBounds(1);
    527   gfx::Size page_size(page_bounds.size());
    528   if (page_size.GetArea() <= 0) {
    529     NOTREACHED() << "Metafile is empty";
    530     page_bounds = gfx::Rect(1, 1);
    531   }
    532 
    533   float scale = sqrt(float(raster_area_in_pixels) / page_size.GetArea());
    534   page_size.set_width(std::max<int>(1, page_size.width() * scale));
    535   page_size.set_height(std::max<int>(1, page_size.height() * scale));
    536 
    537 
    538   RasterBitmap bitmap(page_size);
    539 
    540   gfx::Rect bitmap_rect(page_size);
    541   RECT rect = bitmap_rect.ToRECT();
    542   Playback(bitmap.context(), &rect);
    543 
    544   scoped_ptr<Emf> result(new Emf);
    545   result->Init();
    546   HDC hdc = result->context();
    547   DCHECK(hdc);
    548   skia::InitializeDC(hdc);
    549 
    550   // Params are ignored.
    551   result->StartPage(page_bounds.size(), page_bounds, 1);
    552 
    553   ::ModifyWorldTransform(hdc, NULL, MWT_IDENTITY);
    554   XFORM xform = {
    555     float(page_bounds.width()) / bitmap_rect.width(), 0,
    556     0, float(page_bounds.height()) / bitmap_rect.height(),
    557     page_bounds.x(),
    558     page_bounds.y(),
    559   };
    560   ::SetWorldTransform(hdc, &xform);
    561   ::BitBlt(hdc, 0, 0, bitmap_rect.width(), bitmap_rect.height(),
    562            bitmap.context(), bitmap_rect.x(), bitmap_rect.y(), SRCCOPY);
    563 
    564   result->FinishPage();
    565   result->FinishDocument();
    566 
    567   return result.Pass();
    568 }
    569 
    570 scoped_ptr<Emf> Emf::RasterizeAlphaBlend() const {
    571   gfx::Rect page_bounds = GetPageBounds(1);
    572   if (page_bounds.size().GetArea() <= 0) {
    573     NOTREACHED() << "Metafile is empty";
    574     page_bounds = gfx::Rect(1, 1);
    575   }
    576 
    577   RasterBitmap bitmap(page_bounds.size());
    578 
    579   // Map metafile page_bounds.x(), page_bounds.y() to bitmap 0, 0.
    580   XFORM xform = { 1, 0, 0, 1, -page_bounds.x(), -page_bounds.y()};
    581   ::SetWorldTransform(bitmap.context(), &xform);
    582 
    583   scoped_ptr<Emf> result(new Emf);
    584   result->Init();
    585   HDC hdc = result->context();
    586   DCHECK(hdc);
    587   skia::InitializeDC(hdc);
    588 
    589   HDC bitmap_dc = bitmap.context();
    590   RECT rect = page_bounds.ToRECT();
    591   ::EnumEnhMetaFile(hdc, emf(), &RasterizeAlphaBlendProc, &bitmap_dc, &rect);
    592 
    593   result->FinishDocument();
    594 
    595   return result.Pass();
    596 }
    597 
    598 
    599 }  // namespace printing
    600