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