Think.JS №02

  • Для начинающих: Он нас сосчитал
  • Интерфейс: Определение диалоговых окон
  • Учим матчасть: Работаем с коллекциями
  • Полезный скрипт: Отбросить лишнее

Содержание

Для начинающих: Он нас сосчитал

Интерфейс: Определение диалоговых окон

Учим матчасть: Работаем с коллекциями

Полезный скрипт: Отбросить лишнее

Для начинающих

Он нас сосчитал

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

В то время я считал, что скрипт должен делать то же самое, что и человек, но только автоматически, поэтому сначала сделал макет беджа, аккуратно записал размеры всех составляющих (прямоугольников и текстовых фреймов), а затем написал примерно такое (настоящий скрипт за давностью лет утерян, поэтому восстанавливал по памяти как можно ближе к оригиналу, но не на Visual Basic, а на JavaScript – от этого реализация практически не изменилась).

makeBadge.jsx
Скрипт создает новый документ и рисует в нем бедж заранее определенного формата
with (app) {
  var myText = prompt("Фамилия, имя, отчество, должность:", "Иванов;Иван;Иванович;коммерческий директор");
  if (myText == null) {
    exit();
  }
  var myDoc = documents.add();
  with (myDoc.pages[0]) {
    var myBadge = rectangles.add();
    myBadge.visibleBounds = [0,0,60,110];
    var myPhoto = rectangles.add();
    myPhoto.visibleBounds = [10,10,50,40];
    myPhoto.strokeWeight = .5;
    var myTextFrame = textFrames.add();
    myTextFrame.visibleBounds = [10,50,50,100];
    myTextFrame.contents = myText;
    myTextFrame.search(";", false, false, "^p");
    with (myTextFrame) {
      paragraphs[0].pointSize = 20;
      paragraphs[1].pointSize = 16;
      paragraphs[2].pointSize = 16;
      paragraphs[3].pointSize = 12;
      paragraphs[3].fontStyle = "Bold";
    }
  }
}

В скрипте использованы свойства и методы объектов, не встречавшиеся ранее. Нелишним будет иметь под рукой документация по объектной модели InDesign и сверяться с ней.

В строке myPhoto.strokeWeight = .5 определяется, что объект myPhoto (размера 3х4 сантиметра, для последующей наклейки фотографии), который имеет тип rectangle, должен иметь обводку в половину пункта. Для измерения толщины линий в InDesign всегда применяются пункты – из скрипта можно указать размер в любых других единицах измерения, но если потом считать значение свойства, оно будет выражено в пунктах, поскольку программа автоматически конвертирует переданное значение.

В строке myTextFrame.search(";", false, false, "^p") осуществляется поиск и замена в текстовом фрейме. Это достаточно сложный метод в InDesign CS имеет четыре необязательных параметра, а в CS2 – шесть, наглядным представлением которого является окно поиска-замены (после выполнения скрипта можно посмотреть настройки поиска в соответствующем диалоговом окне InDesign). Очень важен порядок следования параметров – если перепутать, то результат будет совсем не такой, как ожидалось. Первым параметром является строка поиска (“;”); вторым – логическое значение, определяющее, будет ли искаться отдельное слово (если указано true) или строка поиска может быть частью слова (false); третий параметр определяет чувствительность к регистру: true означает, что регистр будет учитываться, false – не будет; четвертый параметр определяет строку замены. В скрипте замена понадобилась потому, что исходный текст был в формате MS Excel, который я пересохранил как csv и копировал-вставлял в окно скрипта (позже узнаем, как читать такие файлы напрямую из скрипта).

Строка paragraphs[0].pointSize = 20 означает, что размер шрифта первого параграфа (с индексом 0) должен быть равен 20 пунктам. Как и толщина линии, размер всегда указывается в пунктах. В строке paragraphs[3].fontStyle = "Bold" определяется, что у четвертого параграфа стиль текста должен быть Bold.

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

Интерфейс

Определение диалоговых окон

Авторам, пишущим скрипты для InDesign относительно повезло с инструментами создания диалоговых окон. С одной стороны, в Photoshop (а равно и в Bridge) инструментарий значительно богаче – есть возможность создавать сложные виджеты, группировать элементы диалога, обновлять состояние по событиям и пр.; но с другой стороны в Illustrator вообще не предусмотрены диалоговые окна, поэтому автором скриптов приходится довольствоваться стандартными средствами JavaScript.

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

