Think.JS №06

  • Для начинающих: Иерархия текстовых объектов. Часть II
  • Учим матчасть: Пункт обмена единиц
  • Интерфейс: Единичный случай
  • Полезный скрипт: Юникод

Содержание

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

Учим матчасть: Пункт обмена единиц

Интерфейс: Единичный случай

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

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

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

В этой статье рассмотрим текстовые объекты, которые составляют text flow (поток текста). В InDesign CS2 текстовый поток имеет много составляющих, которые находятся в четкой связи между собой. В этот раз начнем с самых малых сих.

Объект insertionPoint
(точка вставки) представляет объект, находящийся между двумя знаками в тексте. Несмотря на то, что точка вставки в тексте не видна, такой объект имеет все свойства, характерные для текстовых объектов, вплоть до свойства contents. Если в объекте story нет ни одного знака, свойство story.insertionPoints не бывает пустым. Все inline-объекты в тексте в качестве свойства parent имеют ссылку на объект типа insertionPoint. При использовании оптического кернинга объект insertionPoint содержит реальное значение кернинга между двумя символами. Попытка считать значение кернинга с других текстовых объектов в таком случае приводит к ошибке.

Объект character
(символ) является любым значимым символом в потоке текста. Это может быть буква, цифра, пробельные символы, символы разрыва, символы конца абзаца. В InDesign каждый символ имеет Unicode-значение, которое можно посмотреть в панели Info. Символы разрыва кроме Unicode-значения в качестве свойства contents содержат значения специальных нумераторов (значение можно посмотреть в документации).

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

В объекте типа paragraph
(абзац) может не содержать ни одного слова, но как минимум один объект character и insertionPoint. Абзацы разделяются при помощи символа конца абзаца или символов разрыва. Последний абзац текста может не содержать символа конца абзаца – вместо него используется символ конца текста.

Объект типа textStyleRange
(отрезок текста) определяет текстовый объект, который имеет одно оформление – стиль абзаца и знака, настройки оформления. В зависимости от конкретных настроек текста в отрезке может быть и несколько абзацев, и всего лишь один объект типа insertionPoint.

Универсальный объект text
(текст) определяет произвольный текстовый объект. В тексте может быть несколько отрезков, произвольное количество абзацев, любое количество слов, знаков и точек вставки. Текст может располагаться в нескольких текстовых фреймах или содержать весь текст объекта story. Обычно текст определяется знаком начала и знаком конца текста.

Иерархия текстовых объектов, которая была серьезно переработана в InDesign CS2, позволяет по объекту любого уровня установить все объекты выше или ниже в иерархии – от объекта story до объектов insertionPoint и character. А если можно установить, то можно и обработать.

Все последующие скрипты работают только в CS2, поскольку в InDesign CS иерархия текстовых объектов подчинялась не всегда понятным принципам, что серьезно затрудняло написание хороших скриптов.

parentWord.jsx
Скрипт демонстрирует возможность поиска слова, в которое входит точка вставки
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  if (selection.length != 1) {
    exit();
  }
  if (selection[0].constructor.name != "InsertionPoint") {
    exit();
  }
  var myIPoint = selection[0];
  alert(myIPoint.words[0].contents)
}

Таким же образом можно установить абзацы, в который входит точка вставки или выделенный знак. Следующий скрипт демонстрирует универсальность методов работы с текстовыми объектами. Пользователь может выделить объект любого типа – insertionPoint, character, text, textColumn, textFrame – скрипт в любом случае получит доступ к свойству paragraphs выделенного объекта.

parentParagraphs.jsx
Скрипт сообщает содержимое абзацев текстового объекта.
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myContents = selection[0].contents;
  } catch (error) {
    exit();
  }
  var myTextObject = selection[0];
  for (var counter = 0; counter < myTextObject.paragraphs.length; counter++) {
    alert(myTextObject.paragraphs[counter].contents);
  }
}

