Как правильно сохранить табличный документ в поток памяти и выгрузить в PDF?

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

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

В чем причина ошибки при сохранении в поток памяти?

Проанализируем исходную реализацию, с которой возникла проблема:


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

Посмотрим на вызов метода ТабличныйДокумент.Записать(). Если при записи на диск с указанием пути к файлу расширение .pdf разработчики нередко указывают в параметрах явно, то при вызове метода для потока памяти второй параметр часто случайно опускают. В платформе 1С метод Записать у объекта ТабличныйДокумент вторым параметром принимает значение перечисления ТипФайлаТабличногоДокумента. Если этот параметр не передан, платформа по умолчанию сохраняет данные во внутреннем формате ТипФайлаТабличногоДокумента.MXL.

В результате в поток записывается структура документа 1С (MXL). Когда мы впоследствии сохраняем полученные ДвоичныеДанные на диск с расширением .pdf, программа просмотра PDF пытается прочитать структуру файла MXL как документ PDF и сообщает о критической ошибке формата. Таким образом, главное правило — всегда явно указывать тип файла при записи в поток.

Корректный пример записи табличного документа в поток памяти

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


&НаСервере
Процедура СохранитьТабличныйДокументВФайл()

    ТабДок = Новый ТабличныйДокумент;
    // ... здесь выполняется заполнение макета и вывод секций ...

    // Создаем поток в оперативной памяти
    ПотокФайла = Новый ПотокВПамяти();
    
    // В обязательном порядке передаем тип выгружаемого файла
    ТабДок.Записать(ПотокФайла, ТипФайлаТабличногоДокумента.PDF);
    
    // Получаем двоичные данные и закрываем поток
    ДвоичныеДанныеДокумента = ПотокФайла.ЗакрытьИПолучитьДвоичныеДанные();
    
    // Сохраняем готовый PDF-файл на диск
    МестоположениеФайла = "d:\Отчет.pdf";
    ДвоичныеДанныеДокумента.Записать(МестоположениеФайла);

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

Обратим внимание на метод ЗакрытьИПолучитьДвоичныеДанные(). Он атомарно завершает работу с потоком и сразу возвращает сформированный объект ДвоичныеДанные. Если предварительно вызвать метод Закрыть(), последующее обращение к ЗакрытьИПолучитьДвоичныеДанные() завершится ошибкой, так как закрытый поток уже недоступен для чтения.

Оптимизация схемы Клиент-Сервер и избыточность временного хранилища

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

Выясним причину неэффективности такого подхода. Помещение во временное хранилище предназначено исключительно для передачи данных между серверными вызовами либо между клиентом и сервером. Внутри одной процедуры на сервере этот шаг создает лишнюю нагрузку на сериализацию данных. Если сохранение файла выполняется на сервере, метод Записать() вызывается непосредственно у исходного объекта ДвоичныеДанные.

Если же файл требуется доставить на компьютер пользователя, разберем типовую и правильную схему взаимодействия:

  1. На сервере формируем ДвоичныеДанные через ПотокВПамяти и помещаем их во временное хранилище, возвращая адрес на клиент.
  2. На клиенте получаем файл по адресу временного хранилища и предлагаем пользователю сохранить его на локальный диск с помощью стандартных диалогов платформы.

&НаСервере
Функция СформироватьПечатнуюФормуВПамяти()
    
    ТабДок = Новый ТабличныйДокумент;
    // Заполнение табличного документа
    
    ПотокФайла = Новый ПотокВПамяти();
    ТабДок.Записать(ПотокФайла, ТипФайлаТабличногоДокумента.PDF);
    ДвоичныеДанные = ПотокФайла.ЗакрытьИПолучитьДвоичныеДанные();
    
    Возврат ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);

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

&НаКлиенте
Процедура СохранитьНаКлиенте(Команда)
    
    АдресФайла = СформироватьПечатнуюФормуВПамяти();
    ПараметрыПолучения = Новый ПараметрыДиалогаПолученияФайлов();
    ПараметрыПолучения.Заголовок = "Сохранение отчета в PDF";
    
    НачатьПолучениеФайлаССервера(АдресФайла, "ПечатнаяФорма.pdf", ПараметрыПолучения);

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

Важные технические нюансы работы с объектом «ПотокВПамяти» и экспортом PDF

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

  1. Нагрузка на оперативную память: Объект ПотокВПамяти размещает генерируемый двоичный массив целиком в адресном пространстве процесса rphost (на сервере) или тонкого клиента. Если отчет формируется на сотни или тысячи страниц, одновременное создание нескольких таких документов может вызвать резкий скачок потребления памяти рабочим процессом платформы и привести к аварийному перезапуску службы. Для тяжелых отчетов безопаснее использовать альтернативу: сохранять файл на серверный диск через ПолучитьИмяВременногоФайла("pdf") с последующей очисткой через УдалитьФайлы().
  2. Позиционирование каретки: Если поток не закрывается сразу через ЗакрытьИПолучитьДвоичныеДанные(), а передается в другие объекты (например, ЧтениеДанных), обязательно нужно сбрасывать указатель: Поток.ТекущаяПозиция = 0;, поскольку после записи табличного документа каретка находится в самом конце потока.
  3. Шрифты на сервере Linux: Если сервер 1С работает под управлением операционной системы Linux, экспорт табличного документа в PDF требует наличия установленной библиотеки fontconfig и пакета системных шрифтов (Microsoft Core TrueType Fonts). Если шрифты отсутствуют, сервер выдаст ошибку формирования файла либо итоговый PDF будет содержать нечитаемые символы.
← На главную