Других элементов предусмотрено мало: checkboxes (переключатели), radiobuttons (радиокнопки), dropdowns (выпадающие списки) и группы двух видов – вот собственно и весь доступный инструментарий. Будем надеяться, что в следующих версиях InDesign разработчики расширят комплект доступных элементов и, главное, сделают возможность обновления элементов диалогового окна без его закрытия. А пока будем изучать то, что есть.

Новое далоговое окно создается при помощи метода объекта app: app.dialogs.add(). При создании диалогового окна можно передать некоторые свойства, например, name: app.dialogs.add({name: «Выбор документа:»}). Еще одним полезным свойством диалогового окна является canCancel, которое определяет, будет ли в диалоговом окне присутствовать кнопка отмены. По умолчанию canCancel имеет значение true (кнопка отмены присутствует), но при создании диалогового окна, которое важно для правильной работы скрипта и вызывается уже в процессе работы, можно указать: app.dialogs.add({name: «Выбор документа:», canCancel: false}).

У объекта app одновременно может быть несколько диалоговых окон, но больше одного за раз показать пользователю невозможно (также невозможно отобразить диалоговое окно, если в InDesign открыт диалог сохранения или открытия файла, поиска отсутствующих связей, или окно замены шрифтов – в общем, любой модальный диалог). Показ диалогового окна выполняется при помощи метода show: myDialog.show(). Этот метод возвращает логическое значение: если пользователь нажал кнопку Ok, то возвращается true, иначе – false (даже в том случае, если canCancel == false, когда пользователь не может нажать ни на какую другую кнопку, кроме Ok).

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

Взаимное расположение элементов в диалоге определяется табличной структурой. Диалоговое окно содержит элементы типа dialogColumns (столбцы), каждый из которых может содержать элементы типа dialogRows (строки) или элементы borderPanels (панель группировки) и enablingGroups (отключаемая панель). В свою очередь, dialogRows могут содержать dialogColumns, borderPanels, enablingGroups. Последние два – тоже могут содержать dialogColumns. Получается достаточно сложная структура – таблица, в которой в каждой ячейке может содержаться другая таблица (ситуация осложняется еще и тем, что если элемент структуры не содержит ни одного элемента, то он не отображается в окне). По началу такая структура кажется странной, но зато она позволяет отказаться от указания размеров элементов, что бывает иногда очень удобным. Некоторая практика в работе с диалоговыми окнами помогает группировать элементы достаточно удобно для пользователя.

Для практики продемонстрируем небольшой скрипт, который сообщает “Hello, word!” в диалоговом окне. Для этого нам потребуется создать элемент типа staticText (постоянный текст), важнейшим свойством которого является staticLabel (надпись).

 

dialogHelloWord.jsx
Скрипт создает диалоговое окно с надписью
with (app) {
  var myDialog = dialogs.add({name: "Приветствие"});
  with (myDialog.dialogColumns.add()) {
    dialogRows.add().staticTexts.add({staticLabel: "Hello, word!"});
  }
  var myResult = myDialog.show();
  if (myResult) {
    alert("Вы вежливо ответили на приветствие");
  } else {
    alert("Вы невежливо отказались от приветствия");
  }
}

Как видим, элементы в диалоговое окно можно создавать «цепочкой»: в строке dialogRows.add().staticTexts.add({staticLabel: "Hello, word!"}); сначала создается объект dialogRows, а затем в нем создается объект staticTexts. Это намного облегчает написание и читабельность кода – такая запись может быть определена как: «в новой строке создать постоянный текст».

Учим матчасть

Работаем с коллекциями

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

На первый взгляд, коллекции похожи на массивы – у них тоже есть свойство length, к элементу коллекции можно обратиться по его индексу, например: app.documents[0]. Элементы в коллекции можно перебирать в цикле и получать доступ к каждому: for (var counter = 0; counter < app.documents.length; counter++){alert(app.documents[counter].name + “: ” String(app.documents[counter].pages.length + “страницы”))}. Но на этом сходство с массивом заканчивается – нельзя, к примеру, воспользоваться методом массива concat: myDoc.paragraphStyles.concat(myDoc.characterStyles) для получения общего списка стилей параграфов и знаков. Также не работают push(), shift() и пр.

К элементу коллекции можно также обратиться с помощью свойства item() (по поведению это типичный метод, поскольку получает и возвращает значения, но в документации указано, что это – свойство). В качестве параметра этому свойству передается имя элемента коллекции или его индекс, возвращается сам элемент. Но все же правильнее обращаться по индексу как в массиве: myDoc.pages[1] потому, что по умолчанию свойство item() считает переданный параметр именем. Поэтому, коллекция, где элементы имеют свойство name, не будет возвращать нужных элементов.

