Think.JS №14

  • Для начинающих: Поиск с параметрами. Часть II
  • Полевые испытания: Место встречи отменить нельзя
  • Учим матчасть: Постраничный экспорт в pdf
  • Полезный скрипт: Кто старое помянет

Содержание

Для начинающих: Поиск с параметрами. Часть II

Полевые испытания: Место встречи отменить нельзя

Учим матчасть: Постраничный экспорт в pdf

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

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

Поиск с параметрами. Часть II

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

Чтобы заменить текст любого стиля на другой, его нужно указать в качестве параметра свойства changePreferences.fontStyle, например changePreferences.fontStyle = "Bold".

changeFontStyle.jsx
Скрипт демонстрирует замену любого стиля на стиль Bold
with (app) {
  try {
    var myDoc = activeDocument;
// Получаем активный документ
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
// Получаем выбранный текст    
  } catch (error) {
    exit();
  }
  findPreferences = null;
  changePreferences = null;
// Обнуляем настройки поиска-замены
  changePreferences.fontStyle = "Bold";
// Указываем стиль шрифта в замене  
  myStory.search("a", false, true, "a");
// Заменяем текст
  findPreferences = null;
  changePreferences = null;  
// Обнуляем настройки поиска-замены
}

Очень важным преимуществом при поиске и замене является возможность комбинирования свойств настроек поиска-замены («Здесь еще важна такая вещь, как комбинаторность» © «Служебный роман»). Например, чтобы заменить стиль у текста, отформатированного только определенным стилем абзаца, нужно воспользоваться свойствами поиска-замены совместно.

changeFontStyleParagraphStyle.jsx
Скрипт заменяет только текст, отформатированный указанным стилем абзаца.
with (app) {
  try {
    var myDoc = activeDocument;
// Получаем активный документ
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
// Получаем выбранный текст    
  } catch (error) {
    exit();
  }
  findPreferences = null;
  changePreferences = null;
// Обнуляем настройки поиска-замены
  findPreferences.appliedParagraphStyle = myDoc.paragraphStyles[1];
// Указываем стиль абзаца для поиска  
  changePreferences.fontStyle = "Bold";
// Указываем стиль шрифта в замене  
  myStory.search("a", false, true, "a");
// Заменяем текст
  findPreferences = null;
  changePreferences = null;  
// Обнуляем настройки поиска-замены
}

Очень часто бывает, что нужно изменить размер шрифта только у текстов, отформатированных определенным стилем абзаца. Делается это опять-таки при помощи комбинирования с использованием свойства changePreferences.pointSize.

changePointSize.jsx
Скрипт меняет размер шрифта у текста, отформатированного указанным стилем
with (app) {
  try {
    var myDoc = activeDocument;
// Получаем активный документ
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
// Получаем выбранный текст    
  } catch (error) {
    exit();
  }
  findPreferences = null;
  changePreferences = null;
// Обнуляем настройки поиска-замены
  findPreferences.appliedParagraphStyle = myDoc.paragraphStyles[1];
// Указываем стиль абзаца для поиска  
  changePreferences.pointSize = 8;
// Указываем размер текста в замене  
  myStory.search("", false, true, "");
// Заменяем любой текст
  findPreferences = null;
  changePreferences = null;  
// Обнуляем настройки поиска-замены
}

Таким же образом можно поменять один стиль абзаца на другой.

changeParagraphStyle.jsx
Скрипт заменяет указанный стиль абзаца на другой
with (app) {
  try {
    var myDoc = activeDocument;
// Получаем активный документ
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
// Получаем выбранный текст    
  } catch (error) {
    exit();
  }
  findPreferences = null;
  changePreferences = null;
// Обнуляем настройки поиска-замены
  findPreferences.appliedParagraphStyle = myDoc.paragraphStyles[1];
// Указываем стиль абзаца для поиска  
  changePreferences.appliedParagraphStyle = myDoc.paragraphStyles[2];
// Указываем стиль абзаца для замены  
  myStory.search("", false, true, "");
// Заменяем любой текст
  findPreferences = null;
  changePreferences = null;  
// Обнуляем настройки поиска-замены
}

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

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

