blob: 4e506a54dba6a1a04cb097be29ab7a73da0865e7 [file] [log] [blame]
license.botbf09a502008-08-24 00:55:551// 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.commit09911bf2008-07-26 23:55:294
[email protected]8ff1d422009-07-07 21:31:395#include "printing/printing_context.h"
initial.commit09911bf2008-07-26 23:55:296
7#include <winspool.h>
8
9#include "base/file_util.h"
[email protected]dcccb942009-02-01 18:23:0010#include "base/message_loop.h"
11#include "base/time.h"
[email protected]5cca3a52008-08-19 22:35:2912#include "base/time_format.h"
[email protected]8ff1d422009-07-07 21:31:3913#include "printing/printed_document.h"
[email protected]c399a8a2008-11-22 19:38:0014#include "skia/ext/platform_device_win.h"
initial.commit09911bf2008-07-26 23:55:2915
[email protected]e1acf6f2008-10-27 20:43:3316using base::Time;
17
initial.commit09911bf2008-07-26 23:55:2918namespace {
19
20// Retrieves the content of a GetPrinter call.
21void GetPrinterHelper(HANDLE printer, int level, scoped_array<uint8>* buffer) {
22 DWORD buf_size = 0;
23 GetPrinter(printer, level, NULL, 0, &buf_size);
24 if (buf_size) {
25 buffer->reset(new uint8[buf_size]);
26 memset(buffer->get(), 0, buf_size);
27 if (!GetPrinter(printer, level, buffer->get(), buf_size, &buf_size)) {
28 buffer->reset();
29 }
30 }
31}
32
33} // namespace
34
35namespace printing {
36
37class PrintingContext::CallbackHandler
38 : public IPrintDialogCallback,
39 public IObjectWithSite {
40 public:
41 CallbackHandler(PrintingContext& owner, HWND owner_hwnd)
42 : owner_(owner),
43 owner_hwnd_(owner_hwnd),
44 services_(NULL) {
45 }
46
47 ~CallbackHandler() {
48 if (services_)
49 services_->Release();
50 }
51
52 IUnknown* ToIUnknown() {
53 return static_cast<IUnknown*>(static_cast<IPrintDialogCallback*>(this));
54 }
55
56 // IUnknown
57 virtual HRESULT WINAPI QueryInterface(REFIID riid, void**object) {
58 if (riid == IID_IUnknown) {
59 *object = ToIUnknown();
60 } else if (riid == IID_IPrintDialogCallback) {
61 *object = static_cast<IPrintDialogCallback*>(this);
62 } else if (riid == IID_IObjectWithSite) {
63 *object = static_cast<IObjectWithSite*>(this);
64 } else {
65 return E_NOINTERFACE;
66 }
67 return S_OK;
68 }
69
70 // No real ref counting.
71 virtual ULONG WINAPI AddRef() {
72 return 1;
73 }
74 virtual ULONG WINAPI Release() {
75 return 1;
76 }
77
78 // IPrintDialogCallback methods
79 virtual HRESULT WINAPI InitDone() {
80 return S_OK;
81 }
82
83 virtual HRESULT WINAPI SelectionChange() {
84 if (services_) {
85 // TODO(maruel): Get the devmode for the new printer with
86 // services_->GetCurrentDevMode(&devmode, &size), send that information
87 // back to our client and continue. The client needs to recalculate the
88 // number of rendered pages and send back this information here.
89 }
90 return S_OK;
91 }
92
93 virtual HRESULT WINAPI HandleMessage(HWND dialog,
94 UINT message,
95 WPARAM wparam,
96 LPARAM lparam,
97 LRESULT* result) {
98 // Cheap way to retrieve the window handle.
99 if (!owner_.dialog_box_) {
100 // The handle we receive is the one of the groupbox in the General tab. We
101 // need to get the grand-father to get the dialog box handle.
102 owner_.dialog_box_ = GetAncestor(dialog, GA_ROOT);
103 // Trick to enable the owner window. This can cause issues with navigation
104 // events so it may have to be disabled if we don't fix the side-effects.
105 EnableWindow(owner_hwnd_, TRUE);
106 }
107 return S_FALSE;
108 }
109
110 virtual HRESULT WINAPI SetSite(IUnknown* site) {
111 if (!site) {
112 DCHECK(services_);
113 services_->Release();
114 services_ = NULL;
115 // The dialog box is destroying, PrintJob::Worker don't need the handle
116 // anymore.
117 owner_.dialog_box_ = NULL;
118 } else {
119 DCHECK(services_ == NULL);
120 HRESULT hr = site->QueryInterface(IID_IPrintDialogServices,
121 reinterpret_cast<void**>(&services_));
122 DCHECK(SUCCEEDED(hr));
123 }
124 return S_OK;
125 }
126
127 virtual HRESULT WINAPI GetSite(REFIID riid, void** site) {
128 return E_NOTIMPL;
129 }
130
131 private:
132 PrintingContext& owner_;
133 HWND owner_hwnd_;
134 IPrintDialogServices* services_;
135
136 DISALLOW_EVIL_CONSTRUCTORS(CallbackHandler);
137};
138
139PrintingContext::PrintingContext()
140 : hdc_(NULL),
[email protected]0ae80b892008-10-15 17:56:40141#ifndef NDEBUG
initial.commit09911bf2008-07-26 23:55:29142 page_number_(-1),
143#endif
144 dialog_box_(NULL),
145 dialog_box_dismissed_(false),
[email protected]8ff1d422009-07-07 21:31:39146 in_print_job_(false),
147 abort_printing_(false) {
initial.commit09911bf2008-07-26 23:55:29148}
149
150PrintingContext::~PrintingContext() {
151 ResetSettings();
152}
153
[email protected]c8ad40c2009-06-08 17:05:21154PrintingContext::Result PrintingContext::AskUserForSettings(
155 HWND window,
156 int max_pages,
157 bool has_selection) {
initial.commit09911bf2008-07-26 23:55:29158 DCHECK(window);
159 DCHECK(!in_print_job_);
160 dialog_box_dismissed_ = false;
161 // Show the OS-dependent dialog box.
162 // If the user press
163 // - OK, the settings are reset and reinitialized with the new settings. OK is
164 // returned.
165 // - Apply then Cancel, the settings are reset and reinitialized with the new
166 // settings. CANCEL is returned.
167 // - Cancel, the settings are not changed, the previous setting, if it was
168 // initialized before, are kept. CANCEL is returned.
169 // On failure, the settings are reset and FAILED is returned.
170 PRINTDLGEX dialog_options = { sizeof(PRINTDLGEX) };
171 dialog_options.hwndOwner = window;
[email protected]c8ad40c2009-06-08 17:05:21172 // Disable options we don't support currently.
initial.commit09911bf2008-07-26 23:55:29173 // TODO(maruel): Reuse the previously loaded settings!
174 dialog_options.Flags = PD_RETURNDC | PD_USEDEVMODECOPIESANDCOLLATE |
[email protected]c8ad40c2009-06-08 17:05:21175 PD_NOCURRENTPAGE | PD_HIDEPRINTTOFILE;
176 if (!has_selection)
177 dialog_options.Flags |= PD_NOSELECTION;
178
initial.commit09911bf2008-07-26 23:55:29179 PRINTPAGERANGE ranges[32];
180 dialog_options.nStartPage = START_PAGE_GENERAL;
181 if (max_pages) {
182 // Default initialize to print all the pages.
183 memset(ranges, 0, sizeof(ranges));
184 ranges[0].nFromPage = 1;
185 ranges[0].nToPage = max_pages;
186 dialog_options.nPageRanges = 1;
187 dialog_options.nMaxPageRanges = arraysize(ranges);
[email protected]3a0e4a32009-06-09 19:07:05188 dialog_options.nMinPage = 1;
initial.commit09911bf2008-07-26 23:55:29189 dialog_options.nMaxPage = max_pages;
190 dialog_options.lpPageRanges = ranges;
191 } else {
192 // No need to bother, we don't know how many pages are available.
193 dialog_options.Flags |= PD_NOPAGENUMS;
194 }
195
196 {
initial.commit09911bf2008-07-26 23:55:29197 if (PrintDlgEx(&dialog_options) != S_OK) {
198 ResetSettings();
199 return FAILED;
200 }
201 }
202 // TODO(maruel): Support PD_PRINTTOFILE.
203 return ParseDialogResultEx(dialog_options);
204}
205
206PrintingContext::Result PrintingContext::UseDefaultSettings() {
207 DCHECK(!in_print_job_);
208
209 PRINTDLG dialog_options = { sizeof(PRINTDLG) };
210 dialog_options.Flags = PD_RETURNDC | PD_RETURNDEFAULT;
211 if (PrintDlg(&dialog_options) == 0) {
212 ResetSettings();
213 return FAILED;
214 }
215 return ParseDialogResult(dialog_options);
216}
217
218PrintingContext::Result PrintingContext::InitWithSettings(
219 const PrintSettings& settings) {
220 DCHECK(!in_print_job_);
221 settings_ = settings;
222 // TODO(maruel): settings_->ToDEVMODE()
223 HANDLE printer;
224 if (!OpenPrinter(const_cast<wchar_t*>(settings_.device_name().c_str()),
225 &printer,
226 NULL))
227 return FAILED;
228
229 Result status = OK;
230
231 if (!GetPrinterSettings(printer, settings_.device_name()))
232 status = FAILED;
233
234 // Close the printer after retrieving the context.
235 ClosePrinter(printer);
236
237 if (status != OK)
238 ResetSettings();
239 return status;
240}
241
242void PrintingContext::ResetSettings() {
243 if (hdc_ != NULL) {
244 DeleteDC(hdc_);
245 hdc_ = NULL;
246 }
247 settings_.Clear();
248 in_print_job_ = false;
249
[email protected]0ae80b892008-10-15 17:56:40250#ifndef NDEBUG
initial.commit09911bf2008-07-26 23:55:29251 page_number_ = -1;
252#endif
253}
254
255PrintingContext::Result PrintingContext::NewDocument(
256 const std::wstring& document_name) {
257 DCHECK(!in_print_job_);
258 if (!hdc_)
[email protected]c8ad40c2009-06-08 17:05:21259 return OnError();
initial.commit09911bf2008-07-26 23:55:29260
261 // Set the flag used by the AbortPrintJob dialog procedure.
262 abort_printing_ = false;
263
264 in_print_job_ = true;
265
266 // Register the application's AbortProc function with GDI.
267 if (SP_ERROR == SetAbortProc(hdc_, &AbortProc))
[email protected]c8ad40c2009-06-08 17:05:21268 return OnError();
initial.commit09911bf2008-07-26 23:55:29269
270 DOCINFO di = { sizeof(DOCINFO) };
271 di.lpszDocName = document_name.c_str();
272
273 // Is there a debug dump directory specified? If so, force to print to a file.
[email protected]e1504d82009-07-03 15:27:15274 std::wstring debug_dump_path = PrintedDocument::debug_dump_path();
initial.commit09911bf2008-07-26 23:55:29275 if (!debug_dump_path.empty()) {
276 // Create a filename.
277 std::wstring filename;
278 Time now(Time::Now());
[email protected]5cca3a52008-08-19 22:35:29279 filename = base::TimeFormatShortDateNumeric(now);
initial.commit09911bf2008-07-26 23:55:29280 filename += L"_";
[email protected]5cca3a52008-08-19 22:35:29281 filename += base::TimeFormatTimeOfDay(now);
initial.commit09911bf2008-07-26 23:55:29282 filename += L"_";
283 filename += document_name;
284 filename += L"_";
285 filename += L"buffer.prn";
286 file_util::ReplaceIllegalCharacters(&filename, '_');
287 file_util::AppendToPath(&debug_dump_path, filename);
288 di.lpszOutput = debug_dump_path.c_str();
289 }
290
291 DCHECK_EQ(MessageLoop::current()->NestableTasksAllowed(), false);
292 // Begin a print job by calling the StartDoc function.
293 // NOTE: StartDoc() starts a message loop. That causes a lot of problems with
294 // IPC. Make sure recursive task processing is disabled.
295 if (StartDoc(hdc_, &di) <= 0)
[email protected]c8ad40c2009-06-08 17:05:21296 return OnError();
initial.commit09911bf2008-07-26 23:55:29297
[email protected]0ae80b892008-10-15 17:56:40298#ifndef NDEBUG
initial.commit09911bf2008-07-26 23:55:29299 page_number_ = 0;
300#endif
301 return OK;
302}
303
304PrintingContext::Result PrintingContext::NewPage() {
305 if (abort_printing_)
306 return CANCEL;
307 DCHECK(in_print_job_);
308
309 // Inform the driver that the application is about to begin sending data.
310 if (StartPage(hdc_) <= 0)
[email protected]c8ad40c2009-06-08 17:05:21311 return OnError();
initial.commit09911bf2008-07-26 23:55:29312
[email protected]0ae80b892008-10-15 17:56:40313#ifndef NDEBUG
initial.commit09911bf2008-07-26 23:55:29314 ++page_number_;
315#endif
316
317 return OK;
318}
319
320PrintingContext::Result PrintingContext::PageDone() {
321 if (abort_printing_)
322 return CANCEL;
323 DCHECK(in_print_job_);
324
325 if (EndPage(hdc_) <= 0)
[email protected]c8ad40c2009-06-08 17:05:21326 return OnError();
initial.commit09911bf2008-07-26 23:55:29327 return OK;
328}
329
330PrintingContext::Result PrintingContext::DocumentDone() {
331 if (abort_printing_)
332 return CANCEL;
333 DCHECK(in_print_job_);
334
335 // Inform the driver that document has ended.
336 if (EndDoc(hdc_) <= 0)
[email protected]c8ad40c2009-06-08 17:05:21337 return OnError();
initial.commit09911bf2008-07-26 23:55:29338
339 ResetSettings();
340 return OK;
341}
342
343void PrintingContext::Cancel() {
344 abort_printing_ = true;
345 in_print_job_ = false;
346 if (hdc_)
347 CancelDC(hdc_);
348 DismissDialog();
349}
350
351void PrintingContext::DismissDialog() {
352 if (dialog_box_) {
353 DestroyWindow(dialog_box_);
354 dialog_box_dismissed_ = true;
355 }
356}
357
[email protected]c8ad40c2009-06-08 17:05:21358PrintingContext::Result PrintingContext::OnError() {
initial.commit09911bf2008-07-26 23:55:29359 // This will close hdc_ and clear settings_.
360 ResetSettings();
361 return abort_printing_ ? CANCEL : FAILED;
362}
363
364// static
365BOOL PrintingContext::AbortProc(HDC hdc, int nCode) {
366 if (nCode) {
367 // TODO(maruel): Need a way to find the right instance to set. Should
368 // leverage PrintJobManager here?
369 // abort_printing_ = true;
370 }
371 return true;
372}
373
374bool PrintingContext::InitializeSettings(const DEVMODE& dev_mode,
375 const std::wstring& new_device_name,
376 const PRINTPAGERANGE* ranges,
[email protected]c8ad40c2009-06-08 17:05:21377 int number_ranges,
378 bool selection_only) {
[email protected]bd63dc92009-06-14 15:14:53379 skia::PlatformDevice::InitializeDC(hdc_);
initial.commit09911bf2008-07-26 23:55:29380 DCHECK(GetDeviceCaps(hdc_, CLIPCAPS));
381 DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_STRETCHDIB);
382 DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_BITMAP64);
383 // Some printers don't advertise these.
384 // DCHECK(GetDeviceCaps(hdc_, RASTERCAPS) & RC_SCALING);
385 // DCHECK(GetDeviceCaps(hdc_, SHADEBLENDCAPS) & SB_CONST_ALPHA);
386 // DCHECK(GetDeviceCaps(hdc_, SHADEBLENDCAPS) & SB_PIXEL_ALPHA);
387
388 // StretchDIBits() support is needed for printing.
389 if (!(GetDeviceCaps(hdc_, RASTERCAPS) & RC_STRETCHDIB) ||
390 !(GetDeviceCaps(hdc_, RASTERCAPS) & RC_BITMAP64)) {
391 NOTREACHED();
392 ResetSettings();
393 return false;
394 }
395
396 DCHECK(!in_print_job_);
397 DCHECK(hdc_);
initial.commit09911bf2008-07-26 23:55:29398 PageRanges ranges_vector;
[email protected]82270452009-06-19 15:58:01399 if (!selection_only) {
400 // Convert the PRINTPAGERANGE array to a PrintSettings::PageRanges vector.
401 ranges_vector.reserve(number_ranges);
402 for (int i = 0; i < number_ranges; ++i) {
403 PageRange range;
404 // Transfer from 1-based to 0-based.
405 range.from = ranges[i].nFromPage - 1;
406 range.to = ranges[i].nToPage - 1;
407 ranges_vector.push_back(range);
408 }
initial.commit09911bf2008-07-26 23:55:29409 }
[email protected]c8ad40c2009-06-08 17:05:21410 settings_.Init(hdc_,
411 dev_mode,
412 ranges_vector,
413 new_device_name,
414 selection_only);
initial.commit09911bf2008-07-26 23:55:29415 return true;
416}
417
418bool PrintingContext::GetPrinterSettings(HANDLE printer,
419 const std::wstring& device_name) {
420 DCHECK(!in_print_job_);
421 scoped_array<uint8> buffer;
422
423 // A PRINTER_INFO_9 structure specifying the per-user default printer
424 // settings.
425 GetPrinterHelper(printer, 9, &buffer);
426 if (buffer.get()) {
427 PRINTER_INFO_9* info_9 = reinterpret_cast<PRINTER_INFO_9*>(buffer.get());
428 if (info_9->pDevMode != NULL) {
429 if (!AllocateContext(device_name, info_9->pDevMode)) {
430 ResetSettings();
431 return false;
432 }
[email protected]c8ad40c2009-06-08 17:05:21433 return InitializeSettings(*info_9->pDevMode, device_name, NULL, 0, false);
initial.commit09911bf2008-07-26 23:55:29434 }
435 buffer.reset();
436 }
437
438 // A PRINTER_INFO_8 structure specifying the global default printer settings.
439 GetPrinterHelper(printer, 8, &buffer);
440 if (buffer.get()) {
441 PRINTER_INFO_8* info_8 = reinterpret_cast<PRINTER_INFO_8*>(buffer.get());
442 if (info_8->pDevMode != NULL) {
443 if (!AllocateContext(device_name, info_8->pDevMode)) {
444 ResetSettings();
445 return false;
446 }
[email protected]c8ad40c2009-06-08 17:05:21447 return InitializeSettings(*info_8->pDevMode, device_name, NULL, 0, false);
initial.commit09911bf2008-07-26 23:55:29448 }
449 buffer.reset();
450 }
451
452 // A PRINTER_INFO_2 structure specifying the driver's default printer
453 // settings.
454 GetPrinterHelper(printer, 2, &buffer);
455 if (buffer.get()) {
456 PRINTER_INFO_2* info_2 = reinterpret_cast<PRINTER_INFO_2*>(buffer.get());
457 if (info_2->pDevMode != NULL) {
458 if (!AllocateContext(device_name, info_2->pDevMode)) {
459 ResetSettings();
460 return false;
461 }
[email protected]c8ad40c2009-06-08 17:05:21462 return InitializeSettings(*info_2->pDevMode, device_name, NULL, 0, false);
initial.commit09911bf2008-07-26 23:55:29463 }
464 buffer.reset();
465 }
466 // Failed to retrieve the printer settings.
467 ResetSettings();
468 return false;
469}
470
471bool PrintingContext::AllocateContext(const std::wstring& printer_name,
472 const DEVMODE* dev_mode) {
473 hdc_ = CreateDC(L"WINSPOOL", printer_name.c_str(), NULL, dev_mode);
474 DCHECK(hdc_);
475 return hdc_ != NULL;
476}
477
478PrintingContext::Result PrintingContext::ParseDialogResultEx(
479 const PRINTDLGEX& dialog_options) {
480 // If the user clicked OK or Apply then Cancel, but not only Cancel.
481 if (dialog_options.dwResultAction != PD_RESULT_CANCEL) {
482 // Start fresh.
483 ResetSettings();
484
485 DEVMODE* dev_mode = NULL;
486 if (dialog_options.hDevMode) {
487 dev_mode =
488 reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode));
489 DCHECK(dev_mode);
490 }
491
492 std::wstring device_name;
493 if (dialog_options.hDevNames) {
494 DEVNAMES* dev_names =
495 reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames));
496 DCHECK(dev_names);
497 if (dev_names) {
498 device_name =
499 reinterpret_cast<const wchar_t*>(
500 reinterpret_cast<const wchar_t*>(dev_names) +
501 dev_names->wDeviceOffset);
502 GlobalUnlock(dialog_options.hDevNames);
503 }
504 }
505
506 bool success = false;
507 if (dev_mode && !device_name.empty()) {
508 hdc_ = dialog_options.hDC;
[email protected]c8ad40c2009-06-08 17:05:21509 PRINTPAGERANGE* page_ranges = NULL;
510 DWORD num_page_ranges = 0;
511 bool print_selection_only = false;
initial.commit09911bf2008-07-26 23:55:29512 if (dialog_options.Flags & PD_PAGENUMS) {
[email protected]c8ad40c2009-06-08 17:05:21513 page_ranges = dialog_options.lpPageRanges;
514 num_page_ranges = dialog_options.nPageRanges;
initial.commit09911bf2008-07-26 23:55:29515 }
[email protected]c8ad40c2009-06-08 17:05:21516 if (dialog_options.Flags & PD_SELECTION) {
517 print_selection_only = true;
518 }
519 success = InitializeSettings(*dev_mode,
520 device_name,
521 dialog_options.lpPageRanges,
522 dialog_options.nPageRanges,
523 print_selection_only);
initial.commit09911bf2008-07-26 23:55:29524 }
525
526 if (!success && dialog_options.hDC) {
527 DeleteDC(dialog_options.hDC);
528 hdc_ = NULL;
529 }
530
531 if (dev_mode) {
532 GlobalUnlock(dialog_options.hDevMode);
533 }
534 } else {
535 if (dialog_options.hDC) {
536 DeleteDC(dialog_options.hDC);
537 }
538 }
539
540 if (dialog_options.hDevMode != NULL)
541 GlobalFree(dialog_options.hDevMode);
542 if (dialog_options.hDevNames != NULL)
543 GlobalFree(dialog_options.hDevNames);
544
545 switch (dialog_options.dwResultAction) {
546 case PD_RESULT_PRINT:
547 return hdc_ ? OK : FAILED;
548 case PD_RESULT_APPLY:
549 return hdc_ ? CANCEL : FAILED;
550 case PD_RESULT_CANCEL:
551 return CANCEL;
552 default:
553 return FAILED;
554 }
555}
556
557PrintingContext::Result PrintingContext::ParseDialogResult(
558 const PRINTDLG& dialog_options) {
559 // If the user clicked OK or Apply then Cancel, but not only Cancel.
560 // Start fresh.
561 ResetSettings();
562
563 DEVMODE* dev_mode = NULL;
564 if (dialog_options.hDevMode) {
565 dev_mode =
566 reinterpret_cast<DEVMODE*>(GlobalLock(dialog_options.hDevMode));
567 DCHECK(dev_mode);
568 }
569
570 std::wstring device_name;
571 if (dialog_options.hDevNames) {
572 DEVNAMES* dev_names =
573 reinterpret_cast<DEVNAMES*>(GlobalLock(dialog_options.hDevNames));
574 DCHECK(dev_names);
575 if (dev_names) {
576 device_name =
577 reinterpret_cast<const wchar_t*>(
578 reinterpret_cast<const wchar_t*>(dev_names) +
579 dev_names->wDeviceOffset);
580 GlobalUnlock(dialog_options.hDevNames);
581 }
582 }
583
584 bool success = false;
585 if (dev_mode && !device_name.empty()) {
586 hdc_ = dialog_options.hDC;
[email protected]c8ad40c2009-06-08 17:05:21587 success = InitializeSettings(*dev_mode, device_name, NULL, 0, false);
initial.commit09911bf2008-07-26 23:55:29588 }
589
590 if (!success && dialog_options.hDC) {
591 DeleteDC(dialog_options.hDC);
592 hdc_ = NULL;
593 }
594
595 if (dev_mode) {
596 GlobalUnlock(dialog_options.hDevMode);
597 }
598
599 if (dialog_options.hDevMode != NULL)
600 GlobalFree(dialog_options.hDevMode);
601 if (dialog_options.hDevNames != NULL)
602 GlobalFree(dialog_options.hDevNames);
603
604 return hdc_ ? OK : FAILED;
605}
606
607} // namespace printing