101 СПОСОБ  ЗАРАБОТАТЬ   НА ПЕЧАТИ

Бой табличной скуке!

  • Михаил Борисов
  • 8 февраля 2008 г.
  • 5113
Автоматизируем форматирование таблиц.

Хотя InDesign CS3 стал поддерживать стили для таблиц, их форматирование с помощью скриптов не утратило актуальности: встроенные функции решают часть проблем, но все не охватывают. Первая причина — переход на версию CS3 произойдёт не сразу, многие ещё долго будут работать в InDesign CS2; вторая — скриптинг предоставляет ни с чем не сравнимую гибкость при решении задач, что позволяет полностью автоматизировать форматирование. Предлагаемый пример (я активно использую этот скрипт) — отправная точка для создания собственных скриптов по форматированию таблиц; кроме того, он решает вопросы оптимизации, т. е. универсален.

При импорте документа Word в InDesign параметры ячеек теряются (рис. 1, вверху). Наша задача — из полуфабриката создать полноценную таблицу (рис. 1, внизу).

Pис. 1. Таблица до и после

Предположим, вёрстка у нас двухколоночная, поэтому предусмотрим, чтобы таблицы были двух видов: узкие (в одну колонку) и широкие (занимающие всю полосу набора). Для заливки строк будем использовать два чередующихся цвета, цвет окантовки у всех ячеек выберем белым. Для текста в шапке таблицы возьмём стиль абзаца Tbl Hdr, для остального текста — Table contents.

Обеспечим выключку во всех ячейках по центру, а ширину крайней левой колонки зададим такой, чтобы по возможности не было переноса текста. В моей практике в последней колонке текста всегда больше, чем в остальных, поэтому ради компактности таблицы увеличим её ширину за счёт других. В случае же слишком длинного текста ограничим её ширину, например, 5 см. Присвоим стили абзацам, окружающим таблицу, и получим полностью сформатированную по нашим правилам, что невозможно даже в InDesign СS3, не говоря уже о более ранних версиях.

Перечисляем необходимые параметры:

DEFAULT_SIDE_INSET = DEFAULT_STROKE_WIDTH =
"0.5 mm" ;
VERTICAL_INSET = "1 mm";
narrow = "120 mm";
wide = "174 mm";

Используем нестандартный метод задания параметров — в явном виде, в миллиметрах, что зачастую нагляднее. Поскольку они превратятся из чисел в текстовые строки, соответствующе их оформим, заключив с обеих сторон в кавычки. Интересно, что InDesign понимает такую запись без дополнительных действий с нашей стороны — правильно считывает единицы измерения и устанавливает требуемые значения.

Как вариант подойдёт способ принудительного переключения в нужные единицы:

app.activeDocument.viewPreferences.horizontal
MeasurementUnits = MeasurementUnits.centimeters;
app.activeDocument.viewPreferences.vertical
MeasurementUnits = MeasurementUnits.centimeters;

Нам придётся использовать цвета для заливки ячеек таблицы. В отличие от названия стиля, который непосредственно в скрипте задать нельзя (как мы его видим в палитре Paragraph Styles, например, «Table»), с цветом ситуация значительно проще. InDesign понимает их названия такими, как мы видим в окне Swatches, и для цвета содержимого таблицы можно записать:

alterFill = "alterFillColor"; для шапки: headerFill = "headerFillColor";

Разумеется, эти цвета перед запуском скрипта должны присутствовать в публикации. Устанавливаем ссылки на объекты, с которыми будем работать:

myDocument = app.activeDocument;

Стандартная строка для работы с любым выделенным объектом:

mySelection = myDocument.selection[0];

Чтобы скрипт работал корректно, ничего в таблице выделять не будем, а просто обозначим установкой в неё курсора (это необходимо, если на странице несколько таблиц). Для обращения ко всей таблице получим:

myTable = mySelection.parent.parent;

Прямой родитель у точки вставки — ячейка, а у неё — таблица, а у той — текстовый фрейм, в котором она находится.

myTextFrame = myTable.parent;

Для использования стилей публикации создаём ссылки на них:

parStyles = myDocument.paragraphStyles;

Следующий шаг — проверка публикации на наличие требуемых стилей: это позволит избежать ошибки, если нужного нам не окажется. Перебираем по очереди все стили, найдя один из требуемых, устанавливаем соответствующий признак. InDesign хранит стили не по названиям, как цвета, а по индексам, поэтому присвоить стиль непосредственно по его названию не получится. Запоминать будем их индексы, по которым в дальнейшем будем обращаться.

Создадим временное хранилище для них.

parStylesStorage = new Array();//хранилище для индексов стилей
flagStorage = new Array();//здесь будем хранить признаки наличия требуемых стилей.

Запоминаем индексы используемых стилей:

