1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
|
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "printing/pdf_metafile_cg_mac.h"
#include <stdint.h>
#include <algorithm>
#include "base/apple/scoped_cftyperef.h"
#include "base/logging.h"
#include "base/mac/mac_util.h"
#include "base/numerics/math_constants.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/sys_string_conversions.h"
#include "printing/mojom/print.mojom.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
using base::apple::ScopedCFTypeRef;
namespace {
// Rotate a page by `num_rotations` * 90 degrees, counter-clockwise.
void RotatePage(CGContextRef context, const CGRect& rect, int num_rotations) {
switch (num_rotations) {
case 0:
break;
case 1:
// After rotating by 90 degrees with the axis at the origin, the page
// content is now "off screen". Shift it right to move it back on screen.
CGContextTranslateCTM(context, rect.size.width, 0);
// Rotates counter-clockwise by 90 degrees.
CGContextRotateCTM(context, base::kPiDouble / 2);
break;
case 2:
// After rotating by 180 degrees with the axis at the origin, the page
// content is now "off screen". Shift it right and up to move it back on
// screen.
CGContextTranslateCTM(context, rect.size.width, rect.size.height);
// Rotates counter-clockwise by 90 degrees.
CGContextRotateCTM(context, base::kPiDouble);
break;
case 3:
// After rotating by 270 degrees with the axis at the origin, the page
// content is now "off screen". Shift it right to move it back on screen.
CGContextTranslateCTM(context, 0, rect.size.height);
// Rotates counter-clockwise by 90 degrees.
CGContextRotateCTM(context, -base::kPiDouble / 2);
break;
default:
NOTREACHED();
break;
}
}
} // namespace
namespace printing {
PdfMetafileCg::PdfMetafileCg() = default;
PdfMetafileCg::~PdfMetafileCg() = default;
bool PdfMetafileCg::Init() {
// Ensure that Init hasn't already been called.
DCHECK(!context_.get());
DCHECK(!pdf_data_.get());
pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, 0));
if (!pdf_data_.get()) {
LOG(ERROR) << "Failed to create pdf data for metafile";
return false;
}
ScopedCFTypeRef<CGDataConsumerRef> pdf_consumer(
CGDataConsumerCreateWithCFData(pdf_data_.get()));
if (!pdf_consumer.get()) {
LOG(ERROR) << "Failed to create data consumer for metafile";
pdf_data_.reset();
return false;
}
context_.reset(CGPDFContextCreate(pdf_consumer.get(), nullptr, nullptr));
if (!context_.get()) {
LOG(ERROR) << "Failed to create pdf context for metafile";
pdf_data_.reset();
}
return true;
}
bool PdfMetafileCg::InitFromData(base::span<const uint8_t> data) {
DCHECK(!context_.get());
DCHECK(!pdf_data_.get());
if (data.empty())
return false;
if (!base::IsValueInRangeForNumericType<CFIndex>(data.size()))
return false;
pdf_data_.reset(CFDataCreateMutable(kCFAllocatorDefault, data.size()));
CFDataAppendBytes(pdf_data_.get(), data.data(), data.size());
return true;
}
void PdfMetafileCg::StartPage(const gfx::Size& page_size,
const gfx::Rect& content_area,
float scale_factor,
mojom::PageOrientation page_orientation) {
DCHECK_EQ(page_orientation, mojom::PageOrientation::kUpright)
<< "Not implemented";
DCHECK(context_.get());
DCHECK(!page_is_open_);
page_is_open_ = true;
float height = page_size.height();
float width = page_size.width();
CGRect bounds = CGRectMake(0, 0, width, height);
CGContextBeginPage(context_.get(), &bounds);
CGContextSaveGState(context_.get());
// Move to the context origin.
CGContextTranslateCTM(context_.get(), content_area.x(), -content_area.y());
// Flip the context.
CGContextTranslateCTM(context_.get(), 0, height);
CGContextScaleCTM(context_.get(), scale_factor, -scale_factor);
}
bool PdfMetafileCg::FinishPage() {
DCHECK(context_);
DCHECK(page_is_open_);
CGContextRestoreGState(context_.get());
CGContextEndPage(context_.get());
page_is_open_ = false;
return true;
}
bool PdfMetafileCg::FinishDocument() {
DCHECK(context_.get());
DCHECK(!page_is_open_);
#ifndef NDEBUG
// Check that the context will be torn down properly; if it's not, `pdf_data`
// will be incomplete and generate invalid PDF files/documents.
if (context_.get()) {
CFIndex extra_retain_count = CFGetRetainCount(context_.get()) - 1;
if (extra_retain_count > 0) {
LOG(ERROR) << "Metafile context has " << extra_retain_count
<< " extra retain(s) on Close";
}
}
#endif
CGPDFContextClose(context_.get());
context_.reset();
return true;
}
bool PdfMetafileCg::RenderPage(unsigned int page_number,
CGContextRef context,
const CGRect& rect,
bool autorotate,
bool fit_to_page) const {
CGPDFDocumentRef pdf_doc = GetPDFDocument();
if (!pdf_doc) {
LOG(ERROR) << "Unable to create PDF document from data";
return false;
}
const unsigned int page_count = GetPageCount();
DCHECK_NE(page_count, 0U);
DCHECK_NE(page_number, 0U);
DCHECK_LE(page_number, page_count);
CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
CGRect source_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFCropBox);
const int pdf_src_rotation = CGPDFPageGetRotationAngle(pdf_page);
const bool source_is_landscape =
(source_rect.size.width > source_rect.size.height);
const bool dest_is_landscape = (rect.size.width > rect.size.height);
const bool rotate = autorotate && (source_is_landscape != dest_is_landscape);
const float source_width =
rotate ? source_rect.size.height : source_rect.size.width;
const float source_height =
rotate ? source_rect.size.width : source_rect.size.height;
// See if we need to scale the output.
float scaling_factor = 1.0;
const bool scaling_needed =
fit_to_page && ((source_width != rect.size.width) ||
(source_height != rect.size.height));
if (scaling_needed) {
float x_scaling_factor = rect.size.width / source_width;
float y_scaling_factor = rect.size.height / source_height;
scaling_factor = std::min(x_scaling_factor, y_scaling_factor);
}
CGContextSaveGState(context);
int num_rotations = 0;
if (rotate) {
if (pdf_src_rotation == 0 || pdf_src_rotation == 270) {
num_rotations = 1;
} else {
num_rotations = 3;
}
} else {
if (pdf_src_rotation == 180 || pdf_src_rotation == 270) {
num_rotations = 2;
}
}
RotatePage(context, rect, num_rotations);
CGContextScaleCTM(context, scaling_factor, scaling_factor);
// Some PDFs have a non-zero origin. Need to take that into account and align
// the PDF to the CoreGraphics's coordinate system origin. Also realign the
// contents from the bottom-left of the page to top-left in order to stay
// consistent with Print Preview.
// A rotational vertical offset is calculated to determine how much to offset
// the y-component of the origin to move the origin from bottom-left to
// top-right. When the source is not rotated, the offset is simply the
// difference between the paper height and the source height. When rotated,
// the y-axis of the source falls along the width of the source and paper, so
// the offset becomes the difference between the paper width and the source
// width.
const float rotational_vertical_offset =
rotate ? (rect.size.width - (scaling_factor * source_width))
: (rect.size.height - (scaling_factor * source_height));
const float x_origin_offset = -1 * source_rect.origin.x;
const float y_origin_offset =
rotational_vertical_offset - source_rect.origin.y;
CGContextTranslateCTM(context, x_origin_offset, y_origin_offset);
CGContextDrawPDFPage(context, pdf_page);
CGContextRestoreGState(context);
return true;
}
unsigned int PdfMetafileCg::GetPageCount() const {
CGPDFDocumentRef pdf_doc = GetPDFDocument();
return pdf_doc ? CGPDFDocumentGetNumberOfPages(pdf_doc) : 0;
}
gfx::Rect PdfMetafileCg::GetPageBounds(unsigned int page_number) const {
CGPDFDocumentRef pdf_doc = GetPDFDocument();
if (!pdf_doc) {
LOG(ERROR) << "Unable to create PDF document from data";
return gfx::Rect();
}
if (page_number == 0 || page_number > GetPageCount()) {
LOG(ERROR) << "Invalid page number: " << page_number;
return gfx::Rect();
}
CGPDFPageRef pdf_page = CGPDFDocumentGetPage(pdf_doc, page_number);
CGRect page_rect = CGPDFPageGetBoxRect(pdf_page, kCGPDFMediaBox);
return gfx::Rect(page_rect);
}
uint32_t PdfMetafileCg::GetDataSize() const {
// PDF data is only valid/complete once the context is released.
DCHECK(!context_);
if (!pdf_data_)
return 0;
return static_cast<uint32_t>(CFDataGetLength(pdf_data_.get()));
}
bool PdfMetafileCg::GetData(void* dst_buffer, uint32_t dst_buffer_size) const {
// PDF data is only valid/complete once the context is released.
DCHECK(!context_);
DCHECK(pdf_data_);
DCHECK(dst_buffer);
DCHECK_GT(dst_buffer_size, 0U);
uint32_t data_size = GetDataSize();
if (dst_buffer_size > data_size) {
return false;
}
CFDataGetBytes(pdf_data_.get(), CFRangeMake(0, dst_buffer_size),
static_cast<UInt8*>(dst_buffer));
return true;
}
bool PdfMetafileCg::ShouldCopySharedMemoryRegionData() const {
// Since `InitFromData()` copies the data, the caller doesn't have to.
return false;
}
mojom::MetafileDataType PdfMetafileCg::GetDataType() const {
return mojom::MetafileDataType::kPDF;
}
CGContextRef PdfMetafileCg::context() const {
return context_.get();
}
CGPDFDocumentRef PdfMetafileCg::GetPDFDocument() const {
// Make sure that we have data, and that it's not being modified any more.
DCHECK(pdf_data_.get());
DCHECK(!context_.get());
if (!pdf_doc_.get()) {
ScopedCFTypeRef<CGDataProviderRef> pdf_data_provider(
CGDataProviderCreateWithCFData(pdf_data_.get()));
pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider.get()));
}
return pdf_doc_.get();
}
} // namespace printing
|