Think.JS №05

  • Для начинающих: Иерархия текстовых объектов. Часть I
  • Учим матчасть: Метод проб и ошибок
  • Интерфейс: Углы, проценты и числа
  • Полезный скрипт: Называть его по имени

Содержание

Для начинающих: Иерархия текстовых объектов. Часть I

Учим матчасть: Метод проб и ошибок

Интерфейс: Углы, проценты и числа

Полезный скрипт: Называть его по имени

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

Иерархия текстовых объектов. Часть I

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

Базовым текстовым объектом является story (статья). Родительским объектом для статьи является объект document, который имеет свойство – коллекцию stories. Если пользователь или скрипт создают в документе новый текстовый фрейм, автоматически создается объект типа story. У каждой статьи обязательно должен присутствовать хотя бы один текстовый фрейм – если удалить все связанные текстовые фреймы в статье, то автоматически удалится объект story. Количество связанных фреймов в статье можно узнать при помощи свойства myStory.textFrames.length. Содержимое статьи можно получить при помощи свойства myStory.contents.

storiesContents.jsx
Скрипт сообщает количество статей в документе и выводит содержимое каждой
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  alert("Количество статей в документе: " + String(myDoc.stories.length));
  if(confirm("Вывести содержимое каждой статьи?")) {
    for (var counter = 0; counter < myDoc.stories.length; counter++) {
      alert(myDoc.stories[counter].contents);
    }
  }
}

Объект типа textFrame является вторым уровнем иерархии текстовых объектов. Он представляет «физическое» наличие текста в документе, поскольку относится к сборному типу объектов pageItems
(элементов, которые создаются различными инструментами при размещении на страницах и могут быть выделены инструментом Selection Tool). Свойство parent, которое содержит ссылку на родительский объект, у textFrame, вопреки ожиданиям, не содержит ссылки на объект типа story – именно потому, что textFrame может быть представлен как pageItem. У таких объектов родительским считается объект типа page (страница) или типа spread (разворот), но может быть также и другой объект pageItem (например, group). Зато использование свойства parent с помощью несложных манипуляций позволяет получить страницу, на которой расположен любой из тестовых объектов. Получить ссылку на статью, к которой относится текстовый фрейм, можно при помощи свойства parentStory, которое присутствует у всех текстовых объектов.

textFramePages.jsx
Скрипт сообщает, на какой странице находится каждый текстовый фрейм статьи
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
  for (var counter = 0; counter < myStory.textFrames.length; counter++) {
    if (myStory.textFrames[counter].parent.constructor.name == "Page") {
      alert("Текстовый фрейм " + String(counter + 1) + " находится на странице " + myStory.textFrames[counter].parent.name);
    }
  }
}

Связанные текстовые фреймы статьи также имеют строгую структуру. Для доступа к первому фрейму из цепочки связанных, используется свойство startTextFrame, к последнему – endTextFrame. Чтобы получить предыдущий текстовый фрейм относительно выбранного, используется свойство previousTextFrame, последующий можно получить при помощи свойства nextTextFrame. Два последних свойства могут быть определены как undefined, если выбранный фрейм является первым или последним соответственно (или единственным) в цепочке. Поэтому к этим свойствам нужно обращаться с предварительной проверкой.

textFrameThread.jsx
Скрипт демонстрирует работу с цепочкой текстовых фреймов
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
}
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
  if (selection[0].constructor.name != "TextFrame") {
    exit();
  } 
  alert("Содержимое выбранного фрейма: \n" + selection[0].contents);
  alert("Содержимое первого фрейма цепочки: \n" + selection[0].startTextFrame.contents);
  alert("Содержимое последнего фрейма цепочки: \n" + selection[0].endTextFrame.contents);
  if (selection[0] != selection[0].startTextFrame)  {
    alert("Содержимое предыдущего фрейма цепочки: \n" + selection[0].previousTextFrame.contents);
  }
  if (selection[0] != selection[0].endTextFrame) {
    alert("Содержимое следующего фрейма цепочки: \n" + selection[0].nextTextFrame.contents);
  }
}

Текстовый фрейм является единственным текстовым объектом, который можно создавать при помощи метода add() и удалять при помощи метода remove(). Все остальные либо создаются автоматически, как story, либо в результате изменения свойства contents (редактированием, вставкой, удалением и т.п.).

Поскольку свойства previousTextFrame и nextTextFrame являются изменяемыми, при помощи скрипта можно создавать связанные цепочки фреймов.

addFrameToThread.jsx
Скрипт создает новый текстовый фрейм и соединяет его с выбранным фреймом в цепочку
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
  if (selection[0].constructor.name != "TextFrame") {
    exit();
  }
  var myTextFrame = selection[0];
  var myBounds = myTextFrame.geometricBounds;
  var myPage = myTextFrame.parent;
  var myNextTextFrame = myPage.textFrames.add();
  with (myNextTextFrame) {
    geometricBounds = [myBounds[0], myBounds[3] + 3, myBounds[2], myBounds[3] + 103];
    previousTextFrame = myTextFrame;
  }
}

