Think.JS №15

  • Для начинающих: Копии к бою
  • Начальное ускорение: Очень быстрый поиск текста
  • Учим матчасть: Статистика знает все
  • Полезный скрипт: Равенство и братство

Содержание

Для начинающих: Копии к бою

Начальное ускорение: Очень быстрый поиск текста

Учим матчасть: Статистика знает все

Полезный скрипт: Равенство и братство

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

Копии к бою

Штатный поиск с заменой в текстовых объектах при всей своей полезности не умеет делать одной очень важной вещи – замены найденного текста на содержимое буфера обмена. Без использования скриптов эта проблема вообще не решается, а скритами – легко и просто.

Для копирования и вставки в InDesign предусмотрены методы: copy (скопировать), cut (вырезать), paste (вставить), pasteInPlace (вставить с сохранением позиции), pasteInto (вставить внутрь), pasteWithoutFormatting (вставить без форматирования). Все эти методы определены для объекта app, при этом у них отсутствуют параметры и возвращаемые значения. Последнее условие не очень удобно, поскольку после вставки объектов бывает полезно получить ссылку на вставленный объект. При работе с объектами типа pageItem, получить объект достаточно просто – вставленный объект (или объекты) выделяются в документе, поэтому достаточно обработать selection. При работе с текстовыми объектами как правило выделяется последняя точка вставки (insertionPoint) вставленного объекта, поэтому полностью получить вставленный текст можно при помощи некоторых манипуляций, которые выполняются до вставки.

Как уже было сказано, методы copy() и cut() не имеют параметров, а работают исключительно с объектом selection активного документа. То есть, конструкция вида copy(myTextFrame) не приведет к копированию указанного текстового фрейма. Более того, если в активном документе ничего не выделено, то подобный вызов приведет к ошибке и аварийному завершению скрипта. Перед копированием необходимо выделить нужный объект (или объекты), сняв выделение со всех остальных. Для вставки наличие выделенных объектов как правило некритично, исключая метод pasteInto().

cut&Paste.jsx
Скрипт демонстрирует перенос объекта с одной страницы на другую методом cut-paste
with (app) {
  var myDoc = documents.add();
// Создаем документ
  var myRectangle = myDoc.pages[0].rectangles.add({visibleBounds: [10,10, 60,60]});
// На первой странице создаем прямоугольник с указанными размерами
  myRectangle.select();
// Выделяем прямоугольник
  cut();
// Вырезаем выделенное
  with (myDoc) {
// Для документа
    var myPage = pages.add();
// Добавляем страницу
    activeWindow.activePage = myPage;
// Указываем, что добавленная страница будет активной в текущем окне документа
  }
  paste();
// Вставляем скопированный объект
  var myRectangle = selection[0];
// Получаем вставленный объект из selection
  alert(myRectangle.visibleBounds)
// Сообщаем его visibleBounds
}

Если объект вставляется на ту же страницу, с которой он был скопирован или вырезан, то, как правило, он помещается не в тоже самое место, с которого был скопирован, а со смещением вниз и вправо, так же, как при обычной работе с copy-paste. Чтобы поместить вырезанный или скопированный объект в то же место, следует воспользоваться методом pasteInPlace(). Но при использовании этого метода, например, при переносе объектов с одной полосы на другую следует учитывать, что положение объекта после вставки зависит от положения конечной страницы в развороте. То есть, объект, скопированный с четной страницы после вставки методом pasteInPlace() будет помещен а четную страницу разворота или на рабочий стол, если такой страницы в развороте нет.

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

cut&PasteInto().jsx
Скрипт демонстрирует использование метода pasteInto()
with (app) {
  var myDoc = documents.add();
// Создаем документ
  var myRectangle = myDoc.pages[0].rectangles.add({visibleBounds: [10,10, 60,60]});
// На первой странице создаем прямоугольник с указанными размерами
  myRectangle.select();
// Выделяем прямоугольник
  cut();
// Вырезаем выделенное
  with (myDoc) {
// Для документа
    var myPage = pages.add();
// Добавляем страницу
    activeWindow.activePage = myPage;
// Указываем, что добавленная страница будет активной в текущем окне документа
  }
  var myOval = myPage.ovals.add({visibleBounds: [20,20,90,90]});
// Создаем овал
  myOval.select();
// Выделяем его
  pasteInto();
// Вставляем в овал скопированный прямоугольник
  var myRectangle = selection[0].rectangles.lastItem();
// Получаем вставленный объект из коллекции  rectangles овала
  alert(myRectangle.visibleBounds)
// Сообщаем его visibleBounds
}

