Think.JS №03

  • Для начинающих: Покрасочные и отделочные работы
  • Полевые испытания: Проблема удаления
  • Интерфейс: Группируем виджеты
  • Полезный скрипт: Постраничный вывод

Содержание

Для начинающих: Покрасочные и отделочные работы

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

Интерфейс: Группируем виджеты

Полезный скрипт: Постраничный вывод

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

Покрасочные и отделочные работы

Красить различные объекты в InDesign приходится довольно часто. Так же часто приходится проверять, какого цвета объект, какой у него стиль обводки – для перекраски объектов, их сортировки или для любых других целей. Этот процесс не очень сложен, но имеет некоторые тонкости.

Для примера напишем простой скрипт, который будет создавать в новом документе прямоугольник и красить его.

rectangleBrush.jsx
Скрипт демонстрирует методы окраски документа
with (app) {
  var myDoc = documents.add();
  var myRectangle = myDoc.pages[0].rectangles.add();
  with (myRectangle) {
    geometricBounds = [30,30,60,60];
    fillColor = myDoc.swatches.item("Black");
    fillTint = 20;
    strokeColor = myDoc.swatches.item("Black");
    strokeTint = 80;
    strokeType = "Solid";
    strokeWeight = 2;
    strokeAlignment = StrokeAlignment.outsideAlignment;
  }
}

Цвет заливки объекта определяется свойством fillColor. Значением этого свойства должно быть или объект swatch (из палитры Swatches) или имя такого объекта (надежно работает только в CS2). Для того чтобы получить этот объект, используется конструкция myDoc.swatches(“имя”). Имена можно посмотреть в палитре Swatches, следует только помнить, что квадратные скобки означают, что этот swatch не может быть удален или отредактирован. Именем же его является только то, что в скобках: [Black] – Black. При попытке применить к объекту несуществующий swatch, или при ошибке в указании имени, скрипт завершится с ошибкой.

Свойство fillTint определяет насыщенность окраски в процентном значении. Наглядным представлением этого свойства является панель Color. Значение свойства fillTint должно быть в интервале 0..100 включительно; если указать значение меньше или больше, то возникнет ошибка.

Два этих свойства в совокупности определяют настоящий цвет объекта.

Цвет обводки объекта определяется точно так же, как и цвет заливки. Кроме этих настроек, обводка имеет еще два важных свойства: strokeType и strokeWeight. Первое из этих свойств определяет тип обводки, а второе – толщину линии. Доступные значения strokeType можно посмотреть в панели Stroke в списке Type. Толщина линии всегда задается в пунктах. Иногда (особенно в InDesign CS2) объекты по умолчанию создаются с неопределенным свойством strokeType, поэтому надежнее указывать нужный тип всегда принудительно.

Еще одним свойством, влияющим на отображение объекта, является strokeAligment. Это свойства задает, как именно будет располагаться обводка. Существуют три позиции – centerAligment, insideAligment, outsideAligment – по центру, внутри и снаружи соответственно. При прочих равных настройках, strokeAligment влияет на свойство visibleBounds (видимые границы) объекта. Если обводка снаружи, то visibleBounds будет больше, чем geometricBounds на толщину обводки, при центрально положении – на половину толщины обводки, при внутреннем – будут равны.

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

swatchLine.jsx
Скрипт создает линейку для проверки качества печати объектов с разной насыщенностью
with (app) {
  var myDoc = documents.add();
  var myPosition = 0;
  var mySwatch = myDoc.swatches.item("Black");
  for (var counter = 1; counter <= 10; counter++) {
    var myRectangle = myDoc.pages[0].rectangles.add();
    with (myRectangle) {
      strokeWeight = 0;
      geometricBounds = [myPosition, 0, myPosition + 10, 10];
      fillColor = mySwatch;
      fillTint = counter * 10;
    }
    myPosition = myPosition + 10;
  }
}

