Think.JS №20

  • Для начинающих: Дело мастера боится
  • Полевые испытания: Кто ваша мама, ребята?
  • Полезный скрипт: Колонтитул своими руками

Содержание

Для начинающих: Дело мастера боится

Полевые испытания: Кто ваша мама, ребята?

Полезный скрипт: Колонтитул своими руками

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

Дело мастера боится

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

Коллекция мастер-страниц, а точнее, мастер-разворотов, хранится в свойстве masterSpreads объекта типа document. Доступ к каждому мастер-развороту осуществляется как через его индекс, так и через имя. Причем имя – свойство name – у мастер-разворота не простое, а составное, зависящее от двух других свойств: namePrefix и baseName.

masterSpreadName.jsx
Скрипт сообщает имя, префикс и базовое имя мастер-разворота
with (app) {
// Получаем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Получаем первый мастер-разворот
  var mySpread = myDoc.masterSpreads[0];
// Выводим имя, префикс и базовое имя разворота
  alert("name: " + mySpread.name + "\nnamePrefix: " + mySpread.namePrefix + "\nbaseName: "+ mySpread.baseName);
}

Отдельные страницы мастер-разворота доступны через коллекцию masterSpread.pages. В частности, можно узнать количество страниц в развороте.

masterSpreadPagesLength.jsx
Скрипт сообщает количество страниц в мастер-развороте
with (app) {
// Получаем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Получаем первый мастер-разворот
  var mySpread = myDoc.masterSpreads[0];
// Выводим количество страниц в развороте
  alert(mySpread.pages.length);
}

Однако намного полезнее знать не количество страниц в самом развороте, а к скольким страницам этот мастер-разворот применен. Желательно заодно и получить их все. Делается это перебором страниц с проверкой свойства appliedMaster.

getAppliedPages.jsx
Скрипт демонстрирует процедуру получения всех страниц, к которым применен данный мастер
with (app) {
// Получаем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Получаем первый мастер-разворот
  var mySpread = myDoc.masterSpreads[0];
// Создаем массив для хранения найденных страниц  
  var myPages = [];
// Перебираем в цикле все страницы документа
  for (var counter = 0; counter < myDoc.pages.length; counter++) {
// Если к странице применен указанный мастер-разворот, то помещаем ее в массив
    if (myDoc.pages[counter].appliedMaster == mySpread) {
      myPages.push(myDoc.pages[counter]);
    }
  }
// Сообщаем количество найденных страниц
  alert(myPages.length);
}

Чтобы получить все мастер-объекты (которые не отделены от мастер-разворота) на странице, к которой применен определенный мастер-разворот, используется свойство masterPageItems, в котором вперемешку хранятся объекты типа guide и pageItem. Такой способ хранения достаточно удобен, несмотря на то, что объекты не разделяются по типам – коллекция masterPageItems аналогична по составу коллекции pageItem. Более того, эти две коллекции в сумме составляют весь видимый набор объектов на странице, поскольку мастер-объекты не включаются в коллекцию pageItem, а также отделенные объекты не относятся к коллекции masterPageItems.

masterPagesItemsInfo.jsx
Скрипт демонстрирует использование коллекции masterPageItems
with (app) {
// Получаем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Получаем первый мастер-разворот
  var mySpread = myDoc.masterSpreads[0];
// Создаем массив для хранения найденных страниц  
  var myPages = [];
// Перебираем в цикле все страницы документа
  for (var counter = 0; counter < myDoc.pages.length; counter++) {
// Если к странице применен указанный мастер-разворот, то помещаем ее в массив
    if (myDoc.pages[counter].appliedMaster == mySpread) {
      myPages.push(myDoc.pages[counter]);
    }
  }
// Если найдено не менее одной страницы
  if (myPages.length > 0) {
// Сообщаем количество мастер-объектов
    alert(myPages[0].masterPageItems);
  }
}

Чтобы отделить мастер-объект на определенной странице, используется метод override() этого объекта. В качестве обязательного параметра этот метод принимает ссылку на целевую страницу. Метод возвращает объект, который создается на странице в результате отделения мастер-объекта, поэтому его можно подвергнуть обработке, например, изменить размер, цвет, содержимое – в зависимости от типа объекта.

