1 /* -*- Mode: C++; tab-width: 4; 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 "nsDeviceContextSpecX.h"
8 #import <Cocoa/Cocoa.h>
9 #include "mozilla/gfx/PrintPromise.h"
10 #include <CoreFoundation/CoreFoundation.h>
13 #ifdef MOZ_ENABLE_SKIA_PDF
14 # include "mozilla/gfx/PrintTargetSkPDF.h"
16 #include "mozilla/gfx/PrintTargetCG.h"
17 #include "mozilla/Logging.h"
18 #include "mozilla/Preferences.h"
19 #include "mozilla/RefPtr.h"
20 #include "mozilla/Telemetry.h"
22 #include "AppleUtils.h"
23 #include "nsCocoaUtils.h"
25 #include "nsCUPSShim.h"
26 #include "nsDirectoryServiceDefs.h"
27 #include "nsILocalFileMac.h"
28 #include "nsIOutputStream.h"
30 #include "nsPrinterListCUPS.h"
31 #include "nsPrintSettingsX.h"
32 #include "nsQueryObject.h"
35 // This must be the last include:
36 #include "nsObjCExceptions.h"
38 using namespace mozilla;
39 using mozilla::gfx::IntSize;
40 using mozilla::gfx::PrintEndDocumentPromise;
41 using mozilla::gfx::PrintTarget;
42 using mozilla::gfx::PrintTargetCG;
43 #ifdef MOZ_ENABLE_SKIA_PDF
44 using mozilla::gfx::PrintTargetSkPDF;
47 //----------------------------------------------------------------------
48 // nsDeviceContentSpecX
50 nsDeviceContextSpecX::nsDeviceContextSpecX() = default;
52 nsDeviceContextSpecX::~nsDeviceContextSpecX() {
53 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
56 ::PMRelease(mPrintSession);
59 ::PMRelease(mPageFormat);
61 if (mPMPrintSettings) {
62 ::PMRelease(mPMPrintSettings);
65 NS_OBJC_END_TRY_IGNORE_BLOCK;
68 NS_IMPL_ISUPPORTS(nsDeviceContextSpecX, nsIDeviceContextSpec)
70 NS_IMETHODIMP nsDeviceContextSpecX::Init(nsIPrintSettings* aPS,
71 bool aIsPrintPreview) {
72 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
74 RefPtr<nsPrintSettingsX> settings(do_QueryObject(aPS));
76 return NS_ERROR_NO_INTERFACE;
78 // Note: unlike other platforms, we don't set our base class's mPrintSettings
79 // here since we don't need it currently (we do set mPMPrintSettings below).
81 NSPrintInfo* printInfo = settings->CreateOrCopyPrintInfo();
83 return NS_ERROR_FAILURE;
85 if (aPS->GetOutputDestination() ==
86 nsIPrintSettings::kOutputDestinationStream) {
87 aPS->GetOutputStream(getter_AddRefs(mOutputStream));
89 return NS_ERROR_FAILURE;
92 mPrintSession = static_cast<PMPrintSession>([printInfo PMPrintSession]);
93 mPageFormat = static_cast<PMPageFormat>([printInfo PMPageFormat]);
94 mPMPrintSettings = static_cast<PMPrintSettings>([printInfo PMPrintSettings]);
95 MOZ_ASSERT(mPrintSession && mPageFormat && mPMPrintSettings);
96 ::PMRetain(mPrintSession);
97 ::PMRetain(mPageFormat);
98 ::PMRetain(mPMPrintSettings);
101 #ifdef MOZ_ENABLE_SKIA_PDF
102 nsAutoString printViaPdf;
103 mozilla::Preferences::GetString("print.print_via_pdf_encoder", printViaPdf);
104 if (printViaPdf.EqualsLiteral("skia-pdf")) {
105 // Annoyingly, PMPrinterPrintWithFile does not pay attention to the
106 // kPMDestination* value set in the PMPrintSession; it always sends the PDF
107 // to the specified printer. This means that if we create the PDF using
108 // SkPDF then we need to manually handle user actions like "Open PDF in
109 // Preview" and "Save as PDF...".
110 // TODO: Currently we do not support using SkPDF for kPMDestinationFax or
111 // kPMDestinationProcessPDF ("Add PDF to iBooks, etc.), and we only support
112 // it for kPMDestinationFile if the destination file is a PDF.
113 // XXX Could PMWorkflowSubmitPDFWithSettings/PMPrinterPrintWithProvider
115 OSStatus status = noErr;
116 PMDestinationType destination;
117 status = ::PMSessionGetDestinationType(mPrintSession, mPMPrintSettings,
119 if (status == noErr) {
120 if (destination == kPMDestinationPrinter ||
121 destination == kPMDestinationPreview) {
122 mPrintViaSkPDF = true;
123 } else if (destination == kPMDestinationFile) {
124 AutoCFRelease<CFURLRef> destURL(nullptr);
125 status = ::PMSessionCopyDestinationLocation(
126 mPrintSession, mPMPrintSettings, destURL.receive());
127 if (status == noErr) {
128 AutoCFRelease<CFStringRef> destPathRef =
129 CFURLCopyFileSystemPath(destURL, kCFURLPOSIXPathStyle);
130 NSString* destPath = (NSString*)CFStringRef(destPathRef);
131 NSString* destPathExt = [destPath pathExtension];
132 if ([destPathExt isEqualToString:@"pdf"]) {
133 mPrintViaSkPDF = true;
141 int16_t outputFormat = aPS->GetOutputFormat();
143 if (outputFormat == nsIPrintSettings::kOutputFormatPDF) {
144 // We don't actually currently support/use kOutputFormatPDF on mac, but
145 // this is for completeness in case we add that (we probably need to in
146 // order to support adding links into saved PDFs, for example).
147 Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
150 PMDestinationType destination;
151 OSStatus status = ::PMSessionGetDestinationType(
152 mPrintSession, mPMPrintSettings, &destination);
153 if (status == noErr && (destination == kPMDestinationFile ||
154 destination == kPMDestinationPreview ||
155 destination == kPMDestinationProcessPDF)) {
156 Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
159 Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE,
166 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
169 NS_IMETHODIMP nsDeviceContextSpecX::BeginDocument(
170 const nsAString& aTitle, const nsAString& aPrintToFileName,
171 int32_t aStartPage, int32_t aEndPage) {
172 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
176 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
179 RefPtr<PrintEndDocumentPromise> nsDeviceContextSpecX::EndDocument() {
180 return nsIDeviceContextSpec::EndDocumentPromiseFromResult(DoEndDocument(),
184 nsresult nsDeviceContextSpecX::DoEndDocument() {
185 NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
187 #ifdef MOZ_ENABLE_SKIA_PDF
188 if (mPrintViaSkPDF) {
189 OSStatus status = noErr;
191 nsCOMPtr<nsILocalFileMac> tmpPDFFile = do_QueryInterface(mTempFile);
193 return NS_ERROR_FAILURE;
195 AutoCFRelease<CFURLRef> pdfURL(nullptr);
196 // Note that the caller is responsible to release pdfURL according to
197 // nsILocalFileMac.idl, even though we didn't follow the Core Foundation
198 // naming conventions here (the method should've been called CopyCFURL).
199 nsresult rv = tmpPDFFile->GetCFURL(pdfURL.receive());
200 NS_ENSURE_SUCCESS(rv, rv);
202 PMDestinationType destination;
203 status = ::PMSessionGetDestinationType(mPrintSession, mPMPrintSettings,
206 switch (destination) {
207 case kPMDestinationPrinter: {
208 PMPrinter currentPrinter = NULL;
209 status = ::PMSessionGetCurrentPrinter(mPrintSession, ¤tPrinter);
210 if (status != noErr) {
211 return NS_ERROR_FAILURE;
213 CFStringRef mimeType = CFSTR("application/pdf");
214 status = ::PMPrinterPrintWithFile(currentPrinter, mPMPrintSettings,
215 mPageFormat, mimeType, pdfURL);
218 case kPMDestinationPreview: {
219 // XXXjwatt Or should we use CocoaFileUtils::RevealFileInFinder(pdfURL);
220 AutoCFRelease<CFStringRef> pdfPath =
221 CFURLCopyFileSystemPath(pdfURL, kCFURLPOSIXPathStyle);
222 NSString* path = (NSString*)CFStringRef(pdfPath);
223 NSWorkspace* ws = [NSWorkspace sharedWorkspace];
227 case kPMDestinationFile: {
228 AutoCFRelease<CFURLRef> destURL(nullptr);
229 status = ::PMSessionCopyDestinationLocation(
230 mPrintSession, mPMPrintSettings, destURL.receive());
231 if (status == noErr) {
232 AutoCFRelease<CFStringRef> sourcePathRef =
233 CFURLCopyFileSystemPath(pdfURL, kCFURLPOSIXPathStyle);
234 NSString* sourcePath = (NSString*)CFStringRef(sourcePathRef);
236 AutoCFRelease<CFStringRef> destPathRef =
237 CFURLCopyFileSystemPath(destURL, kCFURLPOSIXPathStyle);
238 NSString* destPath = (NSString*)CFStringRef(destPathRef);
239 NSString* destPathExt = [destPath pathExtension];
240 MOZ_ASSERT([destPathExt isEqualToString:@"pdf"],
241 "nsDeviceContextSpecX::Init only allows '.pdf' for now");
242 // We could use /usr/sbin/cupsfilter to convert the PDF to PS, but
243 // currently we don't.
245 NSFileManager* fileManager = [NSFileManager defaultManager];
246 if ([fileManager fileExistsAtPath:sourcePath]) {
247 NSURL* src = static_cast<NSURL*>(CFURLRef(pdfURL));
248 NSURL* dest = static_cast<NSURL*>(CFURLRef(destURL));
249 bool ok = [fileManager
250 replaceItemAtURL:dest
254 NSFileManagerItemReplacementUsingNewMetadataOnly
258 return NS_ERROR_FAILURE;
265 MOZ_ASSERT_UNREACHABLE("nsDeviceContextSpecX::Init doesn't set "
266 "mPrintViaSkPDF for other values");
269 return (status == noErr) ? NS_OK : NS_ERROR_FAILURE;
275 NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
278 void nsDeviceContextSpecX::GetPaperRect(double* aTop, double* aLeft,
279 double* aBottom, double* aRight) {
280 NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
283 ::PMGetAdjustedPaperRect(mPageFormat, &paperRect);
285 *aTop = paperRect.top;
286 *aLeft = paperRect.left;
287 *aBottom = paperRect.bottom;
288 *aRight = paperRect.right;
290 NS_OBJC_END_TRY_IGNORE_BLOCK;
293 already_AddRefed<PrintTarget> nsDeviceContextSpecX::MakePrintTarget() {
294 double top, left, bottom, right;
295 GetPaperRect(&top, &left, &bottom, &right);
296 const double width = right - left;
297 const double height = bottom - top;
298 IntSize size = IntSize::Ceil(width, height);
300 #ifdef MOZ_ENABLE_SKIA_PDF
301 if (mPrintViaSkPDF) {
302 // TODO: Add support for stream printing via SkPDF if we enable that again.
304 NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mTempFile));
305 NS_ENSURE_SUCCESS(rv, nullptr);
306 nsAutoCString tempPath("tmp-printing.pdf");
307 mTempFile->AppendNative(tempPath);
308 rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
309 NS_ENSURE_SUCCESS(rv, nullptr);
310 mTempFile->GetNativePath(tempPath);
311 auto stream = MakeUnique<SkFILEWStream>(tempPath.get());
312 return PrintTargetSkPDF::CreateOrNull(std::move(stream), size);
316 return PrintTargetCG::CreateOrNull(mOutputStream, mPrintSession, mPageFormat,
317 mPMPrintSettings, size);