blob: d9d621351398ea51b9ae344be38778e1bf383a33 [file] [log] [blame]
[email protected]9fc44162012-01-23 22:56:411// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]14b210792009-10-12 18:45:312// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]9b617532011-04-04 23:15:065#include "printing/pdf_metafile_cg_mac.h"
[email protected]14b210792009-10-12 18:45:316
avi126e93c2015-12-21 21:48:167#include <stdint.h>
8
[email protected]b5cf844c2012-06-18 21:49:209#include <algorithm>
10
[email protected]14b210792009-10-12 18:45:3111#include "base/logging.h"
[email protected]ba64e2b2011-06-14 18:18:3812#include "base/mac/mac_util.h"
[email protected]df0ca6c82010-10-17 04:09:0613#include "base/mac/scoped_cftyperef.h"
Peter Kasting76ed0662017-09-14 08:28:0714#include "base/numerics/math_constants.h"
thestig6b4461f2016-10-28 19:34:3015#include "base/numerics/safe_conversions.h"
[email protected]13ac53532013-03-30 00:27:0016#include "base/strings/sys_string_conversions.h"
tfarina3b0452d2014-12-31 15:20:0917#include "ui/gfx/geometry/rect.h"
tfarinaebe974f02015-01-03 04:25:3218#include "ui/gfx/geometry/size.h"
[email protected]14b210792009-10-12 18:45:3119
[email protected]3df79f42013-06-24 18:49:0520using base::ScopedCFTypeRef;
[email protected]df0ca6c82010-10-17 04:09:0621
[email protected]df6df682011-05-02 19:50:5022namespace {
23
thestig23fe51772014-09-27 17:40:5124// Rotate a page by |num_rotations| * 90 degrees, counter-clockwise.
25void 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.
Peter Kasting76ed0662017-09-14 08:28:0734 CGContextRotateCTM(context, base::kPiDouble / 2);
thestig23fe51772014-09-27 17:40:5135 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.
Peter Kasting76ed0662017-09-14 08:28:0742 CGContextRotateCTM(context, base::kPiDouble);
thestig23fe51772014-09-27 17:40:5143 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.
Peter Kasting76ed0662017-09-14 08:28:0749 CGContextRotateCTM(context, -base::kPiDouble / 2);
thestig23fe51772014-09-27 17:40:5150 break;
51 default:
52 NOTREACHED();
53 break;
thestig707a24b22015-09-14 18:16:3354 }
thestig23fe51772014-09-27 17:40:5155}
56
[email protected]df6df682011-05-02 19:50:5057} // namespace
58
[email protected]14b210792009-10-12 18:45:3159namespace printing {
60
thestig3520b802016-04-04 22:12:5261PdfMetafileCg::PdfMetafileCg() : page_is_open_(false) {}
[email protected]14b210792009-10-12 18:45:3162
thestig3520b802016-04-04 22:12:5263PdfMetafileCg::~PdfMetafileCg() {}
[email protected]1d20cdf2011-02-16 18:09:2864
[email protected]9b617532011-04-04 23:15:0665bool PdfMetafileCg::Init() {
[email protected]14b210792009-10-12 18:45:3166 // 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";
[email protected]8f17cd3e2011-03-16 01:39:4273 return false;
[email protected]14b210792009-10-12 18:45:3174 }
[email protected]df0ca6c82010-10-17 04:09:0675 ScopedCFTypeRef<CGDataConsumerRef> pdf_consumer(
[email protected]14b210792009-10-12 18:45:3176 CGDataConsumerCreateWithCFData(pdf_data_));
77 if (!pdf_consumer.get()) {
78 LOG(ERROR) << "Failed to create data consumer for metafile";
thestig3520b802016-04-04 22:12:5279 pdf_data_.reset();
[email protected]8f17cd3e2011-03-16 01:39:4280 return false;
[email protected]14b210792009-10-12 18:45:3181 }
thestig3520b802016-04-04 22:12:5282 context_.reset(CGPDFContextCreate(pdf_consumer, nullptr, nullptr));
[email protected]14b210792009-10-12 18:45:3183 if (!context_.get()) {
84 LOG(ERROR) << "Failed to create pdf context for metafile";
thestig3520b802016-04-04 22:12:5285 pdf_data_.reset();
[email protected]14b210792009-10-12 18:45:3186 }
87
[email protected]8f17cd3e2011-03-16 01:39:4288 return true;
[email protected]14b210792009-10-12 18:45:3189}
90
[email protected]9b617532011-04-04 23:15:0691bool PdfMetafileCg::InitFromData(const void* src_buffer,
thestig6b4461f2016-10-28 19:34:3092 size_t src_buffer_size) {
[email protected]14b210792009-10-12 18:45:3193 DCHECK(!context_.get());
94 DCHECK(!pdf_data_.get());
95
thestig6b4461f2016-10-28 19:34:3096 if (!src_buffer || !src_buffer_size)
[email protected]14b210792009-10-12 18:45:3197 return false;
thestig6b4461f2016-10-28 19:34:3098
99 if (!base::IsValueInRangeForNumericType<CFIndex>(src_buffer_size))
100 return false;
[email protected]14b210792009-10-12 18:45:31101
102 pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, src_buffer_size));
103 CFDataAppendBytes(pdf_data_, static_cast<const UInt8*>(src_buffer),
104 src_buffer_size);
[email protected]14b210792009-10-12 18:45:31105 return true;
106}
107
thestig192677ec2016-06-09 07:43:17108void PdfMetafileCg::StartPage(const gfx::Size& page_size,
[email protected]39892b92011-04-30 02:24:44109 const gfx::Rect& content_area,
[email protected]9b617532011-04-04 23:15:06110 const float& scale_factor) {
[email protected]14b210792009-10-12 18:45:31111 DCHECK(context_.get());
112 DCHECK(!page_is_open_);
113
[email protected]15c2bb322010-12-21 22:30:10114 double height = page_size.height();
115 double width = page_size.width();
116
[email protected]14b210792009-10-12 18:45:31117 CGRect bounds = CGRectMake(0, 0, width, height);
118 CGContextBeginPage(context_, &bounds);
119 page_is_open_ = true;
120 CGContextSaveGState(context_);
121
[email protected]9d42d0722011-07-26 19:22:29122 // Move to the context origin.
123 CGContextTranslateCTM(context_, content_area.x(), -content_area.y());
124
[email protected]14b210792009-10-12 18:45:31125 // Flip the context.
126 CGContextTranslateCTM(context_, 0, height);
127 CGContextScaleCTM(context_, scale_factor, -scale_factor);
128}
129
[email protected]9b617532011-04-04 23:15:06130bool PdfMetafileCg::FinishPage() {
[email protected]14b210792009-10-12 18:45:31131 DCHECK(context_.get());
132 DCHECK(page_is_open_);
133
134 CGContextRestoreGState(context_);
135 CGContextEndPage(context_);
136 page_is_open_ = false;
[email protected]8f17cd3e2011-03-16 01:39:42137 return true;
[email protected]14b210792009-10-12 18:45:31138}
139
[email protected]9b617532011-04-04 23:15:06140bool PdfMetafileCg::FinishDocument() {
[email protected]14b210792009-10-12 18:45:31141 DCHECK(context_.get());
142 DCHECK(!page_is_open_);
143
[email protected]72f966b2009-10-14 20:02:27144#ifndef NDEBUG
Lei Zhang764461802017-11-15 23:36:07145 // Check that the context will be torn down properly; if it's not, |pdf_data|
[email protected]72f966b2009-10-14 20:02:27146 // 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
[email protected]14619482010-04-01 16:46:23155 CGPDFContextClose(context_.get());
thestig3520b802016-04-04 22:12:52156 context_.reset();
[email protected]8f17cd3e2011-03-16 01:39:42157 return true;
[email protected]14b210792009-10-12 18:45:31158}
159
thestig99fc2132017-04-27 03:37:54160bool PdfMetafileCg::RenderPage(unsigned int page_number,
[email protected]9b617532011-04-04 23:15:06161 CGContextRef context,
162 const CGRect rect,
thestig99fc2132017-04-27 03:37:54163 const MacRenderPageParams& params) const {
164 CGPDFDocumentRef pdf_doc = GetPDFDocument();
[email protected]72f966b2009-10-14 20:02:27165 if (!pdf_doc) {
166 LOG(ERROR) << "Unable to create PDF document from data";
167 return false;
168 }
Lei Zhang764461802017-11-15 23:36:07169
Lei Zhangc3f34662017-11-16 20:29:41170 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
[email protected]72f966b2009-10-14 20:02:27175 CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
[email protected]a09ef7e2011-09-28 21:59:33176 CGRect source_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox);
thestig23fe51772014-09-27 17:40:51177 int pdf_src_rotation = CGPDFPageGetRotationAngle(pdf_page);
[email protected]b5cf844c2012-06-18 21:49:20178 const bool source_is_landscape =
Lei Zhang01a1d3c02019-05-21 04:59:10179 (source_rect.size.width > source_rect.size.height);
[email protected]b5cf844c2012-06-18 21:49:20180 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;
[email protected]72f966b2009-10-14 20:02:27187
[email protected]b5cf844c2012-06-18 21:49:20188 // See if we need to scale the output.
Lei Zhang764461802017-11-15 23:36:07189 float scaling_factor = 1.0;
[email protected]b5cf844c2012-06-18 21:49:20190 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);
[email protected]f3cab622010-06-28 18:56:30199 }
[email protected]b5cf844c2012-06-18 21:49:20200 // 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.
Lei Zhang01a1d3c02019-05-21 04:59:10206 float x_offset =
207 params.center_horizontally
208 ? ((rect.size.width - (source_width * scaling_factor)) / 2)
209 : 0;
[email protected]b5cf844c2012-06-18 21:49:20210 if (rotate)
211 x_offset = -x_offset;
212
Lei Zhang01a1d3c02019-05-21 04:59:10213 float y_offset =
214 params.center_vertically
215 ? ((rect.size.height - (source_height * scaling_factor)) / 2)
216 : 0;
[email protected]b5cf844c2012-06-18 21:49:20217
[email protected]72f966b2009-10-14 20:02:27218 CGContextSaveGState(context);
[email protected]b5cf844c2012-06-18 21:49:20219
220 // The transform operations specified here gets applied in reverse order.
221 // i.e. the origin offset translation happens first.
222 // Origin is at bottom-left.
[email protected]f3cab622010-06-28 18:56:30223 CGContextTranslateCTM(context, x_offset, y_offset);
thestig23fe51772014-09-27 17:40:51224
225 int num_rotations = 0;
[email protected]b5cf844c2012-06-18 21:49:20226 if (rotate) {
thestig23fe51772014-09-27 17:40:51227 if (pdf_src_rotation == 0 || pdf_src_rotation == 270) {
228 num_rotations = 1;
229 } else {
230 num_rotations = 3;
231 }
232 } else {
233 if (pdf_src_rotation == 180 || pdf_src_rotation == 270) {
234 num_rotations = 2;
235 }
[email protected]b5cf844c2012-06-18 21:49:20236 }
thestig23fe51772014-09-27 17:40:51237 RotatePage(context, rect, num_rotations);
238
[email protected]f3cab622010-06-28 18:56:30239 CGContextScaleCTM(context, scaling_factor, scaling_factor);
[email protected]b5cf844c2012-06-18 21:49:20240 CGContextTranslateCTM(context, x_origin_offset, y_origin_offset);
241
[email protected]72f966b2009-10-14 20:02:27242 CGContextDrawPDFPage(context, pdf_page);
243 CGContextRestoreGState(context);
[email protected]b5cf844c2012-06-18 21:49:20244
[email protected]72f966b2009-10-14 20:02:27245 return true;
246}
247
[email protected]9b617532011-04-04 23:15:06248unsigned int PdfMetafileCg::GetPageCount() const {
[email protected]72f966b2009-10-14 20:02:27249 CGPDFDocumentRef pdf_doc = GetPDFDocument();
250 return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
251}
252
[email protected]9b617532011-04-04 23:15:06253gfx::Rect PdfMetafileCg::GetPageBounds(unsigned int page_number) const {
[email protected]72f966b2009-10-14 20:02:27254 CGPDFDocumentRef pdf_doc = GetPDFDocument();
255 if (!pdf_doc) {
256 LOG(ERROR) << "Unable to create PDF document from data";
257 return gfx::Rect();
258 }
259 if (page_number > GetPageCount()) {
260 LOG(ERROR) << "Invalid page number: " << page_number;
261 return gfx::Rect();
262 }
263 CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
264 CGRect page_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFMediaBox);
265 return gfx::Rect(page_rect);
266}
267
thestig707a24b22015-09-14 18:16:33268uint32_t PdfMetafileCg::GetDataSize() const {
[email protected]14b210792009-10-12 18:45:31269 // PDF data is only valid/complete once the context is released.
270 DCHECK(!context_);
271
272 if (!pdf_data_)
273 return 0;
thestig707a24b22015-09-14 18:16:33274 return static_cast<uint32_t>(CFDataGetLength(pdf_data_));
[email protected]14b210792009-10-12 18:45:31275}
276
thestig707a24b22015-09-14 18:16:33277bool PdfMetafileCg::GetData(void* dst_buffer, uint32_t dst_buffer_size) const {
[email protected]14b210792009-10-12 18:45:31278 // PDF data is only valid/complete once the context is released.
279 DCHECK(!context_);
280 DCHECK(pdf_data_);
281 DCHECK(dst_buffer);
282 DCHECK_GT(dst_buffer_size, 0U);
283
thestig707a24b22015-09-14 18:16:33284 uint32_t data_size = GetDataSize();
[email protected]14b210792009-10-12 18:45:31285 if (dst_buffer_size > data_size) {
286 return false;
287 }
288
289 CFDataGetBytes(pdf_data_, CFRangeMake(0, dst_buffer_size),
290 static_cast<UInt8*>(dst_buffer));
291 return true;
292}
293
[email protected]9b617532011-04-04 23:15:06294CGContextRef PdfMetafileCg::context() const {
[email protected]8f17cd3e2011-03-16 01:39:42295 return context_.get();
296}
297
[email protected]9b617532011-04-04 23:15:06298CGPDFDocumentRef PdfMetafileCg::GetPDFDocument() const {
[email protected]72f966b2009-10-14 20:02:27299 // Make sure that we have data, and that it's not being modified any more.
300 DCHECK(pdf_data_.get());
301 DCHECK(!context_.get());
302
303 if (!pdf_doc_.get()) {
[email protected]df0ca6c82010-10-17 04:09:06304 ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider(
[email protected]72f966b2009-10-14 20:02:27305 CGDataProviderCreateWithCFData(pdf_data_));
306 pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider));
307 }
308 return pdf_doc_.get();
309}
310
[email protected]14b210792009-10-12 18:45:31311} // namespace printing