Bug 1370488 - Add support for having printing on Windows print via Skia PDF and PDFium r=jwatt draft
authorFarmer Tseng <fatseng@mozilla.com>
Thu, 08 Jun 2017 18:55:42 +0800
changeset 604545 7b5e718c7fe5dbeee13bbd7a0c958cd285d0833f
parent 604544 25650284eb0108590a5c339ccaa46243a5f01389
child 605261 02002f9632f609f1f9d31759c4201b686629eb43
child 605264 0792ff5665ba5fd26347cc7b1e126482dacd0756
child 606656 ccbfda9a51a833dc659dfe4e2d209fa59500d003
child 607533 4575b9faf2e3f3c484f56e747a5cc65a8a3a9a9c
child 608064 ee3418d4f7dbd6f52766a4f66c9911ac7c3e48d3
push id67115
push userbmo:fatseng@mozilla.com
push dateThu, 06 Jul 2017 03:15:42 +0000
reviewersjwatt
bugs1370488
milestone56.0a1
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