specialWordToTable.jsx
Скрипт для форматирования текста на основе специальных слов и преобразования его в таблицу
with (app) {
  try {
    var myDoc = activeDocument;
// Получаем активный документ
  } catch (error) {
    exit();
  }
  try {
    var myStory = selection[0].parentStory;
// Получаем выбранный текст    
  } catch (error) {
    exit();
  }
  try {
    var myPStyle = myDoc.paragraphStyles.add({name: "TableBold"});
// Пробуем создать стиль и настраиваем его
    with (myPStyle) {
      pointSize = 7;
      fontStyle = "Bold";
    }
  } catch (error) {
    var myPStyle = myDoc.paragraphStyles.item("TableBold");
// Если стиль создать не удалось, значит он уже существует
  }
  findPreferences = null;
  changePreferences = null;
// Обнуляем настройки поиска-замены
  changePreferences.appliedParagraphStyle = myPStyle;
// Указываем стиль абзаца для замены  
  myStory.search("[BOLD] ", false, false, "");
// Заменяем стиль у абзацев, содержащих специально слово  
  changePreferences = null;
// Обнуляем настройки замены  
  var myResult = myStory.search("[BOLD] ", false, false);
// Повторный поиск для получения специальных слов для последующей обработки  
  myResult.reverse();
// Разворачиваем результат поиска
  for (var counter = 0; counter < myResult.length; counter++) {
// Для каждого найденного специального слова
    var myParagraph = myResult[counter].paragraphs[0];
// Определяем родительский абзац    
    myResult[counter].remove();
// Удаляем само слово    
    with (myParagraph.insertionPoints) {
// Определяем текст, который нужно сконвертировать в таблицу (без знака конца абзаца)
      var myText = itemByRange(firstItem(), previousItem(lastItem()));
    }
    myText.convertToTable();
// Конвертируем текст в таблицу 
  }
  findPreferences = null;
  changePreferences = null;  
// Обнуляем настройки поиска-замены
}

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

Место встречи отменить нельзя

Не всегда скрипт делает то, что должно сохраниться в документе. Иногда некоторые действия должны быть отменены. Примеров тому масса, самый простой из них мы сегодня разберем, заодно ознакомившись с работой метода undo().

Как уже было сказано, линейные размеры объекта типа pageItem определяются при помощи свойств visibleBounds и geometricBounds. Вычислить длину и ширину легко, но только в том случае, если объект не был перед измерением пользователем повернут. Даже при незначительном повороте для получения реальных размеров объекта придется вспоминать школьный курс геометрии, в частности то, что квадрат гипотенузы равен сумме квадратов катетов. Положение осложняется тем, что в свойствах bounds хранятся координаты всего двух точек, что означает, что для получения координат оставшихся двух точек тоже придется проводить некоторые математические вычисления. Разве это путь для настоящих скриптописателей? Конечно, нет! Чтобы не осложнять себе работу, можно просто развернуть измеряемый объект так, чтобы реальный угол его поворота стал равен нулю, измерить его координаты в таком виде, а затем снова развернуть его на тот же угол поворота. Однако, это не совсем корректный путь, поскольку документ во время работы скрипта изменяется. Гораздо правильнее после поворота и измерения размеров воспользоваться методом undo() объекта типа document.

realSize.jsx
Скрипт сообщает реальные размеры объекта при любом значении угла поворота
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  try {
    var myObject = selection[0];
    var chkBounds = myObject.visibleBounds;
  } catch (error) {
    exit();
  }
  if (myObject.absoluteRotationAngle == 0) {
    var myWidth = myObject.visibleBounds[3] - myObject.visibleBounds[1];
    var myHeight = myObject.visibleBounds[2] - myObject.visibleBounds[0];
  } else {
    myObject.rotate (0);
    var myWidth = myObject.visibleBounds[3] - myObject.visibleBounds[1];
    var myHeight = myObject.visibleBounds[2] - myObject.visibleBounds[0];    
    myDoc.undo();
  }
  alert("Длина: " + String(myWidth) + ", ширина: " + String(myHeight));
}

В результате мы гарантированно получаем реальные размеры выбранного объекта и оставляем документ в том же виде, как и до выполнения скрипта. Это значит, что пользователь, буде ему вздумается отменить какое-то действие, которое он выполнил до запуска скрипта, не будет неприятно удивлен, когда для отмены выполненного действия ему придется нажать Control + Z не один, а много раз.

В подобной ситуации можно воспользоваться и другим методом: сохранить документ при помощи метода save(), а потом загрузить сохраненную копию. Но в этом случае скорость работы скрипта будет заметно ниже. Кроме того, иногда пользователи работают с документами, которые еще ни разу не были сохранены, или вовсе не предназначаются для сохранения. В этом случае метод с сохранением и восстановлением будет вовсе неуместен.

В документации от Adobe упомянуты свойства app.undoName и app.undoHistory. Судя по описанию, эти свойства должны содержать имя последнего изменения текущего документа и массив с полным списком изменений соответственно. Однако, при наличии таких свойств, app.undoName всегда содержит пустую строку, а app.undoHistory – пустой массив. Это как раз один из примеров не очень добросовестной подготовки документации. На самом деле, свойства undoName и undoHistory присутствуют у каждого документа InDesign (что понятно, поскольку undo может относиться только к конкретному документу) и содержат реальные значения.

