diamond АШ Tlg

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

Используется БСП. Работает одинаково на Linux, Windows, MacOS, не требует установки офиса на сервере, многократно быстрее чем COMОбъект.

Шпаргалка по созданию внешней печатной формы, которая выводит результат в MS Word или в любой другой офисный пакет (LibreOffice, OnlyOffice, МойОфис и т.д.), который установлен на компьютере пользователя. При этом офисный пакет запускается даже на Linux (используем современный язык платформы 1С)!

Подготовка шаблона печатной формы

Верстаем в MS Word (LibreOffice, Google Doc) шаблон на красивом фирменном бланке с колонтитулами и логотипами, по следующему образцу. Просто набираем в лоб текст, без создания закладок и прочих хитростей:

Шаблон печатной формы в MS Word

В фигурных скобках находятся тэги, все они должны иметь формат {v8 ...}. Используются два вида тэгов:

  • {v8 Область.ИмяОбласти} и {/v8 Область.ИмяОбласти} определяют начало и конец именованной области макета. Обязательно должен быть открывающий тэг и закрывающий! На печать вы выводятся.
  • {v8 ИмяПараметра} - параметр, будет заменен значением при программном заполнении. Сохраняет форматирование (шрифт, цвет, фон)! Закрывающий тэг не нужен

Созданный шаблон загружаем в новый макет в двоичном формате:

Загрузка бинарного макета

По соглашению, принятому в библиотеке БСП имя макета должно иметь формат ПФ_DOC_НаименованиеМакета:

бинарноый макет, наименование

Подготовка внешней обработки

В модуль обработки добавляем стандарное описание внешней обработки для БСП. В качестве примера внешняя печатная форма добавлена назначением на справочник "Сотрудники":

Функция СведенияОВнешнейОбработке() Экспорт
	
    ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("3.1.9.232");
	
    ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиПечатнаяФорма();
    ПараметрыРегистрации.Версия = "1.0.0.1"; 
    ПараметрыРегистрации.Информация = ЭтотОбъект.Метаданные().Синоним;

    ПараметрыРегистрации.Назначение.Добавить("Справочник.Сотрудники");
	
    НоваяКоманда = ПараметрыРегистрации.Команды.Добавить();
    НоваяКоманда.Представление = ЭтотОбъект.Метаданные().Синоним;
    НоваяКоманда.Идентификатор = ЭтотОбъект.Метаданные().Имя;
    НоваяКоманда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыВызовКлиентскогоМетода();
    НоваяКоманда.ПоказыватьОповещение = Ложь;
	
    Возврат ПараметрыРегистрации;
	
КонецФункции

Ключевые моменты в описании таковы (другие значения не годятся для нашей задачи):

Вид обработки: ВидОбработкиПечатнаяФорма()
Тип команды: ТипКомандыВызовКлиентскогоМетода()

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

&НаКлиенте
Процедура Печать(Идентификатор, ОбъектыНазначения)  Экспорт

    // ОбъектыНазначения - это массив ссылок на объекты, на которые мы 
    // назначили внешнюю печатную форму в описании. Их пользователь может выбрать
    // как в форме элемента, так и в форме списка (множественный выбор)

    Для Каждого ОбъектНазначения Из ОбъектыНазначения Цикл
        
        Адрес = ВыполнитьПечатьDocxНаСервере(ОбъектНазначения);

        // в Адресе находится адрес ВременногоХранилища с готовым результатом
        // Как с ним поступить правильно покажу в конце статьи
    
    КонецЦикла;

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

Программное заполнение

&НаСервере
Функция ВыполнитьПечатьDocxНаСервере(ОбъектНазначения)
        
    ОписаниеОбластиТекст = Новый Структура("ИмяОбласти,ТипОбласти", "ТекстДоговора", "Общая");
    ОписаниеОбластиВКолонтитул = Новый Структура("ИмяОбласти,ТипОбласти", "ВерхнийКолонтитул", "ВерхнийКолонтитул");
    ОписаниеОбластиНКолонтитул = Новый Структура("ИмяОбласти,ТипОбласти", "НижнийКолонтитул", "НижнийКолонтитул");        
        
    ДвоичныеДанныеМакета = РеквизитФормыВЗначение("Объект").ПолучитьМакет("ПФ_DOC_Допсоглашение");
    Макет = УправлениеПечатью.ИнициализироватьМакетОфисногоДокумента(ДвоичныеДанныеМакета,,);
    АдресХранилищаПечатнойФормы = "";
        
    ПечатнаяФорма = УправлениеПечатью.ИнициализироватьПечатнуюФорму(,,Макет);
    Если ПечатнаяФорма = Неопределено Тогда
        УправлениеПечатью.ОчиститьСсылки(Макет);
        Возврат АдресХранилищаПечатнойФормы;
    КонецЕсли;
            
    // Пример вывода верхнего колонтитула
        
    Область = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбластиВКолонтитул);
    УправлениеПечатью.ПрисоединитьОбласть(ПечатнаяФорма, Область, Ложь);
            
    // Пример вывода нижнего колонтитула
        
    Область = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбластиНКолонтитул);
    УправлениеПечатью.ПрисоединитьОбласть(ПечатнаяФорма, Область, Ложь);
    
    // Тело печатной формы с параметрами:

    ДанныеЗаполнения = ПолучитьДанныеДляПечатнойФормы(ОбъектНазначения);
    // здесь мы должны получить структуру, ключ - имя параметра в шаблоне Word, значение - строка

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

