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