Поместится ли текст в ячейке? (с примерами использования: авторазмер шрифта, перенос строк)


Периодически возникает вопрос: поместится ли текст в ячейке табличного документа? Хоть возникает он редко, но всё-таки возникает. В этой статье рассказывается как можно получить ответ на этот вопрос.

 

Вступление:

На просторах интернета я встречал различные решения, но все они носили "вероятностный" характер и строились на определении среднего размера символа (или максимального размера), вычислением длины строки, количеством строк, используя всяческие эмпирические коэффициенты, и всё равно не могли гарантировать, что текст поместится в ячейку.

Единственное решение, близкое к задаче, которое я нашел: Автоматический подбор размера шрифта в объекте РисунокТабличногоДокумента. Но оно для размещения текста в РисунокТабличногоДокумента, а не в область как в данной статье. Оно требует предварительной подготовки, но ничего проще именно для РисункаТабличногоДокумента на данный момент мне неизвестно (попытку упрощения см ниже).

Дальнейший поиск в интернете подтвердил существование как самой проблемы, так и наличие сложностей с её решением. Стоит признать, обсуждения сводились, как это часто бывает, к тому, что автор вопроса просит о никому не нужном функционале. И вообще, автор такого вопроса в ходе осуждения приобретал статус борца с ветрянными мельницами.

Сам я эту проблему зачастую обходил теми или иными путями. И это всех удовлетворяло: и меня, и заказчиков. Видимо, из-за того, что встречается она редко:

1. В ценниках. Наименование товара обычно центрируют по высоте, и при очень длинном наименовании его начало и конец имеют риск не поместиться в отведенное ему место (а в ценниках обычно отключают автовысоту). Из этого положения два выхода:

а) Выбирать тот или иной шаблон ценника. Не реализовано в примерах.

б) Подгонять размер текста под ценник. Реализовано в примерах:

Авторазмер текста

 2. И когда наименование нужно написать на несколько строк:

В типовых это реализовано так:

Типовое решение вывода длинного текста

мне же больше по душе выводить его так (с автоматическим переносом слов):

человеческое написание

Решение:

Намучавшись за два вечера, пришел, надеюсь, к простому решению:

Идея такая:

1. Запоминаем исходный размер ячейки в мм.

2. Назначаем ячейке свойство АвтоВыстота = Истина

3. Вставляем текст и опять смотрим высоту ячейки.

4. Если высота ячейки изменилась в большую сторону - значит, текст не помещается.

Высоту ячейки в мм будем измерять с помощью вставки в ячейку Рисунка и измерением его размеров.

Подробно:

Функция получения высоты ячейки:

Функция ВысотаОбластиВмм(пОбласть, пТабДок)
    
    Надпись = пТабДок.Рисунки.Добавить(ТипРисункаТабличногоДокумента.Текст);    
    Надпись.Расположить(пОбласть);    
    Высота = Надпись.Высота;    
    пТабДок.Рисунки.Удалить(Надпись);
    
    Возврат(Высота);
    
КонецФункции

Функция для определения вместится ли текст в ячейку:

Функция ТекстУмещаетсяВЯчейке(пОбласть, пТабДок, пТекст)     
        
    ВысотаДо = ВысотаОбластиВмм(пОбласть, пТабДок);    
    
    // Скопируем область в новый табличный документ.
    //  И там уже будем играть с её свойствами.
    ВремТабДок = Новый ТабличныйДокумент;
    ВремТабДок.Вывести(пТабДок);
    ВремОбласть = ВремТабДок.Область(пОбласть.Имя);    
    
    ВремОбласть.АвтоВысотаСтроки = Истина;
    ВремОбласть.ВысотаСтроки     = 0;    
    
    ВремОбласть.Текст = пТекст;        
    ВысотаПосле = ВысотаОбластиВмм(ВремОбласть, ВремТабДок);
                
    Если ВысотаДо >= ВысотаПосле Тогда
        Возврат(Истина);
    Иначе
        Возврат(Ложь);
    КонецЕсли;

КонецФункции

Для возможности использования этого метода необходимо соблюдать следующие условия:

1. Необходимо как-нибудь назвать область.

2. Заполнение области = Текст, а не Параметр или Шаблон. В принципе, можно использовать и то и другое, но код становиться мудрёней. Во всяком случае, я ничего простого не придумал. И так как жизнь это усложняет не сильно не стал заморачиваться из-за этого.

3. Размещение текста = Перенос. Вот это нужно обязательно! Размещение текста програмно поставить можно, но эффект от изменения, насколько я понял, появляется только после отображения табличного документа с этой ячейкой.

