Bug 1370488 - Add support for having printing on Windows print via Skia PDF and PDFium r=jwatt
authorFarmer Tseng <fatseng@mozilla.com>
Thu, 08 Jun 2017 18:55:42 +0800
changeset 367603 b611ec2a42bf37a5deffb6957df83ddaa7302059
parent 367602 07255610c658f6f17514a344907685346277800c
child 367604 fdc36d2f4626ac399780fe4f2c029bbd75d387f3
push id32140
push userkwierso@gmail.com
push dateThu, 06 Jul 2017 22:34:46 +0000
treeherdermozilla-central@433c379d6e44 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwatt
bugs1370488
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1370488 - Add support for having printing on Windows print via Skia PDF and PDFium r=jwatt 1. Convert PDF to EMF via PDFViaEMFPrintHelper. 2. Replay EMF on printer DC. MozReview-Commit-ID: 8YTcaZ2Y1rO
widget/windows/nsDeviceContextSpecWin.cpp
widget/windows/nsDeviceContextSpecWin.h
--- a/widget/windows/nsDeviceContextSpecWin.cpp
+++ b/widget/windows/nsDeviceContextSpecWin.cpp
@@ -32,24 +32,29 @@
 #include "mozilla/Services.h"
 #include "nsWindowsHelpers.h"
 
 #include "mozilla/gfx/Logging.h"
 
 #ifdef MOZ_ENABLE_SKIA_PDF
 #include "mozilla/gfx/PrintTargetSkPDF.h"
 #include "nsIUUIDGenerator.h"
+#include "mozilla/widget/PDFViaEMFPrintHelper.h"
 #endif
 
 static mozilla::LazyLogModule kWidgetPrintingLogMod("printing-widget");
 #define PR_PL(_p1)  MOZ_LOG(kWidgetPrintingLogMod, mozilla::LogLevel::Debug, _p1)
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
+#ifdef MOZ_ENABLE_SKIA_PDF
+using namespace mozilla::widget;
+#endif
+
 static const wchar_t kDriverName[] =  L"WINSPOOL";
 
 //----------------------------------------------------------------------------------
 // The printer data is shared between the PrinterEnumerator and the nsDeviceContextSpecWin
 // The PrinterEnumerator creates the printer info
 // but the nsDeviceContextSpecWin cleans it up
 // If it gets created (via the Page Setup Dialog) but the user never prints anything
 // then it will never be delete, so this class takes care of that.
@@ -88,17 +93,21 @@ struct AutoFreeGlobalPrinters
 
 //----------------------------------------------------------------------------------
 nsDeviceContextSpecWin::nsDeviceContextSpecWin()
 {
   mDriverName    = nullptr;
   mDeviceName    = nullptr;
   mDevMode       = nullptr;
 #ifdef MOZ_ENABLE_SKIA_PDF
-  mPrintViaSkPDF = false;
+  mPrintViaSkPDF          = false;
+  mDC                     = NULL;
+  mPDFPageCount           = 0;
+  mPDFCurrentPageNum      = 0;
+  mPrintViaPDFInProgress  = false;
 #endif
 }
 
 
 //----------------------------------------------------------------------------------
 
 NS_IMPL_ISUPPORTS(nsDeviceContextSpecWin, nsIDeviceContextSpec)
 
@@ -110,16 +119,21 @@ nsDeviceContextSpecWin::~nsDeviceContext
 
   nsCOMPtr<nsIPrintSettingsWin> psWin(do_QueryInterface(mPrintSettings));
   if (psWin) {
     psWin->SetDeviceName(nullptr);
     psWin->SetDriverName(nullptr);
     psWin->SetDevMode(nullptr);
   }
 
+#ifdef MOZ_ENABLE_SKIA_PDF
+  if (mPrintViaSkPDF ) {
+    CleanupPrintViaPDF();
+  }
+#endif
   // Free them, we won't need them for a while
   GlobalPrinters::GetInstance()->FreeGlobalPrinters();
 }
 
 
 //------------------------------------------------------------------
 // helper
 static char16_t * GetDefaultPrinterNameFromGlobalPrinters()
