1 // Copyright (c) 2011 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/metrics/histogram.h"
11 #include "base/time.h"
12 #include "skia/ext/vector_platform_device_emf_win.h"
13 #include "third_party/skia/include/core/SkBitmap.h"
14 #include "ui/gfx/codec/jpeg_codec.h"
15 #include "ui/gfx/codec/png_codec.h"
16 #include "ui/gfx/gdi_util.h"
17 #include "ui/gfx/point.h"
18 #include "ui/gfx/rect.h"
19 #include "ui/gfx/size.h"
22 const int kCustomGdiCommentSignature
= 0xdeadbabe;
23 struct PageBreakRecord
{
29 explicit PageBreakRecord(PageBreakType type_in
)
30 : signature(kCustomGdiCommentSignature
), type(type_in
) {
32 bool IsValid() const {
33 return (signature
== kCustomGdiCommentSignature
) &&
34 (type
>= START_PAGE
) && (type
<= END_PAGE
);
41 bool DIBFormatNativelySupported(HDC dc
, uint32 escape
, const BYTE
* bits
,
43 BOOL supported
= FALSE
;
44 if (ExtEscape(dc
, QUERYESCSUPPORT
, sizeof(escape
),
45 reinterpret_cast<LPCSTR
>(&escape
), 0, 0) > 0) {
46 ExtEscape(dc
, escape
, size
, reinterpret_cast<LPCSTR
>(bits
),
47 sizeof(supported
), reinterpret_cast<LPSTR
>(&supported
));
52 Emf::Emf() : emf_(NULL
), hdc_(NULL
), page_count_(0) {
58 DeleteEnhMetaFile(emf_
);
61 bool Emf::InitToFile(const FilePath
& metafile_path
) {
62 DCHECK(!emf_
&& !hdc_
);
63 hdc_
= CreateEnhMetaFile(NULL
, metafile_path
.value().c_str(), NULL
, NULL
);
68 bool Emf::InitFromFile(const FilePath
& metafile_path
) {
69 DCHECK(!emf_
&& !hdc_
);
70 emf_
= GetEnhMetaFile(metafile_path
.value().c_str());
76 DCHECK(!emf_
&& !hdc_
);
77 hdc_
= CreateEnhMetaFile(NULL
, NULL
, NULL
, NULL
);
82 bool Emf::InitFromData(const void* src_buffer
, uint32 src_buffer_size
) {
83 DCHECK(!emf_
&& !hdc_
);
84 emf_
= SetEnhMetaFileBits(src_buffer_size
,
85 reinterpret_cast<const BYTE
*>(src_buffer
));
89 bool Emf::FinishDocument() {
90 DCHECK(!emf_
&& hdc_
);
91 emf_
= CloseEnhMetaFile(hdc_
);
97 bool Emf::Playback(HDC hdc
, const RECT
* rect
) const {
98 DCHECK(emf_
&& !hdc_
);
101 // Get the natural bounds of the EMF buffer.
102 bounds
= GetPageBounds(1).ToRECT();
105 return PlayEnhMetaFile(hdc
, emf_
, rect
) != 0;
108 bool Emf::SafePlayback(HDC context
) const {
109 DCHECK(emf_
&& !hdc_
);
111 if (!GetWorldTransform(context
, &base_matrix
)) {
115 return EnumEnhMetaFile(context
,
117 &Emf::SafePlaybackProc
,
118 reinterpret_cast<void*>(&base_matrix
),
119 &GetPageBounds(1).ToRECT()) != 0;
122 gfx::Rect
Emf::GetPageBounds(unsigned int page_number
) const {
123 DCHECK(emf_
&& !hdc_
);
124 DCHECK_EQ(1U, page_number
);
125 ENHMETAHEADER header
;
126 if (GetEnhMetaFileHeader(emf_
, sizeof(header
), &header
) != sizeof(header
)) {
130 if (header
.rclBounds
.left
== 0 &&
131 header
.rclBounds
.top
== 0 &&
132 header
.rclBounds
.right
== -1 &&
133 header
.rclBounds
.bottom
== -1) {
134 // A freshly created EMF buffer that has no drawing operation has invalid
135 // bounds. Instead of having an (0,0) size, it has a (-1,-1) size. Detect
136 // this special case and returns an empty Rect instead of an invalid one.
139 return gfx::Rect(header
.rclBounds
.left
,
140 header
.rclBounds
.top
,
141 header
.rclBounds
.right
- header
.rclBounds
.left
,
142 header
.rclBounds
.bottom
- header
.rclBounds
.top
);
145 uint32
Emf::GetDataSize() const {
146 DCHECK(emf_
&& !hdc_
);
147 return GetEnhMetaFileBits(emf_
, 0, NULL
);
150 bool Emf::GetData(void* buffer
, uint32 size
) const {
151 DCHECK(emf_
&& !hdc_
);
152 DCHECK(buffer
&& size
);
154 GetEnhMetaFileBits(emf_
, size
, reinterpret_cast<BYTE
*>(buffer
));
155 DCHECK(size2
== size
);
156 return size2
== size
&& size2
!= 0;
159 bool Emf::GetDataAsVector(std::vector
<uint8
>* buffer
) const {
160 uint32 size
= GetDataSize();
164 buffer
->resize(size
);
165 if (!GetData(&buffer
->front(), size
))
170 bool Emf::SaveTo(const FilePath
& file_path
) const {
171 HANDLE file
= CreateFile(file_path
.value().c_str(), GENERIC_WRITE
,
172 FILE_SHARE_READ
| FILE_SHARE_WRITE
, NULL
,
173 CREATE_ALWAYS
, 0, NULL
);
174 if (file
== INVALID_HANDLE_VALUE
)
177 bool success
= false;
178 std::vector
<uint8
> buffer
;
179 if (GetDataAsVector(&buffer
)) {
181 if (WriteFile(file
, &*buffer
.begin(), static_cast<DWORD
>(buffer
.size()),
183 written
== buffer
.size()) {
191 int CALLBACK
Emf::SafePlaybackProc(HDC hdc
,
192 HANDLETABLE
* handle_table
,
193 const ENHMETARECORD
* record
,
196 const XFORM
* base_matrix
= reinterpret_cast<const XFORM
*>(param
);
197 EnumerationContext context
;
198 context
.handle_table
= handle_table
;
199 context
.objects_count
= objects_count
;
201 Record
record_instance(&context
, record
);
202 bool success
= record_instance
.SafePlayback(base_matrix
);
207 Emf::Record::Record(const EnumerationContext
* context
,
208 const ENHMETARECORD
* record
)
214 bool Emf::Record::Play() const {
215 return 0 != PlayEnhMetaFileRecord(context_
->hdc
,
216 context_
->handle_table
,
218 context_
->objects_count
);
221 bool Emf::Record::SafePlayback(const XFORM
* base_matrix
) const {
222 // For EMF field description, see [MS-EMF] Enhanced Metafile Format
225 // This is the second major EMF breakage I get; the first one being
226 // SetDCBrushColor/SetDCPenColor/DC_PEN/DC_BRUSH being silently ignored.
228 // This function is the guts of the fix for bug 1186598. Some printer drivers
229 // somehow choke on certain EMF records, but calling the corresponding
230 // function directly on the printer HDC is fine. Still, playing the EMF record
233 // The main issue is that SetLayout is totally unsupported on these printers
234 // (HP 4500/4700). I used to call SetLayout and I stopped. I found out this is
235 // not sufficient because GDI32!PlayEnhMetaFile internally calls SetLayout(!)
238 // So I resorted to manually parse the EMF records and play them one by one.
239 // The issue with this method compared to using PlayEnhMetaFile to play back
240 // an EMF buffer is that the later silently fixes the matrix to take in
241 // account the matrix currently loaded at the time of the call.
242 // The matrix magic is done transparently when using PlayEnhMetaFile but since
243 // I'm processing one field at a time, I need to do the fixup myself. Note
244 // that PlayEnhMetaFileRecord doesn't fix the matrix correctly even when
245 // called inside an EnumEnhMetaFile loop. Go figure (bis).
247 // So when I see a EMR_SETWORLDTRANSFORM and EMR_MODIFYWORLDTRANSFORM, I need
248 // to fix the matrix according to the matrix previously loaded before playing
249 // back the buffer. Otherwise, the previously loaded matrix would be ignored
250 // and the EMF buffer would always be played back at its native resolution.
253 // I also use this opportunity to skip over eventual EMR_SETLAYOUT record that
256 // Another tweak we make is for JPEGs/PNGs in calls to StretchDIBits.
257 // (Our Pepper plugin code uses a JPEG). If the printer does not support
258 // JPEGs/PNGs natively we decompress the JPEG/PNG and then set it to the
260 // TODO(sanjeevr): We should also add JPEG/PNG support for SetSIBitsToDevice
262 // We also process any custom EMR_GDICOMMENT records which are our
263 // placeholders for StartPage and EndPage.
264 // Note: I should probably care about view ports and clipping, eventually.
266 switch (record()->iType
) {
267 case EMR_STRETCHDIBITS
: {
268 const EMRSTRETCHDIBITS
* sdib_record
=
269 reinterpret_cast<const EMRSTRETCHDIBITS
*>(record());
270 const BYTE
* record_start
= reinterpret_cast<const BYTE
*>(record());
271 const BITMAPINFOHEADER
*bmih
=
272 reinterpret_cast<const BITMAPINFOHEADER
*>(record_start
+
273 sdib_record
->offBmiSrc
);
274 const BYTE
* bits
= record_start
+ sdib_record
->offBitsSrc
;
275 bool play_normally
= true;
277 HDC hdc
= context_
->hdc
;
278 scoped_ptr
<SkBitmap
> bitmap
;
279 if (bmih
->biCompression
== BI_JPEG
) {
280 if (!DIBFormatNativelySupported(hdc
, CHECKJPEGFORMAT
, bits
,
281 bmih
->biSizeImage
)) {
282 play_normally
= false;
283 base::TimeTicks start_time
= base::TimeTicks::Now();
284 bitmap
.reset(gfx::JPEGCodec::Decode(bits
, bmih
->biSizeImage
));
285 UMA_HISTOGRAM_TIMES("Printing.JPEGDecompressTime",
286 base::TimeTicks::Now() - start_time
);
288 } else if (bmih
->biCompression
== BI_PNG
) {
289 if (!DIBFormatNativelySupported(hdc
, CHECKPNGFORMAT
, bits
,
290 bmih
->biSizeImage
)) {
291 play_normally
= false;
292 bitmap
.reset(new SkBitmap());
293 base::TimeTicks start_time
= base::TimeTicks::Now();
294 gfx::PNGCodec::Decode(bits
, bmih
->biSizeImage
, bitmap
.get());
295 UMA_HISTOGRAM_TIMES("Printing.PNGDecompressTime",
296 base::TimeTicks::Now() - start_time
);
299 if (!play_normally
) {
300 DCHECK(bitmap
.get());
302 SkAutoLockPixels
lock(*bitmap
.get());
303 DCHECK_EQ(bitmap
->getConfig(), SkBitmap::kARGB_8888_Config
);
304 const uint32_t* pixels
=
305 static_cast<const uint32_t*>(bitmap
->getPixels());
306 if (pixels
== NULL
) {
310 BITMAPINFOHEADER bmi
= {0};
311 gfx::CreateBitmapHeader(bitmap
->width(), bitmap
->height(), &bmi
);
312 res
= (0 != StretchDIBits(hdc
, sdib_record
->xDest
, sdib_record
->yDest
,
314 sdib_record
->cyDest
, sdib_record
->xSrc
,
316 sdib_record
->cxSrc
, sdib_record
->cySrc
,
318 reinterpret_cast<const BITMAPINFO
*>(&bmi
),
319 sdib_record
->iUsageSrc
,
320 sdib_record
->dwRop
));
327 case EMR_SETWORLDTRANSFORM
: {
328 DCHECK_EQ(record()->nSize
, sizeof(DWORD
) * 2 + sizeof(XFORM
));
329 const XFORM
* xform
= reinterpret_cast<const XFORM
*>(record()->dParm
);
330 HDC hdc
= context_
->hdc
;
332 res
= 0 != SetWorldTransform(hdc
, base_matrix
) &&
333 ModifyWorldTransform(hdc
, xform
, MWT_LEFTMULTIPLY
);
335 res
= 0 != SetWorldTransform(hdc
, xform
);
339 case EMR_MODIFYWORLDTRANSFORM
: {
340 DCHECK_EQ(record()->nSize
,
341 sizeof(DWORD
) * 2 + sizeof(XFORM
) + sizeof(DWORD
));
342 const XFORM
* xform
= reinterpret_cast<const XFORM
*>(record()->dParm
);
343 const DWORD
* option
= reinterpret_cast<const DWORD
*>(xform
+ 1);
344 HDC hdc
= context_
->hdc
;
348 res
= 0 != SetWorldTransform(hdc
, base_matrix
);
350 res
= 0 != ModifyWorldTransform(hdc
, xform
, MWT_IDENTITY
);
353 case MWT_LEFTMULTIPLY
:
354 case MWT_RIGHTMULTIPLY
:
355 res
= 0 != ModifyWorldTransform(hdc
, xform
, *option
);
359 res
= 0 != SetWorldTransform(hdc
, base_matrix
) &&
360 ModifyWorldTransform(hdc
, xform
, MWT_LEFTMULTIPLY
);
362 res
= 0 != SetWorldTransform(hdc
, xform
);
375 case EMR_GDICOMMENT
: {
376 const EMRGDICOMMENT
* comment_record
=
377 reinterpret_cast<const EMRGDICOMMENT
*>(record());
378 if (comment_record
->cbData
== sizeof(PageBreakRecord
)) {
379 const PageBreakRecord
* page_break_record
=
380 reinterpret_cast<const PageBreakRecord
*>(comment_record
->Data
);
381 if (page_break_record
&& page_break_record
->IsValid()) {
382 if (page_break_record
->type
== PageBreakRecord::START_PAGE
) {
383 res
= !!::StartPage(context_
->hdc
);
384 } else if (page_break_record
->type
== PageBreakRecord::END_PAGE
) {
385 res
= !!::EndPage(context_
->hdc
);
406 SkDevice
* Emf::StartPageForVectorCanvas(
407 const gfx::Size
& page_size
, const gfx::Rect
& content_area
,
408 const float& scale_factor
) {
409 if (!StartPage(page_size
, content_area
, scale_factor
))
412 return skia::VectorPlatformDeviceEmf::CreateDevice(page_size
.width(),
417 bool Emf::StartPage(const gfx::Size
& /*page_size*/,
418 const gfx::Rect
& /*content_area*/,
419 const float& /*scale_factor*/) {
424 PageBreakRecord
record(PageBreakRecord::START_PAGE
);
425 return !!GdiComment(hdc_
, sizeof(record
),
426 reinterpret_cast<const BYTE
*>(&record
));
429 bool Emf::FinishPage() {
433 PageBreakRecord
record(PageBreakRecord::END_PAGE
);
434 return !!GdiComment(hdc_
, sizeof(record
),
435 reinterpret_cast<const BYTE
*>(&record
));
438 Emf::Enumerator::Enumerator(const Emf
& emf
, HDC context
, const RECT
* rect
) {
439 context_
.handle_table
= NULL
;
440 context_
.objects_count
= 0;
443 if (!EnumEnhMetaFile(context
,
445 &Emf::Enumerator::EnhMetaFileProc
,
446 reinterpret_cast<void*>(this),
451 DCHECK_EQ(context_
.hdc
, context
);
454 Emf::Enumerator::const_iterator
Emf::Enumerator::begin() const {
455 return items_
.begin();
458 Emf::Enumerator::const_iterator
Emf::Enumerator::end() const {
462 int CALLBACK
Emf::Enumerator::EnhMetaFileProc(HDC hdc
,
463 HANDLETABLE
* handle_table
,
464 const ENHMETARECORD
* record
,
467 Enumerator
& emf
= *reinterpret_cast<Enumerator
*>(param
);
468 if (!emf
.context_
.handle_table
) {
469 DCHECK(!emf
.context_
.handle_table
);
470 DCHECK(!emf
.context_
.objects_count
);
471 emf
.context_
.handle_table
= handle_table
;
472 emf
.context_
.objects_count
= objects_count
;
473 emf
.context_
.hdc
= hdc
;
475 DCHECK_EQ(emf
.context_
.handle_table
, handle_table
);
476 DCHECK_EQ(emf
.context_
.objects_count
, objects_count
);
477 DCHECK_EQ(emf
.context_
.hdc
, hdc
);
479 emf
.items_
.push_back(Record(&emf
.context_
, record
));
483 } // namespace printing