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_cg_mac.h"
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"
19 using base::ScopedCFTypeRef
;
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.
40 base::LazyInstance
<base::ThreadLocalPointer
<struct __CFSet
> >::Leaky
41 thread_pdf_docs
= LAZY_INSTANCE_INITIALIZER
;
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
));
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_
);
65 if (thread_pdf_docs_owned_
) {
66 CFRelease(thread_pdf_docs
.Pointer()->Get());
67 thread_pdf_docs
.Pointer()->Set(NULL
);
71 bool PdfMetafileCg::Init() {
72 // Ensure that Init hasn't already been called.
73 DCHECK(!context_
.get());
74 DCHECK(!pdf_data_
.get());
76 pdf_data_
.reset(CFDataCreateMutable(kCFAllocatorDefault
, 0));
77 if (!pdf_data_
.get()) {
78 LOG(ERROR
) << "Failed to create pdf data for metafile";
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
);
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
);
97 bool PdfMetafileCg::InitFromData(const void* src_buffer
,
98 uint32 src_buffer_size
) {
99 DCHECK(!context_
.get());
100 DCHECK(!pdf_data_
.get());
102 if (!src_buffer
|| src_buffer_size
== 0) {
106 pdf_data_
.reset(CFDataCreateMutable(kCFAllocatorDefault
, src_buffer_size
));
107 CFDataAppendBytes(pdf_data_
, static_cast<const UInt8
*>(src_buffer
),
113 SkBaseDevice
* PdfMetafileCg::StartPageForVectorCanvas(
114 const gfx::Size
& page_size
, const gfx::Rect
& content_area
,
115 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_
);
126 double height
= page_size
.height();
127 double width
= page_size
.width();
129 CGRect bounds
= CGRectMake(0, 0, width
, height
);
130 CGContextBeginPage(context_
, &bounds
);
131 page_is_open_
= true;
132 CGContextSaveGState(context_
);
134 // Move to the context origin.
135 CGContextTranslateCTM(context_
, content_area
.x(), -content_area
.y());
138 CGContextTranslateCTM(context_
, 0, height
);
139 CGContextScaleCTM(context_
, scale_factor
, -scale_factor
);
141 return context_
.get() != NULL
;
144 bool PdfMetafileCg::FinishPage() {
145 DCHECK(context_
.get());
146 DCHECK(page_is_open_
);
148 CGContextRestoreGState(context_
);
149 CGContextEndPage(context_
);
150 page_is_open_
= false;
154 bool PdfMetafileCg::FinishDocument() {
155 DCHECK(context_
.get());
156 DCHECK(!page_is_open_
);
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";
169 CGPDFContextClose(context_
.get());
170 context_
.reset(NULL
);
174 bool PdfMetafileCg::RenderPage(unsigned int page_number
,
175 CGContextRef context
,
177 const MacRenderPageParams
& params
) const {
178 CGPDFDocumentRef pdf_doc
= GetPDFDocument();
180 LOG(ERROR
) << "Unable to create PDF document from data";
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
);
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
;
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
);
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
;
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;
216 x_offset
= -x_offset
;
218 float y_offset
= params
.center_vertically
?
219 ((rect
.size
.height
- (source_height
* scaling_factor
)) / 2) : 0;
221 CGContextSaveGState(context
);
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
);
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
);
234 CGContextScaleCTM(context
, scaling_factor
, scaling_factor
);
235 CGContextTranslateCTM(context
, x_origin_offset
, y_origin_offset
);
237 CGContextDrawPDFPage(context
, pdf_page
);
238 CGContextRestoreGState(context
);
243 unsigned int PdfMetafileCg::GetPageCount() const {
244 CGPDFDocumentRef pdf_doc
= GetPDFDocument();
245 return pdf_doc
? CGPDFDocumentGetNumberOfPages(pdf_doc
) : 0;
248 gfx::Rect
PdfMetafileCg::GetPageBounds(unsigned int page_number
) const {
249 CGPDFDocumentRef pdf_doc
= GetPDFDocument();
251 LOG(ERROR
) << "Unable to create PDF document from data";
254 if (page_number
> GetPageCount()) {
255 LOG(ERROR
) << "Invalid page number: " << page_number
;
258 CGPDFPageRef pdf_page
= CGPDFDocumentGetPage(pdf_doc
, page_number
);
259 CGRect page_rect
= CGPDFPageGetBoxRect(pdf_page
, kCGPDFMediaBox
);
260 return gfx::Rect(page_rect
);
263 uint32
PdfMetafileCg::GetDataSize() const {
264 // PDF data is only valid/complete once the context is released.
269 return static_cast<uint32
>(CFDataGetLength(pdf_data_
));
272 bool PdfMetafileCg::GetData(void* dst_buffer
, uint32 dst_buffer_size
) const {
273 // PDF data is only valid/complete once the context is released.
277 DCHECK_GT(dst_buffer_size
, 0U);
279 uint32 data_size
= GetDataSize();
280 if (dst_buffer_size
> data_size
) {
284 CFDataGetBytes(pdf_data_
, CFRangeMake(0, dst_buffer_size
),
285 static_cast<UInt8
*>(dst_buffer
));
289 bool PdfMetafileCg::SaveTo(const base::FilePath
& file_path
) const {
290 DCHECK(pdf_data_
.get());
291 DCHECK(!context_
.get());
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));
298 CFURLWriteDataAndPropertiesToResource(path_url
, pdf_data_
, NULL
, &error_code
);
299 return error_code
== 0;
302 CGContextRef
PdfMetafileCg::context() const {
303 return context_
.get();
306 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());
311 if (!pdf_doc_
.get()) {
312 ScopedCFTypeRef
<CGDataProviderRef
> pdf_data_provider(
313 CGDataProviderCreateWithCFData(pdf_data_
));
314 pdf_doc_
.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider
));
316 return pdf_doc_
.get();
319 } // namespace printing