Postscript with scheme

ghostscript

色々お世話になっているgsの供給元 Ghostscript を訪ねてみた。商用版とGNU版のディアルライセンスなのね。知らんかったぞ。

試しにdebian(32Bit)でコンパイルしてみたけど、何の問題もなく6分でgsが出来上がった。もっとトラブルが出るかと思ったけど拍子抜け(ドライバー関係を何も指定しなかったから、速かったのかも?)。

Examplesの中に超有名なgolfer.epsとかtiger.epsなんてのが置いてあった。昔のOS本と言うか普及本でよくお目にかかったな。最近はリナとかがすっかかり浸透したと言うか、客層が変化しちゃって、見ること無い。

きっと、ウブとか入れるだけで、何から何までセットアップされちゃうんで、gvつかって印刷が出来るかチェックしてみましょう、なんて記事は嫌われるんだろうね。そう言えば日本語の印刷チェックで、縦書きされた日本国憲法の前文なんてのも有ったような。

tiger.epsの前文をちょっと覗いてみる。

%!PS-Adobe-2.0 EPSF-1.2
%%Creator: Adobe Illustrator(TM) 1.2d4
%%For: OpenWindows Version 2
%%Title: tiger.eps
%%CreationDate: 4/12/90 3:20 AM
%%DocumentProcSets: Adobe_Illustrator_1.2d1 0 0
%%DocumentSuppliedProcSets: Adobe_Illustrator_1.2d1 0 0
%%BoundingBox: 17 171 567 739
%%EndComments
/tigersave save def     % prevent residual side effects
  :

アドビのイラストレーターで書きましたって注釈から始まってるな。もう30年前の作品か。ドックイヤーに換算すると、2世紀も前の作品。人間世界の浮世絵みたいなものか。

冬だから、snowflak.psでも、楽しんでみますかね。雪は天からの手紙って、寺田先生がおっしゃっていたような気がするけど、ps語の勉強には、打って付けと思われる。

勉強の資料は、

A First Guide to PostScript

PostScript Operators

PostScript 基礎文法最速マスター

オペレーターをじっくり見ていると、なんだかforthの拡張版ってか、実用forthって思えるのは、気のせい?

GNU GV

gsだけじゃなくてgvってのも有ったな。前回やったののオリジナルではgvを使ってた。んな訳でgvってのを探してみたよ。

GNU GV

こちらがそうだった。案内には、

GNU GV allows viewing and navigating PostScript and PDF documents on 
an X display, by providing a graphical user interface for 
the Ghostscript interpreter. 

閲覧専用のソフトだ。これを使えば、前回のスクリプトで作成されたpsファイルも、正しいサイズで表示されたよ。変にgsのオプションをまさぐる事しなくて良かったわい(ちょっとggしちゃったけどね)。

quick blood graph

グラフを書くと言えば、オイラーの場合血圧測定データに決まっている。ログは測定日がYYMMDDHHで始まるCSVデータになってる。SlibにもCSVデータをハンドリング出来るライブラリーが揃っているようだけど、もっと手軽に扱ってしまえ。

去年の12月のデータを抜き出して、加工する。

ob$ grep '^2012' current.csv | cut -b 5- | sed 's/,/ /g' >data.txt
ob$ cat data.txt
0104 136 69 51
0121 109 62 58
 :
3105 122 70 50
3121 115 59 59

冒頭部分が2012(20年12月)のものをログから引っ張ってきて、DDHH以降のものにする。そして、カンマをスペースに変換、schemeに喰わせるデータファイルを作成。大晦日でも、紅白歌合戦を見ないでさっさと寝てしまいました。

細かい事を言うと、最左のDDHHは、DDが10進数の日付、HHは24進数の時刻なんで、本当は混ぜるな危険。なんだけど、細かい事は気にしないって言うhacker魂が発露してるのさ。

diff from …

