содержание   назад   вперед


Виктор Кон,   Постскрипт: инструкция к применению

3. Второй уровень программирования

3.1 Глобальные преобразования системы координат

Самой сильной стороной векторных рисунков является то, что они очень легко масштабируются без потери точности. Также их можно перемещать и вращать на любой угол. При этом не надо переписывать все координаты точек, можно выполнить описание фрагмента рисунка в любой системе координат и потом быстро настроить на любую другую систему координат. То есть изменяется не рисунок, а система координат, а это намного проще. Итак стандартно в начале рисования левый нижний угол страницы имеет координаты 0,0. Команда

translate -- берет из стека два числа Y,X и переносит начало координат в точку с координатами X,Y. Это означает, что к координатам всех точек всех объектов перед рисованием будет прибавлены вектор с координатами X,Y. Другая команда

scale -- берет из стека два числа Y,X и умножает все координаты всех точек для всех объектов на два числа, горизонтальные координаты - на X, вертикальные - на Y. Еще одна команда

rotate -- берет из стека одно число A - угол в градусах и производит вращение всех координат всех объектов на этот угол.

Повторное применение команд translate и rotate эквивалентно одной команде с аргументами, равными сумме аргументов, а повторное применение команды scale эквивалентно одной команде с аргументами, равными произведению аргументов. В частности, если нужно применить преобразование к какому либо объекту, а потом отменить его применение к остальным объектам, то нужно выполнить повторное преобразование, которое нейтрализует действие первого преобразования. Так как таких преобразований может быть много, то иногда неудобно вычислять аргументы в явном виде. Для этого в Постскрипте есть две команды:

gsave -- запоминает все параметры рисования, которые были установлены на момент применения этой команды, и

grestore -- восстанавливает параметры, которые были спасены предыдущей командой.

Это касается не только указанных выше операторов, но и самой текущей позиции курсора. Например, команда fill вообще говоря теряет позицию курсора. Поэтому вместо нее разумно всегда использовать процедуру языка vkPS. Ниже перечислены новые процедуры

/gs{gsave}bd -- без аргументов

/gr{grestore}bd -- без аргументов

/tr{translate}bd -- два аргумента

/sca{scale}bd -- два аргумента

/Sc{dup sca}bd -- один аргумент

/r{rotate}bd -- один аргумент

/fir{closepath gs fill gr}bd -- без аргументов

/fr{sg fir}bd -- один аргумент

Вообще говоря, имея возможность поставить текст любого размера на любую позицию, можно без труда написать сколь угодно сложную формулу. Но эта запись не будет простой. С другой стороны, если тексты в Постскрипте масштабируются, то совсем нет нужды масштабировать шрифты. В большом числе случаев достаточно использовать 5 шрифтов, задаваемых одной буквой, которые были описаны ранее. Но есть одна проблема. Иногда очень удобно писать химические формулы или математические вычисления, используя индексы. Индексы рисуются обычно меньшего размера и серединой по вертикали на верхней или нижней части строки. Чтобы упростить процедуру написания индексов, можно ввести такую вот систему процедур

/ind{/Z ed /Y ed /X ed /tt ed X Y rm Z Sc tt s /Z 1 Z div def Z Sc X Y neg rm}bd -- процедура имеет 4 параметра -- text и 3 числа (здесь я указываю прямой порядок записи, то есть именно так, как аргументы будут записаны перед командой). Первые 2 числа указывают сдвиги по горизонтали и вертикали, третье число -- множитель масштабирования. Сама эта команда достаточно сложная, но ее можно использовать в других процедурах. Например,

/ib{/U FS 12 div def 0.8 U mul -3 U mul 0.6 ind}bd -- эта процедура уже имеет только один аргумент -- текст, а коэффициент масштабирования и сдвиги указаны явно. Сдвиги указаны для текста размером 12 pt и масштабируются на реальный размер, который задается переменной FS. Сдвиги соответствуют нижнему индексу.

/it{/U FS 12 div def 0.8 U mul 4 U mul 0.6 ind}bd -- эта процедура эквивалентна предыдущей, но сдвиги соответствуют верхнему индексу.

