Think.JS №07

  • Для начинающих: Объект Selection. Часть II
  • Полевые испытания: Координаты текста
  • Учим матчасть: Файловая система. Обзор
  • Полезный скрипт: Втягивание текста

Содержание

Для начинающих: Объект Selection. Часть II

Полевые испытания: Координаты текста

Учим матчасть: Файловая система. Обзор

Полезный скрипт: Втягивание текста

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

Объект Selection. Часть II

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

Есть два способа для выделения объекта. В первую очередь, использование метода select() того объекта, который можно выделить. Такой метод есть у каждого объекта типа pageItem и у каждого текстового объекта, за исключением story.

selectPageItem.jsx
Скрипт демонстрирует использование метода select() объекта типа pageItem:
with (app) {
  var myDoc = documents.add();
  with (myDoc.pages[0]) {
    var myRect = rectangles.add({geometricBounds: [20,20,80,80]});
    var myOval = ovals.add({geometricBounds: [90,20,40,80]});
    myRect.select();
  }
}

 

selectTextItem.jsx
Скрипт демонстрирует использование метода select() для текстовых объектов:
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myContents = selection[0].contents;
    var myTextObj = selection[0];
  } catch (error) {
    exit();
  }
  myTextObj.paragraphs[0].select();
}

Второй способ выделения объектов основан на использовании метода select() объектов app и document. Отличие его в том, что при помощи этого способа можно выделить один или несколько объектов типа pageItem.

selectPageItems.jsx
Скрипт демонстрирует использование метода app.select() для нескольких объектов типа pageItem, переданных в качестве параметра:
with (app) {
  var myDoc = documents.add();
  with (myDoc.pages[0]) {
    var myRect = rectangles.add({geometricBounds: [20,20,80,80]});
    var myOval = ovals.add({geometricBounds: [90,20,40,80]});
  }
  select([myRect, myOval]);
}

Как известно, InDesign не позволяет выделять несколько текстовых объектов одновременно. Это не означает, что невозможно выделить несколько знаков, слов или абзацев, просто в таком случае выделенным будет объект типа text, который и будет содержать абзацы, слова и знаки. Поэтому текстовые объекты как правило выделяют при помощи их собственного метода select(), а не app.select() или document.select(). Но выделить, например, несколько абзацев подряд можно – для этого используется необязательный параметр метода select() – existingSelection. Используя значение SelectionOptions.addTo можно добавлять текущий объект в группу выделенных.

addObjToSelection.jsx
Скрипт демонстрирует возможность добавления текстового объекта к выделению:
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myContents = selection[0].contents;
    var myTextObj = selection[0];
  } catch (error) {
    exit();
  }
  myTextObj.parentStory.paragraphs[0].select();
  myTextObj.parentStory.paragraphs[1].select(SelectionOptions.addTo);
}

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

paragraphsRangeSelect.jsx
Скрипт выделяет указанный диапазон абзацев:
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myContents = selection[0].contents;
    var myTextObj = selection[0];
  } catch (error) {
    exit();
  }
  var myText = myTextObj.parentStory.paragraphs.itemByRange(0, 3);
  myText.select();
}

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

paragraphTextSelect.jsx
Скрипт демонстрирует возможность выделения текста абзаца без знака окончания:
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myContents = selection[0].contents;
    var myTextObj = selection[0];
  } catch (error) {
    exit();
  }
  var myText = myTextObj.paragraphs[0].characters.itemByRange(0, myTextObj.paragraphs[0].characters.length - 2);
  myText.select();
}

Иногда бывает очень полезна возможность выделить на странице все объект определенного типа, например, pageItems. InDesign позволяет передать методу select() объектов app или document в качестве параметра всю коллекцию объектов.

selectCollections.jsx
Скрипт демонстрирует возможность выделения всех объектов в коллекции:
with (app) {
  var myDoc = documents.add();
  with (myDoc.pages[0]) {
    var myRect = rectangles.add({geometricBounds: [20,20,80,80]});
    var myOval = ovals.add({geometricBounds: [90,20,140,80]});
    var myTextFrame = textFrames.add({geometricBounds: [110,40,160,70]});
  }
  myDoc.select(myDoc.pages[0].allPageItems);
}

Для того чтобы отменить выделение какого-либо объекта из группы выделенных, можно воспользоваться значением SelectionOptions.removeFrom в качестве дополнительного параметра existingSelection.

deselectObject.jsx
Скрипт демонстрирует возможность снятия выделения с объекта:
with (app) {
  var myDoc = documents.add();
  with (myDoc.pages[0]) {
    var myRect = rectangles.add({geometricBounds: [20,20,80,80]});
    var myOval = ovals.add({geometricBounds: [90,20,140,80]});
    var myTextFrame = textFrames.add({geometricBounds: [110,40,160,70]});
  }
  myDoc.select(myDoc.pages[0].allPageItems);
  myOval.select(SelectionOptions.removeFrom);
}

