blob: e11ad087e3e13b02a7e5fe90eea1655060482eea [file] [log] [blame]
[email protected]9fc44162012-01-23 22:56:411// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]14b210792009-10-12 18:45:312// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]9b617532011-04-04 23:15:065#include "printing/pdf_metafile_cg_mac.h"
[email protected]14b210792009-10-12 18:45:316
[email protected]b5cf844c2012-06-18 21:49:207#include <algorithm>
8
[email protected]57999812013-02-24 05:40:529#include "base/files/file_path.h"
[email protected]264300242011-11-07 06:03:3010#include "base/lazy_instance.h"
[email protected]14b210792009-10-12 18:45:3111#include "base/logging.h"
[email protected]ba64e2b2011-06-14 18:18:3812#include "base/mac/mac_util.h"
[email protected]df0ca6c82010-10-17 04:09:0613#include "base/mac/scoped_cftyperef.h"
[email protected]13ac53532013-03-30 00:27:0014#include "base/strings/sys_string_conversions.h"
[email protected]df6df682011-05-02 19:50:5015#include "base/threading/thread_local.h"
[email protected]08397d52011-02-05 01:53:3816#include "ui/gfx/rect.h"
[email protected]8f17cd3e2011-03-16 01:39:4217#include "ui/gfx/size.h"
[email protected]14b210792009-10-12 18:45:3118
[email protected]df0ca6c82010-10-17 04:09:0619using base::mac::ScopedCFTypeRef;
20
[email protected]df6df682011-05-02 19:50:5021namespace {
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
[email protected]9fc44162012-01-23 22:56:4140base::LazyInstance<base::ThreadLocalPointer<struct __CFSet> >::Leaky
41 thread_pdf_docs = LAZY_INSTANCE_INITIALIZER;
[email protected]df6df682011-05-02 19:50:5042
[email protected]df6df682011-05-02 19:50:5043} // namespace
44
[email protected]14b210792009-10-12 18:45:3145namespace printing {
46
[email protected]9b617532011-04-04 23:15:0647PdfMetafileCg::PdfMetafileCg()
[email protected]df6df682011-05-02 19:50:5048 : page_is_open_(false),
49 thread_pdf_docs_owned_(false) {
[email protected]264300242011-11-07 06:03:3050 if (!thread_pdf_docs.Pointer()->Get() &&
[email protected]59e82f02012-08-10 22:47:1451 base::mac::IsOSSnowLeopard()) {
[email protected]df6df682011-05-02 19:50:5052 thread_pdf_docs_owned_ = true;
[email protected]264300242011-11-07 06:03:3053 thread_pdf_docs.Pointer()->Set(
54 CFSetCreateMutable(kCFAllocatorDefault, 0, &kCFTypeSetCallBacks));
[email protected]df6df682011-05-02 19:50:5055 }
[email protected]14b210792009-10-12 18:45:3156}
57
[email protected]df6df682011-05-02 19:50:5058PdfMetafileCg::~PdfMetafileCg() {
[email protected]69f5b1e62011-09-01 06:34:0459 DCHECK(thread_checker_.CalledOnValidThread());
[email protected]264300242011-11-07 06:03:3060 if (pdf_doc_ && thread_pdf_docs.Pointer()->Get()) {
[email protected]df6df682011-05-02 19:50:5061 // Transfer ownership to the pool.
[email protected]264300242011-11-07 06:03:3062 CFSetAddValue(thread_pdf_docs.Pointer()->Get(), pdf_doc_);
[email protected]df6df682011-05-02 19:50:5063 }
64
65 if (thread_pdf_docs_owned_) {
[email protected]264300242011-11-07 06:03:3066 CFRelease(thread_pdf_docs.Pointer()->Get());
67 thread_pdf_docs.Pointer()->Set(NULL);
[email protected]df6df682011-05-02 19:50:5068 }
69}
[email protected]1d20cdf2011-02-16 18:09:2870
[email protected]9b617532011-04-04 23:15:0671bool PdfMetafileCg::Init() {
[email protected]14b210792009-10-12 18:45:3172 // 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";
[email protected]8f17cd3e2011-03-16 01:39:4279 return false;
[email protected]14b210792009-10-12 18:45:3180 }
[email protected]df0ca6c82010-10-17 04:09:0681 ScopedCFTypeRef<CGDataConsumerRef> pdf_consumer(
[email protected]14b210792009-10-12 18:45:3182 CGDataConsumerCreateWithCFData(pdf_data_));
83 if (!pdf_consumer.get()) {
84 LOG(ERROR) << "Failed to create data consumer for metafile";
85 pdf_data_.reset(NULL);
[email protected]8f17cd3e2011-03-16 01:39:4286 return false;
[email protected]14b210792009-10-12 18:45:3187 }
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
[email protected]8f17cd3e2011-03-16 01:39:4294 return true;
[email protected]14b210792009-10-12 18:45:3195}
96
[email protected]9b617532011-04-04 23:15:0697bool PdfMetafileCg::InitFromData(const void* src_buffer,
98 uint32 src_buffer_size) {
[email protected]14b210792009-10-12 18:45:3199 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
[email protected]62f2e802011-05-26 14:28:35113SkDevice* PdfMetafileCg::StartPageForVectorCanvas(
[email protected]534c4fb2011-08-02 16:44:20114 const gfx::Size& page_size, const gfx::Rect& content_area,
[email protected]d2fdcf02011-03-21 22:16:43115 const float& scale_factor) {
116 NOTIMPLEMENTED();
117 return NULL;
118}
119
[email protected]9b617532011-04-04 23:15:06120bool PdfMetafileCg::StartPage(const gfx::Size& page_size,
[email protected]39892b92011-04-30 02:24:44121 const gfx::Rect& content_area,
[email protected]9b617532011-04-04 23:15:06122 const float& scale_factor) {
[email protected]14b210792009-10-12 18:45:31123 DCHECK(context_.get());
124 DCHECK(!page_is_open_);
125
[email protected]15c2bb322010-12-21 22:30:10126 double height = page_size.height();
127 double width = page_size.width();
128
[email protected]14b210792009-10-12 18:45:31129 CGRect bounds = CGRectMake(0, 0, width, height);
130 CGContextBeginPage(context_, &bounds);
131 page_is_open_ = true;
132 CGContextSaveGState(context_);
133
[email protected]9d42d0722011-07-26 19:22:29134 // Move to the context origin.
135 CGContextTranslateCTM(context_, content_area.x(), -content_area.y());
136
[email protected]14b210792009-10-12 18:45:31137 // Flip the context.
138 CGContextTranslateCTM(context_, 0, height);
139 CGContextScaleCTM(context_, scale_factor, -scale_factor);
[email protected]15c2bb322010-12-21 22:30:10140
[email protected]edc531f92011-03-18 17:52:23141 return context_.get() != NULL;
[email protected]14b210792009-10-12 18:45:31142}
143
[email protected]9b617532011-04-04 23:15:06144bool PdfMetafileCg::FinishPage() {
[email protected]14b210792009-10-12 18:45:31145 DCHECK(context_.get());
146 DCHECK(page_is_open_);
147
148 CGContextRestoreGState(context_);
149 CGContextEndPage(context_);
150 page_is_open_ = false;
[email protected]8f17cd3e2011-03-16 01:39:42151 return true;
[email protected]14b210792009-10-12 18:45:31152}
153
[email protected]9b617532011-04-04 23:15:06154bool PdfMetafileCg::FinishDocument() {
[email protected]14b210792009-10-12 18:45:31155 DCHECK(context_.get());
156 DCHECK(!page_is_open_);
157
[email protected]72f966b2009-10-14 20:02:27158#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
[email protected]14619482010-04-01 16:46:23169 CGPDFContextClose(context_.get());
[email protected]14b210792009-10-12 18:45:31170 context_.reset(NULL);
[email protected]8f17cd3e2011-03-16 01:39:42171 return true;
[email protected]14b210792009-10-12 18:45:31172}
173
[email protected]9b617532011-04-04 23:15:06174bool PdfMetafileCg::RenderPage(unsigned int page_number,
175 CGContextRef context,
176 const CGRect rect,
[email protected]b5cf844c2012-06-18 21:49:20177 const MacRenderPageParams& params) const {
[email protected]72f966b2009-10-14 20:02:27178 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);
[email protected]a09ef7e2011-09-28 21:59:33184 CGRect source_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox);
[email protected]f3cab622010-06-28 18:56:30185 float scaling_factor = 1.0;
[email protected]b5cf844c2012-06-18 21:49:20186 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;
[email protected]72f966b2009-10-14 20:02:27195
[email protected]b5cf844c2012-06-18 21:49:20196 // 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);
[email protected]f3cab622010-06-28 18:56:30206 }
[email protected]b5cf844c2012-06-18 21:49:20207 // 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
[email protected]72f966b2009-10-14 20:02:27221 CGContextSaveGState(context);
[email protected]b5cf844c2012-06-18 21:49:20222
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.
[email protected]f3cab622010-06-28 18:56:30226 CGContextTranslateCTM(context, x_offset, y_offset);
[email protected]b5cf844c2012-06-18 21:49:20227 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 }
[email protected]f3cab622010-06-28 18:56:30234 CGContextScaleCTM(context, scaling_factor, scaling_factor);
[email protected]b5cf844c2012-06-18 21:49:20235 CGContextTranslateCTM(context, x_origin_offset, y_origin_offset);
236
[email protected]72f966b2009-10-14 20:02:27237 CGContextDrawPDFPage(context, pdf_page);
238 CGContextRestoreGState(context);
[email protected]b5cf844c2012-06-18 21:49:20239
[email protected]72f966b2009-10-14 20:02:27240 return true;
241}
242
[email protected]9b617532011-04-04 23:15:06243unsigned int PdfMetafileCg::GetPageCount() const {
[email protected]72f966b2009-10-14 20:02:27244 CGPDFDocumentRef pdf_doc = GetPDFDocument();
245 return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
246}
247
[email protected]9b617532011-04-04 23:15:06248gfx::Rect PdfMetafileCg::GetPageBounds(unsigned int page_number) const {
[email protected]72f966b2009-10-14 20:02:27249 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
[email protected]9b617532011-04-04 23:15:06263uint32 PdfMetafileCg::GetDataSize() const {
[email protected]14b210792009-10-12 18:45:31264 // PDF data is only valid/complete once the context is released.
265 DCHECK(!context_);
266
267 if (!pdf_data_)
268 return 0;
[email protected]b5ab3982010-02-16 23:58:27269 return static_cast<uint32>(CFDataGetLength(pdf_data_));
[email protected]14b210792009-10-12 18:45:31270}
271
[email protected]9b617532011-04-04 23:15:06272bool PdfMetafileCg::GetData(void* dst_buffer, uint32 dst_buffer_size) const {
[email protected]14b210792009-10-12 18:45:31273 // 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
[email protected]b5ab3982010-02-16 23:58:27279 uint32 data_size = GetDataSize();
[email protected]14b210792009-10-12 18:45:31280 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
[email protected]79f63882013-02-10 05:15:45289bool PdfMetafileCg::SaveTo(const base::FilePath& file_path) const {
[email protected]72f966b2009-10-14 20:02:27290 DCHECK(pdf_data_.get());
291 DCHECK(!context_.get());
292
293 std::string path_string = file_path.value();
[email protected]df0ca6c82010-10-17 04:09:06294 ScopedCFTypeRef<CFURLRef> path_url(CFURLCreateFromFileSystemRepresentation(
[email protected]72f966b2009-10-14 20:02:27295 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
[email protected]9b617532011-04-04 23:15:06302CGContextRef PdfMetafileCg::context() const {
[email protected]8f17cd3e2011-03-16 01:39:42303 return context_.get();
304}
305
[email protected]9b617532011-04-04 23:15:06306CGPDFDocumentRef PdfMetafileCg::GetPDFDocument() const {
[email protected]72f966b2009-10-14 20:02:27307 // 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()) {
[email protected]df0ca6c82010-10-17 04:09:06312 ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider(
[email protected]72f966b2009-10-14 20:02:27313 CGDataProviderCreateWithCFData(pdf_data_));
314 pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider));
315 }
316 return pdf_doc_.get();
317}
318
[email protected]14b210792009-10-12 18:45:31319} // namespace printing