Made first-run UI more accessibility-friendly.
[chromium-blink-merge.git] / printing / emf_win.cc
blob3003a2f4d987725ff810207d61648df84434340a
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.
5 #include "printing/emf_win.h"
7 #include "base/files/file_path.h"
8 #include "base/logging.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/win/scoped_gdi_object.h"
11 #include "base/win/scoped_hdc.h"
12 #include "base/win/scoped_select_object.h"
13 #include "skia/ext/vector_platform_device_emf_win.h"
14 #include "third_party/skia/include/core/SkBitmap.h"
15 #include "ui/gfx/codec/jpeg_codec.h"
16 #include "ui/gfx/codec/png_codec.h"
17 #include "ui/gfx/gdi_util.h"
18 #include "ui/gfx/rect.h"
19 #include "ui/gfx/size.h"
21 namespace {
23 const int kCustomGdiCommentSignature = 0xdeadbabe;
24 struct PageBreakRecord {
25 int signature;
26 enum PageBreakType {
27 START_PAGE,
28 END_PAGE,
29 } type;
30 explicit PageBreakRecord(PageBreakType type_in)
31 : signature(kCustomGdiCommentSignature), type(type_in) {
33 bool IsValid() const {
34 return (signature == kCustomGdiCommentSignature) &&
35 (type >= START_PAGE) && (type <= END_PAGE);
39 int CALLBACK IsAlphaBlendUsedEnumProc(HDC,
40 HANDLETABLE*,
41 const ENHMETARECORD *record,
42 int,
43 LPARAM data) {
44 bool* result = reinterpret_cast<bool*>(data);
45 if (!result)
46 return 0;
47 switch (record->iType) {
48 case EMR_ALPHABLEND: {
49 *result = true;
50 return 0;
51 break;
54 return 1;
57 int CALLBACK RasterizeAlphaBlendProc(HDC metafile_dc,
58 HANDLETABLE* handle_table,
59 const ENHMETARECORD *record,
60 int num_objects,
61 LPARAM data) {
62 HDC bitmap_dc = *reinterpret_cast<HDC*>(data);
63 // Play this command to the bitmap DC.
64 ::PlayEnhMetaFileRecord(bitmap_dc, handle_table, record, num_objects);
65 switch (record->iType) {
66 case EMR_ALPHABLEND: {
67 const EMRALPHABLEND* alpha_blend =
68 reinterpret_cast<const EMRALPHABLEND*>(record);
69 // Don't modify transformation here.
70 // Old implementation did reset transformations for DC to identity matrix.
71 // That was not correct and cause some bugs, like unexpected cropping.
72 // EMRALPHABLEND is rendered into bitmap and metafile contexts with
73 // current transformation. If we don't touch them here BitBlt will copy
74 // same areas.
75 ::BitBlt(metafile_dc,
76 alpha_blend->xDest,
77 alpha_blend->yDest,
78 alpha_blend->cxDest,
79 alpha_blend->cyDest,
80 bitmap_dc,
81 alpha_blend->xDest,
82 alpha_blend->yDest,
83 SRCCOPY);
84 break;
86 case EMR_CREATEBRUSHINDIRECT:
87 case EMR_CREATECOLORSPACE:
88 case EMR_CREATECOLORSPACEW:
89 case EMR_CREATEDIBPATTERNBRUSHPT:
90 case EMR_CREATEMONOBRUSH:
91 case EMR_CREATEPALETTE:
92 case EMR_CREATEPEN:
93 case EMR_DELETECOLORSPACE:
94 case EMR_DELETEOBJECT:
95 case EMR_EXTCREATEFONTINDIRECTW:
96 // Play object creation command only once.
97 break;
99 default:
100 // Play this command to the metafile DC.
101 ::PlayEnhMetaFileRecord(metafile_dc, handle_table, record, num_objects);
102 break;
104 return 1; // Continue enumeration
107 // Bitmapt for rasterization.
108 class RasterBitmap {
109 public:
110 explicit RasterBitmap(const gfx::Size& raster_size)
111 : saved_object_(NULL) {
112 context_.Set(::CreateCompatibleDC(NULL));
113 if (!context_) {
114 NOTREACHED() << "Bitmap DC creation failed";
115 return;
117 ::SetGraphicsMode(context_, GM_ADVANCED);
118 void* bits = NULL;
119 gfx::Rect bitmap_rect(raster_size);
120 gfx::CreateBitmapHeader(raster_size.width(), raster_size.height(),
121 &header_.bmiHeader);
122 bitmap_.Set(::CreateDIBSection(context_, &header_, DIB_RGB_COLORS, &bits,
123 NULL, 0));
124 if (!bitmap_)
125 NOTREACHED() << "Raster bitmap creation for printing failed";
127 saved_object_ = ::SelectObject(context_, bitmap_);
128 RECT rect = bitmap_rect.ToRECT();
129 ::FillRect(context_, &rect,
130 static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH)));
134 ~RasterBitmap() {
135 ::SelectObject(context_, saved_object_);
138 HDC context() const {
139 return context_;
142 base::win::ScopedCreateDC context_;
143 BITMAPINFO header_;
144 base::win::ScopedBitmap bitmap_;
145 HGDIOBJ saved_object_;
147 private:
148 DISALLOW_COPY_AND_ASSIGN(RasterBitmap);
153 } // namespace
155 namespace printing {
157 bool DIBFormatNativelySupported(HDC dc, uint32 escape, const BYTE* bits,
158 int size) {
159 BOOL supported = FALSE;
160 if (ExtEscape(dc, QUERYESCSUPPORT, sizeof(escape),
161 reinterpret_cast<LPCSTR>(&escape), 0, 0) > 0) {
162 ExtEscape(dc, escape, size, reinterpret_cast<LPCSTR>(bits),
163 sizeof(supported), reinterpret_cast<LPSTR>(&supported));
165 return !!supported;
168 Emf::Emf() : emf_(NULL), hdc_(NULL), page_count_(0) {
171 Emf::~Emf() {
172 DCHECK(!hdc_);
173 if (emf_)
174 DeleteEnhMetaFile(emf_);
177 bool Emf::InitToFile(const base::FilePath& metafile_path) {
178 DCHECK(!emf_ && !hdc_);
179 hdc_ = CreateEnhMetaFile(NULL, metafile_path.value().c_str(), NULL, NULL);
180 DCHECK(hdc_);
181 return hdc_ != NULL;
184 bool Emf::InitFromFile(const base::FilePath& metafile_path) {
185 DCHECK(!emf_ && !hdc_);
186 emf_ = GetEnhMetaFile(metafile_path.value().c_str());
187 DCHECK(emf_);
188 return emf_ != NULL;
191 bool Emf::Init() {
192 DCHECK(!emf_ && !hdc_);
193 hdc_ = CreateEnhMetaFile(NULL, NULL, NULL, NULL);
194 DCHECK(hdc_);
195 return hdc_ != NULL;
198 bool Emf::InitFromData(const void* src_buffer, uint32 src_buffer_size) {
199 DCHECK(!emf_ && !hdc_);
200 emf_ = SetEnhMetaFileBits(src_buffer_size,
201 reinterpret_cast<const BYTE*>(src_buffer));
202 return emf_ != NULL;
205 bool Emf::FinishDocument() {
206 DCHECK(!emf_ && hdc_);
207 emf_ = CloseEnhMetaFile(hdc_);
208 DCHECK(emf_);
209 hdc_ = NULL;
210 return emf_ != NULL;
213 bool Emf::Playback(HDC hdc, const RECT* rect) const {
214 DCHECK(emf_ && !hdc_);
215 RECT bounds;
216 if (!rect) {
217 // Get the natural bounds of the EMF buffer.
218 bounds = GetPageBounds(1).ToRECT();
219 rect = &bounds;
221 return PlayEnhMetaFile(hdc, emf_, rect) != 0;
224 bool Emf::SafePlayback(HDC context) const {
225 DCHECK(emf_ && !hdc_);
226 XFORM base_matrix;
227 if (!GetWorldTransform(context, &base_matrix)) {
228 NOTREACHED();
229 return false;
231 Emf::EnumerationContext playback_context;
232 playback_context.base_matrix = &base_matrix;
233 RECT rect = GetPageBounds(1).ToRECT();
234 return EnumEnhMetaFile(context,
235 emf_,
236 &Emf::SafePlaybackProc,
237 reinterpret_cast<void*>(&playback_context),
238 &rect) != 0;
241 gfx::Rect Emf::GetPageBounds(unsigned int page_number) const {
242 DCHECK(emf_ && !hdc_);
243 DCHECK_EQ(1U, page_number);
244 ENHMETAHEADER header;
245 if (GetEnhMetaFileHeader(emf_, sizeof(header), &header) != sizeof(header)) {
246 NOTREACHED();
247 return gfx::Rect();
249 // Add 1 to right and bottom because it's inclusive rectangle.
250 // See ENHMETAHEADER.
251 return gfx::Rect(header.rclBounds.left,
252 header.rclBounds.top,
253 header.rclBounds.right - header.rclBounds.left + 1,
254 header.rclBounds.bottom - header.rclBounds.top + 1);
257 uint32 Emf::GetDataSize() const {
258 DCHECK(emf_ && !hdc_);
259 return GetEnhMetaFileBits(emf_, 0, NULL);
262 bool Emf::GetData(void* buffer, uint32 size) const {
263 DCHECK(emf_ && !hdc_);
264 DCHECK(buffer && size);
265 uint32 size2 =
266 GetEnhMetaFileBits(emf_, size, reinterpret_cast<BYTE*>(buffer));
267 DCHECK(size2 == size);
268 return size2 == size && size2 != 0;
271 bool Emf::GetDataAsVector(std::vector<uint8>* buffer) const {
272 uint32 size = GetDataSize();
273 if (!size)
274 return false;
276 buffer->resize(size);
277 if (!GetData(&buffer->front(), size))
278 return false;
279 return true;
282 bool Emf::SaveTo(const base::FilePath& file_path) const {
283 HANDLE file = CreateFile(file_path.value().c_str(), GENERIC_WRITE,
284 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
285 CREATE_ALWAYS, 0, NULL);
286 if (file == INVALID_HANDLE_VALUE)
287 return false;
289 bool success = false;
290 std::vector<uint8> buffer;
291 if (GetDataAsVector(&buffer)) {
292 DWORD written = 0;
293 if (WriteFile(file, &*buffer.begin(), static_cast<DWORD>(buffer.size()),
294 &written, NULL) &&
295 written == buffer.size()) {
296 success = true;
299 CloseHandle(file);
300 return success;
303 int CALLBACK Emf::SafePlaybackProc(HDC hdc,
304 HANDLETABLE* handle_table,
305 const ENHMETARECORD* record,
306 int objects_count,
307 LPARAM param) {
308 Emf::EnumerationContext* context =
309 reinterpret_cast<Emf::EnumerationContext*>(param);
310 context->handle_table = handle_table;
311 context->objects_count = objects_count;
312 context->hdc = hdc;
313 Record record_instance(record);
314 bool success = record_instance.SafePlayback(context);
315 DCHECK(success);
316 return 1;
319 Emf::EnumerationContext::EnumerationContext() {
320 memset(this, 0, sizeof(*this));
323 Emf::Record::Record(const ENHMETARECORD* record)
324 : record_(record) {
325 DCHECK(record_);
328 bool Emf::Record::Play(Emf::EnumerationContext* context) const {
329 return 0 != PlayEnhMetaFileRecord(context->hdc,
330 context->handle_table,
331 record_,
332 context->objects_count);
335 bool Emf::Record::SafePlayback(Emf::EnumerationContext* context) const {
336 // For EMF field description, see [MS-EMF] Enhanced Metafile Format
337 // Specification.
339 // This is the second major EMF breakage I get; the first one being
340 // SetDCBrushColor/SetDCPenColor/DC_PEN/DC_BRUSH being silently ignored.
342 // This function is the guts of the fix for bug 1186598. Some printer drivers
343 // somehow choke on certain EMF records, but calling the corresponding
344 // function directly on the printer HDC is fine. Still, playing the EMF record
345 // fails. Go figure.
347 // The main issue is that SetLayout is totally unsupported on these printers
348 // (HP 4500/4700). I used to call SetLayout and I stopped. I found out this is
349 // not sufficient because GDI32!PlayEnhMetaFile internally calls SetLayout(!)
350 // Damn.
352 // So I resorted to manually parse the EMF records and play them one by one.
353 // The issue with this method compared to using PlayEnhMetaFile to play back
354 // an EMF buffer is that the later silently fixes the matrix to take in
355 // account the matrix currently loaded at the time of the call.
356 // The matrix magic is done transparently when using PlayEnhMetaFile but since
357 // I'm processing one field at a time, I need to do the fixup myself. Note
358 // that PlayEnhMetaFileRecord doesn't fix the matrix correctly even when
359 // called inside an EnumEnhMetaFile loop. Go figure (bis).
361 // So when I see a EMR_SETWORLDTRANSFORM and EMR_MODIFYWORLDTRANSFORM, I need
362 // to fix the matrix according to the matrix previously loaded before playing
363 // back the buffer. Otherwise, the previously loaded matrix would be ignored
364 // and the EMF buffer would always be played back at its native resolution.
365 // Duh.
367 // I also use this opportunity to skip over eventual EMR_SETLAYOUT record that
368 // could remain.
370 // Another tweak we make is for JPEGs/PNGs in calls to StretchDIBits.
371 // (Our Pepper plugin code uses a JPEG). If the printer does not support
372 // JPEGs/PNGs natively we decompress the JPEG/PNG and then set it to the
373 // device.
374 // TODO(sanjeevr): We should also add JPEG/PNG support for SetSIBitsToDevice
376 // We also process any custom EMR_GDICOMMENT records which are our
377 // placeholders for StartPage and EndPage.
378 // Note: I should probably care about view ports and clipping, eventually.
379 bool res = false;
380 const XFORM* base_matrix = context->base_matrix;
381 switch (record()->iType) {
382 case EMR_STRETCHDIBITS: {
383 const EMRSTRETCHDIBITS * sdib_record =
384 reinterpret_cast<const EMRSTRETCHDIBITS*>(record());
385 const BYTE* record_start = reinterpret_cast<const BYTE *>(record());
386 const BITMAPINFOHEADER *bmih =
387 reinterpret_cast<const BITMAPINFOHEADER *>(record_start +
388 sdib_record->offBmiSrc);
389 const BYTE* bits = record_start + sdib_record->offBitsSrc;
390 bool play_normally = true;
391 res = false;
392 HDC hdc = context->hdc;
393 scoped_ptr<SkBitmap> bitmap;
394 if (bmih->biCompression == BI_JPEG) {
395 if (!DIBFormatNativelySupported(hdc, CHECKJPEGFORMAT, bits,
396 bmih->biSizeImage)) {
397 play_normally = false;
398 bitmap.reset(gfx::JPEGCodec::Decode(bits, bmih->biSizeImage));
400 } else if (bmih->biCompression == BI_PNG) {
401 if (!DIBFormatNativelySupported(hdc, CHECKPNGFORMAT, bits,
402 bmih->biSizeImage)) {
403 play_normally = false;
404 bitmap.reset(new SkBitmap());
405 gfx::PNGCodec::Decode(bits, bmih->biSizeImage, bitmap.get());
408 if (!play_normally) {
409 DCHECK(bitmap.get());
410 if (bitmap.get()) {
411 SkAutoLockPixels lock(*bitmap.get());
412 DCHECK_EQ(bitmap->config(), SkBitmap::kARGB_8888_Config);
413 const uint32_t* pixels =
414 static_cast<const uint32_t*>(bitmap->getPixels());
415 if (pixels == NULL) {
416 NOTREACHED();
417 return false;
419 BITMAPINFOHEADER bmi = {0};
420 gfx::CreateBitmapHeader(bitmap->width(), bitmap->height(), &bmi);
421 res = (0 != StretchDIBits(hdc, sdib_record->xDest, sdib_record->yDest,
422 sdib_record->cxDest,
423 sdib_record->cyDest, sdib_record->xSrc,
424 sdib_record->ySrc,
425 sdib_record->cxSrc, sdib_record->cySrc,
426 pixels,
427 reinterpret_cast<const BITMAPINFO *>(&bmi),
428 sdib_record->iUsageSrc,
429 sdib_record->dwRop));
431 } else {
432 res = Play(context);
434 break;
436 case EMR_SETWORLDTRANSFORM: {
437 DCHECK_EQ(record()->nSize, sizeof(DWORD) * 2 + sizeof(XFORM));
438 const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm);
439 HDC hdc = context->hdc;
440 if (base_matrix) {
441 res = 0 != SetWorldTransform(hdc, base_matrix) &&
442 ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY);
443 } else {
444 res = 0 != SetWorldTransform(hdc, xform);
446 break;
448 case EMR_MODIFYWORLDTRANSFORM: {
449 DCHECK_EQ(record()->nSize,
450 sizeof(DWORD) * 2 + sizeof(XFORM) + sizeof(DWORD));
451 const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm);
452 const DWORD* option = reinterpret_cast<const DWORD*>(xform + 1);
453 HDC hdc = context->hdc;
454 switch (*option) {
455 case MWT_IDENTITY:
456 if (base_matrix) {
457 res = 0 != SetWorldTransform(hdc, base_matrix);
458 } else {
459 res = 0 != ModifyWorldTransform(hdc, xform, MWT_IDENTITY);
461 break;
462 case MWT_LEFTMULTIPLY:
463 case MWT_RIGHTMULTIPLY:
464 res = 0 != ModifyWorldTransform(hdc, xform, *option);
465 break;
466 case 4: // MWT_SET
467 if (base_matrix) {
468 res = 0 != SetWorldTransform(hdc, base_matrix) &&
469 ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY);
470 } else {
471 res = 0 != SetWorldTransform(hdc, xform);
473 break;
474 default:
475 res = false;
476 break;
478 break;
480 case EMR_SETLAYOUT:
481 // Ignore it.
482 res = true;
483 break;
484 case EMR_GDICOMMENT: {
485 const EMRGDICOMMENT* comment_record =
486 reinterpret_cast<const EMRGDICOMMENT*>(record());
487 if (comment_record->cbData == sizeof(PageBreakRecord)) {
488 const PageBreakRecord* page_break_record =
489 reinterpret_cast<const PageBreakRecord*>(comment_record->Data);
490 if (page_break_record && page_break_record->IsValid()) {
491 if (page_break_record->type == PageBreakRecord::START_PAGE) {
492 res = !!::StartPage(context->hdc);
493 DCHECK_EQ(0, context->dc_on_page_start);
494 context->dc_on_page_start = ::SaveDC(context->hdc);
495 } else if (page_break_record->type == PageBreakRecord::END_PAGE) {
496 DCHECK_NE(0, context->dc_on_page_start);
497 ::RestoreDC(context->hdc, context->dc_on_page_start);
498 context->dc_on_page_start = 0;
499 res = !!::EndPage(context->hdc);
500 } else {
501 res = false;
502 NOTREACHED();
504 } else {
505 res = Play(context);
507 } else {
508 res = true;
510 break;
512 default: {
513 res = Play(context);
514 break;
517 return res;
520 SkBaseDevice* Emf::StartPageForVectorCanvas(
521 const gfx::Size& page_size, const gfx::Rect& content_area,
522 const float& scale_factor) {
523 if (!StartPage(page_size, content_area, scale_factor))
524 return NULL;
526 return skia::VectorPlatformDeviceEmf::CreateDevice(page_size.width(),
527 page_size.height(),
528 true, hdc_);
531 bool Emf::StartPage(const gfx::Size& /*page_size*/,
532 const gfx::Rect& /*content_area*/,
533 const float& /*scale_factor*/) {
534 DCHECK(hdc_);
535 if (!hdc_)
536 return false;
537 page_count_++;
538 PageBreakRecord record(PageBreakRecord::START_PAGE);
539 return !!GdiComment(hdc_, sizeof(record),
540 reinterpret_cast<const BYTE *>(&record));
543 bool Emf::FinishPage() {
544 DCHECK(hdc_);
545 if (!hdc_)
546 return false;
547 PageBreakRecord record(PageBreakRecord::END_PAGE);
548 return !!GdiComment(hdc_, sizeof(record),
549 reinterpret_cast<const BYTE *>(&record));
552 Emf::Enumerator::Enumerator(const Emf& emf, HDC context, const RECT* rect) {
553 items_.clear();
554 if (!EnumEnhMetaFile(context,
555 emf.emf(),
556 &Emf::Enumerator::EnhMetaFileProc,
557 reinterpret_cast<void*>(this),
558 rect)) {
559 NOTREACHED();
560 items_.clear();
562 DCHECK_EQ(context_.hdc, context);
565 Emf::Enumerator::const_iterator Emf::Enumerator::begin() const {
566 return items_.begin();
569 Emf::Enumerator::const_iterator Emf::Enumerator::end() const {
570 return items_.end();
573 int CALLBACK Emf::Enumerator::EnhMetaFileProc(HDC hdc,
574 HANDLETABLE* handle_table,
575 const ENHMETARECORD* record,
576 int objects_count,
577 LPARAM param) {
578 Enumerator& emf = *reinterpret_cast<Enumerator*>(param);
579 if (!emf.context_.handle_table) {
580 DCHECK(!emf.context_.handle_table);
581 DCHECK(!emf.context_.objects_count);
582 emf.context_.handle_table = handle_table;
583 emf.context_.objects_count = objects_count;
584 emf.context_.hdc = hdc;
585 } else {
586 DCHECK_EQ(emf.context_.handle_table, handle_table);
587 DCHECK_EQ(emf.context_.objects_count, objects_count);
588 DCHECK_EQ(emf.context_.hdc, hdc);
590 emf.items_.push_back(Record(record));
591 return 1;
594 bool Emf::IsAlphaBlendUsed() const {
595 bool result = false;
596 ::EnumEnhMetaFile(NULL,
597 emf(),
598 &IsAlphaBlendUsedEnumProc,
599 &result,
600 NULL);
601 return result;
604 Emf* Emf::RasterizeMetafile(int raster_area_in_pixels) const {
605 gfx::Rect page_bounds = GetPageBounds(1);
606 gfx::Size page_size(page_bounds.size());
607 if (page_size.GetArea() <= 0) {
608 NOTREACHED() << "Metafile is empty";
609 page_bounds = gfx::Rect(1, 1);
612 float scale = sqrt(float(raster_area_in_pixels) / page_size.GetArea());
613 page_size.set_width(std::max<int>(1, page_size.width() * scale));
614 page_size.set_height(std::max<int>(1, page_size.height() * scale));
617 RasterBitmap bitmap(page_size);
619 gfx::Rect bitmap_rect(page_size);
620 RECT rect = bitmap_rect.ToRECT();
621 Playback(bitmap.context(), &rect);
623 scoped_ptr<Emf> result(new Emf);
624 result->Init();
625 HDC hdc = result->context();
626 DCHECK(hdc);
627 skia::InitializeDC(hdc);
629 // Params are ignored.
630 result->StartPage(page_bounds.size(), page_bounds, 1);
632 ::ModifyWorldTransform(hdc, NULL, MWT_IDENTITY);
633 XFORM xform = {
634 float(page_bounds.width()) / bitmap_rect.width(), 0,
635 0, float(page_bounds.height()) / bitmap_rect.height(),
636 page_bounds.x(),
637 page_bounds.y(),
639 ::SetWorldTransform(hdc, &xform);
640 ::BitBlt(hdc, 0, 0, bitmap_rect.width(), bitmap_rect.height(),
641 bitmap.context(), bitmap_rect.x(), bitmap_rect.y(), SRCCOPY);
643 result->FinishPage();
644 result->FinishDocument();
646 return result.release();
649 Emf* Emf::RasterizeAlphaBlend() const {
650 gfx::Rect page_bounds = GetPageBounds(1);
651 if (page_bounds.size().GetArea() <= 0) {
652 NOTREACHED() << "Metafile is empty";
653 page_bounds = gfx::Rect(1, 1);
656 RasterBitmap bitmap(page_bounds.size());
658 // Map metafile page_bounds.x(), page_bounds.y() to bitmap 0, 0.
659 XFORM xform = { 1, 0, 0, 1, -page_bounds.x(), -page_bounds.y()};
660 ::SetWorldTransform(bitmap.context(), &xform);
662 scoped_ptr<Emf> result(new Emf);
663 result->Init();
664 HDC hdc = result->context();
665 DCHECK(hdc);
666 skia::InitializeDC(hdc);
668 HDC bitmap_dc = bitmap.context();
669 RECT rect = page_bounds.ToRECT();
670 ::EnumEnhMetaFile(hdc, emf(), &RasterizeAlphaBlendProc, &bitmap_dc, &rect);
672 result->FinishDocument();
674 return result.release();
678 } // namespace printing