Говоря о текстовом фрейме, нельзя не упомянуть свойство textFramePreferences. Это свойство само по себе является объектом с множеством свойств, специфичных именно для текстового фрейма, как «сборного» объекта, который является текстовым, одновременно являясь объектом типа pageItem. В этот объект выделены свойства, которых нет ни у других текстовых объектов, ни у pageItem. Диалоговое окно «Text Frame Options» является наглядным представлением объекта textFramePreferences. При помощи этого свойства можно, например, настроить значения внутренних отступов и свойства базовых линий текстового фрейма (последнее – только для InDesign CS2). В этом же объекте настраиваются значения textColumn (колонка) – следующего объекта в иерархии текстовых объектов.

Каждый текстовый фрейм имеет минимум одну колонку текста, которая создается автоматически при создании фрейма. Метода textColumns.add() не существует – изменение количества колонок происходит после изменения свойства textFramePreferences.textColumnCount. Дополнительно существуют настройки средника колонок (textColumnGutter), фиксированной ширины колонки (textColumnFixedWidth) и логическое свойство useFixedColumnWidth, которое определяет, будет ли использовано фиксированное значение ширины колонки (если это значение будет равно false, то размеры колонок будут вычисляться в зависимости от размеров фрейма, внутренних отступов, количества колонок и ширины средника). Максимальное количество колонок во фрейме – 40, максимальная фиксированная ширина и максимальный средник – 8640 пунктов.

setTextColumn.jsx
Скрипт изменяет количество колонок во фрейме
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
  if (selection[0].constructor.name != "TextFrame") {
    exit();
  }
  with (selection[0].textFramePreferences) {
    textColumnCount = 2;
    textColumnGutter = 3;
    useFixedColumnWidth = false;
  }
}

Во второй части мы рассмотрим текстовые объекты, которые могут содержаться в текстовых фреймах.

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

Метод проб и ошибок

В прошлом выпуске мы учились писать скрипты, защищенные от ошибок выполнения, вызванных непредусмотренным поведением пользователя. Кроме этого весьма полезного применения у конструкции try{}..catch() {} есть еще одно – отлов ошибок.

Избавиться от синтаксических ошибок поможет интерпретатор, который обрабатывает скрипты до их выполнения. Если написать vap myDoc = activeDocument вместо var myDoc = activeDocument, то скрипт не будет выполнен из-за ошибки интерпретации. Так же хорошо интерпретатор отловит несоответствие количества открытых скобок количеству закрытых. С кавычками ситуация сложнее – правильная с точки зрения интерпретатора запись может выполнять совсем не те действия, которые задумал автор скрипта. Самое сложное при проверке скриптов – это отлов логических ошибок, в борьбе с которыми интерпретатор поможет мало.

Конструкция try{}..catch() {} может существенно помочь в поиске источника ошибки. Если при выполнении блока операторов в try {} происходит ошибка, в catch (){} передается объект типа Error для последующей возможной обработки. Имя этого объекта передается параметром, например: catch (error){}.

errorProperties.jsx
Скрипт сообщает свойства объекта Error и значения этих свойств
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    for (i in error) {
      alert(i + ": " + error[i])
    }
  }
}

Выделите в документе нетекстовый объект и запустите скрипт. Скрипт сообщит номер ошибки (number), имя файла скрипта (fileName), номер строки, в которой произошла ошибка (line), исходный текст скрипта (source), позиции начала (start) и конца (end) текста, вызвавшего ошибку, сообщение об ошибке (message), сгенерированное интерпретатором, имя (name) ошибки и ее описание (description), которое зачастую совпадает с сообщением. В принципе, те же самые сведения сообщает интерпретатор, если не пользоваться конструкцией try{}..catch (){}, но в этом случае выполнение скрипта завершится аварийно (если использовать конструкцию – то выполнение продолжится).

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

errorDescr.jsx
Пользовательский обработчик ошибок
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    var myMessage = "";
    myMessage += "Номер ошибки: " + error.number + "\n";
    myMessage += "Описание ошибки: " + error.message + "\n";
    myMessage += "Строка: " + (error.source.split("\n")[Number(error.line) - 1]) + "\n";
    alert(myMessage);
  }
}

Можно написать несколько обработчиков ошибок для различных ситуаций. Например, один из них должен будет выводить код и описание ошибки методом alert(), другой – сохранять подробное описание ошибки и состояние переменных в файл, чтобы пользователь мог выслать описание ошибки автору скрипта.

Интерфейс

Углы, проценты и числа

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

Например, в поле, предусмотренном для введения значения угла поворота, всегда отображается знак градуса – очень наглядно.

rotationAngle.jsx
Скрипт демонстрирует работу виджета angleEditbox
with (app) {
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add().borderPanels.add()) {
    with (dialogColumns.add()) {
     dialogRows.add().staticTexts.add({staticLabel: "Повернуть объект на"}) 
    }
    with (dialogColumns.add()) {
      var useAngle = dialogRows.add().angleEditboxes.add({editValue: 0, minimumValue: -180, maximumValue: 180, smallNudge: 1, largeNudge: 5});
    }
  }
  myDialog.show();
}

