Definice nových příkazů v PostScriptu

14. 6. 2007
Doba čtení: 12 minut

Sdílet

Minule jsme si uvedli základní informace o tom, jakým způsobem je možné v PostScriptu zapisovat jednoduché programy. Dnes si ukážeme způsob definice nových příkazů pomocí operátoru "def", který je použit jak pro vytváření nových procedur/funkcí, tak i pro zkrácení délky výsledného souboru.

Obsah

1. Převod číselné hodnoty na řetězec
2. První demonstrační příklad – ukázka výpisu číselné hodnoty na tištěnou stránku
3. Definice nových příkazů
4. Druhý demonstrační příklad – zkrácené podoby příkazů pro vytvoření cesty
5. Porovnání velikosti původního PostScriptového souboru a souboru zkráceného pomocí nových příkazů
6. Třetí demonstrační příklad – vytvoření zkrácené podoby samotného příkazu „def“
7. Obsah dalšího pokračování tohoto seriálu

1. Převod číselné hodnoty na řetězec

Při psaní programů v PostScriptu se můžeme dostat do situace, kdy potřebujeme zobrazit hodnotu nějaké číselné konstanty či proměnné. Tisk takové hodnoty na konzoli (případ Ghostscriptu) či jiné podobné zařízení (displej tiskárny) není vždy možný nebo vhodný, proto si ukážeme, jak takovou hodnotu zobrazit přímo na vytvářené (RIPované) stránce. Jakým způsobem se na tištěnou stránku vypisuje hodnota řetězce již víme – tuto operaci zajišťuje příkaz show. Tento příkaz ovšem vyžaduje jako svůj parametr řetězec a pokud by byla na zásobníku uložena číselná konstanta, běh programu a tím i celé RIPování by skončilo s chybou. Musíme tedy zvolit jiný způsob známý i z dalších programovacích jazyků (C, Java, JavaScript): nejprve převést číselnou hodnotu na řetězec a posléze tento řetězec zobrazit.

V PostScriptu máme k dispozici několik operátorů, které slouží pro převody hodnot na různé typy. V předchozí části tohoto seriálu jsme si řekli, že PostScript rozeznává několik typů objektů. Jsou to takzvané jednoduché typy (boolean, integer, mark, fontID, name, null, operator a real) a složené (kompozitní) objekty (array, dictionary, file, save, string, gstate a packedarray). Pomocí příkazu type je možné zjistit, jakého typu je daný objekt nebo konstanta. Všimněte si, že v tomto případě se PostScript chová jako dynamické jazyky s introspekcí. Tvůrci se ostatně netají svojí inspirací v LISPu a jeho dialektech. Podobné chování však mají i další programovací jazyky, například Python a částečně i C++ a Java (RTTI).

Pro převod řetězce (obsahujícího znaky reprezentující číslo) na skutečnou celočíselnou hodnotu slouží příkaz cvi. Podobně je možné příkazem cvr převést řetězec na reálné číslo. Opačné převody je možné provádět příkazem cvs (převod různých typů objektů na jejich textovou reprezentaci) a příkazem cvrs. Tento příkaz na zásobníku očekává (kromě samotného řetězce a převáděné hodnoty) i takzvaný radix, což je základ číselné soustavy. Většinou se používají radixy 2, 8, 10 a 16, samozřejmě je však možné použít i jiné hodnoty. Samotné převodní příkazy nám však nestačí – ještě potřebujeme vyhradit paměť pro řetězec, do kterého se bude převod provádět. K tomuto účelu nám poslouží programová konstrukce:

/jméno počet_znaků string def 

Která vytvoří řetězec se jménem jméno, ve kterém může být uloženo maximálně počet_znaků znaků. Použití této programové konstrukce si ukážeme ve druhé kapitole na demonstračním příkladu.

2. První demonstrační příklad – ukázka výpisu číselné hodnoty na tištěnou stránku

V demonstračním příkladu je na stránku vytištěn výsledek výrazu 10+32. Nejprve je vyhledán vhodný font (v tomto případě standardní interní font Helvetica), je změněna jeho velikost na jeden palec, tj. 72 typografických bodů a takto zkonfigurovaný font je příkazem setfont nastaven jako aktuální font. Posléze se pomocí příkazu moveto přesune bod vykreslování (CP) na souřadnice [100, 700]. Programovou konstrukcí /str 10 string def je vytvořen řetězec s názvem str a velikostí (kapacitou) deseti znaků. Posléze je proveden požadovaný výpočet, tj. 10 32 add (je použita převrácená polská notace – RPN), přičemž výsledek výpočtu je ponechán na zásobníku operandů. Převod výsledku na řetězec je zajištěn konstrukcí str cvs.

