При формировании печатных форм договоров, писем или актов разработчики часто сталкиваются с неприятной особенностью платформы 1С: если в макет выводится ячейка с большим объемом динамического текста со свойством РазмещениеТекста.Переносить, она не разрывается между страницами мягко, как в текстовых редакторах. Если ячейка целиком не умещается на оставшейся части листа, платформа переносит ее на следующую страницу полностью, оставляя снизу огромное пустое пространство. Если же текст превышает высоту целой страницы, он либо обрезается, либо сжимается при включенном автопараметре масштабирования.
В этой статье мы подробно разберем, почему возникает такое поведение, как программно разделить текст с помощью метода ПроверитьВывод(), а также какие архитектурные нюансы и альтернативные решения существуют в платформе 1С:Предприятие.
Выясним причину такого поведения платформы. Объект ТабличныйДокумент концептуально спроектирован как сетка электронных таблиц, а не как потоковый текстовый процессор. В модели печати 1С строка (или вертикально объединенная ячейка) является неделимым атомарным блоком. Движок компоновки страниц не умеет разрезать ячейку поперек горизонтальной линии разрыва страницы. Следовательно, чтобы длинный текст естественно распределялся по листам, разработчику необходимо самостоятельно разбить его на более мелкие фрагменты и выводить их последовательно.
Рассмотрим основной и наиболее надежный способ разбиения текста в рамках табличного документа. Алгоритм строится следующим образом:
Символы.ПС).ПроверитьВывод().ВывестиГоризонтальныйРазделительСтраниц() и начинаем заполнение новой страницы с отложенного слова.Разберем по шагам реализацию этого механизма в коде. Сначала подготовим вспомогательную функцию для разбора строки с сохранением разделителей:
Функция РазложитьСтрокуПоРазделителям(Знач ИсходнаяСтрока, МассивРазделителей)
МассивРезультат = Новый Массив;
ТекСтрока = ИсходнаяСтрока;
Пока Истина Цикл
МинПозиция = Неопределено;
ТекРазделитель = Неопределено;
Для Каждого Разделитель Из МассивРазделителей Цикл
Позиция = Найти(ТекСтрока, Разделитель);
Если Позиция <> 0 И (МинПозиция = Неопределено Или Позиция < МинПозиция) Тогда
МинПозиция = Позиция;
ТекРазделитель = Разделитель;
КонецЕсли;
КонецЦикла;
Если МинПозиция = Неопределено Тогда
Если Не ПустаяСтрока(ТекСтрока) Тогда
МассивРезультат.Добавить(ТекСтрока);
КонецЕсли;
Прервать;
КонецЕсли;
Слово = Лев(ТекСтрока, МинПозиция - 1) + ТекРазделитель;
Если СтрДлина(Слово) > 0 Тогда
МассивРезультат.Добавить(Слово);
КонецЕсли;
ТекСтрока = Сред(ТекСтрока, МинПозиция + СтрДлина(ТекРазделитель));
КонецЦикла;
Возврат МассивРезультат;
КонецФункции
Теперь посмотрим на пример процедуры вывода текста в результирующий ТабличныйДокумент:
Функция СформироватьПечатнуюФорму(СсылкаНаОбъект) Экспорт
ТабДокумент = Новый ТабличныйДокумент;
ТабДокумент.РазмерСтраницы = "A4";
ТабДокумент.АвтоМасштаб = Ложь;
ТабДокумент.МасштабПечати = 100;
Макет = ПолучитьМакет("Макет");
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьТекст = Макет.ПолучитьОбласть("Текст");
// Параметры области должны совпадать с параметрами документа
ОбластьТекст.РазмерСтраницы = ТабДокумент.РазмерСтраницы;
ОбластьТекст.АвтоМасштаб = ТабДокумент.АвтоМасштаб;
ОбластьТекст.МасштабПечати = ТабДокумент.МасштабПечати;
ТабДокумент.Вывести(ОбластьШапка);
Разделители = Новый Массив;
Разделители.Добавить(Символы.ПС);
Разделители.Добавить(" ");
МассивСлов = РазложитьСтрокуПоРазделителям(СокрЛП(СсылкаНаОбъект.ТекстДокумента), Разделители);
ТекстНакопленный = "";
Для Индекс = 0 По МассивСлов.ВГраница() Цикл
Слово = МассивСлов[Индекс];
ТекстНакопленный = ТекстНакопленный + Слово;
ОбластьТекст.Параметры.Текст = ТекстНакопленный;
Попытка
Если Не ТабДокумент.ПроверитьВывод(ОбластьТекст) Тогда
// Блок не помещается на страницу: откатываем последнее добавленное слово
ДлинаОтката = СтрДлина(ТекстНакопленный) - СтрДлина(Слово);
ТекстНакопленный = Лев(ТекстНакопленный, ДлинаОтката);
ОбластьТекст.Параметры.Текст = ТекстНакопленный;
ТабДокумент.Вывести(ОбластьТекст);
ТабДокумент.ВывестиГоризонтальныйРазделительСтраниц();
// Переносим невлезшее слово в начало следующей страницы
ТекстНакопленный = Слово;
ИначеЕсли Индекс = МассивСлов.ВГраница() Тогда
// Достигнут конец текста - выводим остаток
ТабДокумент.Вывести(ОбластьТекст);
КонецЕсли;
Исключение
ВызватьИсключение "Ошибка проверки вывода. Убедитесь, что в системе установлен принтер: " + ОписаниеОшибки();
КонецПопытки;
КонецЦикла;
Возврат ТабДокумент;
КонецФункции
Проанализируем технические нюансы, с которыми можно столкнуться при использовании описанного алгоритма:
ПроверитьВывод() обращается к графической подсистеме ОС и драйверу текущего принтера. Если код выполняется на сервере под управлением Linux без графического окружения, где не настроена служба печати CUPS или не установлен виртуальный принтер (например, cups-pdf) по умолчанию, вызов метода приведет к аварийному исключению.O(N), где каждый вызов ПроверитьВывод() инициирует полный расчет геометрии макета. Для больших документов (от 3 страниц) это приводит к заметным задержкам формирования.Чтобы ускорить формирование многостраничного текста, применим оптимизацию. Вместо добавления по одному слову используем алгоритм бинарного поиска (дихотомии). Мы берем весь оставшийся объем слов и делим его пополам, проверяя вместимость половинного блока методом ПроверитьВывод(). Это снижает количество тяжелых геометрических расчетов с сотен до единиц на страницу, сохраняя точное совпадение с границей листа.
Кроме того, если текст разбит на логические абзацы стандартными переводами строк, можно разбить его с помощью функции СтрПолучитьСтроку(). Для контроля переноса абзацев целиком полезно использовать свойство ячейки ВместеСоСледующим — это предотвратит разрыв заголовков и следующих за ними строк.
Если специфика задачи позволяет уйти от строго табличного макета, рассмотрим другие стандартные инструменты платформы:
@media print, page-break-inside: avoid) и последующим выводом через встроенное поле HTML или сохранением напрямую в PDF средствами платформы.