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"
24 int CALLBACK
IsAlphaBlendUsedEnumProc(HDC
,
26 const ENHMETARECORD
*record
,
29 bool* result
= reinterpret_cast<bool*>(data
);
32 switch (record
->iType
) {
33 case EMR_ALPHABLEND
: {
42 int CALLBACK
RasterizeAlphaBlendProc(HDC metafile_dc
,
43 HANDLETABLE
* handle_table
,
44 const ENHMETARECORD
*record
,
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
71 case EMR_CREATEBRUSHINDIRECT
:
72 case EMR_CREATECOLORSPACE
:
73 case EMR_CREATECOLORSPACEW
:
74 case EMR_CREATEDIBPATTERNBRUSHPT
:
75 case EMR_CREATEMONOBRUSH
:
76 case EMR_CREATEPALETTE
:
78 case EMR_DELETECOLORSPACE
:
79 case EMR_DELETEOBJECT
:
80 case EMR_EXTCREATEFONTINDIRECTW
:
81 // Play object creation command only once.
85 // Play this command to the metafile DC.
86 ::PlayEnhMetaFileRecord(metafile_dc
, handle_table
, record
, num_objects
);
89 return 1; // Continue enumeration
92 // Bitmapt for rasterization.
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";
102 ::SetGraphicsMode(context_
.Get(), GM_ADVANCED
);
104 gfx::Rect
bitmap_rect(raster_size
);
105 gfx::CreateBitmapHeader(raster_size
.width(), raster_size
.height(),
107 bitmap_
.Set(::CreateDIBSection(context_
.Get(), &header_
, DIB_RGB_COLORS
,
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
)));
119 ::SelectObject(context_
.Get(), saved_object_
);
122 HDC
context() const {
123 return context_
.Get();
126 base::win::ScopedCreateDC context_
;
128 base::win::ScopedBitmap bitmap_
;
129 HGDIOBJ saved_object_
;
132 DISALLOW_COPY_AND_ASSIGN(RasterBitmap
);
141 bool DIBFormatNativelySupported(HDC dc
, uint32_t escape
, const BYTE
* bits
,
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
));
152 Emf::Emf() : emf_(NULL
), hdc_(NULL
) {
162 DeleteEnhMetaFile(emf_
);
166 bool Emf::InitToFile(const base::FilePath
& metafile_path
) {
167 DCHECK(!emf_
&& !hdc_
);
168 hdc_
= CreateEnhMetaFile(NULL
, metafile_path
.value().c_str(), NULL
, NULL
);
173 bool Emf::InitFromFile(const base::FilePath
& metafile_path
) {
174 DCHECK(!emf_
&& !hdc_
);
175 emf_
= GetEnhMetaFile(metafile_path
.value().c_str());
181 DCHECK(!emf_
&& !hdc_
);
182 hdc_
= CreateEnhMetaFile(NULL
, NULL
, NULL
, 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
));
194 bool Emf::FinishDocument() {
195 DCHECK(!emf_
&& hdc_
);
196 emf_
= CloseEnhMetaFile(hdc_
);
202 bool Emf::Playback(HDC hdc
, const RECT
* rect
) const {
203 DCHECK(emf_
&& !hdc_
);
206 // Get the natural bounds of the EMF buffer.
207 bounds
= GetPageBounds(1).ToRECT();
210 return PlayEnhMetaFile(hdc
, emf_
, rect
) != 0;
213 bool Emf::SafePlayback(HDC context
) const {
214 DCHECK(emf_
&& !hdc_
);
216 if (!GetWorldTransform(context
, &base_matrix
)) {
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
,
227 &Emf::SafePlaybackProc
,
228 reinterpret_cast<void*>(&playback_context
),
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
)) {
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 {
252 HDC
Emf::context() const {
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
);
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
,
275 Emf::EnumerationContext
* context
=
276 reinterpret_cast<Emf::EnumerationContext
*>(param
);
277 context
->handle_table
= handle_table
;
278 context
->objects_count
= objects_count
;
280 Record
record_instance(record
);
281 bool success
= record_instance
.SafePlayback(context
);
286 Emf::EnumerationContext::EnumerationContext() {
287 memset(this, 0, sizeof(*this));
290 Emf::Record::Record(const ENHMETARECORD
* record
)
295 bool Emf::Record::Play(Emf::EnumerationContext
* context
) const {
296 return 0 != PlayEnhMetaFileRecord(context
->hdc
,
297 context
->handle_table
,
299 context
->objects_count
);
302 bool Emf::Record::SafePlayback(Emf::EnumerationContext
* context
) const {
303 // For EMF field description, see [MS-EMF] Enhanced Metafile Format
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
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(!)
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.
334 // I also use this opportunity to skip over eventual EMR_SETLAYOUT record that
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
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.
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;
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());
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
) {
386 BITMAPINFOHEADER bmi
= {0};
387 gfx::CreateBitmapHeader(bitmap
->width(), bitmap
->height(), &bmi
);
388 res
= (0 != StretchDIBits(hdc
, sdib_record
->xDest
, sdib_record
->yDest
,
390 sdib_record
->cyDest
, sdib_record
->xSrc
,
392 sdib_record
->cxSrc
, sdib_record
->cySrc
,
394 reinterpret_cast<const BITMAPINFO
*>(&bmi
),
395 sdib_record
->iUsageSrc
,
396 sdib_record
->dwRop
));
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
;
408 res
= 0 != SetWorldTransform(hdc
, base_matrix
) &&
409 ModifyWorldTransform(hdc
, xform
, MWT_LEFTMULTIPLY
);
411 res
= 0 != SetWorldTransform(hdc
, xform
);
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
;
424 res
= 0 != SetWorldTransform(hdc
, base_matrix
);
426 res
= 0 != ModifyWorldTransform(hdc
, xform
, MWT_IDENTITY
);
429 case MWT_LEFTMULTIPLY
:
430 case MWT_RIGHTMULTIPLY
:
431 res
= 0 != ModifyWorldTransform(hdc
, xform
, *option
);
435 res
= 0 != SetWorldTransform(hdc
, base_matrix
) &&
436 ModifyWorldTransform(hdc
, xform
, MWT_LEFTMULTIPLY
);
438 res
= 0 != SetWorldTransform(hdc
, xform
);
459 bool Emf::StartPage(const gfx::Size
& /*page_size*/,
460 const gfx::Rect
& /*content_area*/,
461 const float& /*scale_factor*/) {
465 bool Emf::FinishPage() {
469 Emf::Enumerator::Enumerator(const Emf
& emf
, HDC context
, const RECT
* rect
) {
471 if (!EnumEnhMetaFile(context
,
473 &Emf::Enumerator::EnhMetaFileProc
,
474 reinterpret_cast<void*>(this),
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 {
493 int CALLBACK
Emf::Enumerator::EnhMetaFileProc(HDC hdc
,
494 HANDLETABLE
* handle_table
,
495 const ENHMETARECORD
* record
,
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
;
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
));
514 bool Emf::IsAlphaBlendUsed() const {
516 ::EnumEnhMetaFile(NULL
,
518 &IsAlphaBlendUsedEnumProc
,
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);
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
);
546 HDC hdc
= result
->context();
548 skia::InitializeDC(hdc
);
550 // Params are ignored.
551 result
->StartPage(page_bounds
.size(), page_bounds
, 1);
553 ::ModifyWorldTransform(hdc
, NULL
, MWT_IDENTITY
);
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.
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
);
592 HDC hdc
= result
->context();
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