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.
4
5#include "printing/pdf_metafile_skia.h"
6
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 "skia/ext/vector_platform_device_skia.h"
14#include "third_party/skia/include/core/SkData.h"
15#include "third_party/skia/include/core/SkRefCnt.h"
16#include "third_party/skia/include/core/SkScalar.h"
17#include "third_party/skia/include/core/SkStream.h"
18#include "third_party/skia/include/core/SkTypeface.h"
19#include "third_party/skia/include/pdf/SkPDFDevice.h"
20#include "third_party/skia/include/pdf/SkPDFDocument.h"
21#include "ui/gfx/point.h"
22#include "ui/gfx/rect.h"
23#include "ui/gfx/size.h"
24
25#if defined(OS_MACOSX)
26#include "printing/pdf_metafile_cg_mac.h"
27#endif
28
29#if defined(OS_POSIX)
30#include "base/file_descriptor_posix.h"
31#endif
32
33namespace printing {
34
35struct PdfMetafileSkiaData {
36  skia::RefPtr<SkPDFDevice> current_page_;
37  SkPDFDocument pdf_doc_;
38  SkDynamicMemoryWStream pdf_stream_;
39#if defined(OS_MACOSX)
40  PdfMetafileCg pdf_cg_;
41#endif
42};
43
44PdfMetafileSkia::~PdfMetafileSkia() {}
45
46bool PdfMetafileSkia::Init() {
47  return true;
48}
49bool PdfMetafileSkia::InitFromData(const void* src_buffer,
50                                   uint32 src_buffer_size) {
51  return data_->pdf_stream_.write(src_buffer, src_buffer_size);
52}
53
54SkBaseDevice* PdfMetafileSkia::StartPageForVectorCanvas(
55    const gfx::Size& page_size, const gfx::Rect& content_area,
56    const float& scale_factor) {
57  DCHECK(!page_outstanding_);
58  page_outstanding_ = true;
59
60  // Adjust for the margins and apply the scale factor.
61  SkMatrix transform;
62  transform.setTranslate(SkIntToScalar(content_area.x()),
63                         SkIntToScalar(content_area.y()));
64  transform.preScale(SkFloatToScalar(scale_factor),
65                     SkFloatToScalar(scale_factor));
66
67  SkISize pdf_page_size = SkISize::Make(page_size.width(), page_size.height());
68  SkISize pdf_content_size =
69      SkISize::Make(content_area.width(), content_area.height());
70  skia::RefPtr<SkPDFDevice> pdf_device =
71      skia::AdoptRef(new skia::VectorPlatformDeviceSkia(
72          pdf_page_size, pdf_content_size, transform));
73  data_->current_page_ = pdf_device;
74  return pdf_device.get();
75}
76
77bool PdfMetafileSkia::StartPage(const gfx::Size& page_size,
78                                const gfx::Rect& content_area,
79                                const float& scale_factor) {
80  NOTREACHED();
81  return false;
82}
83
84bool PdfMetafileSkia::FinishPage() {
85  DCHECK(data_->current_page_.get());
86
87  data_->pdf_doc_.appendPage(data_->current_page_.get());
88  page_outstanding_ = false;
89  return true;
90}
91
92bool PdfMetafileSkia::FinishDocument() {
93  // Don't do anything if we've already set the data in InitFromData.
94  if (data_->pdf_stream_.getOffset())
95    return true;
96
97  if (page_outstanding_)
98    FinishPage();
99
100  data_->current_page_.clear();
101
102  int font_counts[SkAdvancedTypefaceMetrics::kOther_Font + 2];
103  data_->pdf_doc_.getCountOfFontTypes(font_counts);
104  for (int type = 0;
105       type <= SkAdvancedTypefaceMetrics::kOther_Font + 1;
106       type++) {
107    for (int count = 0; count < font_counts[type]; count++) {
108      UMA_HISTOGRAM_ENUMERATION(
109          "PrintPreview.FontType", type,
110          SkAdvancedTypefaceMetrics::kOther_Font + 2);
111    }
112  }
113
114  return data_->pdf_doc_.emitPDF(&data_->pdf_stream_);
115}
116
117uint32 PdfMetafileSkia::GetDataSize() const {
118  return base::checked_cast<uint32>(data_->pdf_stream_.getOffset());
119}
120
121bool PdfMetafileSkia::GetData(void* dst_buffer,
122                              uint32 dst_buffer_size) const {
123  if (dst_buffer_size < GetDataSize())
124    return false;
125
126  SkAutoDataUnref data(data_->pdf_stream_.copyToData());
127  memcpy(dst_buffer, data->bytes(), dst_buffer_size);
128  return true;
129}
130
131gfx::Rect PdfMetafileSkia::GetPageBounds(unsigned int page_number) const {
132  // TODO(vandebo) add a method to get the page size for a given page to
133  // SkPDFDocument.
134  NOTIMPLEMENTED();
135  return gfx::Rect();
136}
137
138unsigned int PdfMetafileSkia::GetPageCount() const {
139  // TODO(vandebo) add a method to get the number of pages to SkPDFDocument.
140  NOTIMPLEMENTED();
141  return 0;
142}
143
144gfx::NativeDrawingContext PdfMetafileSkia::context() const {
145  NOTREACHED();
146  return NULL;
147}
148
149#if defined(OS_WIN)
150bool PdfMetafileSkia::Playback(gfx::NativeDrawingContext hdc,
151                               const RECT* rect) const {
152  NOTREACHED();
153  return false;
154}
155
156bool PdfMetafileSkia::SafePlayback(gfx::NativeDrawingContext hdc) const {
157  NOTREACHED();
158  return false;
159}
160
161#elif defined(OS_MACOSX)
162/* TODO(caryclark): The set up of PluginInstance::PrintPDFOutput may result in
163   rasterized output.  Even if that flow uses PdfMetafileCg::RenderPage,
164   the drawing of the PDF into the canvas may result in a rasterized output.
165   PDFMetafileSkia::RenderPage should be not implemented as shown and instead
166   should do something like the following CL in PluginInstance::PrintPDFOutput:
167http://codereview.chromium.org/7200040/diff/1/webkit/plugins/ppapi/ppapi_plugin_instance.cc
168*/
169bool PdfMetafileSkia::RenderPage(unsigned int page_number,
170                                 CGContextRef context,
171                                 const CGRect rect,
172                                 const MacRenderPageParams& params) const {
173  DCHECK_GT(data_->pdf_stream_.getOffset(), 0U);
174  if (data_->pdf_cg_.GetDataSize() == 0) {
175    SkAutoDataUnref data(data_->pdf_stream_.copyToData());
176    data_->pdf_cg_.InitFromData(data->bytes(), data->size());
177  }
178  return data_->pdf_cg_.RenderPage(page_number, context, rect, params);
179}
180#endif
181
182#if defined(OS_CHROMEOS) || defined(OS_ANDROID)
183bool PdfMetafileSkia::SaveToFD(const base::FileDescriptor& fd) const {
184  DCHECK_GT(data_->pdf_stream_.getOffset(), 0U);
185
186  if (fd.fd < 0) {
187    DLOG(ERROR) << "Invalid file descriptor!";
188    return false;
189  }
190  base::File file(fd.fd);
191  SkAutoDataUnref data(data_->pdf_stream_.copyToData());
192  bool result =
193      file.WriteAtCurrentPos(reinterpret_cast<const char*>(data->data()),
194                             GetDataSize()) == static_cast<int>(GetDataSize());
195  DLOG_IF(ERROR, !result) << "Failed to save file with fd " << fd.fd;
196
197  if (!fd.auto_close)
198    file.TakePlatformFile();
199  return result;
200}
201#endif
202
203PdfMetafileSkia::PdfMetafileSkia()
204    : data_(new PdfMetafileSkiaData),
205      page_outstanding_(false) {
206}
207
208scoped_ptr<PdfMetafileSkia> PdfMetafileSkia::GetMetafileForCurrentPage() {
209  scoped_ptr<PdfMetafileSkia> metafile;
210  SkPDFDocument pdf_doc(SkPDFDocument::kDraftMode_Flags);
211  if (!pdf_doc.appendPage(data_->current_page_.get()))
212    return metafile.Pass();
213
214  SkDynamicMemoryWStream pdf_stream;
215  if (!pdf_doc.emitPDF(&pdf_stream))
216    return metafile.Pass();
217
218  SkAutoDataUnref data_copy(pdf_stream.copyToData());
219  if (data_copy->size() == 0)
220    return scoped_ptr<PdfMetafileSkia>();
221
222  metafile.reset(new PdfMetafileSkia);
223  if (!metafile->InitFromData(data_copy->bytes(),
224                              base::checked_cast<uint32>(data_copy->size()))) {
225    metafile.reset();
226  }
227  return metafile.Pass();
228}
229
230}  // namespace printing
231