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 <stdint.h>
8
9 #include <algorithm>
10
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"
19
20 using base::ScopedCFTypeRef;
21
22 namespace {
23
24 // Rotate a page by |num_rotations| * 90 degrees, counter-clockwise.
RotatePage(CGContextRef context,const CGRect & rect,int num_rotations)25 void RotatePage(CGContextRef context, const CGRect& rect, int num_rotations) {
26 switch (num_rotations) {
27 case 0:
28 break;
29 case 1:
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);
35 break;
36 case 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
39 // screen.
40 CGContextTranslateCTM(context, rect.size.width, rect.size.height);
41 // Rotates counter-clockwise by 90 degrees.
42 CGContextRotateCTM(context, base::kPiDouble);
43 break;
44 case 3:
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);
50 break;
51 default:
52 NOTREACHED();
53 break;
54 }
55 }
56
57 } // namespace
58
59 namespace printing {
60
61 PdfMetafileCg::PdfMetafileCg() = default;
62
63 PdfMetafileCg::~PdfMetafileCg() = default;
64
Init()65 bool PdfMetafileCg::Init() {
66 // Ensure that Init hasn't already been called.
67 DCHECK(!context_.get());
68 DCHECK(!pdf_data_.get());
69
70 pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, 0));
71 if (!pdf_data_.get()) {
72 LOG(ERROR) << "Failed to create pdf data for metafile";
73 return false;
74 }
75 ScopedCFTypeRef<CGDataConsumerRef> pdf_consumer(
76 CGDataConsumerCreateWithCFData(pdf_data_));
77 if (!pdf_consumer.get()) {
78 LOG(ERROR) << "Failed to create data consumer for metafile";
79 pdf_data_.reset();
80 return false;
81 }
82 context_.reset(CGPDFContextCreate(pdf_consumer, nullptr, nullptr));
83 if (!context_.get()) {
84 LOG(ERROR) << "Failed to create pdf context for metafile";
85 pdf_data_.reset();
86 }
87
88 return true;
89 }
90
InitFromData(base::span<const uint8_t> data)91 bool PdfMetafileCg::InitFromData(base::span<const uint8_t> data) {
92 DCHECK(!context_.get());
93 DCHECK(!pdf_data_.get());
94
95 if (data.empty())
96 return false;
97
98 if (!base::IsValueInRangeForNumericType<CFIndex>(data.size()))
99 return false;
100
101 pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, data.size()));
102 CFDataAppendBytes(pdf_data_, data.data(), data.size());
103 return true;
104 }
105
StartPage(const gfx::Size & page_size,const gfx::Rect & content_area,float scale_factor)106 void PdfMetafileCg::StartPage(const gfx::Size& page_size,
107 const gfx::Rect& content_area,
108 float scale_factor) {
109 DCHECK(context_.get());
110 DCHECK(!page_is_open_);
111
112 page_is_open_ = true;
113 float height = page_size.height();
114 float width = page_size.width();
115
116 CGRect bounds = CGRectMake(0, 0, width, height);
117 CGContextBeginPage(context_, &bounds);
118 CGContextSaveGState(context_);
119
120 // Move to the context origin.
121 CGContextTranslateCTM(context_, content_area.x(), -content_area.y());
122
123 // Flip the context.
124 CGContextTranslateCTM(context_, 0, height);
125 CGContextScaleCTM(context_, scale_factor, -scale_factor);
126 }
127
FinishPage()128 bool PdfMetafileCg::FinishPage() {
129 DCHECK(context_.get());
130 DCHECK(page_is_open_);
131
132 CGContextRestoreGState(context_);
133 CGContextEndPage(context_);
134 page_is_open_ = false;
135 return true;
136 }
137
FinishDocument()138 bool PdfMetafileCg::FinishDocument() {
139 DCHECK(context_.get());
140 DCHECK(!page_is_open_);
141
142 #ifndef NDEBUG
143 // Check that the context will be torn down properly; if it's not, |pdf_data|
144 // will be incomplete and generate invalid PDF files/documents.
145 if (context_.get()) {
146 CFIndex extra_retain_count = CFGetRetainCount(context_.get()) - 1;
147 if (extra_retain_count > 0) {
148 LOG(ERROR) << "Metafile context has " << extra_retain_count
149 << " extra retain(s) on Close";
150 }
151 }
152 #endif
153 CGPDFContextClose(context_.get());
154 context_.reset();
155 return true;
156 }
157
RenderPage(unsigned int page_number,CGContextRef context,const CGRect & rect,const MacRenderPageParams & params) const158 bool PdfMetafileCg::RenderPage(unsigned int page_number,
159 CGContextRef context,
160 const CGRect& rect,
161 const MacRenderPageParams& params) const {
162 CGPDFDocumentRef pdf_doc = GetPDFDocument();
163 if (!pdf_doc) {
164 LOG(ERROR) << "Unable to create PDF document from data";
165 return false;
166 }
167
168 const unsigned int page_count = GetPageCount();
169 DCHECK_NE(page_count, 0U);
170 DCHECK_NE(page_number, 0U);
171 DCHECK_LE(page_number, page_count);
172
173 CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
174 CGRect source_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox);
175 int pdf_src_rotation = CGPDFPageGetRotationAngle(pdf_page);
176 const bool source_is_landscape =
177 (source_rect.size.width > source_rect.size.height);
178 const bool dest_is_landscape = (rect.size.width > rect.size.height);
179 const bool rotate =
180 params.autorotate ? (source_is_landscape != dest_is_landscape) : false;
181 const float source_width =
182 rotate ? source_rect.size.height : source_rect.size.width;
183 const float source_height =
184 rotate ? source_rect.size.width : source_rect.size.height;
185
186 // See if we need to scale the output.
187 float scaling_factor = 1.0;
188 const bool scaling_needed =
189 (params.shrink_to_fit && ((source_width > rect.size.width) ||
190 (source_height > rect.size.height))) ||
191 (params.stretch_to_fit && ((source_width < rect.size.width) &&
192 (source_height < rect.size.height)));
193 if (scaling_needed) {
194 float x_scaling_factor = rect.size.width / source_width;
195 float y_scaling_factor = rect.size.height / source_height;
196 scaling_factor = std::min(x_scaling_factor, y_scaling_factor);
197 }
198 // Some PDFs have a non-zero origin. Need to take that into account and align
199 // the PDF to the origin.
200 const float x_origin_offset = -1 * source_rect.origin.x;
201 const float y_origin_offset = -1 * source_rect.origin.y;
202
203 // If the PDF needs to be centered, calculate the offsets here.
204 float x_offset =
205 params.center_horizontally
206 ? ((rect.size.width - (source_width * scaling_factor)) / 2)
207 : 0;
208 if (rotate)
209 x_offset = -x_offset;
210
211 float y_offset =
212 params.center_vertically
213 ? ((rect.size.height - (source_height * scaling_factor)) / 2)
214 : 0;
215
216 CGContextSaveGState(context);
217
218 // The transform operations specified here gets applied in reverse order.
219 // i.e. the origin offset translation happens first.
220 // Origin is at bottom-left.
221 CGContextTranslateCTM(context, x_offset, y_offset);
222
223 int num_rotations = 0;
224 if (rotate) {
225 if (pdf_src_rotation == 0 || pdf_src_rotation == 270) {
226 num_rotations = 1;
227 } else {
228 num_rotations = 3;
229 }
230 } else {
231 if (pdf_src_rotation == 180 || pdf_src_rotation == 270) {
232 num_rotations = 2;
233 }
234 }
235 RotatePage(context, rect, num_rotations);
236
237 CGContextScaleCTM(context, scaling_factor, scaling_factor);
238 CGContextTranslateCTM(context, x_origin_offset, y_origin_offset);
239
240 CGContextDrawPDFPage(context, pdf_page);
241 CGContextRestoreGState(context);
242
243 return true;
244 }
245
GetPageCount() const246 unsigned int PdfMetafileCg::GetPageCount() const {
247 CGPDFDocumentRef pdf_doc = GetPDFDocument();
248 return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
249 }
250
GetPageBounds(unsigned int page_number) const251 gfx::Rect PdfMetafileCg::GetPageBounds(unsigned int page_number) const {
252 CGPDFDocumentRef pdf_doc = GetPDFDocument();
253 if (!pdf_doc) {
254 LOG(ERROR) << "Unable to create PDF document from data";
255 return gfx::Rect();
256 }
257 if (page_number == 0 || page_number > GetPageCount()) {
258 LOG(ERROR) << "Invalid page number: " << page_number;
259 return gfx::Rect();
260 }
261 CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
262 CGRect page_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFMediaBox);
263 return gfx::Rect(page_rect);
264 }
265
GetDataSize() const266 uint32_t PdfMetafileCg::GetDataSize() const {
267 // PDF data is only valid/complete once the context is released.
268 DCHECK(!context_);
269
270 if (!pdf_data_)
271 return 0;
272 return static_cast<uint32_t>(CFDataGetLength(pdf_data_));
273 }
274
GetData(void * dst_buffer,uint32_t dst_buffer_size) const275 bool PdfMetafileCg::GetData(void* dst_buffer, uint32_t dst_buffer_size) const {
276 // PDF data is only valid/complete once the context is released.
277 DCHECK(!context_);
278 DCHECK(pdf_data_);
279 DCHECK(dst_buffer);
280 DCHECK_GT(dst_buffer_size, 0U);
281
282 uint32_t data_size = GetDataSize();
283 if (dst_buffer_size > data_size) {
284 return false;
285 }
286
287 CFDataGetBytes(pdf_data_, CFRangeMake(0, dst_buffer_size),
288 static_cast<UInt8*>(dst_buffer));
289 return true;
290 }
291
context() const292 CGContextRef PdfMetafileCg::context() const {
293 return context_.get();
294 }
295
GetPDFDocument() const296 CGPDFDocumentRef PdfMetafileCg::GetPDFDocument() const {
297 // Make sure that we have data, and that it's not being modified any more.
298 DCHECK(pdf_data_.get());
299 DCHECK(!context_.get());
300
301 if (!pdf_doc_.get()) {
302 ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider(
303 CGDataProviderCreateWithCFData(pdf_data_));
304 pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider));
305 }
306 return pdf_doc_.get();
307 }
308
309 } // namespace printing
310