Эта статья о том, как я писал скрипт для создания календарей в Photoshop, от самого начала и до самого конца, с багами и пасхалками. Много текста, некоторого кода и чуть-чуть иллюстраций.
В начале
Всё началось с идеи, схематично зарисованной на крошечном листке бумаги. Захотелось написать скрипт, который мог прямо в Photoshop создать календарь на заданный год. Изначально нужно было получить годовой календарик размером 6 на 2 месяцев. Однако в процессе разработки «хотелки» росли, и в итоге список желаемых настроек стал таким, чтобы пользователь мог задать:
- год
- свои имена месяцев и дней недели
- своё начало недели (с понедельника, воскресенья или любого другого дня)
- свои выходные (пометить как выходной любой день)
- отображение номеров недель
- некоторые интервалы между элементами
- взаиморасположение элементов и порядок следования дней
- цвета текстовых слоёв, шрифт и его размеры
- свои праздники (загружается специально созданный текстовый файл, из которого вытаскиваются праздничные даты и отмечаются на календаре) – особое желание
Также хотелось, чтобы была функция, похожая по действию на «Предпросмотр» — создавался тестовый месяц, который бы чётко отражал результат настроек.
Почему мне важно сделать это в Photoshop? Просто я хочу создавать красочные календари с фотографиями или иллюстрациями, прилагая как можно меньше усилий и, по возможности, работая именно в этой программе. Возможно, кто-то тоже хочет.
Инструментарий
Итак, план есть, пришло время выбрать необходимые инструменты.
Язык. Photoshop поддерживает следующие скриптовые языки: AppleScript, VBScript и JavaScript. Думаю, тут всем понятно, что первый подходит для MacOS, второй – для Windows, третий – для обоих. Был выбран JS, так как некоторое время назад я изучил его основы, а разработка такого скрипта могла послужить таким образом хорошей практикой (и послужила, надо сказать).
Текстовый редактор. Первую сотню строк я написал в обычном «блокноте», но так долго продолжаться не могло. Я вспомнил про Sublime Text с его восхитительной для меня функцией множественного курсора, которая, к слову, спасала меня очень много раз в процессе работы.
Скриптовые инструменты от XBYTOR. На деле я использовал только один его инструмент, ActionToJavascript.jsx, но зато он был очень полезен. Этот скрипт работает так — на входе задается записанная операция в Photoshop (это наподобие макросов в MS Office, только закрытый формат), а на выходе получается файлик с расширением *.jsx, в котором прописана функция, выполняющая c помощью низкоуровневых команд всё то же самое, что и исходная операция.
Scripting Listener для Photoshop. Официальный плагин от Adobe. Выполняет, по сути, то же самое, что инструмент выше, но работает по несколько другому принципу — он следит за действиями пользователя в окне программы и записывает их в два текстовых файла на рабочем столе. Первый файл — это JavaScript, второй — VBS или AppleScript, в зависимости от ОС. И, да, он пишет всегда, его нужно удалять или выключать перед запуском программы, добавляя тильду (~
) в начале имени плагина.
Adobe ExtendScript Toolkit. Этот инструмент, по сути, нативное решение для написания скриптов к программам Adobe, но я использовал его только в конце работы. С его помощью можно закрыть исходный код от посторонних. На выходе получается рабочий скрипт с нечитаемым содержимым.
Документация. Без неё написать что-то сложнее всплывающего окошка «Hello word!» будет довольно проблематично. В конце статьи вы можете найти некоторые ссылки.
Оптимизация
Оптимизировать работу приходилось прямо в процессе написания скрипта. Первые тормоза начались, когда скрипт уже мог создать три месяца. Изначально я хотел сделать всю работу в одном документе Photoshop. Но оказалось, что после первого месяца скорость начинала сильно провисать – и если январь составлялся влёт, то с каждым новым месяцем время создания увеличивалось. На февраль уже уходило полторы минуты, а на март – три. Объяснилось это просто – так как каждая дата, каждая надпись является отдельным текстовым слоем, то на создание и перемещение нового слоя в растущем стеке слоёв стало уходить всё больше и больше времени. Поэтому, скрепя сердце в первый раз, я перенёс создание каждого месяца в новый документ, а результат просто копировался в основной документ. Таким образом, решилась проблема увеличения времени создания месяцев – теперь каждый месяц в среднем создавался за одинаковое количество времени.
Следующая неожиданность приключилась, когда мне нужно было узнать, каких размеров каждый из полученных месяцев, чтобы их правильно расставить друг от друга (пользователь может выставлять различные интервалы). Быстро написав простую функцию, которая должна была вернуть значения высот и ширин месяцев, я запустил её. Photoshop намертво завис. Дав ему щедрых десять минут, после которых он так и не реанимировался, процесс пришлось убить. Догадываюсь, что однажды он всё-таки бы закончил, но десять минут это слишком много само по себе. Дело опять оказалось в количестве слоёв в документе – их было слишком много, чтобы сразу получить нужные цифры. И, снова скрепя сердце, я перенёс вычисление размеров на тот этап, где создавался каждый месяц. Теперь созданный месяц сразу определялся по размерам и показания записывались в массив для последующего использования. Звучит несложно, но на деле это заняло у меня довольно много времени.
Ошибки
Тут прежде всего хочу сказать большое спасибо моим тестировщикам, без них я бы оставил в коде ещё больше недочётов, чем есть сейчас.
Ошибка с отрисовкой. Это совет для тех, кто пишет скрипты с интерфейсом пользователя и при отрисовке использует сложный перерасчёт размеров элементов (кнопок, например) — проверяйте работу своего скрипта в Windows и MacOS. У меня был случай, что в одном месте скрипт зависал в системе MacOS. Я долго искал причину и она крылась в следующем: расчёт размера одной из кнопок должен был подгоняться по отношению к другой в сторону увеличения, причём эта операция была загнана мною в цикл (согласен, спорное решение). На Windows на всех версиях Photoshop это отрабатывало отлично, так как подгоняемая кнопка всегда была меньше основной. Но на MacOS оказалось наоборот — основная кнопка была меньше подгоняемой на один-два пикселя, и скрипт уходил в бесконечный цикл. Сейчас я это поправил, и единственный вывод, который можно сделать — не используйте сомнительные решения при перерасчёте элементов интерфейса (ваш Кэп).
Ещё парочка удручающих моментов заключается в том, что разные версии Photoshop отрисовывают окошки по-разному, особенно элементы с прозрачностью, из-за чего мне приходилось перерисовывать эти кнопки уже с фоном (хотя, мне кажется, можно было заморочиться и придумать что-то другое).
Поклонники творчества сэра Терри Пратчетта, возможно, обратили внимание на кнопку «Holi WIZZARD». Мне нравятся такие вещи, не знаю, является это пасхалкой или нет (кстати, о них ниже), я видел нечто подобное у одного антивируса – там сканер называется Luke Filewalker (Люк Скай Файлоуокер).
Ошибки внутренние. Редко, но встречаются ошибки, которые очень сложно объяснить. В моём случае такой проблемой оказалось задание размера ширины (а, возможно, и высоты) блочного текста. Поясню: в Photoshop есть два вида текста – Point Text (короткий) и Paragraph Text (блочный). Первый не имеет границ, в отличие от второго.
Так вот, у меня произошло следующее – я задал размер ширины этого блочного текста в 800 пикселей. На выходе у меня получилась ширина в 3333 пикселей. Я не поверил своим глазам и стал проверять код. Ошибки не было. Дело было вечером, и я грешил на уставшую голову, поэтому стал проверять с особой тщательностью с листочком в руках. Ошибка на глаза не попадалась.
Спустя полчаса, когда я уже отчаялся понять, что же происходит, на глаза попалась ссылка с аналогичной проблемой. Оказывается, что если разрешение документа больше 72 dpi, то Photoshop выставляет какое-то своё значение. Решается это так: берём разрешение нашего документа (у меня оно было равно 300), делим стандартное 72 на наше разрешение и полученный результат умножаем на нужную ширину блока текста. В итоге, чтобы получить блок в 800 пикселей для документа с 300 dpi, нужно задать размер в 800* 72 / 300 = 192 пикселя. Вот так всё просто.
Ошибка с неделями. Ещё один момент, в котором я сильно сглупил, оказался в вычислении номеров недель. По своему незнанию, я сделал нумерацию недель, исходя из абсолютной уверенности в том, что первая неделя начинается с первого января. Я был настолько в этом уверен, что даже не удосужился проверить заранее, а сразу приступил к реализации. Лишь когда я закончил, то решил проверить, а правильно ли начинать нумерацию с 1го января. Оказалось, что нет.
Наверняка многие (или все), кто читает эту статью, знают, что существует стандарт ISO 8601 для формата даты и времени. И согласно нему, неделя начинается с понедельника, а первой неделей является та, на которую выпадает первый четверг января. И первая неделя может начинаться вовсе не с 1го января, а, к примеру, со 2го или даже 4го (как в этом 2016 году). Так что эту часть кода тоже пришлось полностью переписывать.
Ошибка с… массивом? Вот тут уж я не знаю в чём было дело, однако решение оказалось интересным.
Вот код, который создаёт семь выпадающих меню с именами шрифтов:
for (var i=0; i<7; i++){
fontGroup.nameOfFont = fontGroup.add('dropdownlist', undefined, arrFonts);
fontGroup.nameOfFont.selection = fontIndex[i];
}
/*
fontGroup – элемент «группа» в объекте «окно»
arrFonts – массив с именами шрифтов
fontIndex – массив с порядковым номером нужного имени шрифта для каждого из семи меню
*/
При первой генерации окна всё проходило хорошо и без проблем. Но если пользователь загрузит сохранения, то окно будет закрыто и переоткрыто с новым значением fontIndex. При пересоздании окна с подгруженными настройками Фотошоп вылетал, если шрифт имел имя (в моём случае) Aarcover (Plain):001.001. Остальные шрифты проходили нормально, но вот этот давал сбой всей программе.
Казалось бы, и ладно, вероятность крайне мала (на самом деле нет), но меня это теребило очень сильно. Первое решение проблемы оказалось таким:
alert(arrFonts);
for (var i=0; i<7; i++){
fontGroup.nameOfFont = fontGroup.add('dropdownlist', undefined, arrFonts);
fontGroup.nameOfFont.selection = fontIndex[i];
}
То есть, если перед этим циклом просто вызвать окно-предупреждение, в котором будут перечислены все элементы массива arrFonts (и это окошко получалось на весь экран и даже не было видно кнопки ОК), то окно скрипта сгенерируется нормально, как надо. Другие «алерты» с пустыми строками и бессвязным текстом не помогали, только с массивом шрифтов.
Спустя некоторое время появилось второе решение, которое работает, но я не понимаю, почему.
var tempArrFont = arrFonts.slice();
for (var i=0; i<7; i++){
fontGroup.nameOfFont = fontGroup.add('dropdownlist', undefined, tempArrFont);
fontGroup.nameOfFont.selection = fontIndex[i];
}
Получается, мы делаем копию «проблемного» массива методом slice, и используем эту копию вместо оригинала. Это сработало и используется теперь. Я буду рад увидеть в комментариях ответ, почему это так происходит и можно ли как-то это исправить.
Полезный перерыв
Где-то в начале декабря 2015 года я сделал паузу в написании скрипта. Но остановился я не потому, что устал или потерял интерес. Я отвлёкся от скрипта, чтобы написать другой скрипт. Не стану подробно останавливаться на этой вещи, скажу лишь то, что в процессе разработки этого скрипта я обнаружил, что создание текстового слоя с помощью низкоуровневых команд выполнялось на порядок быстрее, чем с помощью моей функции, использующей стандартные методы из руководства. Да, в новой функции было куда больше строк кода, чем в той функции, которую я написал изначально – и всё-таки она работала быстрее. Можно было бы догадаться и так, что низкий уровень наше всё, но в тот момент я основательно сглупил и проверил это лишь в середине.
Для большей наглядности приведу сравнительную табличку:
Время, затраченное на создание 600 текстовых слоёв, содержащих текст «01»
(НУ — низкоуровневое создание, СМ — создание стандартными методами)
Версия Photoshop | Стационарный компьютер (хар-ки) |
Ноутбук (хар-ки) |
---|---|---|
CS5 | НУ: 0 мин 47 сек СМ: 3 мин 43 сек |
НУ: 2 мин 28 сек СМ: 9 мин 49 сек |
CC 2015 | НУ: 1 мин 18 сек СМ: 10 мин 42 сек |
НУ: 3 мин 12 сек СМ: 42 мин 36 сек |
Увидев такие показатели (обратите внимание на CS5 – обходит новый CC по скорости, шельмец) меня охватило чувство разочарования – ведь моя старая функция создания текста в «календарном» скрипте уже органично вписана в его структуру. И переделывать всё мне очень и очень не хотелось. Но, подумав, что ведь на самом деле всё не так плохо, просто нужно очень внимательно и осторожно подменить одну функцию другой. Затем протестировать, исправить все появившиеся недочёты и полностью перейти на новый способ. Тем более, что уменьшив время на создание календаря, я только выиграю.
Итак, для начала нужно было избавиться от ненужных строк кода в полученном элементе.
// Вспомогательные функции (используются в дальнейшем для сокращения),
// создано инструментом XBYTOR'а
cTID = function(s) { return app.charIDToTypeID(s); };
sTID = function(s) { return app.stringIDToTypeID(s); };
// Практически сырая функция, полученная при конвертации операции в скрипт
// Создаёт слой с текстом "TEXT TEXT", шрифтом Verdana, размером в 9,32 пункта, чёрного цвета
function step1() {
var desc1 = new ActionDescriptor();
var ref1 = new ActionReference();
ref1.putClass(cTID('TxLr'));
desc1.putReference(cTID('null'), ref1);
var desc2 = new ActionDescriptor();
desc2.putString(cTID('Txt '), "TEXT TEXT");
var desc3 = new ActionDescriptor();
desc3.putEnumerated(sTID("warpStyle"), sTID("warpStyle"), sTID("warpNone"));
desc3.putDouble(sTID("warpValue"), 0);
desc3.putDouble(sTID("warpPerspective"), 0);
desc3.putDouble(sTID("warpPerspectiveOther"), 0);
desc3.putEnumerated(sTID("warpRotate"), cTID('Ornt'), cTID('Hrzn'));
desc2.putObject(cTID('warp'), cTID('warp'), desc3);
var desc4 = new ActionDescriptor();
desc4.putUnitDouble(cTID('Hrzn'), cTID('#Prc'), 35.5820105820106);
desc4.putUnitDouble(cTID('Vrtc'), cTID('#Prc'), 48.2254697286012);
desc2.putObject(cTID('TxtC'), cTID('Pnt '), desc4);
desc2.putEnumerated(sTID("textGridding"), sTID("textGridding"), cTID('None'));
desc2.putEnumerated(cTID('Ornt'), cTID('Ornt'), cTID('Hrzn'));
desc2.putEnumerated(cTID('AntA'), cTID('Annt'), cTID('AnCr'));
var desc5 = new ActionDescriptor();
desc5.putUnitDouble(cTID('Left'), cTID('#Pnt'), 0);
desc5.putUnitDouble(cTID('Top '), cTID('#Pnt'), -8.04805660247803);
desc5.putUnitDouble(cTID('Rght'), cTID('#Pnt'), 50.8121795654297);
desc5.putUnitDouble(cTID('Btom'), cTID('#Pnt'), 2.8260350227356);
desc2.putObject(sTID("bounds"), sTID("bounds"), desc5);
var desc6 = new ActionDescriptor();
desc6.putUnitDouble(cTID('Left'), cTID('#Pnt'), 0);
desc6.putUnitDouble(cTID('Top '), cTID('#Pnt'), -7);
desc6.putUnitDouble(cTID('Rght'), cTID('#Pnt'), 52.0618438720703);
desc6.putUnitDouble(cTID('Btom'), cTID('#Pnt'), 0);
desc2.putObject(sTID("boundingBox"), sTID("boundingBox"), desc6);
var list1 = new ActionList();
var desc7 = new ActionDescriptor();
desc7.putEnumerated(sTID("textType"), sTID("textType"), cTID('Pnt '));
desc7.putEnumerated(cTID('Ornt'), cTID('Ornt'), cTID('Hrzn'));
var desc8 = new ActionDescriptor();
desc8.putDouble(sTID("xx"), 1);
desc8.putDouble(sTID("xy"), 0);
desc8.putDouble(sTID("yx"), 0);
desc8.putDouble(sTID("yy"), 1);
desc8.putDouble(sTID("tx"), 0);
desc8.putDouble(sTID("ty"), 0);
desc7.putObject(cTID('Trnf'), cTID('Trnf'), desc8);
desc7.putInteger(sTID("rowCount"), 1);
desc7.putInteger(sTID("columnCount"), 1);
desc7.putBoolean(sTID("rowMajorOrder"), true);
desc7.putUnitDouble(sTID("rowGutter"), cTID('#Pnt'), 0);
desc7.putUnitDouble(sTID("columnGutter"), cTID('#Pnt'), 0);
desc7.putUnitDouble(cTID('Spcn'), cTID('#Pnt'), 0);
desc7.putEnumerated(sTID("frameBaselineAlignment"), sTID("frameBaselineAlignment"), sTID("alignByAscent"));
desc7.putUnitDouble(sTID("firstBaselineMinimum"), cTID('#Pnt'), 0);
var desc9 = new ActionDescriptor();
desc9.putDouble(cTID('Hrzn'), 0);
desc9.putDouble(cTID('Vrtc'), 0);
desc7.putObject(cTID('base'), cTID('Pnt '), desc9);
list1.putObject(sTID("textShape"), desc7);
desc2.putList(sTID("textShape"), list1);
var list2 = new ActionList();
var desc10 = new ActionDescriptor();
desc10.putInteger(cTID('From'), 0);
desc10.putInteger(cTID('T '), 10);
var desc11 = new ActionDescriptor();
desc11.putBoolean(sTID("styleSheetHasParent"), true);
desc11.putString(sTID("fontPostScriptName"), "Verdana");
desc11.putString(cTID('FntN'), "Verdana");
desc11.putString(cTID('FntS'), "Regular");
desc11.putInteger(cTID('Scrp'), 0);
desc11.putInteger(cTID('FntT'), 1);
desc11.putUnitDouble(cTID('Sz '), cTID('#Pnt'), 9.31999969482422);
desc11.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("defaultDigits"));
desc11.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pnt'), 5.76000165939331);
desc11.putEnumerated(sTID("textLanguage"), sTID("textLanguage"), sTID("russianLanguage"));
var desc12 = new ActionDescriptor();
desc12.putString(sTID("fontPostScriptName"), "MyriadPro-Regular");
desc12.putString(cTID('FntN'), "Myriad Pro");
desc12.putString(cTID('FntS'), "Regular");
desc12.putInteger(cTID('Scrp'), 0);
desc12.putInteger(cTID('FntT'), 0);
desc12.putUnitDouble(cTID('Sz '), cTID('#Pnt'), 12);
desc12.putDouble(cTID('HrzS'), 100);
desc12.putDouble(cTID('VrtS'), 100);
desc12.putBoolean(sTID("syntheticBold"), false);
desc12.putBoolean(sTID("syntheticItalic"), false);
desc12.putBoolean(sTID("autoLeading"), true);
desc12.putInteger(cTID('Trck'), 0);
desc12.putUnitDouble(cTID('Bsln'), cTID('#Pnt'), 0);
desc12.putDouble(sTID("characterRotation"), 0);
desc12.putEnumerated(cTID('AtKr'), cTID('AtKr'), sTID("metricsKern"));
desc12.putEnumerated(sTID("fontCaps"), sTID("fontCaps"), cTID('Nrml'));
desc12.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("defaultDigits"));
desc12.putEnumerated(sTID("dirOverride"), sTID("dirOverride"), sTID("dirOverrideDefault"));
desc12.putEnumerated(sTID("kashidas"), sTID("kashidas"), sTID("kashidaDefault"));
desc12.putEnumerated(sTID("diacVPos"), sTID("diacVPos"), sTID("diacVPosOpenType"));
desc12.putUnitDouble(sTID("diacXOffset"), cTID('#Pnt'), 0);
desc12.putUnitDouble(sTID("diacYOffset"), cTID('#Pnt'), 0);
desc12.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pnt'), 100);
desc12.putEnumerated(sTID("baseline"), sTID("baseline"), cTID('Nrml'));
desc12.putEnumerated(sTID("otbaseline"), sTID("otbaseline"), cTID('Nrml'));
desc12.putEnumerated(sTID("strikethrough"), sTID("strikethrough"), sTID("strikethroughOff"));
desc12.putEnumerated(cTID('Undl'), cTID('Undl'), sTID("underlineOff"));
desc12.putUnitDouble(sTID("underlineOffset"), cTID('#Pnt'), 0);
desc12.putBoolean(sTID("ligature"), true);
desc12.putBoolean(sTID("altligature"), false);
desc12.putBoolean(sTID("contextualLigatures"), false);
desc12.putBoolean(sTID("alternateLigatures"), false);
desc12.putBoolean(sTID("oldStyle"), false);
desc12.putBoolean(sTID("fractions"), false);
desc12.putBoolean(sTID("ordinals"), false);
desc12.putBoolean(sTID("swash"), false);
desc12.putBoolean(sTID("titling"), false);
desc12.putBoolean(sTID("connectionForms"), false);
desc12.putBoolean(sTID("stylisticAlternates"), false);
desc12.putBoolean(sTID("ornaments"), false);
desc12.putBoolean(sTID("justificationAlternates"), false);
desc12.putEnumerated(sTID("figureStyle"), sTID("figureStyle"), cTID('Nrml'));
desc12.putBoolean(sTID("proportionalMetrics"), false);
desc12.putBoolean(cTID('kana'), false);
desc12.putBoolean(sTID("italics"), false);
desc12.putBoolean(cTID('ruby'), false);
desc12.putEnumerated(sTID("baselineDirection"), sTID("baselineDirection"), sTID("rotated"));
desc12.putEnumerated(sTID("textLanguage"), sTID("textLanguage"), sTID("englishLanguage"));
desc12.putEnumerated(sTID("japaneseAlternate"), sTID("japaneseAlternate"), sTID("defaultForm"));
desc12.putDouble(sTID("mojiZume"), 0);
desc12.putEnumerated(sTID("gridAlignment"), sTID("gridAlignment"), sTID("roman"));
desc12.putBoolean(sTID("enableWariChu"), false);
desc12.putInteger(sTID("wariChuCount"), 2);
desc12.putInteger(sTID("wariChuLineGap"), 0);
desc12.putDouble(sTID("wariChuScale"), 0.5);
desc12.putInteger(sTID("wariChuWidow"), 2);
desc12.putInteger(sTID("wariChuOrphan"), 2);
desc12.putEnumerated(sTID("wariChuJustification"), sTID("wariChuJustification"), sTID("wariChuAutoJustify"));
desc12.putInteger(sTID("tcyUpDown"), 0);
desc12.putInteger(sTID("tcyLeftRight"), 0);
desc12.putDouble(sTID("leftAki"), -1);
desc12.putDouble(sTID("rightAki"), -1);
desc12.putInteger(sTID("jiDori"), 0);
desc12.putBoolean(sTID("noBreak"), false);
var desc13 = new ActionDescriptor();
desc13.putDouble(cTID('Rd '), 0);
desc13.putDouble(cTID('Grn '), 0);
desc13.putDouble(cTID('Bl '), 0);
desc12.putObject(cTID('Clr '), sTID("RGBColor"), desc13);
var desc14 = new ActionDescriptor();
desc14.putDouble(cTID('Rd '), 0);
desc14.putDouble(cTID('Grn '), 0);
desc14.putDouble(cTID('Bl '), 0);
desc12.putObject(sTID("strokeColor"), sTID("RGBColor"), desc14);
desc12.putBoolean(cTID('Fl '), true);
desc12.putBoolean(cTID('Strk'), false);
desc12.putBoolean(sTID("fillFirst"), true);
desc12.putBoolean(sTID("fillOverPrint"), false);
desc12.putBoolean(sTID("strokeOverPrint"), false);
desc12.putEnumerated(sTID("lineCap"), sTID("lineCap"), sTID("buttCap"));
desc12.putEnumerated(sTID("lineJoin"), sTID("lineJoin"), sTID("miterJoin"));
desc12.putUnitDouble(sTID("lineWidth"), cTID('#Pnt'), 1);
desc12.putUnitDouble(sTID("miterLimit"), cTID('#Pnt'), 4);
desc12.putDouble(sTID("lineDashOffset"), 0);
desc11.putObject(sTID("baseParentStyle"), cTID('TxtS'), desc12);
desc10.putObject(cTID('TxtS'), cTID('TxtS'), desc11);
list2.putObject(cTID('Txtt'), desc10);
desc2.putList(cTID('Txtt'), list2);
var list3 = new ActionList();
var desc15 = new ActionDescriptor();
desc15.putInteger(cTID('From'), 0);
desc15.putInteger(cTID('T '), 10);
var desc16 = new ActionDescriptor();
desc16.putBoolean(sTID("styleSheetHasParent"), true);
desc16.putEnumerated(cTID('Algn'), cTID('Alg '), cTID('Left'));
desc16.putUnitDouble(sTID("firstLineIndent"), cTID('#Pnt'), 0);
desc16.putUnitDouble(sTID("startIndent"), cTID('#Pnt'), 0);
desc16.putUnitDouble(sTID("endIndent"), cTID('#Pnt'), 0);
desc16.putUnitDouble(sTID("spaceBefore"), cTID('#Pnt'), 0);
desc16.putUnitDouble(sTID("spaceAfter"), cTID('#Pnt'), 0);
desc16.putInteger(sTID("dropCapMultiplier"), 1);
desc16.putDouble(sTID("autoLeadingPercentage"), 1.20000004768372);
desc16.putEnumerated(sTID("leadingType"), sTID("leadingType"), sTID("leadingBelow"));
desc16.putEnumerated(sTID("directionType"), sTID("directionType"), sTID("dirLeftToRight"));
desc16.putEnumerated(sTID("kashidaWidthType"), sTID("kashidaWidthType"), sTID("kashidaWidthMedium"));
desc16.putEnumerated(sTID("justificationMethodType"), sTID("justificationMethodType"), sTID("justifMethodAutomatic"));
desc16.putBoolean(sTID("hyphenate"), true);
desc16.putInteger(sTID("hyphenateWordSize"), 6);
desc16.putInteger(sTID("hyphenatePreLength"), 2);
desc16.putInteger(sTID("hyphenatePostLength"), 2);
desc16.putInteger(sTID("hyphenateLimit"), 0);
desc16.putDouble(sTID("hyphenationZone"), 36);
desc16.putBoolean(sTID("hyphenateCapitalized"), true);
desc16.putDouble(sTID("hyphenationPreference"), 0.5);
desc16.putDouble(sTID("justificationWordMinimum"), 0.80000001192093);
desc16.putDouble(sTID("justificationWordDesired"), 1);
desc16.putDouble(sTID("justificationWordMaximum"), 1.33000004291534);
desc16.putDouble(sTID("justificationLetterMinimum"), 0);
desc16.putDouble(sTID("justificationLetterDesired"), 0);
desc16.putDouble(sTID("justificationLetterMaximum"), 0);
desc16.putDouble(sTID("justificationGlyphMinimum"), 1);
desc16.putDouble(sTID("justificationGlyphDesired"), 1);
desc16.putDouble(sTID("justificationGlyphMaximum"), 1);
desc16.putEnumerated(sTID("singleWordJustification"), cTID('Alg '), cTID('JstA'));
desc16.putBoolean(sTID("hangingRoman"), false);
desc16.putInteger(sTID("autoTCY"), 0);
desc16.putBoolean(sTID("keepTogether"), true);
desc16.putEnumerated(sTID("burasagari"), sTID("burasagari"), sTID("burasagariNone"));
desc16.putEnumerated(sTID("preferredKinsokuOrder"), sTID("preferredKinsokuOrder"), sTID("pushIn"));
desc16.putBoolean(sTID("kurikaeshiMojiShori"), false);
desc16.putBoolean(sTID("textEveryLineComposer"), false);
desc16.putDouble(sTID("defaultTabWidth"), 36);
var desc17 = new ActionDescriptor();
desc17.putString(sTID("fontPostScriptName"), "MyriadPro-Regular");
desc17.putString(cTID('FntN'), "Myriad Pro");
desc17.putString(cTID('FntS'), "Regular");
desc17.putInteger(cTID('Scrp'), 0);
desc17.putInteger(cTID('FntT'), 0);
desc17.putUnitDouble(cTID('Sz '), cTID('#Pnt'), 12);
desc17.putDouble(cTID('HrzS'), 100);
desc17.putDouble(cTID('VrtS'), 100);
desc17.putBoolean(sTID("syntheticBold"), false);
desc17.putBoolean(sTID("syntheticItalic"), false);
desc17.putBoolean(sTID("autoLeading"), true);
desc17.putInteger(cTID('Trck'), 0);
desc17.putUnitDouble(cTID('Bsln'), cTID('#Pnt'), 0);
desc17.putDouble(sTID("characterRotation"), 0);
desc17.putEnumerated(cTID('AtKr'), cTID('AtKr'), sTID("metricsKern"));
desc17.putEnumerated(sTID("fontCaps"), sTID("fontCaps"), cTID('Nrml'));
desc17.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("arabicDigits"));
desc17.putEnumerated(sTID("kashidas"), sTID("kashidas"), sTID("kashidaDefault"));
desc17.putEnumerated(sTID("diacVPos"), sTID("diacVPos"), sTID("diacVPosOpenType"));
desc17.putUnitDouble(sTID("diacXOffset"), cTID('#Pnt'), 0);
desc17.putUnitDouble(sTID("diacYOffset"), cTID('#Pnt'), 0);
desc17.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pnt'), 0);
desc17.putEnumerated(sTID("baseline"), sTID("baseline"), cTID('Nrml'));
desc17.putEnumerated(sTID("strikethrough"), sTID("strikethrough"), sTID("strikethroughOff"));
desc17.putEnumerated(cTID('Undl'), cTID('Undl'), sTID("underlineOff"));
desc17.putBoolean(sTID("ligature"), true);
desc17.putBoolean(sTID("altligature"), false);
desc17.putBoolean(sTID("contextualLigatures"), true);
desc17.putBoolean(sTID("alternateLigatures"), false);
desc17.putBoolean(sTID("oldStyle"), false);
desc17.putBoolean(sTID("fractions"), false);
desc17.putBoolean(sTID("ordinals"), false);
desc17.putBoolean(sTID("swash"), false);
desc17.putBoolean(sTID("titling"), false);
desc17.putBoolean(sTID("connectionForms"), false);
desc17.putBoolean(sTID("stylisticAlternates"), false);
desc17.putBoolean(sTID("ornaments"), false);
desc17.putEnumerated(sTID("figureStyle"), sTID("figureStyle"), cTID('Nrml'));
desc17.putEnumerated(sTID("baselineDirection"), sTID("baselineDirection"), sTID("withStream"));
desc17.putEnumerated(sTID("textLanguage"), sTID("textLanguage"), sTID("englishLanguage"));
var desc18 = new ActionDescriptor();
desc18.putDouble(cTID('Rd '), 0);
desc18.putDouble(cTID('Grn '), 0);
desc18.putDouble(cTID('Bl '), 0);
desc17.putObject(cTID('Clr '), sTID("RGBColor"), desc18);
var desc19 = new ActionDescriptor();
desc19.putDouble(cTID('Rd '), 0);
desc19.putDouble(cTID('Grn '), 0);
desc19.putDouble(cTID('Bl '), 0);
desc17.putObject(sTID("strokeColor"), sTID("RGBColor"), desc19);
desc16.putObject(sTID("defaultStyle"), cTID('TxtS'), desc17);
desc15.putObject(sTID("paragraphStyle"), sTID("paragraphStyle"), desc16);
list3.putObject(sTID("paragraphStyleRange"), desc15);
desc2.putList(sTID("paragraphStyleRange"), list3);
var list4 = new ActionList();
desc2.putList(sTID("kerningRange"), list4);
desc1.putObject(cTID('Usng'), cTID('TxLr'), desc2);
executeAction(cTID('Mk '), desc1, DialogModes.NO);
};
Я смог удалить несколько строк без нарушения функционала. Чистил я до тех пор, пока это было легко: ищешь новый неповторяющийся дескриптор, смотришь, что он делает, удаляешь, проверяешь работу и, если всё работает без видимых изменений, продолжаешь так делать и дальше. Когда ошибки после каждого такого грубого хирургического вмешательства в тело функции стали возникать всё чаще, я остановился – лучшее враг хорошего. Затем нашёл, в каком месте задаётся шрифт, его размер и цвет, перевёл эти места в задаваемые переменные.
Полученная функция отлично отрабатывала, причём ей не нужно было указывать, где создавать новый текст – если активна какая-то группа, то текстовый слой создастся в ней. Если активен текстовый слой, находящийся в какой-то группе, то – правильно, новый слой будет в этой группе. Очень удобно и экономит время, замена проведена не зря. Вот код полученной функции.
// Объект, содержащий необходимые параметры для создания текстового слоя
var sizeTestParam = {
text: сам текст
fontName: имя шрифта (postcript),
fontSize: размер шрифта,
color: цвет (объект)
}
newTextLayer(sizeTestParam, 'Left'); //Образец выполнения. Выравнивание тоже можно было поместить в объект
// Сама функция создания
function newTextLayer() {
var paramset = arguments[0];
var justific = arguments[1];
if (justific===undefined) {justific = 'Cntr'} // Выравнивание текста
var text = paramset.text + ""||"<empty>";
var textLength = text.length;
var fontName = paramset.fontName||"TimesNewRomanPS-BoldMT";
var fontSize = paramset.fontSize||5;
var colorR = paramset.color.rgb.red||0;
var colorG = paramset.color.rgb.green||0;
var colorB = paramset.color.rgb.blue||0;
var desc1 = new ActionDescriptor();
var ref1 = new ActionReference();
ref1.putClass(cTID('TxLr'));
desc1.putReference(cTID('null'), ref1);
var desc2 = new ActionDescriptor();
desc2.putString(cTID('Txt '), text);
// КООРДИНАТЫ ТЕКСТА (СЕЙЧАС В ПРОЦЕНТАХ)
var desc4 = new ActionDescriptor();
desc4.putUnitDouble(cTID('Hrzn'), cTID('#Prc'), 5);
desc4.putUnitDouble(cTID('Vrtc'), cTID('#Prc'), 5);
desc2.putObject(cTID('TxtC'), cTID('Pnt '), desc4);
desc2.putEnumerated(sTID("textGridding"), sTID("textGridding"), cTID('None'));
desc2.putEnumerated(cTID('Ornt'), cTID('Ornt'), cTID('Hrzn'));
desc2.putEnumerated(cTID('AntA'), cTID('Annt'), cTID('AnCr'));
var list1 = new ActionList();
var desc7 = new ActionDescriptor();
desc7.putEnumerated(sTID("textType"), sTID("textType"), cTID('Pnt '));
desc7.putEnumerated(cTID('Ornt'), cTID('Ornt'), cTID('Hrzn'));
desc7.putInteger(sTID("rowCount"), 1);
desc7.putInteger(sTID("columnCount"), 1);
desc7.putBoolean(sTID("rowMajorOrder"), true);
desc7.putUnitDouble(sTID("rowGutter"), cTID('#Pxl'), 0);
desc7.putUnitDouble(sTID("columnGutter"), cTID('#Pxl'), 0);
desc7.putUnitDouble(cTID('Spcn'), cTID('#Pxl'), 0);
desc7.putEnumerated(sTID("frameBaselineAlignment"), sTID("frameBaselineAlignment"), sTID("alignByAscent"));
desc7.putUnitDouble(sTID("firstBaselineMinimum"), cTID('#Pxl'), 0);
var desc9 = new ActionDescriptor();
desc9.putDouble(cTID('Hrzn'), 0);
desc9.putDouble(cTID('Vrtc'), 0);
desc7.putObject(cTID('base'), cTID('Pnt '), desc9);
list1.putObject(sTID("textShape"), desc7);
desc2.putList(sTID("textShape"), list1);
var list2 = new ActionList();
var desc10 = new ActionDescriptor();
desc10.putInteger(cTID('From'), 0);
// КОЛИЧЕСТВО СИМВОЛОВ В ТЕКСТЕ (ИНАЧЕ ДРУГОЙ ЦВЕТ-РАЗМЕР)
desc10.putInteger(cTID('T '), textLength);
var desc11 = new ActionDescriptor();
desc11.putBoolean(sTID("styleSheetHasParent"), true);
// ШРИФТ
desc11.putString(sTID("fontPostScriptName"), fontName);
desc11.putInteger(cTID('Scrp'), 0);
desc11.putInteger(cTID('FntT'), 1);
// РАЗМЕР ТЕКСТА
desc11.putUnitDouble(cTID('Sz '), cTID('#Pnt'), fontSize);
desc11.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("defaultDigits"));
desc11.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pxl'), 100);
// ЦВЕТ ШРИФТА
var colorDesc = new ActionDescriptor();
colorDesc.putDouble(cTID('Rd '), colorR);
colorDesc.putDouble(cTID('Grn '), colorG);
colorDesc.putDouble(cTID('Bl '), colorB);
desc11.putObject(cTID('Clr '), sTID("RGBColor"), colorDesc);
var desc13 = new ActionDescriptor();
desc13.putString(sTID("fontPostScriptName"), "MyriadPro-Regular");
desc13.putString(cTID('FntN'), "Myriad Pro");
desc13.putString(cTID('FntS'), "Regular");
desc13.putInteger(cTID('Scrp'), 0);
desc13.putInteger(cTID('FntT'), 0);
desc13.putUnitDouble(cTID('Sz '), cTID('#Pxl'), 12);
desc13.putDouble(cTID('HrzS'), 100);
desc13.putDouble(cTID('VrtS'), 100);
desc13.putBoolean(sTID("syntheticBold"), false);
desc13.putBoolean(sTID("syntheticItalic"), false);
desc13.putBoolean(sTID("autoLeading"), true);
desc13.putInteger(cTID('Trck'), 0);
desc13.putUnitDouble(cTID('Bsln'), cTID('#Pxl'), 0);
desc13.putEnumerated(cTID('AtKr'), cTID('AtKr'), sTID("metricsKern"));
desc13.putEnumerated(sTID("fontCaps"), sTID("fontCaps"), cTID('Nrml'));
desc13.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("defaultDigits"));
desc13.putEnumerated(sTID("dirOverride"), sTID("dirOverride"), sTID("dirOverrideDefault"));
desc13.putEnumerated(sTID("kashidas"), sTID("kashidas"), sTID("kashidaDefault"));
desc13.putEnumerated(sTID("diacVPos"), sTID("diacVPos"), sTID("diacVPosOpenType"));
desc13.putUnitDouble(sTID("diacXOffset"), cTID('#Pxl'), 0);
desc13.putUnitDouble(sTID("diacYOffset"), cTID('#Pxl'), 0);
desc13.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pxl'), 100);
desc13.putEnumerated(sTID("baseline"), sTID("baseline"), cTID('Nrml'));
desc13.putEnumerated(sTID("otbaseline"), sTID("otbaseline"), cTID('Nrml'));
desc13.putEnumerated(sTID("strikethrough"), sTID("strikethrough"), sTID("strikethroughOff"));
desc13.putEnumerated(cTID('Undl'), cTID('Undl'), sTID("underlineOff"));
desc13.putUnitDouble(sTID("underlineOffset"), cTID('#Pxl'), 0);
desc13.putBoolean(sTID("ligature"), true);
desc13.putBoolean(sTID("altligature"), false);
desc13.putBoolean(sTID("contextualLigatures"), false);
desc13.putBoolean(sTID("alternateLigatures"), false);
desc13.putBoolean(sTID("oldStyle"), false);
desc13.putBoolean(sTID("fractions"), false);
desc13.putBoolean(sTID("ordinals"), false);
desc13.putBoolean(sTID("swash"), false);
desc13.putBoolean(sTID("titling"), false);
desc13.putBoolean(sTID("connectionForms"), false);
desc13.putBoolean(sTID("stylisticAlternates"), false);
desc13.putBoolean(sTID("ornaments"), false);
desc13.putBoolean(sTID("justificationAlternates"), false);
desc13.putEnumerated(sTID("figureStyle"), sTID("figureStyle"), cTID('Nrml'));
desc13.putBoolean(sTID("proportionalMetrics"), false);
desc13.putBoolean(cTID('kana'), false);
desc13.putBoolean(sTID("italics"), false);
desc13.putBoolean(cTID('ruby'), false);
desc13.putEnumerated(sTID("baselineDirection"), sTID("baselineDirection"), sTID("rotated"));
desc13.putEnumerated(sTID("textLanguage"), sTID("textLanguage"), sTID("englishLanguage"));
desc13.putEnumerated(sTID("japaneseAlternate"), sTID("japaneseAlternate"), sTID("defaultForm"));
desc13.putDouble(sTID("mojiZume"), 0);
desc13.putEnumerated(sTID("gridAlignment"), sTID("gridAlignment"), sTID("roman"));
desc13.putBoolean(sTID("enableWariChu"), false);
desc13.putInteger(sTID("wariChuCount"), 2);
desc13.putInteger(sTID("wariChuLineGap"), 0);
desc13.putDouble(sTID("wariChuScale"), 0.5);
desc13.putInteger(sTID("wariChuWidow"), 2);
desc13.putInteger(sTID("wariChuOrphan"), 2);
desc13.putEnumerated(sTID("wariChuJustification"), sTID("wariChuJustification"), sTID("wariChuAutoJustify"));
desc13.putInteger(sTID("tcyUpDown"), 0);
desc13.putInteger(sTID("tcyLeftRight"), 0);
desc13.putDouble(sTID("leftAki"), -1);
desc13.putDouble(sTID("rightAki"), -1);
desc13.putInteger(sTID("jiDori"), 0);
desc13.putBoolean(sTID("noBreak"), false);
desc13.putEnumerated(sTID("lineCap"), sTID("lineCap"), sTID("buttCap"));
desc13.putEnumerated(sTID("lineJoin"), sTID("lineJoin"), sTID("miterJoin"));
desc13.putUnitDouble(sTID("lineWidth"), cTID('#Pxl'), 1);
desc13.putUnitDouble(sTID("miterLimit"), cTID('#Pxl'), 4);
desc13.putDouble(sTID("lineDashOffset"), 0);
desc11.putObject(sTID("baseParentStyle"), cTID('TxtS'), desc13);
desc10.putObject(cTID('TxtS'), cTID('TxtS'), desc11);
list2.putObject(cTID('Txtt'), desc10);
desc2.putList(cTID('Txtt'), list2);
var list3 = new ActionList();
var desc16 = new ActionDescriptor();
desc16.putInteger(cTID('From'), 0);
desc16.putInteger(cTID('T '), 16);
var desc17 = new ActionDescriptor();
desc17.putBoolean(sTID("styleSheetHasParent"), true);
// ВЫРАВНИВАНИЕ
desc17.putEnumerated(cTID('Algn'), cTID('Alg '), cTID(justific));
desc17.putUnitDouble(sTID("firstLineIndent"), cTID('#Pxl'), 0);
desc17.putUnitDouble(sTID("startIndent"), cTID('#Pxl'), 0);
desc17.putUnitDouble(sTID("endIndent"), cTID('#Pxl'), 0);
desc17.putUnitDouble(sTID("spaceBefore"), cTID('#Pxl'), 0);
desc17.putUnitDouble(sTID("spaceAfter"), cTID('#Pxl'), 0);
desc17.putInteger(sTID("dropCapMultiplier"), 1);
desc17.putDouble(sTID("autoLeadingPercentage"), 1.20000004768372);
desc17.putEnumerated(sTID("leadingType"), sTID("leadingType"), sTID("leadingBelow"));
desc17.putEnumerated(sTID("directionType"), sTID("directionType"), sTID("dirLeftToRight"));
desc17.putEnumerated(sTID("kashidaWidthType"), sTID("kashidaWidthType"), sTID("kashidaWidthMedium"));
desc17.putEnumerated(sTID("justificationMethodType"), sTID("justificationMethodType"), sTID("justifMethodAutomatic"));
desc17.putBoolean(sTID("hyphenate"), true);
desc17.putInteger(sTID("hyphenateWordSize"), 6);
desc17.putInteger(sTID("hyphenatePreLength"), 2);
desc17.putInteger(sTID("hyphenatePostLength"), 2);
desc17.putInteger(sTID("hyphenateLimit"), 0);
desc17.putDouble(sTID("hyphenationZone"), 36);
desc17.putBoolean(sTID("hyphenateCapitalized"), true);
desc17.putDouble(sTID("hyphenationPreference"), 0.5);
desc17.putDouble(sTID("justificationWordMinimum"), 0.80000001192093);
desc17.putDouble(sTID("justificationWordDesired"), 1);
desc17.putDouble(sTID("justificationWordMaximum"), 1.33000004291534);
desc17.putDouble(sTID("justificationLetterMinimum"), 0);
desc17.putDouble(sTID("justificationLetterDesired"), 0);
desc17.putDouble(sTID("justificationLetterMaximum"), 0);
desc17.putDouble(sTID("justificationGlyphMinimum"), 1);
desc17.putDouble(sTID("justificationGlyphDesired"), 1);
desc17.putDouble(sTID("justificationGlyphMaximum"), 1);
desc17.putEnumerated(sTID("singleWordJustification"), cTID('Alg '), cTID('JstA'));
desc17.putBoolean(sTID("hangingRoman"), false);
desc17.putInteger(sTID("autoTCY"), 0);
desc17.putBoolean(sTID("keepTogether"), true);
desc17.putEnumerated(sTID("burasagari"), sTID("burasagari"), sTID("burasagariNone"));
desc17.putEnumerated(sTID("preferredKinsokuOrder"), sTID("preferredKinsokuOrder"), sTID("pushIn"));
desc17.putBoolean(sTID("kurikaeshiMojiShori"), false);
desc17.putBoolean(sTID("textEveryLineComposer"), false);
desc17.putDouble(sTID("defaultTabWidth"), 36);
var desc18 = new ActionDescriptor();
desc18.putString(sTID("fontPostScriptName"), "MyriadPro-Regular");
desc18.putString(cTID('FntN'), "Myriad Pro");
desc18.putString(cTID('FntS'), "Regular");
desc18.putInteger(cTID('Scrp'), 0);
desc18.putInteger(cTID('FntT'), 0);
desc18.putUnitDouble(cTID('Sz '), cTID('#Pxl'), 12);
desc18.putDouble(cTID('HrzS'), 100);
desc18.putDouble(cTID('VrtS'), 100);
desc18.putBoolean(sTID("syntheticBold"), false);
desc18.putBoolean(sTID("syntheticItalic"), false);
desc18.putBoolean(sTID("autoLeading"), true);
desc18.putInteger(cTID('Trck'), 0);
desc18.putUnitDouble(cTID('Bsln'), cTID('#Pxl'), 0);
desc18.putDouble(sTID("characterRotation"), 0);
desc18.putEnumerated(cTID('AtKr'), cTID('AtKr'), sTID("metricsKern"));
desc18.putEnumerated(sTID("fontCaps"), sTID("fontCaps"), cTID('Nrml'));
desc18.putEnumerated(sTID("digitSet"), sTID("digitSet"), sTID("arabicDigits"));
desc18.putEnumerated(sTID("kashidas"), sTID("kashidas"), sTID("kashidaDefault"));
desc18.putEnumerated(sTID("diacVPos"), sTID("diacVPos"), sTID("diacVPosOpenType"));
desc18.putUnitDouble(sTID("diacXOffset"), cTID('#Pxl'), 0);
desc18.putUnitDouble(sTID("diacYOffset"), cTID('#Pxl'), 0);
desc18.putUnitDouble(sTID("markYDistFromBaseline"), cTID('#Pxl'), 0);
desc18.putEnumerated(sTID("baseline"), sTID("baseline"), cTID('Nrml'));
desc18.putEnumerated(sTID("strikethrough"), sTID("strikethrough"), sTID("strikethroughOff"));
desc18.putEnumerated(cTID('Undl'), cTID('Undl'), sTID("underlineOff"));
desc18.putBoolean(sTID("ligature"), true);
desc18.putBoolean(sTID("altligature"), false);
desc18.putBoolean(sTID("contextualLigatures"), true);
desc18.putBoolean(sTID("alternateLigatures"), false);
desc18.putBoolean(sTID("oldStyle"), false);
desc18.putBoolean(sTID("fractions"), false);
desc18.putBoolean(sTID("ordinals"), false);
desc18.putBoolean(sTID("swash"), false);
desc18.putBoolean(sTID("titling"), false);
desc18.putBoolean(sTID("connectionForms"), false);
desc18.putBoolean(sTID("stylisticAlternates"), false);
desc18.putBoolean(sTID("ornaments"), false);
desc18.putEnumerated(sTID("figureStyle"), sTID("figureStyle"), cTID('Nrml'));
desc18.putEnumerated(sTID("baselineDirection"), sTID("baselineDirection"), sTID("withStream"));
desc18.putEnumerated(sTID("textLanguage"), sTID("textLanguage"), sTID("englishLanguage"));
var desc19 = new ActionDescriptor();
desc19.putDouble(cTID('Rd '), 0);
desc19.putDouble(cTID('Grn '), 0);
desc19.putDouble(cTID('Bl '), 0);
desc18.putObject(cTID('Clr '), sTID("RGBColor"), desc19);
var desc20 = new ActionDescriptor();
desc20.putDouble(cTID('Rd '), 0);
desc20.putDouble(cTID('Grn '), 0);
desc20.putDouble(cTID('Bl '), 0);
desc18.putObject(sTID("strokeColor"), sTID("RGBColor"), desc20);
desc17.putObject(sTID("defaultStyle"), cTID('TxtS'), desc18);
desc16.putObject(sTID("paragraphStyle"), sTID("paragraphStyle"), desc17);
list3.putObject(sTID("paragraphStyleRange"), desc16);
desc2.putList(sTID("paragraphStyleRange"), list3);
var list4 = new ActionList();
desc2.putList(sTID("kerningRange"), list4);
desc1.putObject(cTID('Usng'), cTID('TxLr'), desc2);
executeAction(cTID('Mk '), desc1, DialogModes.NO);
};
И ещё кое-что добавлю. Я не уверен, но кажется не все низкоуровневые функции выполняются быстрее стандартных методов — однажды мне показалось, что перемещение слоя на заданное количество пикселов по X и Y выполнялось быстрее именно стандартным методом. Экспериментируйте.
Пасхальные яйца
Без этого нельзя ну никак. В скрипте есть несколько пасхалок, и некоторые из них видны только мне в исходном коде (например, одна функция называется fireStarter). Но есть те, которые я оставил для своих пользователей. Ниже спойлер, кому интересно.
«Нужно боольше золота!». Если несколько раз кликнуть по кнопке info, то — правильно, появляются некоторые фразы из Warcraft III вместо справки.
Морской бой. Не совсем он, на самом деле. Скорее, что-то по его мотивам. Если в одном из полей, где пользователь заводит интервалы между элементами месяца ввести не цифру, а слово «egg», то будет предложено сыграть в морской бой. Игрок выбирает количество выстрелов, мины, авиаудары и идёт искать вражеские корабли. Если топит все корабли — выиграл, иначе — проиграл, всё просто.
Справочные материалы к скрипту
Честно скажу, для меня это был первый случай, где документацию я писал без фанатизма, но с удовольствием. Составил я её очень подробно, настолько, насколько умею это делать вообще. Скооперировавшись с подругой, получил отличный (IMHO) перевод руководства пользователя. Так же она помогла мне переводить элементы интерфейса, подсказки и многие другие вещи в скрипте, за что ей огромное спасибо.
Скрипт не выглядит интуитивно понятным в некоторых режимах, поэтому в качестве дополнения к документации сделал коротенькие видеоуроки. Всего их получилось около 10-12 штук, где я постарался максимально отобразить все возможности скрипта.
И для полного комплекта неплохо было бы иметь свой маленький форум для вопросов. Для этого я воспользовался сервисом «Google группы».
Плюсом, я добавил к скрипту директорию «Examples» с созданными календарями. Один из них перекидной с фотографиями моего города, сделанные лучшим другом — отдельная гордость.
На мой взгляд, всего этого должно быть достаточно для моего пользователя.
Разумеется, я добавил все ссылки — на руководство, видеоуроки и форум прямо в справочное меню скрипта. Ролики и форум находятся в интернете, руководство прилагается к скрипту, но может быть дополнительно скачано из моей личной публичной папки на диске Google (помимо английской версии, есть и русская).
Открытие магазинчика
«И разве мой талант, и мой душевный жар не заслужили скромный гонорар?»
Скрипт получился рабочим и многофункциональным, поэтому я решил, что он кому-нибудь окажется полезен. Я выбрал довольно популярную нынче площадку с умеренными процентами, выставил всё, что у меня есть, протрубил в некоторых местах о запуске и сразу пошёл спать — я очень сильно переживал по поводу запуска, поэтому о результатах решил узнать на свежую голову.
В конце
Весь этап разработки, от идеи до выхода в свет, уложился в период с середины октября 2015 года до 28 сентября 2016. Сезоны менялись — осень, зима, весна, лето, снова осень — а мои мысли были заняты этим проектом. Если вы захотите спросить меня — а стоило ли оно того, то отвечу однозначно — для меня стоило точно. Я получил идеальный для себя результат, многократно превзошедший оригинальную задумку — что может быть лучше?
Теперь я хочу только одного. Отдохнуть. Отпустив результат работы в свободное плавание, я почувствовал тоску, облегчение и невероятную усталость. Да, есть ещё поддержка и прочие вещи, которые требуют моего внимания, но это уже другое. А вообще, уже есть идеи, как ещё его улучшить, или даже сделать совершенно новую версию с другим подходом. Но чуть позже.
А, из побочных эффектов — я стал постоянно обращать внимание на календари, оказавшиеся поблизости. Каждый раз я задаю себе вопрос — а мой скрипт так сможет? И теперь в большинстве случаев я отвечаю себе «Да». Бывают и ответы «нет», конечно, но это особые календари, где, к примеру, все числа закручены в спираль. Хотя, если написать скрипт… Впрочем, нет, не сейчас.
Код писался в свободное время, временами и по ночам, я был одержим этим проектом, спасибо жене за понимание.
И всем, кто осилил эту статью – вы славные ребята. Надеюсь, каждый из вас нашёл что-нибудь полезное здесь для себя.
P.S. Если кому понравился котяня, то он вот здесь (~ 4 Mb).
Дополнительные материалы:
Документация Adobe Photoshop — лично я использовал документацию к версии CS5 (для основного кода) и CS2 (там есть раздел по UI)
Scripting Listener — плагин на Photoshop CC от Adobe для Windows и MacOS
Adobe ExtendScript Toolkit — версии CS3-CS5 и версия СС
Автор: morgreek