@@ -254,16 +268,50 @@ already_AddRefed<PrintTarget> nsDeviceCo
     if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
       nsXPIDLString filename;
       mPrintSettings->GetToFileName(getter_Copies(filename));
 
       nsAutoCString printFile(NS_ConvertUTF16toUTF8(filename).get());
       auto skStream = MakeUnique<SkFILEWStream>(printFile.get());
       return PrintTargetSkPDF::CreateOrNull(Move(skStream), size);
     }
+
+    if (mDevMode) {
+      // When printing to a printer via Skia PDF we open a temporary file that
+      // we draw the print output into as PDF output, then once we reach
+      // EndDcoument we'll convert that PDF file to EMF page by page to print
+      // each page. Here we create the temporary file and wrap it in a
+      // PrintTargetSkPDF that we return.
+      nsresult rv =
+        NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mPDFTempFile));
+      NS_ENSURE_SUCCESS(rv, nullptr);
+
+      nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
+        do_GetService("@mozilla.org/uuid-generator;1", &rv);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return nullptr;
+      }
+      nsID uuid;
+      rv = uuidGenerator->GenerateUUIDInPlace(&uuid);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return nullptr;
+      }
+      char uuidChars[NSID_LENGTH];
+      uuid.ToProvidedString(uuidChars);
+
+      nsAutoCString printFile("tmp-printing");
+      printFile.Append(nsPrintfCString("%s.pdf", uuidChars));
+      rv = mPDFTempFile->AppendNative(printFile);
+      NS_ENSURE_SUCCESS(rv, nullptr);
+
+      nsAutoCString filePath;
+      mPDFTempFile->GetNativePath(filePath);
+      auto skStream = MakeUnique<SkFILEWStream>(filePath.get());
+      return PrintTargetSkPDF::CreateOrNull(Move(skStream), size);
+    }
   }
 #endif
 
   if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
     nsXPIDLString filename;
     mPrintSettings->GetToFileName(getter_Copies(filename));
 
     double width, height;
@@ -307,35 +355,183 @@ already_AddRefed<PrintTarget> nsDeviceCo
   return nullptr;
 }
 
 float
 nsDeviceContextSpecWin::GetDPI()
 {
   // To match the previous printing code we need to return 72 when printing to
   // PDF and 144 when printing to a Windows surface.
+#ifdef MOZ_ENABLE_SKIA_PDF
+  if (mPrintViaSkPDF) {
+    return 72.0f;
+  }
+#endif
   return mOutputFormat == nsIPrintSettings::kOutputFormatPDF ? 72.0f : 144.0f;
 }
 
 float
 nsDeviceContextSpecWin::GetPrintingScale()
 {
   MOZ_ASSERT(mPrintSettings);
-
+#ifdef MOZ_ENABLE_SKIA_PDF
+  if (mPrintViaSkPDF) {
+    return 1.0f; // PDF is vector based, so we don't need a scale
+  }
+#endif
   // To match the previous printing code there is no scaling for PDF.
   if (mOutputFormat == nsIPrintSettings::kOutputFormatPDF) {
     return 1.0f;
   }
 
   // The print settings will have the resolution stored from the real device.
   int32_t resolution;
   mPrintSettings->GetResolution(&resolution);
   return float(resolution) / GetDPI();
 }
 
