Новости

О проекте

Зачем?

Используемое ПО

Методика разработки сцен на PostScript

Операторы языка PostScript

Библиотека

Изучение языка

Урок 1

Урок 2

Урок 3

Урок 4

Урок 5

Урок 6

Урок 7

Статьи

Ссылки...

Занятие четвёртое

Начиная весной эту серию уроков у меня было сомнение -- хватит ли материала на семь занятий. Задав в Интернет поиск по ключевому слову 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:

  • ashow - при печати строки добавляет после каждого символа заданный промежуток;
  • widthshow - при печати строки добавляет заданный промежуток после каждого появления некоторого символа (например после каждого пробела);
  • awidthshow - является комбинацией двух предыдущих операторов;
  • kshow - выполняет заданную процедуру между каждой парой символов в строке.

Текущий символ и символ, следующий за ним передаются этой процедуре как аргументы. Так строка

   {pop pop (-) show} (World) kshow 

напечатает его с дефисом между каждой парой букв:

  W-o-r-l-d 

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

Кодирование шрифта

Каждый словарь шрифта содержит описание символов в соответствии с некоторой кодовой таблицей, в частности это может быть ASCII. Таким образом каждый символ имеет свой цифровой код -- число от 0 до 255. Кодовая таблица не является постоянной, ее можно изменять из приложения.

Коды символов можно использовать двумя способами:

  • их можно с помощью оператора put вставлять в строку;
  • или непосредственно использовать в строке в виде восьмеричных чисел.

Многие шрифты имеют символы не входящие в стандартную кодировку и кроме того, часто такие символы отсутствуют на клавиатуре. Чтобы посмотреть как кодируются символы того или иного шрифта, напишем программу, которая распечатывает кодовую таблицу любого заданного шрифта.

   /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



©Фурашев А. 2002
e-mail

Хостинг от uCoz