При использовании стандартных фонтов можно писать латинские и греческие символы в любом порядке, а также индексы. Индексы пишутся тем же фонтом, что и предыдущий текст, если нужно изменить, то можно задать пустой текст с буквой нужного фонта перед применением процедуры индекса. Если же необходимо промасштабировать всю строку форматированного текста, то достаточно вначале и в конце поставить команды масштабирования. Просто для удобства чтения программы можно ввести масштабирующие скобки разного уровня. В vkPS используются следующие процедуры

/is1{1.25 Sc}bd -- /ds1{0.8 Sc}bd

/is2{1.5625 Sc}bd -- /ds2{0.64 Sc}bd

/is3{2 Sc}bd -- /ds3{0.5 Sc}bd

/is4{2.8 Sc}bd -- /ds4{1 2.8 div Sc}bd

/is5{4 Sc}bd -- /ds5{0.25 Sc}bd

Если нужно увеличить текст, то левая процедура является открывающей, а правая - закрывающей, если нужно уменьшить, то наоборот. Но можно использовать и стандартную конструкцию типа gs 3.333 Sc весь нужный текст gr


3.2 Операторы циклов и условные операторы

Операторы циклов - обязательная конструкция вычислительных языков. Полезны они также и при рисовании однотипных фигур. Например, мы хотим нарисовать квадратную сетку линий, в которой каждая следующая линия сдвинута от предыдущей на постоянный шаг. Вместо того, чтобы рисовать сотню линий, можно задать цикл и вся конструкция нарисуется в одну строку текста. В Постскрипте существует простой цикл, цикл с параметром и цикл по условию. Мы рассмотрим сначала простой цикл и цикл с параметром. Затем мы рассмотрим условия и потом уже циклы по условию. Оператор простого цикла называется

repeat -- он берет из стека два объекта: процедуру и число повторений, после этого он повторяет процедуру столько раз, какое значение имеет число. Напомню, что процедура - это произвольный кусок программы, заключенный в фигурные скобки. Соответственно, если процедура имеет параметры, то все они должны быть заранее поставлены в стек, откуда процедура будет их брать. Самый простой пример - рисование лестницы по точкам. Это можно записать так

1 slw 10 10 m 10 0 0 10 10 0 0 10 10 0 0 10 6 {rl} repeat sl -- Здесь мы задаем толщину линии, координаты начальной точки, затем все координаты смещений (в обратном порядке), затем число повторений цикла, саму процедуру (из одного оператора) и команду повторить, а затем нарисовать линию. В этом примере мы не так уж много и сэкономили. Но лестница сама состоит из повторяющихся элементов. Программу можно переписать так

1 slw 10 10 m 3 {0 10 rl 10 0 rl} repeat sl -- Это уже покороче, и самое главное то, что теперь мы можем увеличить число ступенек до 1000 и текст не изменится.

Если использовать переменные, то простой оператор цикла может быть эффективно использован во всех случаях, когда известно число повторений. Однако при вычислении функций одной переменной с постоянным шагом часто используют цикл с параметром. Этот оператор называется

for -- он берет из стека четыре объекта: процедуру, конечное значение параметра, шаг по параметру, начальное значение параметра. Число повторений в нем не фигурирует и равно разности конечного и начального значений, деленной на шаг плюс 1. С помощью этого цикла очень удобно рисовать графики функций, например, как в указанной программе

1 slw gs 10 10 tr 0 0 m 10 10 80 {/X ed X X sin 50 mul l} for sl gr -- Здесь мы рисуем синус из нулевой точки аргумента. Важно понимать следующее. В этом цикле перед каждым повторением процедуры в стек автоматически закладывается число - параметр цикла. В нашем случае это и аргумент функции и координата X точки. Поэтому процедура начинается с того, что приготовленное значение параметра сначала запоминается в переменную. Затем оно снова закладывается в стек, но уже два раза, второй раз оно используется как аргумент функции и умножается на 50. Такой способ вытаскивания параметра в переменную очень надежен и нагляден. Но в нашем случае тот же результат можно было бы получить и с другим кодом, а именно

1 slw gs 10 10 tr 0 0 m 10 10 80 {dup sin 50 mul l} for sl gr -- в данном случае удвоение значения параметра в стеке делается непосредственно.

