Установка штампа в файл PDF средствами платформы 1С без применения ImageMagick
На проекте столкнулся с PDF файлами, объем которых в десятки страниц. Их конвертация доходила до получаса. После оптимизации, которая описана в данной статье, обработка файлов ускорились до 2 минут. Так появилась идея статьи. Рассмотрим редактирование файла PDF: вставим в PDF картинку со штампом. И немного помучаем ImageMagick. Будет полезно для программистов любого уровня.
1С стала поддерживать редактирование файлов PDF, чтобы вставить в PDF картинку необходимо указать место отсчета от верхнего левого края. И все бы было отлично, но есть некоторые детали, которые не учел вендор.
Обычно вставка картинки в PDF — это установка штампа, который чаще всего устанавливается снизу документа. Если мы не знаем размеры файла PDF, а платформа нам не дает такой информации, то штамп можно разместить неверно.
Сейчас приходится пользоваться сторонними приложениями, чтобы это выяснить размер файла PDF. Рассмотрим ImageMagick — программа для конвертации файлов (опенсорс), как самое распространенное решение совмещенное с 1С. Практически все у кого есть 1С: Документооборот (далее ДО), плюс ЭЦП — пользуются этой программой.
В целом ImageMagick — это консольный конвертер и растровых, и векторных, и даже видео файлов. При этом оказалось, что с его помощью можно собирать и информацию о файлах. Работает он через командную строку. Перейдем к коду.
Пример функции установки картинки штампа в PDF файл:
// Установка штампа в PDF файл средствами платформы.
//
// Параметры:
// Штамп - определяемый тип ПрисоединенныйФайл, Двоичные данные, Строка - данные штампа, должна быть картинка.
// ДанныеPDF - определяемый тип ПрисоединенныйФайл, Двоичные данные, Строка - данные файла PDF куда вставляем штамп.
//
// Возвращаемое значение:
// Строка - Адрес хранилища на двоичные данные итогового PDF
//
Функция УстановкаШтампаБезКонвертированияPDF(Штамп, ДанныеPDF) Экспорт
Если ОбъектВСпискеТиповПрисоединенныеФайлы(Штамп) Тогда
ДанныеШтампа = РаботаСФайлами.ДвоичныеДанныеФайла(Штамп, Ложь);
ИначеЕсли ТипЗнч(Штамп) = Тип("ДвоичныеДанные") Тогда
ДанныеШтампа = Штамп;
Иначе
ДанныеШтампа = ПолучитьИзВременногоХранилища(Штамп);
КонецЕсли;
Если ОбъектВСпискеТиповПрисоединенныеФайлы(ДанныеPDF) Тогда
ДанныеФайлаPdf = РаботаСФайлами.ДвоичныеДанныеФайла(ДанныеPDF, Ложь);
ИначеЕсли ТипЗнч(ДанныеPDF) = Тип("ДвоичныеДанные") Тогда
ДанныеФайлаPdf = ДанныеPDF;
Иначе
ДанныеФайлаPdf = ПолучитьИзВременногоХранилища(ДанныеPDF);
КонецЕсли;
Если ЗначениеЗаполнено(ДанныеШтампа) И ЗначениеЗаполнено(ДанныеФайлаPdf) Тогда
ШтампКартинка = Новый Картинка(ДанныеШтампа, Истина);
ДокументPDF = Новый ДокументPDF;
ДокументPDF.Прочитать(ДанныеФайлаPdf.ОткрытьПотокДляЧтения());
РасположениеШтампаЭПВPdf = ПолучитьРасположениеШтампаЭПВPdf();
ОриентацияЛандшафт = ОриентацияPDFфайла(ДанныеФайлаPdf) = ОриентацияСтраницы.Ландшафт;
Если РасположениеШтампаЭПВPdf = Перечисления.pdf_РасположениеШтампаЭПВPdf.ЛевыйВерхний Тогда
ОтступX = "0";
ОтступY = "0";
ИначеЕсли РасположениеШтампаЭПВPdf = Перечисления.pdf_РасположениеШтампаЭПВPdf.ЛевыйНижний Тогда
ОтступX = "0";
ОтступY = ?(ОриентацияЛандшафт = Истина, "170", "260");
ИначеЕсли РасположениеШтампаЭПВPdf = Перечисления.pdf_РасположениеШтампаЭПВPdf.ПравыйВерхний Тогда
ОтступX = ?(ОриентацияЛандшафт = Истина, "210", "140");
ОтступY = "0";
ИначеЕсли РасположениеШтампаЭПВPdf = Перечисления.pdf_РасположениеШтампаЭПВPdf.ПравыйНижний Тогда
ОтступX = ?(ОриентацияЛандшафт = Истина, "210", "140");
ОтступY = ?(ОриентацияЛандшафт = Истина, "170", "260");
КонецЕсли;
ОписаниеШтампа = Новый ОписаниеОтображаемогоОбъектаPDF;
ОписаниеШтампа.Лево = ОтступX;
ОписаниеШтампа.Верх = ОтступY;
ОписаниеШтампа.Объект = ШтампКартинка;
ДокументPDF.ДобавитьОтображаемыйОбъект(ОписаниеШтампа);
ПотокЗаписи = Новый ПотокВПамяти;
ДокументPDF.Записать(ПотокЗаписи);
Возврат ПоместитьВоВременноеХранилище(ПотокЗаписи.ЗакрытьИПолучитьДвоичныеДанные());
КонецЕсли;
КонецФункции
Было добавлено перечисление расположения штампа pdf_РасположениеШтампаЭПВPdf.
Рассмотрим и функцию Ориентация PDF-файла, в ней мы и будем работать с ImageMagick.
// Вычисляет ориентацию файла PDF через ImageMagick.
//
// Параметры:
// Адрес - Двоичные данные, Строка - либо двоичные данные файла, либо адрес хранилища с двоичными данными.
//
// Возвращаемое значение:
// ОриентацияСтраницы - портрет или ландшафт
//
Функция ОриентацияPDFфайла(Адрес) Экспорт
ФайлНаДиске = ПолучитьИмяВременногоФайла("pdf");
Если ТипЗнч(Адрес) = Тип("ДвоичныеДанные") Тогда
ДанныеФайла = Адрес;
Иначе
ДанныеФайла = ПолучитьИзВременногоХранилища(Адрес);
КонецЕсли;
ДанныеФайла.Записать(ФайлНаДиске);
Текст = Новый ТекстовыйДокумент;
ИнфаНаДиске = ПолучитьИмяВременногоФайла("txt");
Текст.Записать(ИнфаНаДиске);
ПараметрыImageMagick = СтрШаблон("magick %1 -verbose -format '[%%[printsize.x]]' info:%2", ФайлНаДиске, ИнфаНаДиске);
ЗапуститьImageMagick(ПараметрыImageMagick, ФайлНаДиске);
json = Новый ЧтениеJSON;
json.ОткрытьФайл(ИнфаНаДиске);
json.Прочитать();
РаботаСФайламиБЭД.УдалитьВременныеФайлы(ИнфаНаДиске);
Попытка
Ширина = Окр(25.4 * ПрочитатьJSON(json), 0);
Исключение
Ширина = 0;
КонецПопытки;
Ориентация = ОриентацияСтраницы.Портрет;
Если Ширина > 290 Тогда
Ориентация = ОриентацияСтраницы.Ландшафт;
КонецЕсли;
Возврат Ориентация;
КонецФункции
// Запускает ImageMagick с указанными параметрами. В случае ошибки вызывается исключение,
// в остальных случаях возвращается код возврата ImageMagick.
//
// Параметры:
// Параметры - Строка - параметры запуска ImageMagick.
// ФайлыКУдалению - Массив - необязательный параметр, временные файлы, требующие удаления.
//
// Возвращаемое значение:
// Число - 0 в случае успешного выполнения, 300+ - при завершении с предупреждением.
// Полный список предупреждений см. в документации к ImageMagick.
//
Функция ЗапуститьImageMagick(Параметры, ФайлыКУдалению) Экспорт
СисИнфо = Новый СистемнаяИнформация;
ТипПлатформыСервера = СисИнфо.ТипПлатформы;
ПолныйПуть = ПолучитьФункциональнуюОпцию("pdf_ПутьКImageMagick");
Если (ТипПлатформыСервера <> ТипПлатформы.Linux_x86
И ТипПлатформыСервера <> ТипПлатформы.Linux_x86_64)
И Не ЗначениеЗаполнено(ПолныйПуть) Тогда
РаботаСФайламиБЭД.УдалитьВременныеФайлы(ФайлыКУдалению);
ВызватьИсключение НСтр("ru = 'В настройках программы не указан полный путь к программе ImageMagick.'");
КонецЕсли;
Если ПолныйПуть = "convert.exe" Тогда
РаботаСФайламиБЭД.УдалитьВременныеФайлы(ФайлыКУдалению);
ВызватьИсключение НСтр("ru = 'В настройках программы указан полный путь к устаревшей версии ImageMagick (convert.exe).'");
КонецЕсли;
Если (ТипПлатформыСервера = ТипПлатформы.Linux_x86
Или ТипПлатформыСервера = ТипПлатформы.Linux_x86_64) Тогда
Если ПолныйПуть <> "" Тогда
СтрокаКоманды = СокрЛП(ПолныйПуть) + "" + Параметры;
Иначе
СтрокаКоманды = Параметры;
КонецЕсли;
Иначе
СтрокаКоманды = """" + СокрЛП(ПолныйПуть) + """ " + Параметры;
КонецЕсли;
СтрокаЗапуска = СтрокаКоманды;
КодВозврата = Неопределено;
Попытка
ЗапуститьПриложение(СтрокаЗапуска, , Истина, КодВозврата);
Исключение
РаботаСФайламиБЭД.УдалитьВременныеФайлы(ФайлыКУдалению);
ТекстИсключения = СтрШаблон(НСтр("ru = 'Ошибка при вызове ImageMagick с командной строкой:
|%1
|(%2)'"),
СтрокаКоманды,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение ТекстИсключения;
КонецПопытки;
РаботаСФайламиБЭД.УдалитьВременныеФайлы(ФайлыКУдалению);
// Успех.
Если КодВозврата = 0 Тогда
Возврат КодВозврата;
КонецЕсли;
// Не ImageMagick.
Если КодВозврата = Неопределено Тогда
ТекстИсключения = СтрШаблон(НСтр("ru = 'Нет кода возврата при вызове ImageMagick с командной строкой:
|%1
|Возможно, указанный путь не является путем к ImageMagick.'"),
СтрокаКоманды);
ВызватьИсключение ТекстИсключения;
КонецЕсли;
// Предупреждение.
Если КодВозврата >= 300
И КодВозврата < 400 Тогда
ТекстПредупреждения = СтрШаблон(НСтр("ru = 'Предупреждение %1 при вызове ImageMagick с командной строкой:
|%2
|Подробности см. в документации к ImageMagick.'"),
КодВозврата,
СтрокаКоманды);
ЗаписьЖурналаРегистрации(НСтр("ru = 'Работа с картинками'"),
УровеньЖурналаРегистрации.Предупреждение,,,
ТекстПредупреждения);
Возврат КодВозврата;
КонецЕсли;
// Ошибка ОС или иного приложения.
Если КодВозврата < 300 Тогда
ТекстИсключения = СтрШаблон(НСтр("ru = 'Ошибка %1 при вызове ImageMagick с командной строкой:
|%2
|Возможно, нарушена структура командной строки.'"),
КодВозврата,
СтрокаКоманды);
ВызватьИсключение ТекстИсключения;
КонецЕсли;
// Ошибка ImageMagick.
ТекстИсключения = СтрШаблон(НСтр("ru = 'Ошибка %1 при вызове ImageMagick с командной строкой:
|%2
|Подробности см. в документации к ImageMagick.'"),
КодВозврата,
СтрокаКоманды);
ВызватьИсключение ТекстИсключения;
КонецФункции
Функция ПолучитьРасположениеШтампаЭПВPdf() Экспорт
Возврат ПолучитьФункциональнуюОпцию("pdf_РасположениеШтампаЭПВPdf");
КонецФункции
Функция ОбъектВСпискеТиповПрисоединенныеФайлы(Объект)
Возврат Метаданные.ОпределяемыеТипы.ПрисоединенныйФайл.Тип.СодержитТип(ТипЗнч(Объект));
КонецФункции
Параметр info: командной строки ImageMagick, позволяет сохранить данные об обрабатываемом файле.
А -format позволяет указать, какие именно данные нам интересны. Список полей опции -format можно посмотреть здесь.
Программист продвинутого уровня, который работает в ДО скажет, что в ДО уже есть типовой функционал, проставляющий штампы в файлы PDF. Тут у меня есть возражения.
Во-первых, данный метод на порядок ускоряет работу с многостраничными документами. В типовом функционале PDF файл конвертируется в картинку, потом вставляется штамп и снова конвертируется в PDF. Если в файле много страниц, то процесс может сильно затянуться.
Во-вторых, если исходный PDF в векторном формате, т.е. слои документа с текстом, то они так и остаются. В типовом функционале, из-за конвертации текстовые слои пропадают.
Comments
So empty here ... leave a comment!