Уважающая себя фирма думает о Новом годе загодя — хороший календарь планируется ещё летом. С помощью скриптов вы сможете быстро сделать календарную сетку и перейти к более творческой части проекта…
Мы продолжаем серию статей о скриптах в Adobe Illustrator CS, начатую в 2004 г. («Скрипты под Illustrator: долой рутину!», ? 7, с. 92; «Визитки на конвейере», ? 8, с. 80; «Мал золотник, да дорог», ? 10, с. 88). Давайте рассмотрим скрипт, создающий календарь, ведь расположение вручную в определённом порядке около полутысячи объектов отнимает уйму времени и сил, требует большого внимания. Чего мы хотим от скрипта?
- Диапазон действия — ближайшие 10 лет.
- Поддержка двух языков — английского и русского.
- Создание календарей двух типов — с горизонтальным (портретная ориентация) и вертикальным (дни недели с краёв) расположением недель.
Заботимся об удобстве
Пишем мы для себя, поэтому постараемся, чтобы скрипт всё делал сам. Самое главное — найти такие параметры, чтобы календарь вписался в отведённый формат. Именно этап «пристрелки» отнимает львиную долю времени — вывод календаря на экран получается намного быстрее. Сперва реализуем создание сетки всего нескольких месяцев — для быстрой «пристрелки» к занимаемому формату. Тогда не придётся терять время на многократный просчёт всего года.
Чтобы добиться максимальной гибкости в оформлении, задействуем стили текста (новинка Illustrator CS): меняя стили, будем автоматически обновлять все связанные с ними элементы. Рабочим дням присвоим один стиль, выходным — другой, месяцам — третий (меняются кегль и цвет).
Что касается гарнитуры текста, то из-за ограниченности скриптов в части взаимодействия с пользователем (выбрать шрифт из установленных в системе нельзя) нам доступен лишь шрифт по умолчанию (прописан в стиле Normal Character Style). Поэтому, чтобы на этапе «пристрелки» видеть реальную картину, желательно сразу же в этом стиле задать нужную гарнитуру (проще всего отредактировать конфигурационный файл Adobe Illustrator Startup_CMYK в папке C:\Program Files\
Adobe\Illustrator CS\Plug-ins). Но универсального решения тут нет, в зависимости от используемого шрифта так или иначе придётся «подгонять» параметры календаря — здесь-то и пригодятся стили.
Постараемся, чтобы скрипт выполнял как можно больше операций самостоятельно. Пусть он сам рассчитывает расстояния между днями и месяцами по горизонтали и вертикали, размер кегля и т. д. Определим минимум информации, достаточной, чтобы скрипт вписал календарь в требуемые размеры.
Сначала — расчёты
Для автоматического определения всех параметров в качестве отправной точки примем кегль дней (daySize): именно он определяет главное качество календаря — читабельность. От него будут зависеть расстояния между соседними днями по горизонтали и вертикали (соответственно, stepX и stepY), названиями дней недели и самими днями (DayOfWeekTоDays — DoWtoDays), месяцами (monthBetween) и положение названия месяца (monthBBox). Основные расчётные параметры календаря см. на рис. 1.
Рис. 1. Проектирование внешнего вида календаря — самый ответственный этап («вручную» отнимает львиную долю времени) |
Для построения календаря на год достаточно задать его габаритные размеры: по ним определятся размеры месяца, а уже через них можно выйти на размер дней. В общем случае кегль дней зависит от количества недель в месяце, которое может колебаться от 4 (февраль) до 6. Усредняя, можно было бы взять 5,5 недель, но в 2003 г. 10 месяцев имели 6 недель (в среднем — 5,8). Поэтому усредним ещё раз: (5,5+5,8):2=5,65. Зная размеры, занимаемые месяцем, можно точно «вписать» в него среднестатистическое количество дней (28–31).
После определения кегля дней остаётся только расставить их в нужном порядке для каждого месяца. К сожалению, механизм скриптинга в Illustrator CS не позволяет задавать позицию базовой линии символов напрямую — только положение контейнера, их содержащего. Позиция текста в нём зависит от кегля и типа гарнитуры (последнее мы учесть не можем). Продумаем переход от размера контейнера для текста к кеглю шрифта: зная, что высота шрифта в кеглях соответствует его величине в пунктах, а пункты — единицы измерения в Illustrator CS по умолчанию, достаточно задать связь по формуле:
daySize = 0.8 * bBoxHeight
В зависимости от шрифта соотношение можно менять, но удобнее менять результат с помощью стилей.
Учитываем расположение недель
Сначала рассмотрим календарь с вертикальным расположением недель. Получаем для высоты текстового контейнера и смещения названия месяца:
bBoxHeight = 0.9 * sel[0].height / (7 + 2);
monthBBox = bBoxHeight * 2;
где 7 и 2 — количество элементов по высоте и смещение названия месяца относительно блока с днями (см. рис. 1).
Шаг по вертикали для дней установим равным высоте текстового контейнера:
stepY = bBoxHeight;
Шаг между неделями определим, разделив фактическую ширину месяца на среднестатистическое количество недель в месяце (названия дней недели не учитываем):
stepX = 0.9 * sel[0].width / 5,65;
Рис. 2. Задание размеров календарной сетки для обоих типов календарей |
Из рис. 2 видно, что при горизонтальном расположении календаря можно учесть названия дней недели, поэтому запас на них при формировании месячной сетки оставлять не нужно. В данной раскладке по высоте размещаются: среднее количество недель, ряд с названиями дней недели — bBoxHeight (находится на расстоянии DoWtoDays от самих недель) и название самого месяца (его смещение больше, чем при вертикальном положении, на размер, занимаемый днями недели) — рис. 1. Получим:
bBoxHeight = 0.9 * (sel[0].height / (5.65 + 1 + 2));
monthBBox = 3 * bBoxHeight;
Для шага между днями по горизонтали и вертикали:
stepY = 0.9 * sel[0].width / 7;
stepX = stepX = bBoxHeight
Я не ошибся, поменяв оси местами (далее мы поймём, почему).
Вот, пожалуй, и всё, что касается отличий двух вариантов календаря. Оставшиеся параметры — смещение названий дней недели и расстояние между месяцами — одинаковы в обоих случаях:
DoWtoDays = daySize;
betweenMonthes = 2 * (1 — 0.9) * sel[0].width;
В принципе, в зависимости от выбранного шрифта и общего дизайна могут понадобиться несущественные отклонения величин в ту или иную сторону: приведённые соотношения отражают лишь личные предпочтения автора. Если же по каким-то причинам автоматический расчёт значений вас не устраивает, можно легко изменить начало скрипта, чтобы вводить требуемые параметры вручную.
Последние приготовления
Оставшиеся значения, которые мы использовали в сценарии, — названия месяцев (NoM, Name of Month) и дней недели (DoW, Day of Week) на русском и английском языках — запишем в виде массивов. Для удобства включим в них обе языковых версии: для получения дня недели на английском достаточно будет в соответствующем массиве сделать смещение на 7 записей, для месяца — на 12.
NoM = new Array
("Январь","Февраль", "Март","Апрель","Май", "Июнь","Июль","Август",
"Сентябрь","Октябрь", "Ноябрь","Декабрь", "January","February",
"March","April", "May","June","July", "August","September",
"October","November","December");
DoW = new Array ("Пн","Вт","Ср","Чт","Пт","Сб","Вс","Mo",
"Tu","We","Tu","Fr","Sa","Su");
Зададим количество дней в каждом месяце (DpM, Days рer Month). Учтём, что в феврале могут быть 28 или 29 дней, поэтому проверим каждый год на високосность. Если остатка от деления номера года на «4» нет, год — високосный, если есть — обычный. Для определения текущей даты воспользуемся свойствами объекта Date (из JavaScript), у которого существует метод getFullYear(), выдающий номер текущего года.
today = new Date();
if (today .getFullYear() % 4 == 0) { feb = 29 } else { feb = 28 }
DpM = new Array (31,feb,31,30,31,30,31,31,30,31,30,31);
Начальные точки для расчётов — дни недели, с которых начинаются охватываемые года. Чтобы не мучиться с их вычислением, зададим значения вручную (1 — понедельник, 2 — вторник и т. д.) на 10 лет вперёд — начиная с 2005 г., в котором Новый год приходится на субботу:
NewYears = new Array (6,7,1,2,4,4,6,7,2,3);
Для установки связи между годом и соответствующим ему значением в массиве NewYears из значения каждого года будем вычитать 2005: в результате получим число, которые будет указывать порядковый номер года в массиве (2005 — «0», 2006 — «1» и т. д.). Нам останется только проверять, что год не выходит из допустимого диапазона:
if ((2015 >= year) && (year >= 2005)) { NewYear = NewYears[ year-2005 ] } else {alert ("Year is out of range!")}
Берём в руки краски
Настал черёд задать три цвета: рабочие дни — увы, по-прежнему чёрные, выходные — красные, названия месяцев — зелёные. При необходимости все последующие изменения можно будет внести через стили (View->Type->Character->Character Styles). Ради компактности скрипта и повышения читабельности, вместо троекратной процедуры описания цветов (для каждого цвета своя), представим её в обобщенном виде — через функцию, которой каждый раз будем передавать определённые нами цветовые компоненты.
function color (c,m,y,k) {
clr = new CMYKcolor();
clr .cyan = c;
clr .magenta = m;
clr .yellow = y;
clr .black = k;
return clr }
Функция color() использует четыре значения, которые мы определили как стандартные компоненты триадных красок. Там, где она будет вызываться, вместо неё подставится значение, идущее после стандартного оператора выдачи результата (return), т. е. будет создан новый цвет в модели CMYK. Таким образом, вместо 5 строк на каждый цвет мы обойдёмся всего 7. Теперь перейдём непосредственно к описанию цветов: значения в скобках передадутся соответствующим параметрам объекта clr.
colorWorkDay = color (0,0,0,100);
colorWeekend = color (0,100,100,0);
colorMonth = color (100,0,100,0);
В принципе, эти определения можно опустить и необходимые значения указывать непосредственно в соответствующих местах сценария, но мы пожертвуем этим для большей читабельности скрипта.
Стили нам помогут!
Ранее для оформления текста мы решили использовать стили. Стиль для рабочих дней назовём "WorkDay" и зададим его параметры: размер шрифта (daySize), цвет — чёрный (colorWorkDay). Поскольку эти свойства являются принадлежностью стиля отдельных символов, задаются они через свойство characterAttributes (в отличие от форматирования, которому в модели Illustrator CS соответствует paragraphAttributes). Стиль для выходных дней будет основан на "WorkDay", отличаясь только цветом заливки (colorWeekend). Стиль для названий месяцев зададим как "Month" и установим для него размер шрифта в 1,5 раза больше, чем у дней, а цвет — зелёный (colorMonth).
charStyle_1 = aD.characterStyles.add("WorkDay");
charStyle_2 = aD.characterStyles.add("Weekend");
charStyle_3 = aD.characterStyles.add("Month");
charAttr_1 = charStyle_1.characterAttributes;
charAttr_2 = charStyle_2.characterAttributes;
charAttr_3 = charStyle_3.characterAttributes;
charAttr_1.size = daySize;
charAttr_2.size = charAttr_1.size;
charAttr_3.size = charAttr_1.size*1.5;
charAttr_1. fillColor = colorWorkDay;
charAttr_2. fillColor = colorWeekend;
charAttr_3. fillColor = colorMonth;
Все основные моменты описаны, нюансы учтены — переходим к формированию календарной сетки.
Диалог пользователя
При максимальной автоматизации создания календаря минимальный набор параметров таков: размеры опорного объекта-контейнера (должен быть выделен) для среднестатистического месяца; год, на который календарь создаётся; язык (английский/русский); расположение недель (горизонтальное/вертикальное). Остальные параметры будут рассчитываться на основе исходных. В принципе, от последнего пункта можно отказаться — необходимое направление недель определится также автоматически. Например, если ширина опорного объекта больше его высоты, логично вертикальное расположение, если нет — горизонтальное. Как вы помните, для ускорения этапа «пристрелки» мы предусматриваем возможность создания нескольких тестовых месяцев.
Чтобы заново не описывать ссылки на классы используемых объектов, воспользуемся уже созданным библиотечным скриптом:
// @include "~/ predefined.js".
Поскольку раньше мы не имели дела с текстом, пришла пора добавить в него строку со ссылкой на класс, соответствующий контейнерам для текста:
tF = aD.textFrames;
После проверки на наличие в документе выделенного объекта, все необходимые параметры передадим скрипту из окна диалога пользователя.
if (sel[0]) {
value = prompt ("Specify the required values in the order: \nlanguage (Ru / Eng), year, number of monthes,\ndays
orientation (Horizontal / Vertical):" , "Ru, 2005, 2");
Для «пристрелки» будут создаваться 2 месяца — достаточно, чтобы оценить будущий вид календаря. Для считывания параметров используем знакомый метод — через функцию split, которой в качестве разделителя значений укажем запятую с пробелом «, ».
if (value != null) { splitString = value.split(", ")
lang = splitString [0];
year = splitString [1];
numMonthes = splitString [2];
dir = splitString[3];
if (dir == null) { if (sel[0].width > sel[0].height ) { dir = "V"
} else { dir = "H"} }
Последние 2 строчки позволяют задать расположение дней недели вручную. Если же оно явно не задано (по умолчанию), то будет определено скриптом.
Месяц за месяцем
Процесс разобьём на три этапа: помесячный вывод дней года; добавление названий дней недели, месяцев и расстановка сформированных месяцев на листе. Рассмотрим создание календаря на 2005 г. — для остальных принцип будет тем же (необходимые начальные значения будут браться из массива NewYears). Начнём основной цикл — вывод дней для заданного количества месяцев.
Забегая вперёд, скажу, что положение текущего месяца будет устанавливаться относительно предыдущего, поэтому для января зададим смещение жёстко:
monthTranslateX = 0;
Шаг первый — считывание дня недели первого дня года (NewYears) и количества дней в первом месяце (из массива DpM). День недели, приходящийся на начало каждого месяца (first), определяется по дню недели, которым оканчивается предыдущий (last), кроме января, у которого начало месяца совпадает с Новым годом.
for ( month=0; month