Історія однієї одержимості, або як я писав календарний скрипт для 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, другий — DOS або 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 (і це віконце виходило на весь екран і навіть не було видно кнопки OK), то вікно скрипта сгенерируется нормально, як треба. Інші «алерти» з порожніми рядками і нескладним текстом не допомагали, тільки з масивом шрифтів.
Через деякий час з'явилося друге рішення, яке працює, але я не розумію, чому.
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('S '), 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('S '), 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("порядкові числівники"), 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('рубін'), '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('S '), 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("порядкові числівники"), 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('M '), 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('S '), 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('S '), 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("порядкові числівники"), 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('рубін'), '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('S '), 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("порядкові числівники"), 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('M '), desc1, DialogModes.NO);
};

І ще дещо додам. Я не впевнений, але здається не всі низькорівневі функції виконуються швидше стандартних методів — одного разу мені здалося, що переміщення шару на задану кількість пікселів по X і Y виконувалося швидше саме стандартним методом. Експериментуйте.

Великодні яйця
Без цього не можна ніяк. В скрипті є кілька сюрпризів, і деякі з них видно тільки мені у вихідному коді (наприклад, одна функція називається fireStarter). Але є ті, які я залишив для своїх користувачів. Нижче спойлер, кому цікаво.
Тик3е вересня. Кнопка Simple Mode має іконку, яка зображує (я намагався, принаймні) відривний календар з цією датою.
«Потрібно боольше золота!». Якщо кілька разів клацнути по кнопці info, то — правильно, з'являються деякі фрази з Warcraft III замість довідки.
Морський бій. Не зовсім він, насправді. Скоріше, щось за його мотивами. Якщо в одному з полів, де користувач заводить інтервали між елементами місяці ввести не цифру, а слово «egg», то буде запропоновано зіграти в морський бій. Гравець вибирає кількість пострілів, міни, авіаудари і йде шукати ворожі кораблі. Якщо все топить кораблі — виграв, інакше — програв, все просто.



Довідкові матеріали до скрипта
Чесно скажу, для мене це був перший випадок, де документацію я писав без фанатизму, але з задоволенням. Склав я її дуже докладно, настільки, наскільки вмію це робити взагалі. Скооперувавшись з подругою, отримав відмінний (IMHO) переклад посібника користувача. Так само вона допомогла мені перекладати елементи інтерфейсу, підказки і багато інших речей в скрипті, за що їй величезне спасибі.

Скрипт не виглядає інтуїтивно зрозумілим у деяких режимах, тому як доповнення до документації зробив коротенькі відеоуроки. Усього їх вийшло близько 10-12 штук, де я постарався максимально відобразити всі можливості скрипта.
І для повного комплекту непогано було б мати свій маленький форум для запитань. Для цього я скористався сервісом Google групи».
Плюсом, я додав до скрипта директорію «Examples» з створеними календарями. Один з них перекидний з фотографіями мого міста, зроблені кращим другом — окрема гордість.
<b class=«spoiler_title»Картинки з превьюшки

На мій погляд, всього цього має бути достатньо для мого користувача.
Зрозуміло, я додав посилання на керівництво, відеоуроки і форум прямо в довідкове меню скрипта. Ролики і форум в інтернеті, керівництво додається до скрипта, але може бути додатково скачано з моєї особистої публічної папки на диску Google (крім англійської версії, є і російська).

Відкриття магазинчика
«хіба мій талант, і мій душевний жар не заслужили скромний гонорар?»
Скрипт вийшов робітникам і багатофункціональним, тому я вирішив, що він кому-небудь опиниться корисний. Я вибрав досить популярну нині майданчик з помірними відсотками, виставив все, що у мене є, протрубив у деяких місцях про запуск і відразу пішов спати — я дуже сильно переживав з приводу запуску, тому про результати вирішив дізнатися на свіжу голову.

В кінці
Весь етап розробки, від ідеї до виходу в світ, вклався в період з середини жовтня 2015 року до 28 вересня 2016. Сезони змінювалися — осінь, зима, весна, літо, осінь знову — а мої думки були зайняті цим проектом. Якщо ви захочете запитати мене — а чи варто було воно того, відповім однозначно — для мене коштувало точно. Я отримав ідеальний для себе результат, багаторазово перевершив оригінальну задумку — що може бути краще?

Тепер я хочу тільки одного. Відпочити. Відпустивши результат роботи у вільне плавання, я відчув тугу, полегшення і неймовірну втому. Так, є ще підтримка та інші речі, які вимагають моєї уваги, але це вже інше. А взагалі, вже є ідеї, як ще його поліпшити, або навіть зробити абсолютно нову версію з іншим підходом. Але трохи пізніше.

А, з побічних ефектів — я став постійно звертати увагу на календарі, що опинилися поблизу. Кожен раз я задаю собі питання — а мій скрипт так зможе? І тепер у більшості випадків я відповідаю собі «Так». Бувають і відповіді «ні», звичайно, але це особливі календарі, де, наприклад, всі числа закручені в спіраль. Хоча, якщо написати скрипт… Втім, ні, не зараз.
Код писався у вільний час, часом і ночами, я був тут цим проектом, спасибі дружині за розуміння.
І всім, хто осилив цю статтю – ви славні хлопці. Сподіваюся, кожен із вас знайшов що-небудь корисне для себе.

P. S. Якщо кому сподобався котяня, він ось тут (~ 4 Мб).

Додаткові матеріали:

Документація Adobe Photoshop — особисто я використовував документацію до версії CS5 (для основного коду) і CS2 (там є розділ по UI)
Scripting Listener — плагін Photoshop CC від Adobe <a href=«download.adobe.com/pub/adobe/photoshop/win/13.x/Win_Scripting_Plug-In.zip>Windows, MacOS
Adobe ExtendScript Toolkit — версії CS3-CS5 і версія СС
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.