-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy path589_oprint_winapi.cpp
367 lines (327 loc) · 17.1 KB
/
589_oprint_winapi.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
// исходный текст программы сохранен в кодировке UTF-8 с сигнатурой
// oprint_winapi.cpp
// имитация команды print командной строки операционной системы Windows
// (а также MS-DOS), используемой для отправки указанного файла на принтер
// (отправка на принтер осуществляется с помощью функций Windows API)
// я добавил в программу две строки для работы с локалью, чтобы можно было
// читать русские буквы из файла в кодировке UTF-8
#include <io.h> // для функции _setmode
#include <fcntl.h> // для константы _O_U16TEXT
#include <fstream> // для работы с потоками, связанными с файлами
#include <sstream> // для работы с потоками, связанными со строками
#include <iostream>
#include <codecvt> // для работы с фасетом codecvt_utf8 локали
// #include <locale> // не понадобился
#include <windows.h> // для функций WinAPI
using namespace std;
// #include <process.h> // для функции exit не понадобился
// прототип функции для вывода текста на устройство
BOOL TextOutput(HDC pDC, LPCWSTR str, int align_mode = 0);
// прототип функции для получения названия принтера по умолчанию
BOOL GetDefPrinter(LPWSTR* printerName);
// прототип функции для вывода строки на принтер
BOOL toPrinter(LPCWSTR str);
int wmain(int argc, wchar_t* argv[])
{
// переключение стандартного потока вывода в формат Юникода
_setmode(_fileno(stdout), _O_U16TEXT);
// переключение стандартного потока ошибок в формат Юникода
_setmode(_fileno(stderr), _O_U16TEXT);
// создаем константу, содержащую локаль с нужным фасетом для
// преобразования символов при чтении из файла в кодировке UTF-8
const locale utf8_locale = locale(locale(), new codecvt_utf8<wchar_t>());
if (argc != 2)
{
wcerr << L"Формат команды: oprint имя_файла\n";
exit(-1);
// это сообщение об ошибке подразумевает, что исполняемый файл программы
// должен называться oprint.exe
}
wifstream infile; // создать входной поток
infile.imbue(utf8_locale); // связываем наш поток с нужной локалью
infile.open(argv[1]); // открыть файл
if (!infile) // проверить на ошибки
{
wcerr << L"Не получается открыть " << argv[1] << endl;
exit(-1);
}
wostringstream ostr; // создадим выходной поток, связанный со строкой
wchar_t ch; // символ для считывания
while (infile.get(ch)) // считать символ (пока не достигнут конец файла)
ostr.put(ch); // отправить символ в выходной поток
// вызов функции для вывода строки на принтер по умолчанию
if (!toPrinter(ostr.str().c_str())) // если при вызове случилась ошибка
exit(-1); // завершаем программу ошибкой
wcout << L"Вывод файла на принтер выполнен успешно!\n";
// если принтер по умолчанию — виртуальный принтер Microsoft XPS Document Writer,
// то данные выведены в файл «Распечатанный документ.xps» текущего каталога
// (у меня получалось, что текущий каталог — это каталог, в котором находится
// исполняемый файл программы)
return 0;
}
// функция для вывода текста на устройство
// align_mode
// = 0 (по умолчанию) не заботиться о выравнивании;
// = 1 выравнять текст по обеим сторонам;
// = 2 выравнять текст влево;
// = 3 выравнять текст по центру;
// = 4 выравнять текст вправо.
BOOL TextOutput(HDC pDC, LPCWSTR instr, int align_mode)
{
// замена символов '\t' на 8 пробелов '\x2002' (en space)
wstring temp = instr; // возьмем заданный аргументом функции текст,
size_t ns = temp.find(L'\t'); // ищем в нем первое вхождение символа '\t'
while (ns != wstring::npos) // пока вхождение '\t' найдено
{
temp.replace(ns, 1, L"\x2002\x2002\x2002\x2002\x2002\x2002\x2002\x2002"); // замена
ns = temp.find(L'\t'); // новый поиск
}
LPCWSTR str = temp.c_str(); // str — текст с которым работаем дальше
// определить шрифт и его настройки
HFONT hFont;
int fHeight = -MulDiv(11, GetDeviceCaps(pDC, LOGPIXELSY), 72);
hFont = CreateFontW(fHeight, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"Arial");
SelectObject(pDC, hFont);
// устанавливаем режим выравнивания текста
int coordX = 100; // координата X (100 пикселей — для выравнивания влево или по обеим сторонам)
if (align_mode == 2)
SetTextAlign(pDC, TA_LEFT);
else if (align_mode == 3)
{
coordX = GetDeviceCaps(pDC, HORZRES) / 2;
SetTextAlign(pDC, TA_CENTER);
}
else if (align_mode == 4)
{
coordX = GetDeviceCaps(pDC, HORZRES) - 100;
SetTextAlign(pDC, TA_RIGHT);
}
// получим максимально возможную ширину подстроки
// и вычтем 200 пикселей: чтобы оставить расстояние в 100 пикселей
// с каждой стороны (зазор между текстом и краем листа)
int max_width = GetDeviceCaps(pDC, HORZRES) - 200;
// и максимально возможную высоту страницы
int max_height = GetDeviceCaps(pDC, VERTRES) - 200;
BOOL OK;
// переберем строку посимвольно и разобьем ее на подстроки,
// которые будем печатать
int i = 0; // текущий номер символа в основной строке
SIZE sz; // структура с размерами (ширина, высота) подстроки в пикселях
int start = i; // начало подстроки (номер символа в основной строке)
int n = 1; // номер подстроки
while (i < wcslen(str))
{
// получаем размеры подстроки в пикселях
OK = GetTextExtentPoint32W(pDC, str + start, i - start + 1, &sz);
// если ширина подстроки превысила максимальный размер
// или это последняя подстрока
// или текущий символ — символ новой строки
// или текущий символ — символ новой страницы
if (sz.cx >= max_width ||
i == wcslen(str) - 1 ||
*(str + i) == L'\n' ||
*(str + i) == L'\f')
{
// если это не последняя подстрока
// и текущий символ — не символ новой строки
// и текущий символ — не символ новой страницы
if (i != wcslen(str) - 1 && *(str + i) != L'\n' && *(str + i) != L'\f')
{
// распечатать подстроку выбранным шрифтом, закончив
// символом, который еще влазил в максимальную ширину
// если мы находимся в середине слова
if (*(str + i) != L' ')
// вернуться к началу слова
while (*(str + i) != L' ')
i--;
// иначе вернуться к предыдущему символу
else
i--;
if (align_mode == 1) // выравнивание текста по обеим сторонам
{
// снова получим размеры подстроки, которую собираемся печатать
// (это нужно потому, что неизвестно, на сколько символов отступили)
OK = GetTextExtentPoint32W(pDC, str + start, i - start + 1, &sz);
// подсчитаем число пробелов в этой подстроке
int k = 0;
for (int j = start; j < i + 1; j++)
{
if (*(str + j) == L' ')
k++;
}
// настроим выравнивание по ширине
if (k > 0)
OK = SetTextJustification(pDC, max_width - sz.cx, k);
}
// печатаем строку (если превышена высота листа, перейдем на следующий лист)
if ((n + 1) * sz.cy > max_height)
{
EndPage(pDC);
StartPage(pDC);
n = 1; // нумерация подстрок опять начинается с единицы
}
OK = TextOutW(pDC, coordX, n * sz.cy, str + start, i - start + 1);
if (!OK)
{
wcout << L"Не получилось вывести на страницу текст!\n";
return FALSE;
}
if (align_mode == 1) // выравнивание текста по обеим сторонам
{
// вернем выравнивание по умолчанию
OK = SetTextJustification(pDC, 0, 0);
}
start = i + 1; // переходим к следующей подстроке
n++; // увеличим номер подстроки
}
else if (*(str + i) == L'\n') // печать последней в параграфе строки
{
// печатаем строку (если превышена высота листа, перейдем на следующий лист)
if ((n + 1) * sz.cy > max_height)
{
EndPage(pDC);
StartPage(pDC);
n = 1; // нумерация подстрок опять начинается с единицы
}
// сам символ '\n' печатать не будем
OK = TextOutW(pDC, coordX, n * sz.cy, str + start, i - start);
if (!OK)
{
wcout << L"Не получилось вывести на страницу текст!\n";
return FALSE;
}
start = i + 1; // переходим к следующей подстроке
n++; // увеличим номер подстроки
}
else if (*(str + i) == L'\f') // печать подстроки до символа новой страницы
// и переход на следующую страницу
{
// печатаем строку (если превышена высота листа, перейдем на следующий лист)
if ((n + 1) * sz.cy > max_height)
{
EndPage(pDC);
StartPage(pDC);
n = 1; // нумерация подстрок опять начинается с единицы
}
// сам символ '\f' печатать не будем
OK = TextOutW(pDC, coordX, n * sz.cy, str + start, i - start);
if (!OK)
{
wcout << L"Не получилось вывести на страницу текст!\n";
return FALSE;
}
// переход на следующую страницу
EndPage(pDC);
StartPage(pDC);
start = i + 1; // переходим к следующей подстроке
n = 1; // нумерация подстрок опять начинается с единицы
}
else // печать последней подстроки и выход из цикла
{
// печатаем строку (если превышена высота листа, перейдем на следующий лист)
if ((n + 1) * sz.cy > max_height)
{
EndPage(pDC);
StartPage(pDC);
n = 1; // нумерация подстрок опять начинается с единицы
}
OK = TextOutW(pDC, coordX, n * sz.cy, str + start, i - start + 1);
if (!OK)
{
wcout << L"Не получилось вывести на страницу текст!\n";
return FALSE;
}
break; // выход из цикла
}
}
// переходим к следующему символу основной строки
i++;
}
return TRUE;
}
// функция для получения названия принтера по умолчанию
BOOL GetDefPrinter(LPWSTR* printerName)
{
DWORD n; // количество символов (включая нулевой символ), требуемое под
// массив-буфер для размещения названия принтера по умолчанию
// 1-й вызов: получим количество символов n
BOOL OK = GetDefaultPrinterW(NULL, &n);
// выделим память под массив-буфер для размещения названия принтера
*printerName = NULL;
if (n > 0)
*printerName = new WCHAR[n];
// 2-й вызов: получим название принтера по умолчанию
if (*printerName != NULL)
OK = GetDefaultPrinterW(*printerName, &n);
return OK;
}
// функция для посылки задания на печать на принтер
BOOL toPrinter(LPCWSTR str)
{
LPWSTR printerName; // для названия принтера по умолчанию
// вызываем функцию для получения названия принтера по умолчанию
if (!GetDefPrinter(&printerName))
{
wcout << L"Не удалось получить принтер по умолчанию!\n";
return FALSE; // завершаем программу ошибкой
}
HDC printerDC; // контекст устройства (принтера)
// получим контекст устройства (принтера), которое выбрал пользователь
printerDC = CreateDCW(L"WINSPOOL", printerName, NULL, NULL);
// если контекст устройства получен успешно
if (printerDC != NULL)
{
DOCINFOW docinfo; // информация о документе, который будем печатать
docinfo.cbSize = sizeof(docinfo); // размер структуры с документом
docinfo.lpszDocName = L"Мой документ"; // название документа
if (wcscmp(printerName,
L"Microsoft XPS Document Writer") == 0)
docinfo.lpszOutput = L"Распечатанный документ.xps";
else
docinfo.lpszOutput = NULL;
docinfo.lpszDatatype = NULL;
docinfo.fwType = 0;
int idJob; // номер задания, отправляемого на принтер
// проинформируем спулер печати о начале печати нового документа
idJob = StartDocW(printerDC, &docinfo);
if (idJob > 0)
{
int result;
// начать печать страницы
result = StartPage(printerDC);
if (result > 0)
{
// распечатать текст
BOOL OK = TextOutput(printerDC, str, 1); // 1 — выровнять по обеим сторонам
if (!OK)
{
wcout << L"Не получилось вывести на страницу текст!\n";
return FALSE;
}
// закончить печать страницы
EndPage(printerDC);
}
else
{
wcout << L"Не получилось начать печать страницы!\n";
return FALSE;
}
// проинформируем спулер печати об окончании печати документа
EndDoc(printerDC);
}
else
{
wcout << L"Не получилось поставить новый документ в очередь печати!\n";
return FALSE;
}
// удалить контекст устройства (принтера), так как он нам больше не нужен
DeleteDC(printerDC);
}
// контекст устройства не получен
else
{
wcout << L"Не удалось получить контекст устройства (принтера)!\n";
return FALSE;
}
return TRUE;
}