home / uni / ps / resource PostScript言語 / リソース

 再利用できる資産を、文書から切り離して管理し、どの文書からも参照できるようにする仕組みです。 DLLや共有ライブラリのような概念に近いかもしれません。 フォントもリソースの一種です。

home / uni / ps / resource ●リソースを探す

 findresourceオペレータを使う。 リソースの名前、リソースのカテゴリを指定する。 リソースのカテゴリにはFontCIDFontProcSetなどがある。 このうちFontfindfontオペレータを使うことがほとんどだと思うが、CIDフォントはfindresourceじゃないと見つけられない。

 ProcSetはサブルーチン集のようなもの。 標準でもCMapを定義するためのCIDInit ProcSetなどがある。

GS>/MS-PGothic /CIDFont findresource
Loading a TT font from /cygdrive/c/windows/fonts/msgothic.ttc to emulate a CID font MS-PGothic ... Done.
GS<1>==
-dict-

 結果として見つかったリソースが得られる。 これはリソースによって異なるが、フォントやProcSetなら辞書が返ってくる。 フォントならfindfontが返した辞書と同じように扱えばよいし、ProcSetなら通常はbeginすることになる。

home / uni / ps / resource ●リソースを定義する

 defineresourceオペレータを使う。 リソース名・リソースインスタンス・リソースカテゴリを指定する。 フォントの場合はdefinefont、コンポジットフォントの場合はcomposefontなどのオペレータがあるのであまりお世話になることはないだろう。 PLRMを見ていると、CIDFontTypeが定義されているフォントをdefinefontすると、CIDフォントとして定義されるように読めるのだが、GSでは普通のフォントになってしまう(CIDFontカテゴリでfindresourceしても見つからない)。 明示的にCIDFontカテゴリでdefineresourceしないといけないようだ。

 ProcSetの場合はプロシージャなどを登録した辞書をリソースインスタンスとして指定する。 要は辞書が得られるだけなので、プロシージャ以外のものが入っていても構わない。

home / uni / ps / resource ●外部ファイルによるリソース定義

 ProcSetの場合、defineresourceできるということは、リソースにするプロシージャを全部知っているということだから、リソースにできてもあまりありがたみがない。 つまり、リソース部分を外部のファイルに分けないと意味がない。 リソース定義部分を適当なファイルに書いて、インタープリタ実行時に読み込ませることで、他のプログラムでいちいちプロシージャを書かなくても、findresouceするだけでサブルーチン群が利用可能になる。

 問題はどうやってインタープリタに食わせるか?である。 これはPostScriptの規格では決めれておらず、処理系ごとに異なる。 GhostscriptとDistillerで違うし、PostScriptプリンタもあるのだから。

Ghostscriptの場合
  • コマンドラインで指定する。 一番安直な方法。 リソースを定義しているファイルが複数あるなら、初期化用PostScriptファイルを書いて、その中に各リソース定義用ファイルをずらずらと書き(runあるいはrunlibfileオペレータで実行する)、この初期化用PostScriptファイルをコマンドラインで指定する。 実際に実行したいプログラムはコマンドライン上で続けてそのあとに指定すればよい。 いちいち初期化ファイルを指定するのが面倒ならば、リソース定義用PostScriptファイルを最初に読み込むようなシェルスクリプトを書き、gsの代わりに実行するか、gsを置き換えればよい。

     環境変数GS_OPTIONSでも指定できるが、gsndなどでちょっと面倒なことが起きる。

  • 初期化時に読み込まれるファイルにしのばせる。 やはり初期化用PostScriptファイルを書いて、Resource/Initにあるgs_init.psあたりに仕込む。 システムが更新されると上書きされちゃう可能性がある。
  • Resourceディレクトリを利用する。 Resourceディレクトリ(通常は

    /usr/share/ghostscript/ver/Resource

    )の下に、カテゴリ名と同じ名前のディレクトリを掘り、その下にリソース名と同じファイル名でリソース定義ファイルを置く。 findresourceしたときにメモリ上にリソースが存在しないと、Ghostscriptはカテゴリ名とリソース名からファイルを探してそれを読み込むようになっている。 必要になったときに読み込まれるので、ファイルにエラーがあってもとりあえずGSは起動するし、起動時間も若干短くて済むはず。 ProcSetディレクトリは初期状態では存在しないが、作成さえしてやれば同様に動くようだ。 ファイル名とリソース名が異なる場合、Resourceディレクトリからはシンボリックリンクを張っておけばよい。 ひとつのファイルで複数のリソースを定義してしまい、それぞれのリソース名についてシンボリックリンクを作成する、というゴマカシもできそう。

