Штамп в PDF средствами 1С ставится без растеризации документа: файл читается объектом ДокументPDF, картинка штампа описывается через ОписаниеОтображаемогоОбъектаPDF с координатами и номером страницы, после чего документ записывается обратно. Страницы при этом не превращаются в изображения, поэтому многостраничный файл обрабатывается за секунды, а текстовый слой сохраняется.

Единственная сложность — координаты отсчитываются от левого верхнего угла, а штамп обычно нужен внизу. Чтобы поставить его правильно, нужно знать размер и ориентацию страницы. Ниже — как это решить, рабочий код и подводные камни, на которые я натыкался на проектах с «1С:Документооборотом» и электронной подписью.

Почему типовой способ работает медленно

Штамп в PDF 1С: растеризация страниц против добавления штампа поверх векторного документа
Штамп в PDF 1С: растеризация страниц против добавления штампа поверх векторного документа

Во многих конфигурациях визуализация штампа электронной подписи устроена так:

  1. PDF конвертируется в набор изображений (через ImageMagick и Ghostscript).
  2. На изображение нужной страницы накладывается штамп.
  3. Изображения собираются обратно в PDF.

На договоре в две страницы это незаметно. На документах в десятки страниц такая цепочка может работать десятки минут и нагружать сервер. Вторая проблема — качество: после растеризации исчезает текстовый слой, документ нельзя искать и копировать, а размер файла растёт.

РастеризацияДокументPDF
Скорость на многостраничных файлахМинуты и большеСекунды
Текстовый слойПропадаетСохраняется
Размер файлаРастётПочти не меняется
Внешние зависимостиImageMagick, GhostscriptНе нужны (кроме определения размера страницы на старых версиях)

Как платформа добавляет картинку в PDF

Объекты для работы с существующими PDF появились в платформе 8.3 и расширялись от версии к версии, поэтому перед разработкой сверьтесь с синтаксис-помощником своей версии. Схема такая:

  • ДокументPDF — читает файл из потока или по пути, умеет записывать результат;
  • ОписаниеОтображаемогоОбъектаPDF — описание того, что и куда поместить: свойства Объект (картинка), Лево, Верх, Ширина, Высота, НомерСтраницы;
  • метод ДобавитьОтображаемыйОбъект у документа добавляет описание на страницу.

Координаты Лево и Верх отсчитываются от левого верхнего угла страницы. Если не задать НомерСтраницы, проверьте, на какую страницу попадёт штамп в вашей версии: для штампа подписи его обычно ставят на последнюю страницу, и это лучше указывать явно.

Определяем размер и ориентацию страницы

Чтобы поставить штамп в правый нижний угол, нужно знать ширину и высоту листа: A4 книжный (210×297 мм) и альбомный (297×210 мм) требуют разных координат. Варианты получения размера:

  1. Средствами платформы. В свежих версиях появляются методы для чтения сведений о страницах документа. Если они есть в вашей версии — используйте их, это лучший вариант.
  2. Внешней утилитой. На серверах с «1С:Документооборотом» ImageMagick обычно уже установлен для работы с подписями. Его можно использовать не для конвертации, а только для чтения сведений о файле — это быстро.
  3. Утилитой pdfinfo из пакета poppler-utils на Linux — она печатает размер страницы в пунктах и не требует Ghostscript.

Команда ImageMagick, которая выводит ширину первой страницы в дюймах, выглядит так:

bash
magick "doc.pdf[0]" -format "%[printsize.x]" info:

Суффикс [0] важен: без него ImageMagick разберёт все страницы, и на большом файле экономия времени пропадёт.

Код: ориентация страницы через ImageMagick

Функция сохраняет PDF во временный файл, запускает ImageMagick с выводом результата в текстовый файл и переводит дюймы в миллиметры. Код серверный; путь к ImageMagick для Windows берётся из настройки.