Для того чтобы выделить объект и при этом снять выделение со всех выделенных ранее, можно использовать значение SelectionOptions.replaceWith в качестве параметра existingSelection. Он используется по умолчанию, но не будет никакого вреда, если указать его принудительно.

selectObjectDeselectAllOther.jsx
Скрипт демонстрирует возможность снятия выделения со всех объектов при выделении нового:
with (app) {
  var myDoc = documents.add();
  with (myDoc.pages[0]) {
    var myRect = rectangles.add({geometricBounds: [20,20,80,80]});
    var myOval = ovals.add({geometricBounds: [90,20,140,80]});
    var myTextFrame = textFrames.add({geometricBounds: [110,40,160,70]});
  }
  myDoc.select(myDoc.pages[0].allPageItems);
  myOval.select(SelectionOptions.replaceWith);
}

Для того чтобы выделить все объекты, которые могут быть выделены, в качестве параметра метода document.select() может быть использовано специальное значение (нумератор)
SelectAll.all. Для того чтобы снять выделение со всех объектов сразу, используется значение NothingEnum.nothing.

selectDeselectAll.jsx
Скрипт демонстрирует возможность выделения и снятия выделения всех объектов
with (app) {
  var myDoc = documents.add();
  with (myDoc.pages[0]) {
    var myRect = rectangles.add({geometricBounds: [20,20,80,80]});
    var myOval = ovals.add({geometricBounds: [90,20,140,80]});
    var myTextFrame = textFrames.add({geometricBounds: [110,40,160,70]});
  }
  myDoc.select(SelectAll.all);
  alert(selection);
  myDoc.select(NothingEnum.nothing);
  alert(selection);
}

Как известно, InDesign не позволяет одновременно выделять объекты на разных разворотах (кроме текста в связанных текстовых фреймах), поэтому попытка выделения объектов на разных разворотах приведет к ошибке. В то же время, выделение объектов при помощи значения SelectAll.all к ошибке не приводит, поскольку выделяются только объекты того разворота, который в данный момент открыт в активном окне документа. Точно также работает команда Select All в InDesign.

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

Координаты текста

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

Для получения координаты Y любого текстового объекта используется свойство baseline, для получения координаты X – свойство horizontalOffset. Самым наглядным примером координат являются координаты точки вставки.

firstInsertionPointPos.jsx
Скрипт сообщает координаты первой точки вставки первого параграфа выделенного текстового объекта:
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myContents = selection[0].contents;
    var myTextObj = selection[0];
  } catch (error) {
    exit();
  }
  with (myTextObj.paragraphs[0].insertionPoints[0]) {
    var myX = horizontalOffset;
    var myY = baseline;
  }
  alert("X = " + myX + ", Y = " + myY);
}

Любой текстовый объект (кроме textFrame) в качестве координат имеет значения horizontalOffset и baseline первой точки вставки, относящейся к этому объекту.

textPos.jsx
Скрипт демонстрирует специфику определения координат текстовых объектов:
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myContents = selection[0].contents;
    var myTextObj = selection[0];
  } catch (error) {
    exit();
  }
  var myText = myTextObj.paragraphs[0].characters.itemByRange(4, myTextObj.paragraphs[0].characters.length - 1);
  with (myText) {
    var myX = horizontalOffset;
    var myY = baseline;
    select();
  }
  alert("X = " + myX + ", Y = " + myY);
}

Свойство baseline возвращает значение координаты базовой линии текстового объекта. Как из этого свойства получить, например, координаты прямоугольника, в котором находится указанный знак? Для этого можно использовать свойства ascent и descent текстового объекта (кроме insertionPoint, который этого свойства не имеет). Свойство ascent определяет максимальный отступ от базовой линии вверх, в котором должны умещаться все знаки текущего текста (например, ascent слова Нью-Йорк будет расстояние от базовой линии до верхней части буквы Й), а descent – максимальный отступ от базовой линии вниз (например, расстояние до нижней точки строчной буквы «р»). Очень удобно, что ascent и descent определяются в текущих единицах измерения, поэтому никаких преобразований не требуется – скрипт, использующий эти свойства, будет работать при любых настройках единиц измерения.

charBorder.jsx
Скрипт рисует прямоугольник вокруг первого знака первого абзаца выбранного текстового объекта:
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myContents = selection[0].contents;
    var myTextObj = selection[0];
  } catch (error) {
    exit();
  }
  var myChar = myTextObj.paragraphs[0].characters[0];
  with (myChar) {
    var myX_0 = insertionPoints[0].horizontalOffset;
    var myY_0 = insertionPoints[0].baseline - ascent;
    var myX_1 = insertionPoints[1].horizontalOffset;
    var myY_1 = insertionPoints[1].baseline + descent;
  }
  var myPage = myChar.parentTextFrames[0].parent;
  var myRectangle = myPage.rectangles.add({geometricBounds: [myY_0, myX_0, myY_1, myX_1]});
}

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

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

