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 }