Свойство editValue определяет числовое значение поля ввода. Для ограничения интервала значений, которые может ввести пользователь, используются свойства minimumValue
(наименьшее значение) и maximumValue
(наибольшее значение). Свойство smallNudge определяет шаг изменения значения поля в том случае, если пользователь не вводит числовые значения, а использует клавиши со стрелками. Свойство largeNudge определяет большой шаг изменения в том случае, если одновременно с клавишами стрелок используется клавиша Shift.

Кроме свойства editValue у виджета angleEditbox есть свойство editContents. Разница между этими двумя свойствами в том, что первое определяет значение, а вторая – текст в окне ввода. Соответственно, editValue нужно использовать при операциях (в данном случае — поворота), как параметр, а editContents в том случае, если нужно использовать текстовое представление значения (вмесе со знаком градуса), например, при запросе подтверждения: var myConfirm = confirm("Повернуть объект на " + useAngle.editContents + "?"). Следует отметить, что свойство editContents «округляется» до двух или трех цифр после запятой, например, значение 32,6588 будет округлено до 32,659. Именно поэтому не следует использовать editContents для передачи данных.

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

rotationAngle.jsx
Скрипт демонстрирует работу виджета angleCombobox
with (app) {
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add().borderPanels.add()) {
    with (dialogColumns.add()) {
     dialogRows.add().staticTexts.add({staticLabel: "Повернуть объект на"}) 
    }
    with (dialogColumns.add()) {
      var useAngle = dialogRows.add().angleComboboxes.add({stringList: ["-180", "-90", "0", "90", "180"], editValue: 0, minimumValue: -180, maximumValue: 180, smallNudge: 1, largeNudge: 5});
    }
  }
  var myResult = myDialog.show();
}

Обратите внимание, что элементами массива stringList должны быть строки, а не числа (соответственно названию: stringList – список строк). Следует внимательно следить за тем, чтобы значения списка строк находились внутри допустимого диапазона, поскольку при выборе из списка проверка значения не происходит – в результате неправильного ввода могут возникать скрытые и трудно устанавливаемые ошибки.

Для ввода процентных значений (например, для масштабирования) используются виджеты percentEditbox и percentCombobox. Свойства у них точно такие же, как и у angleEditbox и angleCombobox соответственно.

resizePercent.jsx
Скрипт демонстрирует работу виджета persentCombobox
with (app) {
  var myPercents = ["0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"];
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add().borderPanels.add()) {
    with (dialogColumns.add()) {
     dialogRows.add().staticTexts.add({staticLabel: "Масштабировать на"}) 
    }
    with (dialogColumns.add()) {
      var usePercent = dialogRows.add().percentComboboxes.add({stringList: myPercents, editValue: 0, minimumValue: 0, maximumValue: 200, smallNudge: 1, largeNudge: 5});
    }
  }
  var myResult = myDialog.show();
}

Для ввода целых чисел используются виджеты integerEditbox и integerCombobox; для ввода чисел с дробной частью – realEditbox и realCombobox. Свойства их также полностью соответствуют свойствам angleEditbox и angleCombobox. Иногда применение этих виджетов оправдано, например, в тех случаях, если требуется получить от пользователя целое значение угла. В этом случае можно пожертвовать наглядностью в пользу точности.

integerRotationAngle.jsx
Скрипт демонстрирует работу виджета integerCombobox
with (app) {
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add().borderPanels.add()) {
    with (dialogColumns.add()) {
     dialogRows.add().staticTexts.add({staticLabel: "Повернуть объект на"}) 
    }
    with (dialogColumns.add()) {
      var useAngle = dialogRows.add().integerComboboxes.add({stringList: ["-180", "-90", "0", "90", "180"], editValue: 0.0000, minimumValue: -180, maximumValue: 180, smallNudge: 1, largeNudge: 5});
    }
    with (dialogColumns.add()) {
     dialogRows.add().staticTexts.add({staticLabel: "градусов"}) 
    }
  }
  var myResult = myDialog.show();
}

При вводе значения с десятичной точкой в поле integerCombobox происходит автоматическое округление значения свойства editValue до целого числа.

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

Называть его по имени

В этот раз представлен не один, а два скрипта, которые будут очень полезны как при разработке, так и в использовании скриптов. Иногда в переменной (особенно в ini-переменных) нужно указать путь к файлу или папке, что сделать достаточно непросто и всегда муторно, особенно, если искомый объект зарыт глубоко в дебрях файлового дерева.

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

getFileName.jsx
Скрипт сообщает URI-совместимое имя файла
with (app) {
  var myName = File.openDialog("Choose file...");
  if (myName != null) {
    prompt("InDesign scripts compatibily name:", "\"" + myName + "\"");
  }
}
getFolderName.jsx
Скрипт сообщает URI-совместимое имя папки
with (app) {
  var myName = Folder.selectDialog("Choose folder...");
  if (myName != null) {
    prompt("InDesign scripts compatibily name:", "\"" + myName + "\"");
  }
}

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Connecting to %s