+#ifdef MOZ_ENABLE_SKIA_PDF
+void
+nsDeviceContextSpecWin::CleanupPrintViaPDF()
+{
+  if (mPDFPrintHelper) {
+    mPDFPrintHelper->CloseDocument();
+    mPDFPrintHelper = nullptr;
+    mPDFPageCount = 0;
+  }
+
+  if (mPDFTempFile) {
+    mPDFTempFile->Remove(/* aRecursive */ false);
+    mPDFTempFile = nullptr;
+  }
+
+  if (mDC != NULL) {
+    if (mPrintViaPDFInProgress) {
+      ::EndDoc(mDC);
+      mPrintViaPDFInProgress = false;
+    }
+    ::DeleteDC(mDC);
+    mDC = NULL;
+  }
+}
+
+void
+nsDeviceContextSpecWin::FinishPrintViaPDF()
+{
+  MOZ_ASSERT(mDC != NULL);
+  MOZ_ASSERT(mPDFPrintHelper);
+  MOZ_ASSERT(mPDFTempFile);
+  MOZ_ASSERT(mPrintViaPDFInProgress);
+
+  bool isPrinted = false;
+  bool endPageSuccess = false;
+  if (::StartPage(mDC) > 0) {
+    isPrinted = mPDFPrintHelper->DrawPage(mDC, mPDFCurrentPageNum++,
+                                          ::GetDeviceCaps(mDC, HORZRES),
+                                          ::GetDeviceCaps(mDC, VERTRES));
+    if (::EndPage(mDC) > 0) {
+      endPageSuccess = true;
+    }
+  }
+
+  if (mPDFCurrentPageNum < mPDFPageCount && isPrinted && endPageSuccess) {
+    nsresult rv = NS_DispatchToCurrentThread(NewRunnableMethod(
+      "nsDeviceContextSpecWin::PrintPDFOnThread",
+      this,
+      &nsDeviceContextSpecWin::FinishPrintViaPDF));
+    if (NS_SUCCEEDED(rv)) {
+      return;
+    }
+  }
+
+  CleanupPrintViaPDF();
+}
+#endif
+
+nsresult
+nsDeviceContextSpecWin::BeginDocument(const nsAString& aTitle,
+                                      const nsAString& aPrintToFileName,
+                                      int32_t          aStartPage,
+                                      int32_t          aEndPage)
+{
+#ifdef MOZ_ENABLE_SKIA_PDF
+  if (mPrintViaSkPDF && (mOutputFormat != nsIPrintSettings::kOutputFormatPDF)) {
+    // Here we create mDC which we'll draw each page from our temporary PDF file
+    // to once we reach EndDocument. The only reason we create it here rather
+    // than in EndDocument is so that we don't need to store aTitle and
+    // aPrintToFileName as member data.
+    NS_WARNING_ASSERTION(mDriverName, "No driver!");
+    mDC = ::CreateDCW(mDriverName, mDeviceName, nullptr, mDevMode);
+    if (mDC == NULL) {
+      gfxCriticalError(gfxCriticalError::DefaultOptions(false))
+        << "Failed to create device context in GetSurfaceForPrinter";
+      return NS_ERROR_FAILURE;
+    }
+
+    const uint32_t DOC_TITLE_LENGTH = MAX_PATH - 1;
+    nsString title(aTitle);
+    nsString printToFileName(aPrintToFileName);
+    if (title.Length() > DOC_TITLE_LENGTH) {
+      title.SetLength(DOC_TITLE_LENGTH - 3);
+      title.AppendLiteral("...");
+    }
+
+    DOCINFOW di;
+    di.cbSize = sizeof(di);
+    di.lpszDocName = title.Length() > 0 ? title.get() : L"Mozilla Document";
+    di.lpszOutput = printToFileName.Length() > 0 ?
+                      printToFileName.get() : nullptr;
+    di.lpszDatatype = nullptr;
+    di.fwType = 0;
+
+    if (::StartDocW(mDC, &di) <= 0) {
+      // Defer calling CleanupPrintViaPDF() in destructor because PDF temp file
+      // is not ready yet.
+      return NS_ERROR_FAILURE;
+    }
+
+    mPrintViaPDFInProgress = true;
+  }
+#endif
+
+  return NS_OK;
+}
+
+nsresult
+nsDeviceContextSpecWin::EndDocument()
+{
+  nsresult rv = NS_OK;
+#ifdef MOZ_ENABLE_SKIA_PDF
+  if (mPrintViaSkPDF &&
+      mOutputFormat != nsIPrintSettings::kOutputFormatPDF &&
+      mPrintViaPDFInProgress) {
+
+    mPDFPrintHelper = MakeUnique<PDFViaEMFPrintHelper>();
+    rv = mPDFPrintHelper->OpenDocument(mPDFTempFile);
+    NS_ENSURE_SUCCESS(rv, rv);
+    mPDFPageCount = mPDFPrintHelper->GetPageCount();
+    if (mPDFPageCount <= 0) {
+      CleanupPrintViaPDF();
+      return NS_ERROR_FAILURE;
+    }
+    mPDFCurrentPageNum = 0;
+
+    rv = NS_DispatchToCurrentThread(NewRunnableMethod(
+      "nsDeviceContextSpecWin::PrintPDFOnThread",
+      this,
+      &nsDeviceContextSpecWin::FinishPrintViaPDF));
+    if (NS_FAILED(rv)) {
+      CleanupPrintViaPDF();
+      NS_WARNING("Failed to dispatch to the current thread!");
+    }
+  }
+#endif
+  return rv;
+}
+
 //----------------------------------------------------------------------------------
 void nsDeviceContextSpecWin::SetDeviceName(char16ptr_t aDeviceName)
 {
   CleanAndCopyString(mDeviceName, aDeviceName);
 }
 
 //----------------------------------------------------------------------------------
 void nsDeviceContextSpecWin::SetDriverName(char16ptr_t aDriverName)