В цикле происходит создание объектов с различной насыщенностью (с шагом, равным 10%) в зависимости от повторения. Для этого определено условие цикла: повторять для значений от 1 до 10 включительно, при этом fillTint объекта указывается равным значению счетчика counter умноженному на 10.

Обратите внимание на специальную переменную myPosition. Она введена для того, чтобы использовать цикл для помещения всех объектов rectangles друг под другом. Свойство geometricBounds каждого объекта зависит от этой переменной, которая при каждом выполнении цикла увеличивается на высоту созданного в этом же выполнении объекта.

У написанного нами скрипта есть еще одно полезное свойство. Так как объект swatch определяется до начала цикла, то после несложного редактирования можно сделать линейку для проверки любого объекта swatch, указав его имя вместо Black.

Отредактировав условия цикла, можно создавать линейки с шагом не 10, а, например, 5 процентов.

Полевые испытания

Проблема удаления

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

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

textFrameRemover.jsx
Скрипт для удаления всех текстовых фреймов в документе. Версия 1.
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  for (var counter = 0; counter < myDoc.textFrames.length; counter++) {
    myDoc.textFrames[counter].remove();
  }
}

Выполним этот скрипт в документе, в котором есть некоторое количество текстовых фреймов. В лучшем случае после выполнения будут удалены не все фреймы, в худшем – скрипт аварийно завершит работу. Отчего так происходит?

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

В первом проходе цикла удаляется самый первый элемент коллекции, тот, который имеет индекс 0. Но после удаления первого элемента, индекс 0 присваивается следующему элементу в коллекции. Происходит это автоматически. В следующем повторении удаляется элемент, имевший в самом начале цикла индекс 2 (после удаления – индекс 1). Получается, что элемент, который после первого повторения цикла стал самым первым, остался незамеченным и не попал под удаление. Несложно догадаться, что так происходит со всеми нечетными элементами в коллекции. Именно поэтому после выполнения, казалось бы, правильного цикла удаляются не все текстовые фреймы. Хуже может быть только в том случае, если условие цикла указывать не в виде myDoc.textFrames.length
(после каждого удаления свойство length меняется), а присвоить сначала это значение переменной. Например: var myLength = myDoc.textFrames.length; for (var counter = 0; counter < myLength; counter++). В этом случае гарантировано аварийное завершение, потому, что после удаления половины текстовых фреймов, скрипт сделает попытку обратиться к несуществующему элементу.

Как же решается проблема удаления? Есть несколько путей, выбор зависит от эстетических предпочтений и желательной скорости работы.

textFrameRemover.jsx
Скрипт удаляет в активном документе все текстовые фреймы. Правильный вариант №1.
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  for (var counter = myDoc.textFrames.length; counter > 0; counter--) {
    myDoc.textFrames[counter -1].remove();
  }
}

Этот скрипт отличается от предыдущего тем, что удаление происходит не с начала коллекции, а с конца. Тем самым гарантируется, что ни один элемент не минует своей участи.

textFrameRemover.jsx
Скрипт удаляет в активном документе все текстовые фреймы. Правильный вариант №2.
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  while (myDoc.textFrames.length > 0) {
    myDoc.textFrames[0].remove();
  }
}

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

textFrameRemover.jsx
Скрипт удаляет в активном документе все текстовые фреймы. Правильный вариант №3.
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  var myTextFrames = [];
  for (var counter = 0; counter < myDoc.textFrames.length; counter++) {
    myTextFrames.push(myDoc.textFrames[counter]);
  }
  myTextFrames.reverse();
  for (var counter = 0; counter < myTextFrames.length; counter++) {
    myTextFrames[counter].remove();
  }
}

Этот вариант удаления рекомендуется использовать в том случае, если перед удалением каждый элемент в коллекции обрабатывается (например, считывается contents у текстовых фреймов). Сначала вся коллекция помещается при помощи цикла в пустой массив, затем обрабатывается. Перед удалением массив переворачивается при помощи метода reverse(). После этого первый элемент массива будет содержать последний элемент коллекции, второй – предпоследнего и т.д. Следовательно, элементы в коллекции будут удаляться с конца, как в правильном варианте №1. Спрашивается, почему бы не применить reverse() к самой коллекции? А потому, что нет такого метода у коллекции, только у Array есть.

