[M108 Migration][HBBTV] Implement ewk_context_register_jsplugin_mime_types API
[platform/framework/web/chromium-efl.git] / printing / pdf_metafile_cg_mac.cc
1 // Copyright 2012 The Chromium Authors
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 "printing/mojom/print.mojom.h"
18 #include "ui/gfx/geometry/rect.h"
19 #include "ui/gfx/geometry/size.h"
20
21 using base::ScopedCFTypeRef;
22
23 namespace {
24
25 // Rotate a page by `num_rotations` * 90 degrees, counter-clockwise.
26 void RotatePage(CGContextRef context, const CGRect& rect, int num_rotations) {
27   switch (num_rotations) {
28     case 0:
29       break;
30     case 1:
31       // After rotating by 90 degrees with the axis at the origin, the page
32       // content is now "off screen". Shift it right to move it back on screen.
33       CGContextTranslateCTM(context, rect.size.width, 0);
34       // Rotates counter-clockwise by 90 degrees.
35       CGContextRotateCTM(context, base::kPiDouble / 2);
36       break;
37     case 2:
38       // After rotating by 180 degrees with the axis at the origin, the page
39       // content is now "off screen". Shift it right and up to move it back on
40       // screen.
41       CGContextTranslateCTM(context, rect.size.width, rect.size.height);
42       // Rotates counter-clockwise by 90 degrees.
43       CGContextRotateCTM(context, base::kPiDouble);
44       break;
45     case 3:
46       // After rotating by 270 degrees with the axis at the origin, the page
47       // content is now "off screen". Shift it right to move it back on screen.
48       CGContextTranslateCTM(context, 0, rect.size.height);
49       // Rotates counter-clockwise by 90 degrees.
50       CGContextRotateCTM(context, -base::kPiDouble / 2);
51       break;
52     default:
53       NOTREACHED();
54       break;
55   }
56 }
57
58 }  // namespace
59
60 namespace printing {
61
62 PdfMetafileCg::PdfMetafileCg() = default;
63
64 PdfMetafileCg::~PdfMetafileCg() = default;
65
66 bool PdfMetafileCg::Init() {
67   // Ensure that Init hasn't already been called.
68   DCHECK(!context_.get());
69   DCHECK(!pdf_data_.get());
70
71   pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, 0));
72   if (!pdf_data_.get()) {
73     LOG(ERROR) << "Failed to create pdf data for metafile";
74     return false;
75   }
76   ScopedCFTypeRef<CGDataConsumerRef> pdf_consumer(
77       CGDataConsumerCreateWithCFData(pdf_data_));
78   if (!pdf_consumer.get()) {
79     LOG(ERROR) << "Failed to create data consumer for metafile";
80     pdf_data_.reset();
81     return false;
82   }
83   context_.reset(CGPDFContextCreate(pdf_consumer, nullptr, nullptr));
84   if (!context_.get()) {
85     LOG(ERROR) << "Failed to create pdf context for metafile";
86     pdf_data_.reset();
87   }
88
89   return true;
90 }
91
92 bool PdfMetafileCg::InitFromData(base::span<const uint8_t> data) {
93   DCHECK(!context_.get());
94   DCHECK(!pdf_data_.get());
95
96   if (data.empty())
97     return false;
98
99   if (!base::IsValueInRangeForNumericType<CFIndex>(data.size()))
100     return false;
101
102   pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, data.size()));
103   CFDataAppendBytes(pdf_data_, data.data(), data.size());
104   return true;
105 }
106
107 void PdfMetafileCg::StartPage(const gfx::Size& page_size,
108                               const gfx::Rect& content_area,
109                               float scale_factor,
110                               mojom::PageOrientation page_orientation) {
111   DCHECK_EQ(page_orientation, mojom::PageOrientation::kUpright)
112       << "Not implemented";
113   DCHECK(context_.get());
114   DCHECK(!page_is_open_);
115
116   page_is_open_ = true;
117   float height = page_size.height();
118   float width = page_size.width();
119
120   CGRect bounds = CGRectMake(0, 0, width, height);
121   CGContextBeginPage(context_, &bounds);
122   CGContextSaveGState(context_);
123
124   // Move to the context origin.
125   CGContextTranslateCTM(context_, content_area.x(), -content_area.y());
126
127   // Flip the context.
128   CGContextTranslateCTM(context_, 0, height);
129   CGContextScaleCTM(context_, scale_factor, -scale_factor);
130 }
131
132 bool PdfMetafileCg::FinishPage() {
133   DCHECK(context_.get());
134   DCHECK(page_is_open_);
135
136   CGContextRestoreGState(context_);
137   CGContextEndPage(context_);
138   page_is_open_ = false;
139   return true;
140 }
141
142 bool PdfMetafileCg::FinishDocument() {
143   DCHECK(context_.get());
144   DCHECK(!page_is_open_);
145
146 #ifndef NDEBUG
147   // Check that the context will be torn down properly; if it's not, `pdf_data`
148   // will be incomplete and generate invalid PDF files/documents.
149   if (context_.get()) {
150     CFIndex extra_retain_count = CFGetRetainCount(context_.get()) - 1;
151     if (extra_retain_count > 0) {
152       LOG(ERROR) << "Metafile context has " << extra_retain_count
153                  << " extra retain(s) on Close";
154     }
155   }
156 #endif
157   CGPDFContextClose(context_.get());
158   context_.reset();
159   return true;
160 }
161
162 bool PdfMetafileCg::RenderPage(unsigned int page_number,
163                                CGContextRef context,
164                                const CGRect& rect,
165                                bool autorotate,
166                                bool fit_to_page) const {
167   CGPDFDocumentRef pdf_doc = GetPDFDocument();
168   if (!pdf_doc) {
169     LOG(ERROR) << "Unable to create PDF document from data";
170     return false;
171   }
172
173   const unsigned int page_count = GetPageCount();
174   DCHECK_NE(page_count, 0U);
175   DCHECK_NE(page_number, 0U);
176   DCHECK_LE(page_number, page_count);
177
178   CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
179   CGRect source_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox);
180   const int pdf_src_rotation = CGPDFPageGetRotationAngle(pdf_page);
181   const bool source_is_landscape =
182       (source_rect.size.width > source_rect.size.height);
183   const bool dest_is_landscape = (rect.size.width > rect.size.height);
184   const bool rotate = autorotate && (source_is_landscape != dest_is_landscape);
185   const float source_width =
186       rotate ? source_rect.size.height : source_rect.size.width;
187   const float source_height =
188       rotate ? source_rect.size.width : source_rect.size.height;
189
190   // See if we need to scale the output.
191   float scaling_factor = 1.0;
192   const bool scaling_needed =
193       fit_to_page && ((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
201   CGContextSaveGState(context);
202
203   int num_rotations = 0;
204   if (rotate) {
205     if (pdf_src_rotation == 0 || pdf_src_rotation == 270) {
206       num_rotations = 1;
207     } else {
208       num_rotations = 3;
209     }
210   } else {
211     if (pdf_src_rotation == 180 || pdf_src_rotation == 270) {
212       num_rotations = 2;
213     }
214   }
215   RotatePage(context, rect, num_rotations);
216
217   CGContextScaleCTM(context, scaling_factor, scaling_factor);
218
219   // Some PDFs have a non-zero origin. Need to take that into account and align
220   // the PDF to the CoreGraphics's coordinate system origin. Also realign the
221   // contents from the bottom-left of the page to top-left in order to stay
222   // consistent with Print Preview.
223   // A rotational vertical offset is calculated to determine how much to offset
224   // the y-component of the origin to move the origin from bottom-left to
225   // top-right. When the source is not rotated, the offset is simply the
226   // difference between the paper height and the source height. When rotated,
227   // the y-axis of the source falls along the width of the source and paper, so
228   // the offset becomes the difference between the paper width and the source
229   // width.
230   const float rotational_vertical_offset =
231       rotate ? (rect.size.width - (scaling_factor * source_width))
232              : (rect.size.height - (scaling_factor * source_height));
233   const float x_origin_offset = -1 * source_rect.origin.x;
234   const float y_origin_offset =
235       rotational_vertical_offset - source_rect.origin.y;
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 == 0 || 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 bool PdfMetafileCg::ShouldCopySharedMemoryRegionData() const {
291   // Since `InitFromData()` copies the data, the caller doesn't have to.
292   return false;
293 }
294
295 mojom::MetafileDataType PdfMetafileCg::GetDataType() const {
296   return mojom::MetafileDataType::kPDF;
297 }
298
299 CGContextRef PdfMetafileCg::context() const {
300   return context_.get();
301 }
302
303 CGPDFDocumentRef PdfMetafileCg::GetPDFDocument() const {
304   // Make sure that we have data, and that it's not being modified any more.
305   DCHECK(pdf_data_.get());
306   DCHECK(!context_.get());
307
308   if (!pdf_doc_.get()) {
309     ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider(
310         CGDataProviderCreateWithCFData(pdf_data_));
311     pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider));
312   }
313   return pdf_doc_.get();
314 }
315
316 }  // namespace printing