overrideAllMasterPageItems.jsx
Скрипт демонстрирует процедуру отделения мастер-объекта
with (app) {
// Получаем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Получаем первый мастер-разворот
  var mySpread = myDoc.masterSpreads[0];
// Создаем массив для хранения найденных страниц  
  var myPages = [];
// Перебираем в цикле все страницы документа
  for (var counter = 0; counter < myDoc.pages.length; counter++) {
// Если к странице применен указанный мастер-разворот, то помещаем ее в массив
    if (myDoc.pages[counter].appliedMaster == mySpread) {
      myPages.push(myDoc.pages[counter]);
    }
  }
// Если найдено не менее одной страницы
  if (myPages.length > 0) {
// Пока на странице есть мастер-объекты, отеляем парый объект из коллекции
    while (myPages[0].masterPageItems.length > 0) {
      myPages[0].masterPageItems[0].override(myPages[0]);
    }
  }
}

Однако после отделения мастер-объекта от мастер-разворота информация о том, что объект был отделен, остается доступной через свойство overridden. Если это свойство у объекта равно true, то объект был отделен от мастер-разворота. Для того чтобы объект вернуть в исходное состояние мастер-объекта (при этом будут удалены все изменения, сделанные с объектом после отделения), используется метод removeOverride(). Такой метод есть как у объектов типа pageItem и guides, так и у страниц (объект page). Если требуется отменить отделение у какой-нибудь части объектов, то логичнее воспользоваться методами самих объектов, а если нужно отменить отделение всех объектов на странице, то лучше вызвать метод страницы – так скрипт сработает быстрее.

removeOverrides.jsx
Скрипт демонстрирует процедуру отмены отделения объектов от мастер-разворота
with (app) {
// Получаем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Получаем первый мастер-разворот
  var mySpread = myDoc.masterSpreads[0];
// Создаем массив для хранения найденных страниц  
  var myPages = [];
// Перебираем в цикле все страницы документа
  for (var counter = 0; counter < myDoc.pages.length; counter++) {
// Если к странице применен указанный мастер-разворот, то помещаем ее в массив
    if (myDoc.pages[counter].appliedMaster == mySpread) {
      myPages.push(myDoc.pages[counter]);
    }
  }
// Создаем массив для хранения отделенных объектов
  var myItems = [];
// Если найдено не менее одной страницы
  if (myPages.length > 0) {
// Проверяем все объекты типа pageItems
    for (var counter = 0; counter < myPages[0].allPageItems.length; counter++) {
// Если объект был отделен от мастера, то помещаем его в массив
      if (myPages[0].allPageItems[counter].overridden) {
        myItems.push(myPages[0].allPageItems[counter]);
      }
    }
// Для каждого отделенного объекта вызываем метод восстановления
    for (var counter = 0; counter < myItems.length; counter++) {
      myItems[counter].removeOverride();
    }
  }
}

Для того чтобы окончательно и бесповоротно отделить объект от мастер-разворота без возможности восстановления используется метод detach(). Естественно, что перед вызовом этого метода нужно отделить мастер-объект на целевую страницу, получив ссылку на отделенный объект. И уже для полученного объекта вызывать соответствующий метод.

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

Кто ваша мама, ребята?

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

Разные уровни родственных отношений текстовых объектов мы уже подробно рассматривали, но не мешало бы вкратце повторить еще раз.

Иерархия текстовых объектов разработана достаточно хорошо, поэтому, получив любой текстовый объект, можно размотать цепочку взаимосвязей в обе стороны. Например, получив произвольный текст, можно получить все абзацы, в которых этот текст располагается, использовав свойство paragraphs. Также легко и просто можно все слова этого текста, все его знаки и все точки вставки, обследовав свойства words, characters и insertionPoints соответственно. Для получения всех текстовых фреймов, в котором расположен найденный текст, используется свойство parentTextFrames, содержащее коллекцию текстовых фреймов. В конце концов, родительская статья для любого текстового объекта обнаруживается при использовании свойства parentStory.

С объектами типа pageItem все несколько сложнее. В самом общем и простом случае они располагаются на страницах или же на развороте. Причем, если родительским объектом (свойство parent) является объект типа spread, то объект расположен за пределами страницы, а именно на монтажном столе.

baseParentObject.jsx
Скрипт демонстрирует получение типа родительского объекта для указанного объекта в обычных случаях
with (app) {
// Получаем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Определяем, является ли выбранный объект pageItem  
  try {
    var myObject = selection[0];
    var myBounds = myObject.visibleBounds;
  } catch (error) {
    exit();
  }
// Проверяем свойство constructor name у выбранного объекта и сообщаем результат  
  switch (myObject.parent.constructor.name) {
    case "Page":
      alert("Выбранный объект расположен на странице");
      break;
    case "Spread":
      alert("Выбранный объект расположен на монтажном столе");
      break;
    case "MasterSpread":
      alert("Выбранный объект расположен на мастер-развороте");
      break;
    default:
      alert("Выбранный объект расположен непонятно где");
      break;
  }
}

