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"
11 #include "base/logging.h"
12 #include "base/mac/mac_util.h"
13 #include "base/mac/scoped_cftyperef.h"
14 #include "base/numerics/math_constants.h"
15 #include "base/numerics/safe_conversions.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "ui/gfx/geometry/rect.h"
18 #include "ui/gfx/geometry/size.h"
20 using base::ScopedCFTypeRef;
24 // Rotate a page by |num_rotations| * 90 degrees, counter-clockwise.
25 void RotatePage(CGContextRef context, const CGRect rect, int num_rotations) {
26 switch (num_rotations) {
30 // After rotating by 90 degrees with the axis at the origin, the page
31 // content is now "off screen". Shift it right to move it back on screen.
32 CGContextTranslateCTM(context, rect.size.width, 0);
33 // Rotates counter-clockwise by 90 degrees.
34 CGContextRotateCTM(context, base::kPiDouble / 2);
37 // After rotating by 180 degrees with the axis at the origin, the page
38 // content is now "off screen". Shift it right and up to move it back on
40 CGContextTranslateCTM(context, rect.size.width, rect.size.height);
41 // Rotates counter-clockwise by 90 degrees.
42 CGContextRotateCTM(context, base::kPiDouble);
45 // After rotating by 270 degrees with the axis at the origin, the page
46 // content is now "off screen". Shift it right to move it back on screen.
47 CGContextTranslateCTM(context, 0, rect.size.height);
48 // Rotates counter-clockwise by 90 degrees.
49 CGContextRotateCTM(context, -base::kPiDouble / 2);
61 PdfMetafileCg::PdfMetafileCg() : page_is_open_(false) {}
63 PdfMetafileCg::~PdfMetafileCg() {}
65 bool PdfMetafileCg::Init() {
66 // Ensure that Init hasn't already been called.
67 DCHECK(!context_.get());
68 DCHECK(!pdf_data_.get());
70 pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, 0));
71 if (!pdf_data_.get()) {
72 LOG(ERROR) << "Failed to create pdf data for metafile";
75 ScopedCFTypeRef<CGDataConsumerRef> pdf_consumer(
76 CGDataConsumerCreateWithCFData(pdf_data_));
77 if (!pdf_consumer.get()) {
78 LOG(ERROR) << "Failed to create data consumer for metafile";
82 context_.reset(CGPDFContextCreate(pdf_consumer, nullptr, nullptr));
83 if (!context_.get()) {
84 LOG(ERROR) << "Failed to create pdf context for metafile";
91 bool PdfMetafileCg::InitFromData(const void* src_buffer,
92 size_t src_buffer_size) {
93 DCHECK(!context_.get());
94 DCHECK(!pdf_data_.get());
96 if (!src_buffer || !src_buffer_size)
99 if (!base::IsValueInRangeForNumericType<CFIndex>(src_buffer_size))
102 pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, src_buffer_size));
103 CFDataAppendBytes(pdf_data_, static_cast<const UInt8*>(src_buffer),
108 void PdfMetafileCg::StartPage(const gfx::Size& page_size,
109 const gfx::Rect& content_area,
110 const float& scale_factor) {
111 DCHECK(context_.get());
112 DCHECK(!page_is_open_);
114 double height = page_size.height();
115 double width = page_size.width();
117 CGRect bounds = CGRectMake(0, 0, width, height);
118 CGContextBeginPage(context_, &bounds);
119 page_is_open_ = true;
120 CGContextSaveGState(context_);
122 // Move to the context origin.
123 CGContextTranslateCTM(context_, content_area.x(), -content_area.y());
126 CGContextTranslateCTM(context_, 0, height);
127 CGContextScaleCTM(context_, scale_factor, -scale_factor);
130 bool PdfMetafileCg::FinishPage() {
131 DCHECK(context_.get());
132 DCHECK(page_is_open_);
134 CGContextRestoreGState(context_);
135 CGContextEndPage(context_);
136 page_is_open_ = false;
140 bool PdfMetafileCg::FinishDocument() {
141 DCHECK(context_.get());
142 DCHECK(!page_is_open_);
145 // Check that the context will be torn down properly; if it's not, |pdf_data|
146 // will be incomplete and generate invalid PDF files/documents.
147 if (context_.get()) {
148 CFIndex extra_retain_count = CFGetRetainCount(context_.get()) - 1;
149 if (extra_retain_count > 0) {
150 LOG(ERROR) << "Metafile context has " << extra_retain_count
151 << " extra retain(s) on Close";
155 CGPDFContextClose(context_.get());
160 bool PdfMetafileCg::RenderPage(unsigned int page_number,
161 CGContextRef context,
163 const MacRenderPageParams& params) const {
164 CGPDFDocumentRef pdf_doc = GetPDFDocument();
166 LOG(ERROR) << "Unable to create PDF document from data";
170 const unsigned int page_count = GetPageCount();
171 DCHECK_NE(page_count, 0U);
172 DCHECK_NE(page_number, 0U);
173 DCHECK_LE(page_number, page_count);
175 CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
176 CGRect source_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox);
177 int pdf_src_rotation = CGPDFPageGetRotationAngle(pdf_page);
178 const bool source_is_landscape =
179 (source_rect.size.width > source_rect.size.height);
180 const bool dest_is_landscape = (rect.size.width > rect.size.height);
182 params.autorotate ? (source_is_landscape != dest_is_landscape) : false;
183 const float source_width =
184 rotate ? source_rect.size.height : source_rect.size.width;
185 const float source_height =
186 rotate ? source_rect.size.width : source_rect.size.height;
188 // See if we need to scale the output.
189 float scaling_factor = 1.0;
190 const bool scaling_needed =
191 (params.shrink_to_fit && ((source_width > rect.size.width) ||
192 (source_height > rect.size.height))) ||
193 (params.stretch_to_fit && ((source_width < rect.size.width) &&
194 (source_height < rect.size.height)));
195 if (scaling_needed) {
196 float x_scaling_factor = rect.size.width / source_width;
197 float y_scaling_factor = rect.size.height / source_height;
198 scaling_factor = std::min(x_scaling_factor, y_scaling_factor);
200 // Some PDFs have a non-zero origin. Need to take that into account and align
201 // the PDF to the origin.
202 const float x_origin_offset = -1 * source_rect.origin.x;
203 const float y_origin_offset = -1 * source_rect.origin.y;
205 // If the PDF needs to be centered, calculate the offsets here.
206 float x_offset = params.center_horizontally ?
207 ((rect.size.width - (source_width * scaling_factor)) / 2) : 0;
209 x_offset = -x_offset;
211 float y_offset = params.center_vertically ?
212 ((rect.size.height - (source_height * scaling_factor)) / 2) : 0;
214 CGContextSaveGState(context);
216 // The transform operations specified here gets applied in reverse order.
217 // i.e. the origin offset translation happens first.
218 // Origin is at bottom-left.
219 CGContextTranslateCTM(context, x_offset, y_offset);
221 int num_rotations = 0;
223 if (pdf_src_rotation == 0 || pdf_src_rotation == 270) {
229 if (pdf_src_rotation == 180 || pdf_src_rotation == 270) {
233 RotatePage(context, rect, num_rotations);
235 CGContextScaleCTM(context, scaling_factor, scaling_factor);
236 CGContextTranslateCTM(context, x_origin_offset, y_origin_offset);
238 CGContextDrawPDFPage(context, pdf_page);
239 CGContextRestoreGState(context);
244 unsigned int PdfMetafileCg::GetPageCount() const {
245 CGPDFDocumentRef pdf_doc = GetPDFDocument();
246 return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
249 gfx::Rect PdfMetafileCg::GetPageBounds(unsigned int page_number) const {
250 CGPDFDocumentRef pdf_doc = GetPDFDocument();
252 LOG(ERROR) << "Unable to create PDF document from data";
255 if (page_number > GetPageCount()) {
256 LOG(ERROR) << "Invalid page number: " << page_number;
259 CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
260 CGRect page_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFMediaBox);
261 return gfx::Rect(page_rect);
264 uint32_t PdfMetafileCg::GetDataSize() const {
265 // PDF data is only valid/complete once the context is released.
270 return static_cast<uint32_t>(CFDataGetLength(pdf_data_));
273 bool PdfMetafileCg::GetData(void* dst_buffer, uint32_t dst_buffer_size) const {
274 // PDF data is only valid/complete once the context is released.
278 DCHECK_GT(dst_buffer_size, 0U);
280 uint32_t data_size = GetDataSize();
281 if (dst_buffer_size > data_size) {
285 CFDataGetBytes(pdf_data_, CFRangeMake(0, dst_buffer_size),
286 static_cast<UInt8*>(dst_buffer));
290 CGContextRef PdfMetafileCg::context() const {
291 return context_.get();
294 CGPDFDocumentRef PdfMetafileCg::GetPDFDocument() const {
295 // Make sure that we have data, and that it's not being modified any more.
296 DCHECK(pdf_data_.get());
297 DCHECK(!context_.get());
299 if (!pdf_doc_.get()) {
300 ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider(
301 CGDataProviderCreateWithCFData(pdf_data_));
302 pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider));
304 return pdf_doc_.get();
307 } // namespace printing