Продолжаю тему печати PDF документов из под .NET.
В принципе, распечатать документ не трудно, есть даже готовые решения. Сложности возникают, когда нужно управлять некоторыми параметрами печати. В своей практике я столкнулся с задачей по реализации минитипографии — когда при печати документов нужно указывать, из какого лотка брать очередной лист, т.е. печатать документы по шаблонам. Первым делом я попытался найти готовые решения, но не обнаружив ничего подходящего, стал придумывать свое.
Первое, что пришло в голову — рендерить страницы документа в растровые картинки и при помощи стандартных средств .NET выводить их на печать. Хорошо, что класс PrintDocument позволяет налету менять лоток принтера. О таком подходе я писал в предыдущей статье
Как только заработало первое приложение, обнаружился существенный недостаток этого решения. После преобразования PDF в картинку, файл раздувался до огромных размеров, поэтому печать шла очень долго. При этом объемы печати продолжали расти, и я решил найти найти другой способ.
Мне помог язык управления принтером PCL от Hewlett-Packard. В интернете информации по нему оказалось крайне мало, не говоря уже о рунете. Единственное внятное описание — официальная спецификация. Сказано — сделано. Запустил HEX редактор + утилитку от HP JetAsm и начал изучать PCL. Не так страшен черт, как его малюют. Язык хоть и бинарный, но довольно простой и логичный. В результате, через пару часов собрал тестовое приложение. И во благо всем, кому это может быть полезно и интересно, расскажу немного про PCL.
В Википедии написано:
“PCL (от англ. Printer Command Language) — язык управления принтером, разработанный компанией Hewlett-Packard. В первой версии это был просто набор команд для печати ASCII-символов, теперь же, в версиях PCL6 и PCL-X стало возможным печатать в цвете, а также печатать изображения, но вне Microsoft Windows и HP-UX этот язык редко используется”
От себя добавлю, что это язык бинарный и стековый — поступающие данные заносятся на стек, а при появлении оператора данные забираются со стека в качестве аргументов оператора.
Официальное описание PCL есть в интернете. Также понять и валидировать содержимое PCL файлов поможет бесплатная утилита от HP JetAsm. Ее и другие полезные файлы можно взять с официального сайта без регистрации. Для это нужно перейти по ссылке, нажать SDK->Public и найти интересующие файлы.
Старый добрый GhostScript
Для начала нам нужно где-то достать pcl файл. Известная по прошлой статье утилита GhostScript из коробки умеет конвертировать PDF в PCL. Сделать это можно командой:
gswin64c.exe -o example.pcl -sDEVICE=pxlmono example.pdf
Таким образом, без лишних движений мы получили PCL файл, который можно отправлять на принтер в качестве RawData.
Однако, если нужно не только распечатать документ, но и распределить его по лоткам принтера (например, согласно какому-то шаблону), то придется соответствующим образом модифицировать сгенерированный PCL файл. Для этого нужно разобраться с его устройством.
Итак, откроем PCL файл в каком-либо HEX редакторе. Я использовал WinHex.
Язык имеет иерархичную структуру. Но вначале устройству надо понять что дальше будет передаваться поток данных PCL, для это служит строка инициализации.
Иерархия операторов PCL имеет следующий вид:
<сессия>
<страница>элементы страницы</страница>
<страница>элементы страницы</страница>
<страница>элементы страницы</страница>
</сессия>
При этом сессий может быть несколько, а элементы страницы могут быть вложенными в другие элементы.
Начнем по порядку
Я уже говорил, что вначале потока идет стандартная строка. Именно по ней устройство определяет, что дальше будут передаваться данные в формате PСLXL. Длина строки инициализации 99 байт.
Дальше начинается поток данных PCL. Чтобы начать описывать элементы документа, должна быть открыта сессия, для этого существует команда BeginSession с кодом 0x41. Этот и другие коды команд можно найти в мануале. В одном файле может быть одна или несколько сессий, но чаще одна.
Там же смотрим аргументы, которые необходимо передать в BeginSession.
Аргументы имеют следующую структуру: [тип данных][данные][тип атрибута][атрибут]
UnitsPerMeasure — разрешение рабочей области x и y, имеет тип данных uint16_xy — структура из двух двухбайтовых слов.
Measure — перечисление единиц измерения, в которых будем работать {eInch | eMillimeter | eTenthsOfAMillimeter}
ErrorReport — перечисление, определяющее, как устройство будет обрабатывать ошибки.
BeginPage
Аналогичным образом инициализируется страница. Именно оператор BeginPage позволяет задать режим дуплекса и указать лоток из которого будет производиться забор бумаги.
Разберем аргументы оператора BeginPage.
Orientation — ориентация страницы, может принимать значения {ePortraitOrientation | eLandscapeOrientation | eReversePortrait | eReverseLandscape}
MediaSize — размер страницы, перечисление, в нашем случае eA4Paper. Вместо типа MediaSize может быть CustomMediaSize, CustomMediaSizeUnits.
MediaSource — источник бумаги.
0 — eDefaultSource
1 — eAutoSelect
2 — eManualFeed
3 — eMultiPurposeTray
4 — eUpperCassette
5 — eLowerCassette
6 — eEnvelopeTray
7 — eThirdCassette
1-248 — External Trays
Вместо MediaSource может быть MediaType с именем источника.
SimplexPageMode — одностраничный режим печати, обязательно должно быть значение eSimplexFrontSide = 0
Вместо SimplexPageMode может быть DuplexPageMode или DuplexPageSide.
DuplexPageSide — печатает одну страницу на одном листе, но можно задать на какой стороне листа {eFrontMediaSide | eBackMediaSide}
DuplexPageMode — две последовательные страницы печатаются на двух сторонах одного листа, можно задавать значения eDuplexHorizontalBinding = 0 и eDuplexVerticalBinding = 1
И завершается все это командой BeginPage с кодом 0x43
Надеюсь, принцип понятен. Этого достаточно для разработки приложения и модификации PCL файла таким образом, чтобы можно было менять режим Duplex — Simplex и указывать, из какого лотка забирать бумагу. Для этого в файле нужно найти объявление очередной страницы и изменить ее нужным образом.
Про реализацию демоприложения печатающего PDF в векторе по шаблону расскажу в следующей статье.
Если есть неточности или нужно больше деталей, пожалуйста, напишите об этом в комментариях.
Поделиться с друзьями
Комментарии (3)
Neftedollar
16.05.2016 21:13Очевидно в статье нет .NET
Beetle_ru
16.05.2016 21:16Работаю над продолжением, там будет пример на C#. Хотя действительно, идею из данной статьи можно реализовать на чем угодно.
DeadYogi
Исправьте <старница>элементы страницы</старница> на <страница>элементы страницы</страница>