0

Print Compositor interface support for XPS

The Print Compositor (nee PDF Compositor) is being extended to support
generating XPS documents for Windows.  PDF is still created for the
individual pages (e.g., for Print Preview), while XPS will optionally
be used for the full document which is used for printing to a
destination.

Include desired document type in calls for Print Compositor so it can
generate the correct type for each required composition request.

Bug: 1008222
Change-Id: Idb3e182d5126cca3c9de337b9c5c6dc4220b94d7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2032786
Reviewed-by: Lei Zhang <thestig@chromium.org>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Commit-Queue: Alan Screen <awscreen@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1238177}
This commit is contained in:
Alan Screen
2023-12-15 19:04:32 +00:00
committed by Chromium LUCI CQ
parent e8d283b65f
commit c2a2758e79
15 changed files with 126 additions and 26 deletions

@ -1247,7 +1247,8 @@ IN_PROC_BROWSER_TEST_F(PrintBrowserTest,
client->CompositeDocument(
kDefaultDocumentCookie, main_frame,
*TestPrintRenderFrame::GetDefaultDidPrintContentParams(),
ui::AXTreeUpdate(), base::DoNothing());
ui::AXTreeUpdate(), PrintCompositeClient::GetDocumentType(),
base::DoNothing());
ASSERT_TRUE(client->GetCompositeRequest(kDefaultDocumentCookie));
// `requested_subframes_` should be empty.
ASSERT_TRUE(client->requested_subframes_.empty());

