Think.JS №11

  • Для начинающих: Практическое применение трансформаций
  • Учим матчасть: Практическая работа с файлами. Часть I
  • Начальное ускорение: Табличные данные
  • Полезный скрипт: Равняйсь! Смирно!

Содержание

Для начинающих: Практическое применение трансформаций

Учим матчасть: Практическая работа с файлами. Часть I

Начальное ускорение: Табличные данные

Полезный скрипт: Равняйсь! Смирно!

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

Практическое применение трансформаций

Теория, как известно, должна подтверждаться практикой. В прошлый раз мы освоили много теоретических знаний о способах трансформации объектов типа pageItem, сейчас займемся наработкой практических навыков.

Наиболее распространенной задачей является приведение двух или более объектов к одному размеру.

resizeSelectedObjects.jsx
Скрипт демонстрирует использование функции для приведения выделенных объектов к одному размеру
function resize (myEtalon, myObject, myPoint) {
  try {
    with (myEtalon) {
      var myEtalonWidth = visibleBounds[2] - visibleBounds[0];
      var myEtalonHeight = visibleBounds[3] - visibleBounds[1];
    }
    with (myObject) {
      var myObjectWidth = visibleBounds[2] - visibleBounds[0];
      var myObjectHeight = visibleBounds[3] - visibleBounds[1];
    }
    var myVertical = myEtalonWidth * 100 / myObjectWidth;
    var myHorizontal = myEtalonHeight * 100 / myObjectHeight;
    myObject.resize(myHorizontal, myVertical, myPoint, true, true, false)
    return true;
  } catch (error) {
    return false;
  }
}
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  if (selection.length < 2) {
    exit();
  }
  var myEtalon = selection[selection.length - 1];
  for (var counter = 0; counter < selection.length - 1; counter++) {
    var myResult = resize(myEtalon, selection[counter], AnchorPoint.centerAnchor);
  }
}

В основной части скрипта проверяется наличие открытого документа и как минимум двух выделенных объектов. Первый объект, выделенный в документе, считается эталонным. Поскольку в объект selection при выделении объекты добавляются в начало, то первым выделенным будет объект selection[selection.length - 1]. В цикле обрабатываются все объекты, кроме эталонного (его тоже можно обработать, но смысла никакого, поскольку масштабирование будет сделано с настройками 100% на 100%).

Основную работу выполняет функция resize(), которая принимает три аргумента: эталонный объект, обрабатываемый объект и точку трансформации.

В первую очередь определяются размеры обоих объектов. Это нужно для того, чтобы узнать отношение размеров изменяемого объекта к размерам объекта эталонного. При чем за 100% принимаются текущие размеры измеряемого документа. Для получения размеров используется пропорция (изучали во втором классе школы по математике)
КонечныйРазмер * 100 / ИсходныйРазмер. При необходимости можно модифицировать функцию так, чтобы объекты приводились не к 100% размеров эталонного объекта, а к указанному, например к половине.

Интересный эффект дает использование метода resize() совместно с методом duplicate(). Последний дублирует объект в указанной в качестве параметра точке (при отсутствии параметра – в том же месте, что и объект).

resizeAndDuplicate.jsx
Скрипт создает копии равномерно уменьшающегося объекта
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  if (selection.length != 1) {
    exit();
  }
  try {
    for (var counter = 0; counter < 9; counter++) {
      selection[0].duplicate();
      selection[0].resize(90, 90, AnchorPoint.topLeftAnchor, true, true, false);
    }
  } catch (error) {
    alert(error)
  }
}

С каждым выполнением цикла объект уменьшается на 10% по отношению к текущим размерам и дублируется. Метод duplicate() возвращает созданный при дублировании объект, поэтому можно использовать разную окраску объектов для создания красивых эффектов. Если использовать объекты с заливкой None, то можно получить эффект, имитирующий отображение трехмерных примитивов в виде wireframe. Можно использовать различные точки трансформации для различных углов наклона объекта. Непропорциональное масштабирование тоже создает интересный эффект.

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

resizeStep.jsx
Скрипт демонстрирует создание циклически уменьшающихся объектов с постоянным линейным шагом
function resizeStep(myEtalon, mySize, myPercent) {
  return myEtalon * myPercent / mySize;
}
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  if (selection.length != 1) {
    exit();
  }
  try {
    with (selection[0]) {
      var myWidth = visibleBounds[3] - visibleBounds[1];
      var myHeight = visibleBounds[2] - visibleBounds[0];
    }
    for (var counter = 0; counter < 9; counter++) {
      with (selection[0]) {
        duplicate();
        var objWidth = visibleBounds[3] - visibleBounds[1];
        var objHeight = visibleBounds[2] - visibleBounds[0];
        resize(resizeStep(myWidth, objWidth, (100 - 10*(counter + 1))), resizeStep(myHeight, objHeight, (100 - 10*(counter + 1))), AnchorPoint.centerAnchor, true, true, false);
      }
    }
  } catch (error) {
    alert(error)
  }
}

