В процессе автоматизации документооборота в любой современной компании часто возникает задача автоматической отправки прикрепленных файлов (договоров, актов, накладных в формате PDF, а также сканов документов в форматах JPG, PNG) напрямую на принтер. Особенно остро эта проблема стоит в клиент-серверной архитектуре, когда пользователи работают в терминальном режиме (RDP), а сами базы данных и файлы могут физически находиться на разных серверах. Основная цель разработчика в этом случае — обеспечить так называемую "тихую" (фоновую) печать, при которой файлы уходят на печать мгновенно, без вывода диалоговых окон операционной системы и без необходимости интерактивного участия пользователя.
В этой статье мы подробно разберем, как решить данную задачу наиболее эффективными способами. Мы проанализируем как классические методы, основанные на использовании внешних программ и COM-объектов, так и современные возможности платформы 1С:Предприятие 8.3. Выясним основные подводные камни терминального режима и предложим проверенные решения для бесперебойной работы печати.
---
Если в вашей организации используется современная версия платформы 1С (начиная с релиза 8.3.21), у вас есть замечательная возможность отказаться от любых внешних программ, COM-объектов и сторонних утилит. В платформу был добавлен специализированный объект ДокументPDF, который позволяет считывать PDF-файлы прямо из кода и отправлять их на печать.
Рассмотрим подробнее реализацию этого механизма. Разработчики часто сталкиваются со следующей проблемой при работе с ДокументPDF: после завершения печати процесс 1С может удерживать монопольную блокировку на файл на диске. Это делает невозможным последующее удаление временного файла или его перезапись в цикле. Чтобы обойти это ограничение, мы рекомендуем открывать файл для чтения не напрямую по пути, а через специальный объект ФайловыйПоток.
Разберем по шагам готовый код серверной процедуры для печати PDF-документа на принтер по умолчанию:
&НаСервере
Процедура НапечататьPDFВстроеннымиСредствами(ПутьКФайлу)
// Инициализируем объект для работы с PDF
пДокументPDF = Новый ДокументPDF();
Попытка
// Открываем файл через файловый поток для чтения
рПоток = Новый ФайловыйПоток(ПутьКФайлу, РежимОткрытияФайла.Открыть, РежимДоступаКФайлу.Чтение);
пДокументPDF.Прочитать(рПоток);
// Отправляем на печать без вывода интерактивного диалога выбора принтера
пДокументPDF.Напечатать(РежимИспользованияДиалогаПечати.НеИспользовать);
// Обязательно закрываем поток для снятия блокировки с файла
рПоток.Закрыть();
Исключение
// Логируем или выводим ошибку пользователю
ЗаписьЖурналаРегистрации("ПечатьДокументов",
УровеньЖурналаРегистрации.Ошибка,
,
,
"Ошибка при печати файла " + ПутьКФайлу + ": " + ОписаниеОшибки());
ВызватьИсключение "Не удалось распечатать файл: " + ОписаниеОшибки();
КонецПопытки;
// Очищаем объектную переменную
пДокументPDF = Неопределено;
КонецПроцедуры
Этот метод является наиболее надежным, быстрым и кроссплатформенным, поскольку он выполняется полностью в рамках штатных механизмов 1С и не зависит от установленного в системе программного обеспечения для просмотра PDF.
---
Что делать, если ваша конфигурация работает на версии платформы ниже 8.3.21? Использование стандартного Acrobat Reader (файл AcroRd32.exe) со стандартными ключами командной строки /N /T имеет очень серьезный недостаток: после отправки файла на печать окно программы Acrobat Reader остается открытым в памяти сессии пользователя. Процессы накапливаются, начинают нагружать оперативную память терминального сервера, блокируют файлы на диске и в итоге приводят к зависанию системы печати.
Отличной альтернативой является использование бесплатного, чрезвычайно быстрого и легковесного просмотрщика SumatraPDF. Эта программа поддерживает полноценное и стабильное управление через параметры командной строки, не оставляя фоновых процессов после завершения задания печати.
Проанализируем пример отправки PDF-файла на печать через SumatraPDF:
&НаКлиенте
Процедура ПечатьЧерезSumatraPDF(ПутьКФайлу, ИмяПринтера = "")
// Путь к исполняемому файлу SumatraPDF на терминальном сервере
ПутьКПриложению = "C:\Program Files\SumatraPDF\SumatraPDF.exe";
// Проверим наличие установленного просмотрщика
ФайлПрограммы = Новый Файл(ПутьКПриложению);
Если Не ФайлПрограммы.Существует() Тогда
ВызватьИсключение "На сервере не установлена утилита SumatraPDF по пути: " + ПутьКПриложению;
КонецЕсли;
// Формируем параметры запуска в зависимости от переданного принтера
Если ПустаяСтрока(ИмяПринтера) Тогда
// Печать на принтер, установленный в Windows по умолчанию
ПараметрыЗапуска = "-print-to-default -silent """ + ПутьКФайлу + """";
Else
// Печать на конкретный (выбранный) принтер
ПараметрыЗапуска = "-print-to """ + ИмяПринтера + """ -silent """ + ПутьКФайлу + """";
КонецЕсли;
// Запускаем приложение во внешнем потоке
// Третий параметр "Истина" означает, что 1С будет ожидать завершения процесса печати
ЗапуститьПриложение(ПутьКПриложению + " " + ПараметрыЗапуска, , Истина);
КонецПроцедуры
Использование флага -silent гарантирует, что SumatraPDF отработает полностью в скрытом режиме без отрисовки какого-либо графического интерфейса пользователя.
---
Еще одно проверенное временем решение для старых платформ 1С — консольная утилита PDFtoPrinter. Это специализированный бесплатный инструмент, созданный исключительно для одной задачи — отправки PDF-файлов на физические принтеры из командной строки Windows. У программы нет графического интерфейса, она мгновенно отправляет файл в очередь печати операционной системы и сразу завершает свою работу.
Посмотрим на пример интеграции этой утилиты в код 1С:
&НаКлиенте
Процедура ПечатьЧерезPDFtoPrinter(ПутьКФайлу, ИмяПринтера)
ПутьКУтилите = "C:\Tools\PDFtoPrinter.exe";
// Синтаксис утилиты: PDFtoPrinter.exe [ИмяФайла] [ИмяПринтера]
ПараметрыЗапуска = """" + ПутьКФайлу + """ """ + ИмяПринтера + """";
Попытка
ЗапуститьПриложение(ПутьКУтилите + " " + ПараметрыЗапуска, , Истина);
Исключение
Сообщить("Не удалось запустить утилиту печати: " + ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры
---
Для печати картинок часто рекомендуют использовать COM-объект Shell.Application и его метод ShellExecute с глаголом printto. Однако в условиях работы на серверах терминалов под управлением современных серверных ОС Windows этот метод нестабилен. При вызове команды система может попытаться запустить стандартный "Мастер печати фотографий Windows", который требует интерактивного нажатия кнопки "Далее" от пользователя. Процесс зависает, ожидая клика, который пользователь даже не видит в своей сессии.
Чтобы избежать этой проблемы, мы рекомендуем использовать метод конвертации изображений в формат PDF на лету. Этот элегантный подход позволяет свести печать файлов любых типов (и картинок, и PDF) к одному единому стабильному сценарию.
Разберем по шагам алгоритм конвертации картинки в PDF средствами 1С:
ТабличныйДокумент в оперативной памяти.ДокументPDF).Реализуем этот механизм в виде функции конвертации:
&НаСервере
Функция КонвертироватьКартинкуВPDF(ПутьККартинке)
// Создаем временный табличный документ
ТабДок = Новый ТабличныйДокумент;
ТабДок.ПоляСверху = 0;
ТабДок.ПоляСнизу = 0;
ТабДок.ПоляСлева = 0;
ТабДок.ПоляСправа = 0;
// Добавляем рисунок на страницу
Рисунок = ТабДок.Рисунки.Добавить(ТипРисункаТабличногоДокумента.Картинка);
Рисунок.Картинка = Новый Картинка(ПутьККартинке);
// Задаем размеры рисунка под стандартный лист A4 (размеры в мм)
Рисунок.Лево = 0;
Рисунок.Верх = 0;
Рисунок.Ширина = 210;
Рисунок.Высота = 297;
// Масштабируем картинку, чтобы она аккуратно вписалась в лист
ТабДок.АвтоМасштаб = Истина;
ТабДок.ОриентацияСтраницы = ОриентацияСтраницы.Портрет;
// Формируем путь для сохранения временного PDF-файла
ПутьКВременномуPDF = ПолучитьИмяВременногоФайла("pdf");
// Записываем документ в формате PDF
ТабДок.Записать(ПутьКВременномуPDF, ТипФайлаТабличногоДокумента.PDF);
Возврат ПутьКВременномуPDF;
КонецФункции
Теперь, имея на выходе временный PDF-файл, мы можем гарантировать 100% стабильность его печати без зависаний процессов и появления неожиданных диалоговых окон.
---
При проектировании системы печати в архитектуре "Сервер терминалов + Сервер 1С" разработчики часто совершают архитектурную ошибку: жестко зашивают имя принтера в код или настройки пользователя. В сессиях удаленного рабочего стола (RDP) локальные принтеры пользователей перенаправляются на сервер динамически. При каждом новом входе пользователя в систему его принтеру присваивается новое уникальное имя, содержащее номер сессии (например, HP LaserJet (перенаправлено 2) или Canon LBP (from session 12)).
Если отправить документ на имя принтера, сохраненное вчера, операционная система вернет ошибку, так как этого принтера больше не существует в текущей сессии пользователя.
Для решения этой проблемы необходимо программно определять актуальное имя принтера в текущей сессии RDP. Для этого мы можем воспользоваться методом 1С ПолучитьПринтеры(), который возвращает массив строковых имен всех доступных в данный момент печатающих устройств пользователя. Мы можем найти среди них нужное устройство по маске или базовому имени модели.
Проанализируем следующую функцию поиска актуального имени перенаправленного принтера:
&НаКлиенте
Функция ОпределитьИмяТерминальногоПринтера(БазовоеИмяПринтера)
// Получаем массив всех принтеров, доступных в текущей RDP-сессии
СписокПринтеров = ПолучитьПринтеры();
// Приводим базовое имя к верхнему регистру для поиска без учета регистра
ПоисковаяСтрока = ВРег(БазовоеИмяПринтера);
Для Каждого ТекущееИмяПринтера Из СписокПринтеров Цикл
// Проверяем, содержит ли имя принтера базовое название модели
Если Найти(ВРег(ТекущееИмяПринтера), ПоисковаяСтрока) > 0 Тогда
// Найдено актуальное имя принтера (например, "HP LaserJet (перенаправлено 5)")
Возврат ТекущееИмяПринтера;
КонецЕсли;
КонецЦикла;
// Если принтер не найден, возвращаем пустую строку
Возврат "";
КонецФункции
Используя этот алгоритм, вы всегда будете отправлять документы на актуальное устройство печати, независимо от того, под каким номером сессии пользователь вошел в терминал сегодня.
---
При реализации фоновой печати критически важно понимать, в каком контексте (на клиенте или на сервере) должен выполняться код отправки документа на принтер:
ПолучитьПринтеры(), а также запуск внешних утилит (SumatraPDF, PDFtoPrinter) через функцию ЗапуститьПриложение должен выполняться строго в клиентском контексте (&НаКлиенте). Это обусловлено тем, что служба сервера 1С (работающая на отдельной машине) "не видит" принтеры, подключенные в сессию конкретного терминального пользователя.ПолучитьИмяВременногоФайла(), затем передать этот временный файл (или путь к нему) на клиентскую машину для последующей отправки в очередь печати локального терминального принтера.Соблюдение этих простых архитектурных правил позволит вам создать быструю, надежную и полностью автоматизированную систему печати любых типов прикрепленных файлов в вашей корпоративной системе на базе 1С:Предприятие.