@ -618,7 +618,7 @@ void PrintViewManagerBase::DidPrintDocument(
auto* client = PrintCompositeClient::FromWebContents(web_contents());
client->CompositeDocument(
params->document_cookie, GetCurrentTargetFrame(), content,
ui::AXTreeUpdate(),
ui::AXTreeUpdate(), PrintCompositeClient::GetDocumentType(),
base::BindOnce(&PrintViewManagerBase::OnComposeDocumentDone,
weak_ptr_factory_.GetWeakPtr(), params->document_cookie,
params->page_size, params->content_area,

@ -980,6 +980,7 @@ void PrintPreviewUI::DidPrepareDocumentForPreview(int32_t document_cookie,
client->PrepareToCompositeDocument(
document_cookie, render_frame_host,
PrintCompositeClient::GetDocumentType(),
mojo::WrapCallbackWithDefaultInvokeIfNotRun(
base::BindOnce(&PrintPreviewUI::OnPrepareForDocumentToPdfDone,
weak_ptr_factory_.GetWeakPtr(), request_id),

@ -20,7 +20,9 @@
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/service_process_host.h"
#include "printing/common/metafile_utils.h"
#include "printing/mojom/print.mojom.h"
#include "printing/print_settings.h"
#include "printing/printed_document.h"
#include "printing/printing_utils.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
@ -71,6 +73,21 @@ PrintCompositeClient::PrintCompositeClient(content::WebContents* web_contents)
PrintCompositeClient::~PrintCompositeClient() {}
// static
mojom::PrintCompositor::DocumentType PrintCompositeClient::GetDocumentType() {
// Using the compositor already means that the source is modifiable (e.g., not
// PDF).
mojom::SkiaDocumentType skia_document_type =
GetPrintDocumentType(/*source_is_pdf=*/false);
#if BUILDFLAG(IS_WIN)
if (skia_document_type == mojom::SkiaDocumentType::kXPS) {
return mojom::PrintCompositor::DocumentType::kXPS;
}
#endif
CHECK_EQ(skia_document_type, mojom::SkiaDocumentType::kPDF);
return mojom::PrintCompositor::DocumentType::kPDF;
}
void PrintCompositeClient::RenderFrameDeleted(
content::RenderFrameHost* render_frame_host) {
if (document_cookie_ == 0) {
@ -226,6 +243,7 @@ void PrintCompositeClient::CompositePage(
void PrintCompositeClient::PrepareToCompositeDocument(
int document_cookie,
content::RenderFrameHost* render_frame_host,
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::PrepareToCompositeDocumentCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!GetIsDocumentConcurrentlyComposited(document_cookie));
@ -233,6 +251,7 @@ void PrintCompositeClient::PrepareToCompositeDocument(
auto* compositor = CreateCompositeRequest(document_cookie, render_frame_host);
is_doc_concurrently_composited_ = true;
compositor->PrepareToCompositeDocument(
document_type,
base::BindOnce(&PrintCompositeClient::OnDidPrepareToCompositeDocument,
std::move(callback)));
}
@ -264,6 +283,7 @@ void PrintCompositeClient::CompositeDocument(
content::RenderFrameHost* render_frame_host,
const mojom::DidPrintContentParams& content,
const ui::AXTreeUpdate& accessibility_tree,
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::CompositeDocumentCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!GetIsDocumentConcurrentlyComposited(document_cookie));
@ -292,6 +312,7 @@ void PrintCompositeClient::CompositeDocument(
compositor->CompositeDocument(
GenerateFrameGuid(render_frame_host), std::move(region),
ConvertContentInfoMap(render_frame_host, content.subframe_content_info),
document_type,
base::BindOnce(&PrintCompositeClient::OnDidCompositeDocument,
base::Unretained(this), document_cookie,
std::move(callback)));

@ -38,6 +38,10 @@ class PrintCompositeClient
PrintCompositeClient& operator=(const PrintCompositeClient&) = delete;
~PrintCompositeClient() override;
// Determine the document format type to be generated when compositing full
// document.
static mojom::PrintCompositor::DocumentType GetDocumentType();
// content::WebContentsObserver
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
@ -60,15 +64,19 @@ class PrintCompositeClient
mojom::PrintCompositor::CompositePageCallback callback);
// Notifies compositor to collect individual pages into a document
// when processing the individual pages for preview.
// when processing the individual pages for preview. The `document_type`
// specified determines the format of the document passed back in the
// `callback` from `FinishDocumentComposition()`.
void PrepareToCompositeDocument(
int document_cookie,
content::RenderFrameHost* render_frame_host,
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::PrepareToCompositeDocumentCallback callback);
// Notifies compositor of the total number of pages being concurrently
// collected into the document, allowing for completion of the composition
// when all pages have been received.
// when all pages have been received. The format of the provided document
// is of the `document_type` specified in `PrepareToCompositeDocument()`.
void FinishDocumentComposition(
int document_cookie,
uint32_t pages_count,
@ -81,6 +89,7 @@ class PrintCompositeClient
content::RenderFrameHost* render_frame_host,
const mojom::DidPrintContentParams& content,
const ui::AXTreeUpdate& accessibility_tree,
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::CompositeDocumentCallback callback);
// Get the concurrent composition status for a document. Identifies if the

@ -95,6 +95,7 @@ void PdfPrintJob::OnDidPrintWithParams(
->CompositeDocument(
params->document_cookie, printing_rfh_, content,
result->get_data()->accessibility_tree,
printing::mojom::PrintCompositor::DocumentType::kPDF,
base::BindOnce(&PdfPrintJob::OnCompositeDocumentToPdfDone,
weak_ptr_factory_.GetWeakPtr()));
}

@ -161,25 +161,30 @@ void PrintCompositorImpl::CompositePage(
TRACE_EVENT0("print", "PrintCompositorImpl::CompositePage");
if (docinfo_)
docinfo_->pages_provided++;
HandleCompositionRequest(frame_guid, std::move(serialized_content),
subframe_content_map, std::move(callback));
// This function is always called to composite a page to PDF.
HandleCompositionRequest(
frame_guid, std::move(serialized_content), subframe_content_map,
mojom::PrintCompositor::DocumentType::kPDF, std::move(callback));
}
void PrintCompositorImpl::CompositeDocument(
uint64_t frame_guid,
base::ReadOnlySharedMemoryRegion serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::CompositeDocumentCallback callback) {
TRACE_EVENT0("print", "PrintCompositorImpl::CompositeDocument");
DCHECK(!docinfo_);
HandleCompositionRequest(frame_guid, std::move(serialized_content),
subframe_content_map, std::move(callback));
subframe_content_map, document_type,
std::move(callback));
}
void PrintCompositorImpl::PrepareToCompositeDocument(
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::PrepareToCompositeDocumentCallback callback) {
DCHECK(!docinfo_);
docinfo_ = std::make_unique<DocumentInfo>();
docinfo_ = std::make_unique<DocumentInfo>(document_type);
std::move(callback).Run(mojom::PrintCompositor::Status::kSuccess);
}
@ -238,7 +243,7 @@ void PrintCompositorImpl::UpdateRequestsWithSubframeInfo(
// Fulfill the request now.
FulfillRequest(request->serialized_content, request->subframe_content_map,
std::move(request->callback));
request->document_type, std::move(request->callback));
// Check for a collected print preview document that was waiting on
// this page to finish.
@ -287,6 +292,7 @@ void PrintCompositorImpl::HandleCompositionRequest(
uint64_t frame_guid,
base::ReadOnlySharedMemoryRegion serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PrintCompositor::DocumentType document_type,
CompositePagesCallback callback) {
base::ReadOnlySharedMemoryMapping mapping = serialized_content.Map();
if (!mapping.IsValid()) {
@ -305,7 +311,7 @@ void PrintCompositorImpl::HandleCompositionRequest(
// fail by trying to use a typeface which hasn't been deserialized yet.
if (requests_.empty()) {
FulfillRequest(mapping.GetMemoryAsSpan<uint8_t>(), subframe_content_map,
std::move(callback));
document_type, std::move(callback));
return;
}
}
@ -318,7 +324,7 @@ void PrintCompositorImpl::HandleCompositionRequest(
requests_.push_back(std::make_unique<RequestInfo>(
mapping.GetMemoryAsSpan<uint8_t>(), subframe_content_map,
std::move(pending_subframes), std::move(callback)));
std::move(pending_subframes), document_type, std::move(callback)));
}
void PrintCompositorImpl::HandleDocumentCompletionRequest() {
@ -334,7 +340,8 @@ void PrintCompositorImpl::HandleDocumentCompletionRequest() {
mojom::PrintCompositor::Status PrintCompositorImpl::CompositePages(
base::span<const uint8_t> serialized_content,
const ContentToFrameMap& subframe_content_map,
base::ReadOnlySharedMemoryRegion* region) {
base::ReadOnlySharedMemoryRegion* region,
mojom::PrintCompositor::DocumentType document_type) {
TRACE_EVENT0("print", "PrintCompositorImpl::CompositePages");
PictureDeserializationContext subframes =
@ -371,6 +378,8 @@ mojom::PrintCompositor::Status PrintCompositorImpl::CompositePages(
if (docinfo_) {
// Create full document if needed.
if (!docinfo_->doc) {
// TODO(crbug.com/1008222) Make use of `document_type` parameter once
// `MakeXpsDocument()` is available.
docinfo_->doc = MakePdfDocument(creator_, accessibility_tree_,
GeneratePdfDocumentOutline::kNone,
&docinfo_->compositor_stream);
@ -435,10 +444,11 @@ PrintCompositorImpl::GetPictureDeserializationContext(
void PrintCompositorImpl::FulfillRequest(
base::span<const uint8_t> serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PrintCompositor::DocumentType document_type,
CompositePagesCallback callback) {
base::ReadOnlySharedMemoryRegion region;
auto status =
CompositePages(serialized_content, subframe_content_map, &region);
auto status = CompositePages(serialized_content, subframe_content_map,
&region, document_type);
std::move(callback).Run(status, std::move(region));
}
@ -475,7 +485,11 @@ PrintCompositorImpl::FrameContentInfo::FrameContentInfo() = default;
PrintCompositorImpl::FrameContentInfo::~FrameContentInfo() = default;
PrintCompositorImpl::DocumentInfo::DocumentInfo() = default;
// TODO(crbug.com/1008222) Make use of `document_type` parameter once
// `MakeXpsDocument()` is available.
PrintCompositorImpl::DocumentInfo::DocumentInfo(
mojom::PrintCompositor::DocumentType document_type)
: document_type(document_type) {}
PrintCompositorImpl::DocumentInfo::~DocumentInfo() = default;
@ -483,9 +497,11 @@ PrintCompositorImpl::RequestInfo::RequestInfo(
base::span<const uint8_t> content,
const ContentToFrameMap& content_info,
const base::flat_set<uint64_t>& pending_subframes,
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::CompositePageCallback callback)
: FrameContentInfo(content, content_info),
pending_subframes(pending_subframes),
document_type(document_type),
callback(std::move(callback)) {}
PrintCompositorImpl::RequestInfo::~RequestInfo() = default;

@ -76,8 +76,10 @@ class PrintCompositorImpl : public mojom::PrintCompositor {
uint64_t frame_guid,
base::ReadOnlySharedMemoryRegion serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::CompositeDocumentCallback callback) override;
void PrepareToCompositeDocument(
mojom::PrintCompositor::DocumentType document_type,
mojom::PrintCompositor::PrepareToCompositeDocumentCallback callback)
override;
void FinishDocumentComposition(
@ -101,17 +103,21 @@ class PrintCompositorImpl : public mojom::PrintCompositor {
base::OnceCallback<void(PrintCompositor::Status,
base::ReadOnlySharedMemoryRegion)>;
// The core function for content composition and conversion to a pdf file.
// The core function for content composition and conversion to a PDF file,
// and possibly also into a full document PDF/XPS file.
// Make this function virtual so tests can override it.
virtual mojom::PrintCompositor::Status CompositePages(
base::span<const uint8_t> serialized_content,
const ContentToFrameMap& subframe_content_map,
base::ReadOnlySharedMemoryRegion* region);
base::ReadOnlySharedMemoryRegion* region,
mojom::PrintCompositor::DocumentType document_type);
// Make these functions virtual so tests can override them.
virtual void FulfillRequest(base::span<const uint8_t> serialized_content,
const ContentToFrameMap& subframe_content_map,
CompositePagesCallback callback);
virtual void FulfillRequest(
base::span<const uint8_t> serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PrintCompositor::DocumentType document_type,
CompositePagesCallback callback);
virtual void FinishDocumentRequest(
FinishDocumentCompositionCallback callback);
@ -168,6 +174,7 @@ class PrintCompositorImpl : public mojom::PrintCompositor {
RequestInfo(base::span<const uint8_t> content,
const ContentToFrameMap& content_info,
const base::flat_set<uint64_t>& pending_subframes,
mojom::PrintCompositor::DocumentType document_type,
CompositePagesCallback callback);
~RequestInfo();
@ -175,17 +182,19 @@ class PrintCompositorImpl : public mojom::PrintCompositor {
// for composition.
base::flat_set<uint64_t> pending_subframes;
mojom::PrintCompositor::DocumentType document_type;
CompositePagesCallback callback;
bool is_concurrent_doc_composition = false;
};
// Stores the concurrent document composition information.
struct DocumentInfo {
DocumentInfo();
explicit DocumentInfo(mojom::PrintCompositor::DocumentType document_type);
~DocumentInfo();
SkDynamicMemoryWStream compositor_stream;
sk_sp<SkDocument> doc;
mojom::PrintCompositor::DocumentType document_type;
uint32_t pages_provided = 0;
uint32_t pages_written = 0;
uint32_t page_count = 0;
@ -217,6 +226,7 @@ class PrintCompositorImpl : public mojom::PrintCompositor {
uint64_t frame_guid,
base::ReadOnlySharedMemoryRegion serialized_content,
const ContentToFrameMap& subframe_content_ids,
mojom::PrintCompositor::DocumentType document_type,
CompositePagesCallback callback);
void HandleDocumentCompletionRequest();

@ -36,6 +36,7 @@ class MockPrintCompositorImpl : public PrintCompositorImpl {
protected:
void FulfillRequest(base::span<const uint8_t> serialized_content,
const ContentToFrameMap& subframe_content_map,
mojom::PrintCompositor::DocumentType document_type,
CompositePageCallback callback) override {
const auto* data =
reinterpret_cast<const TestRequestData*>(serialized_content.data());
@ -60,7 +61,8 @@ class MockCompletionPrintCompositorImpl : public PrintCompositorImpl {
mojom::PrintCompositor::Status CompositePages(
base::span<const uint8_t> serialized_content,
const ContentToFrameMap& subframe_content_map,
base::ReadOnlySharedMemoryRegion* region) override {
base::ReadOnlySharedMemoryRegion* region,
mojom::PrintCompositor::DocumentType document_type) override {
const auto* data =
reinterpret_cast<const TestRequestData*>(serialized_content.data());
if (docinfo_)
@ -285,6 +287,7 @@ TEST_F(PrintCompositorImplTest, MultiRequestsBasic) {
impl.CompositeDocument(
3, CreateTestData(3, -1), subframe_content_map,
mojom::PrintCompositor::DocumentType::kPDF,
base::BindOnce(&PrintCompositorImplTest::OnCompositePageCallback));
}
@ -306,6 +309,7 @@ TEST_F(PrintCompositorImplTest, MultiRequestsOrder) {
impl.CompositeDocument(
3, CreateTestData(3, -1), subframe_content_map,
mojom::PrintCompositor::DocumentType::kPDF,
base::BindOnce(&PrintCompositorImplTest::OnCompositePageCallback));
testing::Mock::VerifyAndClearExpectations(&impl);
@ -386,8 +390,10 @@ TEST_F(PrintCompositorImplTest, MultiRequestsBasicFinishDocument) {
// Page 0 with frame 3 has content 1, which refers to frame 8.
// When the content is not available, the request is not fulfilled.
const ContentToFrameMap subframe_content_map = {{1, 8}};
impl.PrepareToCompositeDocument(base::BindOnce(
&PrintCompositorImplTest::OnPrepareToCompositeDocumentCallback));
impl.PrepareToCompositeDocument(
mojom::PrintCompositor::DocumentType::kPDF,
base::BindOnce(
&PrintCompositorImplTest::OnPrepareToCompositeDocumentCallback));
EXPECT_CALL(impl, OnCompositePage(testing::_, testing::_)).Times(0);
impl.CompositePage(
3, CreateTestData(3, 0), subframe_content_map,

@ -19,6 +19,11 @@ interface PrintCompositor {
kCompositingFailure = 3,
};
enum DocumentType {
kPDF = 0,
kXPS = 1,
};
// Notifies that a subframe is unavailable, such as the render frame process
// hosting it crashed or terminated. The subframe will be composited with no
// content in the composited result.
@ -68,7 +73,8 @@ interface PrintCompositor {
// `FinishDocumentComposition()`.
CompositeDocument(uint64 frame_guid,
mojo_base.mojom.ReadOnlySharedMemoryRegion sk_region,
map<uint32, uint64> subframe_content_info)
map<uint32, uint64> subframe_content_info,
DocumentType document_type)
=> (Status status,
mojo_base.mojom.ReadOnlySharedMemoryRegion? document_region);
@ -76,7 +82,7 @@ interface PrintCompositor {
// `CompositePage()` concurrently into a document. Must be issued once
// prior to any `CompositePage()` calls in order for concurrent collection
// to be performed.
PrepareToCompositeDocument()
PrepareToCompositeDocument(DocumentType document_type)
=> (Status status);
// Signals that all pages for a composite document have been sent via

@ -72,6 +72,7 @@ component("printing_base") {
public_deps = [
"//build:chromeos_buildflags",
"//printing/buildflags",
"//printing/mojom",
]
deps = [
"//base",

@ -206,6 +206,13 @@ bool MetafileSkia::FinishDocument() {
doc = MakePdfDocument(printing::GetAgent(), accessibility_tree_,
generate_document_outline_, &stream);
break;
#if BUILDFLAG(IS_WIN)
case mojom::SkiaDocumentType::kXPS:
// TODO(crbug.com/1008222) Update to use MakeXpsDocument() once it is
// available.
NOTIMPLEMENTED();
break;
#endif
case mojom::SkiaDocumentType::kMSKP:
SkSerialProcs procs = SerializationProcs(&data_->subframe_content_info,
data_->typeface_content_info);

@ -100,6 +100,9 @@ enum SkiaDocumentType {
kPDF,
// MultiPictureDocument type
kMSKP,
// XPS document type.
[EnableIf=is_win]
kXPS,
};
// Metafile data type encapsulated by a player.

@ -13,6 +13,7 @@
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "printing/mojom/print.mojom.h"
#include "third_party/icu/source/common/unicode/uchar.h"
#include "ui/gfx/text_elider.h"
@ -28,6 +29,8 @@
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#include "printing/printing_features.h"
#endif
namespace printing {
@ -164,4 +167,14 @@ bool LooksLikePdf(base::span<const char> maybe_pdf_data) {
std::memcmp(maybe_pdf_data.data(), "%PDF-", 5) == 0;
}
mojom::SkiaDocumentType GetPrintDocumentType(bool source_is_pdf) {
#if BUILDFLAG(IS_WIN)
return printing::features::ShouldPrintUsingXps(source_is_pdf)
? mojom::SkiaDocumentType::kXPS
: mojom::SkiaDocumentType::kPDF;
#else
return mojom::SkiaDocumentType::kPDF;
#endif
}
} // namespace printing

@ -14,6 +14,7 @@
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "printing/buildflags/buildflags.h"
#include "printing/mojom/print.mojom-forward.h"
#if BUILDFLAG(USE_CUPS) && !BUILDFLAG(IS_CHROMEOS_ASH)
#include "base/strings/string_piece.h"
@ -80,6 +81,10 @@ gfx::Rect GetPrintableAreaDeviceUnits(HDC hdc);
COMPONENT_EXPORT(PRINTING_BASE)
bool LooksLikePdf(base::span<const char> maybe_pdf_data);
// Determine the document format type appropriate to generate for printing.
COMPONENT_EXPORT(PRINTING_BASE)
mojom::SkiaDocumentType GetPrintDocumentType(bool source_is_pdf);
} // namespace printing
#endif // PRINTING_PRINTING_UTILS_H_