Внешняя печатная форма с сохранением дизайна в MS Word или LibreOffice из 1С

Шпаргалка по созданию внешней печатной формы, которая выводит результат в MS Word или в любой другой офисный пакет (LibreOffice, OnlyOffice, МойОфис и т.д.), который установлен на компьютере пользователя. При этом офисный пакет запускается даже на Linux (используем современный язык платформы 1С)!
Подготовка шаблона печатной формы
Верстаем в MS Word (LibreOffice, Google Doc) шаблон на красивом фирменном бланке с колонтитулами и логотипами, по следующему образцу. Просто набираем в лоб текст, без создания закладок и прочих хитростей:
В фигурных скобках находятся тэги, все они должны иметь формат {v8 ...}. Используются два вида тэгов:
- {v8 Область.ИмяОбласти} и {/v8 Область.ИмяОбласти} определяют начало и конец именованной области макета. Обязательно должен быть открывающий тэг и закрывающий! На печать вы выводятся.
 - {v8 ИмяПараметра} - параметр, будет заменен значением при программном заполнении. Сохраняет форматирование (шрифт, цвет, фон)! Закрывающий тэг не нужен
 
Созданный шаблон загружаем в новый макет в двоичном формате:
По соглашению, принятому в библиотеке БСП имя макета должно иметь формат ПФ_DOC_НаименованиеМакета:
Подготовка внешней обработки
В модуль обработки добавляем стандарное описание внешней обработки для БСП. В качестве примера внешняя печатная форма добавлена назначением на справочник "Сотрудники":
Функция СведенияОВнешнейОбработке() Экспорт
	
    ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("3.1.9.232");
	
    ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиПечатнаяФорма();
    ПараметрыРегистрации.Версия = "1.0.0.1"; 
    ПараметрыРегистрации.Информация = ЭтотОбъект.Метаданные().Синоним;
    ПараметрыРегистрации.Назначение.Добавить("Справочник.Сотрудники");
	
    НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
    НоваяКоманда.Представление = ЭтотОбъект.Метаданные().Синоним;
    НоваяКоманда.Идентификатор = ЭтотОбъект.Метаданные().Имя;
    НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовКлиентскогоМетода();
    НоваяКоманда.ПоказыватьОповещение = Ложь;
	
    Возврат ПараметрыРегистрации;
	
КонецФункции
Ключевые моменты в описании таковы (другие значения не годятся для нашей задачи):
| Вид обработки: | ВидОбработкиПечатнаяФорма() | 
|---|---|
| Тип команды: | ТипКомандыВызовКлиентскогоМетода() | 
На следующем шаге нужно добавить форму и назначить её основной для внешней обработки. Эта форма необязательно должна показываться пользователю, и в моём примере она не показывается - поэтому ничего в ней не рисуем. Нам нужен только модуль этой формы и в модуле важна лишь одна экспортная процедура, которую будет искать и запускать БСП в контексте клиента:
&НаКлиенте
Процедура Печать(Идентификатор, ОбъектыНазначения)  Экспорт
    // ОбъектыНазначения - это массив ссылок на объекты, на которые мы 
    // назначили внешнюю печатную форму в описании. Их пользователь может выбрать
    // как в форме элемента, так и в форме списка (множественный выбор)
    Для Каждого ОбъектНазначения Из ОбъектыНазначения Цикл
        
        Адрес = ВыполнитьПечатьDocxНаСервере(ОбъектНазначения);
        // в Адресе находится адрес ВременногоХранилища с готовым результатом
        // Как с ним поступить правильно покажу в конце статьи
    
    КонецЦикла;
КонецПроцедуры
Программное заполнение
&НаСервере
Функция ВыполнитьПечатьDocxНаСервере(ОбъектНазначения)
        
    ОписаниеОбластиТекст = Новый Структура("ИмяОбласти,ТипОбласти", "ТекстДоговора", "Общая");
    ОписаниеОбластиВКолонтитул = Новый Структура("ИмяОбласти,ТипОбласти", "ВерхнийКолонтитул", "ВерхнийКолонтитул");
    ОписаниеОбластиНКолонтитул = Новый Структура("ИмяОбласти,ТипОбласти", "НижнийКолонтитул", "НижнийКолонтитул");        
        
    ДвоичныеДанныеМакета = РеквизитФормыВЗначение("Объект").ПолучитьМакет("ПФ_DOC_Допсоглашение");
    Макет = УправлениеПечатью.ИнициализироватьМакетОфисногоДокумента(ДвоичныеДанныеМакета,,);
    АдресХранилищаПечатнойФормы = "";
        
    ПечатнаяФорма = УправлениеПечатью.ИнициализироватьПечатнуюФорму(,,Макет);
    Если ПечатнаяФорма = Неопределено Тогда
        УправлениеПечатью.ОчиститьСсылки(Макет);
        Возврат АдресХранилищаПечатнойФормы;
    КонецЕсли;
            
    // Пример вывода верхнего колонтитула
        
    Область = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбластиВКолонтитул);
    УправлениеПечатью.ПрисоединитьОбласть(ПечатнаяФорма, Область, Ложь);
            
    // Пример вывода нижнего колонтитула
        
    Область = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбластиНКолонтитул);
    УправлениеПечатью.ПрисоединитьОбласть(ПечатнаяФорма, Область, Ложь);
    
    // Тело печатной формы с параметрами:
    ДанныеЗаполнения = ПолучитьДанныеДляПечатнойФормы(ОбъектНазначения);
    // здесь мы должны получить структуру, ключ - имя параметра в шаблоне Word, значение - строка
    Область = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбластиТекст);
    УправлениеПечатью.ПрисоединитьОбластьИЗаполнитьПараметры(ПечатнаяФорма, Область, ДанныеЗаполнения);
    
    АдресХранилищаПечатнойФормы = УправлениеПечатью.СформироватьДокумент(ПечатнаяФорма);
            
    УправлениеПечатью.ОчиститьСсылки(Макет);
    УправлениеПечатью.ОчиститьСсылки(ПечатнаяФорма);
    
    Возврат АдресХранилищаПечатнойФормы;
        