И обратно, получив абзац, всегда можно получить доступ к любому тексту, отрезку текста, слову, знаку или точке вставки, которые в этом абзаце содержатся. Доступ к ним можно получить при помощи свойств текстового объекта texts, textStyleRange, words, characters и insertionPoints соответственно.

textObjectInfo.jsx
Скрипт информирует о составе первого абзаца выделенного текстового объекта
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myContents = selection[0].contents;
  } catch (error) {
    exit();
  }
  var myTextObject = selection[0];
  var myParagraph = myTextObject.paragraphs[0];
  var myString = "";
  myString += "Текстов: " + String(myParagraph.texts.length)+ "\n";
  myString +="Отрезков текста: " + String(myParagraph.textStyleRanges.length)+ "\n";
  myString +="Слов: " + String(myParagraph.words.length)+ "\n";
  myString +="Знаков: " + String(myParagraph.characters.length)+ "\n";
  myString +="Точек вставки: " + String(myParagraph.insertionPoints.length)+ "\n";
  alert(myString)
  for (var counter = 0; counter < myParagraph.words.length; counter++) {
    alert(myParagraph.words[counter].contents);
  }
}

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

Почему было введено специальное свойство для родительского текстового фрейма, а не использовано, по аналогии c другими, свойство textFrames? Дело в том, что объекты типа insertionPoint, как уже было сказано, могут содержать inline- или anchored-объекты, в том числе и текстовые фреймы. Именно для таких объектов предусмотрено свойство textFrames. В текстовых объектах (кроме story) – это коллекция текстовых фреймов всех точек вставки в этом объекте.

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

Пункт обмена единиц

Можете ли вы не задумываясь ответить, что больше: 11 пунктов или 4 миллиметра? Или каковы размеры в пунктах плаката 6 на 3 метра? Или, к примеру, сколько футов в километре? Это не самые простые задачи даже для опытного печатника, который привык легко обращаться с разными системами измерений.

В пакете CS2 в общем для всех приложений языке ExtendedScript
(JavaScript расширенный) определен интересный тип объекта – UnitValue. В InDesign CS2 он тоже поддерживается, но в документации по какой-то причине его описание отсутствует. Описание этого объекта можно найти в документе Bridge JavaScript Reference.pdf на официальном сайте Adobe.

Объкт типа UnitValue определяется следующим образом: var myValue = UnitValue (Значение, “Единица_Измерения”).

Список доступных единиц измерения:

in – inch

ft – foot

yd – yard

mi – mile

mm – millimeter

cm – centimeter

m – meter

km – kilometer

pt – point

pc – pica

tpt – traditional point

tpc – traditional pica

ci – cicero

Например, чтобы определить значение в 15 пунктов используется следующая запись: var mySize = UnitValue (15, “pt”), значение в одну десятую фута определяется как var mySize = UnitValue (.1, “ft”).

Многие свойства и методы объектов в InDesign принимают в качестве значений и параметров объекты типа Unit, например, “10mm”. Это особенно полезно, когда скрипт обрабатывает объекты в документе с определенными пользователем настройками viewPreferences. Возникает вполне здравая мысль использовать в качестве значения таких свойств объект UnitValue. Однако такое действие вызывает ошибку – InDesign отказывается автоматически преобразовывать UnitValue в Unit (это, видимо, два разных типа объектов). Для того чтобы обойти ошибку, достаточно явным образом преобразовать UnitValue в String.

unitValueAsUnit.jsx
Скрипт демонстрирует возможность применения UnitValue в качестве параметра
with (app) {
  var myDoc = documents.add();
  var myRectangle = myDoc.pages[0].rectangles.add();
  var myH = UnitValue (5, "cm");
  var myV = UnitValue (3, "cm");
  var myS = UnitValue (.5, "mm");
  with (myRectangle) {
    geometricBounds = [0,0, String(myH), String(myV)];
    strokeColor = "Black"
    strokeType = "Solid";
    strokeWeight = String(myS);
  }
}

Если известно точно, в какой системе используется значение свойства, можно воспользоваться методом UnitValue.as().