Метод pasteWithoutFormatting() имеет смысл применять только при переносе текста без форматирования из одного текстового объекта в другой. В этом случае происходит то же, что и при редактировании свойства contents у объекта, куда вставляется текст. Если вставляется объект типа pageItem, то этот метод действует также как и paste().

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

replaceText.jsx
Скрипт демонстрирует процесс замены текста на содержимое буфера обмена
with (app) {
  var myDoc = documents.add();
// Создаем документ
  var myRectangle = myDoc.pages[0].rectangles.add({visibleBounds: [10,10, 12,12], fillColor: "Black", fillTint: 100});
// На первой странице создаем прямоугольник
  myRectangle.select();
// Выделяем прямоугольник
  cut();
  var myTextFrame = myDoc.pages[0].textFrames.add({visibleBounds: [10,10,60,60]});
// Создаем текстовый фрейм
  myTextFrame.parentStory.contents = "Буря мглою небо кроет, вихри снежные крутя. То как зверь она завоет, то заплачет, как дитя.";
// Заполняем текстовый фрейм
  findPreferences = null;
  changePreferences = null;
// Обнуляем параметры поиска-замены
  var myResult = myTextFrame.parentStory.search(".", false, false);
// Ищем в тексте знак точки
  myResult.reverse();
// Разворачиваем массив результатов поиска
  for (var counter = 0; counter < myResult.length; counter++) {
// Каждый найденный текст выделяем
    myResult[counter].select();
    paste();
// И вызываем метод paste()
  }
}

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

Очень быстрый поиск текста

Метод search() имеет одну не очень приятную особенность – выполняется довольно медленно. Если нужно искать что-то сложное или простое, но много раз, то скрипт начинает заметно притормаживать. Иногда торможение переходит в ступор. В таких случаях нужно искать альтернативы.

Язык JavaScript имеет достаточно мощные средства работы со строковыми переменными. Одновременно эти средства значительно быстрее, чем методы обработки текстовых объектов в InDesign. С другой стороны, средства работы со строками могут обработать только свойство contents тестового объекта, но зато очень быстро. Если текст заведомо не отформатирован, то использовать стандартные методы обработки строк – одно удовольствие.

useStringReplace.jsx
Скрипт обрабатывает текстовый объект через свойство contents
with (app) {
  try {
    var myDoc = activeDocument;
// Получаем активный документ
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
// Получаем выбранный текст    
  } catch (error) {
    exit();
  }
  var myText = myStory.contents;
// Определяем текстовую переменную для обработки
  myText = myText.replace("\u201C", "\u00AB", "g");
// Заменяем левую кавычку-"лапочку" на кавычку-"елочку"
  myText = myText.replace("\u201D", "\u00BB", "g");
// Заменяем правую кавычку-"лапочку" на кавычку-"елочку"
  myStory.contents = myText;
// Передаем текстовую переменную в свойство contents
}

Но если текстовый объект был отформатирован, то после такой обработки форматирование наверняка «поплывет». Еще хуже будет, если обрабатываемый текстовый объект содержит таблицы или inline-объекты – они попросту пропадут. Это совсем не дело, поэтому мы пойдем другим путем.

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

Объект типа string в JavaScript имеет метод indexOf(), который возвращает индекс первого найденного вхождения строковой переменной, указанной в качестве параметра. Например, "Think.JS".indexOf("JS") возвратит значение 6, то есть индекс первого знака подстроки «JS» в строке «Think.JS» (отсчет индексов начинается с 0). Метод indexOf() определен в виде нативной функции и выполняется очень быстро по сравнению со штатными методами поиска. Получив с его помощью индекс искомой подстроки в тексте, можно определить объект типа text и обработать его.

