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 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 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