mmAsPt.jsx
Скрипт демонстрирует использование метода as()
with (app) {
  var myDoc = documents.add();
  var myRectangle = myDoc.pages[0].rectangles.add();
  var myS = UnitValue (.5, "mm");
  with (myRectangle) {
    geometricBounds = [0,0,50,30];
    strokeColor = "Black"
    strokeType = "Solid";
    strokeWeight = myS.as("pt");
  }
}

Метод as() преобразует значение UnitValue в указанную в качестве параметра единицу измерения и возвращает получившееся значение. В этом случае явного преобразования в строку не требуется, поскольку метод возвращает значение в виде числа. Значение самого объекта UnitValue при этом не меняется.

Для преобразования UnitValue из одной единицы измерения в другую служит метод convert(), который возвращает true, если преобразование возможно или false – если преобразовать объект не получилось.

mmConvertPt.jsx
Скрипт демонстрирует использование метода convert()
with (app) {
  var myDoc = documents.add();
  var myRectangle = myDoc.pages[0].rectangles.add();
  var myS = UnitValue (.5, "mm");
  myS.convert("pt")
  with (myRectangle) {
    geometricBounds = [0,0,50,30];
    strokeColor = "Black"
    strokeType = "Solid";
    strokeWeight = String(myS);
  }
}

В этом случае при передаче значения переменной myS в качестве значения нужно явно преобразовывать UnitValue в строку, поэтому предпочтительнее все же пользоваться методом as().

Объекты UnitValue в разных единицах измерения можно сравнивать между собой и проводить с ними математические операции.

ptVSmm.jsx
Скрипт сравнивает значения в разных единицах измерения
with (app) {
  var myPoint = UnitValue (74 , "pt");
  var myMM = UnitValue (7, "mm");
  if (myPoint > myMM) {
    alert(myPoint + " " + " больше, чем " + myMM);
  } else {
    alert(myPoint + " " + " меньше, чем " + myMM);
  }
}

При математических операциях производится автоматическое преобразование всех операндов к единице измерения первого из них; результат тоже представлен в таких единицах.

ptPlusMmPlusM.jsx
Скрипт демонстрирует работу математических операций с разными единицами измерения
with (app) {
  var myPoint = UnitValue (74 , "pt");
  var myMM = UnitValue (7, "mm");
  var myMetr = UnitValue (.07, "m");
  alert(myPoint + myMM + myMetr);
}

В этом случае правило «от перемены мест слагаемых сумма не меняется» действует наполовину верно – единица измерения меняется в зависимости от мест слагаемых.

Интерфейс

Единичный случай

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

Вообще, мысль не делать отдельный виджет для каждой единицы измерения – мысль верная. Свойство measurementEditbox.editUnts позволяет задать текущую единицу измерения. Единицы измерения используются те же, что и в document.viewPreferences, поэтому можно их задавать автоматически исходя из настроек документа.

document2measerement.jsx
Скрипт демонстрирует получение настроек единиц измерения из текущего документа
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  with (myDoc.viewPreferences) {
    var unitH = horizontalMeasurementUnits;
    var unitV = verticalMeasurementUnits;
  }
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add().borderPanels.add()) {
    with (dialogColumns.add()) {
     dialogRows.add().staticTexts.add({staticLabel: "Ширина объекта"});
     dialogRows.add().staticTexts.add({staticLabel: "Высота объекта"});  
    }
    with (dialogColumns.add()) {
      var useObjectH = dialogRows.add().measurementEditboxes.add({editUnits: unitH, editValue: 0, minimumValue: 0, maximumValue: 100, smallNudge: 1, largeNudge: .5});
      var useObjectV = dialogRows.add().measurementEditboxes.add({editUnits: unitV, editValue: 0, minimumValue: 0, maximumValue: 100, smallNudge: 1, largeNudge: .5});
    }
  }
  var myResult = myDialog.show();
}

