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/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
131bool PdfMetafileSkia::SaveTo(const base::FilePath& file_path) const {
132  DCHECK_GT(data_->pdf_stream_.getOffset(), 0U);
133  SkAutoDataUnref data(data_->pdf_stream_.copyToData());
134  if (base::WriteFile(file_path,
135                      reinterpret_cast<const char*>(data->data()),
136                      GetDataSize()) != static_cast<int>(GetDataSize())) {
137    DLOG(ERROR) << "Failed to save file " << file_path.value().c_str();
138    return false;
139  }
140  return true;
141}
142
143gfx::Rect PdfMetafileSkia::GetPageBounds(unsigned int page_number) const {
144  // TODO(vandebo) add a method to get the page size for a given page to
145  // SkPDFDocument.
146  NOTIMPLEMENTED();
147  return gfx::Rect();
148}
149
150unsigned int PdfMetafileSkia::GetPageCount() const {
151  // TODO(vandebo) add a method to get the number of pages to SkPDFDocument.
152  NOTIMPLEMENTED();
153  return 0;
154}
155
156gfx::NativeDrawingContext PdfMetafileSkia::context() const {
157  NOTREACHED();
158  return NULL;
159}
160
161#if defined(OS_WIN)
162bool PdfMetafileSkia::Playback(gfx::NativeDrawingContext hdc,
163                               const RECT* rect) const {
164  NOTREACHED();
165  return false;
166}
167
168bool PdfMetafileSkia::SafePlayback(gfx::NativeDrawingContext hdc) const {
169  NOTREACHED();
170  return false;
171}
172
173HENHMETAFILE PdfMetafileSkia::emf() const {
174  NOTREACHED();
175  return NULL;
176}
177#elif defined(OS_MACOSX)
178/* TODO(caryclark): The set up of PluginInstance::PrintPDFOutput may result in
179   rasterized output.  Even if that flow uses PdfMetafileCg::RenderPage,
180   the drawing of the PDF into the canvas may result in a rasterized output.
181   PDFMetafileSkia::RenderPage should be not implemented as shown and instead
182   should do something like the following CL in PluginInstance::PrintPDFOutput:
183http://codereview.chromium.org/7200040/diff/1/webkit/plugins/ppapi/ppapi_plugin_instance.cc
184*/
185bool PdfMetafileSkia::RenderPage(unsigned int page_number,
186                                 CGContextRef context,
187                                 const CGRect rect,
188                                 const MacRenderPageParams& params) const {
189  DCHECK_GT(data_->pdf_stream_.getOffset(), 0U);
190  if (data_->pdf_cg_.GetDataSize() == 0) {
191    SkAutoDataUnref data(data_->pdf_stream_.copyToData());
192    data_->pdf_cg_.InitFromData(data->bytes(), data->size());
193  }
194  return data_->pdf_cg_.RenderPage(page_number, context, rect, params);
195}
196#endif
197
198#if defined(OS_CHROMEOS) || defined(OS_ANDROID)
199bool PdfMetafileSkia::SaveToFD(const base::FileDescriptor& fd) const {
200  DCHECK_GT(data_->pdf_stream_.getOffset(), 0U);
201
202  if (fd.fd < 0) {
203    DLOG(ERROR) << "Invalid file descriptor!";
204    return false;
205  }
206
207  bool result = true;
208  SkAutoDataUnref data(data_->pdf_stream_.copyToData());
209  if (base::WriteFileDescriptor(fd.fd,
210                                reinterpret_cast<const char*>(data->data()),
211                                GetDataSize()) !=
212      static_cast<int>(GetDataSize())) {
213    DLOG(ERROR) << "Failed to save file with fd " << fd.fd;
214    result = false;
215  }
216
217  if (fd.auto_close) {
218    if (IGNORE_EINTR(close(fd.fd)) < 0) {
219      DPLOG(WARNING) << "close";
220      result = false;
221    }
222  }
223  return result;
224}
225#endif
226
227PdfMetafileSkia::PdfMetafileSkia()
228    : data_(new PdfMetafileSkiaData),
229      page_outstanding_(false) {
230}
231
232PdfMetafileSkia* PdfMetafileSkia::GetMetafileForCurrentPage() {
233  SkPDFDocument pdf_doc(SkPDFDocument::kDraftMode_Flags);
234  SkDynamicMemoryWStream pdf_stream;
235  if (!pdf_doc.appendPage(data_->current_page_.get()))
236    return NULL;
237
238  if (!pdf_doc.emitPDF(&pdf_stream))
239    return NULL;
240
241  SkAutoDataUnref data(pdf_stream.copyToData());
242  if (data->size() == 0)
243    return NULL;
244
245  PdfMetafileSkia* metafile = new PdfMetafileSkia;
246  metafile->InitFromData(data->bytes(),
247                         base::checked_cast<uint32>(data->size()));
248  return metafile;
249}
250
251}  // namespace printing
252