Make Print API work on Windows.

This commit is contained in:
Haojian Wu 2015-04-30 11:31:17 +08:00
parent ca5ee0fc81
commit ff87592722
15 changed files with 1294 additions and 0 deletions

View file

@ -0,0 +1,495 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/printing/pdf_to_emf_converter.h"
#include <queue>
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "chrome/common/chrome_utility_messages.h"
#include "chrome/common/print_messages.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/utility_process_host.h"
#include "content/public/browser/utility_process_host_client.h"
#include "printing/emf_win.h"
#include "printing/pdf_render_settings.h"
namespace printing {
namespace {
using content::BrowserThread;
class PdfToEmfConverterImpl;
// Allows to delete temporary directory after all temporary files created inside
// are closed. Windows cannot delete directory with opened files. Directory is
// used to store PDF and metafiles. PDF should be gone by the time utility
// process exits. Metafiles should be gone when all LazyEmf destroyed.
class RefCountedTempDir
: public base::RefCountedThreadSafe<RefCountedTempDir,
BrowserThread::DeleteOnFileThread> {
public:
RefCountedTempDir() { ignore_result(temp_dir_.CreateUniqueTempDir()); }
bool IsValid() const { return temp_dir_.IsValid(); }
const base::FilePath& GetPath() const { return temp_dir_.path(); }
private:
friend struct BrowserThread::DeleteOnThread<BrowserThread::FILE>;
friend class base::DeleteHelper<RefCountedTempDir>;
~RefCountedTempDir() {}
base::ScopedTempDir temp_dir_;
DISALLOW_COPY_AND_ASSIGN(RefCountedTempDir);
};
typedef scoped_ptr<base::File, BrowserThread::DeleteOnFileThread>
ScopedTempFile;
// Wrapper for Emf to keep only file handle in memory, and load actual data only
// on playback. Emf::InitFromFile() can play metafile directly from disk, but it
// can't open file handles. We need file handles to reliably delete temporary
// files, and to efficiently interact with utility process.
class LazyEmf : public MetafilePlayer {
public:
LazyEmf(const scoped_refptr<RefCountedTempDir>& temp_dir, ScopedTempFile file)
: temp_dir_(temp_dir), file_(file.Pass()) {}
virtual ~LazyEmf() { Close(); }
virtual bool SafePlayback(HDC hdc) const override;
virtual bool SaveTo(base::File* file) const override;
private:
void Close() const;
bool LoadEmf(Emf* emf) const;
mutable scoped_refptr<RefCountedTempDir> temp_dir_;
mutable ScopedTempFile file_; // Mutable because of consts in base class.
DISALLOW_COPY_AND_ASSIGN(LazyEmf);
};
// Converts PDF into EMF.
// Class uses 3 threads: UI, IO and FILE.
// Internal workflow is following:
// 1. Create instance on the UI thread. (files_, settings_,)
// 2. Create pdf file on the FILE thread.
// 3. Start utility process and start conversion on the IO thread.
// 4. Utility process returns page count.
// 5. For each page:
// 1. Clients requests page with file handle to a temp file.
// 2. Utility converts the page, save it to the file and reply.
//
// All these steps work sequentially, so no data should be accessed
// simultaneously by several threads.
class PdfToEmfUtilityProcessHostClient
: public content::UtilityProcessHostClient {
public:
PdfToEmfUtilityProcessHostClient(
base::WeakPtr<PdfToEmfConverterImpl> converter,
const PdfRenderSettings& settings);
void Start(const scoped_refptr<base::RefCountedMemory>& data,
const PdfToEmfConverter::StartCallback& start_callback);
void GetPage(int page_number,
const PdfToEmfConverter::GetPageCallback& get_page_callback);
void Stop();
// UtilityProcessHostClient implementation.
virtual void OnProcessCrashed(int exit_code) override;
virtual void OnProcessLaunchFailed() override;
virtual bool OnMessageReceived(const IPC::Message& message) override;
private:
class GetPageCallbackData {
MOVE_ONLY_TYPE_FOR_CPP_03(GetPageCallbackData, RValue);
public:
GetPageCallbackData(int page_number,
PdfToEmfConverter::GetPageCallback callback)
: page_number_(page_number), callback_(callback) {}
// Move constructor for STL.
GetPageCallbackData(RValue other) { this->operator=(other); }
// Move assignment for STL.
GetPageCallbackData& operator=(RValue rhs) {
page_number_ = rhs.object->page_number_;
callback_ = rhs.object->callback_;
emf_ = rhs.object->emf_.Pass();
return *this;
}
int page_number() const { return page_number_; }
const PdfToEmfConverter::GetPageCallback& callback() const {
return callback_;
}
ScopedTempFile emf() { return emf_.Pass(); }
void set_emf(ScopedTempFile emf) { emf_ = emf.Pass(); }
private:
int page_number_;
PdfToEmfConverter::GetPageCallback callback_;
ScopedTempFile emf_;
};
virtual ~PdfToEmfUtilityProcessHostClient();
bool Send(IPC::Message* msg);
// Message handlers.
void OnProcessStarted();
void OnPageCount(int page_count);
void OnPageDone(bool success, float scale_factor);
void OnFailed();
void OnTempPdfReady(ScopedTempFile pdf);
void OnTempEmfReady(GetPageCallbackData* callback_data, ScopedTempFile emf);
scoped_refptr<RefCountedTempDir> temp_dir_;
// Used to suppress callbacks after PdfToEmfConverterImpl is deleted.
base::WeakPtr<PdfToEmfConverterImpl> converter_;
PdfRenderSettings settings_;
scoped_refptr<base::RefCountedMemory> data_;
// Document loaded callback.
PdfToEmfConverter::StartCallback start_callback_;
// Process host for IPC.
base::WeakPtr<content::UtilityProcessHost> utility_process_host_;
// Queue of callbacks for GetPage() requests. Utility process should reply
// with PageDone in the same order as requests were received.
// Use containers that keeps element pointers valid after push() and pop().
typedef std::queue<GetPageCallbackData> GetPageCallbacks;
GetPageCallbacks get_page_callbacks_;
DISALLOW_COPY_AND_ASSIGN(PdfToEmfUtilityProcessHostClient);
};
class PdfToEmfConverterImpl : public PdfToEmfConverter {
public:
PdfToEmfConverterImpl();
virtual ~PdfToEmfConverterImpl();
virtual void Start(const scoped_refptr<base::RefCountedMemory>& data,
const PdfRenderSettings& conversion_settings,
const StartCallback& start_callback) override;
virtual void GetPage(int page_number,
const GetPageCallback& get_page_callback) override;
// Helps to cancel callbacks if this object is destroyed.
void RunCallback(const base::Closure& callback);
private:
scoped_refptr<PdfToEmfUtilityProcessHostClient> utility_client_;
base::WeakPtrFactory<PdfToEmfConverterImpl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(PdfToEmfConverterImpl);
};
ScopedTempFile CreateTempFile(scoped_refptr<RefCountedTempDir>* temp_dir) {
if (!temp_dir->get())
*temp_dir = new RefCountedTempDir();
ScopedTempFile file;
if (!(*temp_dir)->IsValid())
return file.Pass();
base::FilePath path;
if (!base::CreateTemporaryFileInDir((*temp_dir)->GetPath(), &path))
return file.Pass();
file.reset(new base::File(path,
base::File::FLAG_CREATE_ALWAYS |
base::File::FLAG_WRITE |
base::File::FLAG_READ |
base::File::FLAG_DELETE_ON_CLOSE |
base::File::FLAG_TEMPORARY));
if (!file->IsValid())
file.reset();
return file.Pass();
}
ScopedTempFile CreateTempPdfFile(
const scoped_refptr<base::RefCountedMemory>& data,
scoped_refptr<RefCountedTempDir>* temp_dir) {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
ScopedTempFile pdf_file = CreateTempFile(temp_dir);
if (!pdf_file ||
static_cast<int>(data->size()) !=
pdf_file->WriteAtCurrentPos(data->front_as<char>(), data->size())) {
pdf_file.reset();
}
pdf_file->Seek(base::File::FROM_BEGIN, 0);
return pdf_file.Pass();
}
bool LazyEmf::SafePlayback(HDC hdc) const {
Emf emf;
bool result = LoadEmf(&emf) && emf.SafePlayback(hdc);
// TODO(vitalybuka): Fix destruction of metafiles. For some reasons
// instances of Emf are not deleted. crbug.com/411683
// It's known that the Emf going to be played just once to a printer. So just
// release file here.
Close();
return result;
}
bool LazyEmf::SaveTo(base::File* file) const {
Emf emf;
return LoadEmf(&emf) && emf.SaveTo(file);
}
void LazyEmf::Close() const {
file_.reset();
temp_dir_ = NULL;
}
bool LazyEmf::LoadEmf(Emf* emf) const {
file_->Seek(base::File::FROM_BEGIN, 0);
int64 size = file_->GetLength();
if (size <= 0)
return false;
std::vector<char> data(size);
if (file_->ReadAtCurrentPos(data.data(), data.size()) != size)
return false;
return emf->InitFromData(data.data(), data.size());
}
PdfToEmfUtilityProcessHostClient::PdfToEmfUtilityProcessHostClient(
base::WeakPtr<PdfToEmfConverterImpl> converter,
const PdfRenderSettings& settings)
: converter_(converter), settings_(settings) {
}
PdfToEmfUtilityProcessHostClient::~PdfToEmfUtilityProcessHostClient() {
}
void PdfToEmfUtilityProcessHostClient::Start(
const scoped_refptr<base::RefCountedMemory>& data,
const PdfToEmfConverter::StartCallback& start_callback) {
if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(&PdfToEmfUtilityProcessHostClient::Start,
this,
data,
start_callback));
return;
}
data_ = data;
// Store callback before any OnFailed() call to make it called on failure.
start_callback_ = start_callback;
// NOTE: This process _must_ be sandboxed, otherwise the pdf dll will load
// gdiplus.dll, change how rendering happens, and not be able to correctly
// generate when sent to a metafile DC.
utility_process_host_ =
content::UtilityProcessHost::Create(
this, base::MessageLoop::current()->message_loop_proxy())
->AsWeakPtr();
if (!utility_process_host_)
return OnFailed();
// Should reply with OnProcessStarted().
Send(new ChromeUtilityMsg_StartupPing);
}
void PdfToEmfUtilityProcessHostClient::OnProcessStarted() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!utility_process_host_)
return OnFailed();
scoped_refptr<base::RefCountedMemory> data = data_;
data_ = NULL;
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::FILE,
FROM_HERE,
base::Bind(&CreateTempPdfFile, data, &temp_dir_),
base::Bind(&PdfToEmfUtilityProcessHostClient::OnTempPdfReady, this));
}
void PdfToEmfUtilityProcessHostClient::OnTempPdfReady(ScopedTempFile pdf) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!utility_process_host_)
return OnFailed();
base::ProcessHandle process = utility_process_host_->GetData().handle;
// Should reply with OnPageCount().
Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles(
IPC::GetFileHandleForProcess(pdf->GetPlatformFile(), process, false),
settings_));
}
void PdfToEmfUtilityProcessHostClient::OnPageCount(int page_count) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (start_callback_.is_null())
return OnFailed();
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&PdfToEmfConverterImpl::RunCallback,
converter_,
base::Bind(start_callback_, page_count)));
start_callback_.Reset();
}
void PdfToEmfUtilityProcessHostClient::GetPage(
int page_number,
const PdfToEmfConverter::GetPageCallback& get_page_callback) {
if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&PdfToEmfUtilityProcessHostClient::GetPage,
this,
page_number,
get_page_callback));
return;
}
// Store callback before any OnFailed() call to make it called on failure.
get_page_callbacks_.push(GetPageCallbackData(page_number, get_page_callback));
if (!utility_process_host_)
return OnFailed();
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::FILE,
FROM_HERE,
base::Bind(&CreateTempFile, &temp_dir_),
base::Bind(&PdfToEmfUtilityProcessHostClient::OnTempEmfReady,
this,
&get_page_callbacks_.back()));
}
void PdfToEmfUtilityProcessHostClient::OnTempEmfReady(
GetPageCallbackData* callback_data,
ScopedTempFile emf) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!utility_process_host_)
return OnFailed();
base::ProcessHandle process = utility_process_host_->GetData().handle;
IPC::PlatformFileForTransit transit =
IPC::GetFileHandleForProcess(emf->GetPlatformFile(), process, false);
callback_data->set_emf(emf.Pass());
// Should reply with OnPageDone().
Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_GetPage(
callback_data->page_number(), transit));
}
void PdfToEmfUtilityProcessHostClient::OnPageDone(bool success,
float scale_factor) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (get_page_callbacks_.empty())
return OnFailed();
scoped_ptr<MetafilePlayer> emf;
GetPageCallbackData& data = get_page_callbacks_.front();
if (success)
emf.reset(new LazyEmf(temp_dir_, data.emf().Pass()));
BrowserThread::PostTask(BrowserThread::UI,
FROM_HERE,
base::Bind(&PdfToEmfConverterImpl::RunCallback,
converter_,
base::Bind(data.callback(),
data.page_number(),
scale_factor,
base::Passed(&emf))));
get_page_callbacks_.pop();
}
void PdfToEmfUtilityProcessHostClient::Stop() {
if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&PdfToEmfUtilityProcessHostClient::Stop, this));
return;
}
Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_Stop());
}
void PdfToEmfUtilityProcessHostClient::OnProcessCrashed(int exit_code) {
OnFailed();
}
void PdfToEmfUtilityProcessHostClient::OnProcessLaunchFailed() {
OnFailed();
}
bool PdfToEmfUtilityProcessHostClient::OnMessageReceived(
const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(PdfToEmfUtilityProcessHostClient, message)
IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_ProcessStarted, OnProcessStarted)
IPC_MESSAGE_HANDLER(
ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount, OnPageCount)
IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone,
OnPageDone)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
bool PdfToEmfUtilityProcessHostClient::Send(IPC::Message* msg) {
if (utility_process_host_)
return utility_process_host_->Send(msg);
delete msg;
return false;
}
void PdfToEmfUtilityProcessHostClient::OnFailed() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!start_callback_.is_null())
OnPageCount(0);
while (!get_page_callbacks_.empty())
OnPageDone(false, 0.0f);
utility_process_host_.reset();
}
PdfToEmfConverterImpl::PdfToEmfConverterImpl() : weak_ptr_factory_(this) {
}
PdfToEmfConverterImpl::~PdfToEmfConverterImpl() {
if (utility_client_.get())
utility_client_->Stop();
}
void PdfToEmfConverterImpl::Start(
const scoped_refptr<base::RefCountedMemory>& data,
const PdfRenderSettings& conversion_settings,
const StartCallback& start_callback) {
DCHECK(!utility_client_.get());
utility_client_ = new PdfToEmfUtilityProcessHostClient(
weak_ptr_factory_.GetWeakPtr(), conversion_settings);
utility_client_->Start(data, start_callback);
}
void PdfToEmfConverterImpl::GetPage(int page_number,
const GetPageCallback& get_page_callback) {
utility_client_->GetPage(page_number, get_page_callback);
}
void PdfToEmfConverterImpl::RunCallback(const base::Closure& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
callback.Run();
}
} // namespace
PdfToEmfConverter::~PdfToEmfConverter() {
}
// static
scoped_ptr<PdfToEmfConverter> PdfToEmfConverter::CreateDefault() {
return scoped_ptr<PdfToEmfConverter>(new PdfToEmfConverterImpl());
}
} // namespace printing