for (i=0; i< parStyles.length; i++){
switch(parStyles[i].name){
case ‘Table Header’: parStylesStorage [‘Table Header’] = i;
flagStorage [0]=true; break;
case ‘Table Footer’: parStylesStorage [‘Table Footer’] = i;
flagStorage [1]=true; break;
case ‘Table contents’: parStylesStorage [‘Table contents’] = i;
flagStorage [2]=true; break;
case ‘Table’: parStylesStorage [‘Table’] = i;
flagStorage [3]=true; break;
case ‘Tbl Hdr’: parStylesStorage [‘Tbl Hdr’] = i;
flagStorage [4]=true; break;
}}

Операция break оптимизирует производительность скрипта: если искомый стиль найден, нет смысла продолжать его поиск, и можно переходить к следующему. Проверяем, все ли используемые стили есть в публикации:

if(flagStorage[0] && flagStorage[1] && flagStorage[2] && flagStorage[3] && flagStorage[4]) {

По умолчанию булевы выражения всегда сравниваются с true, поэтому не надо каждый раз использовать конструкцию типа if(flagStorage[…]==true).

Если все стили на месте, переходим к проверке типа выделенной области для корректной работы скрипта. Ведь нам нужно гарантировать, чтобы он был либо точкой вставки, либо абзацем, иначе (например, если выделена ячейка) выражение

myTable = mySelection.parent.parent;

потеряет смысл, т. к. жёстко привязано к конкретному типу выделенной области. Удостоверяемся, что выделение допустимо:

if (mySelection.constructor.name == "InsertionPoint" || mySelection.constructor.name == "Paragraph"){

Свойство constructor позволяет определить тип любого объекта и постоянно применяется для проверки корректности условий перед началом работы. Используется в связке со свойством name, позволяющим определять этот тип в удобной форме.

Приступаем к форматированию. Сначала присваиваем тексту в ячейках таблицы стиль Table contents:

for (i=0; i< myTable.cells.length; i++)…

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

При переборе ячеек пользуемся исключительно JavaScript — задаём цикл от самой первой ячейки и до последней. Универсальный подход редко бывает оптимален. Попробуем отыскать в самом InDesign методы, ускоряющие процесс. Диапазон методов, применимых к ячейке (Cell), разнообразием не блещет — типичный набор. А если посмотреть на методы, существующие для ячеек (Cells)? Тут нас ждёт находка: проблему решает метод itemByRange(). Он имеет два параметра, задающих диапазон ячеек, к которым InDesign обращается через свои внутренние механизмы (первый задаёт начальный объект, второй — конечный). Анализ производительности обеими методами (перебор средствами JavaScript и InDesign) дал убедительный результат: в среднем, скорость форматирования поднялась на порядок! Теперь нам по зубам и наисложнейшие таблицы.

Изменим часть скрипта, относящуюся к форматированию ячеек. Введём две переменные — для доступа ко всем ячейкам и ко всем строкам:

with(myTable.cells)cellsTbl = itemByRange(firstItem(), lastItem());
with(myTable.rows) rowsTbl = itemByRange(firstItem(), lastItem());

Для форматирования текста в таблице можем записать:

with(cellsTbl) {
texts[0].applyStyle(pS [pSA [‘Table contents’]], true);
texts[0] используется для обращения к текстовому содержимому ячейки, поскольку применить метод applyStyle() к ячейке нельзя. Первый параметр — индекс стиля (его мы определили раньше), второй параметр (true) говорит о том, что текущее форматирование текста будет очищено. Горизонтальное выравнивание в ячейке — по центру.
verticalJustification = 1667591796;

Следующий шаг — установка высоты для каждой ячейки соответственно её содержимому. В InDesign свойство autoGrow для строк и ячеек избавляет нас от решения задачи собственными методами:

autoGrow = true;

Задаём параметры оформления:

topInset = VERT_INSET;
bottomInset = VERT_INSET;
leftInset = DEF_SIDE_INSET;
rightInset = DEF_SIDE_INSET;}

На данном этапе все основные операции на уровне отдельных ячеек выполнены. Осталось заняться объектами более высокого уровня. Выполняем операции над таблицей:

with(myTable) {
alternatingFills = AlternatingFillsTypes.alternatingRows;//чередовать заливку по строкам

startRowFillCount = 1;
startRowFillColor = alterFill;//первый используемый цвет
endRowFillColor = "None";//второй используемый цвет
startRowFillTint = 100;//плотность цвета
skipFirstAlternatingFillRows = 1;//начальная строка

Следующий шаг — определение необходимой ширины таблицы. Если колонок больше 5, таблица однозначно должна быть широкой. Если меньше и пользователь не указал явно в окне диалога, что таблица должна быть широкой, делаем её узкой:

width = (columns.length < 5 || table_W == narrow) ? narrow : wide;

Настало время задать выключку влево для крайнего левого столбца. Метод itemByRange() воздействует только на те элементы, к которым он применяется, поэтому раньше (когда работали на уровне всех ячеек таблицы) мы не могли получить доступ к столбцам. Используем конструкцию rowsTbl и в каждом ряду воздействуем только на самую первую ячейку (порядок ячеек в InDesign — слева направо, поэтому для рядов cells[0] означает крайнюю левую ячейку):

with(rowsTbl){
cells[0].texts[0].justification = 1818584692;//выключка влево
topEdgeStrokeWeight = bottomEdgeStrokeWeight = 0;
}

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

Следующий шаг — задание ширины первой колонки. Частенько в таблице фигурируют одни цифры, и лишь первая колонка предназначена для текста — под работу именно с такими таблицами в первую очередь и создавался скрипт. Постепенно увеличиваем ширину первой колонки, пока текст не займёт одну строку. Если это невозможно (текста слишком много), ограничиваем макс. ширину колонки значением 5 см. Оптимальная величина шага — 1 мм.

Использовать конструкцию tbl.columns[0].cells.itemByRange()не получится, поскольку тут нам нужна работа не с массивом данных, а непосредственно с каждой ячейкой — ведь количество символов в них может не совпадать.

step = 0.1// единица измерения (сантиметры) установлена заранее
for (i = 0; i < myTable.rows.length; i++) {
with(myTable.rows[i].cells[0]) while(lines.length > 1 && width < 5) width = width + step;
}

Последний аккорд: оформляем разделительную сетку. Она должна быть только вертикальной (рис. 1):

with(myTable) {
for (i = 0; i < columns.length; i++) {
with (columns[i]) {
leftEdgeStrokeColor = aD.swatches[1]; rightEdgeStrokeColor = aD.swatches[1];
leftEdgeStrokeWeight = DEF_STROKE_W + " pt";
//явно указываем единицу измерения, иначе будут использоваться миллиметры
rightEdgeStrokeWeight = DEF_STROKE_W + " pt";

Изменяем ширину всех колонок с учётом новой ширины первой:

if(i>0) width=(table_W-parent.columns[0].width)/(parent.columns.length-1)
}}}

Не используем метод itemByRange (firstItem(), lastItem()), потому что нужно было сделать исключение для крайнего левого столбца; хотя, конечно же, можно было вместо firstItem() использовать nextItem (firstItem()). Присваиваем стили абзацам:

myParagraphs = mySelection.paragraphs;
curr_para = myParagraphs [0];
curr_para.applyStyle(pS [pSA [‘Table’]], true);
myParagraphs.previousItem(curr_para).applyStyle(pS[pSA[‘Table Header’]],true);
myParagraphs.nextItem(curr_para).applyStyle(pS[pSA[‘Table Footer’]],true);
if (table_W == Obj.w.wide)
with(myParagraphs) itemByRange(previousItem(curr_para), nextItem(curr_para)).leftIndent = 0;

Вот и весь скрипт. А как задать шапку? Если она простая (ячейки не объединены ни по вертикали, ни по горизонтали), делается это просто:

rowType = RowTypes.headerRow;

Задача усложняется, если ячейки объединены: при попытке выполнить такую, казалось бы, безобидную операцию InDesign аварийно заканчивает работу. Обходной путь: определяем ряд, в котором находится курсор (свойство ячейки name содержит номер колонки и ряда, в которых она находится, разделённые знаком «:»; разбиваем строку по этому разделителю, получаем массив строк и берём последний элемент, поскольку номер ряда идёт последним):

curr_row_name = sel.parent.name;
curr_row = curr_row_name.split(":")[1];
tbl.rows[0].select();

расширяем область выделения до той строки, в котором находится курсор

for (I = 1; I <= curr_row; i++) tbl.rows[i].select(SelectionOptions.addTo);
mySelection.rowType = RowTypes.headerRow;
mySelection.fillColor = headerFill;//заливка
mySelection.texts[0].applyStyle(pS[pSA[‘Tbl Hdr’]],true);//оформление стилем
myDocument.select(NothingEnum.nothing);//снятие выделения

Фактически мы повторяем все действия, которые выполняем при создании шапки вручную, — сначала выделяем строки, а потом конвертируем. Метод хорошо работает, если заранее точно известно, сколько рядов занимает шапка. Автоматизировать процесс, к сожалению, не удастся, поскольку структура таблицы может быть сколь угодно сложной.

 

Растягивание таблицы

Если в публикации несколько таблиц, часто возникает ситуация, когда нижний край таблицы немного не доходит до низа колонки набора — начинать новый абзац смысла нет, а оставлять «висельника» тоже нехорошо. Надо увеличить высоту таблицы за счёт добавления интервалов между ячейками так, чтобы она чётко вписывалась в оставшееся до низа колонки место.

Pис. 2. Растягиваем таблицу до края полосы набора

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

Устанавливаем знакомые из предыдущего примера ссылки:

mySelection = app.activeDocument.selection[0];
myTable = sel.parent.parent;
myParagraph = mySelection.storyOffset.paragraphs[0];
myTextFrame = sel.parentTextFrames[0];

Определяем реальную высоту таблицы и требуемую; результат делим на количество строк — это и будет то приращение, на которое нужно увеличить высоту строки.

delta = (myTextFrame.geometricBounds[2] — myParagraph.baseline)/ myTable.rows.length;

Дальнейшее имеет два сценария:

  1. Явная установка высоты ячейки (величина приращения нам уже известна).
  2. Изменение отступов сверху и снизу (предпочтительнее, поскольку не зависит от способа выравнивания текста в ячейке по вертикали).

Остановимся на последнем. Естественно, величина приращения для каждого отступа должна быть вдвое меньше, чтобы в итоге мы вышли на то же значение. Осталось предусмотреть вариант, когда строк не так много, а расстояние до низа колонки набора достаточно большое. Задаём порог (thresholdValue) — макс. значение высоты ячейки.

thresholdValue = «1 cm»;
for (i = 0; i < myTable.rows.length; i++) {
if (delta > thresholdValue) delta = thresholdValue;
myTable.rows[i]. topInset += delta / 2;
myTable.rows[i]. bottomInset += delta / 2;
}

Если строк много, вместо for (i = 0; i < myTable.rows.length; i++) можно использовать уже рассматривавшийся метод itemByRange().

Надеюсь, примеры помогут вам в освоении скриптинга. Удачи!

Об авторе: Михаил Борисов (mikebb@ukr.net), пишет для Publish полезные советы и обзоры ПО.


 

Универсальные методы

Для большей гибкости и удобства любая коллекция InDesign поддерживает методы: получить следующий объект (nextItem()), предыдущий (previousItem()), самый первый (firstItem()), последний (lastItem()), расположенный посредине (middleItem). Это позволяет не тратить время на получение конкретного индекса элемента в коллекции (абсолютное значение), а просто задавать относительное, что гораздо проще.

Комбинируя метод itemByRange() с методами, задающими относительное положение объекта, можно эффективно и просто управлять большими массивами объектов.

Обратите внимание, что параметры диапазона, как и itemByRange(), — это тоже методы. А поскольку они принимают параметры, можно обращаться через необходимую комбинацию к любому объекту, например, к предпоследнему: currentStory.paragraphs.previousItem (currentStory.paragraphs.lastItem()) и т. д.

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

Помните, что работа идёт исключительно с теми объектами, к которым метод применяется. Если задать диапазон строк таблицы, доступ к отдельным ячейкам внутри этих строк будет запрещён.


 

Всегда в резерве

Многие свойства (verticalJustification, horisontalJustification, position и др.) могут иметь только зарезервированные значения. Их допускается задавать двумя способами: либо в текстовом виде (VerticalJustification.centerAlign, Position.Superscript), что увеличивает читабельность кода, либо в цифровом — читабельность приносится в жертву производительности, поскольку InDesign не нужно проводить дополнительного преобразования. Полный список зарезервированных значений (Enumerators) есть в руководстве по написанию скриптов.

Данное число соответствует выбранному нами типу выравнивания. Можно писать и по-другому: verticalJustification = VerticalJustification.centerAlign; Так понятнее, но несколько медленней, поскольку скрипту всё равно придётся выполнять это преобразование.

ПОХОЖИЕ СТАТЬИ
Управление цветом в Adobe Creative Cloud без секретов

Подробное руководство по настройке параметров управления цветом в программах Adobe.

InDesign осваивает ePub

Приёмы построения ePub в Adobe InDesign CC. Советы издательствам, как без лишних хлопот готовить книги для LitRes или Bookmate.



Новый номер

Тема номера: Мир ламинаторов. Послепечать на конвейере. УФ-принтер на «троих». DTF-комплекс Yinstar. Художественное конструирование в СССР. Реальная роботизация полиграфии. Книги как военная добыча. Обзор этикеток с испанских полок. Свежий взгляд на QAZPAK.



Какой следующий принтер вы купите себе на производство?
Широкий УФ
25%
25 %
Сувенирный УФ
27%
27 %
ДТФ (текстиль)
20%
20 %
УФ ДТФ
20%
20 %
Латекс
7%
7 %
Экосольвент
12%
12 %
На водных чернилах
7%
7 %
Сублимацию
8%
8 %
Для прямой печати по ткани
10%
10 %
ДТГ («футболочный»)
3%
3 %
Проголосовало: 59