Как переносить длинный текст по страницам в табличном документе 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При формировании печатных форм договоров, писем или актов разработчики часто сталкиваются с неприятной особенностью платформы 1С: если в макет выводится ячейка с большим объемом динамического текста со свойством РазмещениеТекста.Переносить, она не разрывается между страницами мягко, как в текстовых редакторах. Если ячейка целиком не умещается на оставшейся части листа, платформа переносит ее на следующую страницу полностью, оставляя снизу огромное пустое пространство. Если же текст превышает высоту целой страницы, он либо обрезается, либо сжимается при включенном автопараметре масштабирования.

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

Причина проблемы: особенности верстки табличного документа

Выясним причину такого поведения платформы. Объект ТабличныйДокумент концептуально спроектирован как сетка электронных таблиц, а не как потоковый текстовый процессор. В модели печати 1С строка (или вертикально объединенная ячейка) является неделимым атомарным блоком. Движок компоновки страниц не умеет разрезать ячейку поперек горизонтальной линии разрыва страницы. Следовательно, чтобы длинный текст естественно распределялся по листам, разработчику необходимо самостоятельно разбить его на более мелкие фрагменты и выводить их последовательно.

Решение 1: Пословное разбиение текста с проверкой вместимости

Рассмотрим основной и наиболее надежный способ разбиения текста в рамках табличного документа. Алгоритм строится следующим образом:

  1. Разбиваем исходную строку на массив слов с сохранением разделителей (пробелы и переводы строк Символы.ПС).
  2. Создаем пустую накопительную строку и последовательно добавляем в нее слова из массива.
  3. Помещаем накопленную строку в область макета и проверяем, помещается ли область на текущую страницу с помощью метода ПроверитьВывод().
  4. Как только метод возвращает Ложь, откатываем последнее слово, выводим накопленную область в результирующий табличный документ, принудительно вызываем ВывестиГоризонтальныйРазделительСтраниц() и начинаем заполнение новой страницы с отложенного слова.

Разберем по шагам реализацию этого механизма в коде. Сначала подготовим вспомогательную функцию для разбора строки с сохранением разделителей:


Функция РазложитьСтрокуПоРазделителям(Знач ИсходнаяСтрока, МассивРазделителей)
    МассивРезультат = Новый Массив;
    ТекСтрока = ИсходнаяСтрока;
    
    Пока Истина Цикл
        МинПозиция = Неопределено;
        ТекРазделитель = Неопределено;
        
        Для Каждого Разделитель Из МассивРазделителей Цикл
            Позиция = Найти(ТекСтрока, Разделитель);
            Если Позиция <> 0 И (МинПозиция = Неопределено Или Позиция < МинПозиция) Тогда
                МинПозиция = Позиция;
                ТекРазделитель = Разделитель;
            КонецЕсли;
        КонецЦикла;
        
        Если МинПозиция = Неопределено Тогда
            Если Не ПустаяСтрока(ТекСтрока) Тогда
                МассивРезультат.Добавить(ТекСтрока);
            КонецЕсли;
            Прервать;
        КонецЕсли;
        
        Слово = Лев(ТекСтрока, МинПозиция - 1) + ТекРазделитель;
        Если СтрДлина(Слово) > 0 Тогда
            МассивРезультат.Добавить(Слово);
        КонецЕсли;
        
        ТекСтрока = Сред(ТекСтрока, МинПозиция + СтрДлина(ТекРазделитель));
    КонецЦикла;
    
    Возврат МассивРезультат;
КонецФункции

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


