Методика разработки сцен на 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 |