Если родительским объектом не является страница, разворот или мастер-разворот, то объект либо сгруппирован (в таком случае в свойстве parent будет указана ссылка на объект типа group), либо расположен в тексте как inline-объект. В каждом из этих случаев в принципе, можно найти, например, страницу, на которой находится объект. Однако, родительский объект может в свою очередь располагаться в другой группе, которая в свою очередь находится внутри объекта типа pageItem как в контейнере. В этом случае может помочь общая функция для поиска родительского объекта заданного типа. В качестве параметров функция принимает объект, для которого должен быть найден родительский объект определенного типа и имя этого самого типа, например, “Page”.

getParent.jsx
Скрипт демонстрирует использование функции getParent
// Общая функция для получения родительского объекта заданного типа
function getParent (myObject, myType) {
// Пока объект имеет тип, отличный от Application (самый верхний уровень)
  while (myObject.constructor.name != "Application") {
// Если объект имеет требуемый тип, возвращаем его
    if (myObject.constructor.name == myType) {
      return myObject;
// Иначе помещаем в переменную родительский объект
    } else {
      myObject = myObject.parent;
    }
  }
// Если объект нужного типа не был найден, то возвращаем null
  return null;
}
with (app) {
// Получаем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Определяем, является ли выбранный объект pageItem  
  try {
    var myObject = selection[0];
    var myBounds = myObject.visibleBounds;
  } catch (error) {
    exit();
  }
// Вызываем функцию для получения родительской страницы объекта
  var myPage = getParent(myObject, "Page");
// Если функция вернула страницу, сообщаем ее имя, иначе сообщаем, что объект расположен не на странице
  if (myPage != null) {
    alert("Объект расположен на странице " + myPage.name);
  } else {
    alert("Объект расположен не на странице");
  }
}

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

Почему функция getParent названа общей? Потому, что она хорошо работает в общем случае, но неудовлетворительно при работе с inline-объектами. В этот нет ничего удивительного, поскольку функция работает исключительно со свойством parent. Если скрипт будет работать с объектами, которые, возможно, будут находиться внутри inline-объектов или будут сами являться таковыми, требуется модификация функции. Как правило, подавляющее большинство таких поисков – это попытки нахождения родительской страницы, то следует написать и использовать в будущем специальную функцию именно для этого.

getParentPage.jsx
Скрипт демонстрирует использование специальной функции для поиска родительской страницы объекта
// Функция для получения родительской страницы объекта
function getParentPage (myObject) {
// Пока объект имеет тип, отличный от Application (самый верхний уровень)
  while (myObject.constructor.name != "Application") {
// Если объект имеет требуемый тип, возвращаем его
    if (myObject.constructor.name == "Page") {
      return myObject;
// Иначе помещаем в переменную родительский объект, сначала попробовав получить родительский текстовый фрейм
    } else {
      try {
        myObject = myObject.parentTextFrames[0];
      } catch (error) {
        myObject = myObject.parent;
      }
    }
  }
// Если объект нужного типа не был найден, то возвращаем null
  return null;
}
with (app) {
// Получаем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Определяем, является ли выбранный объект pageItem  
  try {
    var myObject = selection[0];
    var myBounds = myObject.visibleBounds;
  } catch (error) {
    exit();
  }
// Вызываем функцию для получения родительской страницы объекта
  var myPage = getParentPage(myObject);
// Если функция вернула страницу, сообщаем ее имя, иначе сообщаем, что объект расположен не на странице
  if (myPage != null) {
    alert("Объект расположен на странице " + myPage.name);
  } else {
    alert("Объект расположен не на странице");
  }
}

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

Колонтитул своими руками

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

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

Для работы со скриптом нужно создать на мастер-странице один или несколько текстовых фреймов, содержащих слово «колонтитул» и применить эту страницу к тем страницам документа, которые будут обработаны скриптом. После запуска скрипт предложит выбрать мастер-страницу и стиль знака, который определяет текст, попадающий в колонтитул. После выбора настроек пользователем скрипт обработает все страницы, открепив текстовые фреймы от мастер-страницы и заменив текст в них.

Назначение скрипта скорее учебное и демонстрационное, нежели практическое. Из видимых недостатков самый главный – невозможность получения последнего текста, к которому применен указанный стиль. Поэтому объявляю домашнее задание: доработать скрипт для решения этой задачи. Даю подсказку: есть два очевидных пути. Один требует значительной переработки скрипта, а второй – для настоящих ленивых программистов, поскольку будет работать при добавлении одной строки и изменения кое-каких переменных. Оценку «отлично» получит тот, кто укажет оба пути .

