Внутри Display PostScript

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

Внутри Display PostScript. Фото.

Продолжение, начало здесь.

Внутри Quartz – производный от PostScript движок, построенный на основах PDF, только спрятан он в исходные коды, которые никому не положено видеть. Adobe Systems в курсе, и даже получает за них лицензионные отчисления – но очень небольшие.

А как вы думали, откуда в macOS такая необычная легкость в обращении с PDF?

Сторонние разработчики имеют дело с API, в подавляющем большинстве которых этим самым PDF и не пахнет. Чтобы понять от чего избавляют эти API, рекомендую почитать официальное Adobe’овское руководство по, например, PDF 1.7.

А вот из чего сделана графическая подсистема NeXTSTEP, разработчики знали, и даже имели дело непосредственно с PostScript. Еще один язык программирования, все они одинаковые – казалось бы, никаких проблем.

Но есть языки и языки.

Коротко о PostScript

Внутри Display PostScript. Коротко о PostScript. Фото.

Это стековый язык программирования, использующий обратную польскую нотацию. Это язык, написанный людьми (Джоном Варноком), и для людей. Все-таки не ассемблер (хотя к ассемблерам, которыми мне приходилось пользоваться, привыкнуть проще).

Хотя, когда привыкнешь, начинаешь находить во всем этом шиворот-навывороте красоту, смысл, невероятную краткость и выразительность. Мне довелось иметь с PostScript дело почти 20 лет назад, в течении пары лет – и я помню то мгновение, когда я стал понимать его не задумываясь…

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

Вот пример PostScript:

graylevel setgray
x y moveto
(string) show

Очень простой: setgray, moveto и show – команды языка. Перед ними – параметры этих команд. Устанавливаем цвет (в градациях серого, какое-то вещественное число от 0.0 до 1.0, от белого к черному), перемещаемся в точку с координатами x и y, и выводим начиная с этой точки строку “string”, что по-английски обозначает “строка”.

Пока ничего сложного. Поэтому еще один пример, практически тот же самый:

/putsomewhere {
setgray
moveto
show
} bind def

Это подпрограмма, делающая то же самое. Как ей пользоваться, и как сообщить ей что и куда выводить?

А вот так:

(xxx) x y 0.66 putsomewhere

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

И тем не менее, поражает: об этом тоже подумали. В те времена об этом особенно никто не задумывался.

Display PostScript в NeXTSTEP

Программы для NeXTSTEP писались на Objective-C. Это гибрид ANSI C c почти полным функционалом Smalltalk, за исключением двух элементов: сборщика мусора и замыканий.

После приобретения прав на Objective-C, прагматичные ребята на NeXT быстро заменили ANSI C на более практичный GNU C, с некоторыми отклонениями от стандарта. C от слияния никак не пострадал, из программ написанных на этом языке можно было вытаскивать для включения в свой собственный код понравившиеся фрагменты, использовать библиотеки лежащие в свободном доступен – великие артисты крадут!

Почему не скрестили Objective-C еще и с PostScript?

Smalltalk обвиняли в том, что среднему промышленному программисту он непонятен. Ну да, и любой ассемблер – тоже. В Smalltalk используется “человеческая”, а не “дьявольская”, логика. Освоить его намного проще, чем PostScript.

Причина в другом: взяв из Smalltalk его объектно-ориентированную модель (кроме которой в нем ничего не было), о самом языке даже не вспоминали. Даже если бы в исходный Smalltalk внесли какие-нибудь изменения, их просто проигнорировали бы. И никаких лицензионных отчислений за модель Smalltalk платить не требовалось.

Встраивать чей-то (взятый в аренду) язык в другой чужой язык… Согласитесь, это как-то нехорошо.

Еще – Smalltalk был очень медлителен. В нем абсолютно все: числа, символы, логические переменные – было объектами. Это самая важная причина, по моему..

