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/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"
23 const int kCustomGdiCommentSignature
= 0xdeadbabe;
24 struct PageBreakRecord
{
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
,
41 const ENHMETARECORD
*record
,
44 bool* result
= reinterpret_cast<bool*>(data
);
47 switch (record
->iType
) {
48 case EMR_ALPHABLEND
: {
61 bool DIBFormatNativelySupported(HDC dc
, uint32 escape
, const BYTE
* bits
,
63 BOOL supported
= FALSE
;
64 if (ExtEscape(dc
, QUERYESCSUPPORT
, sizeof(escape
),
65 reinterpret_cast<LPCSTR
>(&escape
), 0, 0) > 0) {
66 ExtEscape(dc
, escape
, size
, reinterpret_cast<LPCSTR
>(bits
),
67 sizeof(supported
), reinterpret_cast<LPSTR
>(&supported
));
72 Emf::Emf() : emf_(NULL
), hdc_(NULL
), page_count_(0) {
78 DeleteEnhMetaFile(emf_
);
81 bool Emf::InitToFile(const FilePath
& metafile_path
) {
82 DCHECK(!emf_
&& !hdc_
);
83 hdc_
= CreateEnhMetaFile(NULL
, metafile_path
.value().c_str(), NULL
, NULL
);
88 bool Emf::InitFromFile(const FilePath
& metafile_path
) {
89 DCHECK(!emf_
&& !hdc_
);
90 emf_
= GetEnhMetaFile(metafile_path
.value().c_str());
96 DCHECK(!emf_
&& !hdc_
);
97 hdc_
= CreateEnhMetaFile(NULL
, NULL
, NULL
, NULL
);
102 bool Emf::InitFromData(const void* src_buffer
, uint32 src_buffer_size
) {
103 DCHECK(!emf_
&& !hdc_
);
104 emf_
= SetEnhMetaFileBits(src_buffer_size
,
105 reinterpret_cast<const BYTE
*>(src_buffer
));
109 bool Emf::FinishDocument() {
110 DCHECK(!emf_
&& hdc_
);
111 emf_
= CloseEnhMetaFile(hdc_
);
117 bool Emf::Playback(HDC hdc
, const RECT
* rect
) const {
118 DCHECK(emf_
&& !hdc_
);
121 // Get the natural bounds of the EMF buffer.
122 bounds
= GetPageBounds(1).ToRECT();
125 return PlayEnhMetaFile(hdc
, emf_
, rect
) != 0;
128 bool Emf::SafePlayback(HDC context
) const {
129 DCHECK(emf_
&& !hdc_
);
131 if (!GetWorldTransform(context
, &base_matrix
)) {
135 Emf::EnumerationContext playback_context
;
136 playback_context
.base_matrix
= &base_matrix
;
137 return EnumEnhMetaFile(context
,
139 &Emf::SafePlaybackProc
,
140 reinterpret_cast<void*>(&playback_context
),
141 &GetPageBounds(1).ToRECT()) != 0;
144 gfx::Rect
Emf::GetPageBounds(unsigned int page_number
) const {
145 DCHECK(emf_
&& !hdc_
);
146 DCHECK_EQ(1U, page_number
);
147 ENHMETAHEADER header
;
148 if (GetEnhMetaFileHeader(emf_
, sizeof(header
), &header
) != sizeof(header
)) {
152 // Add 1 to right and bottom because it's inclusive rectangle.
153 // See ENHMETAHEADER.
154 return gfx::Rect(header
.rclBounds
.left
,
155 header
.rclBounds
.top
,
156 header
.rclBounds
.right
- header
.rclBounds
.left
+ 1,
157 header
.rclBounds
.bottom
- header
.rclBounds
.top
+ 1);
160 uint32
Emf::GetDataSize() const {
161 DCHECK(emf_
&& !hdc_
);
162 return GetEnhMetaFileBits(emf_
, 0, NULL
);
165 bool Emf::GetData(void* buffer
, uint32 size
) const {
166 DCHECK(emf_
&& !hdc_
);
167 DCHECK(buffer
&& size
);
169 GetEnhMetaFileBits(emf_
, size
, reinterpret_cast<BYTE
*>(buffer
));
170 DCHECK(size2
== size
);
171 return size2
== size
&& size2
!= 0;
174 bool Emf::GetDataAsVector(std::vector
<uint8
>* buffer
) const {
175 uint32 size
= GetDataSize();
179 buffer
->resize(size
);
180 if (!GetData(&buffer
->front(), size
))
185 bool Emf::SaveTo(const FilePath
& file_path
) const {
186 HANDLE file
= CreateFile(file_path
.value().c_str(), GENERIC_WRITE
,
187 FILE_SHARE_READ
| FILE_SHARE_WRITE
, NULL
,
188 CREATE_ALWAYS
, 0, NULL
);
189 if (file
== INVALID_HANDLE_VALUE
)
192 bool success
= false;
193 std::vector
<uint8
> buffer
;
194 if (GetDataAsVector(&buffer
)) {
196 if (WriteFile(file
, &*buffer
.begin(), static_cast<DWORD
>(buffer
.size()),
198 written
== buffer
.size()) {
206 int CALLBACK
Emf::SafePlaybackProc(HDC hdc
,
207 HANDLETABLE
* handle_table
,
208 const ENHMETARECORD
* record
,
211 Emf::EnumerationContext
* context
=
212 reinterpret_cast<Emf::EnumerationContext
*>(param
);
213 context
->handle_table
= handle_table
;
214 context
->objects_count
= objects_count
;
216 Record
record_instance(record
);
217 bool success
= record_instance
.SafePlayback(context
);
222 Emf::EnumerationContext::EnumerationContext() {
223 memset(this, 0, sizeof(*this));
226 Emf::Record::Record(const ENHMETARECORD
* record
)
231 bool Emf::Record::Play(Emf::EnumerationContext
* context
) const {
232 return 0 != PlayEnhMetaFileRecord(context
->hdc
,
233 context
->handle_table
,
235 context
->objects_count
);
238 bool Emf::Record::SafePlayback(Emf::EnumerationContext
* context
) const {
239 // For EMF field description, see [MS-EMF] Enhanced Metafile Format
242 // This is the second major EMF breakage I get; the first one being
243 // SetDCBrushColor/SetDCPenColor/DC_PEN/DC_BRUSH being silently ignored.
245 // This function is the guts of the fix for bug 1186598. Some printer drivers
246 // somehow choke on certain EMF records, but calling the corresponding
247 // function directly on the printer HDC is fine. Still, playing the EMF record
250 // The main issue is that SetLayout is totally unsupported on these printers
251 // (HP 4500/4700). I used to call SetLayout and I stopped. I found out this is
252 // not sufficient because GDI32!PlayEnhMetaFile internally calls SetLayout(!)
255 // So I resorted to manually parse the EMF records and play them one by one.
256 // The issue with this method compared to using PlayEnhMetaFile to play back
257 // an EMF buffer is that the later silently fixes the matrix to take in
258 // account the matrix currently loaded at the time of the call.
259 // The matrix magic is done transparently when using PlayEnhMetaFile but since
260 // I'm processing one field at a time, I need to do the fixup myself. Note
261 // that PlayEnhMetaFileRecord doesn't fix the matrix correctly even when
262 // called inside an EnumEnhMetaFile loop. Go figure (bis).
264 // So when I see a EMR_SETWORLDTRANSFORM and EMR_MODIFYWORLDTRANSFORM, I need
265 // to fix the matrix according to the matrix previously loaded before playing
266 // back the buffer. Otherwise, the previously loaded matrix would be ignored
267 // and the EMF buffer would always be played back at its native resolution.
270 // I also use this opportunity to skip over eventual EMR_SETLAYOUT record that
273 // Another tweak we make is for JPEGs/PNGs in calls to StretchDIBits.
274 // (Our Pepper plugin code uses a JPEG). If the printer does not support
275 // JPEGs/PNGs natively we decompress the JPEG/PNG and then set it to the
277 // TODO(sanjeevr): We should also add JPEG/PNG support for SetSIBitsToDevice
279 // We also process any custom EMR_GDICOMMENT records which are our
280 // placeholders for StartPage and EndPage.
281 // Note: I should probably care about view ports and clipping, eventually.
283 const XFORM
* base_matrix
= context
->base_matrix
;
284 switch (record()->iType
) {
285 case EMR_STRETCHDIBITS
: {
286 const EMRSTRETCHDIBITS
* sdib_record
=
287 reinterpret_cast<const EMRSTRETCHDIBITS
*>(record());
288 const BYTE
* record_start
= reinterpret_cast<const BYTE
*>(record());
289 const BITMAPINFOHEADER
*bmih
=
290 reinterpret_cast<const BITMAPINFOHEADER
*>(record_start
+
291 sdib_record
->offBmiSrc
);
292 const BYTE
* bits
= record_start
+ sdib_record
->offBitsSrc
;
293 bool play_normally
= true;
295 HDC hdc
= context
->hdc
;
296 scoped_ptr
<SkBitmap
> bitmap
;
297 if (bmih
->biCompression
== BI_JPEG
) {
298 if (!DIBFormatNativelySupported(hdc
, CHECKJPEGFORMAT
, bits
,
299 bmih
->biSizeImage
)) {
300 play_normally
= false;
301 bitmap
.reset(gfx::JPEGCodec::Decode(bits
, bmih
->biSizeImage
));
303 } else if (bmih
->biCompression
== BI_PNG
) {
304 if (!DIBFormatNativelySupported(hdc
, CHECKPNGFORMAT
, bits
,
305 bmih
->biSizeImage
)) {
306 play_normally
= false;
307 bitmap
.reset(new SkBitmap());
308 gfx::PNGCodec::Decode(bits
, bmih
->biSizeImage
, bitmap
.get());
311 if (!play_normally
) {
312 DCHECK(bitmap
.get());
314 SkAutoLockPixels
lock(*bitmap
.get());
315 DCHECK_EQ(bitmap
->config(), SkBitmap::kARGB_8888_Config
);
316 const uint32_t* pixels
=
317 static_cast<const uint32_t*>(bitmap
->getPixels());
318 if (pixels
== NULL
) {
322 BITMAPINFOHEADER bmi
= {0};
323 gfx::CreateBitmapHeader(bitmap
->width(), bitmap
->height(), &bmi
);
324 res
= (0 != StretchDIBits(hdc
, sdib_record
->xDest
, sdib_record
->yDest
,
326 sdib_record
->cyDest
, sdib_record
->xSrc
,
328 sdib_record
->cxSrc
, sdib_record
->cySrc
,
330 reinterpret_cast<const BITMAPINFO
*>(&bmi
),
331 sdib_record
->iUsageSrc
,
332 sdib_record
->dwRop
));
339 case EMR_SETWORLDTRANSFORM
: {
340 DCHECK_EQ(record()->nSize
, sizeof(DWORD
) * 2 + sizeof(XFORM
));
341 const XFORM
* xform
= reinterpret_cast<const XFORM
*>(record()->dParm
);
342 HDC hdc
= context
->hdc
;
344 res
= 0 != SetWorldTransform(hdc
, base_matrix
) &&
345 ModifyWorldTransform(hdc
, xform
, MWT_LEFTMULTIPLY
);
347 res
= 0 != SetWorldTransform(hdc
, xform
);
351 case EMR_MODIFYWORLDTRANSFORM
: {
352 DCHECK_EQ(record()->nSize
,
353 sizeof(DWORD
) * 2 + sizeof(XFORM
) + sizeof(DWORD
));
354 const XFORM
* xform
= reinterpret_cast<const XFORM
*>(record()->dParm
);
355 const DWORD
* option
= reinterpret_cast<const DWORD
*>(xform
+ 1);
356 HDC hdc
= context
->hdc
;
360 res
= 0 != SetWorldTransform(hdc
, base_matrix
);
362 res
= 0 != ModifyWorldTransform(hdc
, xform
, MWT_IDENTITY
);
365 case MWT_LEFTMULTIPLY
:
366 case MWT_RIGHTMULTIPLY
:
367 res
= 0 != ModifyWorldTransform(hdc
, xform
, *option
);
371 res
= 0 != SetWorldTransform(hdc
, base_matrix
) &&
372 ModifyWorldTransform(hdc
, xform
, MWT_LEFTMULTIPLY
);
374 res
= 0 != SetWorldTransform(hdc
, xform
);
387 case EMR_GDICOMMENT
: {
388 const EMRGDICOMMENT
* comment_record
=
389 reinterpret_cast<const EMRGDICOMMENT
*>(record());
390 if (comment_record
->cbData
== sizeof(PageBreakRecord
)) {
391 const PageBreakRecord
* page_break_record
=
392 reinterpret_cast<const PageBreakRecord
*>(comment_record
->Data
);
393 if (page_break_record
&& page_break_record
->IsValid()) {
394 if (page_break_record
->type
== PageBreakRecord::START_PAGE
) {
395 res
= !!::StartPage(context
->hdc
);
396 DCHECK_EQ(0, context
->dc_on_page_start
);
397 context
->dc_on_page_start
= ::SaveDC(context
->hdc
);
398 } else if (page_break_record
->type
== PageBreakRecord::END_PAGE
) {
399 DCHECK_NE(0, context
->dc_on_page_start
);
400 ::RestoreDC(context
->hdc
, context
->dc_on_page_start
);
401 context
->dc_on_page_start
= 0;
402 res
= !!::EndPage(context
->hdc
);
423 SkDevice
* Emf::StartPageForVectorCanvas(
424 const gfx::Size
& page_size
, const gfx::Rect
& content_area
,
425 const float& scale_factor
) {
426 if (!StartPage(page_size
, content_area
, scale_factor
))
429 return skia::VectorPlatformDeviceEmf::CreateDevice(page_size
.width(),
434 bool Emf::StartPage(const gfx::Size
& /*page_size*/,
435 const gfx::Rect
& /*content_area*/,
436 const float& /*scale_factor*/) {
441 PageBreakRecord
record(PageBreakRecord::START_PAGE
);
442 return !!GdiComment(hdc_
, sizeof(record
),
443 reinterpret_cast<const BYTE
*>(&record
));
446 bool Emf::FinishPage() {
450 PageBreakRecord
record(PageBreakRecord::END_PAGE
);
451 return !!GdiComment(hdc_
, sizeof(record
),
452 reinterpret_cast<const BYTE
*>(&record
));
455 Emf::Enumerator::Enumerator(const Emf
& emf
, HDC context
, const RECT
* rect
) {
457 if (!EnumEnhMetaFile(context
,
459 &Emf::Enumerator::EnhMetaFileProc
,
460 reinterpret_cast<void*>(this),
465 DCHECK_EQ(context_
.hdc
, context
);
468 Emf::Enumerator::const_iterator
Emf::Enumerator::begin() const {
469 return items_
.begin();
472 Emf::Enumerator::const_iterator
Emf::Enumerator::end() const {
476 int CALLBACK
Emf::Enumerator::EnhMetaFileProc(HDC hdc
,
477 HANDLETABLE
* handle_table
,
478 const ENHMETARECORD
* record
,
481 Enumerator
& emf
= *reinterpret_cast<Enumerator
*>(param
);
482 if (!emf
.context_
.handle_table
) {
483 DCHECK(!emf
.context_
.handle_table
);
484 DCHECK(!emf
.context_
.objects_count
);
485 emf
.context_
.handle_table
= handle_table
;
486 emf
.context_
.objects_count
= objects_count
;
487 emf
.context_
.hdc
= hdc
;
489 DCHECK_EQ(emf
.context_
.handle_table
, handle_table
);
490 DCHECK_EQ(emf
.context_
.objects_count
, objects_count
);
491 DCHECK_EQ(emf
.context_
.hdc
, hdc
);
493 emf
.items_
.push_back(Record(record
));
497 bool Emf::IsAlphaBlendUsed() const {
499 ::EnumEnhMetaFile(NULL
,
501 &IsAlphaBlendUsedEnumProc
,
507 Emf
* Emf::RasterizeMetafile(int raster_area_in_pixels
) const {
508 gfx::Rect page_bounds
= GetPageBounds(1);
509 gfx::Size
page_size(page_bounds
.size());
510 if (page_size
.GetArea() <= 0) {
511 NOTREACHED() << "Metafile is empty";
512 page_bounds
= gfx::Rect(1, 1);
515 float scale
= sqrt(float(raster_area_in_pixels
) / page_size
.GetArea());
516 page_size
.set_width(std::max
<int>(1, page_size
.width() * scale
));
517 page_size
.set_height(std::max
<int>(1, page_size
.height() * scale
));
519 base::win::ScopedCreateDC
bitmap_dc(::CreateCompatibleDC(NULL
));
521 NOTREACHED() << "Bitmap DC creation failed";
524 ::SetGraphicsMode(bitmap_dc
, GM_ADVANCED
);
527 gfx::CreateBitmapHeader(page_size
.width(), page_size
.height(),
529 base::win::ScopedBitmap
hbitmap(CreateDIBSection(
530 bitmap_dc
, &hdr
, DIB_RGB_COLORS
, &bits
, NULL
, 0));
532 NOTREACHED() << "Raster bitmap creation for printing failed";
534 base::win::ScopedSelectObject
selectBitmap(bitmap_dc
, hbitmap
);
535 RECT rect
= { 0, 0, page_size
.width(), page_size
.height() };
536 HBRUSH white_brush
= static_cast<HBRUSH
>(::GetStockObject(WHITE_BRUSH
));
537 FillRect(bitmap_dc
, &rect
, white_brush
);
539 gfx::Rect
bitmap_rect(page_size
);
540 Playback(bitmap_dc
, &bitmap_rect
.ToRECT());
542 scoped_ptr
<Emf
> result(new Emf
);
544 HDC hdc
= result
->context();
546 skia::InitializeDC(hdc
);
548 // Params are ignored.
549 result
->StartPage(page_bounds
.size(), page_bounds
, 1);
551 ::ModifyWorldTransform(hdc
, NULL
, MWT_IDENTITY
);
553 float(page_bounds
.width()) / bitmap_rect
.width(), 0,
554 0, float(page_bounds
.height()) / bitmap_rect
.height(),
558 ::SetWorldTransform(hdc
, &xform
);
559 ::BitBlt(hdc
, 0, 0, bitmap_rect
.width(), bitmap_rect
.height(),
560 bitmap_dc
, bitmap_rect
.x(), bitmap_rect
.y(), SRCCOPY
);
562 result
->FinishPage();
563 result
->FinishDocument();
565 return result
.release();
568 } // namespace printing