自分でライブラリを書くといろんな知見が得られて面白いので共有したいと思います.
テキストの左側に線を引きたい
よくこういうのがありますが,
こんな感じでテキストの左側に線を引きたくなりがちです.
思想としては,
- block-textを受け取って,contextより行の幅が小さめのinline-boxesをつくる
- inline-boxesのサイズを測って左側に置くラインのサイズを決定する
- 1と2を配置する
がありました.
追記
@monaqa さんのコメント と@bd_gfngfn さんのTweetを参考に実装し直しました.最新を追いたい場合はこのページの後半をご参考ください.
1. block-textを受け取って,contextより行の幅が小さめのinline-boxesをつくる
右側におくboxesのサイズを決定する方法として,
embed-block-top / embed-block-bottom
というapiが準備されています.
また,inline-boxesのサイズを取得する方法として,get-natural-metricsというものも準備されています.
embed-block-top / embed-block-bottom のどちらも型としては
context -> length -> (context -> block-boxes) -> inline-boxes
なのですが,前者で作成した場合,get-natural-metricsで得られる高さは一行分しかありません.
一方,後者はそのブロックテキスト全体の高さが得られるので嬉しい気分になります.
なので,実装としてはこのような感じになります.
informationはblock-text型の変数です.
let ib-info = embed-block-bottom ctx text-width (fun ctx -> read-block ctx information)
2. inline-boxesのサイズを測って左側に置くラインのサイズを決定する
1で書いたとおり,get-natural-metricsが使えます.
ラインを引くのはめんどくさいですが,がんばります.
let (_, height, _) = get-natural-metrics ib-info
let line = stroke 0.5pt Color.black (Gr.line ((line-width *' 0.2), (0pt)) ((line-width *' 0.2), (height -' 2pt))) in
let line-left = inline-graphics line-width height 0pt (fun pt -> ( [line;] |> List.map (fun el -> shift-graphics pt el)))
3. 1と2を配置する
がんばります.
全体の実装は以下のような感じになりました.
let bb-info = % 乗車するやつの情報とか
let size = 11pt in
let ctx =
ctx |> set-font-size size
|> set-leading (size *' 1.2)
|> set-paragraph-margin (size *' 0.2) (size *' 0.2)
in
let ib-info = embed-block-bottom ctx text-width (fun ctx -> read-block ctx information) in
let (_, height, _) = get-natural-metrics ib-info in
let line = stroke 0.5pt Color.black (Gr.line ((line-width *' 0.2), (0pt)) ((line-width *' 0.2), (height -' 2pt))) in
let line-left = inline-graphics line-width height 0pt (fun pt -> ( [line;] |> List.map (fun el -> shift-graphics pt el))) in
line-break true true ctx (line-left ++ ib-info ++ inline-fil)
これのコードを含むように作ってみた例:
library: github
後半
さて,上の実装ではページいっぱいまでいくとページ分割ができずに壊れるという問題を @monaqa さんにご指摘いただきました.
実際,以下のように破壊されてとても悲しい気持ちになります.
それを避けるためにSATySFi v0.6.0では
block-frame-breakable: context -> (length * length * length * length) -> deco-set -> (context -> block-boxes)
というapiが用意されているようです.
こちらのapiは領域のpaddingやその覆い方を指定するとそれに沿っていい感じに配置し,また,ページをまたぐ場合も同様に配置してくれるものすごくいい子のようですね.
部分的な実装は以下のように,全体の実装はgithubに上げました.参考になれば幸いです.
let bb-info = % 乗車するやつの情報とか
let size = 12pt in
let ctx =
ctx |> set-font-size size
|> set-leading (size *' 1.2)
|> set-paragraph-margin (size *' 0.2) (size *' 0.2)
in
let linef = stroke 0.5pt Color.black in
let line (x, y) w h d = [linef (Gr.line (x, y) (x, y+'h));] in
let deco _ _ _ _ = [] in
block-frame-breakable ctx (5pt, 0pt, 0pt, 0pt) HDecoSet.empty (
( fun ctx ->
block-frame-breakable ctx (10pt, 0pt, 0pt, 10pt) (line, line, line, line)
(fun ctx -> block-skip 0pt +++ read-block ctx information)
)
)
謝辞
@bd_gfngfnさん,@monaqa さん
非常に参考になるご指摘ありがとうございます.
おかげで割とすぐに出会った分割できない問題を高速で解決することができました.
おねがい
使い方が正しくなかったら怒ってください.
よろしくおねがいします.