Příkaz cvs na zásobníku očekává dva parametry: převáděnou číselnou či objektovou hodnotu a řetězec, do kterého má být převod proveden. Převáděná číselná hodnota byla na zásobníku ponechána z předchozího výpočtu, takže postačilo přidat jméno řetězce, které se uložilo na zásobník (zde je situace poněkud složitější, ale nám v této chvíli postačuje, že je na zásobník uložen požadovaný parametr). Konečně příkaz show zajistí vytištění požadovaného řetězce – příkaz cvs totiž na zásobníku ponechal objekt reprezentující daný řetězec. V praxi můžeme ten stejný řetězec (v našem případě str) použít vícekrát, což je vhodné například při ladění složitějších PostScriptových programů. Kdykoli narazíme na potřebu vytištění hodnoty nějaké proměnné, postačí použít sekvenci „x“ „y“ moveto „hodnota“ str cvs show.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Prvni demonstracni priklad
% Prevod celociselne hodnoty na text (retezec) a nasledny vypis
% textu do tistene stranky
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% nalezeni fontu ve slovniku
/Helvetica findfont

% nastaveni velikosti pisma
% (v typografickych bodech 72pt=1 palec)
72 scalefont

% nastaveni nalezeneho fontu jako aktivniho
setfont

% presun CP je nutne uvest, i kdyz se jedna
% o pocatek souradnic
100 700 moveto

% vytvoreni retezce o delce deseti znaku
% (alokace pameti ve VM)
/str 10 string def

% vypocet ciselne hodnoty
10 32 add

% prevod vysledku na retezec a nasledne zobrazeni retezce
str cvs show

showpage

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 

441
Screenshot po RIPování a zobrazení prvního demonstračního příkladu

3. Definice nových příkazů

V první kapitole jsme si řekli, že se PostScript v některých případech chová jako dynamický programovací jazyk. Jak uvidíme z dalšího textu, nejde o náhodu, ale o úmysl tvůrců tohoto jazyka. V PostScriptu se velmi často setkáváme s nutností vytváření nových příkazů (které můžeme považovat za operátory). Pro tento účel se používá příkaz def, který přiřazuje zvolenému jménu nějakou hodnotu. V případě, že touto hodnotou je seznam příkazů, stává se zvolené jméno vlastně novým příkazem. V PostScriptu se seznam příkazů (což je jedna z variant pole) zapisuje podobně, jako v LISPu či Scheme, jsou však použity složené závorky. Na první pohled může vypadat práce příkazu def složitě, proto si ukažme vytvoření nového příkazu pro výpočet druhé mocniny:

/mocnina {dup mul} def 

Nejprve je zadáno nové jméno, v tomto případě „mocnina“. Jméno musí být uvozeno lomítkem, protože v opačném případě by se PostScript snažil toto jméno vyhledat ve slovníku a spustit, což by skončilo běhovou chybou. Posléze je ve složených závorkách uveden seznam příkazů, který může mít libovolnou strukturu i velikost. V našem případě se jedná o příkazy dup a mul, které nejprve zkopírují číselnou hodnotu uloženou na zásobníku operandů a posléze tuto hodnotu vynásobí s hodnotou původní (ta je stejná), což je přesná funkce druhé mocniny. Příkazem def je provedeno propojení mezi jménem „mocnina“ a blokem kódu. Od této chvíle je možné v PostScriptovém kódu používat nový příkaz mocnina, například následujícím způsobem:

42 mocnina 

Tímto způsobem je dokonce možné „přepsat“ standardní příkazy PostScriptu, protože PostScript při běhu programů nerozlišuje mezi vestavěnými příkazy a příkazy uživatelskými (přesněji řečeno – vestavěné příkazy stále existují, ale pokud nezměníme pořadí slovníků v zásobníku slovníků, tak je nejprve nalezeno jméno předefinovaného příkazu v uživatelském slovníku, takže se technicky jedná o „překrytí“ a nikoli „přepsání“ vestavěného příkazu). Této vlastnosti samozřejmě není vhodné zneužívat, na druhou stranu to však umožňuje tvorbu „patchů“ pro konkrétní RIPy. Pokud například nějaký RIP nepracuje korektně s barevnými obrázky, je možné předefinovat všechny příkazy pro práci s těmito obrázky a posléze do RIPu poslat běžný PostScriptový soubor pro zpracování.

