• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
19 using base::ScopedCFTypeRef;
20 
21 namespace {
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 
40 base::LazyInstance<base::ThreadLocalPointer<struct __CFSet> >::Leaky
41     thread_pdf_docs = LAZY_INSTANCE_INITIALIZER;
42 
43 }  // namespace
44 
45 namespace printing {
46 
PdfMetafileCg()47 PdfMetafileCg::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 
~PdfMetafileCg()58 PdfMetafileCg::~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 
Init()71 bool 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 
InitFromData(const void * src_buffer,uint32 src_buffer_size)97 bool 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 
StartPageForVectorCanvas(const gfx::Size & page_size,const gfx::Rect & content_area,const float & scale_factor)113 SkBaseDevice* 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 
StartPage(const gfx::Size & page_size,const gfx::Rect & content_area,const float & scale_factor)120 bool 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 
FinishPage()144 bool 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 
FinishDocument()154 bool 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 
RenderPage(unsigned int page_number,CGContextRef context,const CGRect rect,const MacRenderPageParams & params) const174 bool 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 
GetPageCount() const243 unsigned int PdfMetafileCg::GetPageCount() const {
244   CGPDFDocumentRef pdf_doc = GetPDFDocument();
245   return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
246 }
247 
GetPageBounds(unsigned int page_number) const248 gfx::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 
GetDataSize() const263 uint32 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 
GetData(void * dst_buffer,uint32 dst_buffer_size) const272 bool 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 
SaveTo(const base::FilePath & file_path) const289 bool 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 
context() const302 CGContextRef PdfMetafileCg::context() const {
303   return context_.get();
304 }
305 
GetPDFDocument() const306 CGPDFDocumentRef 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