guileでグラフ描きを試してみたら、わけわかめなエラーが出てきた。ggしたら、guileはどうもslibと相性が悪いらしい(要するに併用は、なるべく避けてください、茨の道ですって事)。 裏を取りたいぞ。どうする? で、閃いたのは、

sakae@pen:/usr/local/lib/slib$ wc *.init | grep -v total | sort -rn
   900   3059  26738 guile.init
   709   2277  21161 guile-2.init
   581   1992  17779 scheme48.init
   558   2129  17187 t3.init
   531   2029  16467 jscheme.init
   496   1796  14800 vscm.init
   489   1919  15494 chez.init
   472   1636  15230 mitscheme.init
   456   1821  14528 gambit.init
   443   1711  13929 elk.init
   431   1626  13911 mzscheme.init
   424   1613  13339 scheme2c.init
   421   1574  13255 bigloo.init
   417   1584  12833 s7.init
   393   1478  12133 kawa.init
   389   1443  11940 RScheme.init
   389   1436  11787 scsh.init
   370   1381  11438 macscheme.init
   366   1367  11246 umbscheme.init
   366   1335  11351 STk.init
   357   1283  11192 sisc.init
   286   1086   8745 pscheme.init
     5     29    224 scm.init

xxx.initってスクリプトは、xxxなschemeでslibを使う為の調整スクリプトなんだな。単純にguile系のスクリプトは調整の為の行数が多いって事が分かる。基準は、超昔にお世話になったscmって言うschemeなんだな。ってか、freeで使えるものはscmしか無かったような、、、と、昔を懐かしんでいます。

sakae@pen:/usr/local/lib/slib$ cat scm.init
;"scm.init" Configuration file for SLIB for SCM         -*-scheme-*-

;;; SCM supports SLIB natively; no initialization file is actually
;;; required.  So just stub this file:
(slib:load (in-vicinity (library-vicinity) "require"))

しっかりSCM schemeはSLIBをネイティブにサポートしてますって説明されてた。SCMのアプリケーション寄りライブラリィーをしこしこと作者様は開発してた。それに眼を付けた、よそのschemeインプリメンターが、オイラーの所でも使わせて貰いたいと、xxx.initを寄贈したんだろうね。

以前に宿題にしてた、schemeで書いたprologが色々なschemeをサポートしてる、その舞台裏はどうなってる? の、回答がここに示されていたよ。

ってな事で、以降は、OpenBSDでも動くgambitで試してみる。

真似してね

set-colorで色付け出来ないのは不便だなあ、なんて思ってコードを見てたんだ。そしたら、色の代わりに、灰色具合も取り扱っておりますって説明が有った。

;;@0 sets the PostScript color to the color of the given string, or a
;;grey value between black (0) and white (100).

文字列で色名を与えるか、数字の0から100の範囲で、灰色具合を指定出来る。

