📄

SATySFi で \widehat を実装する

2020/12/09に公開

これは SATySFi Advent Calender 2020 の 9 日目の記事です. 8 日目は amutake さんによる「SATySFi で書かれた本などのまとめを作りました」でした. 10 日目は monaqa さんによる「【入門記事】SATySFi のコマンドの文法」です.

\widehat

今回は, SATySFi の数式コマンドとして \widehat を実装したいと思います.

TeX の \widehat と同じように,数式内で

${ \widehat{x}, \widehat{x^2}, \widehat{xy}, \widehat{xyz} }

の形で使えるようにします.見た目は次のようになります:

型は次のようになります.
\widehat: mathmath

使用するプリミティブ

今回使用するプリミティブを順に説明していきます.

inline-graphics

inline-graphics: lengthlengthlength → (pointgraphics list) → inline-boxes

グラフィックは graphics 型によって表されるのですが,実際にこれを inline-boxes として本文中に組み込むためには,まず横幅/高さ/深さを教えることで SATySFi に組版して文章中の位置を決めてもらい,そうして決定された左下の座標を基準として graphics を作る必要があります.これを実現するのが inline-graphics プリミティブです.

第 1, 2, 3 引数にそれぞれ横幅,高さ,深さを渡し,第 4 引数には「座標を受け取って graphics を返す関数」を渡します.

これが inline-boxes となって数式中に入るので, graphics list には \widehat 自体に加えて, \widehat を付ける対象となる数式も含める必要があります.

embed-math

embed-math : contextmathinline-boxes

\widehat を付ける対象となる数式を,まずこれで inline-boxes に変換します.すると, get-natural-metrics によって横幅/高さ/深さの情報が得られるようになり,また draw-text によって graphics に変換することができるようになります.

get-natural-metrics

get-natural-metrics: inline-boxeslength * length * length

inline-boxes の横幅/高さ/深さを得ます.これをもとに, \widehat の位置や大きさと, inline-graphics に渡す最終的な横幅/高さ/深さを決めます.

draw-text

draw-text: pointinline-boxesgraphics

左下の座標と inline-boxes を渡すと,そこに inline-boxes の内容を描いた結果の graphics を返します.

start-path, line-to, close-with-line

start-path: pointpre-path
line-to: pointpre-pathpre-path
close-with-line: pre-pathpath

graphics を描くためには,まず path を作ります. |> 演算子を用いてメソッドチェーンのように書くと例えば次のようになります:

% 左下の座標が (x, y) で一辺の長さが 1pt の正方形を表す path
start-path (x, y)
|> line-to (x, y +' 1pt)
|> line-to (x +' 1pt, y +' 1pt)
|> line-to (x +' 1pt, y)
|> close-with-line

fill

fill: colorpathgraphics

path の内側を color で塗った graphics を返します.

text-in-math

text-in-math: math-class → (contextinline-boxes) → math

完成した inline-boxes を,数式中に組み込みます. \widehat の付いた数式の math-classMathOrd で良いでしょう.

設計

\widehat は,真ん中の辺りで厚く,端の辺りで薄くなってほしいです.それぞれの厚さを hat-thickness-midhat-thickness-end とします.

\widehat 自体の高さ hat-height は,横幅に比例させます.

また, \widehat は,数式より少し高い場所に置かれるので,その間隔を hat-pos とします.数式の高さより hat-pos だけ上の位置から \widehat を書き始める感じです.

それぞれの具体的な値は次のようにしてみました.

変数
hat-thickness-mid .6pt
hat-thickness-end .2pt
hat-height 横幅の .2 倍
hat-pos 1.2pt

できたもの

最終的にこんな感じになりました:

let-math \widehat it =
    text-in-math
        MathOrd
        (fun ctx -> (
            let it = embed-math ctx ${#it} in
	    let (length, height, depth) = get-natural-metrics it in
	    let hat-thickness-mid = .6pt in
	    let hat-thickness-end = .2pt in
    	    let hat-height = length *' .2 in
	    let hat-pos = 1.2pt in
	    inline-graphics
                length
	        (height +' hat-pos +' hat-height +' hat-thickness-mid)
	        depth
	        (fun (x, y) -> [
	            draw-text (x, y) it;
		    fill
		        Color.black
		        (
		            start-path (x, y +' height +' hat-pos)
		            |> line-to (x, y +' height +' hat-pos +' hat-thickness-end)
		            |> line-to (x +' length *' 0.5, y +' height +' hat-pos +' hat-height +' hat-thickness-mid)
		            |> line-to (x +' length, y +' height +' hat-pos +' hat-thickness-end)
		            |> line-to (x +' length, y +' height +' hat-pos)
		            |> line-to (x +' length *' 0.5, y +' height +' hat-pos +' hat-height)
		            |> close-with-line
		        )
		])
        ))

良ければ使ってみてください!

Discussion