Distillerの場合

 Distillerの場合、初期化で介入できるのは2ヵ所。 ひとつはStartupフォルダで、Distillerは起動時にこのフォルダ内のファイルを全部実行する。 ただし、実行する順番を仮定してはいけない。 これは、リソース定義時に他のリソースに依存することはできないことを意味する。 また、他のファイルを読み込んだり実行したりする場合、ファイル名をフルパスで書く必要がある(とどこかに書いてあった気がする)。 システムが違うとパスは変わってくるので気をつける必要がある。

 もうひとつはprologue.ps。 これはジョブ開始時に読み込まれ、PDF変換の設定で実行の有無を指定できる。 つまり、変換ごとに実行するかどうかを指定できる。 逆に言うと、実行するように設定しないと実行されないので注意。 epilogue.psというのもあり、これはジョブ終了時に実行される。

 これらのファイルの位置は当然システムによって異なる。 Windowsの場合、ドキュメントフォルダのAppData/Roaming(スタートメニューで「shell:appdata」とやると開くフォルダ)の、Adobeフォルダの下のどこかにあるはず。

 とにかく、こうしてファイルが読み込まれるようにしてしまえば、あとはどんな処理系でもfindresourceと書けばリソースが利用可能になる。

home / uni / ps / resource ●リソース内で使うプロシージャ

 リソース内だけで使うサブルーチン、他のリソースで定義されたプロシージャ、リソース定義時のみに使うプロシージャの3つに大きく分けて考えてみる。

 リソース内だけで使うサブルーチンについては、辞書を抱き込んだ方が安全だろう。 呼び出し元で知らずに同じ名前を定義されると面倒なことになる。 例えば、MyProcSetがサブルーチンをsubrという名前の辞書にまとめていたとする。

2 dict begin
1 dict begin
/subproc { ... } bind def
currentdict end /subr exch def

/myproc {
    subr begin
    ... subproc ...
    end
} bind def

/MyProcSet currentdict /ProcSet defineresource pop
end

 呼び出し元がsubrという辞書の存在を知らずに、以下のように書いてしまったとする。

/caller {
    /MyProcSet /ProcSet findresource begin
    10 dict begin
    /subr { ... } bind def % やってもうた!
    myproc % MyProcSet内のプロシージャ、うまく動くと思う?
   end end
} bind def

 もちろんうまく動かないだろう。 この場合、//を使った即時評価を使うのが簡単。

1 dict begin
/subproc { ... } bind def
currentdict end /subr exch def

/myproc {
    //subr begin
    ... subproc ...
    end
} bind def

こうするとmyprocの定義時にsubrが展開され、辞書の実体そのものがプロシージャに組み込まれる。 myprocの実行時にはsubrという名前を使わず、辞書の実体を直接beginするので、呼び出し側がsubrという名前を使ってしまっても正常に動く。 使えない場合(辞書に名前が付いてない・付けられないとか、プロシージャの中で辞書を定義しているような場合)は、「フィルタプロシージャとパラメータ」で書いたような方法が使える。