viewUndoHistory.jsx
Скрипт демонстрирует использование свойства undoHistory
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  alert(myDoc.undoHistory);
}

Какую практическую пользу получить при помощи свойства undoHistory? Например, можно написать скрипт, который реализовывал бы многошаговую отмену без повторного нажатия Control + Z, причем, довольно легко.

multiUndo.jsx
Скрипт реализует возможность многошагового undo
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
  var myDialog = dialogs.add();
  with (myDialog.dialogColumns.add().borderPanels.add().dialogColumns.add()) {
    dialogRows.add().staticTexts.add({staticLabel: "Выберите действие:"});
    var userSelect = dialogRows.add().dropdowns.add({stringList: myDoc.undoHistory, selectedIndex: 0});
  }
  var myResult = myDialog.show();
  if (!myResult) {
    exit();
  }
  for (var counter = 0; counter < userSelect.selectedIndex; counter++) {
    myDoc.undo();
  }
}

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

Постраничный экспорт в pdf

Экспорт в формат pdf – особый случай, поскольку только для него существуют предустановленные настройки – pdf export presets. Это здорово облегчает жизнь авторам скриптов, поскольку настройки экспорта в любой формат имеют множество свойств, которые нужно учитывать и устанавливать. При использовании предустановленных настроек свойства экспорта устанавливаются автоматически.

Кстати сказать, при помощи скриптов можно создавать свои настройки экспорта (при желании – не только pdf) или редактировать существующие, но об этом в другой раз.

Для решения задачи постраничного экспорта в pdf нам нужно получить от пользователя диапазон страниц и имя настроек экспорта. Полученный диапазон будет использован как значение свойства pdfExportPreferences.pageRange, а имя настроек – при экспорте document. exportFile ().

Самое сложное что нужно будет сделать, – это анализ введенного пользователем диапазона, который может быть довольно сложным, например, «2, 5-20, 23, 27, 30-55». Из этой записи путем различных манипуляций нужно будет получить массив номеров страниц и проверить документ на предмет наличия страниц с такими номерами. Вполне логично для обработки диапазонов выделить отдельную функцию.