Я хочу заметить, что все указанные примеры я протестировал. Вы тоже можете это сделать, используя шаблон eps-файла с названием test.eps. В этом шаблоне нет программы, но вы можете ставить все указанные примеры в тело программы перед командой showpage. Цикл for удобен также при рисовании осей координат, когда надо проставить риски с постоянным шагом либо по горизонтали, либо по вертикали. Его также можно использовать при рисовании произвольной функции по точкам. В этом случае достаточно задать числа для значений функции, а значения аргумента с постоянным шагом будет вычислять сама процедура цикла. Его можно также использовать при параметрическом задании кривой, когда обе координаты точек зависят от какого-либо параметра. Так можно рисовать спирали и прочие кривые. Есть еще относительно простой оператор цикла

forall -- он берет из стека два сложных объекта: процедуру и массив, и повторяет процедуру со всеми элементами массива как параметром, то есть число повторений равно числу элементов массива. Вот пример программы.

1 slw 10 10 tr 0 0 m [5 10 15 20] {10 exch rl}forall sl -- Здесь все смещения отрезков по вертикали заданы массивом (в квадратных скобках). К моменту выполнения процедуры координата Y уже в стеке, добавляем координату X и меняем их местами, после чего очередной отрезок кривой готов к исполнению. Этот оператор работает не только с массивами, но и со строками и со словарями, о которых речь пойдет выше.

Перейдем к изучению условных операторов. Результат условия - это два логических объекта true (истина) и false (ложь). Условный оператор выполняет процедуру только в том случае, если условие истинно. Условие можно получить используя операции сравнения. В Постскрипте они записываются как
eg -- (=, равно)
ne -- (!=, не равно)
gt -- (>, больше)
ge -- (>=, больше или равно)
lt -- (<, меньше)
le -- (<=, меньше или равно)
Ясно, что все эти условия проверяются над двумя объектами в стеке, то есть их надо записывать перед записью самих операций. Логические переменные, получаемые в результате проверки условий, можно объединять операциями not, or и xor. Итак первый условный оператор

if -- он берет из стека процедуру, затем логическую переменную, которая получается в результате проверки условия и если логическая переменная равна true, то выполняет процедуру. Очень показателен пример программы вычисления факториала

/sfac 10 string def /fact{dup 1 gt {dup 1 sub fact mul} if }def 10 10 m 5 fact sfac cvs L -- Здесь сначала определяется объект sfac типа строка из 10 пустых символов. Для этого используется команда

string -- она берет из стека число и создает пустую строку с размером, равным значению числа.

Затем определяется процедура fact вычисления факториала. Интересным свойством этой процедуры является то, что при ее определении она использует саму себя. Это совершенно невозможно в вычислительных компилируемых языках, а в Постскрипте это работает, потому что просто в этом месте заново читается и выполняется текст процедуры. Но рекурсия не бесконечная, потому что у нас есть оператор if. Далее идет программа. Процедура fact требует на входе число, на выходе она тоже оставляет число. Это число надо конвертировать в текстовую строку. Это делает команда

cvs -- она берет из стека строку и число и возвращает новую подстроку, в которой число представлено в текстовом виде. Используется столько знаков, сколько нужно, но не больше, чем было в исходной строке (иначе ошибка).

Наконец команда L рисует эту строку на установленную заранее позицию курсора. Результатом выполнения программы является рисунок числа 120. Более сложный оператор условия

ifelse -- берет из стека вторую процедуру, затем первую процедуру и затем логическую переменную как результат выполнения условия. Если логическая переменная истинна, то выполняется первая процедура, а если нет, то выполняется вторая процедура.

Наконец, перейдем к описанию условного цикла. Он организуется с помощью двух команд. Первая

loop -- берет из стека процедуру и выполняет ее сколько угодно раз, пока не встретится вторая команда

exit -- эта команда прерывает цикл.

Ясно, что выход на команду exit может быть получен при выполнении какого либо условия. Если выхода на эту команду нет, то цикл не закончится. Если нет условия, то цикл не сможет работать, он закончится сразу. Стоит отметить, что команда exit заканчивает и все другие циклы тоже. Просто там без нее можно обойтись, а в цикле loop без нее ничего не получится. Этот цикл может быть полезен при рисовании кривой и проверке условия, чтобы она не выходила за пределы области.


