Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / printing / pdf_metafile_cg_mac.cc
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 // 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;
73   };
74 }
75
76 }  // namespace
77
78 namespace printing {
79
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));
88   }
89 }
90
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_);
96   }
97
98   if (thread_pdf_docs_owned_) {
99     CFRelease(thread_pdf_docs.Pointer()->Get());
100     thread_pdf_docs.Pointer()->Set(NULL);
101   }
102 }
103
104 bool PdfMetafileCg::Init() {
105   // Ensure that Init hasn't already been called.
106   DCHECK(!context_.get());
107   DCHECK(!pdf_data_.get());
108
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;
113   }
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;
120   }
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);
125   }
126
127   return true;
128 }
129
130 bool PdfMetafileCg::InitFromData(const void* src_buffer,
131                                  uint32 src_buffer_size) {
132   DCHECK(!context_.get());
133   DCHECK(!pdf_data_.get());
134
135   if (!src_buffer || src_buffer_size == 0) {
136     return false;
137   }
138
139   pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, src_buffer_size));
140   CFDataAppendBytes(pdf_data_, static_cast<const UInt8*>(src_buffer),
141                     src_buffer_size);
142
143   return true;
144 }
145
146 bool PdfMetafileCg::StartPage(const gfx::Size& page_size,
147                               const gfx::Rect& content_area,
148                               const float& scale_factor) {
149   DCHECK(context_.get());
150   DCHECK(!page_is_open_);
151
152   double height = page_size.height();
153   double width = page_size.width();
154
155   CGRect bounds = CGRectMake(0, 0, width, height);
156   CGContextBeginPage(context_, &bounds);
157   page_is_open_ = true;
158   CGContextSaveGState(context_);
159
160   // Move to the context origin.
161   CGContextTranslateCTM(context_, content_area.x(), -content_area.y());
162
163   // Flip the context.
164   CGContextTranslateCTM(context_, 0, height);
165   CGContextScaleCTM(context_, scale_factor, -scale_factor);
166
167   return context_.get() != NULL;
168 }
169
170 bool PdfMetafileCg::FinishPage() {
171   DCHECK(context_.get());
172   DCHECK(page_is_open_);
173
174   CGContextRestoreGState(context_);
175   CGContextEndPage(context_);
176   page_is_open_ = false;
177   return true;
178 }
179
180 bool PdfMetafileCg::FinishDocument() {
181   DCHECK(context_.get());
182   DCHECK(!page_is_open_);
183
184 #ifndef NDEBUG
185   // Check that the context will be torn down properly; if it's not, pdf_data_
186   // will be incomplete and generate invalid PDF files/documents.
187   if (context_.get()) {
188     CFIndex extra_retain_count = CFGetRetainCount(context_.get()) - 1;
189     if (extra_retain_count > 0) {
190       LOG(ERROR) << "Metafile context has " << extra_retain_count
191                  << " extra retain(s) on Close";
192     }
193   }
194 #endif
195   CGPDFContextClose(context_.get());
196   context_.reset(NULL);
197   return true;
198 }
199
200 bool PdfMetafileCg::RenderPage(unsigned int page_number,
201                                CGContextRef context,
202                                const CGRect rect,
203                                const MacRenderPageParams& params) const {
204   CGPDFDocumentRef pdf_doc = GetPDFDocument();
205   if (!pdf_doc) {
206     LOG(ERROR) << "Unable to create PDF document from data";
207     return false;
208   }
209   CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
210   CGRect source_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox);
211   int pdf_src_rotation = CGPDFPageGetRotationAngle(pdf_page);
212   float scaling_factor = 1.0;
213   const bool source_is_landscape =
214         (source_rect.size.width > source_rect.size.height);
215   const bool dest_is_landscape = (rect.size.width > rect.size.height);
216   const bool rotate =
217       params.autorotate ? (source_is_landscape != dest_is_landscape) : false;
218   const float source_width =
219       rotate ? source_rect.size.height : source_rect.size.width;
220   const float source_height =
221       rotate ? source_rect.size.width : source_rect.size.height;
222
223   // See if we need to scale the output.
224   const bool scaling_needed =
225       (params.shrink_to_fit && ((source_width > rect.size.width) ||
226                                 (source_height > rect.size.height))) ||
227       (params.stretch_to_fit && ((source_width < rect.size.width) &&
228                                  (source_height < rect.size.height)));
229   if (scaling_needed) {
230     float x_scaling_factor = rect.size.width / source_width;
231     float y_scaling_factor = rect.size.height / source_height;
232     scaling_factor = std::min(x_scaling_factor, y_scaling_factor);
233   }
234   // Some PDFs have a non-zero origin. Need to take that into account and align
235   // the PDF to the origin.
236   const float x_origin_offset = -1 * source_rect.origin.x;
237   const float y_origin_offset = -1 * source_rect.origin.y;
238
239   // If the PDF needs to be centered, calculate the offsets here.
240   float x_offset = params.center_horizontally ?
241       ((rect.size.width - (source_width * scaling_factor)) / 2) : 0;
242   if (rotate)
243     x_offset = -x_offset;
244
245   float y_offset = params.center_vertically ?
246       ((rect.size.height - (source_height * scaling_factor)) / 2) : 0;
247
248   CGContextSaveGState(context);
249
250   // The transform operations specified here gets applied in reverse order.
251   // i.e. the origin offset translation happens first.
252   // Origin is at bottom-left.
253   CGContextTranslateCTM(context, x_offset, y_offset);
254
255   int num_rotations = 0;
256   if (rotate) {
257     if (pdf_src_rotation == 0 || pdf_src_rotation == 270) {
258       num_rotations = 1;
259     } else {
260       num_rotations = 3;
261     }
262   } else {
263     if (pdf_src_rotation == 180 || pdf_src_rotation == 270) {
264       num_rotations = 2;
265     }
266   }
267   RotatePage(context, rect, num_rotations);
268
269   CGContextScaleCTM(context, scaling_factor, scaling_factor);
270   CGContextTranslateCTM(context, x_origin_offset, y_origin_offset);
271
272   CGContextDrawPDFPage(context, pdf_page);
273   CGContextRestoreGState(context);
274
275   return true;
276 }
277
278 unsigned int PdfMetafileCg::GetPageCount() const {
279   CGPDFDocumentRef pdf_doc = GetPDFDocument();
280   return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
281 }
282
283 gfx::Rect PdfMetafileCg::GetPageBounds(unsigned int page_number) const {
284   CGPDFDocumentRef pdf_doc = GetPDFDocument();
285   if (!pdf_doc) {
286     LOG(ERROR) << "Unable to create PDF document from data";
287     return gfx::Rect();
288   }
289   if (page_number > GetPageCount()) {
290     LOG(ERROR) << "Invalid page number: " << page_number;
291     return gfx::Rect();
292   }
293   CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
294   CGRect page_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFMediaBox);
295   return gfx::Rect(page_rect);
296 }
297
298 uint32 PdfMetafileCg::GetDataSize() const {
299   // PDF data is only valid/complete once the context is released.
300   DCHECK(!context_);
301
302   if (!pdf_data_)
303     return 0;
304   return static_cast<uint32>(CFDataGetLength(pdf_data_));
305 }
306
307 bool PdfMetafileCg::GetData(void* dst_buffer, uint32 dst_buffer_size) const {
308   // PDF data is only valid/complete once the context is released.
309   DCHECK(!context_);
310   DCHECK(pdf_data_);
311   DCHECK(dst_buffer);
312   DCHECK_GT(dst_buffer_size, 0U);
313
314   uint32 data_size = GetDataSize();
315   if (dst_buffer_size > data_size) {
316     return false;
317   }
318
319   CFDataGetBytes(pdf_data_, CFRangeMake(0, dst_buffer_size),
320                  static_cast<UInt8*>(dst_buffer));
321   return true;
322 }
323
324 CGContextRef PdfMetafileCg::context() const {
325   return context_.get();
326 }
327
328 CGPDFDocumentRef PdfMetafileCg::GetPDFDocument() const {
329   // Make sure that we have data, and that it's not being modified any more.
330   DCHECK(pdf_data_.get());
331   DCHECK(!context_.get());
332
333   if (!pdf_doc_.get()) {
334     ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider(
335         CGDataProviderCreateWithCFData(pdf_data_));
336     pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider));
337   }
338   return pdf_doc_.get();
339 }
340
341 }  // namespace printing