1С
// Определяет ориентацию первой страницы PDF по её ширине.
//
// Параметры:
//   ДанныеФайла - ДвоичныеДанные - содержимое PDF.
//
// Возвращаемое значение:
//   ОриентацияСтраницы - Портрет или Ландшафт.
//
Функция ОриентацияСтраницыPDF(ДанныеФайла) Экспорт

	ИмяФайлаPDF = ПолучитьИмяВременногоФайла("pdf");
	ИмяФайлаРезультата = ПолучитьИмяВременногоФайла("txt");
	ДанныеФайла.Записать(ИмяФайлаPDF);

	ШиринаММ = 0;
	Попытка
		ПараметрыКоманды = СтрШаблон("""%1[0]"" -format ""%%[printsize.x]"" info:""%2""",
			ИмяФайлаPDF, ИмяФайлаРезультата);
		ВыполнитьImageMagick(ПараметрыКоманды);

		Текст = Новый ТекстовыйДокумент;
		Текст.Прочитать(ИмяФайлаРезультата, КодировкаТекста.UTF8);
		ШиринаДюймы = Число(СокрЛП(Текст.ПолучитьТекст()));
		ШиринаММ = Окр(ШиринаДюймы * 25.4, 0);
	Исключение
		ЗаписьЖурналаРегистрации("ШтампPDF.РазмерСтраницы", УровеньЖурналаРегистрации.Предупреждение, , ,
			ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
	КонецПопытки;

	УдалитьВременныйФайл(ИмяФайлаPDF);
	УдалитьВременныйФайл(ИмяФайлаРезультата);

	// Ширина больше высоты листа A4 в книжной ориентации — считаем альбомной.
	Если ШиринаММ > 290 Тогда
		Возврат ОриентацияСтраницы.Ландшафт;
	КонецЕсли;
	Возврат ОриентацияСтраницы.Портрет;

КонецФункции

// Запускает ImageMagick и проверяет код возврата.
Процедура ВыполнитьImageMagick(ПараметрыКоманды)

	СистемнаяИнформация = Новый СистемнаяИнформация;
	ЭтоLinux = СистемнаяИнформация.ТипПлатформы = ТипПлатформы.Linux_x86_64
		Или СистемнаяИнформация.ТипПлатформы = ТипПлатформы.Linux_x86;

	Если ЭтоLinux Тогда
		СтрокаКоманды = "magick " + ПараметрыКоманды;
	Иначе
		ПутьКПрограмме = Константы.ПутьКImageMagick.Получить();
		Если Не ЗначениеЗаполнено(ПутьКПрограмме) Тогда
			ВызватьИсключение НСтр("ru = 'Не указан путь к ImageMagick.'");
		КонецЕсли;
		СтрокаКоманды = """" + СокрЛП(ПутьКПрограмме) + """ " + ПараметрыКоманды;
	КонецЕсли;

	КодВозврата = Неопределено;
	ЗапуститьПриложение(СтрокаКоманды, , Истина, КодВозврата);

	Если КодВозврата = Неопределено Или КодВозврата <> 0 Тогда
		ВызватьИсключение СтрШаблон(НСтр("ru = 'ImageMagick завершился с кодом %1. Команда: %2'"),
			КодВозврата, СтрокаКоманды);
	КонецЕсли;

КонецПроцедуры

Процедура УдалитьВременныйФайл(ИмяФайла)

	Попытка
		УдалитьФайлы(ИмяФайла);
	Исключение
		// Временный файл будет удалён платформой позже, ошибку не поднимаем.
	КонецПопытки;

КонецПроцедуры

Константы.ПутьКImageMagick — условное имя: в «1С:Документообороте» путь к программе уже хранится в настройках, используйте существующий механизм вместо новой константы.

Код: установка штампа в PDF

Основная функция принимает двоичные данные PDF и картинки штампа и положение штампа (значение собственного перечисления с четырьмя углами). Возвращает двоичные данные нового PDF.

1С
// Устанавливает картинку штампа на страницу PDF без конвертации документа.
//
// Параметры:
//   ДанныеPDF - ДвоичныеДанные - исходный документ.
//   ДанныеШтампа - ДвоичныеДанные - картинка штампа (PNG).
//   Угол - ПеречислениеСсылка.РасположениеШтампаPDF - угол страницы.
//   НомерСтраницы - Число - страница, на которую ставится штамп.
//
// Возвращаемое значение:
//   ДвоичныеДанные - документ со штампом.
//
Функция УстановитьШтампВPDF(ДанныеPDF, ДанныеШтампа, Угол, НомерСтраницы = 1) Экспорт

	// Размеры штампа и поля, мм.
	ШиринаШтампа = 70;
	ВысотаШтампа = 30;
	Поле = 10;

	Если ОриентацияСтраницыPDF(ДанныеPDF) = ОриентацияСтраницы.Ландшафт Тогда
		ШиринаЛиста = 297;
		ВысотаЛиста = 210;
	Иначе
		ШиринаЛиста = 210;
		ВысотаЛиста = 297;
	КонецЕсли;

	ПравыйКрай = ШиринаЛиста - ШиринаШтампа - Поле;
	НижнийКрай = ВысотаЛиста - ВысотаШтампа - Поле;

	Если Угол = Перечисления.РасположениеШтампаPDF.ЛевыйВерхний Тогда
		Лево = Поле;
		Верх = Поле;
	ИначеЕсли Угол = Перечисления.РасположениеШтампаPDF.ПравыйВерхний Тогда
		Лево = ПравыйКрай;
		Верх = Поле;
	ИначеЕсли Угол = Перечисления.РасположениеШтампаPDF.ЛевыйНижний Тогда
		Лево = Поле;
		Верх = НижнийКрай;
	Иначе
		Лево = ПравыйКрай;
		Верх = НижнийКрай;
	КонецЕсли;

	Документ = Новый ДокументPDF;
	Документ.Прочитать(ДанныеPDF.ОткрытьПотокДляЧтения());

	ОписаниеШтампа = Новый ОписаниеОтображаемогоОбъектаPDF;
	ОписаниеШтампа.Объект = Новый Картинка(ДанныеШтампа, Истина);
	ОписаниеШтампа.НомерСтраницы = НомерСтраницы;
	ОписаниеШтампа.Лево = Лево;
	ОписаниеШтампа.Верх = Верх;
	ОписаниеШтампа.Ширина = ШиринаШтампа;
	ОписаниеШтампа.Высота = ВысотаШтампа;

	Документ.ДобавитьОтображаемыйОбъект(ОписаниеШтампа);

	Поток = Новый ПотокВПамяти;
	Документ.Записать(Поток);
	Возврат Поток.ЗакрытьИПолучитьДвоичныеДанные();

КонецФункции

Второй параметр конструктора Новый Картинка(ДанныеШтампа, Истина) включает прозрачный фон: штамп с прозрачным PNG не закрывает текст под собой.

В реальном коде я добавляю обёртку, которая принимает не только двоичные данные, но и ссылку на присоединённый файл или адрес во временном хранилище, и приводит всё к ДвоичныеДанные. Для присоединённых файлов в конфигурациях на БСП используйте РаботаСФайлами.ДвоичныеДанныеФайла(Ссылка).

Где разместить настройки

  • Положение штампа — перечисление с четырьмя значениями и константа или функциональная опция для значения по умолчанию.
  • Размеры штампа и поля — константы или параметры функции, если разные виды документов требуют разного размещения.
  • Путь к ImageMagick — используйте настройку, которая уже есть в конфигурации.

Если доработка делается в типовом «1С:Документообороте», я выношу её в расширение и перехватываю типовую процедуру визуализации подписи: так обновления конфигурации не затрут изменения. Это обычная задача в рамках внедрения и доработки 1С:Документооборота.

Типичные ошибки

  • Штамп «улетает» за пределы листа. Координаты посчитаны для книжной ориентации, а документ альбомный. Определяйте ориентацию для той страницы, куда ставите штамп, а не только для первой, если в документе встречаются смешанные листы.
  • Нестандартный формат страницы. Код выше рассчитан на A4. Для A3 или произвольных размеров берите фактические ширину и высоту, а не только признак ориентации.
  • ImageMagick не читает PDF. Для работы с PDF ему нужен Ghostscript. Проверьте, что он установлен на том сервере, где выполняется код, а не на компьютере программиста.
  • Медленно, хотя конвертации нет. Забыли [0] в команде ImageMagick — утилита разбирает все страницы.
  • Код работает в файловой базе, но не на сервере. Утилиты запускаются на сервере 1С от имени пользователя службы: у него должны быть права на запуск программы и на временный каталог.
  • Защищённые PDF. Файл с паролем нужно открывать с паролем; подписанный PDF после изменения потеряет встроенную подпись — ставьте штамп до подписания или на копию.

Если нужна похожая доработка — штампы, визуализация подписи, печать в PDF — смотрите доработку 1С и печатные формы 1С.

Итог

  • Штамп в PDF средствами 1С ставится через ДокументPDF и ОписаниеОтображаемогоОбъектаPDF, без растеризации документа.
  • Многостраничные файлы обрабатываются в разы быстрее, текстовый слой сохраняется.
  • Координаты отсчитываются от левого верхнего угла, поэтому нужно знать размер и ориентацию страницы.
  • Если платформа не отдаёт размер страницы, хватит быстрого запроса к ImageMagick или pdfinfo по первой странице.
  • Доработку типовой конфигурации делайте в расширении и проверяйте на документах нестандартных форматов.