formatText.jsx
Скрипт форматирует текст в абзаце от начала до указанного специального символа
with (app) {
// Определяем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Определяем выделенный текст
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
// Определяем специальный символ
  var mySymbol = "-";
// Для каждого абзаца выделенного текста в цикле проводим обработку  
  for (var counter = 0; counter < myStory.paragraphs.length; counter++) {
// Определяем текущий абзац
    var myParagraph = myStory.paragraphs[counter];
// Определяем индекс знака, находящегося до (потому - 1) первого найденного специального символа
    var myIndex = myParagraph.contents.indexOf(mySymbol) - 1;
// Если индекс не равен -1
    if (myIndex != -1) {
// Определяем и обрабатываем текст      
      var myText = myParagraph.characters.itemByRange(0, myIndex);
      myText.fontStyle = "Bold";
    }
  }
}

Этот вариант скрипта обрабатывает текст до указанного специального символа. Чтобы определить текст вместе со специальным символом (или группы символов), нужно модифицировать строку «var myIndex = myParagraph.contents.indexOf(mySymbol) - 1;» заменив на «var myIndex = myParagraph.contents.indexOf(mySymbol) + mySymbol.length - 1;».

Для решения задачи по получению текста от последнего специального символа до конца абзаца можно использовать метод lastIndexOf() объекта типа string. Работает этот метод аналагично indexOf(), но возвращает индекс последнего вхождения искомой подстроки.

findLastText.jsx
Скрипт форматирует текст от указанного спецсимвола до конца абзаца
with (app) {
// Определяем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Определяем выделенный текст
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
// Определяем специальный символ
  var mySymbol = "-";
// Для каждого абзаца выделенного текста в цикле проводим обработку  
  for (var counter = 0; counter < myStory.paragraphs.length; counter++) {
// Определяем текущий абзац
    var myParagraph = myStory.paragraphs[counter];
// Определяем индекс знака, находящегося после последнего символа найденной подстроки
    var myIndex = myParagraph.contents.lastIndexOf(mySymbol) + mySymbol.length;
// Если индекс не равен -1
    if (myIndex != -1) {
// Определяем и обрабатываем текст      
      var myText = myParagraph.characters.itemByRange(myIndex, myParagraph.characters.length - 1);
      myText.fontStyle = "Bold";
    }
  }
}

Если спецсимвол тоже должен быть выделен, то заменяем строку «var myIndex = myParagraph.contents.lastIndexOf(mySymbol) + mySymbol.length;» на «var myIndex = myParagraph.contents.lastIndexOf(mySymbol)».

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

Статистика знает все

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

Свойства и методы, определенные в InDesign для объекта типа File, можно назвать даже избыточными. Их вполне хватило бы для написания несложного файлового менеджера, если бы была возможность сделать приемлемый интерфейс.

Например, одним очень полезным методом – execute() – мы уже пользовались неоднократно. Напомню, что этот метод позволяет выполнить файл, для которого он вызван в приложении, с которым ассоциирован тип файла. Это очень полезно в том случае, если скрипт ведет протоколы работы, которые по окончании работы будут показаны пользователю. Если протоколы сохранять в обычный текстовый файл с расширением txt, то после вызова метода execute() файл будет открыт в соответствующем приложении (а приложение для текстовых файлов есть практически в любой системе).

executeFile.jsx
Скрипт демонстрирует возможность записи протоколов
with (app) {
// Определяем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Определяем выделенный текст
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
// Определяем текстовую переменную и помещаем в нее информацию о количестве  абзацев, слов и знаков
  var myString = "Абзацев: " + String(myStory.paragraphs.length) +  "\n";
  myString += "Слов: " + String(myStory.words.length) +  "\n";
  myString += "Знаков: " + String(myStory.characters.length) +  "\n";
// Определяем файл
  var myFile = File(activeScript.path + "/storyInfo.txt");
  with (myFile) {
// Открываем файл, записываем строку, закрываем файл и выполняем его
    open("w");
    write(myString);
    close();
    execute();
  }
}

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

move2Temp.jsx
Скрипт перемещает выбранный пользователем файл в папку temp
with (app) {
// Предлагаем пользователю выбрать файл
  var myFile = Folder.myDocuments.openDlg();
  if (!myFile) {
    exit();
  } else {
// Если файл выбран, то определяем новый файл в папке temp
    var myNFile = File(Folder.temp + "/" + myFile.name);
// Копируем файл
    myFile.copy(myNFile);
// Удаляем файл
    myFile.remove();
// Открываем папку temp
    Folder.temp.execute();
  }
}

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