Разрывы страниц и разделов

Для вывода разрыва страниц нужно в макете добавить область по следующему образцу:

Макет разрыва страниц

Обратите внимание, что область макета не может быть пустой (иначе БСП просто не выводит ничего), поэтому необходимо вставлять в область разрыва одну пустую строку.

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

К сожалению, с разрывом разделов библиотека на данный момент не умеет работать и такой фокус не работает! Как обходное решение, можно отдавать клиенту за один вызов два файла (например, в первом файле страницы в портретной ориентации, во втором - в альбомной).

Колонтитулы

Колонтитулы выводятся целиком, поэтому в макете выделять тэгами области в колонтитулах не нужно!

Если колонтитул на первой (титульной) странице должен отличаться, то следует отметить это соответствующей настройкой в макете и добавить в макет вторую страницу. На первой странице нарисовать титульный колонтитул, на второй - обычный повторяемый.

Макет таблицы

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

ОписаниеОбластиВТКолонтитул = Новый Структура("ИмяОбласти,ТипОбласти", "ВерхнийТитульныйКолонтитул", "ВерхнийТитульныйКолонтитул");
ОписаниеОбластиВКолонтитул = Новый Структура("ИмяОбласти,ТипОбласти", "ВерхнийКолонтитул", "ВерхнийКолонтитул");
             
// Сначала титульный колонтитул (1-ая страница печатной формы):
        
Область = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбластиВТКолонтитул);
УправлениеПечатью.ПрисоединитьОбласть(ПечатнаяФорма, Область, Ложь);

// Затем обычный (выводится со 2-ой страницы и далее до конца):

Область = УправлениеПечатью.ОбластьМакета(Макет, ОписаниеОбластиВКолонтитул);
УправлениеПечатью.ПрисоединитьОбласть(ПечатнаяФорма, Область, Ложь);

Таблицы

Таблицу в макете следует подготовить примерно также, как это делается в обычном табличном макете, только следует тщательно следить за выравниванием столбцов (рекомендую включить отображение сетки и увеличить масштаб):

Макет таблицы

При выводе шапки, строк и подвала таблицы все разрозненные части будут слиты в одну единую таблицу.

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

Макет таблицы

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

Что ещё может БСП

В примере выше для простоты использованы не все типы областей. В описании функции УправлениеПечатью.ОбластьМакета можно подсмотреть их полный список:

//    * ТипТипОбласти - Строка - тип области: 
//      "ВерхнийКолонтитул", "НижнийКолонтитул",
//      "ВерхнийТитульныйКолонтитул", "НижнийТитульныйКолонтитул",
//      "ВерхнийЧетныйКолонтитул", "НижнийЧетныйКолонтитул",
//      "Общая",
//      "СтрокаТаблицы", 
//      "Список".

Для заполнения таблиц в один заход в этом же модуле смотрите функцию ПрисоединитьИЗаполнитьКоллекцию.

Как вывести результат в любую офисную программу

Ниже пример самого древнего и протухшего кода - не делайте так. Перед выполнением необходимо записать двоичные данные в файл на компьютере клиента:

Попытка
    ОбъектВорд = Новый COMОбъект("Word.Application"); 
    ОбъектВорд.Documents.Add(ИмяФайлаDocx);
    ШаблонВорд = ОбъектВорд.ActiveDocument; 
    ОбъектВорд.Application.Visible = Истина;
    ОбъектВорд.Activate();     	
Исключение
    Сообщить("Разработчик передает пламенный привет линуксам, макосям,");              
    Сообщить("а также LibreOffice");              
КонецПопытки;

Другой устаревший подход - использовать процедуру глобального контекста ЗапуститьПриложение(ИмяФайлаDocx). Он работает только в Windows:

Если ОбщегоНазначенияКлиентСервер.ЭтоWindowsКлиент() Тогда 
    ЗапуститьПриложение(ИмяФайлаDocx);
ИначеЕсли ОбщегоНазначенияКлиентСервер.ЭтоLinuxКлиент() Тогда
    Сообщить("Красноглазые пингвинусы должны страдать!");              
ИначеЕсли ОбщегоНазначенияКлиентСервер.ЭтоOSXКлиент() Тогда
    Сообщить("Нашёл деньги на макбук? Закажи печатку Эксперту по технологическим вопросам!");              
ИначеЕсли ОбщегоНазначенияКлиентСервер.ЭтоВебКлиент() Тогда
    Сообщить("За вами выехали!");              
КонецЕсли;        

Мне больше всего нравится универсальный для любой оси метод открытия файла, добавленный в платформе 8.3.18:

// Адрес - адрес временного хранилища с результатом выполнения печатной формы
// ИмяФайла - предлагаемое имя файла если пользователь захочет его сохранить (необязательно)
ПолучитьФайлССервераАсинх(Адрес, ИмяФайла, Новый ПараметрыДиалогаПолученияФайлов());
Открыть или сохранить файл

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

Библиотека БСП использует платформенный метод НачатьЗапускПриложения(), доступный с платформы 8.3.6, но в этом случае никакого диалога нет, файл сразу открывается в офисе. Его можно явно использовать так:

// Адрес - адрес временного хранилища с результатом выполнения печатной формы
// ИмяФайла - предлагаемое имя файла если пользователь захочет его сохранить (необязательно)
ФайловаяСистемаКлиент.ОткрытьФайл(Адрес,, ИмяФайла);