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/. */
6 #include "PrintTargetCG.h"
9 #include "cairo-quartz.h"
10 #include "mozilla/gfx/HelpersCairo.h"
11 #include "mozilla/StaticPrefs_print.h"
12 #include "nsObjCExceptions.h"
14 #include "nsIOutputStream.h"
16 namespace mozilla::gfx {
18 static size_t PutBytesNull(void* info, const void* buffer, size_t count) {
22 PrintTargetCG::PrintTargetCG(CGContextRef aPrintToStreamContext,
23 PMPrintSession aPrintSession,
24 PMPageFormat aPageFormat,
25 PMPrintSettings aPrintSettings,
27 : PrintTarget(/* aCairoSurface */ nullptr, aSize),
28 mPrintToStreamContext(aPrintToStreamContext),
29 mPrintSession(aPrintSession),
30 mPageFormat(aPageFormat),
31 mPrintSettings(aPrintSettings) {
32 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
34 MOZ_ASSERT(mPrintSession && mPageFormat && mPrintSettings);
36 ::PMRetain(mPrintSession);
37 ::PMRetain(mPageFormat);
38 ::PMRetain(mPrintSettings);
40 // TODO: Add memory reporting like gfxQuartzSurface.
41 // RecordMemoryUsed(mSize.height * 4 + sizeof(gfxQuartzSurface));
43 NS_OBJC_END_TRY_IGNORE_BLOCK;
46 PrintTargetCG::~PrintTargetCG() {
47 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
49 ::PMRelease(mPrintSession);
50 ::PMRelease(mPageFormat);
51 ::PMRelease(mPrintSettings);
53 if (mPrintToStreamContext) {
54 CGContextRelease(mPrintToStreamContext);
57 NS_OBJC_END_TRY_IGNORE_BLOCK;
60 static size_t WriteStreamBytes(void* aInfo, const void* aBuffer,
62 auto* stream = static_cast<nsIOutputStream*>(aInfo);
63 auto* data = static_cast<const char*>(aBuffer);
64 size_t remaining = aCount;
67 // Handle potential narrowing from size_t to uint32_t.
68 uint32_t toWrite = uint32_t(
69 std::min(remaining, size_t(std::numeric_limits<uint32_t>::max())));
70 if (NS_WARN_IF(NS_FAILED(stream->Write(data, toWrite, &wrote)))) {
74 remaining -= size_t(wrote);
79 static void ReleaseStream(void* aInfo) {
80 auto* stream = static_cast<nsIOutputStream*>(aInfo);
85 static CGContextRef CreatePrintToStreamContext(nsIOutputStream* aOutputStream,
86 const IntSize& aSize) {
87 MOZ_ASSERT(aOutputStream);
89 NS_ADDREF(aOutputStream); // Matched by the NS_RELEASE in ReleaseStream.
91 CGRect pageBox{{0.0, 0.0}, {CGFloat(aSize.width), CGFloat(aSize.height)}};
92 CGDataConsumerCallbacks callbacks = {WriteStreamBytes, ReleaseStream};
93 CGDataConsumerRef consumer = CGDataConsumerCreate(aOutputStream, &callbacks);
95 // This metadata is added by the CorePrinting APIs in the non-stream case.
96 NSString* bundleName = [NSBundle.mainBundle.localizedInfoDictionary
97 objectForKey:(NSString*)kCFBundleNameKey];
98 CFMutableDictionaryRef auxiliaryInfo = CFDictionaryCreateMutable(
99 kCFAllocatorDefault, 1, &kCFTypeDictionaryKeyCallBacks,
100 &kCFTypeDictionaryValueCallBacks);
101 CFDictionaryAddValue(auxiliaryInfo, kCGPDFContextCreator,
102 (__bridge CFStringRef)bundleName);
104 CGContextRef pdfContext =
105 CGPDFContextCreate(consumer, &pageBox, auxiliaryInfo);
106 CGDataConsumerRelease(consumer);
107 CFRelease(auxiliaryInfo);
111 /* static */ already_AddRefed<PrintTargetCG> PrintTargetCG::CreateOrNull(
112 nsIOutputStream* aOutputStream, PMPrintSession aPrintSession,
113 PMPageFormat aPageFormat, PMPrintSettings aPrintSettings,
114 const IntSize& aSize) {
115 if (!Factory::CheckSurfaceSize(aSize)) {
119 CGContextRef printToStreamContext = nullptr;
121 printToStreamContext = CreatePrintToStreamContext(aOutputStream, aSize);
122 if (!printToStreamContext) {
127 RefPtr<PrintTargetCG> target = new PrintTargetCG(
128 printToStreamContext, aPrintSession, aPageFormat, aPrintSettings, aSize);
130 return target.forget();
133 already_AddRefed<DrawTarget> PrintTargetCG::GetReferenceDrawTarget() {
135 const IntSize size(1, 1);
137 CGDataConsumerCallbacks callbacks = {PutBytesNull, nullptr};
138 CGDataConsumerRef consumer = CGDataConsumerCreate(nullptr, &callbacks);
139 CGContextRef pdfContext = CGPDFContextCreate(consumer, nullptr, nullptr);
140 CGDataConsumerRelease(consumer);
142 cairo_surface_t* similar = cairo_quartz_surface_create_for_cg_context(
143 pdfContext, size.width, size.height);
145 CGContextRelease(pdfContext);
147 if (cairo_surface_status(similar)) {
151 RefPtr<DrawTarget> dt =
152 Factory::CreateDrawTargetForCairoSurface(similar, size);
154 // The DT addrefs the surface, so we need drop our own reference to it:
155 cairo_surface_destroy(similar);
157 if (!dt || !dt->IsValid()) {
160 mRefDT = dt.forget();
163 return do_AddRef(mRefDT);
166 nsresult PrintTargetCG::BeginPrinting(const nsAString& aTitle,
167 const nsAString& aPrintToFileName,
168 int32_t aStartPage, int32_t aEndPage) {
169 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
171 if (mPrintToStreamContext) {
175 // Print Core of Application Service sent print job with names exceeding
176 // 255 bytes. This is a workaround until fix it.
177 // (https://openradar.appspot.com/34428043)
178 nsAutoString adjustedTitle;
179 PrintTarget::AdjustPrintJobNameForIPP(aTitle, adjustedTitle);
181 if (!adjustedTitle.IsEmpty()) {
182 CFStringRef cfString = ::CFStringCreateWithCharacters(
183 NULL, reinterpret_cast<const UniChar*>(adjustedTitle.BeginReading()),
184 adjustedTitle.Length());
186 ::PMPrintSettingsSetJobName(mPrintSettings, cfString);
187 ::CFRelease(cfString);
192 status = ::PMSetFirstPage(mPrintSettings, aStartPage, false);
193 NS_ASSERTION(status == noErr, "PMSetFirstPage failed");
194 status = ::PMSetLastPage(mPrintSettings, aEndPage, false);
195 NS_ASSERTION(status == noErr, "PMSetLastPage failed");
197 status = ::PMSessionBeginCGDocumentNoDialog(mPrintSession, mPrintSettings,
200 return status == noErr ? NS_OK : NS_ERROR_ABORT;
202 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
205 nsresult PrintTargetCG::EndPrinting() {
206 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
208 if (mPrintToStreamContext) {
209 CGContextFlush(mPrintToStreamContext);
210 CGPDFContextClose(mPrintToStreamContext);
214 ::PMSessionEndDocumentNoDialog(mPrintSession);
217 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
220 nsresult PrintTargetCG::AbortPrinting() {
222 mHasActivePage = false;
224 return EndPrinting();
227 nsresult PrintTargetCG::BeginPage(const IntSize& aSizeInPoints) {
228 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
233 print_save_as_pdf_use_page_rule_size_as_paper_size_enabled()) {
234 width = static_cast<unsigned int>(aSizeInPoints.width);
235 height = static_cast<unsigned int>(aSizeInPoints.height);
237 width = static_cast<unsigned int>(mSize.width);
238 height = static_cast<unsigned int>(mSize.height);
241 CGContextRef context;
242 if (mPrintToStreamContext) {
243 CGRect bounds = CGRectMake(0, 0, width, height);
244 CGContextBeginPage(mPrintToStreamContext, &bounds);
245 context = mPrintToStreamContext;
247 // XXX Why are we calling this if we don't check the return value?
248 PMSessionError(mPrintSession);
250 // XXX For mixed sheet sizes that aren't simply an orientation switch, we
251 // will want to be able to pass a sheet size here, using something like:
252 // PMRect bounds = { 0, 0, double(height), double(width) };
253 // But the docs for PMSessionBeginPageNoDialog's `pageFrame` parameter say:
254 // "You should pass NULL, as this parameter is currentlyunsupported."
255 // https://developer.apple.com/documentation/applicationservices/1463416-pmsessionbeginpagenodialog?language=objc
256 // And indeed, it doesn't appear to do anything.
257 // (It seems weird that CGContextBeginPage (above) supports passing a rect,
258 // and that that works for setting sheet sizes in PDF output, but the Core
259 // Printing API does not.)
260 // We can always switch to PrintTargetPDF - we use that for Windows/Linux
261 // anyway. But Core Graphics output is better than Cairo's in some cases.
263 // For now, we support switching sheet orientation only:
265 print_save_as_pdf_use_page_rule_size_as_paper_size_enabled()) {
266 ::PMOrientation pageOrientation =
267 width < height ? kPMPortrait : kPMLandscape;
268 ::PMSetOrientation(mPageFormat, pageOrientation, kPMUnlocked);
269 // We don't need to reset the orientation, since we set it for every page.
272 ::PMSessionBeginPageNoDialog(mPrintSession, mPageFormat, nullptr);
273 if (status != noErr) {
274 return NS_ERROR_ABORT;
277 // This call will fail if it wasn't called between the PMSessionBeginPage/
278 // PMSessionEndPage calls:
279 ::PMSessionGetCGGraphicsContext(mPrintSession, &context);
282 return NS_ERROR_FAILURE;
286 // Initially, origin is at bottom-left corner of the paper.
287 // Here, we translate it to top-left corner of the paper.
288 CGContextTranslateCTM(context, 0, height);
289 CGContextScaleCTM(context, 1.0, -1.0);
291 cairo_surface_t* surface =
292 cairo_quartz_surface_create_for_cg_context(context, width, height);
294 if (cairo_surface_status(surface)) {
295 return NS_ERROR_FAILURE;
298 mCairoSurface = surface;
300 return PrintTarget::BeginPage(aSizeInPoints);
302 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
305 nsresult PrintTargetCG::EndPage() {
306 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
308 cairo_surface_finish(mCairoSurface);
309 mCairoSurface = nullptr;
311 if (mPrintToStreamContext) {
312 CGContextEndPage(mPrintToStreamContext);
314 OSStatus status = ::PMSessionEndPageNoDialog(mPrintSession);
315 if (status != noErr) {
316 return NS_ERROR_ABORT;
320 return PrintTarget::EndPage();
322 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
325 } // namespace mozilla::gfx