Кстати, закрытие всех открытых документов нужно делать так же, как и удаление объектов – с использованием описанных методов.

Интерфейс

Группируем виджеты

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

Для того, чтобы снабдить каждый виджет соответствующей подписью, как правило делается следующее: в объекте, который будет содержать группу виджетов (обычно это borderPanel, enablingGroup или просто dailogRow) создаются два dialogColumns, в одном из которых последовательно идут надписи, а во втором – соответствующие им окна ввода. Поскольку высота элементов, как правило, одинакова, то взаимное расположение их сохраняется.

nameDialog.jsx
Скрипт демонстрирует принципы группировки элементов диалога
with (app) {
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add()) {
    with (dialogRows.add()) {
      with (dialogColumns.add()) {
        dialogRows.add().staticTexts.add({staticLabel: "Фамилия:"});
        dialogRows.add().staticTexts.add({staticLabel: "Имя:"});
        dialogRows.add().staticTexts.add({staticLabel: "Отчество:"});
      }
      with (dialogColumns.add()) {
       var useFam = dialogRows.add().textEditboxes.add({editContents: "Иванов"});
       var useName = dialogRows.add().textEditboxes.add({editContents: "Иван"});
       var useName2 = dialogRows.add().textEditboxes.add({editContents: "Иванович"});
      }
    }
  }
  myDialog.show();
}

В качестве примера был использован виджет textEditbox, который реализует текстовое окно ввода. Свойство editContents позволяет задавать и считывать содержимое – если понадобиться узнать, какое значение пользователь ввел в поле «Фамилия», достаточно указать свойство useFam.editContents.

Можно попробовать вставлять надписи без создания новой строки, в виде staticTexts.add({staticLabel: "Фамилия:"}) – в этом случае получится, что надписи имеют выключку вправо, вплотную к окнам ввода (если указывать каждый раз новую строку – то выключка влево). Выбор варианта – дело вкуса.

Сходные по смыслу виджеты можно группировать при помощи borderPanel для лучшего восприятия. В нашем примере фамилию, имя и отчества можно вынести в отдельную группу.

nameDialog.jsx
Скрипт демонстрирует принципы группировки элементов диалога
with (app) {
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add()) {
    with (dialogRows.add().borderPanels.add().dialogColumns.add()) {
      dialogRows.add().staticTexts.add({staticLabel: "Данные сотрудника"});
      with (dialogRows.add()) {
        with (dialogColumns.add()) {
          dialogRows.add().staticTexts.add({staticLabel: "Фамилия:"});
          dialogRows.add().staticTexts.add({staticLabel: "Имя:"});
          dialogRows.add().staticTexts.add({staticLabel: "Отчество:"});
        }
        with (dialogColumns.add()) {
         var useFam = dialogRows.add().textEditboxes.add({editContents: "Иванов"});
         var useName = dialogRows.add().textEditboxes.add({editContents: "Иван"});
         var useName2 = dialogRows.add().textEditboxes.add({editContents: "Иванович"});
        }
      }
    }
  }
  myDialog.show();
}

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

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

