До недавнего времени автоматизация вёрстки использовалась лишь в крупных изданиях, которые могли позволить себе приобрести не только лицензионное ПО, но и потратиться на разработку скриптов. Однако времена меняются. Понимая бесперспективность спора относительно того, справедливо ли устанавливать для стран бывшего Союза цены, действующие в развитых странах, многие разработчики ПО перешли к более гибкой политике. В Украине, например, Adobe в прошлом году снизила цену на пакет Creative Suite на 80%, что привело к резкому всплеску продаж. Воспользовались ситуацией все, кто так или иначе был связан с издательским бизнесом — вплоть до мелких региональных газет. Поскольку многие из них до сих пор обходились PageMaker, после перехода на новое ПО им потребовалась кардинальная перестройка большинства производственных процессов.
Особый интерес к скриптам возник у газет бесплатных объявлений, поскольку они больше выигрывают от их применения. Ведь лучший объект для автоматизации найти сложно: вёрстка состоит из огромной массы однообразных рекламных объявлений, которые каким-то образом выделены (например, рамкой, фоном, полужирным начертанием), — вручную создавать их с нужными размерами и форматировать очень трудоёмко. В то же время из-за чёткой структуры данных (как правило, это заголовок, фото, основной текст) их обработка идеальна для скриптинга.
Типичные области применения
Автоматизация даже одной операции — вставки в вёрстку фотографий из базы, хранящейся на сервере, — уже существенно экономит время. Автору довелось принимать участие в автоматизации нескольких газет. И эта операция для целой газеты стала занимать 10-15 минут (в зависимости от компьютера) вместо часов нудной и непроизводительной работы. И чем объёмнее издание, тем заметнее эффект от применения скрипта. Кроме того, полностью исключается человеческий фактор, когда у сидящего за компьютером накапливается усталость, и он начинает допускать механические ошибки. Не менее впечатляющие результаты и при оформлении рекламных объявлений — автоматическая расстановка разных типов блоков в сотни раз ускоряет процесс, исключаются случайные ошибки — а при обработке 20-30 тысяч абзацев вручную это неминуемо.
Ещё одна область приложения скриптинга — собственно вёрстка таких изданий (в широком смысле). Из-за разных размеров объявлений частенько до конца колонки остаётся пустое место. Приходится увеличивать или уменьшать межстрочное расстояние в колонке. А если этого недостаточно (увеличение на целый пункт будет заметно), поджимают изображения, меняют порядок следования блоков и т. д. Почему бы не переложить эти скучные обязанности на компьютер?
Аналогично и с рекламными блоками: из-за разницы в размерах и пропорциях заполнение ими всей страницы (особенно если издание крупноформатное) занимает время — тут автоматизация также окажет существенную помощь.
Быстро получить практически готовое к печати издание — не проблема. Главное — иметь желание и время на создание скриптов и их тестирование. Определённое время займёт этап приладки к производственному процессу, зато потом всё пойдёт на ура. Только не распространяйтесь начальству о том, что работы у вас поубавилось: к сожалению, это не всегда воспринимается так, как вам хотелось бы…
В статье будут рассмотрены основные вопросы, связанные с решением описанных выше задач, и приведены практические рекомендации. Начнём с самого простого.
Скрипт 1. Вставка изображений в макет
Объявления, введённые в базу данных наборщицами, каким-либо способом размещаются в публикации InDesign в виде сплошного текста с тэгами (реализация механизма хранения и выгрузки — тема отдельного разговора). Пусть общий вид объявления таков:
Заголовок
Адрес иллюстрации
Основной текст
Для нас интерес представляет вторая строка, которая имеет вид <"\\Volume\path\fileName"> либо <"Drive:\path\fileName"> (для хранящихся на сервере и локальном компьютере соответственно). Наша задача — заменить ссылку изображением и вписать его по ширине колонки (хотя изображения предварительно форматируются, бдительность не помешает).
Фактически задача сводится к таким шагам: 1) обнаружение ссылки, 2) приведение её к виду, который понимает InDesign (отбросить угловые скобки, для Mac придётся внести небольшие изменения), 3) определение положения на странице для вставки контейнера под изображение, 4) создание этого контейнера, 5) внедрение в него изображения, 6) удаление текста ссылки, 7) приведение контейнера с содержимым к ширине колонки. В принципе, первый и второй шаги можно объединить — соответствующие механизмы существуют и в InDesign, и в JavaScript.
Одно замечание по поводу кавычек — в InDesign можно автоматически заменить универсальные кавычки (") их местными предпочтениями (для русского языка — на « и »), поэтому нужно следить, чтобы все номера издания делались на основе одного шаблона либо продумать их универсальную замену.Поиск изображения
Получение чистого адреса из отформатированной с помощью тэгов строки — наибольшая сложность данного скрипта. Не бойтесь, если сразу что-то будет непонятно — дочитайте до конца, поймите другие шаги, они очень просты, а потом вернитесь к тому, что не поняли.
Для сложной замены текста можно воспользоваться вкладкой GREP — сокращение от английской фразы «search globally for lines matching the regular expression, and print them» — «искать везде строки, соответствующие регулярному выражению, и выводить их». Ранее (Publish № 7, 2006; http://www.publish.ru/adobe/2006/03/4630699/) вкратце давалось описание регулярных выражений. Напомню основные моменты. По сути, такое выражение — строка из символов и метасимволов, задающая правило поиска. В InDesign текстовые шаблоны задаются во вкладках Find What/Wildcards, Location и Repeat, а специфические — в остальных. К сожалению, программа не понимает команды «искать всё, кроме…» (в нашем случае — кроме букв, цифр, точек и косых: чтобы вычленить «чистый» адрес изображения). В результате нам придётся делать эту замену в два шага: сначала очистить от лишних символов начало ссылки (Find [< "(.+)], Change To [$1]), затем конец (Find [">], Change To [""]) — и то с определёнными ограничениями.Я предлагаю не рисковать с ограничениями и полностью положиться на поиск/замену Adobe ExtendScript, базирующийся на JavaScript, который имеет мощные инструменты для поиска любых комбинаций. Большинство символов в регулярном выражении представляют сами себя за исключением специальных — [ ] \ ^ $ . | ? * + ( ) { }, которые могут быть предварены символом \ (обратная косая черта) («экранированы») для представления их самих в качестве символов текста. Таким образом, для очистки адреса от кавычек и скобок шаблоном для поиска будет выражение < +"([^"]+), что в переводе на привычный язык означает: искать строку, начинающуюся с последовательности < +", и запоминать всё, что встретится, до появления первой кавычки. Теперь сформируем шаблон для замены: [1]. В переводе это звучит так: из найденного взять только то, что точно соответствует результату в круглых скобках, — таким образом из строки вычленяем «чистую» ссылку (из <"Drive:\path\fileName"> получаем Drive:\path\fileName). С половиной задачи справились, осталось проверить работоспособность при ссылках на локальные файлы.
Если для Windows-систем никаких дополнительных действий выполнять не надо, то для Mac шаблон для замены нужно чуть усложнить: традиционная форма обращения к не-загрузочным разделам у него такая: \\Volume\path — т. е. ничем не отличается от обращения к обычному сетевому диску. Итого с учётом замены Drive: на \\Drive получим такую строку для поиска/замены: match(/< +"([^"]+)/)[1].replace(/([a-z]):/, «\\»+»$1»). Команда match() вычленяет адрес, а потом подключается замена синтаксиса обращения к диску (replace). Комбинация «$1» в методе replace() полностью соответствует [1] в методе match().
Теперь можно вздохнуть свободно: с самой трудной частью скрипта — поиском/заменой — закончили. Если же по каким-то причинам освоение регулярных выражений не получается, воспользуйтесь возможностями InDesign, описанными в начале этого раздела.
Вставка изображения
Программирование в InDesign — объектно-ориентированное, т. е. каждый объект существует не сам по себе, а в тесной связке с остальными: текущий документ принадлежит программе InDesign, любой абзац — определённой позиции среди остальных абзацев в текущем Story (Материале) или в данном текстовом фрейме; те принадлежат текущему документу и т. д. Доступ напрямую, без перечисления всей родительской иерархии, невозможен. Поэтому для краткости создадим ссылки на наиболее часто используемые объекты:
myStory выбран таким, поскольку скрипту нужно чётко знать, с каким именно материалом работать, — ведь в документе их множество, мы же, устанавливая курсор в любую колонку основной цепочки фреймов, явно указываем скрипту, что работать нужно только с ней. Объект selection — не что иное, как набор всех выделенных элементов документа, а поскольку место вставки курсора трактуется программой тоже как выделение (не тривиально, зато логично), то мы берём только первый (и единственный) объект из этого набора.aD = app.activeDocument;
myStory = aD.selection[0].parentStory;
Далее — отключаем слежение интерфейса программы за действиями скрипта: ведь разнообразные меню и палитры предназначены для ручного взаимодействия с пользователем. Скрипту они без надобности, а главное — если InDesign не тратит ресурсы на обновление своих многочисленных палитр, он работает гораздо быстрее. Для таких случаев есть специальный объект scriptPreferences, который весьма интересен по разным аспектам, в том числе для задания рабочих параметров исполнения и отладки скрипта:
app.scriptPreferences.enableRedraw = false;
Поскольку каждая строка с адресом изображения представляет собой отдельный абзац, будем шагать по абзацам выбранного материала и, если начало строки совпадает с шаблоном адреса (с угловой скобкой), выполнять подстановку. Поскольку мы условились после размещения изображений подгонять их по ширине колонки, сразу же узнаем её размер:
Вообще-то правильнее для каждого абзаца с адресом определять ширину колонки (фрейма), в которой он находится, но поскольку вёрстка задаётся на мастер-страницах, и они, как правило, в пределах одного материала не меняются, нет смысла каждый раз вычислять это значение. Примем за основу ширину самого первого текстового блока: предложенный подход (через textFramePreferences) универсален и корректно работает как с одноколоночными текстовыми блоками, так и с состоящими из нескольких колонок — независимо от того, как именно построена мастер-страница.P = myStory.paragraphs;
columnWidth = P[0].parentTextFrames[0].textFramePreferences.textColumnFixedWidth;
Подготовительный этап закончен, можно переходить к основной работе — перебору всех абзацев и вычленению путей к изображениям:
for (i = 0; i < P.length; i++){
adres = P[i].contents.match(/^(< +")/);
if(adres) != null){
myLink = adres[1].replace(/([a-z]):/, "\\"+"$1");
Во второй строчке проверяем, содержится ли адрес в текущем абзаце объявления. Если да, то получаем «чистый» путь и ищем по нему файл; иначе — переходим к следующему абзацу. Если же файл изображения отсутствует (например, удалённый сервер выключен или диск не виден системе через «Сетевое окружение»), то прекращаем дальнейшие действия и сигнализируем о проблеме. Команда alert() отображает стандартное для любой системы окно с предупреждением (Adobe ExtendScript, как одна из реализаций JavaScript, унаследовал платформенную независимость).
if( !File( myLink ).exists ) return alert( ‘File ‘+ myLink +’ not found’ );
Эти непростые абзацы
Если же файл с изображением по указанному адресу найден, текст в строке с адресом удаляем и вместо него вставляем контейнер для картинки (при размещении изображения вручную через File/Place создание контейнера происходит незаметно для пользователя, в нашем случае потребуется явно указать, куда именно вставлять изображение). Нам подойдёт самый обычный прямо-
угольник. Рассмотрим удаление текста детальнее. Простая операция очистки абзаца contents="" нам не подходит, ведь скриптинг понимает всё буквально: удалить так удалить — всё полностью, хотя нам нужно удалить только текст, а саму строчку сохранить, чтобы потом вставить в неё картинку. Тут можно перейти на уровень отдельных символов и удалить всё содержимое, кроме самого последнего символа — невидимого в обычных условиях символа абзаца. В методе itemByRange() неявно используется свойство length (количество, в данном случае — символов), именно этим и объясняется наличие двойки среди параметров (последний элемент из набора в JavaScript имеет порядковый номер length-1). Вторая строка — альтернативный вариант, дающий тот же самый результат ("\r" — символ новой строки).
P[i].characters.itemByRange(0, -2).contents = "";
P[i].contents = “\r”;
Вставка изображения
Следующий шаг — вставка контейнера (фрейма). Удобно, что InDesign позволяет задать необходимые свойства прямо в момент создания объекта — среди них стиль. По умолчанию все вновь создаваемые геометрические объекты имеют чёрную окантовку, что нас не устраивает. Поэтому принудительно задаём самый первый стиль из библиотеки стилей для объектов (<Window/Styles/Object Styles/[None]>) — он не имеет ни заливки, ни окантовки. Проверять его наличие смысла нет, поскольку он есть в любом документе и не может быть удалён никаким образом.Второй задаваемый нами параметр — размер прямоугольника (задаются габариты в такой последовательности — верх, левый край, низ, правый край; это справедливо для любых объектов). Поскольку на странице множество колонок, задача вычисления положения по горизонтали (да и вертикали) текущего абзаца очень непроста. Воспользуемся объектно-ориентированным программированием и перейдём с глобального уровня (страницы) на локальный (текущего абзаца). Это позволит перейти от абсолютной системы координат (когда всё меряется от верхнего левого угла страницы) в относительные (относительно текущего абзаца). Спустившись на него и указав исходную точку для дальнейших действий (первая доступная точка вставки insertionPoint), зададим параметры прямоугольника относительно неё. При этом избавляемся от необходимости вычислять положение абзацев самостоятельно, что гораздо более трудоёмко. Необходимость использования insertionPoint вызвана тем, что абзац — понятие неоднозначное, в нём может находиться множество символов, а скрипту необходима абсолютная точность.
Поскольку все параметры при задании размера прямоугольника обязательны, примем высоту прямоугольника равной удвоенной его ширине (рекламные блоки, как правило, вытянуты по высоте). Конкретное значение не принципиально, поскольку в InDesign есть операции подгонки контейнера по размерам содержимого, чем потом и воспользуемся.
frame = P[i].insertionPoints[0].rectangles.add( {appliedObjectStyle: aD.objectStyles[0], strokeWeight: 0, geometricBounds: [0, 0, columnWidth, 2*columnWidth]} );
Наконец, та самая долгожданная операция, ради которой всё и затевалось, — вставка изображения в заданное место:
frame.place( File( myLink ) );
Осталось лишь несколько второстепенных операций, связанных с подгонкой размеров контейнера по размерам его содержимого. Логика подсказывает, что последовательность действий должна быть такой: сначала вгоняем изображение в контейнер — разумеется, с сохранением пропорций. При этом можем быть уверенными в том, что изображение полностью поместилось в колонку по ширине. Следующий шаг — подгонка высоты контейнера по размеру изображения. Такой подход работает железно при любых соотношениях сторон картинки.
frame.graphics[0].fit( FitOptions.proportionally );
frame.graphics[0].fit( FitOptions.frameToContent );
Как вы заметили, InDesign максимально объектно-ориентирован: FitOptions — тоже объект, а удобства от такого подхода мы уже оценили. По окончании работы скрипта выводим интерфейс программы из спячки:
app.scriptPreferences.enableRedraw = true;
Вот, собственно, и всё. Как видите, скрипт достаточно прост, нужно лишь чётко представлять себе иерархию и связи между объектами.
Финальные замечания
Для корректной работы скрипта необходимо, чтобы курсор стоял в любом из текстовых блоков основной цепочки. Поэтому в самом начале желательно выполнить соответствующую проверку:
Метод exit() очень полезен: он останавливает все дальнейшие шаги но, к сожалению, реализован из всех продуктов Creative Suite только в InDesign.if (aD.selection[0].constructor.name != “InsertionPoint”){
alert(“Поместите курсор в текстовый блок!”);
exit();}
В случае внесения изменений в текст разработчики рекомендуют использовать метод recompose(), чтобы редактор корректно их отрабатывал, особенно на длинных текстах. Однако на практике (по крайней мере, с InDesign CS5) ничего критического замечено не было. Для тех, у кого версия ПО ниже, рекомендую добавить строчку if(counter%10==0) myStory.recompose(); где counter — счётчик вставленных изображений, при этом перекомпоновываться всё содержимое будет не каждый раз, а через 10 вставок — приемлемый баланс быстродействия и надёжности работы.
Пара слов о повышении скорости работы скрипта. Во-первых, можно изменить порядок прохода абзацев на противоположный — это ощутимо ускоряет просмотр документа, поскольку скрипту не нужно каждый раз вычислять, не превышено ли значение P.length (оно вычисляется каждый раз заново, что очень неэффективно):
for(i = P.length-1; i > -1; i--).
Во-вторых, вместо FitOptions.proportionally и FitOptions.frameToContent можно использовать их кодовые значения (Enumerators) — при этом ExtendScript не потребуется время для преобразования их в своё внутреннее представление. Если в самом начале создать ссылку на стиль объекта, а потом работать с ней, не обращаясь каждый раз к списку всех доступных стилей, тоже получится выигрыш во времени.
Замечание по поиску/замене. Допускаю, что внутренний механизм всё же эффективнее JavaScript-процедуры и получать путь к изображению через app.changePreferences будет быстрее, однако и предложенный метод показал на практике достаточно высокую производительность. Кроме того, логика подсказывает, что основные затраты времени приходятся на перекачку изображений по внутренней сети, на что мы повлиять никак не можем.
Пользователи, которые хотят видеть, как продвигается обработка документа в реальном времени, могут добавить создание Progress Bar — это разработчиками предусмотрено, но по понятным причинам в круг задач данного материала не входит.
Вообще же тем, кто имеет хоть минимальный опыт программирования, рекомендую обратиться к справочным руководствам для детального ознакомления с объектной моделью InDesign (не пугайтесь объём руководства). Просмотрите прилагающиеся примеры — они подобраны так, чтобы показать значительную часть потенциально интересных для автоматизации рутинной работы моментов. А если возникнут вопросы или необходимость автоматизировать собственный рабочий процесс — пишите по электронной почте bmike68b@gmail.com, буду рад помочь.
Метасимволы
Метасимвол . (точка) означает один любой символ.Набор символов в квадратных скобках [ ] позволяет указать, что на данном месте в строке может стоять любой из перечисленных внутри них символов.
Метасимвол ^ (каретка) означает всё, кроме символов, перечисленных в квадратных скобках.
Если нужно указать, что какие-то символы должны встречаться несколько раз, используют метасимвол +.