Не менее интересно использовать метод duplicate() совместно с rotate(). В этом случае эффекты бывают очень впечатляющими.

stepAndRotate.jsx
Скрипт демонстрирует циклический поворот объекта
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  if (selection.length != 1) {
    exit();
  }
  try {
    for (var counter = 1; counter < 18; counter++) {
      selection[0].duplicate();
      selection[0].rotate(5* counter, AnchorPoint.centerAnchor, false, true, false);
    }
  } catch (error) {
    alert(error);
  }
}

Если совместить все основные трансформации, можно добиться эффекта динамики.

allTransform.jsx
Скрипт демонстрирует совместное использование трансформаций
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  if (selection.length != 1) {
    exit();
  }
  var myObject = selection[0]; 
  try {
    for (var counter = 1; counter < 19; counter++) {
      myObject = myObject.duplicate();
      myObject.move(undefined, [5, 0]);
      myObject.resize(90, 90, AnchorPoint.centerAnchor, true, true, false);
      myObject.rotate(5* counter, AnchorPoint.centerAnchor, false, true, false);
    }
  } catch (error) {
    alert(error);
  }
}

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

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

Практическая работа с файлами. Часть I

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

Для первого опыта напишем скрипт, который импортировал бы csv-файлы и создавал из них таблицы. Чем удобны csv? Тем, что для чтения не требуется какой-либо специальный фильтр, как, например, для файлов в формате xls. В то же время получить csv можно практически из любой электронной таблицы. И, что так же немаловажно, сохранив данные из InDesign в csv, их можно прочитать при помощи того же MS Excel.

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

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

Логически эта задача делится на три подзадачи.

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

Второе: предложить пользователю выбрать файл нужного типа и прочитать его.

Третье: сформировать таблицу на основе полученных данных.

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

importCSV.jsx
Скрипт для импорта данных из файла в формате csv и помещения их в таблицу
function readFile () {
  var myResult = [];
  try {
    var myFile = Folder.myDocuments.openDlg("Выберите файл:", "CSV (разделители - запятые): *.csv");
    with (myFile) {
      open("r");
      while (!eof) {
        var myString = readln();
        if (myString != "") {
          var myArr = myString.split(";");
          myResult.push(myArr);
        }
      }
      close();
      return myResult;
    }
  } catch (error) {
    return null;
  }  
}
function addTable(myPoint, myData) {
  try {
    var myRowLength = myData.length;
    var myColLength = myData[0].length;
    var myContents = [];
    for (var counter = 0; counter < myData.length; counter++) {
      myContents = myContents.concat(myData[counter])
    }
    var myTable = myPoint.tables.add({bodyRowCount: myRowLength, columnCount: myColLength, contents: myContents});
  } catch (error) {
      return null;
  }
}
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myContent = selection[0].contents;
  } catch (error) {
    exit();
  }
  if (selection[0].constructor.name == "InsertionPoint") {
    var myPoint = selection[0];
  } else {
    var myPoint = selection[0].insertionPoints.firstItem();
  }
  var myData = readFile();
  if (myData != null) {
   var myTable = addTable(myPoint, myData); 
  }
}

Разберем работу скрипта подробнее. Проверка наличия открытого документа и выбранного текстового объекта проводится стандартно. Поскольку таблица всегда помещается в объект типа insertionPoint, то следует найти эту точку вставки. Если пользователь выбрал текстовый объект другого типа, то точкой вставки будет последняя точка вставки этого объекта.

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

Функция addTable() получает в качестве параметра точку вставки и массив, возвращенный функцией readFile(). Для определения параметров таблицы используются размеры массива – количество строк соответствует количеству элементов, а количество столбцов – числу элементов в каждом из элементов. Затем многомерный массив путем объединения преобразовывается в массив одномерный. Делается это потому, что свойству contents таблицы можно присвоить значение массива строк, сэкономив время на обработке каждой ячейки отдельно. Но для правильного отображения таблицы нужно сначала указать ее размерность. Именно поэтому для хранения прочитанных данных был выбран многомерный массив. Функция возвращает таблицу, которая может быть обработана при помощи любых других функций.

Начальное ускорение

Табличные данные

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

Главным камнем преткновения является большое количество ячеек в таблице. Неопытный программист, скорее всего, пойдет по самому простому пути – перебору всех ячеек с последовательной обработкой каждой из них. Более опытный может заметить, что строки (row) и столбцы (column) имеют во многом схожие свойства, поэтому таблицу можно обработать построчно или по отдельным столбцам. Умело манипулируя строками и столбцами попеременно, он добьется значительного ускорения, но такие скрипты всякий раз будут выполнять массу ненужных операций. Чтобы избежать повторной обработки некоторых ячеек и ускорить выполнение скрипта, нужно воспользоваться одним интересным свойством таблицы.