(define (set-color clrn)
    :
  (define (num->str x)
    (define num (inexact->exact (round (+ 1000 (* x 999/255)))))
    (scheme->ps "." (substring (number->string num) 1 4) " "))
  (cond ((number? clr) (string-append (num->str clr) " setgray"))
        (clr (apply scheme->ps
                    (append (map num->str (color->sRGB clr)) '(setrgbcolor))))
        (else "")))

担当コードの主要部分だ。これを真似して、自前で色を付けられるようにしちゃえ。postscript語レベルは、r g b setrgbcolor ってコードを吐き出させればいいんだな。

なら、scheme語レベルで、命令を拡張だ。RGBは、3つで一組になるから、リストでいいだろう。それなら色の名前として、オイラーが勝手に付けられるからね。女房の白髪染めに、リッチ チョコレートなんて色名が付いてたぞ。なんだか、茶髪より高級そう。

(define (set-rgb rgb)
  (apply scheme->ps
         (append
          (map (lambda (n) (string-append (number->string n) " ")) rgb)
          '(setrgbcolor))))

使えそうな num->strは、内蔵品だったので、自前のやつにした。その結果、数値は0-255じゃなくて、0.0-1.00の浮動小数で指定する必要がある。

チョコレートで思い出した。来月はチョコレート屋の陰謀でバレンタインだ。便乗して、本を送りましょうなんてのもあったな。それより、コンビニの陰謀で恵方巻が先か。平賀源内は、夏の土用の丑の日に鰻を食べましょうで、うなぎ屋を儲けさせたな。いつの時代も変わんらんなあ。

オイラーはひねくれものだから、寒の土用の丑の日に、うなぎを食べるのさ。勿論、冷酒と共にね。ああ、思うだけで、よだれが出てくるぞ。

リッチな色に、どんなのが有るか、ちょいと検索してみる。

vbox$ grep rich clrnamdb.scm
  ("richgold" "sRGB:161/82/38" 1016)

黄金はリッチに決まってるな。goldで探すと、goldensandなんてのが出てきた。砂金ですかい。

グラフ完成

と言う訳で、グラフ描きのスクリプトが出来上がった。

ob$ gsi eps.scm
ob$ emacs BP.eps
ob$ ps2pdf -dEPSCrop BP.eps
ob$ firefox BP.pdf

スクリプトを起動すると、上で作ったdata.txtを入力元としてBP.epsが作成され、そのままgvで表示される。気に入らなかったら、emacsで直接epsファイルを編集。ps2pdfで、最終的にpdfファイルに変換。最後はブラウザーで確認。

最終結果のBP.pdf を載せて億。

スクリプトが吐き出したオリジナルなBP.epsの主要部分は下記

/slib:G24
[
 [      104     136     69      51]
 [      121     109     62      58]
         :
 [      3105    122     70      50]
 [      3121    115     59      59]
] def
whole-page
[ 100 3124 ] [ 50 160 ] setup-plot
gpush
 /fontsize 12 def /Helvetica-Oblique fontsize selectfont
(Blood pressure) (Using Gambit scheme) title-top
gpop
plotrect outline-rect
leftedge (Maximum pressure) 10 rule-vertical
gpush
plotrect clip-to-rect
[ slib:G24 0 1 ] line plot-column
.799  setgray
[ slib:G24 0 1 ] mountain plot-column
gpop
bottomedge (Date ddhh) 5 rule-horizontal
0. .2 1. setrgbcolor
graphrect [ 100 3124 ] [ 50 160 ] setup-plot
gpush
plotrect clip-to-rect
[ 5 2 ] 0 setdash
[ slib:G24 0 2 ] line plot-column
gpop
rightedge (Minimum pressure) -10 rule-vertical

与えたデータは、ps語のarrayになって収納されてる。配列名はgsiが勝手に決めたslib:G24って名前だ。

命令は最後に置かれるってのがforthと言うかpostscriptの約束。例えば、rule-verticalって命令は、3つの引数が必要。最初のleftedgeは、左端に配置、文字列は最高圧力ね、縦線に生える髭の長さは10ポイントにしてって具合。

第二の折れ線は、破線なんだけど、その破線の仕様はsetdashで定義されてる。最初の引数は配列。その内容の5は線の長さ2は空白の長さ、こんな具合に俺様仕様の破線を定義出来る。第二引数の0は、定義した破線のどの部分から使い始めるかって指定。変なこだわりが有るな。 オイラーは5,2って組み合わせがいやだったんで、2,2って組み合わせに変更してからpdfにしといた。こういう事が出来るんで、楽しいな。

それじゃ、もう一つ、主役のplot-columnを調べてみる。

% Plots column K against column J of given two-dimensional ARRAY.
% The arguments are:
%   [ ARRAY J K ] J and K are column-indexes into ARRAY
%   [ PREAMBLE RENDER POSTAMBLE ] Plotting procedures:
%       PREAMBLE  - Executed once before plotting row
%       RENDER    - Called with each pair of coordinates to plot
%       POSTAMBLE - Called once after plotting row (often does stroke)
/plot-column
{ /GPROCS exch def aload pop /YDX exch def /XDX exch def /DATA exch def
  /GD glyphsize def
  /GR GD .5 mul def
  gsave
    /ROW DATA 0 get def ROW XDX get ROW YDX get gtrans moveto
    GPROCS 0 get exec % preamble
    /PROC GPROCS 1 get def DATA {dup XDX get exch YDX get gtrans PROC} forall
    GPROCS 2 get exec stroke % postamble
  grestore
} bind def

コメントにも有るように、配列の内容を表示する。/xxx … def が定義。{}で囲むとそれがブロックになる。ブロックの中に /GPEOCE exch def みたいなのが複数存在してる。これは、ブロック内部だけに通用するローカル定義だな。

ps-modeで閲覧してると、forallみたいなのは、特別な色が付いているので、初心者でもこれはps語がネイティブにサポートしてる命令と分かって好都合。

後は、地道に追って行くだけだ。

code

OpenBSDでgambitするなんて、特殊x特殊だと思われので、リナでgaucheのやり方を書いておく。下記コード冒頭でloadしてるのを、コメントにする。それから、

sakae@pen:/tmp/t$ gosh -u slib  eps.scm

これで、結果が表示される。結果の画像窓を削除すれば、goshも終了する。

;; eps-graph
(load "/usr/local/lib/slib/gambit.init") ; For Gambit scheme

(require 'eps-graph)
(require 'line-i/o)
(require 'string-port)

     (define irradiance
       (let ((file "data.txt"))
         (define (read->list line)
           (define elts '())
           (call-with-input-string line
             (lambda (iprt) (do ((elt (read iprt) (read iprt)))
                                ((eof-object? elt) elts)
                              (set! elts (cons elt elts))))))
         (call-with-input-file file
           (lambda (iprt)
             (define lines '())
             (do ((line (read-line iprt) (read-line iprt)))
                 ((eof-object? line)
                  (let ((nra (make-array (A:floR64b)
                                           (length lines)
                                           (length (car lines)))))
                    (do ((lns lines (cdr lns))
                         (idx (+ -1 (length lines)) (+ -1 idx)))
                        ((null? lns) nra)
                      (do ((kdx (+ -1 (length (car lines))) (+ -1 kdx))
                           (lst (car lns) (cdr lst)))
                          ((null? lst))
                        (array-set! nra (car lst) idx kdx)))))
               (if (and (positive? (string-length line))
                        (char-numeric? (string-ref line 0)))
                   (set! lines (cons (read->list line) lines))))))))

(define (set-rgb rgb)
  (apply scheme->ps
         (append
          (map (lambda (n) (string-append (number->string n) " ")) rgb)
          '(setrgbcolor))))

(let ((xrange '(100 3124))
      (yrange '(50 160))
      (nblue '(0.0 0.2 1.0)))
       (create-postscript-graph
        "BP.eps" '(600 300)
        (whole-page)
        (setup-plot xrange yrange)
        (in-graphic-context
         (set-font "Helvetica-Oblique" 12)
         (title-top
          "Blood pressure"  "Using Gambit scheme"))
        (outline-rect plotrect)
        (rule-vertical leftedge "Maximum pressure" 10)
        (in-graphic-context (clip-to-rect plotrect)
                            (plot-column irradiance 0 1 'line)
                            (set-color 80)
                            (plot-column irradiance 0 1 'mountain))
        (rule-horizontal bottomedge "Date ddhh" 5)
        ;; below are 2'nd line
        (set-rgb nblue)
        (setup-plot xrange yrange graphrect)
        (in-graphic-context (clip-to-rect plotrect)
                            (set-linedash 5 2)
                            (plot-column irradiance 0 2 'line))
        (rule-vertical rightedge "Minimum pressure" -10) ))

(system "gv BP.eps")

This year's Index

Home