View file

@ -0,0 +1,48 @@
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_PRINTING_PDF_TO_EMF_CONVERTER_H_
#define CHROME_BROWSER_PRINTING_PDF_TO_EMF_CONVERTER_H_
#include "base/callback.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/scoped_ptr.h"
namespace base {
class FilePath;
}
namespace printing {
class MetafilePlayer;
class PdfRenderSettings;
class PdfToEmfConverter {
public:
typedef base::Callback<void(int page_count)> StartCallback;
typedef base::Callback<void(int page_number,
float scale_factor,
scoped_ptr<MetafilePlayer> emf)> GetPageCallback;
virtual ~PdfToEmfConverter();
static scoped_ptr<PdfToEmfConverter> CreateDefault();
// Starts conversion of PDF provided as |data|. Calls |start_callback|
// with positive |page_count|. |page_count| is 0 if initialization failed.
virtual void Start(const scoped_refptr<base::RefCountedMemory>& data,
const PdfRenderSettings& conversion_settings,
const StartCallback& start_callback) = 0;
// Requests conversion of the page. |page_number| is 0-base page number in
// PDF provided in Start() call.
// Calls |get_page_callback| after conversion. |emf| of callback in not NULL
// if conversion succeeded.
virtual void GetPage(int page_number,
const GetPageCallback& get_page_callback) = 0;
};
} // namespace printing
#endif // CHROME_BROWSER_PRINTING_PDF_TO_EMF_CONVERTER_H_

