blob: e9f16e05587197f2900091016135a9d4ddd9ad2b [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"
Morten Stenshorneeebf2db2020-05-25 09:00:0517#include "printing/mojom/print.mojom.h"
tfarina3b0452d2014-12-31 15:20:0918#include "ui/gfx/geometry/rect.h"
tfarinaebe974f02015-01-03 04:25:3219#include "ui/gfx/geometry/size.h"
[email protected]14b210792009-10-12 18:45:3120
[email protected]3df79f42013-06-24 18:49:0521using base::ScopedCFTypeRef;
[email protected]df0ca6c82010-10-17 04:09:0622
[email protected]df6df682011-05-02 19:50:5023namespace {
24
Daniel Hosseinian3553e272021-04-24 00:51:1825// Rotate a page by `num_rotations` * 90 degrees, counter-clockwise.
Lei Zhangf69ea3b2020-03-24 17:53:4826void RotatePage(CGContextRef context, const CGRect& rect, int num_rotations) {
thestig23fe51772014-09-27 17:40:5127 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.
Peter Kasting76ed0662017-09-14 08:28:0735 CGContextRotateCTM(context, base::kPiDouble / 2);
thestig23fe51772014-09-27 17:40:5136 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.
Peter Kasting76ed0662017-09-14 08:28:0743 CGContextRotateCTM(context, base::kPiDouble);
thestig23fe51772014-09-27 17:40:5144 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.
Peter Kasting76ed0662017-09-14 08:28:0750 CGContextRotateCTM(context, -base::kPiDouble / 2);
thestig23fe51772014-09-27 17:40:5151 break;
52 default:
53 NOTREACHED();
54 break;
thestig707a24b22015-09-14 18:16:3355 }
thestig23fe51772014-09-27 17:40:5156}
57
[email protected]df6df682011-05-02 19:50:5058} // namespace
59
[email protected]14b210792009-10-12 18:45:3160namespace printing {
61
Lei Zhangf69ea3b2020-03-24 17:53:4862PdfMetafileCg::PdfMetafileCg() = default;
[email protected]14b210792009-10-12 18:45:3163
Lei Zhangf69ea3b2020-03-24 17:53:4864PdfMetafileCg::~PdfMetafileCg() = default;
[email protected]1d20cdf2011-02-16 18:09:2865
[email protected]9b617532011-04-04 23:15:0666bool PdfMetafileCg::Init() {
[email protected]14b210792009-10-12 18:45:3167 // 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";
[email protected]8f17cd3e2011-03-16 01:39:4274 return false;
[email protected]14b210792009-10-12 18:45:3175 }
[email protected]df0ca6c82010-10-17 04:09:0676 ScopedCFTypeRef<CGDataConsumerRef> pdf_consumer(
[email protected]14b210792009-10-12 18:45:3177 CGDataConsumerCreateWithCFData(pdf_data_));
78 if (!pdf_consumer.get()) {
79 LOG(ERROR) << "Failed to create data consumer for metafile";
thestig3520b802016-04-04 22:12:5280 pdf_data_.reset();
[email protected]8f17cd3e2011-03-16 01:39:4281 return false;
[email protected]14b210792009-10-12 18:45:3182 }
thestig3520b802016-04-04 22:12:5283 context_.reset(CGPDFContextCreate(pdf_consumer, nullptr, nullptr));
[email protected]14b210792009-10-12 18:45:3184 if (!context_.get()) {
85 LOG(ERROR) << "Failed to create pdf context for metafile";
thestig3520b802016-04-04 22:12:5286 pdf_data_.reset();
[email protected]14b210792009-10-12 18:45:3187 }
88
[email protected]8f17cd3e2011-03-16 01:39:4289 return true;
[email protected]14b210792009-10-12 18:45:3190}
91
Lei Zhangb4ef0d5d2020-03-27 23:18:2992bool PdfMetafileCg::InitFromData(base::span<const uint8_t> data) {
[email protected]14b210792009-10-12 18:45:3193 DCHECK(!context_.get());
94 DCHECK(!pdf_data_.get());
95
Lei Zhangb4ef0d5d2020-03-27 23:18:2996 if (data.empty())
[email protected]14b210792009-10-12 18:45:3197 return false;
thestig6b4461f2016-10-28 19:34:3098
Lei Zhangb4ef0d5d2020-03-27 23:18:2999 if (!base::IsValueInRangeForNumericType<CFIndex>(data.size()))
thestig6b4461f2016-10-28 19:34:30100 return false;
[email protected]14b210792009-10-12 18:45:31101
Lei Zhangb4ef0d5d2020-03-27 23:18:29102 pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, data.size()));
103 CFDataAppendBytes(pdf_data_, data.data(), data.size());
[email protected]14b210792009-10-12 18:45:31104 return true;
105}
106
thestig192677ec2016-06-09 07:43:17107void PdfMetafileCg::StartPage(const gfx::Size& page_size,
[email protected]39892b92011-04-30 02:24:44108 const gfx::Rect& content_area,
Morten Stenshorneeebf2db2020-05-25 09:00:05109 float scale_factor,
110 mojom::PageOrientation page_orientation) {
111 DCHECK_EQ(page_orientation, mojom::PageOrientation::kUpright)
112 << "Not implemented";
[email protected]14b210792009-10-12 18:45:31113 DCHECK(context_.get());
114 DCHECK(!page_is_open_);
115
Lei Zhangf69ea3b2020-03-24 17:53:48116 page_is_open_ = true;
117 float height = page_size.height();
118 float width = page_size.width();
[email protected]15c2bb322010-12-21 22:30:10119
[email protected]14b210792009-10-12 18:45:31120 CGRect bounds = CGRectMake(0, 0, width, height);
121 CGContextBeginPage(context_, &bounds);
[email protected]14b210792009-10-12 18:45:31122 CGContextSaveGState(context_);
123
[email protected]9d42d0722011-07-26 19:22:29124 // Move to the context origin.
125 CGContextTranslateCTM(context_, content_area.x(), -content_area.y());
126
[email protected]14b210792009-10-12 18:45:31127 // Flip the context.
128 CGContextTranslateCTM(context_, 0, height);
129 CGContextScaleCTM(context_, scale_factor, -scale_factor);
130}
131
[email protected]9b617532011-04-04 23:15:06132bool PdfMetafileCg::FinishPage() {
[email protected]14b210792009-10-12 18:45:31133 DCHECK(context_.get());
134 DCHECK(page_is_open_);
135
136 CGContextRestoreGState(context_);
137 CGContextEndPage(context_);
138 page_is_open_ = false;
[email protected]8f17cd3e2011-03-16 01:39:42139 return true;
[email protected]14b210792009-10-12 18:45:31140}
141
[email protected]9b617532011-04-04 23:15:06142bool PdfMetafileCg::FinishDocument() {
[email protected]14b210792009-10-12 18:45:31143 DCHECK(context_.get());
144 DCHECK(!page_is_open_);
145
[email protected]72f966b2009-10-14 20:02:27146#ifndef NDEBUG
Daniel Hosseinian3553e272021-04-24 00:51:18147 // Check that the context will be torn down properly; if it's not, `pdf_data`
[email protected]72f966b2009-10-14 20:02:27148 // 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
[email protected]14619482010-04-01 16:46:23157 CGPDFContextClose(context_.get());
thestig3520b802016-04-04 22:12:52158 context_.reset();
[email protected]8f17cd3e2011-03-16 01:39:42159 return true;
[email protected]14b210792009-10-12 18:45:31160}
161
thestig99fc2132017-04-27 03:37:54162bool PdfMetafileCg::RenderPage(unsigned int page_number,
[email protected]9b617532011-04-04 23:15:06163 CGContextRef context,
Lei Zhangf69ea3b2020-03-24 17:53:48164 const CGRect& rect,
Daniel Hosseinian345ecfa2020-04-08 03:45:16165 bool autorotate,
166 bool fit_to_page) const {
thestig99fc2132017-04-27 03:37:54167 CGPDFDocumentRef pdf_doc = GetPDFDocument();
[email protected]72f966b2009-10-14 20:02:27168 if (!pdf_doc) {
169 LOG(ERROR) << "Unable to create PDF document from data";
170 return false;
171 }
Lei Zhang764461802017-11-15 23:36:07172
Lei Zhangc3f34662017-11-16 20:29:41173 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
[email protected]72f966b2009-10-14 20:02:27178 CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
[email protected]a09ef7e2011-09-28 21:59:33179 CGRect source_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox);
Daniel Hosseinianc3fd3d52020-04-08 04:03:17180 const int pdf_src_rotation = CGPDFPageGetRotationAngle(pdf_page);
[email protected]b5cf844c2012-06-18 21:49:20181 const bool source_is_landscape =
Lei Zhang01a1d3c02019-05-21 04:59:10182 (source_rect.size.width > source_rect.size.height);
[email protected]b5cf844c2012-06-18 21:49:20183 const bool dest_is_landscape = (rect.size.width > rect.size.height);
Daniel Hosseinian345ecfa2020-04-08 03:45:16184 const bool rotate = autorotate && (source_is_landscape != dest_is_landscape);
[email protected]b5cf844c2012-06-18 21:49:20185 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;
[email protected]72f966b2009-10-14 20:02:27189
[email protected]b5cf844c2012-06-18 21:49:20190 // See if we need to scale the output.
Lei Zhang764461802017-11-15 23:36:07191 float scaling_factor = 1.0;
[email protected]b5cf844c2012-06-18 21:49:20192 const bool scaling_needed =
Daniel Hosseinian345ecfa2020-04-08 03:45:16193 fit_to_page && ((source_width != rect.size.width) ||
194 (source_height != rect.size.height));
[email protected]b5cf844c2012-06-18 21:49:20195 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
[email protected]72f966b2009-10-14 20:02:27201 CGContextSaveGState(context);
[email protected]b5cf844c2012-06-18 21:49:20202
thestig23fe51772014-09-27 17:40:51203 int num_rotations = 0;
[email protected]b5cf844c2012-06-18 21:49:20204 if (rotate) {
thestig23fe51772014-09-27 17:40:51205 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 }
[email protected]b5cf844c2012-06-18 21:49:20214 }
thestig23fe51772014-09-27 17:40:51215 RotatePage(context, rect, num_rotations);
216
[email protected]f3cab622010-06-28 18:56:30217 CGContextScaleCTM(context, scaling_factor, scaling_factor);
Daniel Hosseinianc3fd3d52020-04-08 04:03:17218
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;
[email protected]b5cf844c2012-06-18 21:49:20236 CGContextTranslateCTM(context, x_origin_offset, y_origin_offset);
237
[email protected]72f966b2009-10-14 20:02:27238 CGContextDrawPDFPage(context, pdf_page);
239 CGContextRestoreGState(context);
[email protected]b5cf844c2012-06-18 21:49:20240
[email protected]72f966b2009-10-14 20:02:27241 return true;
242}
243
[email protected]9b617532011-04-04 23:15:06244unsigned int PdfMetafileCg::GetPageCount() const {
[email protected]72f966b2009-10-14 20:02:27245 CGPDFDocumentRef pdf_doc = GetPDFDocument();
246 return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
247}
248
[email protected]9b617532011-04-04 23:15:06249gfx::Rect PdfMetafileCg::GetPageBounds(unsigned int page_number) const {
[email protected]72f966b2009-10-14 20:02:27250 CGPDFDocumentRef pdf_doc = GetPDFDocument();
251 if (!pdf_doc) {
252 LOG(ERROR) << "Unable to create PDF document from data";
253 return gfx::Rect();
254 }
Lei Zhangd67a66b22020-03-24 23:31:31255 if (page_number == 0 || page_number > GetPageCount()) {
[email protected]72f966b2009-10-14 20:02:27256 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
thestig707a24b22015-09-14 18:16:33264uint32_t PdfMetafileCg::GetDataSize() const {
[email protected]14b210792009-10-12 18:45:31265 // PDF data is only valid/complete once the context is released.
266 DCHECK(!context_);
267
268 if (!pdf_data_)
269 return 0;
thestig707a24b22015-09-14 18:16:33270 return static_cast<uint32_t>(CFDataGetLength(pdf_data_));
[email protected]14b210792009-10-12 18:45:31271}
272
thestig707a24b22015-09-14 18:16:33273bool PdfMetafileCg::GetData(void* dst_buffer, uint32_t dst_buffer_size) const {
[email protected]14b210792009-10-12 18:45:31274 // 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
thestig707a24b22015-09-14 18:16:33280 uint32_t data_size = GetDataSize();
[email protected]14b210792009-10-12 18:45:31281 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
Alan Screene35d2632021-12-02 18:44:43290bool PdfMetafileCg::ShouldCopySharedMemoryRegionData() const {
291 // Since `InitFromData()` copies the data, the caller doesn't have to.
292 return false;
293}
294
Alan Screen313f5082021-09-15 21:16:53295mojom::MetafileDataType PdfMetafileCg::GetDataType() const {
296 return mojom::MetafileDataType::kPDF;
297}
298
[email protected]9b617532011-04-04 23:15:06299CGContextRef PdfMetafileCg::context() const {
[email protected]8f17cd3e2011-03-16 01:39:42300 return context_.get();
301}
302
[email protected]9b617532011-04-04 23:15:06303CGPDFDocumentRef PdfMetafileCg::GetPDFDocument() const {
[email protected]72f966b2009-10-14 20:02:27304 // 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()) {
[email protected]df0ca6c82010-10-17 04:09:06309 ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider(
[email protected]72f966b2009-10-14 20:02:27310 CGDataProviderCreateWithCFData(pdf_data_));
311 pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider));
312 }
313 return pdf_doc_.get();
314}
315
[email protected]14b210792009-10-12 18:45:31316} // namespace printing