Методика разработки сцен на PostScript Урок 4 | Занятие четвёртоеНачиная весной эту серию уроков у меня было сомнение -- хватит ли материала на семь занятий. Задав в Интернет поиск по ключевому слову PostScript и получив сообщение, что найдено более 99 тыс. документов, где оно встречается, я успокоился - об этом языке писать можно до 2000 года. Кроме того, обнаружилась книга по PostScript и на русском языке. Это изданный Физматлитом в 1993 г. учебник: Ф.Доймлинг, Д.Стиллеску "Язык программирования PostScript. 20 уроков быстрого освоения". В ней очень детально описывается то, что нами будет пройдено на четырех первых уроках. Следует отметить, что наши занятия методически следуют книге "PostScript Language Tutorial and Cookbook" (Adobe Systems & Addison Wesley, 1987), являющейся де-факто описанием стандарта языка и потому именуемой "Голубой книгой". Тем у кого это издание есть, я рекомендую подождать седьмого урока, который будет отчасти посвящён текущему состоянию языка, литературе и другим не описанным в "Голубой книге" вопросам. ЦиклыВ языке PostScript имеется три основных конструкции циклов: простой цикл, индексируемый цикл и условный цикл. Простой цикл неявным образом уже использовался в примерах предыдущего занятия. Он реализуется с помощью оператора repeat, который берет из стека два операнда: счетчик цикла и повторяемую процедуру. Например, для очистки стека мы использовали следующий цикл: 4 {pop} repeat
Здесь всё достаточно прозрачно, непривычно, может быть, только то, что в качестве одного из аргументов выступает повторяемая процедура. Индексируемый цикл напоминает широко известную по Бейсику конструкцию for... to... next. Оператор for берет из стека четыре операнда: начальное значение счетчика цикла, его приращение, конечное значение счетчика цикла и повторяемую процедуру. За исключением последнего операнда всё выглядит как и в обычной конструкция for. Следует учитывать, что непосредственно перед выполнением этой процедуры for помещает в стек текущее значение счетчика и, если он не используется, то его следует оттуда явным образом удалять(!). Следующая строка напечатает звездочку через каждые 15 единиц на странице: 0 15 450 {0 moveto (*) show } for
Вторая важная особенность for в том, что его операнды не обязательно должны быть целыми числами. Вот пример использования этого для возможной модификации шрифта:
/Helvetica findfont
30 scalefont
setfont
/printword {
0 0 moveto
(PC Magazine) show
} def
200 300 translate
.95 -.05 0 % начало приращения, конец
{setgray printword -1.5 translate} for
1 setgray printword
showpage
Условный цикл соответствует конструкции repeat...until в Паскале. Строится он из двух операторов: loop и exit. Опертор loop повторяет выполнение процедуры до тех пор пока в ней не встретится оператор exit, который заканчивает циклическое выполнение, причем не только в конструкции loop, но и в for, repeat и forall (об этой специальной форме цикла будет расказано ниже). Если в повторяемой процедуре нет оператора exit, то цикл будет бесконечным, например: { (PostScript) show } loop
будет бесконечно печатать слово PostScript. Нарисуем незамысловатую последовательность кругов просто, чтобы продемонстрировать работу loop - exit.
%%% Определение процедур
/pagewidth 8 72 mul def % Ширина страницы
/circle { % Рисование круга
x y radius
0 360 arc
stroke
} def
/new-x { % Новая позиция для следующ. круга
x radius add % Это эквивалентно:
/x exch def % х=х+radius
} def
/DoLineOfCrle { % Рисуем линию из окружностей
/y exch def % заносим значения из стека в переменные
/radius exch def
/x 0 def
{ % условный цикл
x pagewidth le % центр нового круга в пределах границы?
{circle new-x} % да: рисуем и вычисл новую позицию
{exit} % иначе - выход из цикла
ifelse
} loop
} def
%%%% Рисуем две линии кругов
15 200 DoLineOfCrle
25 200 DoLineOfCrle
showpage
МассивыPostScript работает с одномерными массивами - (векторами), которые определяются как набор объектов (возможно разного типа), заключенный в квадратные скобки. Так
[ (PC Magazine) 1991 54]
и
[ (Julia) 4 12 78]
массивы, причем первый элемент этих массивов - строка. (Такие структуры в других языках, кроме АПЛ, обычно называют записями). Операции внутри квадратных скобок выполняются по обычным правилам PostScript, так после вычисления [(add) 10 5 6 mul sub] получим массив из двух элементов. Массив может быть также определен с помощью оператора array, который берет из стека число и создает массив такой длины. 8 array Эта строка оставит в стеке массив из 8 элементов. Его элементы -- NULL-объекты. Когда массив создается строкой типа [1 2 3 8 ] то квадратные скобки, используемые для его записи играют в PostScript большую роль, чем это кажется на первый взгляд. Открывающая (левая) квадратная скобка оставляет в стеке объект, называемый маркером (mark). После маркера интерпретатор просматривает строку программы дальше и помещает в стек все встречающиеся ему объекты до правой квадратной скобки. Эта скобка является оператором, создающим массив из хранящихся в стеке объектов: от вершины до маркера. При этом маркер удаляется из стека, а массив остаётся. Массивы, строки и словари -- всё это примеры объектов сложных типов. Их значения хранятся PostScript отдельно от самого объекта (то есть PostScript работает в этом случае не со значением, а с указателем на него). Так операция dup над строкой дублирует объект, но не его значение, которое в этом случае разделяется двумя объектами. Массив в PostScript индексируются с нуля. Для работы с элементами массивов служат операторы put и get. Оператор put берет из стека три аргумента: массив, индекс элемента в массиве и объект. Он помещает объект в массив в позицию, заданную индексом:
/MyArray 12 array def
MyArray 5 (Jerry) put
У оператора get два аргумента: массив и индекс. Он возвращает в стеке элемент массива с заданным индексом. После выполнения строки [0 1 2 3 4 5] 5 get в вершине стека будет число 4. Для работы с массивами также необходим оператор length, возвращающий длину массива (то есть число его элементов). Следующая программа распечатывает массив, находящийся в стеке.
/LeftM 60 def
/TmpString 40 string def
/Helvetica findfont
11 scalefont
setfont % задали шрифт
/newln {
currentpoint 15 sub exch pop % y-15
LeftM exch moveto
} def
/printarr { % в стеке массив
/arr exch def % поместить массив в переменную
0 1 % параметры цикла: от шаг
arr length 1 sub % до (ДлинаМассива - 1)
{
arr exch get % следующий элемент
TmpString cvs % преобразуем вго в строков тип
show newln % печатаем и нач. новую строку
} for
} def
%%%%%% Основная программа %%%%%
LeftM 400 moveto % печатаем отсюда
% задаем массив
[(Julia) % строка
15 % число
/SimplName % литерал
[8 3 4] % массив
{NewLN} % исполняемый массив
LeftM % переменная
]
printarr % печатаем его
showpage
Перед каждым выполнением цикла оператор for помещает в стек счетчик, который используется в качестве индекса в строке arr exch get. (В этой строке берется не сам массив из стека, а ссылка на него по имени переменной, поэтому и делается exch.) Результат работы программы
Julia
15
SimplName
--nostringval--
--nostringval--
60
Как видно из примера обычные действия над массивом в PostScript достаточно утомительны. Так как наиболее частой операцией над массивом в этом языке является применение некоторой процедуры к каждому его элементу, то для этого есть очень красивый оператор forall, который применяет к массиву-аргументу заданную процедуру. Предыдущий пример можно записать с его помощью более компактно:
arr {30 string cvs show} forall
Полиморфные операторыОператоры length, put, get и forall работают как с массивами, так и со строками и словарем. Так length -- возвращает длину строки, массива или число пар ключ-значение в словаре. Еще два оператора, aload и astore облегчают загрузку и сохранение сразу всего массива. Так оператор aload берет в качестве аргумента массив, заносит в стек по очереди все его элементы, а затем заносит туда сам массив. Так строка [1 2 3] aload оставит в стеке: 1 2 3 [1 2 3] оператор astore выполняет обратную функцию. 1 2 3 3 array astore создаст массив [1 2 3] Еще раз о шрифтахДля печати текста часто требуются различные операции по выравниванию слов по границам страницы, выравниванию промежутков между буквами (кернинг), чтобы напечатанный текст выглядел приятно. Для этой цели в PostScript имеется 4 варианта оператора show:
Текущий символ и символ, следующий за ним передаются этой процедуре как аргументы. Так строка
{pop pop (-) show} (World) kshow
напечатает его с дефисом между каждой парой букв: W-o-r-l-d Оба символа удаляются из стека, так как данная прцедура их не использует. В основном оператор предназначен для кернинга, но может быть использован и в других целях. Кодирование шрифтаКаждый словарь шрифта содержит описание символов в соответствии с некоторой кодовой таблицей, в частности это может быть ASCII. Таким образом каждый символ имеет свой цифровой код -- число от 0 до 255. Кодовая таблица не является постоянной, ее можно изменять из приложения. Коды символов можно использовать двумя способами:
Многие шрифты имеют символы не входящие в стандартную кодировку и кроме того, часто такие символы отсутствуют на клавиатуре. Чтобы посмотреть как кодируются символы того или иного шрифта, напишем программу, которая распечатывает кодовую таблицу любого заданного шрифта.
/Helvetica findfont
12 scalefont
setfont
/cod 3 string def
/char 1 string def
/newline {
currentpoint 13 sub
exch pop LM
exch moveto
} def
/prtnum { % в стеке код
cod cvs show
} def
/prtchar { % в стеке код
char 3 - 1 0 roll put
char show
} def
/prtall {
dup prtnum () show
prtchar newline
} def
%%%% основная программа %%%%
/LM 72 def % печатаем первую колонку
LM 450 moveto
12 1 60 {prtall} for
/LM 144 def % печатаем вторую колонку
LM 144 moveto
6 1 100 {prtall} for
showpage
Преобразование шрифта задается матрицей преобразования (трансформации), представляющей из себя массив из шести чисел. Они определяют, как координаты пространства пользователя должны быть трансформированы в позицию на текущей странице. Элементы этого массива определяют масштаб, ориентацию и позицию осей х и у. Графическое состояние программы работает с текущей матрицей трансформирования, определяющей, как на текущей странице позиционируются все изображения. Операторы translate, rotate и scale для модификации координат пространства пользователя изменяет соответствующие элементы этой матрицы. Отдельная матрица преобразования ассоциируется с каждым шрифтом, определяя как символы этого шрифта должны печататься на текущей странице. Эта матрица шрифта может быть изменена непосредственно оператором makefont, который берет из стека словарь шрифта и шестиэлементный массив, а затем преобразует с помощью этого массива матрицу шрифта и помещает измененный словарь шрифта обратно в стек. Чтобы изменить масштаб шрифта по х и по у, нужно задать соответственно значения чисел m и n в матрице [m 0 0 n 0 0 ] Так строки
/Helvetica findfont
6 scalefont
/Helvetica findfont
[6 0 0 6 0 0] makefont
выполняют ровно одно и тоже: создают шрифт Helvetica размером 6 пунктов. Действие оператора makefont однако значительтно шире, чем у scalefont. Он позволяет как угодно сжимать и растягивать текст. Следующая программа из "Голубой книги" показывает технику работы с makefont.
/basefont /Helvetica findfont def
/LM 72 def
/newline {
currentpoint 13 sub
exch pop LM
exch moveto
} def
%%%% основная программа
LM 400 moveto
% обычная печать
basefont [12 0 0 12 0 0] makefont setfont
(Пример нормальной печати) show newline
% растянутый.
basefont [17 0 0 12 0 0] makefont setfont
(Растянутый шрифт) show newline
% сжатый
basefont [7 0 0 12 0 0] makefont setfont
(Сжатый шрифт) show newline
% наклонный
basefont [12 0 6.93 12 0 0] makefont setfont
(Наклонный шрифт) show
showpage
Программа печатает четыре строки, каждый раз преобразуя текущий шрифт, с помощью различных матриц шрифта. Во второй и третьей строке изменялся масштаб по горизонтали (Вы можете поэксперементировать, задавая различный масштаб и по вертикали, но не забудьте поменять константу в процедуре newline, чтобы строки не наползали одна на другую). Интересна последняя строка. Третье число в матрице 6.93 представляет собой результат умножения у на tg 30 градусов. Таким образом задан наклон шрифта. Вы можете поэкспериментировать с матрицами преобразования, чтобы лучше почувствовать, что происходит с шрифтом. Все эти преобразования пространства пользователя можно выполнить также с помощью операторов scale и setmatrix. Однако в отличие от makefont их действие распространяется на все, что печатается на текущей странице. Если нужно сжать, растянуть или наклонить текст, то следует пользоваться только makefont. 20.11.2001 |