diamond АШ Tlg

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

Используется БСП. Работает одинаково на Linux, Windows, MacOS, не требует установки офиса на сервере, быстрее раз в 100 чем 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, значение - строка

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

К полному удовлетворению любителей макоси и линуксоводов существует более современный метод, добавленный в платформе 8.3.18:

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

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