4. Druhý demonstrační příklad – zkrácené podoby příkazů pro vytvoření cesty

S příkazem def se velmi často setkáme při prohlížení PostScriptových souborů generovaných různými ovladači tisku, například při vykreslení vektorového výkresu do PostScriptu v programu AutoCAD. Důvod použití tohoto příkazu je jednoduchý: zápis cest, změna barvy, typu čáry atd. se provádí pomocí poměrně dlouhých příkazů (moveto, setrgbcolor, setcmykcolor apod.), což zbytečně zvětšuje velikost generovaného PostScriptového souboru a také může docházet ke zpomalení RIPování a tisku, zejména na rychlých tiskárnách připojených přes pomalou datovou linku. Proto ovladače na začátku PostScriptového souboru definují několik nových příkazů, které mají délku typicky jeden až dva znaky. Celková délka souboru se může zmenšit až o několik desítek procent.

Ukažme si použití tohoto postupu. V předchozích částech tohoto seriálu jsme si ukazovali příklad na vykreslení několika cest různými typy čar. Tento příklad má po doplnění tří Bézierových křivek (ty jsou zde uvedeny pouze pro ilustraci) následující podobu:

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Vykresleni nekolika cest
% zakladnimi prikazy PostScriptu
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

5.0 setlinewidth

% nastaveni zpusobu ukonceni usecek
0 setlinecap

% prvni krivka
0 setlinejoin
newpath
 50 700 moveto
150 800 lineto
 50 800 lineto
150 700 lineto
stroke

% druha krivka
1 setlinejoin
newpath
 50 550 moveto
150 650 lineto
 50 650 lineto
150 550 lineto
stroke

% treti krivka
2 setlinejoin
newpath
 50 400 moveto
150 500 lineto
 50 500 lineto
150 400 lineto
stroke

% Bezierova krivka
newpath
 50 250 moveto
150 350
 50 350
150 250 curveto
stroke

% nastaveni zpusobu ukonceni usecek
1 setlinecap

% prvni krivka
0 setlinejoin
newpath
250 700 moveto
350 800 lineto
250 800 lineto
350 700 lineto
stroke

% druha krivka
1 setlinejoin
newpath
250 550 moveto
350 650 lineto
250 650 lineto
350 550 lineto
stroke

% treti krivka
2 setlinejoin
newpath
250 400 moveto
350 500 lineto
250 500 lineto
350 400 lineto
stroke

% Bezierova krivka
newpath
250 250 moveto
400 350
200 350
350 250 curveto
stroke

% nastaveni zpusobu ukonceni usecek
2 setlinecap

% prvni krivka
0 setlinejoin
newpath
450 700 moveto
550 800 lineto
450 800 lineto
550 700 lineto
stroke

% druha krivka
1 setlinejoin
newpath
450 550 moveto
550 650 lineto
450 650 lineto
550 550 lineto
stroke

% treti krivka
2 setlinejoin
newpath
450 400 moveto
550 500 lineto
450 500 lineto
550 400 lineto
stroke

% Bezierova krivka
newpath
500 250 moveto
600 350
400 350
500 250 curveto
stroke



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% vykresleni cele stranky
showpage
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 

Vidíme, že se v tomto souboru nachází mnoho stejných příkazů, například moveto, curveto, stroke či setlinejoin. Nabízí se možnost vytvoření nových příkazů, které budou pojmenovány jednoznakovými či dvouznakovými identifikátory (nové příkazy m, l a c mimochodem používá i výše zmiňovaný ovladač AutoCADu). Upravený demonstrační příklad má tvar:

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Vykresleni nekolika cest
% zakladnimi prikazy PostScriptu
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% vytvoreni novych slov
/m {moveto} def
/l {lineto} def
/c {curveto} def
/n {newpath} def
/s {stroke} def
/lc {setlinecap} def
/lj {setlinejoin} def

5.0 setlinewidth

% nastaveni zpusobu ukonceni usecek
0 lc

% prvni krivka
0 lj n 50 700 m 150 800 l 50 800 l 150 700 l s

% druha krivka
1 lj n 50 550 m 150 650 l 50 650 l 150 550 l s

% treti krivka
2 lj n 50 400 m 150 500 l 50 500 l 150 400 l s

% Bezierova krivka
n 50 250 m 150 350 50 350 150 250 c s

% nastaveni zpusobu ukonceni usecek
1 lc

