pdf_metafile_cg_mac.cc revision c2e0dbddbe15c98d52c4786dac06cb8952a8ae6d
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_cg_mac.h"
6
7#include <algorithm>
8
9#include "base/files/file_path.h"
10#include "base/lazy_instance.h"
11#include "base/logging.h"
12#include "base/mac/mac_util.h"
13#include "base/mac/scoped_cftyperef.h"
14#include "base/strings/sys_string_conversions.h"
15#include "base/threading/thread_local.h"
16#include "ui/gfx/rect.h"
17#include "ui/gfx/size.h"
18
19using base::mac::ScopedCFTypeRef;
20
21namespace {
22
23// What is up with this ugly hack? <http://crbug.com/64641>, that's what.
24// The bug: Printing certain PDFs crashes. The cause: When printing, the
25// renderer process assembles pages one at a time, in PDF format, to send to the
26// browser process. When printing a PDF, the PDF plugin returns output in PDF
27// format. There is a bug in 10.5 and 10.6 (<rdar://9018916>,
28// <http://www.openradar.me/9018916>) where reference counting is broken when
29// drawing certain PDFs into PDF contexts. So at the high-level, a PdfMetafileCg
30// is used to hold the destination context, and then about five layers down on
31// the callstack, a PdfMetafileCg is used to hold the source PDF. If the source
32// PDF is drawn into the destination PDF context and then released, accessing
33// the destination PDF context will crash. So the outermost instantiation of
34// PdfMetafileCg creates a pool for deeper instantiations to dump their used
35// PDFs into rather than releasing them. When the top-level PDF is closed, then
36// it's safe to clear the pool. A thread local is used to allow this to work in
37// single-process mode. TODO(avi): This Apple bug appears fixed in 10.7; when
38// 10.7 is the minimum required version for Chromium, remove this hack.
39
40base::LazyInstance<base::ThreadLocalPointer<struct __CFSet> >::Leaky
41    thread_pdf_docs = LAZY_INSTANCE_INITIALIZER;
42
43}  // namespace
44
45namespace printing {
46
47PdfMetafileCg::PdfMetafileCg()
48    : page_is_open_(false),
49      thread_pdf_docs_owned_(false) {
50  if (!thread_pdf_docs.Pointer()->Get() &&
51      base::mac::IsOSSnowLeopard()) {
52    thread_pdf_docs_owned_ = true;
53    thread_pdf_docs.Pointer()->Set(
54        CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks));
55  }
56}
57
58PdfMetafileCg::~PdfMetafileCg() {
59  DCHECK(thread_checker_.CalledOnValidThread());
60  if (pdf_doc_ && thread_pdf_docs.Pointer()->Get()) {
61    // Transfer ownership to the pool.
62    CFSetAddValue(thread_pdf_docs.Pointer()->Get(), pdf_doc_);
63  }
64
65  if (thread_pdf_docs_owned_) {
66    CFRelease(thread_pdf_docs.Pointer()->Get());
67    thread_pdf_docs.Pointer()->Set(NULL);
68  }
69}
70
71bool PdfMetafileCg::Init() {
72  // Ensure that Init hasn't already been called.
73  DCHECK(!context_.get());
74  DCHECK(!pdf_data_.get());
75
76  pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, 0));
77  if (!pdf_data_.get()) {
78    LOG(ERROR) << "Failed to create pdf data for metafile";
79    return false;
80  }
81  ScopedCFTypeRef<CGDataConsumerRef> pdf_consumer(
82      CGDataConsumerCreateWithCFData(pdf_data_));
83  if (!pdf_consumer.get()) {
84    LOG(ERROR) << "Failed to create data consumer for metafile";
85    pdf_data_.reset(NULL);
86    return false;
87  }
88  context_.reset(CGPDFContextCreate(pdf_consumer, NULL, NULL));
89  if (!context_.get()) {
90    LOG(ERROR) << "Failed to create pdf context for metafile";
91    pdf_data_.reset(NULL);
92  }
93
94  return true;
95}
96
97bool PdfMetafileCg::InitFromData(const void* src_buffer,
98                                 uint32 src_buffer_size) {
99  DCHECK(!context_.get());
100  DCHECK(!pdf_data_.get());
101
102  if (!src_buffer || src_buffer_size == 0) {
103    return false;
104  }
105
106  pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, src_buffer_size));
107  CFDataAppendBytes(pdf_data_, static_cast<const UInt8*>(src_buffer),
108                    src_buffer_size);
109
110  return true;
111}
112
113SkDevice* PdfMetafileCg::StartPageForVectorCanvas(
114    const gfx::Size& page_size, const gfx::Rect& content_area,
115    const float& scale_factor) {
116  NOTIMPLEMENTED();
117  return NULL;
118}
119
120bool PdfMetafileCg::StartPage(const gfx::Size& page_size,
121                              const gfx::Rect& content_area,
122                              const float& scale_factor) {
123  DCHECK(context_.get());
124  DCHECK(!page_is_open_);
125
126  double height = page_size.height();
127  double width = page_size.width();
128
129  CGRect bounds = CGRectMake(0, 0, width, height);
130  CGContextBeginPage(context_, &bounds);
131  page_is_open_ = true;
132  CGContextSaveGState(context_);
133
134  // Move to the context origin.
135  CGContextTranslateCTM(context_, content_area.x(), -content_area.y());
136
137  // Flip the context.
138  CGContextTranslateCTM(context_, 0, height);
139  CGContextScaleCTM(context_, scale_factor, -scale_factor);
140
141  return context_.get() != NULL;
142}
143
144bool PdfMetafileCg::FinishPage() {
145  DCHECK(context_.get());
146  DCHECK(page_is_open_);
147
148  CGContextRestoreGState(context_);
149  CGContextEndPage(context_);
150  page_is_open_ = false;
151  return true;
152}
153
154bool PdfMetafileCg::FinishDocument() {
155  DCHECK(context_.get());
156  DCHECK(!page_is_open_);
157
158#ifndef NDEBUG
159  // Check that the context will be torn down properly; if it's not, pdf_data_
160  // will be incomplete and generate invalid PDF files/documents.
161  if (context_.get()) {
162    CFIndex extra_retain_count = CFGetRetainCount(context_.get()) - 1;
163    if (extra_retain_count > 0) {
164      LOG(ERROR) << "Metafile context has " << extra_retain_count
165                 << " extra retain(s) on Close";
166    }
167  }
168#endif
169  CGPDFContextClose(context_.get());
170  context_.reset(NULL);
171  return true;
172}
173
174bool PdfMetafileCg::RenderPage(unsigned int page_number,
175                               CGContextRef context,
176                               const CGRect rect,
177                               const MacRenderPageParams& params) const {
178  CGPDFDocumentRef pdf_doc = GetPDFDocument();
179  if (!pdf_doc) {
180    LOG(ERROR) << "Unable to create PDF document from data";
181    return false;
182  }
183  CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
184  CGRect source_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox);
185  float scaling_factor = 1.0;
186  const bool source_is_landscape =
187        (source_rect.size.width > source_rect.size.height);
188  const bool dest_is_landscape = (rect.size.width > rect.size.height);
189  const bool rotate =
190      params.autorotate ? (source_is_landscape != dest_is_landscape) : false;
191  const float source_width =
192      rotate ? source_rect.size.height : source_rect.size.width;
193  const float source_height =
194      rotate ? source_rect.size.width : source_rect.size.height;
195
196  // See if we need to scale the output.
197  const bool scaling_needed =
198      (params.shrink_to_fit && ((source_width > rect.size.width) ||
199                                (source_height > rect.size.height))) ||
200      (params.stretch_to_fit && ((source_width < rect.size.width) &&
201                                 (source_height < rect.size.height)));
202  if (scaling_needed) {
203    float x_scaling_factor = rect.size.width / source_width;
204    float y_scaling_factor = rect.size.height / source_height;
205    scaling_factor = std::min(x_scaling_factor, y_scaling_factor);
206  }
207  // Some PDFs have a non-zero origin. Need to take that into account and align
208  // the PDF to the origin.
209  const float x_origin_offset = -1 * source_rect.origin.x;
210  const float y_origin_offset = -1 * source_rect.origin.y;
211
212  // If the PDF needs to be centered, calculate the offsets here.
213  float x_offset = params.center_horizontally ?
214      ((rect.size.width - (source_width * scaling_factor)) / 2) : 0;
215  if (rotate)
216    x_offset = -x_offset;
217
218  float y_offset = params.center_vertically ?
219      ((rect.size.height - (source_height * scaling_factor)) / 2) : 0;
220
221  CGContextSaveGState(context);
222
223  // The transform operations specified here gets applied in reverse order.
224  // i.e. the origin offset translation happens first.
225  // Origin is at bottom-left.
226  CGContextTranslateCTM(context, x_offset, y_offset);
227  if (rotate) {
228    // After rotating by 90 degrees with the axis at the origin, the page
229    // content is now "off screen". Shift it right to move it back on screen.
230    CGContextTranslateCTM(context, rect.size.width, 0);
231    // Rotates counter-clockwise by 90 degrees.
232    CGContextRotateCTM(context, M_PI_2);
233  }
234  CGContextScaleCTM(context, scaling_factor, scaling_factor);
235  CGContextTranslateCTM(context, x_origin_offset, y_origin_offset);
236
237  CGContextDrawPDFPage(context, pdf_page);
238  CGContextRestoreGState(context);
239
240  return true;
241}
242
243unsigned int PdfMetafileCg::GetPageCount() const {
244  CGPDFDocumentRef pdf_doc = GetPDFDocument();
245  return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
246}
247
248gfx::Rect PdfMetafileCg::GetPageBounds(unsigned int page_number) const {
249  CGPDFDocumentRef pdf_doc = GetPDFDocument();
250  if (!pdf_doc) {
251    LOG(ERROR) << "Unable to create PDF document from data";
252    return gfx::Rect();
253  }
254  if (page_number > GetPageCount()) {
255    LOG(ERROR) << "Invalid page number: " << page_number;
256    return gfx::Rect();
257  }
258  CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
259  CGRect page_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFMediaBox);
260  return gfx::Rect(page_rect);
261}
262
263uint32 PdfMetafileCg::GetDataSize() const {
264  // PDF data is only valid/complete once the context is released.
265  DCHECK(!context_);
266
267  if (!pdf_data_)
268    return 0;
269  return static_cast<uint32>(CFDataGetLength(pdf_data_));
270}
271
272bool PdfMetafileCg::GetData(void* dst_buffer, uint32 dst_buffer_size) const {
273  // PDF data is only valid/complete once the context is released.
274  DCHECK(!context_);
275  DCHECK(pdf_data_);
276  DCHECK(dst_buffer);
277  DCHECK_GT(dst_buffer_size, 0U);
278
279  uint32 data_size = GetDataSize();
280  if (dst_buffer_size > data_size) {
281    return false;
282  }
283
284  CFDataGetBytes(pdf_data_, CFRangeMake(0, dst_buffer_size),
285                 static_cast<UInt8*>(dst_buffer));
286  return true;
287}
288
289bool PdfMetafileCg::SaveTo(const base::FilePath& file_path) const {
290  DCHECK(pdf_data_.get());
291  DCHECK(!context_.get());
292
293  std::string path_string = file_path.value();
294  ScopedCFTypeRef<CFURLRef> path_url(CFURLCreateFromFileSystemRepresentation(
295      kCFAllocatorDefault, reinterpret_cast<const UInt8*>(path_string.c_str()),
296      path_string.length(), false));
297  SInt32 error_code;
298  CFURLWriteDataAndPropertiesToResource(path_url, pdf_data_, NULL, &error_code);
299  return error_code == 0;
300}
301
302CGContextRef PdfMetafileCg::context() const {
303  return context_.get();
304}
305
306CGPDFDocumentRef PdfMetafileCg::GetPDFDocument() const {
307  // Make sure that we have data, and that it's not being modified any more.
308  DCHECK(pdf_data_.get());
309  DCHECK(!context_.get());
310
311  if (!pdf_doc_.get()) {
312    ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider(
313        CGDataProviderCreateWithCFData(pdf_data_));
314    pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider));
315  }
316  return pdf_doc_.get();
317}
318
319}  // namespace printing
320