При переходе от классического использования COM-объекта Word.Application на сервере к современным серверным механизмам Библиотеки стандартных подсистем (БСП) разработчики часто сталкиваются с проблемой верстки документов. БСП формирует файлы в формате .docx напрямую через сборку XML-структур внутри ZIP-архива. Это происходит на стороне сервера, зачастую под управлением операционной системы Linux, где физически нет установленного MS Word.
В связи с этим возникает серьезная методическая сложность: в отличие от стандартного табличного документа 1С, для которого предусмотрен метод ПроверитьВывод(), у документов MS Word на сервере нет встроенного инструмента для автоматического расчета высоты страниц. В формате OpenXML понятия «физической страницы» не существует — разбиение текста на листы, перенос строк и расчет высоты ячеек происходят динамически на стороне клиента в тот момент, когда пользователь открывает файл в MS Word, LibreOffice или другом офисном пакете. Разметка зависит от локального принтера, версии офиса, шрифтов операционной системы и масштабирования.
Попытки вручную рассчитать высоту XML-тегов или найти идентификаторы с размерами ячеек не принесут стабильного результата. В этой статье мы подробно рассмотрим, как решить проблему «убегающих» таблиц и пустых пространств на листах с помощью правильной настройки шаблонов MS Word и программных методов 1С.
Вместо того чтобы пытаться угадать в коде 1С, когда именно вставить принудительный разрыв страницы, правильнее использовать внутренние возможности верстки самого текстового процессора MS Word. Нам достаточно один раз настроить свойства таблиц и абзацев непосредственно в файле-макете (шаблоне), а БСП при заполнении областей перенесет эти свойства в результирующий XML-файл.
Рассмотрим по шагам три ключевые настройки в шаблоне документа:
Если таблица переносится на следующий лист, дублировать шапку вручную через код 1С не нужно. Откроем наш макет в MS Word, выделим строку с шапкой таблицы, нажмем правую кнопку мыши и перейдем в «Свойства таблицы». На вкладке «Строка» установим флаг «Повторять как заголовок на каждой странице» (в структуре OpenXML за это отвечает тег w:tblHeader). Теперь при открытии готового файла MS Word сам определит границы страниц и автоматически добавит шапку вверху каждого листа.
Часто бывает, что строка таблицы содержит много текста и разрывается посередине: верхняя часть строки остается на одной странице, а нижняя уходит на другую. Чтобы строка переносилась целиком, выделим всю таблицу в макете, зайдем в «Свойства таблицы» -> вкладка «Строка» -> снимем флаг «Разрешить перенос строк на следующую страницу» (в XML запишется тег w:cantSplit). Если строка не помещается на листе, Word автоматически перенесет ее целиком на новый лист.
Чтобы заголовок таблицы или вводный абзац не оставался «сиротой» внизу страницы, когда сама таблица начинается на следующем листе, применим свойство абзаца. Выделим заголовок перед таблицей, зайдем в свойства абзаца на вкладку «Положение на странице» и установим флаг «Не отрывать от следующего» (в XML добавится тег w:keepNext).
Если после таблицы у нас идет область «Подвал» или «Приложение», которая обязательно должна начинаться с нового листа, не нужно вставлять пустые абзацы или разрывы страниц в коде. В шаблоне Word для первого абзаца этой области включим свойство «Начать с новой страницы» (в XML запишется тег w:pageBreakBefore). При выводе этой области БСП перенесет свойство абзаца, и Word сам сделает разрыв страницы в нужном месте.
Если бизнес-логика документа требует строгого деления данных на порции (например, группировка документов по пачкам с жестким ограничением количества строк на листе), мы можем применить программно-эмпирический метод расчета.
Для реализации этого метода разберем алгоритм настройки макета и написания кода:
РазрывСтраницы. Внутри этой области вставим один символ разрыва страницы (в меню Word: Вставка -> Разрыв страницы) и один пустой абзац, так как полностью пустые области БСП может проигнорировать при сборке XML.Проанализируем пример кода, реализующий данный подход:
// Получаем макет и инициализируем печатную форму методами БСП
МакетДанных = УправлениеПечатью.ПолучитьМакет("ОбщийМакет.НашМакетWord");
ПечатнаяФорма = УправлениеПечатью.ИнициализироватьПечатнуюФормуMSWord(МакетДанных);
ОбластьСтрока = УправлениеПечатью.ОбластьМакета(МакетДанных, "СтрокаТаблицы");
ОбластьРазрыв = УправлениеПечатью.ОбластьМакета(МакетДанных, "РазрывСтраницы");
МаксимумСтрокНаЛисте = 22;
ТекущийСчетчикСтрок = 0;
Для Каждого СтрокаТовара Из СсылкаНаДокумент.Товары Цикл
ДанныеЗаполнения = Новый Структура;
ДанныеЗаполнения.Вставить("НомерСтроки", СтрокаТовара.НомерСтроки);
ДанныеЗаполнения.Вставить("Номенклатура", СтрокаТовара.Номенклатура.Наименование);
ДанныеЗаполнения.Вставить("Количество", СтрокаТовара.Количество);
ДанныеЗаполнения.Вставить("Цена", СтрокаТовара.Цена);
ДанныеЗаполнения.Вставить("Сумма", СтрокаТовара.Сумма);
// Присоединяем заполненную строку к печатной форме
УправлениеПечатью.ПрисоединитьОбласть(ПечатнаяФорма, ОбластьСтрока, ДанныеЗаполнения);
ТекущийСчетчикСтрок = ТекущийСчетчикСтрок + 1;
// Если лимит строк исчерпан, вставляем разрыв страницы
Если ТекущийСчетчикСтрок >= МаксимумСтрокНаЛисте Тогда
УправлениеПечатью.ПрисоединитьОбласть(ПечатнаяФорма, ОбластьРазрыв);
ТекущийСчетчикСтрок = 0; // Сбрасываем счетчик для следующей страницы
КонецЕсли;
КонецЦикла;
Важное примечание: Этот метод не учитывает, если в одной из строк таблицы текст перенесется на 3-4 строки вниз внутри ячейки. Он эффективен только при фиксированной высоте строк таблицы.
Если перед нами стоит задача добиться абсолютной точности вывода «пиксель в пиксель», с расчетом промежуточных итогов по страницам и контролем переноса строк, то лучшим решением будет использование стандартного объекта платформы ТабличныйДокумент с последующим его экспортом в формат .docx.
Выясним причину, почему этот способ является наиболее надежным для сложных отчетов. Платформа 1С имеет встроенные математические алгоритмы расчета высоты строк табличного документа на основе используемых шрифтов и настроек страницы. Метод ПроверитьВывод() позволяет гарантированно узнать, поместится ли следующая порция данных на текущем листе.
Разберем по шагам реализацию этого метода в коде:
// Создаем стандартный табличный документ 1С
ТабДок = Новый ТабличныйДокумент;
ТабДок.АвтоМасштаб = Истина;
ТабДок.ОриентацияСтраницы = ОриентацияСтраницы.Портрет;
// Получаем области из макета табличного документа
МакетMXL = ПолучитьМакет("МакетПечатиMXL");
ОбластьШапкаТаблицы = МакетMXL.ПолучитьОбласть("ШапкаТаблицы");
ОбластьСтрокаТаблицы = МакетMXL.ПолучитьОбласть("СтрокаТаблицы");
// Выводим шапку таблицы первый раз
ТабДок.Вывести(ОбластьШапкаТаблицы);
Для Каждого СтрокаДанных Из СсылкаНаДокумент.Товары Цикл
// Заполняем параметры области строки
ЗаполнитьЗначенияСвойств(ОбластьСтрокаТаблицы.Параметры, СтрокаДанных);
// Проверяем, поместится ли строка на текущей странице вместе с шапкой таблицы
МассивВыводимыхОбластей = Новый Массив;
МассивВыводимыхОбластей.Добавить(ОбластьСтрокаТаблицы);
Если Не ТабДок.ПроверитьВывод(МассивВыводимыхОбластей) Тогда
// Если не помещается, принудительно переносим страницу
ТабДок.ВывестиГоризонтальныйРазделительСтраниц();
// Повторно выводим шапку на новой странице
ТабДок.Вывести(ОбластьШапкаТаблицы);
КонецЕсли;
// Выводим строку в табличный документ
ТабДок.Вывести(ОбластьСтрокаТаблицы);
КонецЦикла;
// Сохраняем готовый документ в формате Word (.docx) без участия MS Word на сервере
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("docx");
ТабДок.Записать(ИмяВременногоФайла, ТипФайлаТабличногоДокумента.Docx);
При использовании этого метода платформа 1С самостоятельно преобразует всю сложную верстку табличного документа, включая горизонтальные разделители страниц, в правильные теги разметки OpenXML. На выходе мы получаем корректный файл .docx, который откроется в любом текстовом редакторе в строгом соответствии с нашими правилами разбиения на страницы.