Fix a typo in the MB mixin for official builds.
[chromium-blink-merge.git] / printing / emf_win.cc
blobd5e1888fe7cf42607ea3d1646892d0333cbce74a
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.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/platform_device.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/geometry/rect.h"
20 #include "ui/gfx/geometry/size.h"
22 namespace {
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;
39 return 1;
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;
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;
84 default:
85 // Play this command to the metafile DC.
86 ::PlayEnhMetaFileRecord(metafile_dc, handle_table, record, num_objects);
87 break;
89 return 1; // Continue enumeration
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;
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";
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)));
118 ~RasterBitmap() {
119 ::SelectObject(context_.Get(), saved_object_);
122 HDC context() const {
123 return context_.Get();
126 base::win::ScopedCreateDC context_;
127 BITMAPINFO header_;
128 base::win::ScopedBitmap bitmap_;
129 HGDIOBJ saved_object_;
131 private:
132 DISALLOW_COPY_AND_ASSIGN(RasterBitmap);
137 } // namespace
139 namespace printing {
141 bool DIBFormatNativelySupported(HDC dc, uint32_t escape, const BYTE* bits,
142 int size) {
143 BOOL supported = FALSE;
144 if (ExtEscape(dc, QUERYESCSUPPORT, sizeof(escape),
145 reinterpret_cast<LPCSTR>(&escape), 0, 0) > 0) {
146 ExtEscape(dc, escape, size, reinterpret_cast<LPCSTR>(bits),
147 sizeof(supported), reinterpret_cast<LPSTR>(&supported));
149 return !!supported;
152 Emf::Emf() : emf_(NULL), hdc_(NULL) {
155 Emf::~Emf() {
156 Close();
159 void Emf::Close() {
160 DCHECK(!hdc_);
161 if (emf_)
162 DeleteEnhMetaFile(emf_);
163 emf_ = NULL;
166 bool Emf::InitToFile(const base::FilePath& metafile_path) {
167 DCHECK(!emf_ && !hdc_);
168 hdc_ = CreateEnhMetaFile(NULL, metafile_path.value().c_str(), NULL, NULL);
169 DCHECK(hdc_);
170 return hdc_ != NULL;
173 bool Emf::InitFromFile(const base::FilePath& metafile_path) {
174 DCHECK(!emf_ && !hdc_);
175 emf_ = GetEnhMetaFile(metafile_path.value().c_str());
176 DCHECK(emf_);
177 return emf_ != NULL;
180 bool Emf::Init() {
181 DCHECK(!emf_ && !hdc_);
182 hdc_ = CreateEnhMetaFile(NULL, NULL, NULL, NULL);
183 DCHECK(hdc_);
184 return hdc_ != NULL;
187 bool Emf::InitFromData(const void* src_buffer, uint32_t src_buffer_size) {
188 DCHECK(!emf_ && !hdc_);
189 emf_ = SetEnhMetaFileBits(src_buffer_size,
190 reinterpret_cast<const BYTE*>(src_buffer));
191 return emf_ != NULL;
194 bool Emf::FinishDocument() {
195 DCHECK(!emf_ && hdc_);
196 emf_ = CloseEnhMetaFile(hdc_);
197 DCHECK(emf_);
198 hdc_ = NULL;
199 return emf_ != NULL;
202 bool Emf::Playback(HDC hdc, const RECT* rect) const {
203 DCHECK(emf_ && !hdc_);
204 RECT bounds;
205 if (!rect) {
206 // Get the natural bounds of the EMF buffer.
207 bounds = GetPageBounds(1).ToRECT();
208 rect = &bounds;
210 return PlayEnhMetaFile(hdc, emf_, rect) != 0;
213 bool Emf::SafePlayback(HDC context) const {
214 DCHECK(emf_ && !hdc_);
215 XFORM base_matrix;
216 if (!GetWorldTransform(context, &base_matrix)) {
217 NOTREACHED();
218 return false;
220 Emf::EnumerationContext playback_context;
221 playback_context.base_matrix = &base_matrix;
222 gfx::Rect bound = GetPageBounds(1);
223 RECT rect = bound.ToRECT();
224 return bound.IsEmpty() ||
225 EnumEnhMetaFile(context,
226 emf_,
227 &Emf::SafePlaybackProc,
228 reinterpret_cast<void*>(&playback_context),
229 &rect) != 0;
232 gfx::Rect Emf::GetPageBounds(unsigned int page_number) const {
233 DCHECK(emf_ && !hdc_);
234 DCHECK_EQ(1U, page_number);
235 ENHMETAHEADER header;
236 if (GetEnhMetaFileHeader(emf_, sizeof(header), &header) != sizeof(header)) {
237 NOTREACHED();
238 return gfx::Rect();
240 // Add 1 to right and bottom because it's inclusive rectangle.
241 // See ENHMETAHEADER.
242 return gfx::Rect(header.rclBounds.left,
243 header.rclBounds.top,
244 header.rclBounds.right - header.rclBounds.left + 1,
245 header.rclBounds.bottom - header.rclBounds.top + 1);
248 unsigned int Emf::GetPageCount() const {
249 return 1;
252 HDC Emf::context() const {
253 return hdc_;
256 uint32_t Emf::GetDataSize() const {
257 DCHECK(emf_ && !hdc_);
258 return GetEnhMetaFileBits(emf_, 0, NULL);
261 bool Emf::GetData(void* buffer, uint32_t size) const {
262 DCHECK(emf_ && !hdc_);
263 DCHECK(buffer && size);
264 uint32_t size2 =
265 GetEnhMetaFileBits(emf_, size, reinterpret_cast<BYTE*>(buffer));
266 DCHECK(size2 == size);
267 return size2 == size && size2 != 0;
270 int CALLBACK Emf::SafePlaybackProc(HDC hdc,
271 HANDLETABLE* handle_table,
272 const ENHMETARECORD* record,
273 int objects_count,
274 LPARAM param) {
275 Emf::EnumerationContext* context =
276 reinterpret_cast<Emf::EnumerationContext*>(param);
277 context->handle_table = handle_table;
278 context->objects_count = objects_count;
279 context->hdc = hdc;
280 Record record_instance(record);
281 bool success = record_instance.SafePlayback(context);
282 DCHECK(success);
283 return 1;
286 Emf::EnumerationContext::EnumerationContext() {
287 memset(this, 0, sizeof(*this));
290 Emf::Record::Record(const ENHMETARECORD* record)
291 : record_(record) {
292 DCHECK(record_);
295 bool Emf::Record::Play(Emf::EnumerationContext* context) const {
296 return 0 != PlayEnhMetaFileRecord(context->hdc,
297 context->handle_table,
298 record_,
299 context->objects_count);
302 bool Emf::Record::SafePlayback(Emf::EnumerationContext* context) const {
303 // For EMF field description, see [MS-EMF] Enhanced Metafile Format
304 // Specification.
306 // This is the second major EMF breakage I get; the first one being
307 // SetDCBrushColor/SetDCPenColor/DC_PEN/DC_BRUSH being silently ignored.
309 // This function is the guts of the fix for bug 1186598. Some printer drivers
310 // somehow choke on certain EMF records, but calling the corresponding
311 // function directly on the printer HDC is fine. Still, playing the EMF record
312 // fails. Go figure.
314 // The main issue is that SetLayout is totally unsupported on these printers
315 // (HP 4500/4700). I used to call SetLayout and I stopped. I found out this is
316 // not sufficient because GDI32!PlayEnhMetaFile internally calls SetLayout(!)
317 // Damn.
319 // So I resorted to manually parse the EMF records and play them one by one.
320 // The issue with this method compared to using PlayEnhMetaFile to play back
321 // an EMF buffer is that the later silently fixes the matrix to take in
322 // account the matrix currently loaded at the time of the call.
323 // The matrix magic is done transparently when using PlayEnhMetaFile but since
324 // I'm processing one field at a time, I need to do the fixup myself. Note
325 // that PlayEnhMetaFileRecord doesn't fix the matrix correctly even when
326 // called inside an EnumEnhMetaFile loop. Go figure (bis).
328 // So when I see a EMR_SETWORLDTRANSFORM and EMR_MODIFYWORLDTRANSFORM, I need
329 // to fix the matrix according to the matrix previously loaded before playing
330 // back the buffer. Otherwise, the previously loaded matrix would be ignored
331 // and the EMF buffer would always be played back at its native resolution.
332 // Duh.
334 // I also use this opportunity to skip over eventual EMR_SETLAYOUT record that
335 // could remain.
337 // Another tweak we make is for JPEGs/PNGs in calls to StretchDIBits.
338 // (Our Pepper plugin code uses a JPEG). If the printer does not support
339 // JPEGs/PNGs natively we decompress the JPEG/PNG and then set it to the
340 // device.
341 // TODO(sanjeevr): We should also add JPEG/PNG support for SetSIBitsToDevice
343 // We also process any custom EMR_GDICOMMENT records which are our
344 // placeholders for StartPage and EndPage.
345 // Note: I should probably care about view ports and clipping, eventually.
346 bool res = false;
347 const XFORM* base_matrix = context->base_matrix;
348 switch (record()->iType) {
349 case EMR_STRETCHDIBITS: {
350 const EMRSTRETCHDIBITS * sdib_record =
351 reinterpret_cast<const EMRSTRETCHDIBITS*>(record());
352 const BYTE* record_start = reinterpret_cast<const BYTE *>(record());
353 const BITMAPINFOHEADER *bmih =
354 reinterpret_cast<const BITMAPINFOHEADER *>(record_start +
355 sdib_record->offBmiSrc);
356 const BYTE* bits = record_start + sdib_record->offBitsSrc;
357 bool play_normally = true;
358 res = false;
359 HDC hdc = context->hdc;
360 scoped_ptr<SkBitmap> bitmap;
361 if (bmih->biCompression == BI_JPEG) {
362 if (!DIBFormatNativelySupported(hdc, CHECKJPEGFORMAT, bits,
363 bmih->biSizeImage)) {
364 play_normally = false;
365 bitmap.reset(gfx::JPEGCodec::Decode(bits, bmih->biSizeImage));
367 } else if (bmih->biCompression == BI_PNG) {
368 if (!DIBFormatNativelySupported(hdc, CHECKPNGFORMAT, bits,
369 bmih->biSizeImage)) {
370 play_normally = false;
371 bitmap.reset(new SkBitmap());
372 gfx::PNGCodec::Decode(bits, bmih->biSizeImage, bitmap.get());
375 if (!play_normally) {
376 DCHECK(bitmap.get());
377 if (bitmap.get()) {
378 SkAutoLockPixels lock(*bitmap.get());
379 DCHECK_EQ(bitmap->colorType(), kN32_SkColorType);
380 const uint32_t* pixels =
381 static_cast<const uint32_t*>(bitmap->getPixels());
382 if (pixels == NULL) {
383 NOTREACHED();
384 return false;
386 BITMAPINFOHEADER bmi = {0};
387 gfx::CreateBitmapHeader(bitmap->width(), bitmap->height(), &bmi);
388 res = (0 != StretchDIBits(hdc, sdib_record->xDest, sdib_record->yDest,
389 sdib_record->cxDest,
390 sdib_record->cyDest, sdib_record->xSrc,
391 sdib_record->ySrc,
392 sdib_record->cxSrc, sdib_record->cySrc,
393 pixels,
394 reinterpret_cast<const BITMAPINFO *>(&bmi),
395 sdib_record->iUsageSrc,
396 sdib_record->dwRop));
398 } else {
399 res = Play(context);
401 break;
403 case EMR_SETWORLDTRANSFORM: {
404 DCHECK_EQ(record()->nSize, sizeof(DWORD) * 2 + sizeof(XFORM));
405 const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm);
406 HDC hdc = context->hdc;
407 if (base_matrix) {
408 res = 0 != SetWorldTransform(hdc, base_matrix) &&
409 ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY);
410 } else {
411 res = 0 != SetWorldTransform(hdc, xform);
413 break;
415 case EMR_MODIFYWORLDTRANSFORM: {
416 DCHECK_EQ(record()->nSize,
417 sizeof(DWORD) * 2 + sizeof(XFORM) + sizeof(DWORD));
418 const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm);
419 const DWORD* option = reinterpret_cast<const DWORD*>(xform + 1);
420 HDC hdc = context->hdc;
421 switch (*option) {
422 case MWT_IDENTITY:
423 if (base_matrix) {
424 res = 0 != SetWorldTransform(hdc, base_matrix);
425 } else {
426 res = 0 != ModifyWorldTransform(hdc, xform, MWT_IDENTITY);
428 break;
429 case MWT_LEFTMULTIPLY:
430 case MWT_RIGHTMULTIPLY:
431 res = 0 != ModifyWorldTransform(hdc, xform, *option);
432 break;
433 case 4: // MWT_SET
434 if (base_matrix) {
435 res = 0 != SetWorldTransform(hdc, base_matrix) &&
436 ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY);
437 } else {
438 res = 0 != SetWorldTransform(hdc, xform);
440 break;
441 default:
442 res = false;
443 break;
445 break;
447 case EMR_SETLAYOUT:
448 // Ignore it.
449 res = true;
450 break;
451 default: {
452 res = Play(context);
453 break;
456 return res;
459 bool Emf::StartPage(const gfx::Size& /*page_size*/,
460 const gfx::Rect& /*content_area*/,
461 const float& /*scale_factor*/) {
462 return true;
465 bool Emf::FinishPage() {
466 return true;
469 Emf::Enumerator::Enumerator(const Emf& emf, HDC context, const RECT* rect) {
470 items_.clear();
471 if (!EnumEnhMetaFile(context,
472 emf.emf(),
473 &Emf::Enumerator::EnhMetaFileProc,
474 reinterpret_cast<void*>(this),
475 rect)) {
476 NOTREACHED();
477 items_.clear();
479 DCHECK_EQ(context_.hdc, context);
482 Emf::Enumerator::~Enumerator() {
485 Emf::Enumerator::const_iterator Emf::Enumerator::begin() const {
486 return items_.begin();
489 Emf::Enumerator::const_iterator Emf::Enumerator::end() const {
490 return items_.end();
493 int CALLBACK Emf::Enumerator::EnhMetaFileProc(HDC hdc,
494 HANDLETABLE* handle_table,
495 const ENHMETARECORD* record,
496 int objects_count,
497 LPARAM param) {
498 Enumerator& emf = *reinterpret_cast<Enumerator*>(param);
499 if (!emf.context_.handle_table) {
500 DCHECK(!emf.context_.handle_table);
501 DCHECK(!emf.context_.objects_count);
502 emf.context_.handle_table = handle_table;
503 emf.context_.objects_count = objects_count;
504 emf.context_.hdc = hdc;
505 } else {
506 DCHECK_EQ(emf.context_.handle_table, handle_table);
507 DCHECK_EQ(emf.context_.objects_count, objects_count);
508 DCHECK_EQ(emf.context_.hdc, hdc);
510 emf.items_.push_back(Record(record));
511 return 1;
514 bool Emf::IsAlphaBlendUsed() const {
515 bool result = false;
516 ::EnumEnhMetaFile(NULL,
517 emf(),
518 &IsAlphaBlendUsedEnumProc,
519 &result,
520 NULL);
521 return result;
524 scoped_ptr<Emf> Emf::RasterizeMetafile(int raster_area_in_pixels) const {
525 gfx::Rect page_bounds = GetPageBounds(1);
526 gfx::Size page_size(page_bounds.size());
527 if (page_size.GetArea() <= 0) {
528 NOTREACHED() << "Metafile is empty";
529 page_bounds = gfx::Rect(1, 1);
532 float scale = sqrt(
533 static_cast<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));
538 RasterBitmap bitmap(page_size);
540 gfx::Rect bitmap_rect(page_size);
541 RECT rect = bitmap_rect.ToRECT();
542 Playback(bitmap.context(), &rect);
544 scoped_ptr<Emf> result(new Emf);
545 result->Init();
546 HDC hdc = result->context();
547 DCHECK(hdc);
548 skia::InitializeDC(hdc);
550 // Params are ignored.
551 result->StartPage(page_bounds.size(), page_bounds, 1);
553 ::ModifyWorldTransform(hdc, NULL, MWT_IDENTITY);
554 XFORM xform = {
555 static_cast<float>(page_bounds.width()) / bitmap_rect.width(),
558 static_cast<float>(page_bounds.height()) / bitmap_rect.height(),
559 static_cast<float>(page_bounds.x()),
560 static_cast<float>(page_bounds.y()),
562 ::SetWorldTransform(hdc, &xform);
563 ::BitBlt(hdc, 0, 0, bitmap_rect.width(), bitmap_rect.height(),
564 bitmap.context(), bitmap_rect.x(), bitmap_rect.y(), SRCCOPY);
566 result->FinishPage();
567 result->FinishDocument();
569 return result.Pass();
572 scoped_ptr<Emf> Emf::RasterizeAlphaBlend() const {
573 gfx::Rect page_bounds = GetPageBounds(1);
574 if (page_bounds.size().GetArea() <= 0) {
575 NOTREACHED() << "Metafile is empty";
576 page_bounds = gfx::Rect(1, 1);
579 RasterBitmap bitmap(page_bounds.size());
581 // Map metafile page_bounds.x(), page_bounds.y() to bitmap 0, 0.
582 XFORM xform = {1,
586 static_cast<float>(-page_bounds.x()),
587 static_cast<float>(-page_bounds.y())};
588 ::SetWorldTransform(bitmap.context(), &xform);
590 scoped_ptr<Emf> result(new Emf);
591 result->Init();
592 HDC hdc = result->context();
593 DCHECK(hdc);
594 skia::InitializeDC(hdc);
596 HDC bitmap_dc = bitmap.context();
597 RECT rect = page_bounds.ToRECT();
598 ::EnumEnhMetaFile(hdc, emf(), &RasterizeAlphaBlendProc, &bitmap_dc, &rect);
600 result->FinishDocument();
602 return result.Pass();
606 } // namespace printing