exportPages2pdf.jsx
Скрипт дла постраничного экспорта документа в pdf с возможностью указания диапазона
// Определяем пользовательские методы для массивов
// Метод получения первого элемента массива
Array.prototype.firstItem = function () {
  return this[0];
}
// Метод получения последнего элемента массива
Array.prototype.lastItem = function () {
  return this[this.length - 1];
}
// Метод проверки массива на наличие элемента
Array.prototype.isElement = function (myNum) {
  for (var counter = 0; counter < this.length; counter++) {
    if (this[counter] == myNum) {
      return true;
    }
  }
  return false;
}
// Функция сортировки массива чисел
function numSort (myNum1, myNum2) {
  if (myNum1 > myNum2) {
    return 1;
  }
  if (myNum1 < myNum2) {
    return -1;
  }
  if (myNum1 == myNum2) {
    return 0;
  }
}
// Функция удаления из массива дублирующихся элементов
function removeDoubles(myArray) {
  var myResult = [myArray[0]];
  var myPrev = myArray[0];
  for (var counter = 1; counter < myArray.length; counter++) {
    if (myArray[counter] != myPrev) {
      myResult.push(myArray[counter]);
      myPrev = myArray[counter];
    }
  }
  return myResult;
}
// Основная функция получения диапазона
function convertDiap (myString) {
  var myResult = [];
// Удаляем все пробелы
  myString = myString.replace(" ", "", "g");
// Преобразовываем строку в массив  
  var myArr = myString.split(",");
// Полученный массив обрабатываем в цикле  
  for (var counter = 0; counter < myArr.length; counter++) {
// Если пользователь не использовал широкий диапазон (вида 3-5)
    if (myArr[counter].indexOf("-") == -1) {
// Помещаем текущий элемент в результирующий массив  
      myResult.push(Number(myArr[counter]));
    } else {
// Иначе разбираем элемент массива на массив
      var mySubArr = myArr[counter].split("-");
// Если первый элемент полученного массива больше последнего, пременяем значения      
      if (Number(mySubArr.firstItem()) > Number(mySubArr.lastItem())) {
        var myN = Number(mySubArr.firstItem());
        mySubArr[0] = Number(mySubArr.lastItem());
        mySubArr[mySubArr.length - 1] = myN;
      }
// Помещаем в результирующий массив числа от первого до последнего элемента (весь диапазон)      
      for (var numcounter = Number(mySubArr.firstItem()); numcounter <= Number(mySubArr.lastItem()); numcounter++) {
        myResult.push(numcounter);
      }
    }
  }
// Сортируем массив  
  myResult.sort(numSort);
// Удаляем дубли  
  myResult = removeDoubles(myResult);
//Возвращаем массив
  return myResult;
}
// Простая функция проверки диапазона страниц
function checkPages (myDoc, myD) {
//Получаем минимальное и максимальное значения страниц  
  var myMin = myDoc.pages.firstItem().documentOffset;
  var myMax = myDoc.pages.lastItem().documentOffset;
// Сравниваем значения с полученным массивом
  if (myD.firstItem() < myMin || myD.lastItem() > myMax) {
    return false;
  } else {
    return true;
  }
}
with (app) {
// Получаем текущий документ
  try {
    var myDoc = activeDocument;
  } catch (error) {
    alert("Нет открытых документов!");
    exit();
  }
// Получаем список настроек для диалога
  var myPresetNames = [];
  for (var counter = 0; counter < pdfExportPresets.length; counter++) {
    myPresetNames.push(pdfExportPresets[counter].name);
  }
//Создаем диалог  
  var myDialog = dialogs.add({name: "Постраничный экспорт в pdf"});
  with (myDialog.dialogColumns.add().borderPanels.add().dialogColumns.add()) {
    dialogRows.add().staticTexts.add({staticLabel: "Диапазон страниц:"});
// Виджет для получения пользовательского диапазона    
    var userPages = dialogRows.add().textEditboxes.add({editContents: myDoc.pages.firstItem().name + "-" + myDoc.pages.lastItem().name});
    dialogRows.add().staticTexts.add({staticLabel: "Настройки:"});
// Виджет для выбора настроек экспорта
    var userPreset = dialogRows.add().dropdowns.add({stringList: myPresetNames, selectedIndex: 0});
  }
// Отображаем диалог
  var myResult = myDialog.show();
  if (!myResult) {
    exit();
  }
// Получаем настройки экспорта как объект
  var myPreset = pdfExportPresets[userPreset.selectedIndex];
// Получаем пользовательский диапазон как строку и передаем ее в обработку функции
  var myD = convertDiap (userPages.editContents);
// Проверяем соответствие диапазона количеству страниц
  var myResult = checkPages(myDoc, myD);
// Если нет соответствия, завершаем работу
  if (!myResult) {
    alert("Указанный диапазон страниц в документе отсутствует!");
    exit();
  }
// Получаем от пользователя папку для экспорта
  var myFolder = Folder.myDocuments.selectDlg("Выберите папку для экспорта:");
  if (!myFolder) {
    exit();
  }
// Обрабатываем страницы документа  
  for (var counter = 0; counter < myDoc.pages.length; counter++) {
// Если номер страницы в документе входит в обработанный диапазон
    if (myD.isElement(myDoc.pages[counter].documentOffset)) {
// Определяем диапазон экспорта как текущую страницу      
      pdfExportPreferences.pageRange = myDoc.pages[counter].name;
// Создаем файл для экспорта на основе имени файла документа и имени страницы      
      var myFile = File(myFolder.path + "/" + myFolder.name + "/" + myDoc.name.replace(".indd", "") + "_" + myDoc.pages[counter].name + ".pdf");
// Экспортируем документ в pdf
      myDoc.exportFile(ExportFormat.pdfType, myFile, false, myPreset);
    }
  }
}

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

Кто старое помянет

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

Чтобы подстраховаться от таких нехороших явлений, достаточно вовремя удалять объекты с тех полос, которые вышли. Но всегда лень делать это вручную, поэтому лучше написать скрипт и пользоваться им постоянно. Скрипт removeOldItems.jsx удаляет все незакрепленные и неотделенные от мастер-страницы объекты (закрепленные и отделенные, соответственно, оставляет). Очень полезен в профилактических целях.

removeOldItems.jsx
Скрипт удаляет все незакрепленные и неотделенные от мастер-страницы объекты во всех страницах документа
// Функция для сбора pageItems на странице и их удаления
function removeItems (myPage) {
  var myPageItems = [];
// Проверяем все объекты  
  for (var counter = 0; counter < myPage.allPageItems.length; counter++) {
    var myItem = myPage.allPageItems[counter];
// Если объект не закреплен или не отделен от мастер-страницы, то помещаем его в массив подлежащих удалению    
    if (!myItem.locked && !myItem.overridden) {
      myPageItems.push(myItem);
    }
  }
// Удаляем все объекты из массива  
  for (var counter = 0; counter < myPageItems.length; counter++) {
    myPageItems[counter].remove();
  }
}
with (app) {
  try {
    var myDoc = activeDocument;
  } catch (error) {
    exit();
  }
// Обрабатываем все страницы при помощи функции  
  for (var counter = 0; counter < myDoc.pages.length; counter++) {
    removeItems(myDoc.pages[counter]);
  }
}

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

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

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

Логотип WordPress.com

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

Фотография Twitter

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

Фотография Facebook

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

Connecting to %s