diff options
Diffstat (limited to '')
-rw-r--r-- | widget/cocoa/nsDeviceContextSpecX.mm | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/widget/cocoa/nsDeviceContextSpecX.mm b/widget/cocoa/nsDeviceContextSpecX.mm new file mode 100644 index 0000000000..0d8fb4140d --- /dev/null +++ b/widget/cocoa/nsDeviceContextSpecX.mm @@ -0,0 +1,303 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsDeviceContextSpecX.h" + +#import <Cocoa/Cocoa.h> +#include <CoreFoundation/CoreFoundation.h> +#include <unistd.h> + +#ifdef MOZ_ENABLE_SKIA_PDF +# include "mozilla/gfx/PrintTargetSkPDF.h" +#endif +#include "mozilla/gfx/PrintTargetCG.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Telemetry.h" + +#include "AppleUtils.h" +#include "nsCocoaUtils.h" +#include "nsCRT.h" +#include "nsCUPSShim.h" +#include "nsDirectoryServiceDefs.h" +#include "nsILocalFileMac.h" +#include "nsPaper.h" +#include "nsPrinterListCUPS.h" +#include "nsPrintSettingsX.h" +#include "nsQueryObject.h" +#include "prenv.h" + +// This must be the last include: +#include "nsObjCExceptions.h" + +using namespace mozilla; +using mozilla::gfx::IntSize; +using mozilla::gfx::PrintTarget; +using mozilla::gfx::PrintTargetCG; +#ifdef MOZ_ENABLE_SKIA_PDF +using mozilla::gfx::PrintTargetSkPDF; +#endif +using mozilla::gfx::SurfaceFormat; + +static LazyLogModule sDeviceContextSpecXLog("DeviceContextSpecX"); + +//---------------------------------------------------------------------- +// nsDeviceContentSpecX + +nsDeviceContextSpecX::nsDeviceContextSpecX() + : mPrintSession(nullptr), + mPageFormat(nullptr), + mPrintSettings(nullptr) +#ifdef MOZ_ENABLE_SKIA_PDF + , + mPrintViaSkPDF(false) +#endif +{ +} + +nsDeviceContextSpecX::~nsDeviceContextSpecX() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + if (mPrintSession) { + ::PMRelease(mPrintSession); + } + if (mPageFormat) { + ::PMRelease(mPageFormat); + } + if (mPrintSettings) { + ::PMRelease(mPrintSettings); + } + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +NS_IMPL_ISUPPORTS(nsDeviceContextSpecX, nsIDeviceContextSpec) + +NS_IMETHODIMP nsDeviceContextSpecX::Init(nsIWidget* aWidget, nsIPrintSettings* aPS, + bool aIsPrintPreview) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + RefPtr<nsPrintSettingsX> settings(do_QueryObject(aPS)); + if (!settings) { + return NS_ERROR_NO_INTERFACE; + } + + bool toFile; + settings->GetPrintToFile(&toFile); + + NSPrintInfo* printInfo = settings->CreateOrCopyPrintInfo(); + if (!printInfo) { + return NS_ERROR_FAILURE; + } + mPrintSession = static_cast<PMPrintSession>([printInfo PMPrintSession]); + mPageFormat = static_cast<PMPageFormat>([printInfo PMPageFormat]); + mPrintSettings = static_cast<PMPrintSettings>([printInfo PMPrintSettings]); + MOZ_ASSERT(mPrintSession && mPageFormat && mPrintSettings); + ::PMRetain(mPrintSession); + ::PMRetain(mPageFormat); + ::PMRetain(mPrintSettings); + [printInfo release]; + +#ifdef MOZ_ENABLE_SKIA_PDF + nsAutoString printViaPdf; + mozilla::Preferences::GetString("print.print_via_pdf_encoder", printViaPdf); + if (printViaPdf.EqualsLiteral("skia-pdf")) { + // Annoyingly, PMPrinterPrintWithFile does not pay attention to the + // kPMDestination* value set in the PMPrintSession; it always sends the PDF + // to the specified printer. This means that if we create the PDF using + // SkPDF then we need to manually handle user actions like "Open PDF in + // Preview" and "Save as PDF...". + // TODO: Currently we do not support using SkPDF for kPMDestinationFax or + // kPMDestinationProcessPDF ("Add PDF to iBooks, etc.), and we only support + // it for kPMDestinationFile if the destination file is a PDF. + // XXX Could PMWorkflowSubmitPDFWithSettings/PMPrinterPrintWithProvider help? + OSStatus status = noErr; + PMDestinationType destination; + status = ::PMSessionGetDestinationType(mPrintSession, mPrintSettings, &destination); + if (status == noErr) { + if (destination == kPMDestinationPrinter || destination == kPMDestinationPreview) { + mPrintViaSkPDF = true; + } else if (destination == kPMDestinationFile) { + AutoCFRelease<CFURLRef> destURL(nullptr); + status = + ::PMSessionCopyDestinationLocation(mPrintSession, mPrintSettings, destURL.receive()); + if (status == noErr) { + AutoCFRelease<CFStringRef> destPathRef = + CFURLCopyFileSystemPath(destURL, kCFURLPOSIXPathStyle); + NSString* destPath = (NSString*)CFStringRef(destPathRef); + NSString* destPathExt = [destPath pathExtension]; + if ([destPathExt isEqualToString:@"pdf"]) { + mPrintViaSkPDF = true; + } + } + } + } + } +#endif + + int16_t outputFormat; + aPS->GetOutputFormat(&outputFormat); + + if (outputFormat == nsIPrintSettings::kOutputFormatPDF) { + // We don't actually currently support/use kOutputFormatPDF on mac, but + // this is for completeness in case we add that (we probably need to in + // order to support adding links into saved PDFs, for example). + Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE, u"pdf_file"_ns, 1); + } else { + PMDestinationType destination; + OSStatus status = ::PMSessionGetDestinationType(mPrintSession, mPrintSettings, &destination); + if (status == noErr && + (destination == kPMDestinationFile || destination == kPMDestinationPreview || + destination == kPMDestinationProcessPDF)) { + Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE, u"pdf_file"_ns, 1); + } else { + Telemetry::ScalarAdd(Telemetry::ScalarID::PRINTING_TARGET_TYPE, u"unknown"_ns, 1); + } + } + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP nsDeviceContextSpecX::BeginDocument(const nsAString& aTitle, + const nsAString& aPrintToFileName, + int32_t aStartPage, int32_t aEndPage) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP nsDeviceContextSpecX::EndDocument() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + +#ifdef MOZ_ENABLE_SKIA_PDF + if (mPrintViaSkPDF) { + OSStatus status = noErr; + + nsCOMPtr<nsILocalFileMac> tmpPDFFile = do_QueryInterface(mTempFile); + if (!tmpPDFFile) { + return NS_ERROR_FAILURE; + } + AutoCFRelease<CFURLRef> pdfURL(nullptr); + // Note that the caller is responsible to release pdfURL according to nsILocalFileMac.idl, + // even though we didn't follow the Core Foundation naming conventions here (the method + // should've been called CopyCFURL). + nsresult rv = tmpPDFFile->GetCFURL(pdfURL.receive()); + NS_ENSURE_SUCCESS(rv, rv); + + PMDestinationType destination; + status = ::PMSessionGetDestinationType(mPrintSession, mPrintSettings, &destination); + + switch (destination) { + case kPMDestinationPrinter: { + PMPrinter currentPrinter = NULL; + status = ::PMSessionGetCurrentPrinter(mPrintSession, ¤tPrinter); + if (status != noErr) { + return NS_ERROR_FAILURE; + } + CFStringRef mimeType = CFSTR("application/pdf"); + status = + ::PMPrinterPrintWithFile(currentPrinter, mPrintSettings, mPageFormat, mimeType, pdfURL); + break; + } + case kPMDestinationPreview: { + // XXXjwatt Or should we use CocoaFileUtils::RevealFileInFinder(pdfURL); + AutoCFRelease<CFStringRef> pdfPath = CFURLCopyFileSystemPath(pdfURL, kCFURLPOSIXPathStyle); + NSString* path = (NSString*)CFStringRef(pdfPath); + NSWorkspace* ws = [NSWorkspace sharedWorkspace]; + [ws openFile:path]; + break; + } + case kPMDestinationFile: { + AutoCFRelease<CFURLRef> destURL(nullptr); + status = + ::PMSessionCopyDestinationLocation(mPrintSession, mPrintSettings, destURL.receive()); + if (status == noErr) { + AutoCFRelease<CFStringRef> sourcePathRef = + CFURLCopyFileSystemPath(pdfURL, kCFURLPOSIXPathStyle); + NSString* sourcePath = (NSString*)CFStringRef(sourcePathRef); +# ifdef DEBUG + AutoCFRelease<CFStringRef> destPathRef = + CFURLCopyFileSystemPath(destURL, kCFURLPOSIXPathStyle); + NSString* destPath = (NSString*)CFStringRef(destPathRef); + NSString* destPathExt = [destPath pathExtension]; + MOZ_ASSERT([destPathExt isEqualToString:@"pdf"], + "nsDeviceContextSpecX::Init only allows '.pdf' for now"); + // We could use /usr/sbin/cupsfilter to convert the PDF to PS, but + // currently we don't. +# endif + NSFileManager* fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath:sourcePath]) { + NSURL* src = static_cast<NSURL*>(CFURLRef(pdfURL)); + NSURL* dest = static_cast<NSURL*>(CFURLRef(destURL)); + bool ok = [fileManager replaceItemAtURL:dest + withItemAtURL:src + backupItemName:nil + options:NSFileManagerItemReplacementUsingNewMetadataOnly + resultingItemURL:nil + error:nil]; + if (!ok) { + return NS_ERROR_FAILURE; + } + } + } + break; + } + default: + MOZ_ASSERT_UNREACHABLE("nsDeviceContextSpecX::Init doesn't set " + "mPrintViaSkPDF for other values"); + } + + return (status == noErr) ? NS_OK : NS_ERROR_FAILURE; + } +#endif + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +void nsDeviceContextSpecX::GetPaperRect(double* aTop, double* aLeft, double* aBottom, + double* aRight) { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + PMRect paperRect; + ::PMGetAdjustedPaperRect(mPageFormat, &paperRect); + + *aTop = paperRect.top; + *aLeft = paperRect.left; + *aBottom = paperRect.bottom; + *aRight = paperRect.right; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +already_AddRefed<PrintTarget> nsDeviceContextSpecX::MakePrintTarget() { + double top, left, bottom, right; + GetPaperRect(&top, &left, &bottom, &right); + const double width = right - left; + const double height = bottom - top; + IntSize size = IntSize::Ceil(width, height); + +#ifdef MOZ_ENABLE_SKIA_PDF + if (mPrintViaSkPDF) { + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mTempFile)); + NS_ENSURE_SUCCESS(rv, nullptr); + nsAutoCString tempPath("tmp-printing.pdf"); + mTempFile->AppendNative(tempPath); + rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, nullptr); + mTempFile->GetNativePath(tempPath); + auto stream = MakeUnique<SkFILEWStream>(tempPath.get()); + return PrintTargetSkPDF::CreateOrNull(std::move(stream), size); + } +#endif + + return PrintTargetCG::CreateOrNull(mPrintSession, mPageFormat, mPrintSettings, size); +} |