license.bot | bf09a50 | 2008-08-24 00:55:55 | [diff] [blame] | 1 | // Copyright (c) 2006-2008 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. |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 4 | |
[email protected] | 0e0fca3 | 2009-07-06 15:25:50 | [diff] [blame] | 5 | #include "printing/emf_win.h" |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 6 | |
| 7 | #include "base/gfx/rect.h" |
| 8 | #include "base/logging.h" |
| 9 | |
[email protected] | 0e0fca3 | 2009-07-06 15:25:50 | [diff] [blame] | 10 | namespace printing { |
initial.commit | 09911bf | 2008-07-26 23:55:29 | [diff] [blame] | 11 | |
| 12 | Emf::Emf() : emf_(NULL), hdc_(NULL) { |
| 13 | } |
| 14 | |
| 15 | Emf::~Emf() { |
| 16 | CloseEmf(); |
| 17 | DCHECK(!emf_ && !hdc_); |
| 18 | } |
| 19 | |
| 20 | bool Emf::CreateDc(HDC sibling, const RECT* rect) { |
| 21 | DCHECK(!emf_ && !hdc_); |
| 22 | hdc_ = CreateEnhMetaFile(sibling, NULL, rect, NULL); |
| 23 | DCHECK(hdc_); |
| 24 | return hdc_ != NULL; |
| 25 | } |
| 26 | |
| 27 | bool Emf::CreateFromData(const void* buffer, size_t size) { |
| 28 | DCHECK(!emf_ && !hdc_); |
| 29 | emf_ = SetEnhMetaFileBits(static_cast<unsigned>(size), |
| 30 | reinterpret_cast<const BYTE*>(buffer)); |
| 31 | DCHECK(emf_); |
| 32 | return emf_ != NULL; |
| 33 | } |
| 34 | |
| 35 | bool Emf::CloseDc() { |
| 36 | DCHECK(!emf_ && hdc_); |
| 37 | emf_ = CloseEnhMetaFile(hdc_); |
| 38 | DCHECK(emf_); |
| 39 | hdc_ = NULL; |
| 40 | return emf_ != NULL; |
| 41 | } |
| 42 | |
| 43 | void Emf::CloseEmf() { |
| 44 | DCHECK(!hdc_); |
| 45 | if (emf_) { |
| 46 | DeleteEnhMetaFile(emf_); |
| 47 | emf_ = NULL; |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | bool Emf::Playback(HDC hdc, const RECT* rect) const { |
| 52 | DCHECK(emf_ && !hdc_); |
| 53 | RECT bounds; |
| 54 | if (!rect) { |
| 55 | // Get the natural bounds of the EMF buffer. |
| 56 | bounds = GetBounds().ToRECT(); |
| 57 | rect = &bounds; |
| 58 | } |
| 59 | return PlayEnhMetaFile(hdc, emf_, rect) != 0; |
| 60 | } |
| 61 | |
| 62 | bool Emf::SafePlayback(HDC context) const { |
| 63 | DCHECK(emf_ && !hdc_); |
| 64 | XFORM base_matrix; |
| 65 | if (!GetWorldTransform(context, &base_matrix)) { |
| 66 | NOTREACHED(); |
| 67 | return false; |
| 68 | } |
| 69 | |
| 70 | return EnumEnhMetaFile(context, |
| 71 | emf_, |
| 72 | &Emf::SafePlaybackProc, |
| 73 | reinterpret_cast<void*>(&base_matrix), |
| 74 | &GetBounds().ToRECT()) != 0; |
| 75 | } |
| 76 | |
| 77 | gfx::Rect Emf::GetBounds() const { |
| 78 | DCHECK(emf_ && !hdc_); |
| 79 | ENHMETAHEADER header; |
| 80 | if (GetEnhMetaFileHeader(emf_, sizeof(header), &header) != sizeof(header)) { |
| 81 | NOTREACHED(); |
| 82 | return gfx::Rect(); |
| 83 | } |
| 84 | if (header.rclBounds.left == 0 && |
| 85 | header.rclBounds.top == 0 && |
| 86 | header.rclBounds.right == -1 && |
| 87 | header.rclBounds.bottom == -1) { |
| 88 | // A freshly created EMF buffer that has no drawing operation has invalid |
| 89 | // bounds. Instead of having an (0,0) size, it has a (-1,-1) size. Detect |
| 90 | // this special case and returns an empty Rect instead of an invalid one. |
| 91 | return gfx::Rect(); |
| 92 | } |
| 93 | return gfx::Rect(header.rclBounds.left, |
| 94 | header.rclBounds.top, |
| 95 | header.rclBounds.right - header.rclBounds.left, |
| 96 | header.rclBounds.bottom - header.rclBounds.top); |
| 97 | } |
| 98 | |
| 99 | unsigned Emf::GetDataSize() const { |
| 100 | DCHECK(emf_ && !hdc_); |
| 101 | return GetEnhMetaFileBits(emf_, 0, NULL); |
| 102 | } |
| 103 | |
| 104 | bool Emf::GetData(void* buffer, size_t size) const { |
| 105 | DCHECK(emf_ && !hdc_); |
| 106 | DCHECK(buffer && size); |
| 107 | unsigned size2 = GetEnhMetaFileBits(emf_, static_cast<unsigned>(size), |
| 108 | reinterpret_cast<BYTE*>(buffer)); |
| 109 | DCHECK(size2 == size); |
| 110 | return size2 == size && size2 != 0; |
| 111 | } |
| 112 | |
| 113 | bool Emf::GetData(std::vector<uint8>* buffer) const { |
| 114 | unsigned size = GetDataSize(); |
| 115 | if (!size) |
| 116 | return false; |
| 117 | |
| 118 | buffer->resize(size); |
| 119 | if (!GetData(&buffer->front(), size)) |
| 120 | return false; |
| 121 | return true; |
| 122 | } |
| 123 | |
| 124 | bool Emf::SaveTo(const std::wstring& filename) const { |
| 125 | HANDLE file = CreateFile(filename.c_str(), GENERIC_WRITE, |
| 126 | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, |
| 127 | CREATE_ALWAYS, 0, NULL); |
| 128 | if (file == INVALID_HANDLE_VALUE) |
| 129 | return false; |
| 130 | |
| 131 | bool success = false; |
| 132 | std::vector<uint8> buffer; |
| 133 | if (GetData(&buffer)) { |
| 134 | DWORD written = 0; |
| 135 | if (WriteFile(file, &*buffer.begin(), static_cast<DWORD>(buffer.size()), |
| 136 | &written, NULL) && |
| 137 | written == buffer.size()) { |
| 138 | success = true; |
| 139 | } |
| 140 | } |
| 141 | CloseHandle(file); |
| 142 | return success; |
| 143 | } |
| 144 | |
| 145 | int CALLBACK Emf::SafePlaybackProc(HDC hdc, |
| 146 | HANDLETABLE* handle_table, |
| 147 | const ENHMETARECORD* record, |
| 148 | int objects_count, |
| 149 | LPARAM param) { |
| 150 | const XFORM* base_matrix = reinterpret_cast<const XFORM*>(param); |
| 151 | EnumerationContext context; |
| 152 | context.handle_table = handle_table; |
| 153 | context.objects_count = objects_count; |
| 154 | context.hdc = hdc; |
| 155 | Record record_instance(&context, record); |
| 156 | bool success = record_instance.SafePlayback(base_matrix); |
| 157 | DCHECK(success); |
| 158 | return 1; |
| 159 | } |
| 160 | |
| 161 | Emf::Record::Record() { |
| 162 | } |
| 163 | |
| 164 | Emf::Record::Record(const EnumerationContext* context, |
| 165 | const ENHMETARECORD* record) |
| 166 | : record_(record), |
| 167 | context_(context) { |
| 168 | DCHECK(record_); |
| 169 | } |
| 170 | |
| 171 | bool Emf::Record::Play() const { |
| 172 | return 0 != PlayEnhMetaFileRecord(context_->hdc, |
| 173 | context_->handle_table, |
| 174 | record_, |
| 175 | context_->objects_count); |
| 176 | } |
| 177 | |
| 178 | bool Emf::Record::SafePlayback(const XFORM* base_matrix) const { |
| 179 | // For EMF field description, see [MS-EMF] Enhanced Metafile Format |
| 180 | // Specification. |
| 181 | // |
| 182 | // This is the second major EMF breakage I get; the first one being |
| 183 | // SetDCBrushColor/SetDCPenColor/DC_PEN/DC_BRUSH being silently ignored. |
| 184 | // |
| 185 | // This function is the guts of the fix for bug 1186598. Some printer drivers |
| 186 | // somehow choke on certain EMF records, but calling the corresponding |
| 187 | // function directly on the printer HDC is fine. Still, playing the EMF record |
| 188 | // fails. Go figure. |
| 189 | // |
| 190 | // The main issue is that SetLayout is totally unsupported on these printers |
| 191 | // (HP 4500/4700). I used to call SetLayout and I stopped. I found out this is |
| 192 | // not sufficient because GDI32!PlayEnhMetaFile internally calls SetLayout(!) |
| 193 | // Damn. |
| 194 | // |
| 195 | // So I resorted to manually parse the EMF records and play them one by one. |
| 196 | // The issue with this method compared to using PlayEnhMetaFile to play back |
| 197 | // an EMF buffer is that the later silently fixes the matrix to take in |
| 198 | // account the matrix currently loaded at the time of the call. |
| 199 | // The matrix magic is done transparently when using PlayEnhMetaFile but since |
| 200 | // I'm processing one field at a time, I need to do the fixup myself. Note |
| 201 | // that PlayEnhMetaFileRecord doesn't fix the matrix correctly even when |
| 202 | // called inside an EnumEnhMetaFile loop. Go figure (bis). |
| 203 | // |
| 204 | // So when I see a EMR_SETWORLDTRANSFORM and EMR_MODIFYWORLDTRANSFORM, I need |
| 205 | // to fix the matrix according to the matrix previously loaded before playing |
| 206 | // back the buffer. Otherwise, the previously loaded matrix would be ignored |
| 207 | // and the EMF buffer would always be played back at its native resolution. |
| 208 | // Duh. |
| 209 | // |
| 210 | // I also use this opportunity to skip over eventual EMR_SETLAYOUT record that |
| 211 | // could remain. |
| 212 | // |
| 213 | // Note: I should probably care about view ports and clipping, eventually. |
| 214 | bool res; |
| 215 | switch (record()->iType) { |
| 216 | case EMR_SETWORLDTRANSFORM: { |
| 217 | DCHECK_EQ(record()->nSize, sizeof(DWORD) * 2 + sizeof(XFORM)); |
| 218 | const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm); |
| 219 | HDC hdc = context_->hdc; |
| 220 | if (base_matrix) { |
| 221 | res = 0 != SetWorldTransform(hdc, base_matrix) && |
| 222 | ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY); |
| 223 | } else { |
| 224 | res = 0 != SetWorldTransform(hdc, xform); |
| 225 | } |
| 226 | break; |
| 227 | } |
| 228 | case EMR_MODIFYWORLDTRANSFORM: { |
| 229 | DCHECK_EQ(record()->nSize, |
| 230 | sizeof(DWORD) * 2 + sizeof(XFORM) + sizeof(DWORD)); |
| 231 | const XFORM* xform = reinterpret_cast<const XFORM*>(record()->dParm); |
| 232 | const DWORD* option = reinterpret_cast<const DWORD*>(xform + 1); |
| 233 | HDC hdc = context_->hdc; |
| 234 | switch (*option) { |
| 235 | case MWT_IDENTITY: |
| 236 | if (base_matrix) { |
| 237 | res = 0 != SetWorldTransform(hdc, base_matrix); |
| 238 | } else { |
| 239 | res = 0 != ModifyWorldTransform(hdc, xform, MWT_IDENTITY); |
| 240 | } |
| 241 | break; |
| 242 | case MWT_LEFTMULTIPLY: |
| 243 | case MWT_RIGHTMULTIPLY: |
| 244 | res = 0 != ModifyWorldTransform(hdc, xform, *option); |
| 245 | break; |
| 246 | case 4: // MWT_SET |
| 247 | if (base_matrix) { |
| 248 | res = 0 != SetWorldTransform(hdc, base_matrix) && |
| 249 | ModifyWorldTransform(hdc, xform, MWT_LEFTMULTIPLY); |
| 250 | } else { |
| 251 | res = 0 != SetWorldTransform(hdc, xform); |
| 252 | } |
| 253 | break; |
| 254 | default: |
| 255 | res = false; |
| 256 | break; |
| 257 | } |
| 258 | break; |
| 259 | } |
| 260 | case EMR_SETLAYOUT: |
| 261 | // Ignore it. |
| 262 | res = true; |
| 263 | break; |
| 264 | default: { |
| 265 | res = Play(); |
| 266 | break; |
| 267 | } |
| 268 | } |
| 269 | return res; |
| 270 | } |
| 271 | |
| 272 | Emf::Enumerator::Enumerator(const Emf& emf, HDC context, const RECT* rect) { |
| 273 | context_.handle_table = NULL; |
| 274 | context_.objects_count = 0; |
| 275 | context_.hdc = NULL; |
| 276 | items_.clear(); |
| 277 | if (!EnumEnhMetaFile(context, |
| 278 | emf.emf(), |
| 279 | &Emf::Enumerator::EnhMetaFileProc, |
| 280 | reinterpret_cast<void*>(this), |
| 281 | rect)) { |
| 282 | NOTREACHED(); |
| 283 | items_.clear(); |
| 284 | } |
| 285 | DCHECK_EQ(context_.hdc, context); |
| 286 | } |
| 287 | |
| 288 | Emf::Enumerator::const_iterator Emf::Enumerator::begin() const { |
| 289 | return items_.begin(); |
| 290 | } |
| 291 | |
| 292 | Emf::Enumerator::const_iterator Emf::Enumerator::end() const { |
| 293 | return items_.end(); |
| 294 | } |
| 295 | |
| 296 | int CALLBACK Emf::Enumerator::EnhMetaFileProc(HDC hdc, |
| 297 | HANDLETABLE* handle_table, |
| 298 | const ENHMETARECORD* record, |
| 299 | int objects_count, |
| 300 | LPARAM param) { |
| 301 | Enumerator& emf = *reinterpret_cast<Enumerator*>(param); |
| 302 | if (!emf.context_.handle_table) { |
| 303 | DCHECK(!emf.context_.handle_table); |
| 304 | DCHECK(!emf.context_.objects_count); |
| 305 | emf.context_.handle_table = handle_table; |
| 306 | emf.context_.objects_count = objects_count; |
| 307 | emf.context_.hdc = hdc; |
| 308 | } else { |
| 309 | DCHECK_EQ(emf.context_.handle_table, handle_table); |
| 310 | DCHECK_EQ(emf.context_.objects_count, objects_count); |
| 311 | DCHECK_EQ(emf.context_.hdc, hdc); |
| 312 | } |
| 313 | emf.items_.push_back(Record(&emf.context_, record)); |
| 314 | return 1; |
| 315 | } |
| 316 | |
[email protected] | 0e0fca3 | 2009-07-06 15:25:50 | [diff] [blame] | 317 | } // namespace printing |