Add ICU message format support
[chromium-blink-merge.git] / chrome / browser / printing / pdf_to_emf_converter.cc
blobe07bf6ab9ff43d9b9efb2feab03f7f3e36e1d37d
1 // Copyright 2014 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 "chrome/browser/printing/pdf_to_emf_converter.h"
7 #include <queue>
9 #include "base/files/file.h"
10 #include "base/files/file_util.h"
11 #include "base/files/scoped_temp_dir.h"
12 #include "base/logging.h"
13 #include "chrome/common/chrome_utility_messages.h"
14 #include "chrome/common/chrome_utility_printing_messages.h"
15 #include "chrome/grit/generated_resources.h"
16 #include "content/public/browser/browser_thread.h"
17 #include "content/public/browser/child_process_data.h"
18 #include "content/public/browser/utility_process_host.h"
19 #include "content/public/browser/utility_process_host_client.h"
20 #include "printing/emf_win.h"
21 #include "printing/pdf_render_settings.h"
22 #include "ui/base/l10n/l10n_util.h"
24 namespace printing {
26 namespace {
28 using content::BrowserThread;
30 class PdfToEmfConverterImpl;
32 // Allows to delete temporary directory after all temporary files created inside
33 // are closed. Windows cannot delete directory with opened files. Directory is
34 // used to store PDF and metafiles. PDF should be gone by the time utility
35 // process exits. Metafiles should be gone when all LazyEmf destroyed.
36 class RefCountedTempDir
37 : public base::RefCountedThreadSafe<RefCountedTempDir,
38 BrowserThread::DeleteOnFileThread> {
39 public:
40 RefCountedTempDir() { ignore_result(temp_dir_.CreateUniqueTempDir()); }
41 bool IsValid() const { return temp_dir_.IsValid(); }
42 const base::FilePath& GetPath() const { return temp_dir_.path(); }
44 private:
45 friend struct BrowserThread::DeleteOnThread<BrowserThread::FILE>;
46 friend class base::DeleteHelper<RefCountedTempDir>;
47 ~RefCountedTempDir() {}
49 base::ScopedTempDir temp_dir_;
50 DISALLOW_COPY_AND_ASSIGN(RefCountedTempDir);
53 typedef scoped_ptr<base::File, BrowserThread::DeleteOnFileThread>
54 ScopedTempFile;
56 // Wrapper for Emf to keep only file handle in memory, and load actual data only
57 // on playback. Emf::InitFromFile() can play metafile directly from disk, but it
58 // can't open file handles. We need file handles to reliably delete temporary
59 // files, and to efficiently interact with utility process.
60 class LazyEmf : public MetafilePlayer {
61 public:
62 LazyEmf(const scoped_refptr<RefCountedTempDir>& temp_dir, ScopedTempFile file)
63 : temp_dir_(temp_dir), file_(file.Pass()) {
64 CHECK(file_);
66 ~LazyEmf() override { Close(); }
68 bool SafePlayback(HDC hdc) const override;
69 bool SaveTo(base::File* file) const override;
71 private:
72 void Close() const;
73 bool LoadEmf(Emf* emf) const;
75 mutable scoped_refptr<RefCountedTempDir> temp_dir_;
76 mutable ScopedTempFile file_; // Mutable because of consts in base class.
78 DISALLOW_COPY_AND_ASSIGN(LazyEmf);
81 // Converts PDF into EMF.
82 // Class uses 3 threads: UI, IO and FILE.
83 // Internal workflow is following:
84 // 1. Create instance on the UI thread. (files_, settings_,)
85 // 2. Create pdf file on the FILE thread.
86 // 3. Start utility process and start conversion on the IO thread.
87 // 4. Utility process returns page count.
88 // 5. For each page:
89 // 1. Clients requests page with file handle to a temp file.
90 // 2. Utility converts the page, save it to the file and reply.
92 // All these steps work sequentially, so no data should be accessed
93 // simultaneously by several threads.
94 class PdfToEmfUtilityProcessHostClient
95 : public content::UtilityProcessHostClient {
96 public:
97 PdfToEmfUtilityProcessHostClient(
98 base::WeakPtr<PdfToEmfConverterImpl> converter,
99 const PdfRenderSettings& settings);
101 void Start(const scoped_refptr<base::RefCountedMemory>& data,
102 const PdfToEmfConverter::StartCallback& start_callback);
104 void GetPage(int page_number,
105 const PdfToEmfConverter::GetPageCallback& get_page_callback);
107 void Stop();
109 // UtilityProcessHostClient implementation.
110 void OnProcessCrashed(int exit_code) override;
111 void OnProcessLaunchFailed() override;
112 bool OnMessageReceived(const IPC::Message& message) override;
114 private:
115 class GetPageCallbackData {
116 MOVE_ONLY_TYPE_FOR_CPP_03(GetPageCallbackData, RValue);
118 public:
119 GetPageCallbackData(int page_number,
120 PdfToEmfConverter::GetPageCallback callback)
121 : page_number_(page_number), callback_(callback) {}
123 // Move constructor for STL.
124 GetPageCallbackData(RValue other) { this->operator=(other); }
126 // Move assignment for STL.
127 GetPageCallbackData& operator=(RValue rhs) {
128 page_number_ = rhs.object->page_number_;
129 callback_ = rhs.object->callback_;
130 emf_ = rhs.object->emf_.Pass();
131 return *this;
134 int page_number() const { return page_number_; }
135 const PdfToEmfConverter::GetPageCallback& callback() const {
136 return callback_;
138 ScopedTempFile TakeEmf() { return emf_.Pass(); }
139 void set_emf(ScopedTempFile emf) { emf_ = emf.Pass(); }
141 private:
142 int page_number_;
143 PdfToEmfConverter::GetPageCallback callback_;
144 ScopedTempFile emf_;
147 ~PdfToEmfUtilityProcessHostClient() override;
149 bool Send(IPC::Message* msg);
151 // Message handlers.
152 void OnProcessStarted();
153 void OnPageCount(int page_count);
154 void OnPageDone(bool success, float scale_factor);
156 void OnFailed();
157 void OnTempPdfReady(ScopedTempFile pdf);
158 void OnTempEmfReady(GetPageCallbackData* callback_data, ScopedTempFile emf);
160 scoped_refptr<RefCountedTempDir> temp_dir_;
162 // Used to suppress callbacks after PdfToEmfConverterImpl is deleted.
163 base::WeakPtr<PdfToEmfConverterImpl> converter_;
164 PdfRenderSettings settings_;
165 scoped_refptr<base::RefCountedMemory> data_;
167 // Document loaded callback.
168 PdfToEmfConverter::StartCallback start_callback_;
170 // Process host for IPC.
171 base::WeakPtr<content::UtilityProcessHost> utility_process_host_;
173 // Queue of callbacks for GetPage() requests. Utility process should reply
174 // with PageDone in the same order as requests were received.
175 // Use containers that keeps element pointers valid after push() and pop().
176 typedef std::queue<GetPageCallbackData> GetPageCallbacks;
177 GetPageCallbacks get_page_callbacks_;
179 DISALLOW_COPY_AND_ASSIGN(PdfToEmfUtilityProcessHostClient);
182 class PdfToEmfConverterImpl : public PdfToEmfConverter {
183 public:
184 PdfToEmfConverterImpl();
186 ~PdfToEmfConverterImpl() override;
188 void Start(const scoped_refptr<base::RefCountedMemory>& data,
189 const PdfRenderSettings& conversion_settings,
190 const StartCallback& start_callback) override;
192 void GetPage(int page_number,
193 const GetPageCallback& get_page_callback) override;
195 // Helps to cancel callbacks if this object is destroyed.
196 void RunCallback(const base::Closure& callback);
198 private:
199 scoped_refptr<PdfToEmfUtilityProcessHostClient> utility_client_;
200 base::WeakPtrFactory<PdfToEmfConverterImpl> weak_ptr_factory_;
202 DISALLOW_COPY_AND_ASSIGN(PdfToEmfConverterImpl);
205 ScopedTempFile CreateTempFile(scoped_refptr<RefCountedTempDir>* temp_dir) {
206 if (!temp_dir->get())
207 *temp_dir = new RefCountedTempDir();
208 ScopedTempFile file;
209 if (!(*temp_dir)->IsValid())
210 return file.Pass();
211 base::FilePath path;
212 if (!base::CreateTemporaryFileInDir((*temp_dir)->GetPath(), &path)) {
213 PLOG(ERROR) << "Failed to create file in "
214 << (*temp_dir)->GetPath().value();
215 return file.Pass();
217 file.reset(new base::File(path,
218 base::File::FLAG_CREATE_ALWAYS |
219 base::File::FLAG_WRITE |
220 base::File::FLAG_READ |
221 base::File::FLAG_DELETE_ON_CLOSE |
222 base::File::FLAG_TEMPORARY));
223 if (!file->IsValid()) {
224 PLOG(ERROR) << "Failed to create " << path.value();
225 file.reset();
227 return file.Pass();
230 ScopedTempFile CreateTempPdfFile(
231 const scoped_refptr<base::RefCountedMemory>& data,
232 scoped_refptr<RefCountedTempDir>* temp_dir) {
233 DCHECK_CURRENTLY_ON(BrowserThread::FILE);
235 ScopedTempFile pdf_file = CreateTempFile(temp_dir);
236 if (!pdf_file ||
237 static_cast<int>(data->size()) !=
238 pdf_file->WriteAtCurrentPos(data->front_as<char>(), data->size())) {
239 pdf_file.reset();
240 return pdf_file.Pass();
242 pdf_file->Seek(base::File::FROM_BEGIN, 0);
243 return pdf_file.Pass();
246 bool LazyEmf::SafePlayback(HDC hdc) const {
247 Emf emf;
248 bool result = LoadEmf(&emf) && emf.SafePlayback(hdc);
249 // TODO(vitalybuka): Fix destruction of metafiles. For some reasons
250 // instances of Emf are not deleted. crbug.com/411683
251 // It's known that the Emf going to be played just once to a printer. So just
252 // release file here.
253 Close();
254 return result;
257 bool LazyEmf::SaveTo(base::File* file) const {
258 Emf emf;
259 return LoadEmf(&emf) && emf.SaveTo(file);
262 void LazyEmf::Close() const {
263 file_.reset();
264 temp_dir_ = NULL;
267 bool LazyEmf::LoadEmf(Emf* emf) const {
268 file_->Seek(base::File::FROM_BEGIN, 0);
269 int64 size = file_->GetLength();
270 if (size <= 0)
271 return false;
272 std::vector<char> data(size);
273 if (file_->ReadAtCurrentPos(data.data(), data.size()) != size)
274 return false;
275 return emf->InitFromData(data.data(), data.size());
278 PdfToEmfUtilityProcessHostClient::PdfToEmfUtilityProcessHostClient(
279 base::WeakPtr<PdfToEmfConverterImpl> converter,
280 const PdfRenderSettings& settings)
281 : converter_(converter), settings_(settings) {
284 PdfToEmfUtilityProcessHostClient::~PdfToEmfUtilityProcessHostClient() {
287 void PdfToEmfUtilityProcessHostClient::Start(
288 const scoped_refptr<base::RefCountedMemory>& data,
289 const PdfToEmfConverter::StartCallback& start_callback) {
290 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
291 BrowserThread::PostTask(BrowserThread::IO,
292 FROM_HERE,
293 base::Bind(&PdfToEmfUtilityProcessHostClient::Start,
294 this,
295 data,
296 start_callback));
297 return;
299 data_ = data;
301 // Store callback before any OnFailed() call to make it called on failure.
302 start_callback_ = start_callback;
304 // NOTE: This process _must_ be sandboxed, otherwise the pdf dll will load
305 // gdiplus.dll, change how rendering happens, and not be able to correctly
306 // generate when sent to a metafile DC.
307 utility_process_host_ =
308 content::UtilityProcessHost::Create(
309 this, base::MessageLoop::current()->task_runner())->AsWeakPtr();
310 utility_process_host_->SetName(l10n_util::GetStringUTF16(
311 IDS_UTILITY_PROCESS_EMF_CONVERTOR_NAME));
312 // Should reply with OnProcessStarted().
313 Send(new ChromeUtilityMsg_StartupPing);
316 void PdfToEmfUtilityProcessHostClient::OnProcessStarted() {
317 DCHECK_CURRENTLY_ON(BrowserThread::IO);
318 if (!utility_process_host_)
319 return OnFailed();
321 scoped_refptr<base::RefCountedMemory> data = data_;
322 data_ = NULL;
323 BrowserThread::PostTaskAndReplyWithResult(
324 BrowserThread::FILE,
325 FROM_HERE,
326 base::Bind(&CreateTempPdfFile, data, &temp_dir_),
327 base::Bind(&PdfToEmfUtilityProcessHostClient::OnTempPdfReady, this));
330 void PdfToEmfUtilityProcessHostClient::OnTempPdfReady(ScopedTempFile pdf) {
331 DCHECK_CURRENTLY_ON(BrowserThread::IO);
332 if (!utility_process_host_ || !pdf)
333 return OnFailed();
334 base::ProcessHandle process = utility_process_host_->GetData().handle;
335 // Should reply with OnPageCount().
336 Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles(
337 IPC::GetFileHandleForProcess(pdf->GetPlatformFile(), process, false),
338 settings_));
341 void PdfToEmfUtilityProcessHostClient::OnPageCount(int page_count) {
342 DCHECK_CURRENTLY_ON(BrowserThread::IO);
343 if (start_callback_.is_null())
344 return OnFailed();
345 BrowserThread::PostTask(BrowserThread::UI,
346 FROM_HERE,
347 base::Bind(&PdfToEmfConverterImpl::RunCallback,
348 converter_,
349 base::Bind(start_callback_, page_count)));
350 start_callback_.Reset();
353 void PdfToEmfUtilityProcessHostClient::GetPage(
354 int page_number,
355 const PdfToEmfConverter::GetPageCallback& get_page_callback) {
356 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
357 BrowserThread::PostTask(
358 BrowserThread::IO,
359 FROM_HERE,
360 base::Bind(&PdfToEmfUtilityProcessHostClient::GetPage,
361 this,
362 page_number,
363 get_page_callback));
364 return;
367 // Store callback before any OnFailed() call to make it called on failure.
368 get_page_callbacks_.push(GetPageCallbackData(page_number, get_page_callback));
370 if (!utility_process_host_)
371 return OnFailed();
373 BrowserThread::PostTaskAndReplyWithResult(
374 BrowserThread::FILE,
375 FROM_HERE,
376 base::Bind(&CreateTempFile, &temp_dir_),
377 base::Bind(&PdfToEmfUtilityProcessHostClient::OnTempEmfReady,
378 this,
379 &get_page_callbacks_.back()));
382 void PdfToEmfUtilityProcessHostClient::OnTempEmfReady(
383 GetPageCallbackData* callback_data,
384 ScopedTempFile emf) {
385 DCHECK_CURRENTLY_ON(BrowserThread::IO);
386 if (!utility_process_host_ || !emf)
387 return OnFailed();
388 base::ProcessHandle process = utility_process_host_->GetData().handle;
389 IPC::PlatformFileForTransit transit =
390 IPC::GetFileHandleForProcess(emf->GetPlatformFile(), process, false);
391 callback_data->set_emf(emf.Pass());
392 // Should reply with OnPageDone().
393 Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage(
394 callback_data->page_number(), transit));
397 void PdfToEmfUtilityProcessHostClient::OnPageDone(bool success,
398 float scale_factor) {
399 DCHECK_CURRENTLY_ON(BrowserThread::IO);
400 if (get_page_callbacks_.empty())
401 return OnFailed();
402 GetPageCallbackData& data = get_page_callbacks_.front();
403 scoped_ptr<MetafilePlayer> emf;
405 if (success) {
406 ScopedTempFile temp_emf = data.TakeEmf();
407 if (!temp_emf) // Unexpected message from utility process.
408 return OnFailed();
409 emf.reset(new LazyEmf(temp_dir_, temp_emf.Pass()));
412 BrowserThread::PostTask(BrowserThread::UI,
413 FROM_HERE,
414 base::Bind(&PdfToEmfConverterImpl::RunCallback,
415 converter_,
416 base::Bind(data.callback(),
417 data.page_number(),
418 scale_factor,
419 base::Passed(&emf))));
420 get_page_callbacks_.pop();
423 void PdfToEmfUtilityProcessHostClient::Stop() {
424 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
425 BrowserThread::PostTask(
426 BrowserThread::IO,
427 FROM_HERE,
428 base::Bind(&PdfToEmfUtilityProcessHostClient::Stop, this));
429 return;
431 Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop());
434 void PdfToEmfUtilityProcessHostClient::OnProcessCrashed(int exit_code) {
435 OnFailed();
438 void PdfToEmfUtilityProcessHostClient::OnProcessLaunchFailed() {
439 OnFailed();
442 bool PdfToEmfUtilityProcessHostClient::OnMessageReceived(
443 const IPC::Message& message) {
444 bool handled = true;
445 IPC_BEGIN_MESSAGE_MAP(PdfToEmfUtilityProcessHostClient, message)
446 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ProcessStarted, OnProcessStarted)
447 IPC_MESSAGE_HANDLER(
448 ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount, OnPageCount)
449 IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone,
450 OnPageDone)
451 IPC_MESSAGE_UNHANDLED(handled = false)
452 IPC_END_MESSAGE_MAP()
453 return handled;
456 bool PdfToEmfUtilityProcessHostClient::Send(IPC::Message* msg) {
457 if (utility_process_host_)
458 return utility_process_host_->Send(msg);
459 delete msg;
460 return false;
463 void PdfToEmfUtilityProcessHostClient::OnFailed() {
464 DCHECK_CURRENTLY_ON(BrowserThread::IO);
465 if (!start_callback_.is_null())
466 OnPageCount(0);
467 while (!get_page_callbacks_.empty())
468 OnPageDone(false, 0.0f);
469 utility_process_host_.reset();
472 PdfToEmfConverterImpl::PdfToEmfConverterImpl() : weak_ptr_factory_(this) {
475 PdfToEmfConverterImpl::~PdfToEmfConverterImpl() {
476 if (utility_client_.get())
477 utility_client_->Stop();
480 void PdfToEmfConverterImpl::Start(
481 const scoped_refptr<base::RefCountedMemory>& data,
482 const PdfRenderSettings& conversion_settings,
483 const StartCallback& start_callback) {
484 DCHECK(!utility_client_.get());
485 utility_client_ = new PdfToEmfUtilityProcessHostClient(
486 weak_ptr_factory_.GetWeakPtr(), conversion_settings);
487 utility_client_->Start(data, start_callback);
490 void PdfToEmfConverterImpl::GetPage(int page_number,
491 const GetPageCallback& get_page_callback) {
492 utility_client_->GetPage(page_number, get_page_callback);
495 void PdfToEmfConverterImpl::RunCallback(const base::Closure& callback) {
496 DCHECK_CURRENTLY_ON(BrowserThread::UI);
497 callback.Run();
500 } // namespace
502 PdfToEmfConverter::~PdfToEmfConverter() {
505 // static
506 scoped_ptr<PdfToEmfConverter> PdfToEmfConverter::CreateDefault() {
507 return scoped_ptr<PdfToEmfConverter>(new PdfToEmfConverterImpl());
510 } // namespace printing