hideFile.jsx
Скрипт «скрывает» файл
with (app) {
// Определяем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Определяем выделенный текст
  try {
    var myStory = selection[0].parentStory;
  } catch (error) {
    exit();
  }
// Определяем текстовую переменную и помещаем в нее информацию о количестве  абзацев, слов и знаков
  var myString = "Абзацев: " + String(myStory.paragraphs.length) +  "\n";
  myString += "Слов: " + String(myStory.words.length) +  "\n";
  myString += "Знаков: " + String(myStory.characters.length) +  "\n";
// Определяем файл
  var myFile = File(activeScript.path + "/storyInfo.txt");
  with (myFile) {
// Открываем файл, записываем строку, закрываем файл и выполняем его
    open("w");
    write(myString);
    close();
// Устанавливаем параметр "Скрытый"    
    hidden = true;
  }
// Открываем папку, содержащую файл
  myFile.parent.execute();
}

Объект типа File имеет специальные свойства, по которым можно определить его размер, дату создания и дату модификации. Эти свойства могут пригодиться для сравнения времени создания файлов и в прочих подобных ситуациях. Например, для статистики.

fileStatistics.jsx
Скрипт создает текстовый файл с информацией о файлах в выбранной пользователем папке
with (app) {
// Предлагаем пользователю выбрать папку
  var myFolder = Folder.myDocuments.selectDlg();
  if (!myFolder) {
    exit();
  }
// Определяем переменную для сохранения протокола работы  
  var myString = "Папка "+ String(myFolder) + "\n\n";
// Получаем все файлы из выбранной папки
  var myFiles = myFolder.getFiles();
// Обрабатываем в цикле все файлы  
  for (var counter = 0; counter < myFiles.length; counter++) {
// Если объект является файлом
    if (myFiles[counter].constructor.name == "File") {
// Определяем его свойства
      var myFile = myFiles[counter];
// Декодированное имя
      myString += "Имя файла: " + decodeURI(myFile.name) + "\n";
// Размер в байтах
      myString += "Размер файла: " + myFile.length + " байтов\n";
// Дата создания (сконвертированная в строку)
      myString += "Дата создания: " + myFile.created.toString() + "\n";
// Дата изменения (сконвертированная в строку)
      myString += "Дата изменения: " + myFile.modified.toString() + "\n\n";
    }
  }
// Определяем имя файла
  var myFile = File(activeScript.path + "/folderInfo.txt");
  with (myFile) {
// Открываем файл, записываем строку, закрываем файл и выполняем его
    open("w");
    write(myString);
    close();
    execute();
  }
}

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

Равенство и братство

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

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

frameLines.jsx
Скрипт для выравнивания количества строк в выделенных фреймах
with (app) {
// Определяем активный документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Анализируем объект selection для получения текстовых фреймов
  var myTextFrames = [];
  for (var counter = 0; counter < selection.length; counter++) {
    if (selection[counter].constructor.name == "TextFrame") {
      myTextFrames.push(selection[counter]);
    }
  }
// Если текстовых фреймов меньше двух, то выходим
  if (myTextFrames.length < 2) {
    exit();
  }
// Проверяем принадлежность фреймов к одной статье
  var myStory = myTextFrames[0].parentStory;
  for (var counter = 1; counter < myTextFrames.length; counter++) {
    if (myTextFrames[counter].parentStory != myStory) {
      exit();
    }
  }
// Получаем суммарное количество строк
  var myLines = 0;
  for (var counter = 0; counter < myTextFrames.length; counter++) {
    myLines += myTextFrames[counter].lines.length;
  }
// Если количество строк не делится на количество фреймов, добавляем нужные
  myLines += myLines % myTextFrames.length;
// Получаем количество строк в каждом фрейме  
  var myLimit = myLines / myTextFrames.length;
// Получаем размер одной строки
  var lineSize = myStory.lines[1].baseline - myStory.lines[0].baseline;
// Получаем размер каждого фрейма
  var mySize = myLimit * lineSize;
// Для каждого фрейма  
  for (var counter = 0; counter < myTextFrames.length; counter++) {
// Меняем размер каждого фрейма
    var myBounds = myTextFrames[counter].geometricBounds;
    myTextFrames[counter].geometricBounds = [myBounds[0], myBounds[1], myBounds[0] + mySize, myBounds[3]];
  }
}

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

 

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Connecting to %s