widget/windows/PDFViaEMFPrintHelper.cpp
author cku <cku@mozilla.com>
Wed, 01 Nov 2017 21:33:28 +0800
changeset 396003 f0df14d5edab03bf2f00b0ffe401713f85f5f100
parent 395724 2e0485d0998d88b1890093e4100904063548c967
child 396004 6354e568837e2c879492446c864da0388cd15e18
permissions -rw-r--r--
Bug 1399787 - Part 2.a. Add functions to load the content of a PDF from a FileDescriptor. r=jwatt All the functions added in Part 2 are utilities for sharing EMF/PDF contents between processes. MozReview-Commit-ID: 3qKosXH56kY

/* -*- 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"

/* Scale DC by keeping aspect ratio */
static
float ComputeScaleFactor(int aDCWidth, int aDCHeight,
                         int aPageWidth, int aPageHeight)
{
  MOZ_ASSERT(aPageWidth !=0 && aPageWidth != 0);

  return (aDCWidth >= aPageWidth && aDCHeight >= aPageHeight)
    ? 1.0 /* If page fits DC - no scaling needed. */
    : std::min(static_cast<float>(aDCWidth) / static_cast<float>(aPageWidth),         static_cast<float>(aDCHeight) / static_cast<float>(aPageHeight));
}

PDFViaEMFPrintHelper::PDFViaEMFPrintHelper()
  : mPDFDoc(nullptr)
  , mPrfile(nullptr)
{
}

PDFViaEMFPrintHelper::~PDFViaEMFPrintHelper()
{
  CloseDocument();
}

nsresult
PDFViaEMFPrintHelper::OpenDocument(nsIFile *aFile)
{
  MOZ_ASSERT(aFile);

  if (mPDFDoc) {
    MOZ_ASSERT_UNREACHABLE("We can only open one PDF at a time,"
                           "Use CloseDocument() to close the opened file"
                           "before calling OpenDocument()");
    return NS_ERROR_FAILURE;
  }


  nsAutoCString nativePath;
  nsresult rv = aFile->GetNativePath(nativePath);
  if (NS_FAILED(rv)) {
    return rv;
  }

  return OpenDocument(nativePath.get());
}

nsresult
PDFViaEMFPrintHelper::OpenDocument(const char* aFileName)
{
  MOZ_ASSERT(aFileName);

  if (mPDFDoc) {
    MOZ_ASSERT_UNREACHABLE("We can only open one PDF at a time,"
                           "Use CloseDocument() to close the opened file"
                           "before calling OpenDocument()");
    return NS_ERROR_FAILURE;
  }

  NS_ENSURE_TRUE(CreatePDFiumEngineIfNeed(), NS_ERROR_FAILURE);

  mPDFDoc = mPDFiumEngine->LoadDocument(aFileName, nullptr);
  NS_ENSURE_TRUE(mPDFDoc, NS_ERROR_FAILURE);

  return NS_OK;
}

nsresult
PDFViaEMFPrintHelper::OpenDocument(const FileDescriptor& aFD)
{
  MOZ_ASSERT(!mPrfile, "Forget to call CloseDocument?");

  if (mPDFDoc) {
    MOZ_ASSERT_UNREACHABLE("We can only open one PDF at a time, "
                           "Use CloseDocument() to close the opened file "
                           "before calling OpenDocument()");
    return NS_ERROR_FAILURE;
  }

  NS_ENSURE_TRUE(CreatePDFiumEngineIfNeed(), NS_ERROR_FAILURE);

  auto rawFD = aFD.ClonePlatformHandle();
  PRFileDesc* prfile = PR_ImportFile(PROsfd(rawFD.release()));
  NS_ENSURE_TRUE(prfile, NS_ERROR_FAILURE);

  mPDFDoc = mPDFiumEngine->LoadDocument(prfile, nullptr);
  if (!mPDFDoc) {
    PR_Close(prfile);
    return NS_ERROR_FAILURE;
  }

  // mPDFiumEngine keeps using this handle until we close mPDFDoc. Instead of
  // closing this HANDLE here, we close it in
  // PDFViaEMFPrintHelper::CloseDocument.
  mPrfile = prfile;

  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);

  float scaleFactor = ComputeScaleFactor(::GetDeviceCaps(aDC, HORZRES),
                                         ::GetDeviceCaps(aDC, VERTRES),
                                         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)
{
  MOZ_ASSERT(aPrinterDC);

  // OpenDocument might fail.
  if (!mPDFDoc) {
    MOZ_ASSERT_UNREACHABLE("Make sure OpenDocument return true before"
                           "using DrawPage.");
    return false;
  }

  // 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.

  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)
{
  // OpenDocument might fail.
  if (!mPDFDoc) {
    MOZ_ASSERT_UNREACHABLE("Make sure OpenDocument return true before"
                           "using DrawPageToFile.");
    return false;
  }

  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;
  }

  if (mPrfile) {
    PR_Close(mPrfile);
    mPrfile = nullptr;
  }
}

bool
PDFViaEMFPrintHelper::CreatePDFiumEngineIfNeed()
{
  if (!mPDFiumEngine) {
    mPDFiumEngine = PDFiumEngineShim::GetInstanceOrNull();
  }

  return !!mPDFiumEngine;
}