Функция СформироватьПечатнуюФорму(СсылкаНаОбъект) Экспорт
    ТабДокумент = Новый ТабличныйДокумент;
    ТабДокумент.РазмерСтраницы = "A4";
    ТабДокумент.АвтоМасштаб = Ложь;
    ТабДокумент.МасштабПечати = 100;
    
    Макет = ПолучитьМакет("Макет");
    ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
    ОбластьТекст = Макет.ПолучитьОбласть("Текст");
    
    // Параметры области должны совпадать с параметрами документа
    ОбластьТекст.РазмерСтраницы = ТабДокумент.РазмерСтраницы;
    ОбластьТекст.АвтоМасштаб = ТабДокумент.АвтоМасштаб;
    ОбластьТекст.МасштабПечати = ТабДокумент.МасштабПечати;
    
    ТабДокумент.Вывести(ОбластьШапка);
    
    Разделители = Новый Массив;
    Разделители.Добавить(Символы.ПС);
    Разделители.Добавить(" ");
    
    МассивСлов = РазложитьСтрокуПоРазделителям(СокрЛП(СсылкаНаОбъект.ТекстДокумента), Разделители);
    ТекстНакопленный = "";
    
    Для Индекс = 0 По МассивСлов.ВГраница() Цикл
        Слово = МассивСлов[Индекс];
        ТекстНакопленный = ТекстНакопленный + Слово;
        ОбластьТекст.Параметры.Текст = ТекстНакопленный;
        
        Попытка
            Если Не ТабДокумент.ПроверитьВывод(ОбластьТекст) Тогда
                // Блок не помещается на страницу: откатываем последнее добавленное слово
                ДлинаОтката = СтрДлина(ТекстНакопленный) - СтрДлина(Слово);
                ТекстНакопленный = Лев(ТекстНакопленный, ДлинаОтката);
                
                ОбластьТекст.Параметры.Текст = ТекстНакопленный;
                ТабДокумент.Вывести(ОбластьТекст);
                ТабДокумент.ВывестиГоризонтальныйРазделительСтраниц();
                
                // Переносим невлезшее слово в начало следующей страницы
                ТекстНакопленный = Слово;
            ИначеЕсли Индекс = МассивСлов.ВГраница() Тогда
                // Достигнут конец текста - выводим остаток
                ТабДокумент.Вывести(ОбластьТекст);
            КонецЕсли;
        Исключение
            ВызватьИсключение "Ошибка проверки вывода. Убедитесь, что в системе установлен принтер: " + ОписаниеОшибки();
        КонецПопытки;
    КонецЦикла;
    
    Возврат ТабДокумент;
КонецФункции

Подводные камни и ограничения метода ПроверитьВывод

Проанализируем технические нюансы, с которыми можно столкнуться при использовании описанного алгоритма:

  1. Зависимость от установленного принтера: Метод ПроверитьВывод() обращается к графической подсистеме ОС и драйверу текущего принтера. Если код выполняется на сервере под управлением Linux без графического окружения, где не настроена служба печати CUPS или не установлен виртуальный принтер (например, cups-pdf) по умолчанию, вызов метода приведет к аварийному исключению.
  2. Разница метрик шрифтов между клиентом и сервером: Движки рендеринга шрифтов в Linux (FreeType) и Windows (GDI/DirectWrite) рассчитывают ширину символов и межстрочные интервалы с небольшими расхождениями. Текст, который на сервере идеально уложился в границы листа, при открытии пользователем на клиенте Windows может неожиданно перепрыгнуть на следующую страницу.
  3. Падение производительности: Пословная проверка создает линейную сложность O(N), где каждый вызов ПроверитьВывод() инициирует полный расчет геометрии макета. Для больших документов (от 3 страниц) это приводит к заметным задержкам формирования.

Оптимизация производительности: дихотомия и предварительный расчет

Чтобы ускорить формирование многостраничного текста, применим оптимизацию. Вместо добавления по одному слову используем алгоритм бинарного поиска (дихотомии). Мы берем весь оставшийся объем слов и делим его пополам, проверяя вместимость половинного блока методом ПроверитьВывод(). Это снижает количество тяжелых геометрических расчетов с сотен до единиц на страницу, сохраняя точное совпадение с границей листа.

Кроме того, если текст разбит на логические абзацы стандартными переводами строк, можно разбить его с помощью функции СтрПолучитьСтроку(). Для контроля переноса абзацев целиком полезно использовать свойство ячейки ВместеСоСледующим — это предотвратит разрыв заголовков и следующих за ними строк.

Альтернативные решения для больших текстов в 1С

Если специфика задачи позволяет уйти от строго табличного макета, рассмотрим другие стандартные инструменты платформы:

  1. Объект «ФорматированныйДокумент»: Специально предназначен для работы с договорами, положениями и большими текстами. В отличие от табличного документа, он изначально поддерживает потоковую верстку, абзацные отступы и при выводе на печать автоматически выполняет естественные построчные переносы страниц.
  2. Офисные шаблоны (DOCX / ODT) средствами БСП: Библиотека стандартных подсистем предоставляет механизм печати по офисным шаблонам без использования COM-соединения. Платформа напрямую модифицирует XML-структуру документа на сервере, сохраняя форматирование, выравнивание по ширине и типографику текстового процессора.
  3. HTML-разметка: Формирование документа через HTML с подключением стилей печати (CSS-правила @media print, page-break-inside: avoid) и последующим выводом через встроенное поле HTML или сохранением напрямую в PDF средствами платформы.
← На главную