View file

@ -17,6 +17,12 @@
#include "printing/printed_document.h"
#include "printing/printed_page.h"
#if defined(OS_WIN)
#include "chrome/browser/printing/pdf_to_emf_converter.h"
#include "printing/pdf_render_settings.h"
#endif
using base::TimeDelta;
namespace {
@ -272,6 +278,103 @@ void PrintJob::OnNotifyPrintJobEvent(const JobEventDetails& event_details) {
}
}
#if defined(OS_WIN)
class PrintJob::PdfToEmfState {
public:
PdfToEmfState(const gfx::Size& page_size, const gfx::Rect& content_area)
: page_count_(0),
current_page_(0),
pages_in_progress_(0),
page_size_(page_size),
content_area_(content_area),
converter_(PdfToEmfConverter::CreateDefault()) {}
void Start(const scoped_refptr<base::RefCountedMemory>& data,
const PdfRenderSettings& conversion_settings,
const PdfToEmfConverter::StartCallback& start_callback) {
converter_->Start(data, conversion_settings, start_callback);
}
void GetMorePages(
const PdfToEmfConverter::GetPageCallback& get_page_callback) {
const int kMaxNumberOfTempFilesPerDocument = 3;
while (pages_in_progress_ < kMaxNumberOfTempFilesPerDocument &&
current_page_ < page_count_) {
++pages_in_progress_;
converter_->GetPage(current_page_++, get_page_callback);
}
}
void OnPageProcessed(
const PdfToEmfConverter::GetPageCallback& get_page_callback) {
--pages_in_progress_;
GetMorePages(get_page_callback);
// Release converter if we don't need this any more.
if (!pages_in_progress_ && current_page_ >= page_count_)
converter_.reset();
}
void set_page_count(int page_count) { page_count_ = page_count; }
gfx::Size page_size() const { return page_size_; }
gfx::Rect content_area() const { return content_area_; }
private:
int page_count_;
int current_page_;
int pages_in_progress_;
gfx::Size page_size_;
gfx::Rect content_area_;
scoped_ptr<PdfToEmfConverter> converter_;
};
void PrintJob::StartPdfToEmfConversion(
const scoped_refptr<base::RefCountedMemory>& bytes,
const gfx::Size& page_size,
const gfx::Rect& content_area) {
DCHECK(!ptd_to_emf_state_.get());
ptd_to_emf_state_.reset(new PdfToEmfState(page_size, content_area));
const int kPrinterDpi = settings().dpi();
ptd_to_emf_state_->Start(
bytes,
printing::PdfRenderSettings(content_area, kPrinterDpi, true),
base::Bind(&PrintJob::OnPdfToEmfStarted, this));
}
void PrintJob::OnPdfToEmfStarted(int page_count) {
if (page_count <= 0) {
ptd_to_emf_state_.reset();
Cancel();
return;
}
ptd_to_emf_state_->set_page_count(page_count);
ptd_to_emf_state_->GetMorePages(
base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
}
void PrintJob::OnPdfToEmfPageConverted(int page_number,
float scale_factor,
scoped_ptr<MetafilePlayer> emf) {
DCHECK(ptd_to_emf_state_);
if (!document_.get() || !emf) {
ptd_to_emf_state_.reset();
Cancel();
return;
}
// Update the rendered document. It will send notifications to the listener.
document_->SetPage(page_number,
emf.Pass(),
scale_factor,
ptd_to_emf_state_->page_size(),
ptd_to_emf_state_->content_area());
ptd_to_emf_state_->GetMorePages(
base::Bind(&PrintJob::OnPdfToEmfPageConverted, this));
}
#endif // OS_WIN
void PrintJob::OnDocumentDone() {
// Be sure to live long enough. The instance could be destroyed by the
// JOB_DONE broadcast.

View file

@ -90,6 +90,19 @@ class PrintJob : public PrintJobWorkerOwner,
// Access the current printed document. Warning: may be NULL.
PrintedDocument* document() const;
#if defined(OS_WIN)
void StartPdfToEmfConversion(
const scoped_refptr<base::RefCountedMemory>& bytes,
const gfx::Size& page_size,
const gfx::Rect& content_area);
void OnPdfToEmfStarted(int page_count);
void OnPdfToEmfPageConverted(int page_number,
float scale_factor,
scoped_ptr<MetafilePlayer> emf);
#endif // OS_WIN
protected:
virtual ~PrintJob();
@ -137,6 +150,11 @@ class PrintJob : public PrintJobWorkerOwner,
// the notified calls Cancel() again.
bool is_canceling_;
#if defined(OS_WIN)
class PdfToEmfState;
scoped_ptr<PdfToEmfState> ptd_to_emf_state_;
#endif // OS_WIN
// Used at shutdown so that we can quit a nested message loop.
base::WeakPtrFactory<PrintJob> quit_factory_;

View file

@ -156,6 +156,8 @@ void PrintViewManagerBase::OnDidPrintPage(
params.data_size);
document->DebugDumpData(bytes.get(), FILE_PATH_LITERAL(".pdf"));
print_job_->StartPdfToEmfConversion(
bytes, params.page_size, params.content_area);
}
#endif // !OS_WIN
}