При замечательной идее реализация виджета получилась не ахти. Самой большой проблемой для авторов скриптов обычно является то, что свойство editValue этого виджета всегда определяется в пунктах (значение editUnts по умолчанию – MeasurementUnits.points). Это удобно для получения размера в пунктах, но при получении значения, например, в миллиметрах, напоминает попытку чесать левое ухо из-под правого колена. Но по-другому никак.

getMillimetersValue.jsx
Скрипт демонстрирует работу виджета measurementEditbox с миллиметрами
with (app) {
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add().borderPanels.add()) {
    with (dialogColumns.add()) {
     dialogRows.add().staticTexts.add({staticLabel: "Размер текста"}) 
    }
    with (dialogColumns.add()) {
      var useFontSize = dialogRows.add().measurementEditboxes.add({editUnits: MeasurementUnits.millimeters, editValue: 8, minimumValue: 0, maximumValue: 100, smallNudge: 1, largeNudge: 5});
    }
  }
  var myResult = myDialog.show();
  alert(useFontSize.editValue)
}

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

В InDesign CS такое поведение было существенной проблемой. Частично ее можно было решить, используя editContents в качестве параметра для преобразований (многие свойства допускают использование параметров вида “11mm”), но это вносило в работу скрипта некоторую опасную неопределенность. Проблема максимальных и минимальных значений таким образом не решалась, поскольку эти свойства требуют в качестве параметра только числа и не преобразуют строки автоматически. Все это в совокупности заставляло авторов зачастую использовать другие виджеты для ввода значений в единицах измерения, отличных от пунктов, поскольку иногда решение указанных проблем занимало куда большее количество кода, чем собственно скрипт.

В InDesign CS2 эта проблема осталась, но решить ее значительно проще благодаря использованию объектов UnitValue.

correctUnits.jsx
Скрипт демонстрирует функции для удобной работы с measurementEditbox и measurementCombobox
function correctUnits (myObject) {
  try {
    var myUnits = myObject.editUnits;
  } catch (error) {
    return myObject;
  }
  switch (myUnits) {
    case MeasurementUnits.picas:
      myObject.editValue = UnitValue(myObject.editValue, "pc").as("pt");
      myObject.minimumValue = UnitValue(myObject.minimumValue, "pc").as("pt");
      myObject.maximumValue = UnitValue(myObject.maximumValue, "pc").as("pt");
      break;
    case MeasurementUnits.inches:
    case MeasurementUnits.inchesDecimal:
      myObject.editValue = UnitValue(myObject.editValue, "inch").as("pt");
      myObject.minimumValue = UnitValue(myObject.minimumValue, "inch").as("pt");
      myObject.maximumValue = UnitValue(myObject.maximumValue, "inch").as("pt");
      break;
    case MeasurementUnits.millimeters:
      myObject.editValue = UnitValue(myObject.editValue, "mm").as("pt");
      myObject.minimumValue = UnitValue(myObject.minimumValue, "mm").as("pt");
      myObject.maximumValue = UnitValue(myObject.maximumValue, "mm").as("pt");
      break;
    case MeasurementUnits.centimeters:
      myObject.editValue = UnitValue(myObject.editValue, "cm").as("pt");
      myObject.minimumValue = UnitValue(myObject.minimumValue, "cm").as("pt");
      myObject.maximumValue = UnitValue(myObject.maximumValue, "cm").as("pt");
      break;
    case MeasurementUnits.ciceros:
      myObject.editValue = UnitValue(myObject.editValue, "ci").as("pt");
      myObject.minimumValue = UnitValue(myObject.minimumValue, "ci").as("pt");
      myObject.maximumValue = UnitValue(myObject.maximumValue, "ci").as("pt");
      break;
    default:
      break;
  }
  return myObject;
}
function getValue (myObject) {
  try {
    var myUnits = myObject.editUnits;
  } catch (error) {
    try {
      return myObject.editValue;
    } catch (error) {
      return null;
    }
  }
  switch (myUnits) {
    case MeasurementUnits.picas:
      return UnitValue(myObject.editValue, "pt").as("pc");
      break;
    case MeasurementUnits.inches:
    case MeasurementUnits.inchesDecimal:
      return  UnitValue(myObject.editValue, "pt").as("inch");
      break;
    case MeasurementUnits.millimeters:
      return UnitValue(myObject.editValue, "pt").as("mm");
      break;
    case MeasurementUnits.centimeters:
      return UnitValue(myObject.editValue, "pt").as("cm");
      break;
    case MeasurementUnits.ciceros:
      return UnitValue(myObject.editValue, "pt").as("ci");
      break;
    default:
      return myObject.editValue;
      break;
  }
}
with (app) {
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add().borderPanels.add()) {
    with (dialogColumns.add()) {
     dialogRows.add().staticTexts.add({staticLabel: "Высота объекта"}) 
    }
    with (dialogColumns.add()) {
      with (dialogRows.add()) {
        var useObjectSize = correctUnits(measurementEditboxes.add({editUnits: MeasurementUnits.millimeters, editValue: 8, minimumValue: 0.001, maximumValue: 180, smallNudge: 1, largeNudge: .5}));
      }
    }
  }
  var myResult = myDialog.show();
  alert(getValue(useObjectSize));
}

