Bug 1345710 - part3: implement PDFViaEMFPrintHelper to convert PDF to EMF r=cjku,jwatt
authorFarmer Tseng <farmer.tseng@gmail.com>
Tue, 10 Jan 2017 17:43:16 +0800
changeset 404687 bf788d19f2d38c09bd08cd0aaadd868bed55df98
parent 404686 aff3b1f916214ed186a348b23651725e089d6a04
child 404688 49138b849ad1afcfcc90fdacd0473a09956f0672
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscjku, jwatt
bugs1345710
milestone55.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 1345710 - part3: implement PDFViaEMFPrintHelper to convert PDF to EMF r=cjku,jwatt This class helps draw a PDF file to a given Windows DC. MozReview-Commit-ID: IjZAIcN3bND
widget/windows/PDFViaEMFPrintHelper.cpp
widget/windows/PDFViaEMFPrintHelper.h
widget/windows/moz.build
new file mode 100644
--- /dev/null
+++ b/widget/windows/PDFViaEMFPrintHelper.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 20; 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 "PDFViaEMFPrintHelper.h"
+#include "nsIFileStreams.h"
+#include "WindowsEMF.h"
+#include "nsFileStreams.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/Unused.h"
+
+static
+float ComputeScaleFactor(int aDCWidth, int aDCHeight,
+                         int aPageWidth, int aPageHeight)
+{
+  MOZ_ASSERT(aPageWidth !=0 && aPageWidth != 0);
+
+  float scaleFactor = 1.0;
+  // If page fits DC - no scaling needed.
+  if (aDCWidth < aPageWidth || aDCHeight < aPageHeight) {
+    float xFactor =
+      static_cast<float>(aDCWidth) / static_cast <float>(aPageWidth);
+    float yFactor =
+      static_cast<float>(aDCHeight) / static_cast <float>(aPageHeight);
+    scaleFactor = std::min(xFactor, yFactor);
+  }
+  return scaleFactor;
+}
+
+PDFViaEMFPrintHelper::PDFViaEMFPrintHelper(PRLibrary* aPDFiumLibrary)
+  : mPDFiumLibrary(aPDFiumLibrary)
+  , mPDFDoc(nullptr)
+{
+  MOZ_ASSERT(mPDFiumLibrary);
+}
+
+PDFViaEMFPrintHelper::~PDFViaEMFPrintHelper()
+{
+  CloseDocument();
+}
+
+bool
+PDFViaEMFPrintHelper::LoadPDFDataToBuffer(nsIFile *aFile)
+{
+  RefPtr<nsFileInputStream> inputStream = new nsFileInputStream();
+  if (NS_FAILED(inputStream->Init(aFile, -1, -1, 0))) {
+    return false;
+  }
+
+  int64_t size = 0;
+  inputStream->GetSize(&size);
+  NS_ENSURE_TRUE(size > 0, false);
+
+  if (!mPDFFileContents.resize(size)) {
+    return false;
+  }
+
+  uint32_t bytesRead = 0;
+  inputStream->Read(mPDFFileContents.begin(), size, &bytesRead);
+  MOZ_ASSERT(bytesRead == size);
+  return true;
+}
+
+nsresult
+PDFViaEMFPrintHelper::OpenDocument(nsIFile *aFile)
+{
+  MOZ_ASSERT(aFile);
+  if (mPDFDoc) {
+    MOZ_ASSERT_UNREACHABLE("We can only open one PDF at a time");
+    return NS_ERROR_FAILURE;
+  }
+
+  if (!mPDFiumEngine) {
+    mPDFiumEngine = MakeUnique<PDFiumEngineShim>(mPDFiumLibrary);
+  }
+
+  if (!LoadPDFDataToBuffer(aFile)) {
+    return NS_ERROR_FAILURE;
+  }
+  // Create Bug 1359713 to implement loading document by path in
+  // PDFiumEngineShim.
+  mPDFDoc = mPDFiumEngine->LoadMemDocument(mPDFFileContents.begin(),
+                                           mPDFFileContents.length(),
+                                           nullptr);
+  if (!mPDFDoc) {
+    mPDFFileContents.clear();
+    return NS_ERROR_FAILURE;
+  }
+
+  if (mPDFiumEngine->GetPageCount(mPDFDoc) < 1) {
+    CloseDocument();
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+bool
+PDFViaEMFPrintHelper::RenderPageToDC(HDC aDC, unsigned int aPageIndex,
+                                     int aPageWidth, int aPageHeight)
+{
+  MOZ_ASSERT(aDC && mPDFDoc);
+  MOZ_ASSERT(static_cast<int>(aPageIndex) <
+             mPDFiumEngine->GetPageCount(mPDFDoc));
+
+  if (aPageWidth <= 0 || aPageHeight <= 0) {
+    return false;
+  }
+
+  FPDF_PAGE pdfPage = mPDFiumEngine->LoadPage(mPDFDoc, aPageIndex);
+  NS_ENSURE_TRUE(pdfPage, false);
+
+  int dcWidth = ::GetDeviceCaps(aDC, HORZRES);
+  int dcHeight = ::GetDeviceCaps(aDC, VERTRES);
+  float scaleFactor = ComputeScaleFactor(dcWidth,dcHeight,
+                                         aPageWidth, aPageHeight);
+  int savedState = ::SaveDC(aDC);
+  ::SetGraphicsMode(aDC, GM_ADVANCED);
+  XFORM xform = { 0 };
+  xform.eM11 = xform.eM22 = scaleFactor;
+  ::ModifyWorldTransform(aDC, &xform, MWT_LEFTMULTIPLY);
+
+  // The caller wanted all drawing to happen within the bounds specified.
+  // Based on scale calculations, our destination rect might be larger
+  // than the bounds. Set the clip rect to the bounds.
+  ::IntersectClipRect(aDC, 0, 0, aPageWidth, aPageHeight);
+
+  mPDFiumEngine->RenderPage(aDC, pdfPage,
+                            0, 0, aPageWidth, aPageHeight,
+                            0, FPDF_ANNOT | FPDF_PRINTING | FPDF_NO_CATCH);
+  mPDFiumEngine->ClosePage(pdfPage);
+  ::RestoreDC(aDC, savedState);
+
+  return true;
+}
+
+bool
+PDFViaEMFPrintHelper::DrawPage(HDC aPrinterDC, unsigned int aPageIndex,
+                               int aPageWidth, int aPageHeight)
+{
+  // There is a comment in Chromium.
+  // https://cs.chromium.org/chromium/src/pdf/pdfium/pdfium_engine.cc?rcl=9ad9f6860b4d6a4ec7f7f975b2c99672e02d5d49&l=4008
+  // Some PDFs seems to render very slowly if RenderPageToDC is directly used
+  // on a printer DC.
+  // The way Chromium works around the issue at the code linked above is to
+  // print to a bitmap and send that to a printer.  Instead of doing that we
+  // render to an EMF file and replay that on the printer DC.  It is unclear
+  // whether our approach will avoid the performance issues though.  Bug
+  // 1359298 covers investigating that.
+
+  MOZ_ASSERT(aPrinterDC);
+  WindowsEMF emf;
+  bool result = emf.InitForDrawing();
+  NS_ENSURE_TRUE(result, false);
+
+  result = RenderPageToDC(emf.GetDC(), aPageIndex, aPageWidth, aPageHeight);
+  NS_ENSURE_TRUE(result, false);
+
+  RECT printRect = {0, 0, aPageWidth, aPageHeight};
+  result = emf.Playback(aPrinterDC, &printRect);
+  return result;
+}
+
+bool
+PDFViaEMFPrintHelper::DrawPageToFile(const wchar_t* aFilePath,
+                                     unsigned int aPageIndex,
+                                     int aPageWidth, int aPageHeight)
+{
+  WindowsEMF emf;
+  bool result = emf.InitForDrawing(aFilePath);
+  NS_ENSURE_TRUE(result, false);
+
+  result = RenderPageToDC(emf.GetDC(), aPageIndex, aPageWidth, aPageHeight);
+  NS_ENSURE_TRUE(result, false);
+  return emf.SaveToFile();
+}
+
+void
+PDFViaEMFPrintHelper::CloseDocument()
+{
+  if (mPDFDoc) {
+    mPDFiumEngine->CloseDocument(mPDFDoc);
+    mPDFDoc = nullptr;
+    mPDFFileContents.clear();
+  }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/widget/windows/PDFViaEMFPrintHelper.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; 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/. */
+
+#ifndef PDFVIAEMFPRINTHELPER_H_
+#define PDFVIAEMFPRINTHELPER_H_
+
+#include "nsCOMPtr.h"
+#include "PDFiumEngineShim.h"
+#include "mozilla/Vector.h"
+
+/* include windows.h for the HDC definitions that we need. */
+#include <windows.h>
+
+class nsIFile;
+class nsFileInputStream;
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * This class helps draw a PDF file to a given Windows DC.
+ * To do that it first converts the PDF file to EMF.
+ * Windows EMF:
+ * https://msdn.microsoft.com/en-us/windows/hardware/drivers/print/emf-data-type
+ */
+class PDFViaEMFPrintHelper
+{
+public:
+  explicit PDFViaEMFPrintHelper(PRLibrary* aPDFiumLibrary);
+  ~PDFViaEMFPrintHelper();
+
+  /** Loads the specified PDF file. */
+  NS_IMETHOD OpenDocument(nsIFile *aFile);
+
+  /** Releases document buffer. */
+  void CloseDocument();
+
+  int GetPageCount() { return mPDFiumEngine->GetPageCount(mPDFDoc); }
+
+  /** Convert specified PDF page to EMF and draw the EMF onto the given DC. */
+  bool DrawPage(HDC aPrinterDC, unsigned int aPageIndex,
+                int aPageWidth, int aPageHeight);
+
+  /** Convert specified PDF page to EMF and save it to file. */
+  bool DrawPageToFile(const wchar_t* aFilePath, unsigned int aPageIndex,
+                      int aPageWidth, int aPageHeight);
+
+private:
+
+  bool LoadPDFDataToBuffer(nsIFile *aFile);
+
+  bool RenderPageToDC(HDC aDC, unsigned int aPageIndex,
+                      int aPageWidth, int aPageHeight);
+
+  UniquePtr<PDFiumEngineShim> mPDFiumEngine;
+  FPDF_DOCUMENT               mPDFDoc;
+  Vector<char>                mPDFFileContents;
+  PRLibrary*                  mPDFiumLibrary;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* PDFVIAEMFPRINTHELPER_H_ */
\ No newline at end of file
--- a/widget/windows/moz.build
+++ b/widget/windows/moz.build
@@ -15,16 +15,18 @@ EXPORTS += [
     'WinUtils.h',
 ]
 
 EXPORTS.mozilla.widget += [
     'AudioSession.h',
     'CompositorWidgetChild.h',
     'CompositorWidgetParent.h',
     'InProcessWinCompositorWidget.h',
+    'PDFiumEngineShim.h',
+    'PDFViaEMFPrintHelper.h',
     'WinCompositorWidget.h',
     'WinMessages.h',
     'WinModifierKeyState.h',
     'WinNativeEventData.h',
 ]
 
 UNIFIED_SOURCES += [
     'AudioSession.cpp',
@@ -88,16 +90,17 @@ if CONFIG['MOZ_CRASHREPORTER']:
     ]
 
 if CONFIG['NS_PRINTING']:
     UNIFIED_SOURCES += [
         'nsDeviceContextSpecWin.cpp',
         'nsPrintOptionsWin.cpp',
         'nsPrintSettingsWin.cpp',
         'PDFiumEngineShim.cpp',
+        'PDFViaEMFPrintHelper.cpp',
         'WindowsEMF.cpp',
     ]
 
 if CONFIG['NS_ENABLE_TSF']:
     SOURCES += [
         'TSFTextStore.cpp',
     ]