Как объединить файлы JPG, XLS, XLSX и TXT в один PDF-документ в 1С и минимизировать его размер?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

В практической деятельности разработчика 1С часто возникает задача консолидации разнородных файлов (изображений, электронных таблиц и текстовых документов) в единый PDF-архив или пакет документов с добавлением сквозного верхнего колонтитула (шапки). При реализации этой задачи стандартными средствами платформы можно столкнуться с двумя серьезными проблемами: неадекватным увеличением размера результирующего PDF-файла (до 150 МБ и более) и некорректным поведением встроенного объекта ПакетОтображаемыхДокументов, который при большом количестве файлов начинает дублировать изображения.

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

Выявляем причину: почему стандартные механизмы дают сбой?

Для начала проанализируем ситуацию и разберем, почему встроенные инструменты ведут себя некорректно в определенных условиях:

  1. Баг платформы в ПакетОтображаемыхДокументов. Поведение, при котором первые 2–3 картинки в итоговом PDF отображаются верно, а последующие дублируют первую выведенную, связано с зарегистрированной ошибкой платформы 1С (встречалась в версиях 8.3.13–8.3.15). При формировании пакета и передаче табличных документов во временное хранилище через метод ПоместитьВоВременноеХранилище платформа некорректно кэшировала внутренние адреса графических объектов.
  2. Огромный размер PDF при прямом сохранении ТабличногоДокумента. Когда мы последовательно выводим все файлы в один большой ТабличныйДокумент, а затем вызываем метод Записать(Путь, ТипФайлаТабличногоДокумента.PDF), платформа сохраняет графические объекты без оптимизации и сжатия. Если исходные JPG-картинки имели высокое разрешение, они переносятся в PDF в «сыром» виде, увеличивая объем файла в несколько раз.

Ниже мы подробно рассмотрим четыре практических метода решения этой задачи в зависимости от возможностей вашей ИТ-инфраструктуры.

Решение 1. Использование объекта ПакетОтображаемыхДокументов (исправление бага платформы)

Использование объекта ПакетОтображаемыхДокументов — наиболее быстрый и технологичный путь, поскольку он формирует пакет асинхронно и сохраняет исходное форматирование страниц для каждого файла. Чтобы этот способ работал корректно и без ошибок с дублированием картинок, необходимо обновить платформу 1С до версии 8.3.16 или выше. В современных релизах этот баг полностью устранен.

Рассмотрим правильный алгоритм формирования пакета документов с очисткой памяти и изоляцией каждого файла в свой контекст:


