Печать в DOCX средствами БСП работает без COM-объекта Word и без установленного Office. Механизм «Работа с макетами офисных документов» в общем модуле УправлениеПечатью распаковывает шаблон DOCX, находит в нем размеченные области и параметры, заполняет их данными и собирает новый файл. Всё выполняется на сервере, поэтому подходит для сервера 1С на Linux.

Описание ниже — по БСП 3.1.9; механизм есть и в более ранних версиях, но сигнатуры процедур в вашей версии стоит сверить с комментариями в модуле. Работает только формат DOCX: старые DOC-файлы нужно пересохранить.

Зачем отказываться от COM

Классическая печать в Word через Новый COMОбъект("Word.Application") создает проблемы:

Проблема COMКак решает механизм БСП
Нужен Windows и установленный Office на сервере или клиентеРаботает на любой ОС сервера 1С
Зависшие процессы WINWORD.EXE, утечки памятиНет внешнего процесса, только файлы и XML
Лицензирование Office на сервереOffice не нужен
Печать на клиенте недоступна в веб-клиентеФайл формируется на сервере и отдается пользователю
Медленно на больших пакетах документовРабота с XML заметно быстрее запуска приложения

На переводах серверов 1С на Linux это одна из первых доработок, которые я делаю: все печатные формы через COM нужно либо переписать на табличный документ, либо перевести на механизм офисных макетов БСП.

Разметка шаблона DOCX

Шаблон делается в Word или любом редакторе, сохраняющем DOCX. Логика та же, что у табличного макета 1С: области и параметры.

Параметры. В тексте пишется {v8 ИмяПараметра}. При заполнении передается структура, где ключ — имя параметра. Значение приводится к строке функцией Строка(), поэтому даты, суммы и количества форматируйте сами до передачи. Параметр можно поставить и в адрес гиперссылки.

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

Области. Фрагмент шаблона заключается в метки:

text
{v8 Область.Шапка}
Договор № {v8 Номер} от {v8 Дата}
{/v8 Область.Шапка}

Правила, которые лучше соблюдать с первого раза:

  • метки начала и конца области — в основной части документа, не внутри таблицы;
  • метки и содержимое области — на разных строках, иначе содержимое не распознается;
  • вложенные области не поддерживаются: распознается только внешняя;
  • для таблицы шапка и строка таблицы размечаются отдельными областями, строка выводится многократно;
  • для многоуровневого списка заранее готовится отдельная область на каждый уровень;
  • разрыв страницы — это отдельная область, внутри которой стоит только разрыв.

Колонтитулы размечать не нужно, они являются областями сами по себе и имеют фиксированные имена:

Имя областиКогда появляется
ВерхнийКолонтитул, НижнийКолонтитулВсегда; при особом титульном — для остальных страниц, при четных/нечетных — для нечетных
ВерхнийТитульныйКолонтитул, НижнийТитульныйКолонтитулВключен «Особый колонтитул для первой страницы»
ВерхнийЧетныйКолонтитул, НижнийЧетныйКолонтитулВключены «Разные колонтитулы для четных и нечетных страниц»

Даже в пустом документе разбор находит область Абзац — фактически перевод строки. Ее удобно использовать как разделитель.

Печать в DOCX средствами БСП: шаблон разбит на области шапки, строк таблицы и колонтитулов
Печать в DOCX средствами БСП: шаблон разбит на области шапки, строк таблицы и колонтитулов

Процедуры модуля УправлениеПечатью

ПроцедураНазначение
ИнициализироватьМакетОфисногоДокументаРазбирает двоичные данные DOCX в структуру макета
ИнициализироватьПечатнуюФормуСоздает пустую печатную форму на основе макета
ОбластьМакетаПолучает область по имени и типу
ПрисоединитьОбластьВыводит область без заполнения
ЗаполнитьПараметрыЗаполняет параметры области или всей формы
ПрисоединитьОбластьИЗаполнитьПараметрыВыводит и сразу заполняет область
ПрисоединитьИЗаполнитьКоллекциюВыводит область для каждого элемента массива структур (типы «СтрокаТаблицы» и «Список»)
ВставитьРазрывНаНовуюСтрокуВставляет перевод строки
СформироватьДокументСобирает DOCX и возвращает адрес во временном хранилище
ОчиститьСсылкиУдаляет временные файлы макета или печатной формы