КонецФункции
Разрывы страниц и разделов
Для вывода разрыва страниц нужно в макете добавить область по следующему образцу:
Обратите внимание, что область макета не может быть пустой (иначе БСП просто не выводит ничего), поэтому необходимо вставлять в область разрыва одну пустую строку.
ОписаниеОбластиРазрывСтраницы = Новый Структура("ИмяОбласти,ТипОбласти", "РазрывСтраницы", "Общая");
ОбластьРазрыв = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбластиРазрывСтраницы);
УправлениеПечатью.ПрисоединитьОбласть(ПечатнаяФорма, ОбластьРазрыв);
К сожалению, с разрывом разделов библиотека на данный момент не умеет работать и такой фокус не работает! Как обходное решение, можно отдавать клиенту за один вызов два файла (например, в первом файле страницы в портретной ориентации, во втором - в альбомной).
Что ещё может БСП
В примере выше для простоты использованы не все типы областей. В описании функции УправлениеПечатью.ОбластьМакета можно подсмотреть их полный список:
//    * ТипТипОбласти - Строка - тип области: 
//      "ВерхнийКолонтитул", "НижнийКолонтитул",
//      "ВерхнийТитульныйКолонтитул", "НижнийТитульныйКолонтитул",
//      "ВерхнийЧетныйКолонтитул", "НижнийЧетныйКолонтитул",
//      "Общая",
//      "СтрокаТаблицы", 
//      "Список".
Для заполнения таблиц в один заход в этом же модуле смотрите функцию ПрисоединитьИЗаполнитьКоллекцию.
Как вывести результат в любую офисную программу
Ниже пример самого древнего и протухшего кода. Перед выполнением необходимо записать двоичные данные в файл на компьютере клиента (вы знаете как это сделать):
Попытка
    ОбъектВорд = Новый COMОбъект("Word.Application"); 
    ОбъектВорд.Documents.Add(ИмяФайлаDocx);
    ШаблонВорд = ОбъектВорд.ActiveDocument; 
    ОбъектВорд.Application.Visible = Истина;
    ОбъектВорд.Activate();     	
Исключение
    Сообщить("Разработчик передает пламенный привет линуксам, макосям,");              
    Сообщить("а также LibreOffice");              
КонецПопытки;
Другой устаревший код - использовать процедуру глобального контекста ЗапуститьПриложение(ИмяФайлаDocx). Он работает только в Windows.
Если ОбщегоНазначенияКлиентСервер.ЭтоWindowsКлиент() Тогда 
    ЗапуститьПриложение(ИмяФайлаDocx);
ИначеЕсли ОбщегоНазначенияКлиентСервер.ЭтоLinuxКлиент() Тогда
    Сообщить("Красноглазые пингвинусы должны страдать!");              
ИначеЕсли ОбщегоНазначенияКлиентСервер.ЭтоOSXКлиент() Тогда
    Сообщить("Нашёл деньги на макбук? Закажи печатку Эксперту по технологическим вопросам!");              
ИначеЕсли ОбщегоНазначенияКлиентСервер.ЭтоВебКлиент() Тогда
    Сообщить("За вами выехали!");              
КонецЕсли;        
К полному удовлетворению любителей макоси и линуксоводов существует более современный метод, добавленный в платформе 8.3.18:
// Адрес - адрес временного хранилища с результатом выполнения печатной формы
// ИмяФайла - предлагаемое имя файла если пользователь захочет его сохранить (необязательно)
ПолучитьФайлССервераАсинх(Адрес, ИмяФайла, Новый ПараметрыДиалогаПолученияФайлов());
    
При нажатии Открыть файл открывается в любом офисе, который назначен на полученный тип файла.