4. Область из одной строки. Т.е. объединять ячейки по вертикали нельзя (в тех которых должен проверяться текст). Исправлено в процессе публикации: создается копия табличного документа с областью и уже меняются её свойства, а не самого табличного документа.


 

В файлах вы найдете:

1. Пример с подбором размера шрифта с использованием функций из статьи.

2. Оптимизированный по быстродействию пример подбора размера шрифта с небольшой модификацией кода (но подбор размера шрифта - не оптимальный. Ниже есть некая попытка его вычилить а не уменьшать каждый раз на 1).

3. Вывод длинных строк в несколько ячеек.

4. Подбор размера шрифта в элементе "Рисунок табличного документа".


Рисунок табличного документа (Надпись):

Скажу сразу, на больших текстах, работает ужасно долго.

Так вот, с рисунком табличного документа (далее "надписью") пришлось повозиться. Всё дело в том, что при установленом свойстве "Авторазмер" сами размеры подгоняются под текст только после их отображения. Здесь вариантов два:

1. либо ждать какое время и затем смотреть размеры, например так: ПодключитьОбработчикОжидания("ИзмеритьРазмеры", 0.1, Истина)

2. либо "заставить" эту заразу отображаться принудительно.

Первый - рабочий. Однако код с ним получается сложный. И на каждую иттерацию - минимум по 0.1 секунды. В итоге это очень долго. Но работает (вроверял на поле таблчного документа).

А вот второй. С помощью применения маленькой хитрости - всё шикарно работает. А хитрость в том, что есть у ТабличногоДокумента метод Показать(). Хорош он тем, что как раз после его исполнения, размеры надписи уже становятся реальными, подогнанными под размер текста. Но плох он тем, что окрывает окно, которое программно или не закроешь, или я просто не знаю как.

Думал, я думал. А точнее, экперементировал, я экспериментировал. И вот до чего дошел:

Создаем на форме элемент "ПолеТабличногоДокумента" выводим туда нашу надпись. И вуаля - вызов метода "ЭлементыФормы.ПолеТабличногоДокумента.Показать()" не открывает никаких дополнительных окон, да что там, пользователь вообще этого не заметит. Зато мы это заметим. Да и мало того, что заметим, так еще и поэксплуатируем в своих корыстных целях! Ведь после вызова этого метода все высоты и ширины устанавливаются в соответсвие с установленной настройкой "АвтоРазмер = Истина". Красота. Но самое замечательное, что полеТабличногоДокумента можно даже не отображать! Разве, что форму с этим самым полем надо обязательно открывать, что накладывает некоторые ограничения на использование данного метода. Но эту форму можно с прогрессбаром сделать, и ни у кого не будет вопросов насчет открытия каких-то лишних форм.

Код приводить в человеческий вид мне лень. Если что, то есть файл в примерах.

Хочу отметить пару интересных моментов:

1. Вычисление высоты надписи при её фиксированной ширине.

2. Попытка оптимизировать алгоритм поиска шрифта.


// ;сообщить(...) - отладка

Функция ВысотаНадписиПриФиксированнойШирине(пНадпись, пЭлТабДок, пТекст)
        
    СтруктураНачЗначений = Новый Структура("Ширина, Высота, Авторазмер, РазмещениеТекста");
    ЗаполнитьЗначенияСвойств(СтруктураНачЗначений, пНадпись);     
    
    //////////////////////////////////////////////////////////////////////////////
    
    НачШирина = пНадпись.Ширина;
    
    пНадпись.Авторазмер = Истина;
    пНадпись.РазмещениеТекста = ТипРазмещенияТекстаТабличногоДокумента.Обрезать;
        
    ТекТекст = "";
    ВсегоСимволов = СтрДлина(Текст);
    
    ИндексДлиныСтроки = 0;
    
    Для Индекс = 1 По ВсегоСимволов Цикл
        
        ИндексДлиныСтроки = ИндексДлиныСтроки + 1;        
        
        ТекСимвол = Сред(пТекст, Индекс, 1);        
        пНадпись.Текст = ТекТекст + ТекСимвол;
        
        пЭлТабДок.Показать();
        
        Если пНадпись.Ширина > НачШирина Тогда
            
            Если ИндексДлиныСтроки = 1 Тогда // не вместился единственный символ.
                Возврат(Неопределено);
            КонецЕсли;
            
            ТекТекст = ТекТекст + Символы.ПС;            
            Индекс = Индекс - 1; // да, да, я и сам не люблю такие манипуляции со счетчиком цикла, но исправлять лень.
            ИндексДлиныСтроки = 0;
            
        Иначе
            ТекТекст = ТекТекст + ТекСимвол;
        КонецЕсли;
        
    КонецЦикла;
    
    пНадпись.Текст = ТекТекст;
    пЭлТабДок.Показать();
    
    ИтоговаяВысота = пНадпись.Высота;
    
    //////////////////////////////////////////////////////////////////////////////
    
    ЗаполнитьЗначенияСвойств(пНадпись, СтруктураНачЗначений);
    
    Возврат(ИтоговаяВысота);
    
