和田研フォントキットの肉付け処理を解き明かす -(4)-

改善点をざっと見た所、rm-geta(in gothic.l)の処理が一番手を付け易そうだったのでこの部分を解析してみる事にした。さてさて、ソースコードだ。

(defun rm-geta (prim getalen)
  (let* ((points (car prim))
         (elements (cadr prim))
         (newelements)
         (linkpoints)
         (yokopoints))
    (do ((l elements (cdr l))(p)(link))
      ((atom l))
      (and (setq link (assq 'link (cddar l)))
           (setq linkpoints (append (cdr link) linkpoints)))
      (and (eq (caar l) 'yoko)
           (setq p (cadar l))
           (setq yokopoints `(,(car p) ,(cadr p) .,yokopoints)))
      (or (memq (caar l) '(tate magaritate))
          (setq linkpoints (append (cadar l) linkpoints))))

今回は「意」という文字を題材にし、中心の「日」の下駄が取り除かれる様子を見ていこうと思う。pointsには漢字を構成する点の座標のリスト、elementsには結合情報( (tate 0 2)等が代入される。「意」のprimdataをは下のようになっている(applykanji -> normkanji -> rm-limitを順に適応した後の形)。点12と点16が「日」の下駄を構成する2点である。rm-getaはこの2点を取り除く処理を行っている。最初のandではlink点をlinkpointsに追加している。次のandでは(yoko (x y))という形式であれば、(x y)をyokopointsに追加している。最後のorは、(tate hogehoge)もしくは(magaritate hogehoge)の場合は何もせずそれ以外の場合(例えばyokoとか)は構成点をlinkpointsにつなげる。すなわち、結果的にlink点とtate線を構成する要素点以外の点がすべてlinkpointsに追加される。どうでも良いが、このorの使いかたはちょっと勉強になった。if not文の代わりとして使えるのか。

BUG? : (tate (link x1 y1) (link x2 y2))とかなっている場合はlinkpointsに追加されない気がするが、果たして?

(((200.0 15.0) (200.0 44.93975 (LINK-OK T)) (287.22797 56.549038)
  (260.3886 89.54386) (210.54404 118.26157) (101.26943 57.77107)
  (154.94818 112.15142) (15.0 124.37173) (383.08292 124.37173)
  (45.673576 45.85627) (353.36786 45.85627) (303.22415 163.76692)
  (303.22415 261.85522) (299.65317 249.30905) (298.7604 203.97173)
  (98.78454 163.76692) (98.78454 260.71466) (98.78454 249.30905)
  (98.78454 203.97173) (118.71968 277.12338) (118.71968 371.1257)
  (296.23987 371.1257) (296.23987 332.34976) (338.12665 279.47345)
  (384.99997 342.14163) (177.56065 264.58972) (243.38274 322.94952)
  (69.85176 282.6068) (15.0 338.61658))
 ((TATE (0 1)) (HIDARI (2 3 4)) (TEN (5 6)) (YOKO (7 8)) (YOKO (9 10) (LINK 1))
  (TATE (11 12) (LINK 14 13) (LINK 3 2)) (TATE (15 16) (LINK 18 17) (LINK 7 6))
  (YOKO (17 13)) (YOKO (18 14)) (YOKO (15 11)) (KOKORO (19 20 21 22))
  (TEN (23 24)) (TEN (25 26)) (TEN (27 28)))
 (YUNIT . 61.10153) (XUNIT . 129.6496) (XLIMIT 0 400) (CENTER . 200.0)) 

この様にして構築されたlinkpoints, yokopointsは次のようになる。上のprimと比べて頂きたい。

-- linkpoints --
(27 28 25 26 23 24 19 20 21 22 15 11 18 14 17 13 18 17 14 13 9 10 1 7 8 5 6 2 3 4) 

-- yokopoints --
(15 11 18 14 17 13 9 10 7 8) 

さて、次々行こう。

    (do ((l elements (cdr l))(epoints)(p1)(lastp)(rp1)(link)(yokolink))
      ((atom l)
       `(,points ,(nreverse newelements) .,(cddr prim)))
      (cond ((memq (caar l) '(tate magaritate))
             (setq epoints (copy-list (cadar l)))
             (setq lastp (last epoints))
             (setq rp1 (nth (setq p1 (car lastp)) points))
             (setq link (assq 'link (cddar l)))

     ほげほげ...

            (t (push (car l) newelements))))))

do文を見るとどうやらelementsを順番に処理し、newelementsに新しいelementsを構築して返すという処理をしているようだ。ふむふむ、という事はnewelementsに注目しながら読んで行けば良いという事かな。

さて、cond文に入ろう。elementがtate or magaritateの場合は最初の長い処理が行われ、そうでない場合は単に今の状態のelementをnewelementsにpushしているだけのようだ。つまりyokoなんかは全く見ていないという事か(意味的には下駄は縦方向にしか現れないという事だ)。epoints. lastp, rp1は(tate 0 1)の場合次のようになる。epointsは線を構成する点のリスト、laspは終端点、rp1は終端点の座標を示しているようだ。linkは現在処理しているelementのlink点のリストである。

-- epoints --
(0 1) 

-- lastp --
(1)

-- rp1 -- 
(200.0 44.93975 (LINK-OK T)) 

さて、次々。

             (and link
                  (setq yokolink
                        (do ((ll (cdr link)(cdr ll))(ret))
                          ((atom ll)(nreverse ret))
                          (and (memeq (car ll) yokopoints)
                               (push (car ll) ret)))))

do文を見る限りlinkを順々に処理し、retを返しているようだ。それをsetq yokolinkしているという構造になっている。肝心のdoループの中身を見ていこう。linkというのは現在処理しているelementのlink点の配列であった。そのlink点が先ほど構成したyokopointsに含まれている場合、その点をretにpushしている。つまりまとめると、任意のyoko線とlinkしている点を蓄えている事になる。これがyokolinkだ。

             (cond ((or (null link)(null yokolink)(memeq p1 linkpoints))
                    (push (car l) newelements))
                   (t
                    (do ((ll yokolink (cdr ll))(minlink)(minlen)(p)(len))
                      ((atom ll)
                       (cond ((< minlen getalen)
                              (rplaca lastp minlink)
;                              (break)                                                                                                         
                              (push `(,(caar l) ,epoints
                                      (link .,(remove minlink (cdr link)))
                                      .,(cddar l)) newelements))
                             (t
                              (push (car l) newelements))))

                      (setq p (nth (car ll) points))
                      (setq len (metric2 rp1 p))
                      (and (or (null minlink)(< len minlen))
                           (setq minlink (car ll) minlen len))))))

最初のor文ではlink, yokolinkが空の場合を除き、さらにlinkpointsに現在処理しているelementの終端点が含まれる場合を除いている。link点になるような点に下駄は発生しないしね...そりゃーそーだ。なのでこの部分もそのままnewelementsにpush。

その次のが本丸だ。先ほど構築したyokolinkに対して順々に処理を行っている事が分かる。minlenという未知変数が出てきているのでひとまずcond文を飛ばして読み進めよう。pは現在処理しているyokolinkの要素の点の座標。rp1は現在しょりしているelementの終端点の座標。lenはその二つの距離を取っている。つぎのand文と合わせて考えてみると「終端点と一番近いlink点(それもyokoとlinkしている奴)」がminlink、「終端点とminlinkとの距離」がmlnlenだ。さて、condに戻ろう。そのminlenがgetalenよりも短い場合にはelementの終端点をminlinkにし(rplaca)、さらにlink点からminlinkを取り除く。こうする事で点が一つ取り除かれた、つまり下駄が取り除かれたのだ。図を参考にして頂きたい。



ここまで読めば狩野さんの指摘されている点も理解できたので、後は実際に狩野さんが提示されている方法を実現する手段を考えるのみだな。「縦画を伸ばして、はみ出した部分を元の縦画と重ね合わせるようなアフィン変換」が理解不能。とりあえずアフィン変換をもうちょっと勉強した方が良さそうだな。現在朝の5時。今夜はアフィン変換様に眠気を誘って頂く事にするか。