Процедура СформироватьПакетДокументовНа Платформе8316(ПутьСохраненияПакета)
    
    МакетPDF = ПолучитьОбщийМакет("МакетPDF");
    ОбластьШапка = МакетPDF.ПолучитьОбласть("Шапка");
    
    Пакет = Новый ПакетОтображаемыхДокументов;
    
    Для Каждого Строка Из ТабличноеПолеФайлы Цикл
        
        Если Не Строка.Пометка Тогда
            Продолжить;
        КонецЕсли;
        
        ФайлИсточника = Новый Файл(Строка.Путь);
        ТабДокДляФайла = Новый ТабличныйДокумент;
        
        // 1. Выводим сквозную шапку из макета
        ВывестиОбласть(ТабДокДляФайла, ОбластьШапка, ФайлИсточника.Имя);
        
        Если ФайлИсточника.Расширение = ".jpg" ИЛИ ФайлИсточника.Расширение = ".jpeg" Тогда
            
            ОбластьИзображение = МакетPDF.ПолучитьОбласть("Изображение");
            Рисунок = ОбластьИзображение.Рисунки.Добавить(ТипРисункаТабличногоДокумента.Картинка);
            Рисунок.Картинка = Новый Картинка(ФайлИсточника.ПолноеИмя);
            Рисунок.Расположить(ОбластьИзображение.Область("R1C1:R66C58"));
            Рисунок.РазмерКартинки = РазмерКартинки.Растянуть;
            
            // Отключаем границы картинки
            Рисунок.ГраницаСверху  = Ложь;
            Рисунок.ГраницаСнизу   = Ложь;
            Рисунок.ГраницаСлева   = Ложь;
            Рисунок.ГраницаСправа  = Ложь;
            
            ТабДокДляФайла.Вывести(ОбластьИзображение);
            
        ИначеЕсли ФайлИсточника.Расширение = ".xls" ИЛИ ФайлИсточника.Расширение = ".xlsx" Тогда
            
            ТабДокФайл = Новый ТабличныйДокумент;
            ТабДокФайл.Прочитать(ФайлИсточника.ПолноеИмя);
            
            // Настройка автовысоты строк для предотвращения обрезки текста
            НачальнаяСтрока = ?(ТабДокФайл.Области.Найти("Лист1") <> Неопределено, ТабДокФайл.Области.Найти("Лист1").Верх, 1);
            КонечнаяСтрока  = ТабДокФайл.ВысотаТаблицы;
            Для ТекущаяСтрока = НачальнаяСтрока По КонечнаяСтрока Цикл
                ОбластьЯчейки = ТабДокФайл.Область(ТекущаяСтрока, 1);
                ОбластьЯчейки.ВысотаСтроки = 0;
                ОбластьЯчейки.АвтоВысотаСтроки = Истина;
            КонецЦикла;
            
            ТабДокФайл.АвтоМасштаб = Истина;
            ТабДокДляФайла.Вывести(ТабДокФайл);
            
        ИначеЕсли ФайлИсточника.Расширение = ".txt" Тогда
            
            ОбластьИзображение = МакетPDF.ПолучитьОбласть("Изображение");
            Рисунок = ОбластьИзображение.Рисунки.Добавить(ТипРисункаТабличногоДокумента.Текст);
            Рисунок.Расположить(ОбластьИзображение.Область("R1C1:R66C58"));
            
            ТекстовыйФайл = Новый ТекстовыйДокумент;
            ТекстовыйФайл.Прочитать(ФайлИсточника.ПолноеИмя, КодировкаТекста.UTF8);
            Рисунок.Текст = ТекстовыйФайл.ПолучитьТекст();
            
            // Убираем рамки текстового поля
            Рисунок.ГраницаСверху  = Ложь;
            Рисунок.ГраницаСнизу   = Ложь;
            Рисунок.ГраницаСлева   = Ложь;
            Рисунок.ГраницаСправа  = Ложь;
            
            ТабДокДляФайла.Вывести(ОбластьИзображение);
            
        КонецЕсли;
        
        ТабДокДляФайла.АвтоМасштаб = Истина;
        
        // Помещаем сформированный ТабДок в пакет документов
        ЭлементПакета = Пакет.Состав.Добавить();
        ЭлементПакета.Данные = ПоместитьВоВременноеХранилище(ТабДокДляФайла, Новый УникальныйИдентификатор);
        ЭлементПакета.Наименование = ФайлИсточника.Имя;
        
    КонецЦикла;
    
    // Записываем итоговый файл
    ИмяВыходногоФайла = ПутьСохраненияПакета + "Пакет_Документов.pdf";
    Пакет.ЗаписатьФайлДляПечати(ИмяВыходногоФайла);
    
КонецПроцедуры

Решение 2. Оптимизация размера PDF при прямом сохранении из 1С

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

Начиная с версии платформы 8.3.14, разработчикам доступен мощный инструмент для работы с графическими объектами — метод Преобразовать() объекта Картинка. Мы можем принудительно уменьшить разрешение тяжелых изображений перед их добавлением в табличный документ. Кроме того, при сохранении табличного документа мы задействуем стандарт PDF/A-3 (доступен с платформы 8.3.16), который использует более эффективные алгоритмы сжатия данных.

Проанализируем пример реализации сжатия графики «на лету»:


Функция ПолучитьСжатуюКартинку(ПутьКФайлу)
    
    ИсходнаяКартинка = Новый Картинка(ПутьКФайлу);
    
    // Если картинка слишком большая, пережимаем ее в JPEG с ограничением качества
    ПараметрыПреобразования = Новый ПараметрыПреобразованияКартинки;
    ПараметрыПреобразования.Формат = ФорматКартинки.JPEG;
    
    // Устанавливаем максимальный размер по ширине с сохранением пропорций
    ПараметрыПреобразования.Ширина = 1280; 
    
    ОбработаннаяКартинка = ИсходнаяКартинка.Преобразовать(ПараметрыПреобразования);
    
    Возврат ОбработаннаяКартинка;
    
КонецФункции

Процедура ЗаписатьОптимизированныйPDF(ТабДок, ПутьЗаписи)
    
    // Вместо стандартного PDF используем формат PDF_A_3 или PDF_A_2
    // Это позволяет существенно снизить итоговый вес документа за счет оптимизации структуры
    ТабДок.Записать(ПутьЗаписи, ТипФайлаТабличногоДокумента.PDF_A_3);
    
КонецПроцедуры

Решение 3. Использование виртуального принтера Bullzip PDF Printer