Вручную пользователь может выделить и обработать некую произвольную прямоугольную область таблицы. Возможно ли это сделать скриптом? Даже нужно! Причем для обработки такой области выделять ее вовсе не обязательно, достаточно воспользоваться методом cells.itemByRange(). Причем в качестве параметров этому методу могут передаваться не только индексы ячеек, но и сами ячейки. При этом метод cells.itemByRange() возвращает объект… cell. Независимо от того, сколько ячеек в него входит, такой объект обрабатывается как единая ячейка.

cellRange.jsx
Скрипт демонстрирует использование диапазона ячеек при обработке таблиц
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myContent = selection[0].contents;
  } catch (error) {
    exit();
  }
  if (selection[0].constructor.name == "InsertionPoint") {
    var myPoint = selection[0];
  } else {
    var myPoint = selection[0].insertionPoints.lastItem();
  }
  var myTable = myPoint.tables.add({bodyRowCount: 6, columnCount: 6});
  with (myTable) {
    var cellRange = cells.itemByRange(columns[1].cells[1], columns[4].cells[3]);
  }
  with (cellRange) {
    fillColor = "Black";
    fillTint = 10;
  }
}

Умело оперируя диапазонами, можно быстро обработать большую таблицу, не затрачивая время на выполнение излишних операций.

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

Равняйсь! Смирно!

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

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

Задача стоит так: выровнять строки, не используя при этом базовые линии документа InDesign. Вместо этого можно создать свои базовые линии, относительно которых выровнять текстовые фреймы. Причем «физическое» воплощение этих базовых линий в документе нам абсолютно не нужно, достаточно знать координаты каждой из них. Чтобы получить эти координаты, нам нужно всего два значения: координаты одной из базовых линий и шаг между ними.

align2Line.jsx
Скрипт выравнивает выделенные текстовые фреймы, имитируя использование базовых линий
function pseudoBaselines (myTextFrame) {
  try {
    var myResult = [];
    var myStart = myTextFrame.lines.lastItem().baseline;
    var myStep = myStart - myTextFrame.lines.previousItem(myTextFrame.lines.lastItem()).baseline;
    with (myTextFrame.parent) {
      var myTop = [];
      var myCurrent = myStart;
      while (myCurrent > bounds[0]) {
        myCurrent -= myStep;
        myTop.push(myCurrent);
      }
      myTop.reverse();
      var myBottom = [myStart];
      var myCurrent = myStart;
      while (myCurrent < bounds[2]) {
        myCurrent += myStep;
        myBottom.push(myCurrent);
      }
      myResult = myTop.concat(myBottom);
      return myResult;
    }
  } catch (error) {
    return null;
  }
}
function align (myBaselines, myTextFrame) {
  var myCurrent = myTextFrame.lines.lastItem().baseline;
  for (var counter = 0; counter < myBaselines.length; counter++) {
    if (myBaselines[counter] > myCurrent) {
      try {
        var myTop = myBaselines[counter - 1];
        var myBottom = myBaselines[counter];
        if (myCurrent - myTop <= myBottom - myCurrent) {
          var myStep = myTop - myCurrent;
        } else {
          var myStep = myBottom - myCurrent;
        }
      } catch (error) {
        var myStep = myBaselines[counter] - myCurrent;
      }
      myTextFrame.move(undefined, [0, myStep]);
      break;
    }
  }
}
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  var myTF = [];
  for (var counter = 0; counter < selection.length; counter++) {
    if (selection[counter].constructor.name == "TextFrame") {
      myTF.push(selection[counter]);
    }
  }
  if (myTF.length < 2) {
    exit();
  }
  myTF.reverse();
  var myBaselines = pseudoBaselines(myTF[0]);
  if (myBaselines == null) {
    exit();
  }
  for (var counter = 1; counter < myTF.length; counter++) {
    align(myBaselines, myTF[counter]);
  }
}

В двух словах о работе скрипта. При запуске скрипт анализирует выделенные пользователем объекты и собирает все тестовые фреймы в массив. Если выделено более одного фрейма, то скрипт на основе первого выделенного фрейма создает массив виртуальных базовых линий, используя функцию pseudoBaselines(). Кстати, эта версия скрипта имеет некоторое ограничение: если в эталонном текстовом фрейме всего одна строка, выравнивание не произойдет. Дело в том, что скрипт вычисляет шаг между базовыми линиями по расстоянию между последней и предпоследней строками эталонного текстового фрейма. После вычисления создаются два массива: myTop (для всех значений выше базовой линии последней строки эталонного текстового фрейма до границы страницы) и myBottom (для значений от базовой линии последней строки включительно вниз до границы страницы). Функция в нормальном состоянии возвращает объединенный массив, в случае ошибки – null. При нормальном выполнении полученный массив передается в функцию align(), которая выполняется для каждого текстового фрейма, кроме эталонного. В этой функции вычисляются ближайшие к базовой линии последней строки значения виртуальных базовых линий. При этом сравнивается, к какой из них базовая линия строки ближе – если к верхней, то фрейм перемещается вверх, если к нижней – то вниз.


Олег Бутрин
THINK.JS выпуск № 11 от 2007-01-15

 

Оставьте комментарий