% prvni krivka
0 lj n 250 700 m 350 800 l 250 800 l 350 700 l s

% druha krivka
1 lj n 250 550 m 350 650 l 250 650 l 350 550 l s

% treti krivka
2 lj n 250 400 m 350 500 l 250 500 l 350 400 l s

% Bezierova krivka
n 250 250 m 400 350 200 350 350 250 c s

% nastaveni zpusobu ukonceni usecek
2 lc

% prvni krivka
0 lj n 450 700 m 550 800 l 450 800 l 550 700 l s

% druha krivka
1 lj n 450 550 m 550 650 l 450 650 l 550 550 l s

% treti krivka
2 lj n 450 400 m 550 500 l 450 500 l 550 400 l s

% Bezierova krivka
n 500 250 m 600 350 400 350 500 250 c s



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% vykresleni cele stranky
showpage
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 

Oba dva PostScriptové soubory dávají stejný výsledek, jsou tedy z hlediska PostScriptu i uživatele ekvivalentní.

442
Screenshot po RIPování a zobrazení druhého demonstračního příkladu

5. Porovnání velikosti původního PostScriptového souboru a souboru zkráceného pomocí nových příkazů

V předchozí kapitole jsme si řekli, že se definice nových příkazů v praxi používá pro snížení velikosti výsledného PostScriptového souboru. Jakého snížení velikosti je však možné dosáhnout? Porovnání velikostí výše uvedených dvou souborů by nebylo z praktického hlediska korektní, protože programově generované PostScriptové soubory většinou neobsahují tolik komentářů. Proto budeme tyto soubory porovnávat po odstranění komentářů a zbytečných prázdných řádků. Některé příkazy jsou sloučeny na jeden řádek, to však velikost souboru nijak neovlivní, protože znak mezery má stejnou délku jako znak konce řádku (alespoň na rozumných operačních systémech, které se nesnaží v textových souborech simulovat dálnopis).

První soubor má po úpravě tvar:

5.0 setlinewidth
0 setlinecap
0 setlinejoin
newpath
 50 700 moveto 150 800 lineto 50 800 lineto 150 700 lineto
stroke
1 setlinejoin
newpath
 50 550 moveto 150 650 lineto 50 650 lineto 150 550 lineto
stroke
2 setlinejoin
newpath
 50 400 moveto 150 500 lineto 50 500 lineto 150 400 lineto
stroke
newpath
 50 250 moveto 150 350 50 350 150 250 curveto
stroke
1 setlinecap
0 setlinejoin
newpath
250 700 moveto 350 800 lineto 250 800 lineto 350 700 lineto
stroke
1 setlinejoin
newpath
250 550 moveto 350 650 lineto 250 650 lineto 350 550 lineto
stroke
2 setlinejoin
newpath
250 400 moveto 350 500 lineto 250 500 lineto 350 400 lineto
stroke
newpath
250 250 moveto 400 350 200 350 350 250 curveto
stroke
2 setlinecap
0 setlinejoin
newpath
450 700 moveto 550 800 lineto 450 800 lineto 550 700 lineto
stroke
1 setlinejoin
newpath
450 550 moveto 550 650 lineto 450 650 lineto 550 550 lineto
stroke
2 setlinejoin
newpath
450 400 moveto 550 500 lineto 450 500 lineto 550 400 lineto
stroke
newpath
500 250 moveto 600 350 400 350 500 250 curveto
stroke
showpage 

Druhý soubor se zkrátil do následující podoby:

/m {moveto} def
/l {lineto} def
/c {curveto} def
/n {newpath} def
/s {stroke} def
/lc {setlinecap} def
/lj {setlinejoin} def
5.0 setlinewidth
0 lc
0 lj n 50 700 m 150 800 l 50 800 l 150 700 l s
1 lj n 50 550 m 150 650 l 50 650 l 150 550 l s
2 lj n 50 400 m 150 500 l 50 500 l 150 400 l s
n 50 250 m 150 350 50 350 150 250 c s
1 lc
0 lj n 250 700 m 350 800 l 250 800 l 350 700 l s
1 lj n 250 550 m 350 650 l 250 650 l 350 550 l s
2 lj n 250 400 m 350 500 l 250 500 l 350 400 l s
n 250 250 m 400 350 200 350 350 250 c s
2 lc
0 lj n 450 700 m 550 800 l 450 800 l 550 700 l s
1 lj n 450 550 m 550 650 l 450 650 l 550 550 l s
2 lj n 450 400 m 550 500 l 450 500 l 550 400 l s
n 500 250 m 600 350 400 350 500 250 c s
showpage 