--- a/widget/windows/nsDeviceContextSpecWin.h
+++ b/widget/windows/nsDeviceContextSpecWin.h
@@ -12,29 +12,39 @@
 #include "nsIPrintSettings.h"
 #include "nsISupportsPrimitives.h"
 #include <windows.h>
 #include "mozilla/Attributes.h"
 #include "mozilla/RefPtr.h"
 
 class nsIWidget;
 
+#ifdef MOZ_ENABLE_SKIA_PDF
+namespace mozilla {
+namespace widget {
+class PDFViaEMFPrintHelper;
+}
+}
+#endif
+
 class nsDeviceContextSpecWin : public nsIDeviceContextSpec
 {
+  typedef mozilla::widget::PDFViaEMFPrintHelper PDFViaEMFPrintHelper;
+
 public:
   nsDeviceContextSpecWin();
 
   NS_DECL_ISUPPORTS
 
   virtual already_AddRefed<PrintTarget> MakePrintTarget() final;
   NS_IMETHOD BeginDocument(const nsAString& aTitle,
                            const nsAString& aPrintToFileName,
                            int32_t          aStartPage,
-                           int32_t          aEndPage) override { return NS_OK; }
-  NS_IMETHOD EndDocument() override { return NS_OK; }
+                           int32_t          aEndPage) override;
+  NS_IMETHOD EndDocument() override;
   NS_IMETHOD BeginPage() override { return NS_OK; }
   NS_IMETHOD EndPage() override { return NS_OK; }
 
   NS_IMETHOD Init(nsIWidget* aWidget, nsIPrintSettings* aPS, bool aIsPrintPreview) override;
 
   float GetDPI() final;
 
   float GetPrintingScale() final;
@@ -62,17 +72,29 @@ protected:
   wchar_t*      mDriverName;
   wchar_t*      mDeviceName;
   LPDEVMODEW mDevMode;
 
   nsCOMPtr<nsIPrintSettings> mPrintSettings;
   int16_t mOutputFormat = nsIPrintSettings::kOutputFormatNative;
 
 #ifdef MOZ_ENABLE_SKIA_PDF
+  void  FinishPrintViaPDF();
+  void  CleanupPrintViaPDF();
+
+  // This variable is independant of nsIPrintSettings::kOutputFormatPDF.
+  // It controls both whether normal printing is done via PDF using Skia and
+  // whether print-to-PDF uses Skia.
   bool mPrintViaSkPDF;
+  nsCOMPtr<nsIFile> mPDFTempFile;
+  HDC mDC;
+  bool mPrintViaPDFInProgress;
+  mozilla::UniquePtr<PDFViaEMFPrintHelper> mPDFPrintHelper;
+  int mPDFPageCount;
+  int mPDFCurrentPageNum;
 #endif
 };
 
 
 //-------------------------------------------------------------------------
 // Printer Enumerator
 //-------------------------------------------------------------------------
 class nsPrinterEnumeratorWin final : public nsIPrinterEnumerator