Home | History | Annotate | Download | only in printing
      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 
     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 
     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 
     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 
     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 
    113 SkDevice* 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 
    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 
    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 
    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 
    174 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 
    243 unsigned int PdfMetafileCg::GetPageCount() const {
    244   CGPDFDocumentRef pdf_doc = GetPDFDocument();
    245   return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
    246 }
    247 
    248 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 
    263 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 
    272 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 
    289 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 
    302 CGContextRef PdfMetafileCg::context() const {
    303   return context_.get();
    304 }
    305 
    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());
    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