Тип области при получении указывается явно: Общая, СтрокаТаблицы, Список или имя колонтитула.

У нескольких процедур есть устаревшие параметры с префиксом Удалить. Они не используются, но не везде имеют значение по умолчанию, поэтому их места при вызове нужно оставлять пустыми через запятые.

Код формирования печатной формы

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

1С
// Формирует печатную форму договора в формате DOCX.
//
// Параметры:
//   ДвоичныеДанныеМакета - ДвоичныеДанные - шаблон DOCX.
//   ДанныеДоговора       - Структура - параметры шапки.
//   Товары               - Массив из Структура - строки таблицы.
//
// Возвращаемое значение:
//   Строка, Неопределено - адрес DOCX во временном хранилище.
//
Функция СформироватьДоговорDOCX(ДвоичныеДанныеМакета, ДанныеДоговора, Товары) Экспорт

	Макет = УправлениеПечатью.ИнициализироватьМакетОфисногоДокумента(ДвоичныеДанныеМакета, , );
	Если Макет = Неопределено Тогда
		ОбщегоНазначения.СообщитьПользователю(НСтр("ru = 'Не удалось прочитать шаблон DOCX.'"));
		Возврат Неопределено;
	КонецЕсли;

	ПечатнаяФорма = Неопределено;
	АдресДокумента = Неопределено;

	Попытка

		ПечатнаяФорма = УправлениеПечатью.ИнициализироватьПечатнуюФорму(, , Макет);

		ОбластьШапка = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбласти("Шапка", "Общая"));
		УправлениеПечатью.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, ОбластьШапка, ДанныеДоговора, Ложь);

		ОбластьШапкаТаблицы = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбласти("ШапкаТаблицы", "Общая"));
		УправлениеПечатью.ПрисоединитьОбласть(ПечатнаяФорма, ОбластьШапкаТаблицы, Ложь);

		ОбластьСтрока = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбласти("СтрокаТовара", "СтрокаТаблицы"));
		УправлениеПечатью.ПрисоединитьИЗаполнитьКоллекцию(ПечатнаяФорма, ОбластьСтрока, Товары, Ложь);

		ОбластьПодвал = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбласти("Подписи", "Общая"));
		УправлениеПечатью.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, ОбластьПодвал, ДанныеДоговора, Ложь);

		АдресДокумента = УправлениеПечатью.СформироватьДокумент(ПечатнаяФорма);

	Исключение
		ЗаписьЖурналаРегистрации(НСтр("ru = 'Печать DOCX'", ОбщегоНазначения.КодОсновногоЯзыка()),
			УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
		ОбщегоНазначения.СообщитьПользователю(НСтр("ru = 'Ошибка формирования документа, подробности в журнале регистрации.'"));
	КонецПопытки;

	// Временные файлы удаляем в любом случае: и после успеха, и после ошибки.
	Если ПечатнаяФорма <> Неопределено Тогда
		УправлениеПечатью.ОчиститьСсылки(ПечатнаяФорма);
	КонецЕсли;
	УправлениеПечатью.ОчиститьСсылки(Макет);

	Возврат АдресДокумента;

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

Функция ОписаниеОбласти(ИмяОбласти, ТипОбласти)
	Возврат Новый Структура("ИмяОбласти, ТипОбласти", ИмяОбласти, ТипОбласти);
КонецФункции

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

1С
Товары = Новый Массив;
Для Каждого СтрокаТовара Из Документ.Товары Цикл
	Товары.Добавить(Новый Структура("НомерСтроки, Номенклатура, Количество, Сумма",
		СтрокаТовара.НомерСтроки,
		Строка(СтрокаТовара.Номенклатура),
		Формат(СтрокаТовара.Количество, "ЧДЦ=3; ЧН=0"),
		Формат(СтрокаТовара.Сумма, "ЧДЦ=2; ЧН=0,00")));
КонецЦикла;

Шаблон удобнее хранить как макет типа «Двоичные данные» и получать через УправлениеПечатью.МакетПечатнойФормы("Документ.Договор.ПФ_DOCX_Договор"): тогда пользователь сможет заменить его на свой в списке макетов печатных форм, не трогая конфигурацию.

Заполнение области и вывод отдельно

ЗаполнитьПараметры и ПрисоединитьОбласть можно вызывать раздельно, например когда параметры одной области собираются из нескольких структур. Но есть ловушка: если заполнить параметры в самой области макета, повторно использовать ее с другими значениями не выйдет — метки уже заменены. Для повторяющихся областей используйте ПрисоединитьОбластьИЗаполнитьПараметры или ПрисоединитьИЗаполнитьКоллекцию: они сначала выводят копию области в форму, а потом заполняют.

Картинки в DOCX

Картинка выводится в параметр. Рабочий способ — передать структуру с адресом двоичных данных во временном хранилище и, по желанию, размерами:

1С
ОписаниеКартинки = Новый Структура;
ОписаниеКартинки.Вставить("АдресКартинки", ПоместитьВоВременноеХранилище(ДвоичныеДанныеПечати, Новый УникальныйИдентификатор));
ОписаниеКартинки.Вставить("Ширина", 40);
// Высоту можно не задавать: она рассчитается пропорционально.

ОбластьПечать = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбласти("Печать", "Общая"));
УправлениеПечатью.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, ОбластьПечать,
	Новый Структура("ОттискПечати", ОписаниеКартинки), Ложь);

