blob: 0a0eb0915bb66ba51cabdc88f52b5c5becd3fb0a [file] [log] [blame]
// 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 <stdint.h>
#include <windows.h>
#include <memory>
#include <queue>
#include <utility>
#include <vector>
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/common/chrome_utility_messages.h"
#include "chrome/common/chrome_utility_printing_messages.h"
#include "chrome/grit/generated_resources.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"
#include "ui/base/l10n/l10n_util.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_.GetPath(); }
private:
friend struct BrowserThread::DeleteOnThread<BrowserThread::FILE>;
friend class base::DeleteHelper<RefCountedTempDir>;
~RefCountedTempDir() {}
base::ScopedTempDir temp_dir_;
DISALLOW_COPY_AND_ASSIGN(RefCountedTempDir);
};
typedef std::unique_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_(std::move(file)) {
CHECK(file_);
}
~LazyEmf() override { Close(); }
bool SafePlayback(HDC hdc) const override;
bool GetDataAsVector(std::vector<char>* buffer) const override;
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,
bool print_text_with_gdi,
const PdfToEmfConverter::StartCallback& start_callback);
void GetPage(int page_number,
const PdfToEmfConverter::GetPageCallback& get_page_callback);
void Stop();
// Needs to be public to handle ChromeUtilityHostMsg_PreCacheFontCharacters
// sync message replies.
bool Send(IPC::Message* msg);
// UtilityProcessHostClient implementation.
void OnProcessCrashed(int exit_code) override;
void OnProcessLaunchFailed(int exit_code) override;
bool OnMessageReceived(const IPC::Message& message) override;
private:
class GetPageCallbackData {
public:
GetPageCallbackData(int page_number,
PdfToEmfConverter::GetPageCallback callback)
: page_number_(page_number), callback_(callback) {}
GetPageCallbackData(GetPageCallbackData&& other) {
*this = std::move(other);
}
GetPageCallbackData& operator=(GetPageCallbackData&& rhs) {
page_number_ = rhs.page_number_;
callback_ = rhs.callback_;
emf_ = std::move(rhs.emf_);
return *this;
}
int page_number() const { return page_number_; }
const PdfToEmfConverter::GetPageCallback& callback() const {
return callback_;
}
ScopedTempFile TakeEmf() { return std::move(emf_); }
void set_emf(ScopedTempFile emf) { emf_ = std::move(emf); }
private:
int page_number_;
PdfToEmfConverter::GetPageCallback callback_;
ScopedTempFile emf_;
DISALLOW_COPY_AND_ASSIGN(GetPageCallbackData);
};
~PdfToEmfUtilityProcessHostClient() override;
// Message handlers.
void OnPageCount(int page_count);
void OnPageDone(bool success, float scale_factor);
void OnPreCacheFontCharacters(const LOGFONT& log_font,
const base::string16& characters);
void OnFailed();
void OnTempPdfReady(bool print_text_with_gdi, 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_;
// 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();
~PdfToEmfConverterImpl() override;
void Start(const scoped_refptr<base::RefCountedMemory>& data,
const PdfRenderSettings& conversion_settings,
bool print_text_with_gdi,
const StartCallback& start_callback) override;
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;
base::FilePath path;
if (!base::CreateTemporaryFileInDir((*temp_dir)->GetPath(), &path)) {
PLOG(ERROR) << "Failed to create file in "
<< (*temp_dir)->GetPath().value();
return file;
}
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()) {
PLOG(ERROR) << "Failed to create " << path.value();
file.reset();
}
return file;
}
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();
return pdf_file;
}
pdf_file->Seek(base::File::FROM_BEGIN, 0);
return pdf_file;
}
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::GetDataAsVector(std::vector<char>* buffer) const {
NOTREACHED();
return false;
}
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_t 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,
bool print_text_with_gdi,
const PdfToEmfConverter::StartCallback& start_callback) {
if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&PdfToEmfUtilityProcessHostClient::Start, this, data,
print_text_with_gdi, start_callback));
return;
}
// 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::ThreadTaskRunnerHandle::Get())
->AsWeakPtr();
utility_process_host_->SetName(l10n_util::GetStringUTF16(
IDS_UTILITY_PROCESS_EMF_CONVERTOR_NAME));
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::FILE, FROM_HERE,
base::Bind(&CreateTempPdfFile, data, &temp_dir_),
base::Bind(&PdfToEmfUtilityProcessHostClient::OnTempPdfReady, this,
print_text_with_gdi));
}
void PdfToEmfUtilityProcessHostClient::OnTempPdfReady(bool print_text_with_gdi,
ScopedTempFile pdf) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!utility_process_host_ || !pdf)
return OnFailed();
// Should reply with OnPageCount().
Send(new ChromeUtilityMsg_RenderPDFPagesToMetafiles_Start(
IPC::GetPlatformFileForTransit(pdf->GetPlatformFile(), false), settings_,
print_text_with_gdi));
}
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_ || !emf)
return OnFailed();
IPC::PlatformFileForTransit transit =
IPC::GetPlatformFileForTransit(emf->GetPlatformFile(), false);
callback_data->set_emf(std::move(emf));
// 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();
GetPageCallbackData& data = get_page_callbacks_.front();
std::unique_ptr<MetafilePlayer> emf;
if (success) {
ScopedTempFile temp_emf = data.TakeEmf();
if (!temp_emf) // Unexpected message from utility process.
return OnFailed();
emf = base::MakeUnique<LazyEmf>(temp_dir_, std::move(temp_emf));
}
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::OnPreCacheFontCharacters(
const LOGFONT& font,
const base::string16& str) {
// TODO(scottmg): pdf/ppapi still require the renderer to be able to precache
// GDI fonts (http://crbug.com/383227), even when using DirectWrite.
// Eventually this shouldn't be added and should be moved to
// FontCacheDispatcher too. http://crbug.com/356346.
// First, comments from FontCacheDispatcher::OnPreCacheFont do apply here too.
// Except that for True Type fonts,
// GetTextMetrics will not load the font in memory.
// The only way windows seem to load properly, it is to create a similar
// device (like the one in which we print), then do an ExtTextOut,
// as we do in the printing thread, which is sandboxed.
HDC hdc = CreateEnhMetaFile(nullptr, nullptr, nullptr, nullptr);
HFONT font_handle = CreateFontIndirect(&font);
DCHECK(font_handle != nullptr);
HGDIOBJ old_font = SelectObject(hdc, font_handle);
DCHECK(old_font != nullptr);
ExtTextOut(hdc, 0, 0, ETO_GLYPH_INDEX, 0, str.c_str(), str.length(), nullptr);
SelectObject(hdc, old_font);
DeleteObject(font_handle);
HENHMETAFILE metafile = CloseEnhMetaFile(hdc);
if (metafile)
DeleteEnhMetaFile(metafile);
}
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(int exit_code) {
OnFailed();
}
bool PdfToEmfUtilityProcessHostClient::OnMessageReceived(
const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(PdfToEmfUtilityProcessHostClient, message)
IPC_MESSAGE_HANDLER(
ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageCount, OnPageCount)
IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_RenderPDFPagesToMetafiles_PageDone,
OnPageDone)
IPC_MESSAGE_HANDLER(ChromeUtilityHostMsg_PreCacheFontCharacters,
OnPreCacheFontCharacters)
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,
bool print_text_with_gdi,
const StartCallback& start_callback) {
DCHECK(!utility_client_.get());
utility_client_ = new PdfToEmfUtilityProcessHostClient(
weak_ptr_factory_.GetWeakPtr(), conversion_settings);
utility_client_->Start(data, print_text_with_gdi, 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
std::unique_ptr<PdfToEmfConverter> PdfToEmfConverter::CreateDefault() {
return std::unique_ptr<PdfToEmfConverter>(new PdfToEmfConverterImpl());
}
} // namespace printing