1 // Copyright (c) 2012 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.
5 #include "printing/pdf_metafile_skia.h"
7 #include "base/containers/hash_tables.h"
8 #include "base/files/file_util.h"
9 #include "base/metrics/histogram.h"
10 #include "base/numerics/safe_conversions.h"
11 #include "base/posix/eintr_wrapper.h"
12 #include "skia/ext/refptr.h"
13 #include "third_party/skia/include/core/SkData.h"
14 #include "third_party/skia/include/core/SkDocument.h"
15 #include "third_party/skia/include/core/SkPictureRecorder.h"
16 #include "third_party/skia/include/core/SkRect.h"
17 #include "third_party/skia/include/core/SkRefCnt.h"
18 #include "third_party/skia/include/core/SkScalar.h"
19 #include "third_party/skia/include/core/SkSize.h"
20 #include "third_party/skia/include/core/SkStream.h"
21 #include "ui/gfx/geometry/point.h"
22 #include "ui/gfx/geometry/rect.h"
23 #include "ui/gfx/geometry/size.h"
24 #include "ui/gfx/geometry/size_conversions.h"
25 #include "ui/gfx/skia_util.h"
27 #if defined(OS_MACOSX)
28 #include "printing/pdf_metafile_cg_mac.h"
32 #include "base/file_descriptor_posix.h"
36 // This struct represents all the data we need to draw and redraw this
37 // page into a SkDocument.
39 Page(const SkSize
& page_size
, const SkRect
& content_area
, float scale
)
40 : page_size_(page_size
),
41 content_area_(content_area
),
47 skia::RefPtr
<SkPicture
> content_
;
51 static bool WriteAssetToBuffer(const SkStreamAsset
* asset
,
54 // Calling duplicate() keeps original asset state unchanged.
55 scoped_ptr
<SkStreamAsset
> assetCopy(asset
->duplicate());
56 size_t length
= assetCopy
->getLength();
59 return (length
== assetCopy
->read(buffer
, length
));
64 struct PdfMetafileSkiaData
{
65 SkPictureRecorder recorder_
; // Current recording
67 std::vector
<Page
> pages_
;
68 scoped_ptr
<SkStreamAsset
> pdf_data_
;
70 #if defined(OS_MACOSX)
71 PdfMetafileCg pdf_cg_
;
75 PdfMetafileSkia::~PdfMetafileSkia() {}
77 bool PdfMetafileSkia::Init() {
81 // TODO(halcanary): Create a Metafile class that only stores data.
82 // Metafile::InitFromData is orthogonal to what the rest of
83 // PdfMetafileSkia does.
84 bool PdfMetafileSkia::InitFromData(const void* src_buffer
,
85 uint32 src_buffer_size
) {
86 data_
->pdf_data_
.reset(new SkMemoryStream(src_buffer
, src_buffer_size
, true));
90 bool PdfMetafileSkia::StartPage(const gfx::Size
& page_size
,
91 const gfx::Rect
& content_area
,
92 const float& scale_factor
) {
93 if (data_
->recorder_
.getRecordingCanvas())
95 DCHECK(!data_
->recorder_
.getRecordingCanvas());
96 SkSize sk_page_size
= gfx::SizeFToSkSize(gfx::SizeF(page_size
));
97 data_
->pages_
.push_back(
98 Page(sk_page_size
, gfx::RectToSkRect(content_area
), scale_factor
));
99 DCHECK_GT(scale_factor
, 0.0f
);
100 // We scale the recording canvas's size so that
101 // canvas->getTotalMatrix() returns a value that ignores the scale
102 // factor. We store the scale factor and re-apply it to the PDF
103 // Canvas later. http://crbug.com/469656
104 // Recording canvas is owned by the data_->recorder_. No ref() necessary.
105 return !!data_
->recorder_
.beginRecording(sk_page_size
.width() / scale_factor
,
106 sk_page_size
.height() / scale_factor
,
110 skia::PlatformCanvas
* PdfMetafileSkia::GetVectorCanvasForNewPage(
111 const gfx::Size
& page_size
,
112 const gfx::Rect
& content_area
,
113 const float& scale_factor
) {
114 if (!StartPage(page_size
, content_area
, scale_factor
))
116 return data_
->recorder_
.getRecordingCanvas();
119 bool PdfMetafileSkia::FinishPage() {
120 if (!data_
->recorder_
.getRecordingCanvas())
122 DCHECK(!(data_
->pages_
.back().content_
));
123 data_
->pages_
.back().content_
=
124 skia::AdoptRef(data_
->recorder_
.endRecordingAsPicture());
128 bool PdfMetafileSkia::FinishDocument() {
129 // If we've already set the data in InitFromData, leave it be.
130 if (data_
->pdf_data_
)
133 if (data_
->recorder_
.getRecordingCanvas())
136 SkDynamicMemoryWStream pdf_stream
;
137 skia::RefPtr
<SkDocument
> pdf_doc
=
138 skia::AdoptRef(SkDocument::CreatePDF(&pdf_stream
));
139 for (const auto& page
: data_
->pages_
) {
140 SkCanvas
* canvas
= pdf_doc
->beginPage(
141 page
.page_size_
.width(), page
.page_size_
.height(), &page
.content_area_
);
142 // No need to save/restore, since this canvas is not reused after endPage()
143 canvas
->scale(page
.scale_factor_
, page
.scale_factor_
);
144 canvas
->drawPicture(page
.content_
.get());
147 if (!pdf_doc
->close())
150 data_
->pdf_data_
.reset(pdf_stream
.detachAsStream());
154 uint32
PdfMetafileSkia::GetDataSize() const {
155 if (!data_
->pdf_data_
)
157 return base::checked_cast
<uint32
>(data_
->pdf_data_
->getLength());
160 bool PdfMetafileSkia::GetData(void* dst_buffer
,
161 uint32 dst_buffer_size
) const {
162 if (!data_
->pdf_data_
)
164 return WriteAssetToBuffer(data_
->pdf_data_
.get(), dst_buffer
,
165 base::checked_cast
<size_t>(dst_buffer_size
));
168 gfx::Rect
PdfMetafileSkia::GetPageBounds(unsigned int page_number
) const {
169 if (page_number
< data_
->pages_
.size()) {
170 return gfx::Rect(gfx::ToFlooredSize(
171 gfx::SkSizeToSizeF(data_
->pages_
[page_number
].page_size_
)));
176 unsigned int PdfMetafileSkia::GetPageCount() const {
177 return base::checked_cast
<unsigned int>(data_
->pages_
.size());
180 gfx::NativeDrawingContext
PdfMetafileSkia::context() const {
186 bool PdfMetafileSkia::Playback(gfx::NativeDrawingContext hdc
,
187 const RECT
* rect
) const {
192 bool PdfMetafileSkia::SafePlayback(gfx::NativeDrawingContext hdc
) const {
197 #elif defined(OS_MACOSX)
198 /* TODO(caryclark): The set up of PluginInstance::PrintPDFOutput may result in
199 rasterized output. Even if that flow uses PdfMetafileCg::RenderPage,
200 the drawing of the PDF into the canvas may result in a rasterized output.
201 PDFMetafileSkia::RenderPage should be not implemented as shown and instead
202 should do something like the following CL in PluginInstance::PrintPDFOutput:
203 http://codereview.chromium.org/7200040/diff/1/webkit/plugins/ppapi/ppapi_plugin_instance.cc
205 bool PdfMetafileSkia::RenderPage(unsigned int page_number
,
206 CGContextRef context
,
208 const MacRenderPageParams
& params
) const {
209 DCHECK_GT(GetDataSize(), 0U);
210 if (data_
->pdf_cg_
.GetDataSize() == 0) {
211 if (GetDataSize() == 0)
213 size_t length
= data_
->pdf_data_
->getLength();
214 std::vector
<uint8_t> buffer(length
);
215 (void)WriteAssetToBuffer(data_
->pdf_data_
.get(), &buffer
[0], length
);
216 data_
->pdf_cg_
.InitFromData(&buffer
[0], base::checked_cast
<uint32
>(length
));
218 return data_
->pdf_cg_
.RenderPage(page_number
, context
, rect
, params
);
222 bool PdfMetafileSkia::SaveTo(base::File
* file
) const {
223 if (GetDataSize() == 0U)
226 // Calling duplicate() keeps original asset state unchanged.
227 scoped_ptr
<SkStreamAsset
> asset(data_
->pdf_data_
->duplicate());
229 const size_t maximum_buffer_size
= 1024 * 1024;
230 std::vector
<char> buffer(std::min(maximum_buffer_size
, asset
->getLength()));
232 size_t read_size
= asset
->read(&buffer
[0], buffer
.size());
235 DCHECK_GE(buffer
.size(), read_size
);
236 if (!file
->WriteAtCurrentPos(&buffer
[0],
237 base::checked_cast
<int>(read_size
))) {
240 } while (!asset
->isAtEnd());
245 #if defined(OS_CHROMEOS) || defined(OS_ANDROID)
246 bool PdfMetafileSkia::SaveToFD(const base::FileDescriptor
& fd
) const {
247 DCHECK_GT(GetDataSize(), 0U);
250 DLOG(ERROR
) << "Invalid file descriptor!";
253 base::File
file(fd
.fd
);
254 bool result
= SaveTo(&file
);
255 DLOG_IF(ERROR
, !result
) << "Failed to save file with fd " << fd
.fd
;
258 file
.TakePlatformFile();
263 PdfMetafileSkia::PdfMetafileSkia() : data_(new PdfMetafileSkiaData
) {
266 scoped_ptr
<PdfMetafileSkia
> PdfMetafileSkia::GetMetafileForCurrentPage() {
267 // If we only ever need the metafile for the last page, should we
268 // only keep a handle on one SkPicture?
269 scoped_ptr
<PdfMetafileSkia
> metafile(new PdfMetafileSkia
);
271 if (data_
->pages_
.size() == 0)
272 return metafile
.Pass();
274 if (data_
->recorder_
.getRecordingCanvas()) // page outstanding
275 return metafile
.Pass();
277 const Page
& page
= data_
->pages_
.back();
279 metafile
->data_
->pages_
.push_back(page
);
281 if (!metafile
->FinishDocument()) // Generate PDF.
284 return metafile
.Pass();
287 } // namespace printing