Bug 1309272, part 8 - Implement printing via Skia PDF for macOS (behind pref print.print_via_pdf_encoder=skia-pdf). r=lsalzman
☠☠ backed out by bc39d8cde9ab ☠ ☠
authorJonathan Watt <jwatt@jwatt.org>
Wed, 23 Nov 2016 09:14:10 +0000
changeset 324470 5ecb0db80f1531e80fd5b70eceed0e4514da09fd
parent 324469 d2046ba274866207e92400089d249cd47f552a48
child 324471 2259891dc1e8202192f5134fa39d7573fc40dbe0
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewerslsalzman
bugs1309272
milestone53.0a1
Bug 1309272, part 8 - Implement printing via Skia PDF for macOS (behind pref print.print_via_pdf_encoder=skia-pdf). r=lsalzman
widget/cocoa/moz.build
widget/cocoa/nsDeviceContextSpecX.h
widget/cocoa/nsDeviceContextSpecX.mm
--- a/widget/cocoa/moz.build
+++ b/widget/cocoa/moz.build
@@ -86,16 +86,24 @@ FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/layout/forms',
     '/layout/generic',
     '/layout/style',
     '/layout/xul',
     '/widget',
 ]
 
+if CONFIG['MOZ_ENABLE_SKIA_PDF']:
+    LOCAL_INCLUDES += [
+        # Skia includes because widget code includes PrintTargetSkPDF.h, and that
+        # includes skia headers.
+        '/gfx/skia/skia/include/config',
+        '/gfx/skia/skia/include/core',
+    ]
+
 RESOURCE_FILES.cursors += [
     'cursors/arrowN.png',
     'cursors/arrowN@2x.png',
     'cursors/arrowS.png',
     'cursors/arrowS@2x.png',
     'cursors/cell.png',
     'cursors/cell@2x.png',
     'cursors/colResize.png',
--- a/widget/cocoa/nsDeviceContextSpecX.h
+++ b/widget/cocoa/nsDeviceContextSpecX.h
@@ -35,11 +35,15 @@ public:
 
 protected:
     virtual ~nsDeviceContextSpecX();
 
 protected:
     PMPrintSession    mPrintSession;              // printing context.
     PMPageFormat      mPageFormat;                // page format.
     PMPrintSettings   mPrintSettings;             // print settings.
+#ifdef MOZ_ENABLE_SKIA_PDF
+    nsCOMPtr<nsIFile> mTempFile;                  // file "print" output is generated to if printing via PDF
+    bool              mPrintViaSkPDF;
+#endif
 };
 
 #endif //nsDeviceContextSpecX_h_
--- a/widget/cocoa/nsDeviceContextSpecX.mm
+++ b/widget/cocoa/nsDeviceContextSpecX.mm
@@ -1,34 +1,49 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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"
 
 #include "mozilla/gfx/PrintTargetCG.h"
+#ifdef MOZ_ENABLE_SKIA_PDF
+#include "mozilla/gfx/PrintTargetSkPDF.h"
+#endif
+#include "mozilla/Preferences.h"
 #include "mozilla/RefPtr.h"
 #include "nsCRT.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsILocalFileMac.h"
 #include <unistd.h>
 
 #include "nsQueryObject.h"
 #include "nsIServiceManager.h"
 #include "nsPrintSettingsX.h"
 
 // This must be the last include:
 #include "nsObjCExceptions.h"
 
 using namespace mozilla;
-using namespace mozilla::gfx;
+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;
 
 nsDeviceContextSpecX::nsDeviceContextSpecX()
 : mPrintSession(NULL)
 , mPageFormat(kPMNoPageFormat)
 , mPrintSettings(kPMNoPrintSettings)
+#ifdef MOZ_ENABLE_SKIA_PDF
+, mPrintViaSkPDF(false)
+#endif
 {
 }
 
 nsDeviceContextSpecX::~nsDeviceContextSpecX()
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   if (mPrintSession)
@@ -49,16 +64,55 @@ NS_IMETHODIMP nsDeviceContextSpecX::Init
   if (!settings)
     return NS_ERROR_NO_INTERFACE;
 
   mPrintSession = settings->GetPMPrintSession();
   ::PMRetain(mPrintSession);
   mPageFormat = settings->GetPMPageFormat();
   mPrintSettings = settings->GetPMPrintSettings();
 
+#ifdef MOZ_ENABLE_SKIA_PDF
+  const nsAdoptingString& printViaPdf =
+    mozilla::Preferences::GetString("print.print_via_pdf_encoder");
+  if (printViaPdf == NS_LITERAL_STRING("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) {
+        CFURLRef destURL;
+        status = ::PMSessionCopyDestinationLocation(mPrintSession,
+                                                    mPrintSettings, &destURL);
+        if (status == noErr) {
+          CFStringRef destPathRef =
+            CFURLCopyFileSystemPath(destURL, kCFURLPOSIXPathStyle);
+          NSString* destPath = (NSString*) destPathRef;
+          NSString* destPathExt = [destPath pathExtension];
+          if ([destPathExt isEqualToString: @"pdf"]) {
+            mPrintViaSkPDF = true;
+          }
+        }
+      }
+    }
+  }
+#endif
+
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 NS_IMETHODIMP nsDeviceContextSpecX::BeginDocument(const nsAString& aTitle, 
                                                   const nsAString& aPrintToFileName,
                                                   int32_t          aStartPage, 
@@ -78,17 +132,100 @@ NS_IMETHODIMP nsDeviceContextSpecX::Begi
 
     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;
+    }
+    CFURLRef pdfURL;
+    nsresult rv = tmpPDFFile->GetCFURL(&pdfURL);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PMDestinationType destination;
+    status = ::PMSessionGetDestinationType(mPrintSession, mPrintSettings,
+                                           &destination);
+
+    switch (destination) {
+    case kPMDestinationPrinter: {
+      PMPrinter currentPrinter = NULL;
+      status = ::PMSessionGetCurrentPrinter(mPrintSession, &currentPrinter);
+      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);
+      CFStringRef pdfPath = CFURLCopyFileSystemPath(pdfURL,
+                                                    kCFURLPOSIXPathStyle);
+      NSString* path = (NSString*) pdfPath;
+      NSWorkspace* ws = [NSWorkspace sharedWorkspace];
+      [ws openFile: path];
+      break;
+    }
+    case kPMDestinationFile: {
+      CFURLRef destURL;
+      status = ::PMSessionCopyDestinationLocation(mPrintSession,
+                                                  mPrintSettings, &destURL);
+      if (status == noErr) {
+        CFStringRef sourcePathRef =
+          CFURLCopyFileSystemPath(pdfURL, kCFURLPOSIXPathStyle);
+        CFStringRef destPathRef =
+          CFURLCopyFileSystemPath(destURL, kCFURLPOSIXPathStyle);
+        NSString* sourcePath = (NSString*) sourcePathRef;
+        NSString* destPath = (NSString*) destPathRef;
+#ifdef DEBUG
+        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*>(pdfURL);
+          NSURL* dest = static_cast<NSURL*>(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);
@@ -102,11 +239,26 @@ void nsDeviceContextSpecX::GetPaperRect(
 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::Floor(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(Move(stream), size);
+    }
+#endif
+
     return PrintTargetCG::CreateOrNull(mPrintSession, mPageFormat,
                                        mPrintSettings, size);
 }