И, кроме того, зачем навязывать хорошим людям лишние сложности? Если не все читатели знают китайский, каждую фразу написанную красивыми иероглифами можно сопроводить переводом. А то и вовсе обойтись одним переводом. Примерно так на NeXT и поступили с Display PostScript и просто PostScript. Его “перевели” на C.

Пример из предыдущего раздела теперь выглядел бы так:

float graylevel = …;
float x = …, y = …;
char* str = “”;

PSsetgray(graylevel);
PSmoveto(x, y);
PSshow(str);

Обычный C. Примерно также, как в 1998-1999 перевели примитивы движка PDF в Core Graphics.

Примерно. На самом деле, в NeXTSTEP все было намного интереснее.

Особенности реализации

Внутри Display PostScript. Особенности реализации. Фото.

Реализация PostScript реально присутствовала в NeXTSTEP. Для каждого процесса в системе создавался свой, независимый интерпретатор PostScript, со своими памятью, словарем, стеком и тому подобным.

В NeXTSTEP важная часть интерфейса, Window Server, управляющая отрисовкой и жизнью окон на экране, с самого начала писалась на NeXT. На адской смеси из PostScript, Display PostScript и Objective-C. Её не доверили никому.

Помимо NeXT, Display PostScript использовался такими компаниями, как Digital Equipment (в Ultix и в некоторых версиях VAX/VMS, которые я не застал), Sun Microsystems и Silicon Graphics.

Самой же интересной особенностью реализации была программа pswrap, позволявшая обычному промышленному программисту расширять набор доступных ему графических команд и превращать их в обычные вызовы на C. Для этого от него требовалось знание PostScript и правил pswrap…

Оборачивание PostScript-кода

Вернемся к примеру из первого раздела. Вообще-то, все команды PostScript, которые использованы в этом примере, “обернуты” в вызовы на C, но какой-то смысл в таких спайках все-таки был, надеюсь.

Сравнить время исполнения разных вариантов невозможно.

Дано:

/putsmw {
setgray
moveto
show
} bind def

Для превращения процедуры putsomewhere в вызов на C, необходимо проделать следующее:

Шаг 1.

Создать файл с расширением “.psw”. В палитре типов файлов в ProjectBuilder времен NeXT предлагался такой тип файлов. И назвать его каким-нибудь понятным именем, например, “putsmw”. Это сокращение от “put somewhere”, то есть, “положи куда-нибудь”.

Шаг 2.

Ввести в него следующее:

defineps putsmw(float x, float y, float graylevel, char* string)
graylevel setgray
x y moveto
(string) show
flushgraphics
endps

Как именно называть переменные и сам вызов, по большому счету, все равно – главное не запутаться при их использовании. В больших программах в этим большие проблемы, из-за которых придуманы многочисленные правила выживания (например, “не сокращай”), эти правила написаны чьей-то кровью. Но в коде в несколько строк, практическое применение которого не предвидится, мы свободны делать все что угодно.

Шаг 3.

В исходном файле (на C или на Objective-C), в которых нам нужно использовать этот вызов, добавляем в верхней части файла директиву #import:


#import “putsmw.h”

Шаг 4.

Вызываем putsmw как если бы это был обычный C-шный вызов:

putsmw( 200.0, 200.0, 0.55, “Hello, PostScript!”);

Это очень простой пример, в описании pswrap (примерно в 40 страниц) подробно описано как возвращать значения из PostScript, как работать с массивами, и много чего еще. Это целая наука. В то время, наверное, всех в мире считали идиотами – и объясняли все в деталях.

В наше время наоборот: даже более сложные вещи объясняют в двух-трех неоднозначных фразах, в надежде что кому надо – догадаются. В крайнем случае, поупражняются неделю или две.

А pswrap достаточно прост, очень логичен и вопросов не вызывает. Все разложено по полочкам.

Описано даже, как использовать интерпретатор, расположенный на удаленном сервере.

История AppleКомпьютеры AppleОбзоры приложений для iOS и Mac