1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
|
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "printing/emf_win.h"
#include <stdint.h>
#include <algorithm>
#include <memory>
#include "base/check_op.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "printing/mojom/print.mojom.h"
#include "skia/ext/skia_utils_win.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/jpeg_codec.h"
#include "ui/gfx/codec/png_codec.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
namespace printing {
namespace {
bool DIBFormatNativelySupported(HDC dc,
uint32_t escape,
const BYTE* bits,
int size) {
BOOL supported = FALSE;
if (ExtEscape(dc, QUERYESCSUPPORT, sizeof(escape),
reinterpret_cast<LPCSTR>(&escape), 0, 0) > 0) {
ExtEscape(dc, escape, size, reinterpret_cast<LPCSTR>(bits),
sizeof(supported), reinterpret_cast<LPSTR>(&supported));
}
return !!supported;
}
} // namespace
Emf::Emf() : emf_(nullptr), hdc_(nullptr) {}
Emf::~Emf() {
Close();
}
void Emf::Close() {
DCHECK(!hdc_);
if (emf_)
DeleteEnhMetaFile(emf_);
emf_ = nullptr;
}
bool Emf::InitToFile(const base::FilePath& metafile_path) {
DCHECK(!emf_ && !hdc_);
hdc_ = CreateEnhMetaFile(nullptr, metafile_path.value().c_str(), nullptr,
nullptr);
DCHECK(hdc_);
return !!hdc_;
}
bool Emf::InitFromFile(const base::FilePath& metafile_path) {
DCHECK(!emf_ && !hdc_);
emf_ = GetEnhMetaFile(metafile_path.value().c_str());
DCHECK(emf_);
return !!emf_;
}
bool Emf::Init() {
DCHECK(!emf_ && !hdc_);
hdc_ = CreateEnhMetaFile(nullptr, nullptr, nullptr, nullptr);
DCHECK(hdc_);
return !!hdc_;
}
bool Emf::InitFromData(base::span<const uint8_t> data) {
DCHECK(!emf_ && !hdc_);
if (!base::IsValueInRangeForNumericType<UINT>(data.size()))
return false;
emf_ = SetEnhMetaFileBits(static_cast<UINT>(data.size()), data.data());
return !!emf_;
}
bool Emf::FinishDocument() {
DCHECK(!emf_ && hdc_);
emf_ = CloseEnhMetaFile(hdc_);
DCHECK(emf_);
hdc_ = nullptr;
return !!emf_;
}
bool Emf::Playback(HDC hdc, const RECT* rect) const {
DCHECK(emf_ && !hdc_);
RECT bounds;
if (!rect) {
// Get the natural bounds of the EMF buffer.
bounds = GetPageBounds(1).ToRECT();
rect = &bounds;
}
return PlayEnhMetaFile(hdc, emf_, rect) != 0;
}
bool Emf::SafePlayback(HDC context) const {
DCHECK(emf_ && !hdc_);
XFORM base_matrix;
if (!GetWorldTransform(context, &base_matrix)) {
NOTREACHED();
return false;
}
Emf::EnumerationContext playback_context;
playback_context.base_matrix = &base_matrix;
gfx::Rect bound = GetPageBounds(1);
RECT rect = bound.ToRECT();
return bound.IsEmpty() ||
EnumEnhMetaFile(context, emf_, &Emf::SafePlaybackProc,
reinterpret_cast<void*>(&playback_context),
&rect) != 0;
}
gfx::Rect Emf::GetPageBounds(unsigned int page_number) const {
DCHECK(emf_ && !hdc_);
DCHECK_EQ(1U, page_number);
ENHMETAHEADER header;
if (GetEnhMetaFileHeader(emf_, sizeof(header), &header) != sizeof(header)) {
NOTREACHED();
return gfx::Rect();
}
// Add 1 to right and bottom because it's inclusive rectangle.
// See ENHMETAHEADER.
return gfx::Rect(header.rclBounds.left, header.rclBounds.top,
header.rclBounds.right - header.rclBounds.left + 1,
header.rclBounds.bottom - header.rclBounds.top + 1);
}
unsigned int Emf::GetPageCount() const {
return 1;
}
HDC Emf::context() const {
return hdc_;
}
uint32_t Emf::GetDataSize() const {
DCHECK(emf_ && !hdc_);
return GetEnhMetaFileBits(emf_, 0, nullptr);
}
bool Emf::GetData(void* buffer, uint32_t size) const {
DCHECK(emf_ && !hdc_);
DCHECK(buffer && size);
uint32_t size2 =
GetEnhMetaFileBits(emf_, size, reinterpret_cast<BYTE*>(buffer));
DCHECK(size2 == size);
return size2 == size && size2 != 0;
}
bool Emf::ShouldCopySharedMemoryRegionData() const {
// `InitFromData()` operates directly upon memory provide to it, so any
// caller for cases where this data is shared cross-process should have the
// data copied before it is operated upon.
return true;
}
mojom::MetafileDataType Emf::GetDataType() const {
return mojom::MetafileDataType::kEMF;
}
int CALLBACK Emf::SafePlaybackProc(HDC hdc,
HANDLETABLE* handle_table,
const ENHMETARECORD* record,
int objects_count,
LPARAM param) {
Emf::EnumerationContext* context =
reinterpret_cast<Emf::EnumerationContext*>(param);
context->handle_table = handle_table;
context->objects_count = objects_count;
context->hdc = hdc;
Record record_instance(record);
bool success = record_instance.SafePlayback(context);
DCHECK(success);
return 1;
}
Emf::EnumerationContext::EnumerationContext() {
memset(this, 0, sizeof(*this));
}
Emf::Record::Record(const ENHMETARECORD* record) : record_(record) {
DCHECK(record_);
}
bool Emf::Record::Play(Emf::EnumerationContext* context) const {
return 0 != PlayEnhMetaFileRecord(context->hdc, context->handle_table,
record_, context->objects_count);
}
bool Emf::Record::SafePlayback(Emf::EnumerationContext* context) const {
// For EMF field description, see [MS-EMF] Enhanced Metafile Format
// Specification.
//
// This is the second major EMF breakage I get; the first one being
// SetDCBrushColor/SetDCPenColor/DC_PEN/DC_BRUSH being silently ignored.
//
// This function is the guts of the fix for bug 1186598. Some printer drivers
// somehow choke on certain EMF records, but calling the corresponding
// function directly on the printer HDC is fine. Still, playing the EMF record
// fails. Go figure.
//
// The main issue is that SetLayout is totally unsupported on these printers
// (HP 4500/4700). I used to call SetLayout and I stopped. I found out this is
// not sufficient because GDI32!PlayEnhMetaFile internally calls SetLayout(!)
// Damn.
//
// So I resorted to manually parse the EMF records and play them one by one.
// The issue with this method compared to using PlayEnhMetaFile to play back
// an EMF buffer is that the later silently fixes the matrix to take in
// account the matrix currently loaded at the time of the call.
// The matrix magic is done transparently when using PlayEnhMetaFile but since
// I'm processing one field at a time, I need to do the fixup myself. Note
// that PlayEnhMetaFileRecord doesn't fix the matrix correctly even when
// called inside an EnumEnhMetaFile loop. Go figure (bis).
//
// So when I see a EMR_SETWORLDTRANSFORM and EMR_MODIFYWORLDTRANSFORM, I need
// to fix the matrix according to the matrix previously loaded before playing
// back the buffer. Otherwise, the previously loaded matrix would be ignored
// and the EMF buffer would always be played back at its native resolution.
// Duh.
//
// I also use this opportunity to skip over eventual EMR_SETLAYOUT record that
// could remain.
//
// Another tweak we make is for JPEGs/PNGs in calls to StretchDIBits.
// (Our Pepper plugin code uses a JPEG). If the printer does not support
// JPEGs/PNGs natively we decompress the JPEG/PNG and then set it to the
// device.
// TODO(sanjeevr): We should also add JPEG/PNG support for SetSIBitsToDevice
//
// We also process any custom EMR_GDICOMMENT records which are our
// placeholders for StartPage and EndPage.
// Note: I should probably care about view ports and clipping, eventually.
bool res = false;
const XFORM* base_matrix = context->base_matrix;
switch (record()->iType) {
case EMR_STRETCHDIBITS: {
const EMRSTRETCHDIBITS* sdib_record =
reinterpret_cast<const EMRSTRETCHDIBITS*>(record());
const BYTE* record_start = reinterpret_cast<const BYTE*>(record());
const BITMAPINFOHEADER* bmih = reinterpret_cast<const BITMAPINFOHEADER*>(
record_start + sdib_record->offBmiSrc);
const BYTE* bits = record_start + sdib_record->offBitsSrc;
bool play_normally = true;
res = false;
HDC hdc = context->hdc;
std::unique_ptr<SkBitmap> bitmap;
if (bmih->biCompression == BI_JPEG) {
if (!DIBFormatNativelySupported(hdc, CHECKJPEGFORMAT, bits,
bmih->biSizeImage)) {
play_normally = false;
bitmap = gfx::JPEGCodec::Decode(bits, bmih->biSizeImage);
DCHECK(bitmap);
DCHECK(!bitmap->isNull());
}
} else if (bmih->biCompression == BI_PNG) {
if (!DIBFormatNativelySupported(hdc, CHECKPNGFORMAT, bits,
bmih->biSizeImage)) {
play_normally = false;
bitmap = std::make_unique<SkBitmap>();
bool png_ok =
gfx::PNGCodec::Decode(bits, bmih->biSizeImage, &*bitmap);
DCHECK(png_ok);
DCHECK(!bitmap->isNull());
}
}
if (play_normally) {
res = Play(context);
} else {
const uint32_t* pixels =
static_cast<const uint32_t*>(bitmap->getPixels());
if (!pixels) {
NOTREACHED();
return false;
}
BITMAPINFOHEADER bmi = {0};
skia::CreateBitmapHeaderForN32SkBitmap(*bitmap, &bmi);
res =
(0 != StretchDIBits(hdc, sdib_record->xDest, sdib_record->yDest,
sdib_record->cxDest, sdib_record->cyDest,
sdib_record->xSrc, sdib_record->ySrc,
sdib_record->cxSrc, sdib_record->cySrc, pixels,
reinterpret_cast<const BITMAPINFO*>(&bmi),
sdib_record->iUsageSrc, sdib_record->dwRop));
}
break;
}
case EMR_SETWORLDTRANSFORM: {
DCHECK_EQ(record()->nSize, sizeof(DWORD) * 2 + sizeof(XFORM));
const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm);
HDC hdc = context->hdc;
if (base_matrix) {
res = 0 != SetWorldTransform(hdc, base_matrix) &&
ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY);
} else {
res = 0 != SetWorldTransform(hdc, xform);
}
break;
}
case EMR_MODIFYWORLDTRANSFORM: {
DCHECK_EQ(record()->nSize,
sizeof(DWORD) * 2 + sizeof(XFORM) + sizeof(DWORD));
const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm);
const DWORD* option = reinterpret_cast<const DWORD*>(xform + 1);
HDC hdc = context->hdc;
switch (*option) {
case MWT_IDENTITY:
if (base_matrix) {
res = 0 != SetWorldTransform(hdc, base_matrix);
} else {
res = 0 != ModifyWorldTransform(hdc, xform, MWT_IDENTITY);
}
break;
case MWT_LEFTMULTIPLY:
case MWT_RIGHTMULTIPLY:
res = 0 != ModifyWorldTransform(hdc, xform, *option);
break;
case 4: // MWT_SET
if (base_matrix) {
res = 0 != SetWorldTransform(hdc, base_matrix) &&
ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY);
} else {
res = 0 != SetWorldTransform(hdc, xform);
}
break;
default:
res = false;
break;
}
break;
}
case EMR_SETLAYOUT:
// Ignore it.
res = true;
break;
default: {
res = Play(context);
break;
}
}
return res;
}
void Emf::StartPage(const gfx::Size& /*page_size*/,
const gfx::Rect& /*content_area*/,
float /*scale_factor*/,
mojom::PageOrientation /*page_orientation*/) {}
bool Emf::FinishPage() {
return true;
}
Emf::Enumerator::Enumerator(const Emf& emf, HDC context, const RECT* rect) {
items_.clear();
if (!EnumEnhMetaFile(context, emf.emf(), &Emf::Enumerator::EnhMetaFileProc,
reinterpret_cast<void*>(this), rect)) {
NOTREACHED();
items_.clear();
}
DCHECK_EQ(context_.hdc, context);
}
Emf::Enumerator::~Enumerator() {}
Emf::Enumerator::const_iterator Emf::Enumerator::begin() const {
return items_.begin();
}
Emf::Enumerator::const_iterator Emf::Enumerator::end() const {
return items_.end();
}
int CALLBACK Emf::Enumerator::EnhMetaFileProc(HDC hdc,
HANDLETABLE* handle_table,
const ENHMETARECORD* record,
int objects_count,
LPARAM param) {
Enumerator& emf = *reinterpret_cast<Enumerator*>(param);
if (!emf.context_.handle_table) {
DCHECK(!emf.context_.handle_table);
DCHECK(!emf.context_.objects_count);
emf.context_.handle_table = handle_table;
emf.context_.objects_count = objects_count;
emf.context_.hdc = hdc;
} else {
DCHECK_EQ(emf.context_.handle_table, handle_table);
DCHECK_EQ(emf.context_.objects_count, objects_count);
DCHECK_EQ(emf.context_.hdc, hdc);
}
emf.items_.push_back(Record(record));
return 1;
}
} // namespace printing
|