home / uni / ps / eps PostScript言語 / EPS

 EPSはPostScript言語で書かれた「挿絵」。 普通のPostScript文書がページを単位として複数のページをひとつのファイルにまとめてあるのに対し、EPSは指定されたひとつの矩形の中に絵を書いた状態になっている。 規格はAdobe Developer Connection / PostScript Technology Center にある Encapsulated PostScript (EPS) File Format Specification Version 3.0 #5002。 以下、この文書のことをEPSFと呼ぶ。 それと、文書の属性を埋め込むのにDSCというのを使うので、PostScript Language Document Structuring Conventions (DSC) Specification Version 3.0 #5001 にも目を通しておくとよい。 以下、この文書のことをDSCと呼ぶ。 また、HiResBoundingBoxは PostScript Language Document Comment Extensions for Page Layout #5644 の方に載っている。

home / uni / ps / eps EPSに最低限必要なもの

%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: 235 300 470 500

 大文字・小文字は区別される(DSC 4.4)。 %%は行頭に書く必要があり(DSC、1章の前)、コロンまでスペースは入れない。 パラメータのあるDSCコメントのコロンは必ず必要で、 コロンの後にはスペースがあってもなくてもよい。 %%BoundingBox:(境界矩形)はEPSの大きさを四角形で定義する。 単位はデフォルトユーザ空間で、左下の座標、右上の座標を整数で設定する。 この外側に描画してはならない。 枠が描画されるかどうかはEPSを読み込んだ方がどうするかによる。 EPSの中で枠を描画したような場合、枠の太さも含めて完全に境界矩形に入っている必要がある(EPSF 2.1)。 もう少し詳しく設定したい場合は%%HiResBoundingBoxを使う(#5644)。 こちらは座標を実数で書ける。

 このほかに、PostScript言語レベル2以上を使う場合は%%LanguageLevel:が、フォントなどの外部リソースを使う場合はそれに応じた%%DocumentNeededResources:などが必要になる。 このほかにも状況に応じて必要なDSCコメントがあるが、手書きでそこまですることはあまりなかろう、胸を張ってEPSであると言うことはできなくなるが。 そもそも、このページで説明しているのは「なんちゃってEPS」の作り方だし。

 あとは普通に図形を描けばよい。 いくつか使ってはいけないオペレータがある(EPSF 2.4)が、普通に図形を書いている場合はどれも使わないものばかりである。

home / uni / ps / eps BoundingBoxを計算するのが面倒くさい

$ gs -sDEVICE=bbox -q -dBATCH -dNOPAUSE input.eps

 出力デバイスとしてbboxを指定するとgsが境界矩形を計算してくれる。 結果はEPSへそのまま貼り付けられる形で標準出力に出てくる。 -qはメッセージの抑制、-dBATCHは終了後に対話モードに入らない、-dNOPAUSEはページごとのポーズをしないという意味。 後者二つは-o outputfileと書くと自動的に設定されるので、outputfileに標準出力を示す-(ハイフン)を指定して-o -と書くこともできるが、うっかりハイフンを忘れると悲惨なことになりそうなので注意。

home / uni / ps / eps EPSを読み込む側

 EPS内でいろいろと状態をいじられる可能性があるので、save・restoreをはじめとして、オペランドスタック・辞書スタックなどの状態にも気を使う必要がある。 EPSF 3.2にお勧めプロシージャが載っている。

 あとは位置合わせ。 EPSはデフォルトユーザ空間でbboxが指定されているのと、左下が (0, 0) とは限らないので、以下の方法がお勧めされている。

1. EPSを貼り付ける場所の原点へtranslateする。

2. EPSのサイズを実際の貼り付けサイズにscaleで合わせる。

3. EPSの左下座標の分だけ、逆方向にtranslateする。

4. クリッピングパスを設定する場合、ここでbboxに相当する矩形にrectclipすればよい。 bboxは右上の点が座標で指定されており、rectclipは矩形の幅と高さで範囲を指定する点に注意。

これで希望の枠内に収まる。

 あるいは、画像の場合と同じような方法も考えられる。 この場合、画像辞書に相当する辞書を以下のように作る。 値はすべてデフォルトユーザ空間で計算する。
WidthEPSの幅。bboxの右端から左端を引いたもの。
HeightEPSの高さ。bboxの上端から下端を引いたもの。
ImageMatrix[ width 0 0 height left bottom ] 、ただし、widthはEPSの幅、heightは高さ、leftはEPS bboxの左端X座標、bottomはEPS bboxの下端Y座標

1. EPSを貼り付ける場所の基準点へtranslateする。たとえば貼り付け予定地の上端中央。

2. 回転したい場合、ここでrotateしておく。

3. EPSの大きさをsetimagewidthsetimageheightで設定する。

4. 必要に応じてvflipimageする。

5. 基準位置から実際の位置にtranslateする。今の場合は基準位置を上端中央としたので-0.5 0 translateでよい。

6. クリッピングパスを設定する場合、ここで0 0 1 1 rectclipすればよい。

7. 画像辞書の画像変換行列をinvertmatrixしてからconcatする。

これで所望の位置に描画されるだろう。

home / uni / ps / eps 読み込み側プロシージャ例

 定義が書いてないerrvokputpreparejpgと同じ。 BeginEPSFEndEPSFはEPSF 3.2からパクったもの。
/getint {
    token not { /getint /ioerror err } if
    dup type /integertype ne { /getint /typecheck err } if
} def

% file [getbbox] bbox
/getbbox {
    4 dict begin
    /src exch def
    /buf 256 string def
    /bbox [ 0 0 1 1 ] def
    {
        src buf readline exch
        (%%BoundingBox:) anchorsearch {
            pop token not { /getbbox /ioerror err } if
            dup (atend) eq {
                pop pop
            } {
                dup type /integertype ne { /getbbox /typecheck err } if
                bbox 0 vokput
                getint bbox 1 vokput
                getint bbox 2 vokput
                getint bbox 3 vokput
                pop pop
                exit
            } ifelse
        } {
            pop
        } ifelse
        not {   % bool result of readline
            /getbbox /ioerror err
        } if
    } loop
    bbox
    end
} def

% bbox [ imagedictfrombbox ] dict
/imagedictfrombbox {
    5 dict begin
    /BoundingBox exch def
    /Width BoundingBox 2 get BoundingBox 0 get sub def
    /Height BoundingBox 3 get BoundingBox 1 get sub def
    /ImageMatrix [ Width 0 0 Height BoundingBox 0 get BoundingBox 1 get ] def
    /DataSource null def
    currentdict end
} def

% file [ prepareEPS ] dict
/prepareEPS {
    dup getbbox imagedictfrombbox begin
    dup 0 setfileposition /DataSource exch def
    currentdict end
} def

% dict [ concatEPS ] -
/concatEPS {
    matrix invertmatrix concat
} def

% dict [ drawEPS ] -
/drawEPS {
    BeginEPSF
    dup /ImageMatrix get concatEPS
    dup /DataSource get cvx exec
    EndEPSF
    pop
} def
getbboxはEPSファイルから%%BoundingBox:コメントを抜き出すもの。 getintgetbboxから呼ばれる下請けプロシージャ。 %%BoundingBoxコメントなど、一部のコメントはEPSファイルを全部吐いてから計算することが許されていて、その場合はヘッダ部分にあるコメントは%%BoundingBox: (atend)となる。 この場合はエラーとせずに解析を続ける。 最後まで見つからなかった場合はioerrorが発生するようになっている。 このプロシージャは4点手抜きをしている。 ひとつはPostScript言語の文字列として%%BoundingBox:が出てきた場合。 PostScript言語の文字列は改行をまたぐことができる。 また、多くの言語がそうであるように、文字列中ではコメント開始文字はその役割を失う。 したがって、
( PostScript language comment can continue over the line.
%%BoundingBox:
the above is a part the string. )
なんて書かれた場合、この%%BoundingBox:はDSCコメントではなくて文字列の一部になるわけだ。 このパターンをきちんと取り扱っていない。

 もうひとつは%%BeginBinary%%BeginDataに対応していない。 つまり、PostScriptプログラムに直接埋め込まれたバイナリデータや、それをテキストとしてエンコードしたデータの途中に、たまたま%%BoungingBoxがあると誤動作する。 3点目は%%BeginDocumentに対応していない点で、EPSの先頭に%%BoundingBox: (atend)と書かれていて、その中にさらにEPSを貼り付けられると、EPSの中に貼ってあるEPS(つまり孫文書)の境界矩形が採用されてしまう。 最後のひとつは%%+によるコメントの行継続に対応していない。 真面目な用途で使う場合は各自でこれらの対策を行なうこと。

 境界矩形は整数4つを要素に持つ配列の形で返ってくる。 これをimagedictfrombboxに渡すと画像辞書を作ってくれる。 境界矩形自体も画像辞書の中にBoundingBoxというキーで書き込まれている。 EPSファイルを開いてprepareEPSを呼び出すと、境界矩形をサーチして画像辞書をつくり、そのDataSourceメンバにファイル先頭にシークした入力ファイルを設定してくれる。 concatEPSは画像変換行列の逆行列をCTMに乗じてくれる。

 drawEPSprepareEPSが生成した画像辞書を渡すと実際にEPSを描画する。 VMの状態は自動で保存してくれる。 このプロシージャは書き方にコツがあって、画像辞書をBeginEPSFの後でスタックから降ろしてしまうと、EndEPSFで期待しているスタックの深さと実際のスタックの深さが合わなくなるので落ちてしまう。 したがって、BeginEPSFの前に降ろすか、EndEPSFの後に降ろすかのどちらかにする必要がある。 このために無駄にduppopが入れてある。

 使い方はこうなる。 setimagewidthsetimageheightvflipimage画像を扱うで説明したものと同じ。 今回はrotaterectclipも入れてある。

gsave
(test.eps) (r) file prepareEPS
105 297 2 div translate
90 rotate
210 setimageheight
-0.5 -0.5 translate
0 0 1 1 rectclip
vflipimage
drawEPS
grestore

ファイルから読み込むのではなく、直接PostScriptプログラム中に貼り付けるような場合は、境界矩形の配列を自分で作って、

gsave
[ 56 215 555 577 ] imagedictfrombbox
105 297 2 div translate
90 rotate
210 setimageheight
-0.5 -0.5 translate
0 0 1 1 rectclip
vflipimage
concatEPS
BeginEPSF
%%BeginDocument: test.eps
... commands for drawing EPS ...
%%EndDocument
EndEPSF
grestore

とすれば動くはず。 ファイルから読み込む場合に比べconcatEPSBeginEPSFが逆になっているのは、ファイルから読む場合は座標系の保存もBeginEPSFにやらせようという意図があり、直接貼り付ける場合はgsavegrestoreしていると分かっているのでその必要がないからである。 座標系を変換しなくていいなら、BeginEPSFよりも前にスタックを消費してしまった方が楽に書ける。


Copyright (C) 2012 You SUZUKI

$Id: eps.htm,v 1.4 2012/12/11 18:04:59 you Exp $