/defrsc {
    1 dict begin
    /subproc { ... } bind def
    currentdict end /subr exch def

    /mainproc [
        subr { begin
        ...
        end } /exec cvx
    ] cvx bind def
} bind def

 defrscを呼ぶとmainprocが定義され、その中でsubprocを含む辞書subrbeginするのだが、下線の部分は//subrとはできない。 なぜなら、subrdefrsc実行したときに定義されるのに対し、//subrdefrsc定義しているときに評価される。 defrscの定義中はまだsubrは存在しないから、未定義エラーになるか、他の変な辞書を持ってきてしまう。

 次に、他のリソースを使うような場合。 これは素直にプロシージャ冒頭でdefineresourceするのがよいと思う。 プロシージャ定義の外でdefineresourceし、辞書の実体をプロシージャに放り込むことも不可能ではないが、defineresourceしたあとに呼び出しているリソースにパッチを当てることがあるかもしれない。 それに、Distillerは初期化ファイルの実行順を仮定できないので、Startupフォルダにリソース定義ファイルを放り込んだ場合は、プロシージャ定義中に他のリソースを参照することができない。

 ただ、いちいち/ProcSet findresource beginを書いて回るのは面倒なのと、使うProcSetが増減したときにプロシージャおしまいのendの増減を忘れがちなので、こんな簡単なプロシージャを定義しておくとよいかもしれない。

/proclist [
    /MyProcSet /YourProcSet /AndSoOn
] def

% proc <prepproc> proc'
/prepproc { [
    proclist { /ProcSet /findresource cvx /begin cvx } forall
    counttomark 1 add index aload pop
    proclist length { /end cvx } repeat
] cvx exch pop } bind def

 こんな風に使う。

/caller {
    ...
} prepproc bind def

prepprocを実行した時点、つまり定義直前で以下のように展開されている。

/caller {
    /MyProcSet /ProcSet findresource
    /YourProcSet /ProcSet findresource
    /AndSoOn /ProcSet findresource
    ...
    end end end
} bind def

 最後に、リソース定義時のみ使うプロシージャ。 前述のprepprocなどがそう。 これは最初にローカルに辞書を作って、最後に捨てればよい。 そうすると、大体こんな感じになる。

10 dict begin % localdict
/proclist [ ... ] def
/prepproc { proclist ... } bind def

/subr 10 dict def
subr begin
/subproc { ... } bind def
end % subr

10 dict begin % procsetdict
/myproc {
    //subr begin
    ...
    end
} prepproc bind def
/MyProcSet currentdict /ProcSet defineresource pop
end % procsetdict
end % localdict

 subrcurrentdict end /subr exch defとしてもよいが、このように先に定義してしまったほうが融通が利く。 例えば、サブルーチン定義中に定義時のみに使うプロシージャを定義したくなったら、一度subrendして定義時のみ使うプロシージャをdefし、もう一度subrbeginすればよい。 場合によってはProcSetを定義している辞書にも当てはまる。

home / uni / ps / resource ●定義時に他のリソースに依存しちゃう場合

 先ほどのprepprocのように、定義しようとしているプロシージャに手を加えるようなプロシージャは、リソース定義時に実行することになる。 prepprocは小さいのでリソースファイルごとに書いても大した手間ではないが、もっと大きくなったり、数が増えたりすると煩雑になる。 リソース定義用ProcSetリソースのようなものを用意しておいてそこで定義しておけばよいのだが、Distillerでも使えるようにしようとすると、先ほどの「実行順を仮定できない」というのがネックになる。

 対策のひとつは、例えばDistillerではリソース定義用ProcSetリソースはStartupから実行し、残りのリソースはprologue.psから実行する方法。 もうひとつは先ほどの例のdefrscのように、プロシージャを定義するプロシージャをつくり、リソースをロードしたらこのプロシージャを実行してもらう方法。 プロシージャを定義するプロシージャの中ならば、他のリソースも自由に使える。 後者の方が汎用性は高いが、定義が煩雑になるのと、リソースを使う側でひと手間必要になるのが欠点。

 一度リソースを定義してしまうとfindresourceは意外と速い(Ghostscriptで実験)ので、定義の最後でリソースを置き換えてしまうとよい。 プロシージャ定義プロシージャはリソース辞書そのものと定義してしまえばよい。 たとえば、プロシージャ定義プロシージャをProcSetInitという名前にするのなら、

10 dict begin
/ProcSetInit {
    10 dict begin
    /myproc { ... } bind def
    ...
    /ProcSetInit currentdict def
    /MyProcSet currentdict /ProcSet defineresource
    end
} bind def
/MyProcSet currentdict /ProcSet defineresource pop
end

とすればよい。 使う側はこんな感じ。

