История одной одержимости, или как я писал календарный скрипт для Photoshop


    Эта статья о том, как я писал скрипт для создания календарей в 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). Но есть те, которые я оставил для своих пользователей. Ниже спойлер, кому интересно.

    Тык
    3е сентября. Кнопка Simple Mode имеет иконку, изображающую (я старался, по крайней мере) отрывной календарь с этой датой.
    «Нужно боольше золота!». Если несколько раз кликнуть по кнопке 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 и версия СС
    Share post

    Comments 21

      0
      Морской бой чудесен. С котиками тоже всё нормально.
      Если по серьёзному, такой вопрос: В листингах длиннющие простыни однотипных вызовов типа:

      	desc18.putBoolean(sTID("alternateLigatures"), false);
      	desc18.putBoolean(sTID("oldStyle"), false);
      	desc18.putBoolean(sTID("fractions"), false);
      


      Не было желания применить какую нибудь кодогенерацию?
        +1
        Да, морской бой самая большая гордость =)
        По поводу кодогенерации — мысли были, но на тот момент я хотел двигаться дальше, раз работало так, как надо. И я на самом деле думаю пхорошенько заняться изучением работы низкоуровневых команд, чтобы по возможности упростить/улучшить такие вещи, мне они самому не нравятся.
        0
        Был у меня небольшой опыт написания скриптов для Фототшопа. Осталось впечатление, что в Adobe приложили все усилия, что бы для их продуктов ни кто никогда и ни за что не писал скриптов.
          0
          Согласен, временами приходится трудновато. Зато, когда всё начинает работать, ощущение, что решил хитроумную головоломку. Лично мне поначалу было трудно разобраться в самой документации, и я был очень удивлён, что можно делать UI — этот раздел был полностью выпилен в документации к CS5, лишь случайно узнал, что он находится в документации к CS2. Возможно, сейчас в CC вернули его.
        • UFO just landed and posted this here
            0
            Звучит на самом деле очень круто, прямо швейцарский нож какой-то. А по какому принципу делалась точная обрезка? Вообще, при наличии желания и времени, думаю, вы сможете всё восстановить — переписать заново то бишь. В конце-концов, почему бы и нет?
            • UFO just landed and posted this here
                0
                после в окошке ткнуть кнопку

                Получается, скрипт как бы работает, но при этом не мешает пользователю выполнять свои операции? Вот это очень интересно, я думал, пока работает скрипт, никуда нельзя тыкать, кроме как самого рабочего окошка. Задали интересную задачку, спасибо)
                VBS меня, кстати, тоже выручает по работе, но я его знаю пока очень поверхностно. Хорошо, когда рабочий процесс умеешь автоматизировать до пары кликов.
            0
            Спасибо за статью! А где скрипт то можно скачать/купить?
              +1
              Спасибо, что прочитали =)
                +1
                Сдается мне нельзя прямые ссылки давать, ищите по «Digital Drawf CalendarMS PRO»

                Первый раз об этой площадке узнал, почему не популярная graphicriver.net?
                  0
                  Просто она у меня больше на слуху, вот поэтому так и вышло. Я увлекаюсь, точнее, учусь, цифровой графике, многие именитые 2d-художники (в т.ч. и наш TamplierPainter) выкладывают свои уроки и прочие материалы туда. Может и не совсем удачный выбор, но мне там нравится.
                0
                «Изначально нужно было получить годовой календарик размером 6 на 2 месяцев.» — для «обоев» на рабочий стол?

                «Просто я хочу создавать красочные календари с фотографиями или иллюстрациями» — никто не запрещает верстать в Photoshop, CorelDraw или Word, но вообще-то, для этих целей существует InDesign.
                  0
                  По первому пункту — ну, на самом деле, да, для теста, в качестве обычной поделки и отправной точки. Потом всё завертелеось.
                  По второму — да, в Индизайне есть встроенный инструмент, поэтому смысла делать скрипт на него особой необходимости и не виделось. Зато я для себя сделал автоматические отмечаловки пользовательских праздников сразу на календаре — к примеру, Чёрная пятница (или Кибер-понедельник) или День рождения друга (можно даже год рождения забить в текстовый файл, скрипт будет возвращать его возраст) и пересоздавать из года в год.
                  Мне показалось это удобным, но я не настаиваю, что нужно делать это в Photoshop — кому как, я просто делал инструмент, который делает удобнее для меня этот процесс в ФШ — и, возможно, найдутся такие же, как и я.
                    0
                    Как «обои», так и настенные календари из примеров в статье, имеют на мой взгляд один, но существенный недостаток: их нельзя использовать по назначению. Для того, чтобы календарём можно было пользоваться, в нём должны быть клетки. Желательно, побольше. О них, увы, забывает большинство дизайнеров календарей. Для себя в прошлом году сделал такой (распечатанный в формате А1 висит на стене).
                      0
                      Ого, выглядит интересно, честно скажу, такие вот не встречал ни разу. Хм, мне даже любопытно стало. Сначала подумал, что по нему сложно искать нужную дату и день недели, но вроде как раз очень быстро получается это определять. А текст внутри (подписи возле дат) насколько сложно читать на А1?
                      И есть ли у вас ещё образцы таких практичных календарей?
                      Да, клетки тоже можно будет приделать — выставить большие интервалы между днями по вертикали и горизонтали, а на фон месяца наложить сетку (то есть задать настройку, которая делает подложку под месяц в виде смарт-объекта, а потом в один из этих смартов поместить сетку, остальные смарты обновятся автоматом).
                      Размер холста ограничивается, по сути, только самим ФШ, и ресурсами компьютера. Можно сделать большой квартальный календарь (который по три месяца) с большими клетками внутри месяца.
                        0
                        Идея «подсмотрена» у rikiki — у меня был их календарь на 2015 год. Я бы купил его и на 2016, если бы не расцветка. У Kleinwaren / von Laufenberg есть хороший, но очень уж строгий. У dot on — забавный двусторонний с наклейками. Ну и так далее. В большинстве своём дизайнерские календари — это увы, про эстетику, а не про функциональность.
                          0
                          Спасибо! Интересные штуки, думаю, даже займусь через некоторое время.
                  0
                  Метод slice() убирает первый (0) элемент массива
                    0
                    Хм. На деле, первым элементом массива и был шрифт Aarcover (Plain):001.001, но он остался на своём нулевом месте. Или вы имеете в виду, что первым элементом затесалось что-то совсем другое? Тогда это многое объясняет.
                      0
                      При опущенных параметрах slice() создаст копию массива

                    Only users with full accounts can post comments. Log in, please.