Add include.
[chromium-blink-merge.git] / printing / pdf_metafile_cg_mac.cc
blob1bc1693c320d5684ea1b32cf97d2dd8cc7fd48ff
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"
7 #include <algorithm>
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;
21 namespace {
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;
43 // Rotate a page by |num_rotations| * 90 degrees, counter-clockwise.
44 void RotatePage(CGContextRef context, const CGRect rect, int num_rotations) {
45 switch (num_rotations) {
46 case 0:
47 break;
48 case 1:
49 // After rotating by 90 degrees with the axis at the origin, the page
50 // content is now "off screen". Shift it right to move it back on screen.
51 CGContextTranslateCTM(context, rect.size.width, 0);
52 // Rotates counter-clockwise by 90 degrees.
53 CGContextRotateCTM(context, M_PI_2);
54 break;
55 case 2:
56 // After rotating by 180 degrees with the axis at the origin, the page
57 // content is now "off screen". Shift it right and up to move it back on
58 // screen.
59 CGContextTranslateCTM(context, rect.size.width, rect.size.height);
60 // Rotates counter-clockwise by 90 degrees.
61 CGContextRotateCTM(context, M_PI);
62 break;
63 case 3:
64 // After rotating by 270 degrees with the axis at the origin, the page
65 // content is now "off screen". Shift it right to move it back on screen.
66 CGContextTranslateCTM(context, 0, rect.size.height);
67 // Rotates counter-clockwise by 90 degrees.
68 CGContextRotateCTM(context, -M_PI_2);
69 break;
70 default:
71 NOTREACHED();
72 break;
76 } // namespace
78 namespace printing {
80 PdfMetafileCg::PdfMetafileCg()
81 : page_is_open_(false),
82 thread_pdf_docs_owned_(false) {
83 if (!thread_pdf_docs.Pointer()->Get() &&
84 base::mac::IsOSSnowLeopard()) {
85 thread_pdf_docs_owned_ = true;
86 thread_pdf_docs.Pointer()->Set(
87 CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks));
91 PdfMetafileCg::~PdfMetafileCg() {
92 DCHECK(thread_checker_.CalledOnValidThread());
93 if (pdf_doc_ && thread_pdf_docs.Pointer()->Get()) {
94 // Transfer ownership to the pool.
95 CFSetAddValue(thread_pdf_docs.Pointer()->Get(), pdf_doc_);
98 if (thread_pdf_docs_owned_) {
99 CFRelease(thread_pdf_docs.Pointer()->Get());
100 thread_pdf_docs.Pointer()->Set(NULL);
104 bool PdfMetafileCg::Init() {
105 // Ensure that Init hasn't already been called.
106 DCHECK(!context_.get());
107 DCHECK(!pdf_data_.get());
109 pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, 0));
110 if (!pdf_data_.get()) {
111 LOG(ERROR) << "Failed to create pdf data for metafile";
112 return false;
114 ScopedCFTypeRef<CGDataConsumerRef> pdf_consumer(
115 CGDataConsumerCreateWithCFData(pdf_data_));
116 if (!pdf_consumer.get()) {
117 LOG(ERROR) << "Failed to create data consumer for metafile";
118 pdf_data_.reset(NULL);
119 return false;
121 context_.reset(CGPDFContextCreate(pdf_consumer, NULL, NULL));
122 if (!context_.get()) {
123 LOG(ERROR) << "Failed to create pdf context for metafile";
124 pdf_data_.reset(NULL);
127 return true;
130 bool PdfMetafileCg::InitFromData(const void* src_buffer,
131 uint32 src_buffer_size) {
132 DCHECK(!context_.get());
133 DCHECK(!pdf_data_.get());
135 if (!src_buffer || src_buffer_size == 0) {
136 return false;
139 pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, src_buffer_size));
140 CFDataAppendBytes(pdf_data_, static_cast<const UInt8*>(src_buffer),
141 src_buffer_size);
143 return true;
146 SkBaseDevice* PdfMetafileCg::StartPageForVectorCanvas(
147 const gfx::Size& page_size, const gfx::Rect& content_area,
148 const float& scale_factor) {
149 NOTIMPLEMENTED();
150 return NULL;
153 bool PdfMetafileCg::StartPage(const gfx::Size& page_size,
154 const gfx::Rect& content_area,
155 const float& scale_factor) {
156 DCHECK(context_.get());
157 DCHECK(!page_is_open_);
159 double height = page_size.height();
160 double width = page_size.width();
162 CGRect bounds = CGRectMake(0, 0, width, height);
163 CGContextBeginPage(context_, &bounds);
164 page_is_open_ = true;
165 CGContextSaveGState(context_);
167 // Move to the context origin.
168 CGContextTranslateCTM(context_, content_area.x(), -content_area.y());
170 // Flip the context.
171 CGContextTranslateCTM(context_, 0, height);
172 CGContextScaleCTM(context_, scale_factor, -scale_factor);
174 return context_.get() != NULL;
177 bool PdfMetafileCg::FinishPage() {
178 DCHECK(context_.get());
179 DCHECK(page_is_open_);
181 CGContextRestoreGState(context_);
182 CGContextEndPage(context_);
183 page_is_open_ = false;
184 return true;
187 bool PdfMetafileCg::FinishDocument() {
188 DCHECK(context_.get());
189 DCHECK(!page_is_open_);
191 #ifndef NDEBUG
192 // Check that the context will be torn down properly; if it's not, pdf_data_
193 // will be incomplete and generate invalid PDF files/documents.
194 if (context_.get()) {
195 CFIndex extra_retain_count = CFGetRetainCount(context_.get()) - 1;
196 if (extra_retain_count > 0) {
197 LOG(ERROR) << "Metafile context has " << extra_retain_count
198 << " extra retain(s) on Close";
201 #endif
202 CGPDFContextClose(context_.get());
203 context_.reset(NULL);
204 return true;
207 bool PdfMetafileCg::RenderPage(unsigned int page_number,
208 CGContextRef context,
209 const CGRect rect,
210 const MacRenderPageParams& params) const {
211 CGPDFDocumentRef pdf_doc = GetPDFDocument();
212 if (!pdf_doc) {
213 LOG(ERROR) << "Unable to create PDF document from data";
214 return false;
216 CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
217 CGRect source_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox);
218 int pdf_src_rotation = CGPDFPageGetRotationAngle(pdf_page);
219 float scaling_factor = 1.0;
220 const bool source_is_landscape =
221 (source_rect.size.width > source_rect.size.height);
222 const bool dest_is_landscape = (rect.size.width > rect.size.height);
223 const bool rotate =
224 params.autorotate ? (source_is_landscape != dest_is_landscape) : false;
225 const float source_width =
226 rotate ? source_rect.size.height : source_rect.size.width;
227 const float source_height =
228 rotate ? source_rect.size.width : source_rect.size.height;
230 // See if we need to scale the output.
231 const bool scaling_needed =
232 (params.shrink_to_fit && ((source_width > rect.size.width) ||
233 (source_height > rect.size.height))) ||
234 (params.stretch_to_fit && ((source_width < rect.size.width) &&
235 (source_height < rect.size.height)));
236 if (scaling_needed) {
237 float x_scaling_factor = rect.size.width / source_width;
238 float y_scaling_factor = rect.size.height / source_height;
239 scaling_factor = std::min(x_scaling_factor, y_scaling_factor);
241 // Some PDFs have a non-zero origin. Need to take that into account and align
242 // the PDF to the origin.
243 const float x_origin_offset = -1 * source_rect.origin.x;
244 const float y_origin_offset = -1 * source_rect.origin.y;
246 // If the PDF needs to be centered, calculate the offsets here.
247 float x_offset = params.center_horizontally ?
248 ((rect.size.width - (source_width * scaling_factor)) / 2) : 0;
249 if (rotate)
250 x_offset = -x_offset;
252 float y_offset = params.center_vertically ?
253 ((rect.size.height - (source_height * scaling_factor)) / 2) : 0;
255 CGContextSaveGState(context);
257 // The transform operations specified here gets applied in reverse order.
258 // i.e. the origin offset translation happens first.
259 // Origin is at bottom-left.
260 CGContextTranslateCTM(context, x_offset, y_offset);
262 int num_rotations = 0;
263 if (rotate) {
264 if (pdf_src_rotation == 0 || pdf_src_rotation == 270) {
265 num_rotations = 1;
266 } else {
267 num_rotations = 3;
269 } else {
270 if (pdf_src_rotation == 180 || pdf_src_rotation == 270) {
271 num_rotations = 2;
274 RotatePage(context, rect, num_rotations);
276 CGContextScaleCTM(context, scaling_factor, scaling_factor);
277 CGContextTranslateCTM(context, x_origin_offset, y_origin_offset);
279 CGContextDrawPDFPage(context, pdf_page);
280 CGContextRestoreGState(context);
282 return true;
285 unsigned int PdfMetafileCg::GetPageCount() const {
286 CGPDFDocumentRef pdf_doc = GetPDFDocument();
287 return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
290 gfx::Rect PdfMetafileCg::GetPageBounds(unsigned int page_number) const {
291 CGPDFDocumentRef pdf_doc = GetPDFDocument();
292 if (!pdf_doc) {
293 LOG(ERROR) << "Unable to create PDF document from data";
294 return gfx::Rect();
296 if (page_number > GetPageCount()) {
297 LOG(ERROR) << "Invalid page number: " << page_number;
298 return gfx::Rect();
300 CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
301 CGRect page_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFMediaBox);
302 return gfx::Rect(page_rect);
305 uint32 PdfMetafileCg::GetDataSize() const {
306 // PDF data is only valid/complete once the context is released.
307 DCHECK(!context_);
309 if (!pdf_data_)
310 return 0;
311 return static_cast<uint32>(CFDataGetLength(pdf_data_));
314 bool PdfMetafileCg::GetData(void* dst_buffer, uint32 dst_buffer_size) const {
315 // PDF data is only valid/complete once the context is released.
316 DCHECK(!context_);
317 DCHECK(pdf_data_);
318 DCHECK(dst_buffer);
319 DCHECK_GT(dst_buffer_size, 0U);
321 uint32 data_size = GetDataSize();
322 if (dst_buffer_size > data_size) {
323 return false;
326 CFDataGetBytes(pdf_data_, CFRangeMake(0, dst_buffer_size),
327 static_cast<UInt8*>(dst_buffer));
328 return true;
331 CGContextRef PdfMetafileCg::context() const {
332 return context_.get();
335 CGPDFDocumentRef PdfMetafileCg::GetPDFDocument() const {
336 // Make sure that we have data, and that it's not being modified any more.
337 DCHECK(pdf_data_.get());
338 DCHECK(!context_.get());
340 if (!pdf_doc_.get()) {
341 ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider(
342 CGDataProviderCreateWithCFData(pdf_data_));
343 pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider));
345 return pdf_doc_.get();
348 } // namespace printing