nameDialog.jsx
Скрипт демонстрирует принципы группировки элементов диалога
with (app) {
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add()) {
    with (dialogRows.add().borderPanels.add().dialogColumns.add()) {
      dialogRows.add().staticTexts.add({staticLabel: "Данные сотрудника"});
      with (dialogRows.add()) {
        with (dialogColumns.add()) {
          dialogRows.add().staticTexts.add({staticLabel: "Фамилия:"});
          dialogRows.add().staticTexts.add({staticLabel: "Имя:"});
          dialogRows.add().staticTexts.add({staticLabel: "Отчество:"});
        }
        with (dialogColumns.add()) {
         var useFam = dialogRows.add().textEditboxes.add({editContents: "Иванов"});
         var useName = dialogRows.add().textEditboxes.add({editContents: "Иван"});
         var useName2 = dialogRows.add().textEditboxes.add({editContents: "Иванович"});
        }
      }
    }
    var useOpt = dialogRows.add().enablingGroups.add({staticLabel: "Дополнительно", checkedState: false});
    with (useOpt) {
      with (dialogColumns.add()) {
        dialogRows.add().staticTexts.add({staticLabel: "E-mail:"});
        dialogRows.add().staticTexts.add({staticLabel: "ICQ:"});
        dialogRows.add().staticTexts.add({staticLabel: "Messenger:"});
      }
      with (dialogColumns.add()) {
       var useEmail = dialogRows.add().textEditboxes.add({editContents: ""});
       var useICQ = dialogRows.add().textEditboxes.add({editContents: ""});
       var useMessenger = dialogRows.add().textEditboxes.add({editContents: ""});
      }
    }
  }
  myDialog.show();
}

Если enablingGroup должен быть «включен», то свойству checkedState задается значение true, иначе – false. Значение этого свойства может быть прочитано после отображения диалогового окна для получения выбора пользователя. Но даже если группа была «выключена», остается доступ к значениям свойств каждого виджета в группе.

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

Постраничный вывод

Часто приходится слышать вопрос: что лучше, скрипты или плагины? Такая постановка вопроса в корне не верна. У скриптов своя область применения, у плагинов – своя. Это как сравнивать автомобиль и самолет: для того, чтобы проехать по городу километр, пусть даже медленно, постоянно застревая в пробках – лучше автомобиль, а для того, чтобы добраться до другого континента – самолет. Лететь в соседний квартал на самолете так же глупо, как пытаться форсировать море на внедорожнике. Однако есть тенденция к гибридизации скриптов и плагинов – для совместного применения. Плагин предоставляет скриптовый интерфейс (по сути, новые объекты, их свойства и методы), а скрипты позволяют использовать их более гибко, подстраивая под конкретные нужды пользователя. Следуя аналогии, получается внедорожник с крыльями – вокруг света он даже с дозаправкой не облетит, но поверх пробок – запросто.

Прекрасным примером того, как могут взаимодействовать скрипты и плагины, является новый плагин Максима Стринжи SnapShot.pln
(скачать его можно на форуме rudtp в разделе, посвященном плагинам для InDesign). Этот плагин добавляет метод snap() к методам объекта page. Метод имеет широкий набор настроек, позволяющий сохранять изображение страницы в разных графических форматах, с различным разрешением и пропорциональным масштабированием.

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

allPageSnap.jsx
Скрипт сохраняет изображения всех страниц документа в определенную папку
var iniFolder = "Screenshots";
var iniType = DbuFileFormat.gif;
var iniColors = DbuColor.rgb;
var iniSize = 1;
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myName = myDoc.name.replace(".indd", "")
  } catch (error) {
    exit();
  }
  var myFolder = Folder(myDoc.filePath + "/" + iniFolder);
  if (!myFolder.exists) {
    myFolder.create();
  }
  for (var counter = 0; counter < myDoc.pages.length; counter++) {
    var myFile = File(myFolder.fullName + "/" + myName + "_" + myDoc.pages[counter].name + ".gif");
    myDoc.pages[counter].snap(myFile, 150, iniType, iniColors, iniSize);
  }
  alert("Экспорт завершен!");
}

Метод snap, реализуемый плагином, имеет следующие настройки. В первую очередь указывается файл для сохранения. Этот параметр является единственным обязательным, все остальные могут использовать значения по умолчанию. Вторым параметром передается разрешение файла (по умолчанию 72 dpi), третий указывает на тип сохраняемого файла (возможные значения: jpg, tif, gif, по умолчанию — первый), третьим – цветовая модель файла (gray, rgb, cmyk, lab, по умолчанию – rgb), четвертым параметром указывается размер (100% соответствует 1, 200% — 2 и т.д.).

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

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

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


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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Connecting to %s