1/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6#include "PrintTargetCG.h"
7
8#include "cairo.h"
9#include "cairo-quartz.h"
10#include "mozilla/gfx/HelpersCairo.h"
11#include "nsObjCExceptions.h"
12#include "nsString.h"
13
14namespace mozilla {
15namespace gfx {
16
17PrintTargetCG::PrintTargetCG(PMPrintSession aPrintSession, PMPageFormat aPageFormat,
18                             PMPrintSettings aPrintSettings, const IntSize& aSize)
19    : PrintTarget(/* aCairoSurface */ nullptr, aSize),
20      mPrintSession(aPrintSession),
21      mPageFormat(aPageFormat),
22      mPrintSettings(aPrintSettings) {
23  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
24
25  MOZ_ASSERT(mPrintSession && mPageFormat && mPrintSettings);
26
27  ::PMRetain(mPrintSession);
28  ::PMRetain(mPageFormat);
29  ::PMRetain(mPrintSettings);
30
31  // TODO: Add memory reporting like gfxQuartzSurface.
32  // RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface));
33
34  NS_OBJC_END_TRY_IGNORE_BLOCK;
35}
36
37PrintTargetCG::~PrintTargetCG() {
38  NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
39
40  ::PMRelease(mPrintSession);
41  ::PMRelease(mPageFormat);
42  ::PMRelease(mPrintSettings);
43
44  NS_OBJC_END_TRY_IGNORE_BLOCK;
45}
46
47/* static */ already_AddRefed<PrintTargetCG> PrintTargetCG::CreateOrNull(
48    PMPrintSession aPrintSession, PMPageFormat aPageFormat, PMPrintSettings aPrintSettings,
49    const IntSize& aSize) {
50  if (!Factory::CheckSurfaceSize(aSize)) {
51    return nullptr;
52  }
53
54  RefPtr<PrintTargetCG> target =
55      new PrintTargetCG(aPrintSession, aPageFormat, aPrintSettings, aSize);
56
57  return target.forget();
58}
59
60static size_t PutBytesNull(void* info, const void* buffer, size_t count) { return count; }
61
62already_AddRefed<DrawTarget> PrintTargetCG::GetReferenceDrawTarget() {
63  if (!mRefDT) {
64    const IntSize size(1, 1);
65
66    CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr};
67    CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks);
68    CGContextRef pdfContext = CGPDFContextCreate(consumer, nullptr, nullptr);
69    CGDataConsumerRelease(consumer);
70
71    cairo_surface_t* similar =
72        cairo_quartz_surface_create_for_cg_context(pdfContext, size.width, size.height);
73
74    CGContextRelease(pdfContext);
75
76    if (cairo_surface_status(similar)) {
77      return nullptr;
78    }
79
80    RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForCairoSurface(similar, size);
81
82    // The DT addrefs the surface, so we need drop our own reference to it:
83    cairo_surface_destroy(similar);
84
85    if (!dt || !dt->IsValid()) {
86      return nullptr;
87    }
88    mRefDT = dt.forget();
89  }
90
91  return do_AddRef(mRefDT);
92}
93
94nsresult PrintTargetCG::BeginPrinting(const nsAString& aTitle, const nsAString& aPrintToFileName,
95                                      int32_t aStartPage, int32_t aEndPage) {
96  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
97
98  // Print Core of Application Service sent print job with names exceeding
99  // 255 bytes. This is a workaround until fix it.
100  // (https://openradar.appspot.com/34428043)
101  nsAutoString adjustedTitle;
102  PrintTarget::AdjustPrintJobNameForIPP(aTitle, adjustedTitle);
103
104  if (!adjustedTitle.IsEmpty()) {
105    CFStringRef cfString = ::CFStringCreateWithCharacters(
106        NULL, reinterpret_cast<const UniChar*>(adjustedTitle.BeginReading()),
107        adjustedTitle.Length());
108    if (cfString) {
109      ::PMPrintSettingsSetJobName(mPrintSettings, cfString);
110      ::CFRelease(cfString);
111    }
112  }
113
114  OSStatus status;
115  status = ::PMSetFirstPage(mPrintSettings, aStartPage, false);
116  NS_ASSERTION(status == noErr, "PMSetFirstPage failed");
117  status = ::PMSetLastPage(mPrintSettings, aEndPage, false);
118  NS_ASSERTION(status == noErr, "PMSetLastPage failed");
119
120  status = ::PMSessionBeginCGDocumentNoDialog(mPrintSession, mPrintSettings, mPageFormat);
121
122  return status == noErr ? NS_OK : NS_ERROR_ABORT;
123
124  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
125}
126
127nsresult PrintTargetCG::EndPrinting() {
128  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
129
130  ::PMSessionEndDocumentNoDialog(mPrintSession);
131  return NS_OK;
132
133  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
134}
135
136nsresult PrintTargetCG::AbortPrinting() {
137#ifdef DEBUG
138  mHasActivePage = false;
139#endif
140  return EndPrinting();
141}
142
143nsresult PrintTargetCG::BeginPage() {
144  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
145
146  PMSessionError(mPrintSession);
147  OSStatus status = ::PMSessionBeginPageNoDialog(mPrintSession, mPageFormat, NULL);
148  if (status != noErr) {
149    return NS_ERROR_ABORT;
150  }
151
152  CGContextRef context;
153  // This call will fail if it wasn't called between the PMSessionBeginPage/
154  // PMSessionEndPage calls:
155  ::PMSessionGetCGGraphicsContext(mPrintSession, &context);
156
157  if (!context) {
158    return NS_ERROR_FAILURE;
159  }
160
161  unsigned int width = static_cast<unsigned int>(mSize.width);
162  unsigned int height = static_cast<unsigned int>(mSize.height);
163
164  // Initially, origin is at bottom-left corner of the paper.
165  // Here, we translate it to top-left corner of the paper.
166  CGContextTranslateCTM(context, 0, height);
167  CGContextScaleCTM(context, 1.0, -1.0);
168
169  cairo_surface_t* surface = cairo_quartz_surface_create_for_cg_context(context, width, height);
170
171  if (cairo_surface_status(surface)) {
172    return NS_ERROR_FAILURE;
173  }
174
175  mCairoSurface = surface;
176
177  return PrintTarget::BeginPage();
178
179  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
180}
181
182nsresult PrintTargetCG::EndPage() {
183  NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
184
185  cairo_surface_finish(mCairoSurface);
186  mCairoSurface = nullptr;
187
188  OSStatus status = ::PMSessionEndPageNoDialog(mPrintSession);
189  if (status != noErr) {
190    return NS_ERROR_ABORT;
191  }
192
193  return PrintTarget::EndPage();
194
195  NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
196}
197
198}  // namespace gfx
199}  // namespace mozilla
200