123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 |
- // Copyright (c) 2012 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 "printing/metafile_skia.h"
- #include <algorithm>
- #include <map>
- #include <tuple>
- #include <utility>
- #include <vector>
- #include "base/bind.h"
- #include "base/callback_helpers.h"
- #include "base/containers/contains.h"
- #include "base/containers/span.h"
- #include "base/files/file.h"
- #include "base/memory/raw_ptr.h"
- #include "base/numerics/safe_conversions.h"
- #include "base/time/time.h"
- #include "base/unguessable_token.h"
- #include "build/build_config.h"
- #include "cc/paint/paint_record.h"
- #include "cc/paint/paint_recorder.h"
- #include "cc/paint/skia_paint_canvas.h"
- #include "printing/metafile_agent.h"
- #include "printing/mojom/print.mojom.h"
- #include "third_party/skia/include/core/SkCanvas.h"
- #include "third_party/skia/include/core/SkPicture.h"
- #include "third_party/skia/include/core/SkSerialProcs.h"
- #include "third_party/skia/include/core/SkStream.h"
- // Note that headers in third_party/skia/src are fragile. This is
- // an experimental, fragile, and diagnostic-only document type.
- #include "third_party/skia/src/utils/SkMultiPictureDocument.h"
- #include "ui/gfx/geometry/skia_conversions.h"
- #if BUILDFLAG(IS_MAC)
- #include "printing/pdf_metafile_cg_mac.h"
- #endif
- #if BUILDFLAG(IS_POSIX)
- #include "base/file_descriptor_posix.h"
- #endif
- #if BUILDFLAG(IS_ANDROID)
- #include "base/files/file_util.h"
- #endif // BUILDFLAG(IS_ANDROID)
- namespace {
- // `InitFromData()` should make a copy of data for the safety of all operations
- // which would then operate upon that.
- constexpr bool kInitFromDataCopyData = true;
- bool WriteAssetToBuffer(const SkStreamAsset* asset, void* buffer, size_t size) {
- // Calling duplicate() keeps original asset state unchanged.
- std::unique_ptr<SkStreamAsset> assetCopy(asset->duplicate());
- size_t length = assetCopy->getLength();
- return length <= size && length == assetCopy->read(buffer, length);
- }
- } // namespace
- namespace printing {
- struct Page {
- Page(const SkSize& s, sk_sp<cc::PaintRecord> c)
- : size(s), content(std::move(c)) {}
- Page(Page&& that) : size(that.size), content(std::move(that.content)) {}
- Page(const Page&) = default;
- Page& operator=(const Page&) = default;
- Page& operator=(Page&& that) {
- size = that.size;
- content = std::move(that.content);
- return *this;
- }
- SkSize size;
- sk_sp<cc::PaintRecord> content;
- };
- struct MetafileSkiaData {
- cc::PaintRecorder recorder; // Current recording
- std::vector<Page> pages;
- std::unique_ptr<SkStreamAsset> data_stream;
- ContentToProxyTokenMap subframe_content_info;
- std::map<uint32_t, sk_sp<SkPicture>> subframe_pics;
- int document_cookie = 0;
- raw_ptr<ContentProxySet> typeface_content_info = nullptr;
- // The scale factor is used because Blink occasionally calls
- // PaintCanvas::getTotalMatrix() even though the total matrix is not as
- // meaningful for a vector canvas as for a raster canvas.
- float scale_factor;
- SkSize size;
- mojom::SkiaDocumentType type;
- #if BUILDFLAG(IS_MAC)
- PdfMetafileCg pdf_cg;
- #endif
- };
- MetafileSkia::MetafileSkia() : data_(std::make_unique<MetafileSkiaData>()) {
- data_->type = mojom::SkiaDocumentType::kPDF;
- }
- MetafileSkia::MetafileSkia(mojom::SkiaDocumentType type, int document_cookie)
- : data_(std::make_unique<MetafileSkiaData>()) {
- data_->type = type;
- data_->document_cookie = document_cookie;
- }
- MetafileSkia::~MetafileSkia() = default;
- bool MetafileSkia::Init() {
- return true;
- }
- void MetafileSkia::UtilizeTypefaceContext(
- ContentProxySet* typeface_content_info) {
- data_->typeface_content_info = typeface_content_info;
- }
- // TODO(halcanary): Create a Metafile class that only stores data.
- // Metafile::InitFromData is orthogonal to what the rest of
- // MetafileSkia does.
- bool MetafileSkia::InitFromData(base::span<const uint8_t> data) {
- data_->data_stream = std::make_unique<SkMemoryStream>(
- data.data(), data.size(), kInitFromDataCopyData);
- return true;
- }
- void MetafileSkia::StartPage(const gfx::Size& page_size,
- const gfx::Rect& content_area,
- float scale_factor,
- mojom::PageOrientation page_orientation) {
- gfx::Size physical_page_size = page_size;
- if (page_orientation != mojom::PageOrientation::kUpright)
- physical_page_size.SetSize(page_size.height(), page_size.width());
- DCHECK_GT(page_size.width(), 0);
- DCHECK_GT(page_size.height(), 0);
- DCHECK_GT(scale_factor, 0.0f);
- if (data_->recorder.getRecordingCanvas())
- FinishPage();
- DCHECK(!data_->recorder.getRecordingCanvas());
- float inverse_scale = 1.0 / scale_factor;
- cc::PaintCanvas* canvas = data_->recorder.beginRecording(
- inverse_scale * physical_page_size.width(),
- inverse_scale * physical_page_size.height());
- // Recording canvas is owned by the `data_->recorder`. No ref() necessary.
- if (content_area != gfx::Rect(page_size) ||
- page_orientation != mojom::PageOrientation::kUpright) {
- canvas->scale(inverse_scale, inverse_scale);
- if (page_orientation == mojom::PageOrientation::kRotateLeft) {
- canvas->translate(0, physical_page_size.height());
- canvas->rotate(-90);
- } else if (page_orientation == mojom::PageOrientation::kRotateRight) {
- canvas->translate(physical_page_size.width(), 0);
- canvas->rotate(90);
- }
- SkRect sk_content_area = gfx::RectToSkRect(content_area);
- canvas->clipRect(sk_content_area);
- canvas->translate(sk_content_area.x(), sk_content_area.y());
- canvas->scale(scale_factor, scale_factor);
- }
- data_->size = gfx::SizeFToSkSize(gfx::SizeF(physical_page_size));
- data_->scale_factor = scale_factor;
- // We scale the recording canvas's size so that
- // canvas->getTotalMatrix() returns a value that ignores the scale
- // factor. We store the scale factor and re-apply it later.
- // http://crbug.com/469656
- }
- cc::PaintCanvas* MetafileSkia::GetVectorCanvasForNewPage(
- const gfx::Size& page_size,
- const gfx::Rect& content_area,
- float scale_factor,
- mojom::PageOrientation page_orientation) {
- StartPage(page_size, content_area, scale_factor, page_orientation);
- return data_->recorder.getRecordingCanvas();
- }
- bool MetafileSkia::FinishPage() {
- if (!data_->recorder.getRecordingCanvas())
- return false;
- sk_sp<cc::PaintRecord> pic = data_->recorder.finishRecordingAsPicture();
- if (data_->scale_factor != 1.0f) {
- cc::PaintCanvas* canvas = data_->recorder.beginRecording(
- data_->size.width(), data_->size.height());
- canvas->scale(data_->scale_factor, data_->scale_factor);
- canvas->drawPicture(pic);
- pic = data_->recorder.finishRecordingAsPicture();
- }
- data_->pages.emplace_back(data_->size, std::move(pic));
- return true;
- }
- bool MetafileSkia::FinishDocument() {
- // If we've already set the data in InitFromData, leave it be.
- if (data_->data_stream)
- return false;
- if (data_->recorder.getRecordingCanvas())
- FinishPage();
- SkDynamicMemoryWStream stream;
- sk_sp<SkDocument> doc;
- cc::PlaybackParams::CustomDataRasterCallback custom_callback;
- switch (data_->type) {
- case mojom::SkiaDocumentType::kPDF:
- doc = MakePdfDocument(printing::GetAgent(), accessibility_tree_, &stream);
- break;
- case mojom::SkiaDocumentType::kMSKP:
- SkSerialProcs procs = SerializationProcs(&data_->subframe_content_info,
- data_->typeface_content_info);
- doc = SkMakeMultiPictureDocument(&stream, &procs);
- // It is safe to use base::Unretained(this) because the callback
- // is only used by `canvas` in the following loop which has shorter
- // lifetime than `this`.
- custom_callback = base::BindRepeating(
- &MetafileSkia::CustomDataToSkPictureCallback, base::Unretained(this));
- break;
- }
- for (const Page& page : data_->pages) {
- cc::SkiaPaintCanvas canvas(
- doc->beginPage(page.size.width(), page.size.height()));
- canvas.drawPicture(page.content, custom_callback);
- doc->endPage();
- }
- doc->close();
- data_->data_stream = stream.detachAsStream();
- return true;
- }
- void MetafileSkia::FinishFrameContent() {
- // Sanity check to make sure we print the entire frame as a single page
- // content.
- DCHECK_EQ(data_->pages.size(), 1u);
- // Also make sure it is in skia multi-picture document format.
- DCHECK_EQ(data_->type, mojom::SkiaDocumentType::kMSKP);
- DCHECK(!data_->data_stream);
- cc::PlaybackParams::CustomDataRasterCallback custom_callback =
- base::BindRepeating(&MetafileSkia::CustomDataToSkPictureCallback,
- base::Unretained(this));
- sk_sp<SkPicture> pic = ToSkPicture(data_->pages[0].content,
- SkRect::MakeSize(data_->pages[0].size),
- nullptr, custom_callback);
- SkSerialProcs procs = SerializationProcs(&data_->subframe_content_info,
- data_->typeface_content_info);
- SkDynamicMemoryWStream stream;
- pic->serialize(&stream, &procs);
- data_->data_stream = stream.detachAsStream();
- }
- uint32_t MetafileSkia::GetDataSize() const {
- if (!data_->data_stream)
- return 0;
- return base::checked_cast<uint32_t>(data_->data_stream->getLength());
- }
- bool MetafileSkia::GetData(void* dst_buffer, uint32_t dst_buffer_size) const {
- if (!data_->data_stream)
- return false;
- return WriteAssetToBuffer(data_->data_stream.get(), dst_buffer,
- base::checked_cast<size_t>(dst_buffer_size));
- }
- bool MetafileSkia::ShouldCopySharedMemoryRegionData() const {
- // When `InitFromData()` copies the data, the caller doesn't have to.
- return !kInitFromDataCopyData;
- }
- mojom::MetafileDataType MetafileSkia::GetDataType() const {
- return mojom::MetafileDataType::kPDF;
- }
- gfx::Rect MetafileSkia::GetPageBounds(unsigned int page_number) const {
- if (page_number < data_->pages.size()) {
- SkSize size = data_->pages[page_number].size;
- return gfx::Rect(base::ClampRound(size.width()),
- base::ClampRound(size.height()));
- }
- return gfx::Rect();
- }
- unsigned int MetafileSkia::GetPageCount() const {
- return base::checked_cast<unsigned int>(data_->pages.size());
- }
- printing::NativeDrawingContext MetafileSkia::context() const {
- NOTREACHED();
- return nullptr;
- }
- #if BUILDFLAG(IS_WIN)
- bool MetafileSkia::Playback(printing::NativeDrawingContext hdc,
- const RECT* rect) const {
- NOTREACHED();
- return false;
- }
- bool MetafileSkia::SafePlayback(printing::NativeDrawingContext hdc) const {
- NOTREACHED();
- return false;
- }
- #elif BUILDFLAG(IS_MAC)
- /* TODO(caryclark): The set up of PluginInstance::PrintPDFOutput may result in
- rasterized output. Even if that flow uses PdfMetafileCg::RenderPage,
- the drawing of the PDF into the canvas may result in a rasterized output.
- PDFMetafileSkia::RenderPage should be not implemented as shown and instead
- should do something like the following CL in PluginInstance::PrintPDFOutput:
- http://codereview.chromium.org/7200040/diff/1/webkit/plugins/ppapi/ppapi_plugin_instance.cc
- */
- bool MetafileSkia::RenderPage(unsigned int page_number,
- CGContextRef context,
- const CGRect& rect,
- bool autorotate,
- bool fit_to_page) const {
- DCHECK_GT(GetDataSize(), 0U);
- if (data_->pdf_cg.GetDataSize() == 0) {
- if (GetDataSize() == 0)
- return false;
- size_t length = data_->data_stream->getLength();
- std::vector<uint8_t> buffer(length);
- std::ignore =
- WriteAssetToBuffer(data_->data_stream.get(), &buffer[0], length);
- data_->pdf_cg.InitFromData(buffer);
- }
- return data_->pdf_cg.RenderPage(page_number, context, rect, autorotate,
- fit_to_page);
- }
- #endif
- #if BUILDFLAG(IS_ANDROID)
- bool MetafileSkia::SaveToFileDescriptor(int fd) const {
- if (GetDataSize() == 0u)
- return false;
- std::unique_ptr<SkStreamAsset> asset(data_->data_stream->duplicate());
- static constexpr size_t kMaximumBufferSize = 1024 * 1024;
- std::vector<uint8_t> buffer(std::min(kMaximumBufferSize, asset->getLength()));
- do {
- size_t read_size = asset->read(&buffer[0], buffer.size());
- if (read_size == 0u)
- break;
- DCHECK_GE(buffer.size(), read_size);
- buffer.resize(read_size);
- if (!base::WriteFileDescriptor(fd, buffer))
- return false;
- } while (!asset->isAtEnd());
- return true;
- }
- #else
- bool MetafileSkia::SaveTo(base::File* file) const {
- if (GetDataSize() == 0U)
- return false;
- // Calling duplicate() keeps original asset state unchanged.
- std::unique_ptr<SkStreamAsset> asset(data_->data_stream->duplicate());
- static constexpr size_t kMaximumBufferSize = 1024 * 1024;
- std::vector<uint8_t> buffer(std::min(kMaximumBufferSize, asset->getLength()));
- do {
- size_t read_size = asset->read(&buffer[0], buffer.size());
- if (read_size == 0)
- break;
- DCHECK_GE(buffer.size(), read_size);
- if (!file->WriteAtCurrentPosAndCheck(
- base::make_span(&buffer[0], read_size))) {
- return false;
- }
- } while (!asset->isAtEnd());
- return true;
- }
- #endif // BUILDFLAG(IS_ANDROID)
- std::unique_ptr<MetafileSkia> MetafileSkia::GetMetafileForCurrentPage(
- mojom::SkiaDocumentType type) {
- // If we only ever need the metafile for the last page, should we
- // only keep a handle on one PaintRecord?
- auto metafile = std::make_unique<MetafileSkia>(type, data_->document_cookie);
- if (data_->pages.size() == 0)
- return metafile;
- if (data_->recorder.getRecordingCanvas()) // page outstanding
- return metafile;
- metafile->data_->pages.push_back(data_->pages.back());
- metafile->data_->subframe_content_info = data_->subframe_content_info;
- metafile->data_->subframe_pics = data_->subframe_pics;
- metafile->data_->typeface_content_info = data_->typeface_content_info;
- if (!metafile->FinishDocument()) // Generate PDF.
- metafile.reset();
- return metafile;
- }
- uint32_t MetafileSkia::CreateContentForRemoteFrame(
- const gfx::Rect& rect,
- const base::UnguessableToken& render_proxy_token) {
- // Create a place holder picture.
- sk_sp<SkPicture> pic = SkPicture::MakePlaceholder(
- SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height()));
- // Store the map between content id and the proxy id.
- uint32_t content_id = pic->uniqueID();
- DCHECK(!base::Contains(data_->subframe_content_info, content_id));
- data_->subframe_content_info[content_id] = render_proxy_token;
- // Store the picture content.
- data_->subframe_pics[content_id] = pic;
- return content_id;
- }
- int MetafileSkia::GetDocumentCookie() const {
- return data_->document_cookie;
- }
- const ContentToProxyTokenMap& MetafileSkia::GetSubframeContentInfo() const {
- return data_->subframe_content_info;
- }
- void MetafileSkia::AppendPage(const SkSize& page_size,
- sk_sp<cc::PaintRecord> record) {
- data_->pages.emplace_back(page_size, std::move(record));
- }
- void MetafileSkia::AppendSubframeInfo(uint32_t content_id,
- const base::UnguessableToken& proxy_token,
- sk_sp<SkPicture> pic_holder) {
- data_->subframe_content_info[content_id] = proxy_token;
- data_->subframe_pics[content_id] = pic_holder;
- }
- SkStreamAsset* MetafileSkia::GetPdfData() const {
- return data_->data_stream.get();
- }
- void MetafileSkia::CustomDataToSkPictureCallback(SkCanvas* canvas,
- uint32_t content_id) {
- // Check whether this is the one we need to handle.
- if (!base::Contains(data_->subframe_content_info, content_id))
- return;
- auto it = data_->subframe_pics.find(content_id);
- DCHECK(it != data_->subframe_pics.end());
- // Found the picture, draw it on canvas.
- sk_sp<SkPicture> pic = it->second;
- SkRect rect = pic->cullRect();
- SkMatrix matrix = SkMatrix::Translate(rect.x(), rect.y());
- canvas->drawPicture(it->second, &matrix, nullptr);
- }
- } // namespace printing
|