1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "PDFViaEMFPrintHelper.h"
7 #include "nsIFileStreams.h"
8 #include "WindowsEMF.h"
9 #include "nsFileStreams.h"
10 #include "mozilla/UniquePtrExtensions.h"
11 #include "mozilla/dom/File.h"
12 #include "mozilla/Unused.h"
13 #include "mozilla/ipc/ProtocolUtils.h"
14 #include "mozilla/FileUtils.h"
15 
16 /* Scale DC by keeping aspect ratio */
ComputeScaleFactor(int aDCWidth,int aDCHeight,int aPageWidth,int aPageHeight)17 static float ComputeScaleFactor(int aDCWidth, int aDCHeight, int aPageWidth,
18                                 int aPageHeight) {
19   MOZ_ASSERT(aPageWidth != 0 && aPageWidth != 0);
20 
21   return (aDCWidth >= aPageWidth && aDCHeight >= aPageHeight)
22              ? 1.0 /* If page fits DC - no scaling needed. */
23              : std::min(static_cast<float>(aDCWidth) /
24                             static_cast<float>(aPageWidth),
25                         static_cast<float>(aDCHeight) /
26                             static_cast<float>(aPageHeight));
27 }
28 
PDFViaEMFPrintHelper()29 PDFViaEMFPrintHelper::PDFViaEMFPrintHelper()
30     : mPDFDoc(nullptr), mPrfile(nullptr) {}
31 
~PDFViaEMFPrintHelper()32 PDFViaEMFPrintHelper::~PDFViaEMFPrintHelper() { CloseDocument(); }
33 
OpenDocument(nsIFile * aFile)34 nsresult PDFViaEMFPrintHelper::OpenDocument(nsIFile* aFile) {
35   MOZ_ASSERT(aFile);
36 
37   if (mPDFDoc) {
38     MOZ_ASSERT_UNREACHABLE(
39         "We can only open one PDF at a time,"
40         "Use CloseDocument() to close the opened file"
41         "before calling OpenDocument()");
42     return NS_ERROR_FAILURE;
43   }
44 
45   AutoFDClose prFileDesc;
46   nsresult rv = aFile->OpenNSPRFileDesc(PR_RDONLY, 0, &prFileDesc.rwget());
47   if (NS_FAILED(rv)) {
48     return rv;
49   }
50 
51   return OpenDocument(FileDescriptor(FileDescriptor::PlatformHandleType(
52       PR_FileDesc2NativeHandle(prFileDesc))));
53 }
54 
OpenDocument(const FileDescriptor & aFD)55 nsresult PDFViaEMFPrintHelper::OpenDocument(const FileDescriptor& aFD) {
56   MOZ_ASSERT(!mPrfile, "Forget to call CloseDocument?");
57 
58   if (mPDFDoc) {
59     MOZ_ASSERT_UNREACHABLE(
60         "We can only open one PDF at a time, "
61         "Use CloseDocument() to close the opened file "
62         "before calling OpenDocument()");
63     return NS_ERROR_FAILURE;
64   }
65 
66   NS_ENSURE_TRUE(CreatePDFiumEngineIfNeed(), NS_ERROR_FAILURE);
67 
68   auto rawFD = aFD.ClonePlatformHandle();
69   PRFileDesc* prfile = PR_ImportFile(PROsfd(rawFD.release()));
70   NS_ENSURE_TRUE(prfile, NS_ERROR_FAILURE);
71 
72   mPDFDoc = mPDFiumEngine->LoadDocument(prfile, nullptr);
73   if (!mPDFDoc) {
74     PR_Close(prfile);
75     return NS_ERROR_FAILURE;
76   }
77 
78   // mPDFiumEngine keeps using this handle until we close mPDFDoc. Instead of
79   // closing this HANDLE here, we close it in
80   // PDFViaEMFPrintHelper::CloseDocument.
81   mPrfile = prfile;
82 
83   return NS_OK;
84 }
85 
RenderPageToDC(HDC aDC,unsigned int aPageIndex,int aPageWidth,int aPageHeight)86 bool PDFViaEMFPrintHelper::RenderPageToDC(HDC aDC, unsigned int aPageIndex,
87                                           int aPageWidth, int aPageHeight) {
88   MOZ_ASSERT(aDC && mPDFDoc);
89   MOZ_ASSERT(static_cast<int>(aPageIndex) <
90              mPDFiumEngine->GetPageCount(mPDFDoc));
91 
92   if (aPageWidth <= 0 || aPageHeight <= 0) {
93     return false;
94   }
95 
96   FPDF_PAGE pdfPage = mPDFiumEngine->LoadPage(mPDFDoc, aPageIndex);
97   NS_ENSURE_TRUE(pdfPage, false);
98 
99   float scaleFactor = ComputeScaleFactor(::GetDeviceCaps(aDC, HORZRES),
100                                          ::GetDeviceCaps(aDC, VERTRES),
101                                          aPageWidth, aPageHeight);
102   int savedState = ::SaveDC(aDC);
103   ::SetGraphicsMode(aDC, GM_ADVANCED);
104   XFORM xform = {0};
105   xform.eM11 = xform.eM22 = scaleFactor;
106   ::ModifyWorldTransform(aDC, &xform, MWT_LEFTMULTIPLY);
107 
108   // The caller wanted all drawing to happen within the bounds specified.
109   // Based on scale calculations, our destination rect might be larger
110   // than the bounds. Set the clip rect to the bounds.
111   ::IntersectClipRect(aDC, 0, 0, aPageWidth, aPageHeight);
112 
113   mPDFiumEngine->RenderPage(aDC, pdfPage, 0, 0, aPageWidth, aPageHeight, 0,
114                             FPDF_ANNOT | FPDF_PRINTING | FPDF_NO_CATCH);
115   mPDFiumEngine->ClosePage(pdfPage);
116   ::RestoreDC(aDC, savedState);
117 
118   return true;
119 }
120 
DrawPage(HDC aPrinterDC,unsigned int aPageIndex,int aPageWidth,int aPageHeight)121 bool PDFViaEMFPrintHelper::DrawPage(HDC aPrinterDC, unsigned int aPageIndex,
122                                     int aPageWidth, int aPageHeight) {
123   MOZ_ASSERT(aPrinterDC);
124 
125   // OpenDocument might fail.
126   if (!mPDFDoc) {
127     MOZ_ASSERT_UNREACHABLE(
128         "Make sure OpenDocument return true before"
129         "using DrawPage.");
130     return false;
131   }
132 
133   // There is a comment in Chromium.
134   // https://cs.chromium.org/chromium/src/pdf/pdfium/pdfium_engine.cc?rcl=9ad9f6860b4d6a4ec7f7f975b2c99672e02d5d49&l=4008
135   // Some PDFs seems to render very slowly if RenderPageToDC is directly used
136   // on a printer DC.
137   // The way Chromium works around the issue at the code linked above is to
138   // print to a bitmap and send that to a printer.  Instead of doing that we
139   // render to an EMF file and replay that on the printer DC.  It is unclear
140   // whether our approach will avoid the performance issues though.  Bug
141   // 1359298 covers investigating that.
142 
143   WindowsEMF emf;
144   bool result = emf.InitForDrawing();
145   NS_ENSURE_TRUE(result, false);
146 
147   result = RenderPageToDC(emf.GetDC(), aPageIndex, aPageWidth, aPageHeight);
148   NS_ENSURE_TRUE(result, false);
149 
150   RECT printRect = {0, 0, aPageWidth, aPageHeight};
151   result = emf.Playback(aPrinterDC, printRect);
152   return result;
153 }
154 
SavePageToFile(const wchar_t * aFilePath,unsigned int aPageIndex,int aPageWidth,int aPageHeight)155 bool PDFViaEMFPrintHelper::SavePageToFile(const wchar_t* aFilePath,
156                                           unsigned int aPageIndex,
157                                           int aPageWidth, int aPageHeight) {
158   MOZ_ASSERT(aFilePath);
159 
160   // OpenDocument might fail.
161   if (!mPDFDoc) {
162     MOZ_ASSERT_UNREACHABLE(
163         "Make sure OpenDocument return true before"
164         "using DrawPageToFile.");
165     return false;
166   }
167 
168   WindowsEMF emf;
169   bool result = emf.InitForDrawing(aFilePath);
170   NS_ENSURE_TRUE(result, false);
171 
172   result = RenderPageToDC(emf.GetDC(), aPageIndex, aPageWidth, aPageHeight);
173   NS_ENSURE_TRUE(result, false);
174   return emf.SaveToFile();
175 }
176 
SavePageToBuffer(unsigned int aPageIndex,int aPageWidth,int aPageHeight,ipc::Shmem & aMem,mozilla::ipc::IShmemAllocator * aAllocator)177 bool PDFViaEMFPrintHelper::SavePageToBuffer(
178     unsigned int aPageIndex, int aPageWidth, int aPageHeight, ipc::Shmem& aMem,
179     mozilla::ipc::IShmemAllocator* aAllocator) {
180   MOZ_ASSERT(aAllocator);
181 
182   // OpenDocument might fail.
183   if (!mPDFDoc) {
184     MOZ_ASSERT_UNREACHABLE(
185         "Make sure OpenDocument return true before"
186         "using DrawPageToFile.");
187     return false;
188   }
189 
190   WindowsEMF emf;
191   bool result = emf.InitForDrawing();
192   NS_ENSURE_TRUE(result, false);
193 
194   result = RenderPageToDC(emf.GetDC(), aPageIndex, aPageWidth, aPageHeight);
195   NS_ENSURE_TRUE(result, false);
196 
197   UINT emfSize = emf.GetEMFContentSize();
198   NS_ENSURE_TRUE(emfSize != 0, false);
199 
200   auto shmType = ipc::SharedMemory::SharedMemoryType::TYPE_BASIC;
201   result = aAllocator->AllocShmem(emfSize, shmType, &aMem);
202   NS_ENSURE_TRUE(result, false);
203 
204   if (!emf.GetEMFContentBits(aMem.get<BYTE>())) {
205     aAllocator->DeallocShmem(aMem);
206     return false;
207     ;
208   }
209 
210   return true;
211 }
212 
CloseDocument()213 void PDFViaEMFPrintHelper::CloseDocument() {
214   if (mPDFDoc) {
215     mPDFiumEngine->CloseDocument(mPDFDoc);
216     mPDFDoc = nullptr;
217   }
218 
219   if (mPrfile) {
220     PR_Close(mPrfile);
221     mPrfile = nullptr;
222   }
223 }
224 
CreatePDFiumEngineIfNeed()225 bool PDFViaEMFPrintHelper::CreatePDFiumEngineIfNeed() {
226   if (!mPDFiumEngine) {
227     mPDFiumEngine = PDFiumEngineShim::GetInstanceOrNull();
228     MOZ_ASSERT(mPDFiumEngine);
229   }
230 
231   return !!mPDFiumEngine;
232 }