Функции написаны так, что могут обрабатывать оба виджета единиц измерения. Громоздкими они получились потому, что название единиц измерения, используемых в viewSettings и в editUnits в названия, используемые в определении UnitValue можно перевести только перебором.

Хотелось бы сказать еще об одном странном свойстве виджетов единиц измерения. Свойство largeNudge следует устанавливать равным одной десятой от требуемого. Иначе из-за ошибки модуле поддержки скриптов шаг будет в десять раз больше установленного, что будет неудобно при использовании скрипта. Ошибка в InDesign CS2 перетекла из CS, хотя на форуме Adobe неоднократно была описана.

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

Юникод

При написании скриптов, которые обрабатывают текст в InDesign часто приходится использовать Юникод, поскольку такие символы, как конец абзаца не могут быть указаны в текстовом файле так сказать напрямую. Можно писать скрипты в кодировке UTF-16 и использовать разного вида пробелы и тире прямо в тексте скрипта, но это не всегда удобно. С другой стороны каждый раз сверяться с таблицей Юникода не менее затруднительно. Можно смотреть значение символов в панели Info, но в ней отображается значение только одного символа за раз. Чтобы раз и навсегда решить проблему с поиском юникод-значения любого текста в InDesign был написан метод String.unicode(), который нам неоднократно пригодится.

stringUnicode.jsx
Функция для преобразования текста в Unicode
String.prototype.unicode = function () {
  var myResult = new Array;
  var hexDigits = "0123456789ABCDEF";
  for (var counter = 0; counter < this.length; counter++) {
    if (escape(this[counter]) == this[counter]) {      
      var hexNum = Math.floor(this.charCodeAt(counter) /16);
      var hex = hexDigits[hexNum] + hexDigits[this.charCodeAt(counter) - (hexNum * 16)];
    } else {
      var hex = escape(this[counter]);
      hex = hex.replace("%", "", "gim");
      hex = hex.replace("u", "", "gim");
    }
    if (hex.length == 2) {
      hex = "00" + hex;
    }
    hex = "\\u" + hex;
    myResult.push(hex);
  }
  return myResult;
}

Работа метода основана на использовании встроенной функции escape(), которая преобразует все знаки, не являющиеся буквами английского алфавита и цифрами в специальную escape-последовательность (применяется для передачи данных в строке http-адреса). Чтобы метод мог нормально обрабатывать английские буквы и цифры, пришлось использовать функцию String.charCodeAt(), которая возвращает код ASCII для указанного символа, и вспомнить шестнадцатеричный метод счисления.

promptUnicode.jsx
Скрипт демонстрирует работу с методом unicode()
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myText = selection[0].contents;
  } catch (error) {
    exit();
  }
  prompt ("Unicode-последовательность:", myText.unicode().join(""));
}

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Connecting to %s