@esslassi/electron-printer 0.0.3 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -53
- package/binding.gyp +51 -33
- package/lib/electronPrinter.d.ts +33 -0
- package/lib/electronPrinter.js +38 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +17 -1
- package/package.json +47 -33
- package/src/linux_printer.cpp +171 -0
- package/src/linux_printer.h +21 -0
- package/src/mac_printer.cpp +166 -0
- package/src/mac_printer.h +22 -0
- package/src/main.cpp +22 -0
- package/src/print.cpp +272 -0
- package/src/printer_factory.cpp +20 -0
- package/src/printer_factory.h +13 -0
- package/src/printer_interface.h +29 -0
- package/src/windows_printer.cpp +210 -0
- package/src/windows_printer.h +35 -0
- package/.gitattributes +0 -2
- package/Gruntfile.js +0 -80
- package/LICENSE +0 -21
- package/binding.js +0 -244
- package/index.js +0 -1
- package/lib/binding.js +0 -244
- package/printer.js +0 -1
- package/src/hello_world.cc +0 -8
- package/src/macros.hh +0 -53
- package/src/node_printer.cc +0 -31
- package/src/node_printer.hpp +0 -77
- package/src/node_printer_win.cc +0 -586
- package/test/getDefaultPrinterName.test.js +0 -18
- package/test/getPrinters.test.js +0 -26
- package/test/incompleteFunctions.js +0 -56
- package/test/printDirect.test.js +0 -34
- package/test/sayMyName.test.js +0 -21
- package/tools/buildElectronLinux.sh +0 -10
- package/tools/buildElectronWindows.ps1 +0 -20
- package/tools/buildWindows.ps1 +0 -23
- package/tools/generateReleaseBuildsLinux.sh +0 -59
- package/tools/generateReleaseBuildsWindows.ps1 +0 -63
- package/tools/getSourceFiles.py +0 -12
- package/tools/remove_directory.py +0 -27
- package/tsconfig.json +0 -22
- package/types/index.d.ts +0 -56
package/src/node_printer_win.cc
DELETED
|
@@ -1,586 +0,0 @@
|
|
|
1
|
-
#include "node_printer.hpp"
|
|
2
|
-
#if _MSC_VER
|
|
3
|
-
#include <windows.h>
|
|
4
|
-
#include <Winspool.h>
|
|
5
|
-
#include <Wingdi.h>
|
|
6
|
-
#pragma comment(lib, "Winspool.lib")
|
|
7
|
-
#else
|
|
8
|
-
#error "Unsupported compiler for windows. Feel free to add it."
|
|
9
|
-
#endif
|
|
10
|
-
|
|
11
|
-
#include <string>
|
|
12
|
-
#include <map>
|
|
13
|
-
#include <utility>
|
|
14
|
-
#include <sstream>
|
|
15
|
-
#include <cstdio>
|
|
16
|
-
|
|
17
|
-
// Utility function to output messages
|
|
18
|
-
void ConsolePrint(const std::string& message) {
|
|
19
|
-
printf("%s\n", message.c_str());
|
|
20
|
-
fflush(stdout); // Ensure the output is flushed immediately
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Function to convert std::u16string to std::wstring
|
|
24
|
-
std::wstring u16stringToWString(const std::u16string& u16str) {
|
|
25
|
-
return std::wstring(u16str.begin(), u16str.end());
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
namespace{
|
|
29
|
-
typedef std::map<std::string, DWORD> StatusMapType;
|
|
30
|
-
|
|
31
|
-
/** Memory value class management to avoid memory leak
|
|
32
|
-
*/
|
|
33
|
-
template<typename Type>
|
|
34
|
-
class MemValue: public MemValueBase<Type> {
|
|
35
|
-
public:
|
|
36
|
-
/** Constructor of allocating iSizeKbytes bytes memory;
|
|
37
|
-
* @param iSizeKbytes size in bytes of required allocating memory
|
|
38
|
-
*/
|
|
39
|
-
MemValue(const DWORD iSizeKbytes) {
|
|
40
|
-
_value = (Type*)malloc(iSizeKbytes);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
~MemValue () {
|
|
44
|
-
free();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
Type* get() {
|
|
48
|
-
return _value;
|
|
49
|
-
}
|
|
50
|
-
protected:
|
|
51
|
-
virtual void free() {
|
|
52
|
-
if(_value != NULL)
|
|
53
|
-
{
|
|
54
|
-
::free(_value);
|
|
55
|
-
_value = NULL;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private:
|
|
60
|
-
Type* _value; // Declaration of the member variable
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
struct PrinterHandle
|
|
64
|
-
{
|
|
65
|
-
PrinterHandle(LPWSTR iPrinterName)
|
|
66
|
-
{
|
|
67
|
-
_ok = OpenPrinterW(iPrinterName, &_printer, NULL);
|
|
68
|
-
}
|
|
69
|
-
~PrinterHandle()
|
|
70
|
-
{
|
|
71
|
-
if(_ok)
|
|
72
|
-
{
|
|
73
|
-
ClosePrinter(_printer);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
operator HANDLE() {return _printer;}
|
|
77
|
-
operator bool() { return (!!_ok);}
|
|
78
|
-
HANDLE& operator *() { return _printer;}
|
|
79
|
-
HANDLE* operator ->() { return &_printer;}
|
|
80
|
-
const HANDLE& operator ->() const { return _printer;}
|
|
81
|
-
HANDLE _printer;
|
|
82
|
-
BOOL _ok;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const StatusMapType& getStatusMap()
|
|
86
|
-
{
|
|
87
|
-
static StatusMapType result;
|
|
88
|
-
if(!result.empty())
|
|
89
|
-
{
|
|
90
|
-
return result;
|
|
91
|
-
}
|
|
92
|
-
// add only first time
|
|
93
|
-
#define STATUS_PRINTER_ADD(value, type) result.insert(std::make_pair(value, type))
|
|
94
|
-
STATUS_PRINTER_ADD("BUSY", PRINTER_STATUS_BUSY);
|
|
95
|
-
STATUS_PRINTER_ADD("DOOR-OPEN", PRINTER_STATUS_DOOR_OPEN);
|
|
96
|
-
STATUS_PRINTER_ADD("ERROR", PRINTER_STATUS_ERROR);
|
|
97
|
-
STATUS_PRINTER_ADD("INITIALIZING", PRINTER_STATUS_INITIALIZING);
|
|
98
|
-
STATUS_PRINTER_ADD("IO-ACTIVE", PRINTER_STATUS_IO_ACTIVE);
|
|
99
|
-
STATUS_PRINTER_ADD("MANUAL-FEED", PRINTER_STATUS_MANUAL_FEED);
|
|
100
|
-
STATUS_PRINTER_ADD("NO-TONER", PRINTER_STATUS_NO_TONER);
|
|
101
|
-
STATUS_PRINTER_ADD("NOT-AVAILABLE", PRINTER_STATUS_NOT_AVAILABLE);
|
|
102
|
-
STATUS_PRINTER_ADD("OFFLINE", PRINTER_STATUS_OFFLINE);
|
|
103
|
-
STATUS_PRINTER_ADD("OUT-OF-MEMORY", PRINTER_STATUS_OUT_OF_MEMORY);
|
|
104
|
-
STATUS_PRINTER_ADD("OUTPUT-BIN-FULL", PRINTER_STATUS_OUTPUT_BIN_FULL);
|
|
105
|
-
STATUS_PRINTER_ADD("PAGE-PUNT", PRINTER_STATUS_PAGE_PUNT);
|
|
106
|
-
STATUS_PRINTER_ADD("PAPER-JAM", PRINTER_STATUS_PAPER_JAM);
|
|
107
|
-
STATUS_PRINTER_ADD("PAPER-OUT", PRINTER_STATUS_PAPER_OUT);
|
|
108
|
-
STATUS_PRINTER_ADD("PAPER-PROBLEM", PRINTER_STATUS_PAPER_PROBLEM);
|
|
109
|
-
STATUS_PRINTER_ADD("PAUSED", PRINTER_STATUS_PAUSED);
|
|
110
|
-
STATUS_PRINTER_ADD("PENDING-DELETION", PRINTER_STATUS_PENDING_DELETION);
|
|
111
|
-
STATUS_PRINTER_ADD("POWER-SAVE", PRINTER_STATUS_POWER_SAVE);
|
|
112
|
-
STATUS_PRINTER_ADD("PRINTING", PRINTER_STATUS_PRINTING);
|
|
113
|
-
STATUS_PRINTER_ADD("PROCESSING", PRINTER_STATUS_PROCESSING);
|
|
114
|
-
STATUS_PRINTER_ADD("SERVER-UNKNOWN", PRINTER_STATUS_SERVER_UNKNOWN);
|
|
115
|
-
STATUS_PRINTER_ADD("TONER-LOW", PRINTER_STATUS_TONER_LOW);
|
|
116
|
-
STATUS_PRINTER_ADD("USER-INTERVENTION", PRINTER_STATUS_USER_INTERVENTION);
|
|
117
|
-
STATUS_PRINTER_ADD("WAITING", PRINTER_STATUS_WAITING);
|
|
118
|
-
STATUS_PRINTER_ADD("WARMING-UP", PRINTER_STATUS_WARMING_UP);
|
|
119
|
-
#undef STATUS_PRINTER_ADD
|
|
120
|
-
return result;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const StatusMapType& getJobStatusMap()
|
|
124
|
-
{
|
|
125
|
-
static StatusMapType result;
|
|
126
|
-
if(!result.empty())
|
|
127
|
-
{
|
|
128
|
-
return result;
|
|
129
|
-
}
|
|
130
|
-
// add only first time
|
|
131
|
-
#define STATUS_PRINTER_ADD(value, type) result.insert(std::make_pair(value, type))
|
|
132
|
-
// Common statuses
|
|
133
|
-
STATUS_PRINTER_ADD("PRINTING", JOB_STATUS_PRINTING);
|
|
134
|
-
STATUS_PRINTER_ADD("PRINTED", JOB_STATUS_PRINTED);
|
|
135
|
-
STATUS_PRINTER_ADD("PAUSED", JOB_STATUS_PAUSED);
|
|
136
|
-
|
|
137
|
-
// Specific statuses
|
|
138
|
-
STATUS_PRINTER_ADD("BLOCKED-DEVQ", JOB_STATUS_BLOCKED_DEVQ);
|
|
139
|
-
STATUS_PRINTER_ADD("DELETED", JOB_STATUS_DELETED);
|
|
140
|
-
STATUS_PRINTER_ADD("DELETING", JOB_STATUS_DELETING);
|
|
141
|
-
STATUS_PRINTER_ADD("ERROR", JOB_STATUS_ERROR);
|
|
142
|
-
STATUS_PRINTER_ADD("OFFLINE", JOB_STATUS_OFFLINE);
|
|
143
|
-
STATUS_PRINTER_ADD("PAPEROUT", JOB_STATUS_PAPEROUT);
|
|
144
|
-
STATUS_PRINTER_ADD("RESTART", JOB_STATUS_RESTART);
|
|
145
|
-
STATUS_PRINTER_ADD("SPOOLING", JOB_STATUS_SPOOLING);
|
|
146
|
-
STATUS_PRINTER_ADD("USER-INTERVENTION", JOB_STATUS_USER_INTERVENTION);
|
|
147
|
-
// XP and later
|
|
148
|
-
#ifdef JOB_STATUS_COMPLETE
|
|
149
|
-
STATUS_PRINTER_ADD("COMPLETE", JOB_STATUS_COMPLETE);
|
|
150
|
-
#endif
|
|
151
|
-
#ifdef JOB_STATUS_RETAINED
|
|
152
|
-
STATUS_PRINTER_ADD("RETAINED", JOB_STATUS_RETAINED);
|
|
153
|
-
#endif
|
|
154
|
-
|
|
155
|
-
#undef STATUS_PRINTER_ADD
|
|
156
|
-
return result;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const StatusMapType& getAttributeMap()
|
|
160
|
-
{
|
|
161
|
-
static StatusMapType result;
|
|
162
|
-
if(!result.empty())
|
|
163
|
-
{
|
|
164
|
-
return result;
|
|
165
|
-
}
|
|
166
|
-
// add only first time
|
|
167
|
-
#define ATTRIBUTE_PRINTER_ADD(value, type) result.insert(std::make_pair(value, type))
|
|
168
|
-
ATTRIBUTE_PRINTER_ADD("DIRECT", PRINTER_ATTRIBUTE_DIRECT);
|
|
169
|
-
ATTRIBUTE_PRINTER_ADD("DO-COMPLETE-FIRST", PRINTER_ATTRIBUTE_DO_COMPLETE_FIRST);
|
|
170
|
-
ATTRIBUTE_PRINTER_ADD("ENABLE-DEVQ", PRINTER_ATTRIBUTE_ENABLE_DEVQ);
|
|
171
|
-
ATTRIBUTE_PRINTER_ADD("HIDDEN", PRINTER_ATTRIBUTE_HIDDEN);
|
|
172
|
-
ATTRIBUTE_PRINTER_ADD("KEEPPRINTEDJOBS", PRINTER_ATTRIBUTE_KEEPPRINTEDJOBS);
|
|
173
|
-
ATTRIBUTE_PRINTER_ADD("LOCAL", PRINTER_ATTRIBUTE_LOCAL);
|
|
174
|
-
ATTRIBUTE_PRINTER_ADD("NETWORK", PRINTER_ATTRIBUTE_NETWORK);
|
|
175
|
-
ATTRIBUTE_PRINTER_ADD("PUBLISHED", PRINTER_ATTRIBUTE_PUBLISHED);
|
|
176
|
-
ATTRIBUTE_PRINTER_ADD("QUEUED", PRINTER_ATTRIBUTE_QUEUED);
|
|
177
|
-
ATTRIBUTE_PRINTER_ADD("RAW-ONLY", PRINTER_ATTRIBUTE_RAW_ONLY);
|
|
178
|
-
ATTRIBUTE_PRINTER_ADD("SHARED", PRINTER_ATTRIBUTE_SHARED);
|
|
179
|
-
ATTRIBUTE_PRINTER_ADD("OFFLINE", PRINTER_ATTRIBUTE_WORK_OFFLINE);
|
|
180
|
-
// XP
|
|
181
|
-
#ifdef PRINTER_ATTRIBUTE_FAX
|
|
182
|
-
ATTRIBUTE_PRINTER_ADD("FAX", PRINTER_ATTRIBUTE_FAX);
|
|
183
|
-
#endif
|
|
184
|
-
// vista
|
|
185
|
-
#ifdef PRINTER_ATTRIBUTE_FRIENDLY_NAME
|
|
186
|
-
ATTRIBUTE_PRINTER_ADD("FRIENDLY-NAME", PRINTER_ATTRIBUTE_FRIENDLY_NAME);
|
|
187
|
-
ATTRIBUTE_PRINTER_ADD("MACHINE", PRINTER_ATTRIBUTE_MACHINE);
|
|
188
|
-
ATTRIBUTE_PRINTER_ADD("PUSHED-USER", PRINTER_ATTRIBUTE_PUSHED_USER);
|
|
189
|
-
ATTRIBUTE_PRINTER_ADD("PUSHED-MACHINE", PRINTER_ATTRIBUTE_PUSHED_MACHINE);
|
|
190
|
-
#endif
|
|
191
|
-
// server 2003
|
|
192
|
-
#ifdef PRINTER_ATTRIBUTE_TS
|
|
193
|
-
ATTRIBUTE_PRINTER_ADD("TS", PRINTER_ATTRIBUTE_TS);
|
|
194
|
-
#endif
|
|
195
|
-
#undef ATTRIBUTE_PRINTER_ADD
|
|
196
|
-
return result;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const StatusMapType& getJobCommandMap()
|
|
200
|
-
{
|
|
201
|
-
static StatusMapType result;
|
|
202
|
-
if(!result.empty())
|
|
203
|
-
{
|
|
204
|
-
return result;
|
|
205
|
-
}
|
|
206
|
-
// add only first time
|
|
207
|
-
#define COMMAND_JOB_ADD(value, type) result.insert(std::make_pair(value, type))
|
|
208
|
-
COMMAND_JOB_ADD("CANCEL", JOB_CONTROL_CANCEL);
|
|
209
|
-
COMMAND_JOB_ADD("PAUSE", JOB_CONTROL_PAUSE);
|
|
210
|
-
COMMAND_JOB_ADD("RESTART", JOB_CONTROL_RESTART);
|
|
211
|
-
COMMAND_JOB_ADD("RESUME", JOB_CONTROL_RESUME);
|
|
212
|
-
COMMAND_JOB_ADD("DELETE", JOB_CONTROL_DELETE);
|
|
213
|
-
COMMAND_JOB_ADD("SENT-TO-PRINTER", JOB_CONTROL_SENT_TO_PRINTER);
|
|
214
|
-
COMMAND_JOB_ADD("LAST-PAGE-EJECTED", JOB_CONTROL_LAST_PAGE_EJECTED);
|
|
215
|
-
#ifdef JOB_CONTROL_RETAIN
|
|
216
|
-
COMMAND_JOB_ADD("RETAIN", JOB_CONTROL_RETAIN);
|
|
217
|
-
#endif
|
|
218
|
-
#ifdef JOB_CONTROL_RELEASE
|
|
219
|
-
COMMAND_JOB_ADD("RELEASE", JOB_CONTROL_RELEASE);
|
|
220
|
-
#endif
|
|
221
|
-
#undef COMMAND_JOB_ADD
|
|
222
|
-
return result;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
void parseJobObject(JOB_INFO_2W *job, Napi::Object result_printer_job)
|
|
226
|
-
{
|
|
227
|
-
Napi::Env env = result_printer_job.Env(); // info.Env();
|
|
228
|
-
// Common fields
|
|
229
|
-
ADD_NAPI_NUMBER_PROPERTY(result_printer_job, "id", job->JobId);
|
|
230
|
-
// Example usage of the string property macro
|
|
231
|
-
ADD_NAPI_STRING_PROPERTY(result_printer_job, "printerName", pPrinterName);
|
|
232
|
-
ADD_NAPI_STRING_PROPERTY(result_printer_job, "user", pUserName);
|
|
233
|
-
ADD_NAPI_STRING_PROPERTY(result_printer_job, "format", pDatatype);
|
|
234
|
-
result_printer_job.Set(Napi::String::New(env, "priority"), Napi::Number::New(env, job->Priority));
|
|
235
|
-
result_printer_job.Set(Napi::String::New(env, "size"), Napi::Number::New(env, job->Size));
|
|
236
|
-
Napi::Array result_printer_job_status = Napi::Array::New(env);
|
|
237
|
-
int i_status = 0;
|
|
238
|
-
for (const auto &itStatus : getJobStatusMap())
|
|
239
|
-
{
|
|
240
|
-
if (job->Status & itStatus.second)
|
|
241
|
-
{
|
|
242
|
-
result_printer_job_status.Set(i_status++, Napi::String::New(env, itStatus.first.c_str()));
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
if ((job->pStatus != NULL) && (*job->pStatus != L'\0'))
|
|
246
|
-
{
|
|
247
|
-
result_printer_job_status.Set(i_status++, Napi::String::New(env, (char16_t *)job->pStatus));
|
|
248
|
-
}
|
|
249
|
-
result_printer_job.Set(Napi::String::New(env, "status"), result_printer_job_status);
|
|
250
|
-
// Specific fields
|
|
251
|
-
ADD_NAPI_STRING_PROPERTY(result_printer_job, "machineName", pMachineName);
|
|
252
|
-
ADD_NAPI_STRING_PROPERTY(result_printer_job, "document", pDocument);
|
|
253
|
-
ADD_NAPI_STRING_PROPERTY(result_printer_job, "notifyName", pNotifyName);
|
|
254
|
-
ADD_NAPI_STRING_PROPERTY(result_printer_job, "printProcessor", pPrintProcessor);
|
|
255
|
-
ADD_NAPI_STRING_PROPERTY(result_printer_job, "parameters", pParameters);
|
|
256
|
-
ADD_NAPI_STRING_PROPERTY(result_printer_job, "driverName", pDriverName);
|
|
257
|
-
result_printer_job.Set(Napi::String::New(env, "position"), Napi::Number::New(env, job->Position));
|
|
258
|
-
result_printer_job.Set(Napi::String::New(env, "startTime"), Napi::Number::New(env, job->StartTime));
|
|
259
|
-
result_printer_job.Set(Napi::String::New(env, "untilTime"), Napi::Number::New(env, job->UntilTime));
|
|
260
|
-
result_printer_job.Set(Napi::String::New(env, "totalPages"), Napi::Number::New(env, job->TotalPages));
|
|
261
|
-
result_printer_job.Set(Napi::String::New(env, "time"), Napi::Number::New(env, job->Time));
|
|
262
|
-
result_printer_job.Set(Napi::String::New(env, "pagesPrinted"), Napi::Number::New(env, job->PagesPrinted));
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
std::string getLastErrorCodeAndMessage() {
|
|
266
|
-
std::ostringstream s;
|
|
267
|
-
DWORD erroCode = GetLastError();
|
|
268
|
-
s << "code: " << erroCode;
|
|
269
|
-
DWORD retSize;
|
|
270
|
-
LPTSTR pTemp = NULL;
|
|
271
|
-
retSize = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|
|
|
272
|
-
FORMAT_MESSAGE_FROM_SYSTEM|
|
|
273
|
-
FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
|
274
|
-
NULL,
|
|
275
|
-
erroCode,
|
|
276
|
-
LANG_NEUTRAL,
|
|
277
|
-
(LPTSTR)&pTemp,
|
|
278
|
-
0,
|
|
279
|
-
NULL );
|
|
280
|
-
if (retSize && pTemp != NULL) {
|
|
281
|
-
//pTemp[strlen(pTemp)-2]='\0'; //remove cr and newline character
|
|
282
|
-
//TODO: check if it is needed to convert c string to std::string
|
|
283
|
-
std::string stringMessage(pTemp);
|
|
284
|
-
s << ", message: " << stringMessage;
|
|
285
|
-
LocalFree((HLOCAL)pTemp);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return s.str();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
std::string retrieveAndParseJobs(const LPWSTR iPrinterName, const DWORD& iTotalJobs, Napi::Object result_printer_jobs, PrinterHandle& iPrinterHandle)
|
|
292
|
-
{
|
|
293
|
-
Napi::Env env = result_printer_jobs.Env();
|
|
294
|
-
DWORD bytes_needed = 0, totalJobs = 0;
|
|
295
|
-
BOOL bError = EnumJobsW(*iPrinterHandle, 0, iTotalJobs, 2, NULL, bytes_needed, &bytes_needed, &totalJobs);
|
|
296
|
-
MemValue<JOB_INFO_2W> jobs(bytes_needed);
|
|
297
|
-
if(!jobs)
|
|
298
|
-
{
|
|
299
|
-
std::string error_str("Error on allocating memory for jobs: ");
|
|
300
|
-
error_str += getLastErrorCodeAndMessage();
|
|
301
|
-
Napi::Object result_printer_job = Napi::Object::New(env);
|
|
302
|
-
result_printer_job.Set(NAPI_STRING_NEW_UTF8(env, "error"), NAPI_STRING_NEW_UTF8(env, error_str.c_str()));
|
|
303
|
-
result_printer_jobs.Set(uint32_t(0), result_printer_job);
|
|
304
|
-
return std::string("");
|
|
305
|
-
}
|
|
306
|
-
DWORD dummy_bytes = 0;
|
|
307
|
-
bError = EnumJobsW(*iPrinterHandle, 0, iTotalJobs, 2, (LPBYTE)jobs.get(), bytes_needed, &dummy_bytes, &totalJobs);
|
|
308
|
-
if(!bError)
|
|
309
|
-
{
|
|
310
|
-
std::string error_str("Error on EnumJobsW: ");
|
|
311
|
-
error_str += getLastErrorCodeAndMessage();
|
|
312
|
-
Napi::Object result_printer_job = Napi::Object::New(env);
|
|
313
|
-
result_printer_job.Set(NAPI_STRING_NEW_UTF8(env, "error"), NAPI_STRING_NEW_UTF8(env, error_str.c_str()));
|
|
314
|
-
result_printer_jobs.Set(uint32_t(0), result_printer_job);
|
|
315
|
-
return std::string("");
|
|
316
|
-
}
|
|
317
|
-
JOB_INFO_2W *job = jobs.get();
|
|
318
|
-
for(DWORD i = 0; i < totalJobs; ++i, ++job)
|
|
319
|
-
{
|
|
320
|
-
Napi::Object result_printer_job = Napi::Object::New(env);
|
|
321
|
-
parseJobObject(job, result_printer_job);
|
|
322
|
-
result_printer_jobs.Set(i, result_printer_job);
|
|
323
|
-
}
|
|
324
|
-
return std::string("");
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
std::string parsePrinterInfo(const PRINTER_INFO_2W *printer, Napi::Object result_printer, PrinterHandle& iPrinterHandle) {
|
|
328
|
-
Napi::Env env = result_printer.Env();
|
|
329
|
-
#define ADD_V8_STRING_PROPERTY(name, key) if((printer->##key != NULL) && (*printer->##key != L'\0')) \
|
|
330
|
-
{ \
|
|
331
|
-
result_printer.Set(Napi::String::New(result_printer.Env(), #name), Napi::String::New(result_printer.Env(), (char16_t*)printer->##key)); \
|
|
332
|
-
}
|
|
333
|
-
//LPTSTR pPrinterName;
|
|
334
|
-
ADD_V8_STRING_PROPERTY(name, pPrinterName)
|
|
335
|
-
//LPTSTR pServerName;
|
|
336
|
-
ADD_V8_STRING_PROPERTY(serverName, pServerName)
|
|
337
|
-
//LPTSTR pShareName;
|
|
338
|
-
ADD_V8_STRING_PROPERTY(shareName, pShareName)
|
|
339
|
-
//LPTSTR pPortName;
|
|
340
|
-
ADD_V8_STRING_PROPERTY(portName, pPortName)
|
|
341
|
-
//LPTSTR pDriverName;
|
|
342
|
-
ADD_V8_STRING_PROPERTY(driverName, pDriverName)
|
|
343
|
-
//LPTSTR pComment;
|
|
344
|
-
ADD_V8_STRING_PROPERTY(comment, pComment)
|
|
345
|
-
//LPTSTR pLocation;
|
|
346
|
-
ADD_V8_STRING_PROPERTY(location, pLocation)
|
|
347
|
-
//LPTSTR pSepFile;
|
|
348
|
-
ADD_V8_STRING_PROPERTY(sepFile, pSepFile)
|
|
349
|
-
//LPTSTR pPrintProcessor;
|
|
350
|
-
ADD_V8_STRING_PROPERTY(printProcessor, pPrintProcessor)
|
|
351
|
-
//LPTSTR pDatatype;
|
|
352
|
-
ADD_V8_STRING_PROPERTY(datatype, pDatatype)
|
|
353
|
-
//LPTSTR pParameters;
|
|
354
|
-
ADD_V8_STRING_PROPERTY(parameters, pParameters)
|
|
355
|
-
#undef ADD_V8_STRING_PROPERTY
|
|
356
|
-
//DWORD Status;
|
|
357
|
-
// statuses from:
|
|
358
|
-
// http://msdn.microsoft.com/en-gb/library/windows/desktop/dd162845(v=vs.85).aspx
|
|
359
|
-
Napi::Array result_printer_status = Napi::Array::New(env);
|
|
360
|
-
int i_status = 0;
|
|
361
|
-
for (const auto& itStatus : getStatusMap()) {
|
|
362
|
-
if (printer->Status & itStatus.second) {
|
|
363
|
-
result_printer_status.Set(i_status++, Napi::String::New(env, itStatus.first.c_str()));
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
result_printer.Set(Napi::String::New(env, "status"), result_printer_status);
|
|
367
|
-
result_printer.Set(Napi::String::New(env, "statusNumber"), Napi::Number::New(env, printer->Status));
|
|
368
|
-
// Attributes
|
|
369
|
-
Napi::Array result_printer_attributes = Napi::Array::New(env);
|
|
370
|
-
int i_attribute = 0;
|
|
371
|
-
for (const auto& itAttribute : getAttributeMap()) {
|
|
372
|
-
if (printer->Attributes & itAttribute.second) {
|
|
373
|
-
result_printer_attributes.Set(i_attribute++, Napi::String::New(env, itAttribute.first.c_str()));
|
|
374
|
-
}
|
|
375
|
-
result_printer.Set(Napi::String::New(env, "attributes"), result_printer_attributes);
|
|
376
|
-
// Other properties
|
|
377
|
-
result_printer.Set(Napi::String::New(env, "priority"), Napi::Number::New(env, printer->Priority));
|
|
378
|
-
result_printer.Set(Napi::String::New(env, "defaultPriority"), Napi::Number::New(env, printer->DefaultPriority));
|
|
379
|
-
result_printer.Set(Napi::String::New(env, "averagePPM"), Napi::Number::New(env, printer->AveragePPM));
|
|
380
|
-
|
|
381
|
-
if (printer->StartTime > 0) {
|
|
382
|
-
result_printer.Set(Napi::String::New(env, "startTime"), Napi::Number::New(env, printer->StartTime));
|
|
383
|
-
}
|
|
384
|
-
if (printer->UntilTime > 0) {
|
|
385
|
-
result_printer.Set(Napi::String::New(env, "untilTime"), Napi::Number::New(env, printer->UntilTime));
|
|
386
|
-
}
|
|
387
|
-
if (printer->cJobs > 0) {
|
|
388
|
-
Napi::Array result_printer_jobs = Napi::Array::New(env, printer->cJobs);
|
|
389
|
-
std::string error_str = retrieveAndParseJobs(printer->pPrinterName, printer->cJobs, result_printer_jobs, iPrinterHandle);
|
|
390
|
-
if (!error_str.empty()) {
|
|
391
|
-
return error_str;
|
|
392
|
-
}
|
|
393
|
-
result_printer.Set(Napi::String::New(env, "jobs"), result_printer_jobs);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return "";
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
MY_NODE_MODULE_CALLBACK(getPrinters)
|
|
406
|
-
{
|
|
407
|
-
MY_NODE_MODULE_HANDLESCOPE;
|
|
408
|
-
DWORD printers_size = 0;
|
|
409
|
-
DWORD printers_size_bytes = 0, dummyBytes = 0;
|
|
410
|
-
DWORD Level = 2;
|
|
411
|
-
DWORD flags = PRINTER_ENUM_LOCAL | PRINTER_ENUM_CONNECTIONS;
|
|
412
|
-
// First try to retrieve the number of printers
|
|
413
|
-
BOOL bError = EnumPrintersW(flags, NULL, Level, NULL, 0, &printers_size_bytes, &printers_size);
|
|
414
|
-
// Check for errors in the first call
|
|
415
|
-
if (!bError && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
|
|
416
|
-
RETURN_EXCEPTION_STR(MY_NODE_MODULE_ENV, "Error retrieving printer size.");
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
// Check if we got a valid size
|
|
420
|
-
if (printers_size_bytes == 0) {
|
|
421
|
-
RETURN_EXCEPTION_STR(info.Env(), "No printers found or invalid size returned.");
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Allocate the required memory
|
|
425
|
-
MemValue<PRINTER_INFO_2W> printers(printers_size_bytes);
|
|
426
|
-
if (!printers.get()) // Make sure to check the pointer
|
|
427
|
-
{
|
|
428
|
-
RETURN_EXCEPTION_STR(MY_NODE_MODULE_ENV, "Error allocating memory for printers");
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Second call to retrieve printer information
|
|
432
|
-
bError = EnumPrintersW(flags, NULL, Level, (LPBYTE)(printers.get()), printers_size_bytes, &dummyBytes, &printers_size);
|
|
433
|
-
if (!bError)
|
|
434
|
-
{
|
|
435
|
-
std::string error_str("Error on EnumPrinters");
|
|
436
|
-
error_str += getLastErrorCodeAndMessage();
|
|
437
|
-
RETURN_EXCEPTION_STR(MY_NODE_MODULE_ENV, error_str.c_str());
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Prepare result array
|
|
441
|
-
Napi::Array result = Napi::Array::New(MY_NODE_MODULE_ENV, printers_size);
|
|
442
|
-
PRINTER_INFO_2W *printer = printers.get();
|
|
443
|
-
DWORD i = 0;
|
|
444
|
-
for (; i < printers_size; ++i, ++printer)
|
|
445
|
-
{
|
|
446
|
-
Napi::Object a_printer = Napi::Object::New(MY_NODE_MODULE_ENV);
|
|
447
|
-
PrinterHandle printerHandle((LPWSTR)(printer->pPrinterName));
|
|
448
|
-
std::string error_str = parsePrinterInfo(printer, a_printer, printerHandle);
|
|
449
|
-
if (!error_str.empty())
|
|
450
|
-
{
|
|
451
|
-
RETURN_EXCEPTION_STR(MY_NODE_MODULE_ENV, error_str.c_str());
|
|
452
|
-
}
|
|
453
|
-
result.Set(i, a_printer);
|
|
454
|
-
}
|
|
455
|
-
MY_NODE_MODULE_RETURN_VALUE(result);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
MY_NODE_MODULE_CALLBACK(getDefaultPrinterName)
|
|
459
|
-
{
|
|
460
|
-
MY_NODE_MODULE_HANDLESCOPE;
|
|
461
|
-
DWORD cSize = 0;
|
|
462
|
-
GetDefaultPrinterW(NULL, &cSize);
|
|
463
|
-
|
|
464
|
-
if(cSize == 0) {
|
|
465
|
-
MY_NODE_MODULE_RETURN_VALUE(NAPI_STRING_NEW_UTF8(MY_NODE_MODULE_ENV, ""));
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
MemValue<uint16_t> bPrinterName(cSize*sizeof(uint16_t));
|
|
469
|
-
BOOL res = GetDefaultPrinterW((LPWSTR)(bPrinterName.get()), &cSize);
|
|
470
|
-
|
|
471
|
-
if(!res) {
|
|
472
|
-
MY_NODE_MODULE_RETURN_VALUE(NAPI_STRING_NEW_UTF8(MY_NODE_MODULE_ENV, ""));
|
|
473
|
-
}
|
|
474
|
-
MY_NODE_MODULE_RETURN_VALUE(NAPI_STRING_NEW_2BYTES(MY_NODE_MODULE_ENV, bPrinterName.get()));
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
MY_NODE_MODULE_CALLBACK(printDirect)
|
|
478
|
-
{
|
|
479
|
-
MY_NODE_MODULE_HANDLESCOPE;
|
|
480
|
-
// Ensure the correct number of arguments
|
|
481
|
-
if (info.Length() < 5) {
|
|
482
|
-
Napi::TypeError::New(env, "Expected 5 arguments").ThrowAsJavaScriptException();
|
|
483
|
-
return env.Null();
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// Check and extract the first argument (string or buffer)
|
|
487
|
-
std::string data;
|
|
488
|
-
if (!getStringOrBufferFromNapiValue(info[0], data)) {
|
|
489
|
-
Napi::TypeError::New(env, "Argument 0 must be a string or Buffer").ThrowAsJavaScriptException();
|
|
490
|
-
return env.Null();
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Check and extract the second argument (string)
|
|
494
|
-
if (!info[1].IsString()) {
|
|
495
|
-
Napi::TypeError::New(env, "Argument 1 must be a string").ThrowAsJavaScriptException();
|
|
496
|
-
return env.Null();
|
|
497
|
-
}
|
|
498
|
-
std::u16string u16PrinterName = info[1].As<Napi::String>().Utf16Value();
|
|
499
|
-
std::wstring printername = u16stringToWString(u16PrinterName);
|
|
500
|
-
|
|
501
|
-
// Check and extract the third argument (string)
|
|
502
|
-
if (!info[2].IsString()) {
|
|
503
|
-
Napi::TypeError::New(env, "Argument 2 must be a string").ThrowAsJavaScriptException();
|
|
504
|
-
return env.Null();
|
|
505
|
-
}
|
|
506
|
-
std::u16string u16DocName = info[2].As<Napi::String>().Utf16Value();
|
|
507
|
-
std::wstring docname = u16stringToWString(u16DocName);
|
|
508
|
-
|
|
509
|
-
// Check and extract the fourth argument (string)
|
|
510
|
-
if (!info[3].IsString()) {
|
|
511
|
-
Napi::TypeError::New(env, "Argument 3 must be a string").ThrowAsJavaScriptException();
|
|
512
|
-
return env.Null();
|
|
513
|
-
}
|
|
514
|
-
std::u16string u16Type = info[3].As<Napi::String>().Utf16Value();
|
|
515
|
-
std::wstring type = u16stringToWString(u16Type);
|
|
516
|
-
|
|
517
|
-
// Convert std::wstring to LPWSTR (mutable wide string pointer)
|
|
518
|
-
std::vector<wchar_t> printernameMutable(printername.begin(), printername.end());
|
|
519
|
-
printernameMutable.push_back(L'\0'); // Ensure null-termination
|
|
520
|
-
|
|
521
|
-
PrinterHandle printerHandle(printernameMutable.data());
|
|
522
|
-
if (!printerHandle) {
|
|
523
|
-
std::string error_str("Error on PrinterHandle: ");
|
|
524
|
-
error_str += getLastErrorCodeAndMessage();
|
|
525
|
-
Napi::TypeError::New(env, error_str).ThrowAsJavaScriptException();
|
|
526
|
-
return env.Null();
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// Fill in the structure with info about this "document."
|
|
530
|
-
DOC_INFO_1W DocInfo;
|
|
531
|
-
DocInfo.pDocName = const_cast<LPWSTR>(docname.c_str());
|
|
532
|
-
DocInfo.pOutputFile = NULL;
|
|
533
|
-
DocInfo.pDatatype = const_cast<LPWSTR>(type.c_str());
|
|
534
|
-
|
|
535
|
-
// Inform the spooler the document is beginning.
|
|
536
|
-
DWORD dwJob = StartDocPrinterW(*printerHandle, 1, (LPBYTE)&DocInfo);
|
|
537
|
-
if (dwJob > 0) {
|
|
538
|
-
// Start a page.
|
|
539
|
-
BOOL bStatus = StartPagePrinter(*printerHandle);
|
|
540
|
-
if (bStatus) {
|
|
541
|
-
// Send the data to the printer.
|
|
542
|
-
DWORD dwBytesWritten = 0L;
|
|
543
|
-
bStatus = WritePrinter(*printerHandle, (LPVOID)(data.c_str()), (DWORD)data.size(), &dwBytesWritten);
|
|
544
|
-
EndPagePrinter(*printerHandle);
|
|
545
|
-
|
|
546
|
-
// Check to see if correct number of bytes were written.
|
|
547
|
-
if (dwBytesWritten != data.size()) {
|
|
548
|
-
Napi::TypeError::New(env, "Not all bytes were sent").ThrowAsJavaScriptException();
|
|
549
|
-
return env.Null();
|
|
550
|
-
}
|
|
551
|
-
} else {
|
|
552
|
-
std::string error_str("StartPagePrinter error: ");
|
|
553
|
-
error_str += getLastErrorCodeAndMessage();
|
|
554
|
-
Napi::TypeError::New(env, error_str).ThrowAsJavaScriptException();
|
|
555
|
-
return env.Null();
|
|
556
|
-
}
|
|
557
|
-
// Inform the spooler that the document is ending.
|
|
558
|
-
EndDocPrinter(*printerHandle);
|
|
559
|
-
} else {
|
|
560
|
-
std::string error_str("StartDocPrinterW error: ");
|
|
561
|
-
error_str += getLastErrorCodeAndMessage();
|
|
562
|
-
Napi::TypeError::New(env, error_str).ThrowAsJavaScriptException();
|
|
563
|
-
return env.Null();
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
MY_NODE_MODULE_RETURN_VALUE(Napi::Number::New(env, dwJob));
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
MY_NODE_MODULE_CALLBACK(getPrinter)
|
|
571
|
-
{
|
|
572
|
-
MY_NODE_MODULE_HANDLESCOPE;
|
|
573
|
-
MY_NODE_MODULE_RETURN_VALUE(NAPI_STRING_NEW_UTF8(MY_NODE_MODULE_ENV, "getPrinter is not implemented yet, request for a PR if required"));
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
MY_NODE_MODULE_CALLBACK(printFile)
|
|
577
|
-
{
|
|
578
|
-
MY_NODE_MODULE_HANDLESCOPE;
|
|
579
|
-
RETURN_EXCEPTION(MY_NODE_MODULE_ENV, "Not yet implemented on Windows");
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
MY_NODE_MODULE_CALLBACK(getSupportedPrintFormats)
|
|
583
|
-
{
|
|
584
|
-
MY_NODE_MODULE_HANDLESCOPE;
|
|
585
|
-
RETURN_EXCEPTION(MY_NODE_MODULE_ENV, "Not yet implemented, Request for a PR");
|
|
586
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
const assert = require('assert/strict');
|
|
2
|
-
const { test, beforeEach } = require('node:test');
|
|
3
|
-
|
|
4
|
-
let addon;
|
|
5
|
-
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
addon = require('../lib');
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test('Ensure getDefaultPrinterName function exists', (t) => {
|
|
11
|
-
assert.ok(addon.getDefaultPrinterName, 'getDefaultPrinterName function should exist');
|
|
12
|
-
assert.strictEqual(typeof addon.getDefaultPrinterName, 'function', 'getDefaultPrinterName should be a function');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('Ensure getDefaultPrinterName returns a string', (t) => {
|
|
16
|
-
const result = addon.getDefaultPrinterName();
|
|
17
|
-
assert.strictEqual(typeof result, "string", 'getDefaultPrinterName should return a string');
|
|
18
|
-
});
|
package/test/getPrinters.test.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
const assert = require('assert/strict');
|
|
2
|
-
const { test, beforeEach } = require('node:test');
|
|
3
|
-
|
|
4
|
-
let addon;
|
|
5
|
-
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
addon = require('../lib');
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test('Ensure getPrinters function exists', (t) => {
|
|
11
|
-
assert.ok(addon.getPrinters, 'getPrinters function should exist');
|
|
12
|
-
assert.strictEqual(typeof addon.getPrinters, 'function', 'getPrinters should be a function');
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test('Ensure getPrinters returns an array', (t) => {
|
|
16
|
-
const printers = addon.getPrinters();
|
|
17
|
-
assert.ok(Array.isArray(printers), 'getPrinters should return an array');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test('Ensure getPrinters handles no printers scenario', (t) => {
|
|
21
|
-
const printers = addon.getPrinters();
|
|
22
|
-
assert.ok(Array.isArray(printers), 'getPrinters should return an array');
|
|
23
|
-
if (printers.length > 0) {
|
|
24
|
-
assert.strictEqual(typeof printers[0], 'object', 'Each printer should be an object');
|
|
25
|
-
}
|
|
26
|
-
});
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
const assert = require('assert/strict');
|
|
2
|
-
const { test, beforeEach } = require('node:test');
|
|
3
|
-
|
|
4
|
-
let addon;
|
|
5
|
-
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
addon = require('../lib');
|
|
8
|
-
});
|
|
9
|
-
|
|
10
|
-
test('Ensure Non-functional apis do not lead any issues', (t) => {
|
|
11
|
-
assert.ok(addon, 'Non-functional apis');
|
|
12
|
-
// Add checks for specific functions if needed
|
|
13
|
-
assert.strictEqual(typeof addon.getPrinter, 'function', 'Addon should have getPrinter');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
// Additional test cases
|
|
17
|
-
test('Test getPrinter behavior', (t) => {
|
|
18
|
-
const result = addon.getPrinter();
|
|
19
|
-
assert.strictEqual(result, 'getPrinter is not implemented yet, request for a PR if required', 'getPrinter is not implemented yet, request for a PR if required"');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('Test printFile behavior', (t) => {
|
|
23
|
-
const filename = process.argv[2] || __filename; // use this file as an input
|
|
24
|
-
if( process.platform != 'win32') {
|
|
25
|
-
addon.printFile({filename:filename,
|
|
26
|
-
printer: process.env[3], // printer name, if missing then will print to default printer
|
|
27
|
-
success:function(jobID){
|
|
28
|
-
console.log("addon.printFile: Job ID: "+jobID);
|
|
29
|
-
},
|
|
30
|
-
error:function(err){
|
|
31
|
-
console.log(err);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
} else {
|
|
35
|
-
// not yet implemented, use printDirect and text
|
|
36
|
-
var fs = require('fs');
|
|
37
|
-
addon.printDirect({data:fs.readFileSync(filename),
|
|
38
|
-
printer: addon.getDefaultPrinterName(), // printer name, if missing then will print to default printer
|
|
39
|
-
type: "RAW",
|
|
40
|
-
success:function(jobID){
|
|
41
|
-
assert.equal(jobID>0, true, 'addon.printFile');
|
|
42
|
-
},
|
|
43
|
-
error:function(err){
|
|
44
|
-
console.log(err);
|
|
45
|
-
assert.strictEqual(!err, true, 'addon.printFile'); // failed test case
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
// getSupportedPrintFormats
|
|
52
|
-
test('Test getSupportedPrintFormats behavior', (t) => {
|
|
53
|
-
assert.throws(() => {
|
|
54
|
-
addon.getSupportedPrintFormats();
|
|
55
|
-
}, /Error/, 'getSupportedPrintFormats should throw an error when no arguments are passed');
|
|
56
|
-
});
|