Если передать в параметр просто адрес из временного хранилища без структуры, в БСП 3.1.9 картинка не выводится: процедура заполнения распознает картинку, но не создает описание размеров. Всегда оборачивайте адрес в структуру.

Подключение к подсистеме «Печать»

Чтобы DOCX открывался из стандартного подменю «Печать», в процедуре Печать менеджера объекта положите адрес файла в коллекцию печатных форм:

1С
Процедура Печать(МассивОбъектов, ПараметрыПечати, КоллекцияПечатныхФорм, ОбъектыПечати, ПараметрыВывода) Экспорт

	Если Не УправлениеПечатью.НуженМакетПечатнойФормы(КоллекцияПечатныхФорм, "ДоговорDOCX") Тогда
		Возврат;
	КонецЕсли;

	ДвоичныеДанныеМакета = УправлениеПечатью.МакетПечатнойФормы("Документ.Договор.ПФ_DOCX_Договор");
	ПечатнаяФорма = УправлениеПечатью.СведенияОПечатнойФорме(КоллекцияПечатныхФорм, "ДоговорDOCX");
	ПечатнаяФорма.ОфисныеДокументы = Новый Соответствие;

	Для Каждого Договор Из МассивОбъектов Цикл
		Адрес = СформироватьДоговорDOCX(ДвоичныеДанныеМакета, ДанныеШапки(Договор), ДанныеТоваров(Договор));
		Если Адрес <> Неопределено Тогда
			ПечатнаяФорма.ОфисныеДокументы.Вставить(Адрес, СтрШаблон("Договор %1", Договор.Номер));
		КонецЕсли;
	КонецЦикла;

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

Состав колонок коллекции печатных форм зависит от версии БСП, поэтому сверьтесь с комментарием к УправлениеПечатьюПереопределяемый в вашей конфигурации. Если печать вызывается из собственной команды, адрес можно передать на клиент и отдать файл через НачатьПолучениеФайлаССервера: асинхронный вариант с диалогом работает и в веб-клиенте без расширения работы с файлами.

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

  • Область не находится. Метки начала и конца стоят на одной строке с текстом или внутри таблицы.
  • Внутренняя область пустая. Области вложены друг в друга — разбор видит только внешнюю.
  • Все строки таблицы одинаковые. Параметры заполнены в самой области макета, а затем она выводится в цикле.
  • Даты с временем «00:00:00». Значения не отформатированы до передачи, сработал Строка().
  • На сервере копятся временные каталоги. Не вызван ОчиститьСсылки после ошибки. Очистку делайте вне блока Попытка, как в примере.
  • Ошибка при вызове процедуры. Пропущены позиции устаревших параметров Удалить....

Печатные формы, в том числе перевод с COM на серверную генерацию при миграции на Linux, я делаю в рамках услуг печатные формы 1С и 1С на Linux и PostgreSQL.

Коротко

  • Печать в DOCX средствами БСП не требует Word и работает на сервере под Linux.
  • Шаблон размечается метками {v8 Параметр} и парами {v8 Область.Имя} / {/v8 Область.Имя}, колонтитулы — готовые области.
  • Последовательность: инициализировать макет, создать печатную форму, вывести области, сформировать документ, очистить ссылки.
  • Картинки передаются структурой с АдресКартинки, а не голым адресом.
  • Временные файлы очищайте всегда, в том числе после ошибки.