ilyachalov (ilyachalov) wrote,
ilyachalov
ilyachalov

Category:

Windows GDI, C++: печать текста

В одном из недавних постов я описал, как можно сделать печать из консольной программы с помощью функций из набора Windows API (в том числе с помощью функций Windows GDI):
https://ilyachalov.livejournal.com/107298.html

(Использовался компилятор среды «Visual Studio Community 2017».)

Для, собственно, вывода текста я остановился там на функции TextOut:
BOOL OK = TextOutW(printerDC, 100, 100, str, wcslen(str));

Это самый простой вариант. Но у него есть недостаток: он выводит на указанное устройство (в нашем случае — виртуальный принтер Microsoft XPS Document Writer) только одну строку. Если строка выходит за пределы листа, то автоматического переноса не произойдет, а символ новой строки '\n' или endl эта функция игнорирует. Например (картинка кликабельна):



Более интересный вариант — функция DrawText:
RECT rc = { 100, 100, GetDeviceCaps(printerDC, HORZRES) - 100,
                      GetDeviceCaps(printerDC, VERTRES) - 100 };
int OK = DrawTextW(printerDC, str, wcslen(str), &rc, DT_WORDBREAK);
В отличие от функции TextOut функции DrawText необходимо задать координаты прямоугольника, в котором она будет печатать текст. В этом кусочке кода такой прямоугольник задан координатами верхнего левого угла (100, 100) и координатами правого нижнего угла.

Функция GetDeviceCaps возвращает разнообразную информацию об устройстве. В данном случае — максимальную ширину (в пикселях) и максимальную высоту (в пикселях) листа. Далее из этих величин отнимаем по 100 пикселей, чтобы между прямоугольником с текстом и краем листа был зазор из 100 пикселей.

Значением DT_WORDBREAK указываем функции DrawText, что необходимо обеспечить автоматический перенос слов, если строка достигает правого края листа.

Также функция DrawText правильно интерпретирует символ новой строки и производит переход на новую строку, если в тексте встречаются соответствующие символы.

Пример (тот же текст, что и в случае функции TextOut, картинка кликабельна):



Всё бы хорошо, однако, можно заметить (см. картинку в 100 % размере), что некоторые буквы наезжают друг на друга или, наоборот, слишком далеко отходят друг от друга в некоторых словах. А некоторые знаки препинания вообще уезжают со своих мест довольно далеко.

Очевидно, требуется программная настройка шрифта. Ранее я не упоминал о шрифте, надеясь, что нормально подойдет шрифт по умолчанию. Поэтому и в программе шрифт не определял.

Кстати, чтобы определить, что за шрифт используется по умолчанию и какие у него настройки, можно применить, например, функции GetTextFace (для получения названия семейства шрифтов) и GetTextMetrics (для получения настроек шрифта). С их помощью я определил, что в моем случае для шрифта по умолчанию название семейства шрифтов — «Arial», а высота его символов — 100 пикселей.

Тут важно заметить, что размеры пикселей (точек) экрана и листа бумаги отличаются. Например, в моем случае (виртуальный принтер Microsoft XPS Document Writer) функция GetDeviceCaps вернула для листа A4 размер в пикселях 4961 × 7016 (600 × 600 dpi или просто 600 dpi). Разрешение экрана у меня 1280 × 1024 в пикселях (размеры экрана 15,2 × 11,4 в дюймах, диагональ 19 дюймов, то есть примерно 84,21 × 89,82 ppi). Получается, что в моем случае размер пикселя на листе бумаги гораздо меньше размера пикселя на экране (примерно в 7 раз).

Определить шрифт нужно до отправки текста на печать. Следующим образом можно определить шрифт с установками, аналогичными шрифту, который у меня выбирается по умолчанию:
HFONT hFont = CreateFontW(100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"Arial");
SelectObject(printerDC, hFont);
Функция CreateFont возвращает дескриптор с настройками шрифта, а функция SelectObject подбирает по этим настройкам наиболее подходящий шрифт из имеющихся в операционной системе. Видно, что у шрифта может быть много настроек, но достаточно указать только высоту символов в пикселях (100) и название семейства шрифтов (Arial), а значения остальных аргументов можно определить нулем.

На странице функции CreateFont на сайте компании Microsoft рекомендуется следующая формула для преобразования размера шрифта в пунктах (1 пункт равен 1/72 дюйма) к размеру шрифта в пикселях устройства:
fHeight = -MulDiv(РазмерВПунктах, GetDeviceCaps(hDC, LOGPIXELSY), 72);

Я прикинул по вышеуказанной картинке, что размер шрифта по умолчанию в пунктах примерно равен 11. Тогда пишем следующее:
HFONT hFont;
int fHeight = -MulDiv(11, GetDeviceCaps(printerDC, LOGPIXELSY), 72);
hFont = CreateFontW(fHeight, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, L"Arial");
SelectObject(printerDC, hFont);
Упомянутая выше формула расчета высоты символов в пикселях дала значение 103 (а не 100, как для шрифта по умолчанию). При этом текст на печать вывелся без упомянутых выше артефактов (картинка кликабельна):



Примеры программ:
Output_to_printer_WinGDI_1_TextOut.cpp
Output_to_printer_WinGDI_2_DrawText.cpp
Tags: Образование, Программирование
Subscribe

  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 0 comments