再利用できる資産を、文書から切り離して管理し、どの文書からも参照できるようにする仕組みです。 DLLや共有ライブラリのような概念に近いかもしれません。 フォントもリソースの一種です。
findresourceオペレータを使う。
リソースの名前、リソースのカテゴリを指定する。
リソースのカテゴリにはFont
・CIDFont
・ProcSet
などがある。
このうちFont
はfindfontオペレータを使うことがほとんどだと思うが、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することになる。
defineresourceオペレータを使う。
リソース名・リソースインスタンス・リソースカテゴリを指定する。
フォントの場合はdefinefont、コンポジットフォントの場合はcomposefontなどのオペレータがあるのであまりお世話になることはないだろう。
PLRMを見ていると、CIDFontType
が定義されているフォントをdefinefontすると、CIDフォントとして定義されるように読めるのだが、GSでは普通のフォントになってしまう(CIDFont
カテゴリでfindresourceしても見つからない)。
明示的にCIDFont
カテゴリでdefineresourceしないといけないようだ。
ProcSetの場合はプロシージャなどを登録した辞書をリソースインスタンスとして指定する。 要は辞書が得られるだけなので、プロシージャ以外のものが入っていても構わない。
ProcSetの場合、defineresourceできるということは、リソースにするプロシージャを全部知っているということだから、リソースにできてもあまりありがたみがない。 つまり、リソース部分を外部のファイルに分けないと意味がない。 リソース定義部分を適当なファイルに書いて、インタープリタ実行時に読み込ませることで、他のプログラムでいちいちプロシージャを書かなくても、findresouceするだけでサブルーチン群が利用可能になる。
問題はどうやってインタープリタに食わせるか?である。 これはPostScriptの規格では決めれておらず、処理系ごとに異なる。 GhostscriptとDistillerで違うし、PostScriptプリンタもあるのだから。
環境変数GS_OPTIONS
でも指定できるが、gsnd
などでちょっと面倒なことが起きる。
Resource/Init
にあるgs_init.ps
あたりに仕込む。
システムが更新されると上書きされちゃう可能性がある。
Resource
ディレクトリを利用する。
Resource
ディレクトリ(通常は
/usr/share/ghostscript/ver/Resource
)の下に、カテゴリ名と同じ名前のディレクトリを掘り、その下にリソース名と同じファイル名でリソース定義ファイルを置く。
findresourceしたときにメモリ上にリソースが存在しないと、Ghostscriptはカテゴリ名とリソース名からファイルを探してそれを読み込むようになっている。
必要になったときに読み込まれるので、ファイルにエラーがあってもとりあえずGSは起動するし、起動時間も若干短くて済むはず。
ProcSet
ディレクトリは初期状態では存在しないが、作成さえしてやれば同様に動くようだ。
ファイル名とリソース名が異なる場合、Resource
ディレクトリからはシンボリックリンクを張っておけばよい。
ひとつのファイルで複数のリソースを定義してしまい、それぞれのリソース名についてシンボリックリンクを作成する、というゴマカシもできそう。
Distillerの場合、初期化で介入できるのは2ヵ所。
ひとつはStartup
フォルダで、Distillerは起動時にこのフォルダ内のファイルを全部実行する。
ただし、実行する順番を仮定してはいけない。
これは、リソース定義時に他のリソースに依存することはできないことを意味する。
また、他のファイルを読み込んだり実行したりする場合、ファイル名をフルパスで書く必要がある(とどこかに書いてあった気がする)。
システムが違うとパスは変わってくるので気をつける必要がある。
もうひとつはprologue.ps
。
これはジョブ開始時に読み込まれ、PDF変換の設定で実行の有無を指定できる。
つまり、変換ごとに実行するかどうかを指定できる。
逆に言うと、実行するように設定しないと実行されないので注意。
epilogue.ps
というのもあり、これはジョブ終了時に実行される。
これらのファイルの位置は当然システムによって異なる。 Windowsの場合、ドキュメントフォルダのAppData/Roaming(スタートメニューで「shell:appdata」とやると開くフォルダ)の、Adobeフォルダの下のどこかにあるはず。
とにかく、こうしてファイルが読み込まれるようにしてしまえば、あとはどんな処理系でもfindresourceと書けばリソースが利用可能になる。
リソース内だけで使うサブルーチン、他のリソースで定義されたプロシージャ、リソース定義時のみに使うプロシージャの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
を含む辞書subr
をbeginするのだが、下線の部分は//subr
とはできない。
なぜなら、subr
はdefrsc
を実行したときに定義されるのに対し、//subr
はdefrsc
を定義しているときに評価される。
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
subr
はcurrentdict end /subr exch def
としてもよいが、このように先に定義してしまったほうが融通が利く。
例えば、サブルーチン定義中に定義時のみに使うプロシージャを定義したくなったら、一度subr
をendして定義時のみ使うプロシージャをdefし、もう一度subr
をbeginすればよい。
場合によってはProcSetを定義している辞書にも当てはまる。
先ほどの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
ProcSetInit
というプロシージャーのみが定義されている。
ProcSetInit
を実行すると、myproc
などが定義され、ProcSetInit
はリソースの辞書そのものと定義されて、リソースが定義しなおされる。
defineresourceの結果(ProcSet辞書)を返す。
ProcSetInit
を実行すると単にプロシージャが定義されている辞書、つまりリソースそれ自身を返す。
システムによってファイルシステムはさまざまなので、リソースに対応するファイル名もさまざまである。 そこで、リソースカテゴリとリソース名から、リソースファイル名を得る方法が規定さている(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
を使うんじゃねぇ!」とも書いてあるんだよね。
どちらも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 $