pdf_metafile_cg_mac.cc 10 KB

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