КонецФункции

Функция ПолучитьОптимальныйРазмерШрифта(пМакет, пИмяНадписи, пТекст, пМаксимальныйРазмерШрифта ,пТабДок)
    
    пТабДок.Вывести(пМакет);
    
    Надпись = пТабДок.Рисунки[пИмяНадписи];
    
    //////////////////////////////////////////////////////////////////////////
    // { Подгон размера шрифта
    
    НачВысота = Надпись.Высота;    
    
    РазмерШрифта = пМаксимальныйРазмерШрифта;
    
    Надпись.Шрифт = Новый Шрифт(Надпись.Шрифт, ,РазмерШрифта);
    ТекВысота = ВысотаНадписиПриФиксированнойШирине(Надпись, пТабДок, пТекст);
    
    Если ТекВысота = Неопределено Тогда
        Возврат(Неопределено);
    КонецЕсли;
    
    Пока НачВысота < ТекВысота
        И РазмерШрифта > 0 Цикл
        
        ;Сообщить("РазмерШрифта = "+РазмерШрифта);
                
        НовРазмерШрифта = Цел(РазмерШрифта / (Sqrt(ТекВысота / НачВысота)));        
        
        ;Сообщить("НовРазмерШрифта = "+НовРазмерШрифта);
        
        Если НовРазмерШрифта = РазмерШрифта Тогда НовРазмерШрифта = НовРазмерШрифта - 1 КонецЕсли;
        
        РазмерШрифта = НовРазмерШрифта;
        Надпись.Шрифт = Новый Шрифт(Надпись.Шрифт, ,РазмерШрифта);
        ТекВысота = ВысотаНадписиПриФиксированнойШирине(Надпись, пТабДок, пТекст);
        
    КонецЦикла;
    
    // Точная подгонка (в сторону увеличения)
    
    ИтоговыйРазмерШрифта = РазмерШрифта;
    
    Пока ТекВысота <> Неопределено
        И НачВысота > ТекВысота
        И РазмерШрифта < пМаксимальныйРазмерШрифта Цикл
        
        ИтоговыйРазмерШрифта = РазмерШрифта;
        
        ;Сообщить("РазмерШрифта = "+РазмерШрифта);
                
        НовРазмерШрифта = РазмерШрифта + 1;        
        
        ;Сообщить("НовРазмерШрифта = "+НовРазмерШрифта);
        
        РазмерШрифта = НовРазмерШрифта;
        Надпись.Шрифт = Новый Шрифт(Надпись.Шрифт, ,РазмерШрифта);
        ТекВысота = ВысотаНадписиПриФиксированнойШирине(Надпись, пТабДок, Текст);

    КонецЦикла;    
    
    // }
    ////////////////////////////////////////////////////////////////////

    //Если ТекВысота = Неопределено Тогда
    //    ИтоговыйРазмерШрифта = Неопределено;    
    //КонецЕсли;
    
    Возврат(ИтоговыйРазмерШрифта);    
    
КонецФункции

Процедура КнопкаВыполнитьНажатие(Кнопка)
    
    Макет = ПолучитьМакет("Макет");
    
    ТабДок = ЭлементыФормы.ПолеТабличногоДокумента;    
    ТабДок.Очистить(); // Мы же хотим посмотреть в удобном месте?    
    
    РазмерШрифта = ПолучитьОптимальныйРазмерШрифта(Макет, "Надпись", Текст, 80 ,ТабДок);    
    
    Если РазмерШрифта <> Неопределено Тогда
        
        Надпись = Макет.Рисунки.Надпись;
        Надпись.Шрифт = Новый Шрифт(Надпись.Шрифт, ,РазмерШрифта);        
        Надпись.Текст = Текст;        
        Макет.Показать();
        
    Иначе
        
        Предупреждение("Ошибка подбора размера (даже единственный символ не поместился)!");
        
    КонецЕсли;     
    
КонецПроцедуры
 

Удачных экспериментов!

Файлы обработки:

-