colontitulSample.jsx
Демонстрационный скрипт для создания скользящих колонтитулов
// Функция для получения родительской страницы объекта
function getParentPage (myObject) {
  while (myObject.constructor.name != "Application") {
    if (myObject.constructor.name == "Page") {
      return myObject;
    } else {
      try {
        myObject = myObject.parentTextFrames[0];
      } catch (error) {
        myObject = myObject.parent;
      }
    }
  }
  return null;
}
// Функция для получения имен элементов в коллекции. Используется для получения stringList в диалогах
function getNames (myArray, myNum) {
  var myResult = new Array();
  try {
    for (var elementCounter = myNum; elementCounter < myArray.length; elementCounter++) {
      try {
        myResult.push(myArray[elementCounter].name);
      } catch (error) {
        myResult.push(String(myArray[elementCounter]));
      }
    }
  } catch (error) {
    myResult = null;
  }
  return myResult;
}
// Функция для получения страниц документа, к которым применен указанный мастер-разворот
function getPages(myDoc, myMaster) {
  var myResult = [];
  for (var counter = 0; counter < myDoc.pages.length; counter++) {
    if (myDoc.pages[counter].appliedMaster == myMaster) {
      myResult.push(myDoc.pages[counter]);
    }
  }
  return myResult;
}
// Функция для получения всех текстовых мастер-фреймов, содержащих только слово "колонтитул" для указанной страницы
function getFrames (myPage) {
  var myResult = [];
  for (var fcounter = 0; fcounter < myPage.masterPageItems.length; fcounter++) {
    var myMPI = myPage.masterPageItems[fcounter];
    if (myMPI.constructor.name == "TextFrame" && myMPI.contents == "колонтитул") {
      myResult.push(myMPI);
    }
  }
  return myResult;
}
// Функция получения первого текста из массива, который расположен на указанной странице
function getText (myTexts, myPage) {
  for (var counter = 0; counter < myTexts.length; counter++) {
    if (getParentPage(myTexts[counter]) == myPage) {
      return myTexts[counter].contents;
    }
  }
  return "";
}
with (app) {
// Получаем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    alert("Нет открытых документов!");
    exit();
  }
// Получаем статью для обработки
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    alert("Не выбран текст!");
    exit();
  }
// Создаем диалог  
  var myDialog = dialogs.add({name: "Колонтитул"});
  with (myDialog.dialogColumns.add().borderPanels.add().dialogColumns.add()) {
    with (dialogRows.add()) {
      with (dialogColumns.add()) {
        dialogRows.add().staticTexts.add({staticLabel:"Мастер-разворот:"});
        dialogRows.add().staticTexts.add({staticLabel:"Стиль знака:"});
      }
      with (dialogColumns.add()) {
// Запрашиваем мастер-разворот и стиль знака
        var userMaster = dialogRows.add().dropdowns.add({stringList: getNames(myDoc.masterSpreads, 0), selectedIndex: 0});
        var userCharacter = dialogRows.add().dropdowns.add({stringList: getNames(myDoc.characterStyles, 0), selectedIndex: 0});
      }
    }
  }
// Отображаем диалог
  var myResult = myDialog.show();
  if (!myResult) {
    exit();
  }
// Получаем выбранный пользователем мастер-разворот и стиль  
  var myMaster = myDoc.masterSpreads[userMaster.selectedIndex];
  var myCharacter = myDoc.characterStyles[userCharacter.selectedIndex];
// Получаем все страницы, к которым применен мастер, при помощи функции
  var myPages = getPages(myDoc, myMaster);
// Подготавливаем настройки поиска
  findPreferences = null;
  changePreferences = null;
  findPreferences.appliedCharacterStyle = myCharacter;
// Получаем все тексты, к которым применен выбранный стиль знака
  var myTexts = myStory.search();
  findPreferences = null;
// Обрабатываем все найденные страницы  
  for (var counter = 0; counter < myPages.length; counter++) {
// Определяем текущаю страницу
    var myPage = myPages[counter];
// Получаем все фреймы, которые должны содержать колонтитул
    var myFrames = getFrames(myPage);
// Получаем текст колонтитула    
    var myText = getText(myTexts, myPage);
// Для каждого фрейма колонтитула
    for (var tcounter = 0; tcounter < myFrames.length; tcounter++) {
      try {
// Отделяем фрейм на страницу
        var myNFrame = myFrames[tcounter].override(myPage);
// Меняем содержимое полученного фрейма
        myNFrame.contents = myText;
      } catch (error) {
        continue;
      }
    }
  }
}

Олег Бутрин
THINK.JS выпуск № 20 от 2007-05-14

 

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Connecting to %s