123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- // Copyright (c) 2012 The Chromium Authors. All rights reserved.
- // 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/logging.h"
- #include "base/mac/mac_util.h"
- #include "base/mac/scoped_cftyperef.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::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_));
- if (!pdf_consumer.get()) {
- LOG(ERROR) << "Failed to create data consumer for metafile";
- pdf_data_.reset();
- return false;
- }
- context_.reset(CGPDFContextCreate(pdf_consumer, 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_, 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_, &bounds);
- CGContextSaveGState(context_);
- // Move to the context origin.
- CGContextTranslateCTM(context_, content_area.x(), -content_area.y());
- // Flip the context.
- CGContextTranslateCTM(context_, 0, height);
- CGContextScaleCTM(context_, scale_factor, -scale_factor);
- }
- bool PdfMetafileCg::FinishPage() {
- DCHECK(context_.get());
- DCHECK(page_is_open_);
- CGContextRestoreGState(context_);
- CGContextEndPage(context_);
- 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_));
- }
- 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_, 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_));
- pdf_doc_.reset(CGPDFDocumentCreateWithProvider(pdf_data_provider));
- }
- return pdf_doc_.get();
- }
- } // namespace printing
|