Очень часто используются методы коллекции firstItem()
(возвращает первый элемент коллекции) и lastItem()
(возвращает последний элемент). Например, чтобы узнать, расположен ли абзац в одном текстовом фрейме или в нескольких связанных, в InDesign CS нужно было сравнить myParagraph.characters.firstItem().parentTextFrame и myParagraph.characters.lastItem().parentTextFrame. Если это два разных объекта, то абзац начинается в одном фрейме, а заканчивается в другом (В InDesign CS2 такой метод тоже работает, но есть и более правильный путь).

Также очень полезны методы previousItem() и nextItem(), которые возвращают предыдущий и последующий соответственно элемент коллекции относительно переданного в качестве параметра. Эти методы чаще всего употребляются при обработке текстов. Например, можно найти некоторый текст в объекте story и вставить за один знак до найденного inline-объект: myStory.insertionPoints.previousItem(myFindText.insertionPoints.firstItem()).rectangles.add(). Этот пример будет работать только в том случае, если myFindText является частью myStory – в этом случае каждый элемент из коллекции myFindText.insertionPoints одновременно будет входить в коллекцию myStory.insertionPoints.

Также для обработки текста очень полезен метод коллекции itemByRange(). В качестве параметров ему передаются индекс первого и индекс последнего элемента выборки (или сами эти элементы, или их имена), а возвращается объект того же типа, что и коллекция (результат очень интересен – например, при выборке овалов с индексами от 0 до N на странице документа, он определяется как объект типа oval, но если передать результат в качестве параметра методу app.select(), то будут выбраны все овалы в указанном интервале, что означает, что результат в некотором роде все же массив или коллекция). Применительно к текстовым объектам это означает возможность получения части текста, который можно обрабатывать (редактировать, копировать, удалять и т.д). Есть, правда, один нюанс. Если в InDesign CS получить var myText = myStory.characters.itemByRange(3, 20).contents, то результатом будет объект String (содержимое текста объекта myStory с 3 по 20 символ), то в InDesign CS2 результатом будет массив с одним элементом, который будет содержать объект String. Эту разницу следует помнить и учитывать.

Есть еще два метода коллекций, которые используются гораздо реже вышеуказанных. Это anyItem() и middleItem(). Первый возвращает случайно выбранный элемент коллекции, а второй – средний элемент.

Полезный скрипт

Отбросить лишнее

Есть у фреймов в InDesign хорошая функция – Fit frame to content. Но есть две неприятные особенности ее применения: фрейм, который содержит только одну строку, подтягивается не только снизу, но и сбоку; со связанными фреймами функция не работает совсем. Для того чтобы обойти эти особенности, был написан скрипт.

frameSize.jsx
Скрипт выравнивает размер текстового фрейма по нижней строке текста
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    alert("Нет открытых документов!");
    exit()
  }
  if (selection.length < 1) {
    alert("Не выбран текстовый фрейм!");
    exit();
  }
  for (var myCounter = 0; myCounter < selection.length; myCounter++) {
    if (selection[myCounter].constructor.name == "TextFrame") {
      try {
        var myFrame = selection[myCounter];
        var myTextLine = myFrame.textColumns.firstItem().lines.lastItem();
        var myBaseline = myTextLine.baseline;
        if (myFrame.textColumns.length > 1) {
          for (var colCounter = 1; colCounter < myFrame.textColumns.length; colCounter++) {
            if (myFrame.textColumns[colCounter].lines.lastItem().baseline > myBaseline) {
              myBaseline = myFrame.textColumns[colCounter].lines.lastItem().baseline;
            }
          }
        } 
        var myBounds = myFrame.visibleBounds;
        myBounds[2] = myBaseline + myFrame.textFramePreferences.insetSpacing[2];
        myFrame.visibleBounds = myBounds;
      } catch (error) {
      }
    }
  }
}

В скрипте определяется координата последней текстовой строки первой текстовой колонки. Затем, если у фрейма несколько колонок, проверяется каждая. Перебором выбирается координата самой нижней текстовой строки. К этой координате прибавляется значение нижнего отступа фрейма. Полученная координата используется для изменения размеров фрейма.


Олег Бутрин
THINK.JS выпуск № 2 от 2006-11-06

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход /  Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход /  Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход /  Изменить )

Connecting to %s