Файловая система. Обзор

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

Методы обращения к файловой системе из скриптов ExtendedScript (расширенной версии JavaScript, которая используется в приложениях CS2) очень просты и легки в использовании, поэтому получение данных из файлов или запись в них широко используются в различных проектах. Для того чтобы импортировать файлы в документ InDesign или экспортировать из него, нужно уметь обращаться с файловой системой. Даже для того, чтобы просто сохранить публикацию, нужно указать файл для его сохранения.

Составляющими файловой системы являются объекты типов Folder
(папка) и File
(файл). Для получения ссылки на папку, используется определение var myFolder = Folder (Путь_К_Папке), для получения ссылки на файл – var myFile = File (Путь_К_Файлу).

В объекте типа Folder могут находиться другие папки или файлы. У вложенных файловых объектов есть свойство parent, которое указывает на родительскую папку. Диск также считается папкой, но свойство parent у него равно null. Этим обстоятельством можно воспользоваться для получения иерархии папок, в которых находится файл.

filePath.jsx
Скрипт получает строку иерархии файловых объектов для файла активного скрипта:
with (app) {
  var myFO = activeScript;
  var myString = "/" + myFO.name;
  do {
    myFO = myFO.parent;
    myString = "/" + myFO.name + myString;
  } while (myFO.parent != null)
  alert(myString);
}

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

У файлов и у папок есть свойство name, которое содержит имя объекта в файловой системе. Для папки – это имя папки, для файла – имя файла с расширением. Имена файловых объектов хранятся в URI-нотации (Universal resource identifier – универсальный идентификатор ресурса), поэтому символы, не являющиеся буквами английского алфавита, цифрами и знаком подчеркивания, кодируются, например, символ пробела заменяется на сочетание «%20». Для получения декодированного значения можно воспользоваться встроенной функцией JavaScript decodeURI().

fileDecodePath.jsx
Скрипт получает декодированную строку иерархии файловых объектов для файла активного скрипта:
with (app) {
  var myFO = activeScript;
  var myString = "/" + decodeURI(myFO.name);
  do {
    myFO = myFO.parent;
    myString = "/" + decodeURI(myFO.name) + myString;
  } while (myFO.parent != null)
  alert(myString);
}

Файловые объекты могут не существовать в данной файловой системе. Для проверки используется свойство exists:

fileExists.jsx
Скрипт демонстрирует проверку существования файла
with (app) {
  var myFO = activeScript;
  var myString = "/" + myFO.name;
  do {
    myFO = myFO.parent;
    myString = "/" + myFO.name + myString;
  } while (myFO.parent != null)
  var myFile = File(myString);
  alert(myFile.exists);
}

Как видим, в процессе считывания имен папок в иерархии, мы составили путь к файлу, который правильно воспринимается файловой системой, поэтому проверка существования файла возвращает значение true.

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

pathMethod.jsx
Скрипт демонстрирует использование свойства path:
with (app) {
  var myFO = activeScript;
  alert(myFO.path);
  alert(myFO.path + "/" + myFO.name);
}

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

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

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

Втягивание текста

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

Как правило, при втягивании текста, размеченного разными стилями, изменяется размер текста только определенного стиля. Функция предназначена именно для такого случая. Работа функции основана на пошаговом изменении размера текста с помощью поиска-замены. Для предотвращения зацикливания, используется счетчик замен – при достижении предельного значения (100 замен) функция прекращает работу.

fitText.jsx
Скрипт демонстрирует использование функции втягивания текста:
function fitText (myStory, myTextStyle, myStep) {
  var myStatus = myStory.overflows;
  var myChk = 0;
  if (myStatus) {
    myStep = 0 - Math.abs(myStep);
  } else {
    myStep = Math.abs(myStep);
  }
  app.findPreferences.appliedParagraphStyle = myTextStyle; 
  var myPointSize = myTextStyle.pointSize;
  while (myStory.overflows == myStatus) {
    myPointSize += myStep;
    app.changePreferences.pointSize = myPointSize;
    myStory.search("", false, false, "");
    myChk++;
    if (myChk == 100) {
      return null;
    }
  }
  if (myStory.overflows) {
    app.changePreferences.pointSize -= myStep;
    myStory.search("", false, false, "");
  }
  app.findPreferences = null;
  app.changePreferences = null;    
}
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
  fitText(myStory, myDoc.paragraphStyles[1], 0.1);
}

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Connecting to %s