1 // Copyright 2012 The Chromium Authors
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"
12 #include "base/check_op.h"
13 #include "base/files/file.h"
14 #include "base/files/file_path.h"
15 #include "base/notreached.h"
16 #include "base/numerics/safe_conversions.h"
17 #include "printing/mojom/print.mojom.h"
18 #include "skia/ext/skia_utils_win.h"
19 #include "third_party/skia/include/core/SkBitmap.h"
20 #include "ui/gfx/codec/jpeg_codec.h"
21 #include "ui/gfx/codec/png_codec.h"
22 #include "ui/gfx/geometry/rect.h"
23 #include "ui/gfx/geometry/size.h"
29 bool DIBFormatNativelySupported(HDC dc,
33 BOOL supported = FALSE;
34 if (ExtEscape(dc, QUERYESCSUPPORT, sizeof(escape),
35 reinterpret_cast<LPCSTR>(&escape), 0, 0) > 0) {
36 ExtEscape(dc, escape, size, reinterpret_cast<LPCSTR>(bits),
37 sizeof(supported), reinterpret_cast<LPSTR>(&supported));
44 Emf::Emf() : emf_(nullptr), hdc_(nullptr) {}
53 DeleteEnhMetaFile(emf_);
57 bool Emf::InitToFile(const base::FilePath& metafile_path) {
58 DCHECK(!emf_ && !hdc_);
59 hdc_ = CreateEnhMetaFile(nullptr, metafile_path.value().c_str(), nullptr,
65 bool Emf::InitFromFile(const base::FilePath& metafile_path) {
66 DCHECK(!emf_ && !hdc_);
67 emf_ = GetEnhMetaFile(metafile_path.value().c_str());
73 DCHECK(!emf_ && !hdc_);
74 hdc_ = CreateEnhMetaFile(nullptr, nullptr, nullptr, nullptr);
79 bool Emf::InitFromData(base::span<const uint8_t> data) {
80 DCHECK(!emf_ && !hdc_);
81 if (!base::IsValueInRangeForNumericType<UINT>(data.size()))
84 emf_ = SetEnhMetaFileBits(static_cast<UINT>(data.size()), data.data());
88 bool Emf::FinishDocument() {
89 DCHECK(!emf_ && hdc_);
90 emf_ = CloseEnhMetaFile(hdc_);
96 bool Emf::Playback(HDC hdc, const RECT* rect) const {
97 DCHECK(emf_ && !hdc_);
100 // Get the natural bounds of the EMF buffer.
101 bounds = GetPageBounds(1).ToRECT();
104 return PlayEnhMetaFile(hdc, emf_, rect) != 0;
107 bool Emf::SafePlayback(HDC context) const {
108 DCHECK(emf_ && !hdc_);
110 if (!GetWorldTransform(context, &base_matrix)) {
114 Emf::EnumerationContext playback_context;
115 playback_context.base_matrix = &base_matrix;
116 gfx::Rect bound = GetPageBounds(1);
117 RECT rect = bound.ToRECT();
118 return bound.IsEmpty() ||
119 EnumEnhMetaFile(context, emf_, &Emf::SafePlaybackProc,
120 reinterpret_cast<void*>(&playback_context),
124 gfx::Rect Emf::GetPageBounds(unsigned int page_number) const {
125 DCHECK(emf_ && !hdc_);
126 DCHECK_EQ(1U, page_number);
127 ENHMETAHEADER header;
128 if (GetEnhMetaFileHeader(emf_, sizeof(header), &header) != sizeof(header)) {
132 // Add 1 to right and bottom because it's inclusive rectangle.
133 // See ENHMETAHEADER.
134 return gfx::Rect(header.rclBounds.left, header.rclBounds.top,
135 header.rclBounds.right - header.rclBounds.left + 1,
136 header.rclBounds.bottom - header.rclBounds.top + 1);
139 unsigned int Emf::GetPageCount() const {
143 HDC Emf::context() const {
147 uint32_t Emf::GetDataSize() const {
148 DCHECK(emf_ && !hdc_);
149 return GetEnhMetaFileBits(emf_, 0, nullptr);
152 bool Emf::GetData(void* buffer, uint32_t size) const {
153 DCHECK(emf_ && !hdc_);
154 DCHECK(buffer && size);
156 GetEnhMetaFileBits(emf_, size, reinterpret_cast<BYTE*>(buffer));
157 DCHECK(size2 == size);
158 return size2 == size && size2 != 0;
161 bool Emf::ShouldCopySharedMemoryRegionData() const {
162 // `InitFromData()` operates directly upon memory provide to it, so any
163 // caller for cases where this data is shared cross-process should have the
164 // data copied before it is operated upon.
168 mojom::MetafileDataType Emf::GetDataType() const {
169 return mojom::MetafileDataType::kEMF;
172 int CALLBACK Emf::SafePlaybackProc(HDC hdc,
173 HANDLETABLE* handle_table,
174 const ENHMETARECORD* record,
177 Emf::EnumerationContext* context =
178 reinterpret_cast<Emf::EnumerationContext*>(param);
179 context->handle_table = handle_table;
180 context->objects_count = objects_count;
182 Record record_instance(record);
183 bool success = record_instance.SafePlayback(context);
188 Emf::EnumerationContext::EnumerationContext() {
189 memset(this, 0, sizeof(*this));
192 Emf::Record::Record(const ENHMETARECORD* record) : record_(record) {
196 bool Emf::Record::Play(Emf::EnumerationContext* context) const {
197 return 0 != PlayEnhMetaFileRecord(context->hdc, context->handle_table,
198 record_, context->objects_count);
201 bool Emf::Record::SafePlayback(Emf::EnumerationContext* context) const {
202 // For EMF field description, see [MS-EMF] Enhanced Metafile Format
205 // This is the second major EMF breakage I get; the first one being
206 // SetDCBrushColor/SetDCPenColor/DC_PEN/DC_BRUSH being silently ignored.
208 // This function is the guts of the fix for bug 1186598. Some printer drivers
209 // somehow choke on certain EMF records, but calling the corresponding
210 // function directly on the printer HDC is fine. Still, playing the EMF record
213 // The main issue is that SetLayout is totally unsupported on these printers
214 // (HP 4500/4700). I used to call SetLayout and I stopped. I found out this is
215 // not sufficient because GDI32!PlayEnhMetaFile internally calls SetLayout(!)
218 // So I resorted to manually parse the EMF records and play them one by one.
219 // The issue with this method compared to using PlayEnhMetaFile to play back
220 // an EMF buffer is that the later silently fixes the matrix to take in
221 // account the matrix currently loaded at the time of the call.
222 // The matrix magic is done transparently when using PlayEnhMetaFile but since
223 // I'm processing one field at a time, I need to do the fixup myself. Note
224 // that PlayEnhMetaFileRecord doesn't fix the matrix correctly even when
225 // called inside an EnumEnhMetaFile loop. Go figure (bis).
227 // So when I see a EMR_SETWORLDTRANSFORM and EMR_MODIFYWORLDTRANSFORM, I need
228 // to fix the matrix according to the matrix previously loaded before playing
229 // back the buffer. Otherwise, the previously loaded matrix would be ignored
230 // and the EMF buffer would always be played back at its native resolution.
233 // I also use this opportunity to skip over eventual EMR_SETLAYOUT record that
236 // Another tweak we make is for JPEGs/PNGs in calls to StretchDIBits.
237 // (Our Pepper plugin code uses a JPEG). If the printer does not support
238 // JPEGs/PNGs natively we decompress the JPEG/PNG and then set it to the
240 // TODO(sanjeevr): We should also add JPEG/PNG support for SetSIBitsToDevice
242 // We also process any custom EMR_GDICOMMENT records which are our
243 // placeholders for StartPage and EndPage.
244 // Note: I should probably care about view ports and clipping, eventually.
246 const XFORM* base_matrix = context->base_matrix;
247 switch (record()->iType) {
248 case EMR_STRETCHDIBITS: {
249 const EMRSTRETCHDIBITS* sdib_record =
250 reinterpret_cast<const EMRSTRETCHDIBITS*>(record());
251 const BYTE* record_start = reinterpret_cast<const BYTE*>(record());
252 const BITMAPINFOHEADER* bmih = reinterpret_cast<const BITMAPINFOHEADER*>(
253 record_start + sdib_record->offBmiSrc);
254 const BYTE* bits = record_start + sdib_record->offBitsSrc;
255 bool play_normally = true;
257 HDC hdc = context->hdc;
258 std::unique_ptr<SkBitmap> bitmap;
259 if (bmih->biCompression == BI_JPEG) {
260 if (!DIBFormatNativelySupported(hdc, CHECKJPEGFORMAT, bits,
261 bmih->biSizeImage)) {
262 play_normally = false;
263 bitmap = gfx::JPEGCodec::Decode(bits, bmih->biSizeImage);
265 DCHECK(!bitmap->isNull());
267 } else if (bmih->biCompression == BI_PNG) {
268 if (!DIBFormatNativelySupported(hdc, CHECKPNGFORMAT, bits,
269 bmih->biSizeImage)) {
270 play_normally = false;
271 bitmap = std::make_unique<SkBitmap>();
273 gfx::PNGCodec::Decode(bits, bmih->biSizeImage, &*bitmap);
275 DCHECK(!bitmap->isNull());
281 const uint32_t* pixels =
282 static_cast<const uint32_t*>(bitmap->getPixels());
287 BITMAPINFOHEADER bmi = {0};
288 skia::CreateBitmapHeaderForN32SkBitmap(*bitmap, &bmi);
290 (0 != StretchDIBits(hdc, sdib_record->xDest, sdib_record->yDest,
291 sdib_record->cxDest, sdib_record->cyDest,
292 sdib_record->xSrc, sdib_record->ySrc,
293 sdib_record->cxSrc, sdib_record->cySrc, pixels,
294 reinterpret_cast<const BITMAPINFO*>(&bmi),
295 sdib_record->iUsageSrc, sdib_record->dwRop));
299 case EMR_SETWORLDTRANSFORM: {
300 DCHECK_EQ(record()->nSize, sizeof(DWORD) * 2 + sizeof(XFORM));
301 const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm);
302 HDC hdc = context->hdc;
304 res = 0 != SetWorldTransform(hdc, base_matrix) &&
305 ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY);
307 res = 0 != SetWorldTransform(hdc, xform);
311 case EMR_MODIFYWORLDTRANSFORM: {
312 DCHECK_EQ(record()->nSize,
313 sizeof(DWORD) * 2 + sizeof(XFORM) + sizeof(DWORD));
314 const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm);
315 const DWORD* option = reinterpret_cast<const DWORD*>(xform + 1);
316 HDC hdc = context->hdc;
320 res = 0 != SetWorldTransform(hdc, base_matrix);
322 res = 0 != ModifyWorldTransform(hdc, xform, MWT_IDENTITY);
325 case MWT_LEFTMULTIPLY:
326 case MWT_RIGHTMULTIPLY:
327 res = 0 != ModifyWorldTransform(hdc, xform, *option);
331 res = 0 != SetWorldTransform(hdc, base_matrix) &&
332 ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY);
334 res = 0 != SetWorldTransform(hdc, xform);
355 void Emf::StartPage(const gfx::Size& /*page_size*/,
356 const gfx::Rect& /*content_area*/,
357 float /*scale_factor*/,
358 mojom::PageOrientation /*page_orientation*/) {}
360 bool Emf::FinishPage() {
364 Emf::Enumerator::Enumerator(const Emf& emf, HDC context, const RECT* rect) {
366 if (!EnumEnhMetaFile(context, emf.emf(), &Emf::Enumerator::EnhMetaFileProc,
367 reinterpret_cast<void*>(this), rect)) {
371 DCHECK_EQ(context_.hdc, context);
374 Emf::Enumerator::~Enumerator() {}
376 Emf::Enumerator::const_iterator Emf::Enumerator::begin() const {
377 return items_.begin();
380 Emf::Enumerator::const_iterator Emf::Enumerator::end() const {
384 int CALLBACK Emf::Enumerator::EnhMetaFileProc(HDC hdc,
385 HANDLETABLE* handle_table,
386 const ENHMETARECORD* record,
389 Enumerator& emf = *reinterpret_cast<Enumerator*>(param);
390 if (!emf.context_.handle_table) {
391 DCHECK(!emf.context_.handle_table);
392 DCHECK(!emf.context_.objects_count);
393 emf.context_.handle_table = handle_table;
394 emf.context_.objects_count = objects_count;
395 emf.context_.hdc = hdc;
397 DCHECK_EQ(emf.context_.handle_table, handle_table);
398 DCHECK_EQ(emf.context_.objects_count, objects_count);
399 DCHECK_EQ(emf.context_.hdc, hdc);
401 emf.items_.push_back(Record(record));
405 } // namespace printing