В тех случаях, когда обновление платформы 1С невозможно (вы вынуждены работать на старых релизах вроде 8.3.13), альтернативным решением является автоматизация печати через виртуальный PDF-принтер. Известный представитель этого класса программ — Bullzip PDF Printer.

Для программного управления параметрами печати мы будем использовать COM-объект Bullzip.PDFPrinterSettings. Проанализируем пример настройки принтера со сжатием картинок и блокировкой всплывающих диалоговых окон:


Процедура НапечататьЧерезBullzip(ТабДок, ПутьКФайлуPDF)
    
    Попытка
        ПринтерSettings = Новый COMОбъект("Bullzip.PDFPrinterSettings");
    Исключение
        Предупреждение("Не удалось инициализировать Bullzip PDF Printer. Проверьте установку программы.");
        Возврат;
    КонецПопытки;
    
    // Настраиваем параметры сохранения файла
    ПринтерSettings.SetValue("Output", ПутьКФайлуPDF); 
    ПринтерSettings.SetValue("ShowSettings", "never"); 
    ПринтерSettings.SetValue("ShowSaveAS", "never"); 
    ПринтерSettings.SetValue("ShowProgress", "no"); 
    ПринтерSettings.SetValue("ShowProgressFinished", "no"); 
    ПринтерSettings.SetValue("ShowPDF", "no"); 
    ПринтерSettings.SetValue("ConfirmOverwrite", "no");
    
    // Настройки сжатия графики для уменьшения объема файла
    // screen - максимальное сжатие (низкое качество, для экрана)
    // ebook - среднее сжатие (оптимальный баланс качества и веса)
    ПринтерSettings.SetValue("PDFSettings", "ebook"); 
    
    // Записываем настройки в конфигурационный файл принтера
    // Параметр 1 означает запись настроек для текущего сеанса печати
    ПринтерSettings.WriteSettings(1);  
    
    // Отправляем табличный документ на печать
    ТабДок.ИмяПринтера = "Bullzip PDF Printer";
    ТабДок.Напечатать(РежимИспользованияДиалогаПечати.НеИспользовать);
    
КонецПроцедуры

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

Решение 4. Консольное склеивание через бесплатную утилиту PDFtk Server

Если у вас в системе работает более 10 пользователей, бесплатная версия Bullzip PDF Printer вам не подойдет по условиям лицензионного соглашения. Покупка коммерческих лицензий на сотни рабочих мест может быть нецелесообразна. В этом случае мы рекомендуем использовать консольную утилиту PDFtk Server (распространяется бесплатно и без ограничений на коммерческое использование).

Идея метода заключается в том, что 1С очень быстро сохраняет отдельные файлы в PDF. Мы можем сохранить каждый файл с шапкой в отдельный временный PDF-файл средствами 1С, а затем мгновенно «склеить» их одной консольной командой без вызова окон печати.

Разберем по шагам реализацию этого механизма:


Процедура СклеитьPDFЧерезPDFtk(МассивПутейВременныхPDF, ПутьВыходногоФайла)
    
    // Формируем строку аргументов для PDFtk
    // Шаблон команды: pdftk.exe file1.pdf file2.pdf cat output result.pdf
    СписокФайловСтрокой = "";
    Для Каждого ПутьКФайлу Из МассивПутейВременныхPDF Цикл
        СписокФайловСтрокой = СписокФайловСтрокой + """" + ПутьКФайлу + """ ";
    КонецЦикла;
    
    ПутьКПрограмме = "C:\Program Files (x86)\PDFtk Server\bin\pdftk.exe";
    ШаблонЗапуска = """%1"" %2 cat output ""%3""";
    
    КомандаЗапуска = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
        ШаблонЗапуска, 
        ПутьКПрограмме, 
        СписокФайловСтрокой, 
        ПутьВыходногоФайла
    );
    
    // Запускаем консольное приложение в скрытом режиме
    // Третий параметр "Истина" указывает системе ожидать завершения выполнения команды
    ЗапуститьПриложение(КомандаЗапуска, , Истина);
    
    // Очищаем временные файлы
    Для Каждого ПутьКФайлу Из МассивПутейВременныхPDF Цикл
        Попытка
            УдалитьФайлы(ПутьКФайлу);
        Исключение
            // Файл заблокирован или уже удален
        КонецПопытки;
    КонецЦикла;
    
КонецПроцедуры

Данный подход обладает неоспоримыми преимуществами: высокая скорость склейки (несколько секунд для десятков файлов), отсутствие зависаний принтера печати, полная независимость от окон интерфейса и абсолютная бесплатность использования решения на любом количестве серверов и рабочих станций.

← На главную