Upload upstream chromium 73.0.3683.0
[platform/framework/web/chromium-efl.git] / printing / pdf_metafile_cg_mac.cc
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.
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() : page_is_open_(false) {}
62
63 PdfMetafileCg::~PdfMetafileCg() {}
64
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
91 bool PdfMetafileCg::InitFromData(const void* src_buffer,
92                                  size_t src_buffer_size) {
93   DCHECK(!context_.get());
94   DCHECK(!pdf_data_.get());
95
96   if (!src_buffer || !src_buffer_size)
97     return false;
98
99   if (!base::IsValueInRangeForNumericType<CFIndex>(src_buffer_size))
100     return false;
101
102   pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, src_buffer_size));
103   CFDataAppendBytes(pdf_data_, static_cast<const UInt8*>(src_buffer),
104                     src_buffer_size);
105   return true;
106 }
107
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_);
113
114   double height = page_size.height();
115   double width = page_size.width();
116
117   CGRect bounds = CGRectMake(0, 0, width, height);
118   CGContextBeginPage(context_, &bounds);
119   page_is_open_ = true;
120   CGContextSaveGState(context_);
121
122   // Move to the context origin.
123   CGContextTranslateCTM(context_, content_area.x(), -content_area.y());
124
125   // Flip the context.
126   CGContextTranslateCTM(context_, 0, height);
127   CGContextScaleCTM(context_, scale_factor, -scale_factor);
128 }
129
130 bool PdfMetafileCg::FinishPage() {
131   DCHECK(context_.get());
132   DCHECK(page_is_open_);
133
134   CGContextRestoreGState(context_);
135   CGContextEndPage(context_);
136   page_is_open_ = false;
137   return true;
138 }
139
140 bool PdfMetafileCg::FinishDocument() {
141   DCHECK(context_.get());
142   DCHECK(!page_is_open_);
143
144 #ifndef NDEBUG
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";
152     }
153   }
154 #endif
155   CGPDFContextClose(context_.get());
156   context_.reset();
157   return true;
158 }
159
160 bool PdfMetafileCg::RenderPage(unsigned int page_number,
161                                CGContextRef context,
162                                const CGRect rect,
163                                const MacRenderPageParams& params) const {
164   CGPDFDocumentRef pdf_doc = GetPDFDocument();
165   if (!pdf_doc) {
166     LOG(ERROR) << "Unable to create PDF document from data";
167     return false;
168   }
169
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);
174
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);
181   const bool rotate =
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;
187
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);
199   }
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;
204
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;
208   if (rotate)
209     x_offset = -x_offset;
210
211   float y_offset = params.center_vertically ?
212       ((rect.size.height - (source_height * scaling_factor)) / 2) : 0;
213
214   CGContextSaveGState(context);
215
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);
220
221   int num_rotations = 0;
222   if (rotate) {
223     if (pdf_src_rotation == 0 || pdf_src_rotation == 270) {
224       num_rotations = 1;
225     } else {
226       num_rotations = 3;
227     }
228   } else {
229     if (pdf_src_rotation == 180 || pdf_src_rotation == 270) {
230       num_rotations = 2;
231     }
232   }
233   RotatePage(context, rect, num_rotations);
234
235   CGContextScaleCTM(context, scaling_factor, scaling_factor);
236   CGContextTranslateCTM(context, x_origin_offset, y_origin_offset);
237
238   CGContextDrawPDFPage(context, pdf_page);
239   CGContextRestoreGState(context);
240
241   return true;
242 }
243
244 unsigned int PdfMetafileCg::GetPageCount() const {
245   CGPDFDocumentRef pdf_doc = GetPDFDocument();
246   return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
247 }
248
249 gfx::Rect PdfMetafileCg::GetPageBounds(unsigned int page_number) const {
250   CGPDFDocumentRef pdf_doc = GetPDFDocument();
251   if (!pdf_doc) {
252     LOG(ERROR) << "Unable to create PDF document from data";
253     return gfx::Rect();
254   }
255   if (page_number > GetPageCount()) {
256     LOG(ERROR) << "Invalid page number: " << page_number;
257     return gfx::Rect();
258   }
259   CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
260   CGRect page_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFMediaBox);
261   return gfx::Rect(page_rect);
262 }
263
264 uint32_t PdfMetafileCg::GetDataSize() const {
265   // PDF data is only valid/complete once the context is released.
266   DCHECK(!context_);
267
268   if (!pdf_data_)
269     return 0;
270   return static_cast<uint32_t>(CFDataGetLength(pdf_data_));
271 }
272
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.
275   DCHECK(!context_);
276   DCHECK(pdf_data_);
277   DCHECK(dst_buffer);
278   DCHECK_GT(dst_buffer_size, 0U);
279
280   uint32_t data_size = GetDataSize();
281   if (dst_buffer_size > data_size) {
282     return false;
283   }
284
285   CFDataGetBytes(pdf_data_, CFRangeMake(0, dst_buffer_size),
286                  static_cast<UInt8*>(dst_buffer));
287   return true;
288 }
289
290 CGContextRef PdfMetafileCg::context() const {
291   return context_.get();
292 }
293
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());
298
299   if (!pdf_doc_.get()) {
300     ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider(
301         CGDataProviderCreateWithCFData(pdf_data_));
302     pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider));
303   }
304   return pdf_doc_.get();
305 }
306
307 }  // namespace printing