3.3 Операторы прямого воздействия на стек

Мы уже не раз видели, что иногда очень трудно так организовать программирование, чтобы в стеке всегда находилось то, что нам нужно в данный момент. В этих случаях приходится заниматься перегруппировкой объектов в стеке перед выполнением нужных команд. Мы уже сталкивались с некоторыми командами. Я перечислю их здесь снова

dup -- дублирует последний элемент в стеке, то есть вынимает 1 а возвращает 2 копии

pop -- уничтожает последний элемент в стеке, то есть вынимает, но ничего не делает

exch -- меняет местами последние два элемента в стеке. Рассмотрим более сложный оператор

roll -- он берет из стека число j, затем число n, после чего берет n объектов и крутит их j раз. При каждом повороте, если j положительно, то верхний элемент уходит вглубь, а все остальные поднимаются, а если j отрицательно, то нижний элемент выходит наверх, а все остальные опускаются. В результате в стеке остается на два объекта меньше (это аргументы команды), а остальные оказываются перегруппироваными. Другой оператор

index -- берет из стека число n (это его аргумент), затем отсчитывает n-й элемент от вершины вглубь, считая от 0 и помещает его наверх. Это похоже на то, что стек представляется массивом переменной длины, у которого первый элемент имеет индекс 0 и находится наверху, а остальные уходят вглубь, и мы хотим вынуть из него заданный элемент с индексом n. Оператор

clear -- полностью очищает стек, в нем не остается ни одного объекта, Оператор

count -- возвращает в стек число объектов, которое в нем было до выполнения операции.

Есть и много других операторов более сложной работы со стеком, используя маркеры. Но это уже очень редко бывает нужно. Во всяком случае это не входит во второй уровень.


3.4 Рисование в ограниченной области, то есть клиппинг

Заданный контур можно использовать не только для рисования линии или закрашивания области. Его также можно использовать для ограничения области рисунка. Все детали графических объектов, которые выходят за данную область не будут нарисованы. Это иногда бывает полезно, например, при рисовании проекций трехмерных объектов с устранением невидимых линий. Или выделения какого-либо фрагмента на картинке. Оператор

clip -- делает замнутый контур областью рисунка. Все что выходит за пределы этой области не рисуется. Исходно такой областью является размер листа бумаги или размер, заданный в комментарии %%BoungingBox для EPS файлов. Этот оператор может уменьшить эту область фигурным образом. Естественно, что данную область надо задавать до рисования. Вот простой пример:

10 10 m 100 0 rl -50 100 rl -50 -100 rl clip 15 slw 10 50 m 100 0 rl sl -- Здесь сначала задан контур треугольника, а затем нарисована толстая прямая линия. Но линия оказалась обрезанной треугольником. Фактически оператор делает пересечение текущей области ограничения (clippath) c областью, заданной данным контуром. Очень часто областью ограничения является прямоугольник. Чтобы упростить запись для этого случая введен оператор

rectclip -- его аргументами являются четыре числа x, y, w, h, то есть координаты левого нижнего угла, ширина и высота прямоугольника. В таком порядке числа надо задавать до оператора. Естественно, что из стека он их берет в обратном порядке. Вместо самих чисел можно также задавать массив или строку в которой записаны эти числа. Оператор сам разберется. Для контуров ограничения у программы есть свой стек, будем его называть клип-стек, куда можно помещать контура. Чтобы поместить данный контур в клип-стек используется команда

clippath -- она запоминает контура и их потом можно использовать для последующего ограничения с помощью команды clip или даже заштриховать с помощью команды fill. Команда

clipsave -- также помещает контур ограничения в клип-стек. Фактически это происходит автоматически при выполнении команды gsave, но данная команда делает только это и не делает всего остального, что делает gsave. Команда

cliprestore -- вынимает контур ограничения из клип-стека. Также как это делает grestore, но без всего остального. Команда

initclip -- возвращает контур отсечения в исходное состояние, какое он имел в начале программы. Наконец есть еще команда

eoclip -- которая проверяет, если заданный контур больше уже действуюшего контура ограничения, то она его не использует. То есть она берет пересечение из уже работающего контура и нового контура. Все эти команды можно проверить на практике, приводить здесь примеры я не буду.

 


содержание   назад   вперед