Velikost prvního souboru je 1049 bytů, druhý soubor je zkrácen na 720 bytů, což představuje cca 68 % původní velikosti. Je zřejmé, že s rostoucím počtem cest ve výkresu se tento poměr zlepšuje a podle znakové délky použitých číselných hodnot se můžeme dostat i na hranici 50 %.

6. Třetí demonstrační příklad – vytvoření zkrácené podoby samotného příkazu „def“

Na první pohled se příkaz def podobá preprocesorovému příkazu #define známého z céčka nebo vytvoření nové pojmenované procedury v Pascalu a podobných programovacích jazycích. Tato podobnost je však pouze zdánlivá, protože PostScript dokonce umožňuje vytvořit zkrácenou či jiným způsobem přejmenovanou podobu příkazu def. To je ukázáno na třetím demonstračním příkladu, jehož velikost je pouhých 703 bytů:

/d{def}def
/m{moveto}d
/l{lineto}d
/c{curveto}d
/n{newpath}d
/s{stroke}d
/lc{setlinecap}d
/lj{setlinejoin}d
5.0 setlinewidth
0 lc
0 lj n 50 700 m 150 800 l 50 800 l 150 700 l s
1 lj n 50 550 m 150 650 l 50 650 l 150 550 l s
2 lj n 50 400 m 150 500 l 50 500 l 150 400 l s
n 50 250 m 150 350 50 350 150 250 c s
1 lc
0 lj n 250 700 m 350 800 l 250 800 l 350 700 l s
1 lj n 250 550 m 350 650 l 250 650 l 350 550 l s
2 lj n 250 400 m 350 500 l 250 500 l 350 400 l s
n 250 250 m 400 350 200 350 350 250 c s
2 lc
0 lj n 450 700 m 550 800 l 450 800 l 550 700 l s
1 lj n 450 550 m 550 650 l 450 650 l 550 550 l s
2 lj n 450 400 m 550 500 l 450 500 l 550 400 l s
n 500 250 m 600 350 400 350 500 250 c s
showpage 

Na poslední ukázce je vytvořeno několik nových funkcí pojmenovaných identifikátory x, y a z, které ve svém těle obsahují více příkazů. Velikost souboru se v tomto případě zmenšila na konečných 671 bytů, což představuje 63 % původní velikosti:

/d{def}def
/m{moveto}d
/l{lineto}d
/c{curveto}d
/n{newpath}d
/s{stroke}d
/lc{setlinecap}d
/x{l s}d
/y{c s}d
/z{setlinejoin n}d
5.0 setlinewidth
0 lc
0 z 50 700 m 150 800 l 50 800 l 150 700 x
1 z 50 550 m 150 650 l 50 650 l 150 550 x
2 z 50 400 m 150 500 l 50 500 l 150 400 x
n 50 250 m 150 350 50 350 150 250 y
1 lc
0 z 250 700 m 350 800 l 250 800 l 350 700 x
1 z 250 550 m 350 650 l 250 650 l 350 550 x
2 z 250 400 m 350 500 l 250 500 l 350 400 x
n 250 250 m 400 350 200 350 350 250 y
2 lc
0 z 450 700 m 550 800 l 450 800 l 550 700 x
1 z 450 550 m 550 650 l 450 650 l 550 550 x
2 z 450 400 m 550 500 l 450 500 l 550 400 x
n 500 250 m 600 350 400 350 500 250 y
showpage 

Další možnost definice nových příkazů spočívá ve využití polí symbolů, touto problematikou se však budeme zabývat až v některé z dalších částí tohoto seriálu.

ict ve školství 24

7. Obsah dalšího pokračování tohoto seriálu

V následující části seriálu o grafických formátech a metaformátech si řekneme, jakým způsobem je možné do PostScriptových programů vkládat smyčky zajišťující opakovaný běh určité části programu. Uvidíme, že možnost tvorby programových smyček může vést k radikálnímu snížení velikosti celého souboru a také k tvorbě obrázků, které by se ručně (či pomocí vektorového grafického editoru) vytvářely složitým a zdlouhavým způsobem. Některé programové smyčky implementované v PostScriptu umožňují provádění poměrně vysokoúrovňových operací, například na slovníky apod., nejedná se tedy pouze o běžné počítané smyčky typu for či nepočítané smyčky typu while (i ty jsou však samozřejmě k dispozici).

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.