/MyProcSet /ProcSet findresource /ProcSetInit get exec begin
...
end
home / uni / ps / resource ●リソースのファイル名を得る

 システムによってファイルシステムはさまざまなので、リソースに対応するファイル名もさまざまである。 そこで、リソースカテゴリとリソース名から、リソースファイル名を得る方法が規定さている(PLRM 3.9.4)。 ただし、システムやカテゴリによってはサポートしてないかもしれない。

 サポートしていれば以下のようにすればリソース名に対応するファイル名を得られる。

/resourcecategory /Category findresource begin
    /resourcename 256 string ResourceFileName
end

 /Categoryというのは「リソースカテゴリを規定しているリソース」というカテゴリである(メタカテゴリ?)。 調べたいカテゴリ名を指定してfindresourceすると辞書が得られ、いろいろな情報が入っているが、その中にResourceFileNameというプロシージャーが入っている。 得られた辞書をbeginしてから、このプロシージャーをリソース名・文字列と共に呼び出すと、ファイル名が文字列で得られる。 この文字列はfileオペレータなどに渡すことができる。

 ただし、ResourceFileNameはこのカテゴリにおけるリソース名からリソースファイル名へのマッピングを示したものに過ぎず、個々のリソースファイルの事情については勘案してない。 リソースファイルがあり、アクセス権が適切ならばfileオペレータは成功するが、それ以外の事情で失敗する場合もある。

 DistillerでTrueTypeのフォントデータをsfntsから得ようとしたら、データじゃなくてデータの長さを示す数値が返ってきたんだよねー。 ア○ビ何やってんだ、と。 これ知ったときには途方に暮れたけど、ResourceFileNameでフォントファイル名を得られることに気づいて、縦書きプロジェクトが先に進んだんですわ。 いやー、やっぱりアドビすげー!(どっちだよ) Ghostscriptだとフォント辞書の中にPathとかLoadPathとかってのが入ってるんだけどねー、PostScript標準じゃないし。 ただ、PLRMには「ページ記述にResourceFileNameを使うんじゃねぇ!」とも書いてあるんだよね。

home / uni / ps / resource ●Ghostscriptのrunlibfile・findlibfile

 どちらもGS_LIB環境変数や-Iオプションの指定などを勘案してファイルを探してくれる。 フルパスで指定してもよい。

 runlibfileはどういうわけか「Ghostscript and the PostScript language」には載っていない(「How to use Ghostscript」の方に載っている)のだが、runの代わりに使える。

 findlibfileはファイルを探してオープンしてくれる。 findlibfileはファイルが見つからない場合にもエラーにならず、元の文字列とfalseを返してくれるので便利である。 見つかった場合は実ファイル名、読み込みモードでオープンしたファイルオブジェクト、trueを返す。 ファイルオブジェクトは実行可能にはなっていないので、実行する場合はcvxする必要がある。

(akamoz-util.ps) findlibfile {
    (executing ) print 1 index print (...\n) print
    cvx run
    (finished executing ) print print (.\n) print
} {
    print (is not found.\n) print
} ifelse

 Ghostscriptの場合、プログラム冒頭でfindlibfileを使い、リソース定義ファイルが見つかったら実行、そのあとfindresourceすれば、リソース定義ファイルをResourceディレクトリの下か、パスの通っている場所のどちらかに置いておけば動く。

 この方法だと、リソース定義ファイルが更新された場合、とりあえずパスの通ったところに置けば、更新されたほうのリソース定義が使われるメリットもある。 Distillerでは動かないので、以下のようにするとよいだろう。

systemdict /findlibfile known { % running on ghostscript
    (resourcefilename) findlibfile {
        (running ) print 1 index print (\n) print
        cvx exec
        print ( done.\n) print
    } { pop } ifelse
} { pop } ifelse

/resourcename /category findresource
...

リソース定義ファイルがResourceディレクトリにないときにfindlibfileを実行するのだから、リソース定義ファイル名を得るのにResourceFileNameは使えない。 コードを書いた人はリソースファイル名を知っているはずだから、そのファイル名を書けばよい。


Copyright (C) 2016 akamoz.jp

$Id: resource.htm,v 1.2 2016/02/21 15:01:01 you Exp $