SATySFiでテキストの左側に線を引きたくなったら

自分でライブラリを書くといろんな知見が得られて面白いので共有したいと思います．

## テキストの左側に線を引きたい

> よくこういうのがありますが，

こんな感じでテキストの左側に線を引きたくなりがちです．
思想としては，

1. block-textを受け取って，contextより行の幅が小さめのinline-boxesをつくる
2. inline-boxesのサイズを測って左側に置くラインのサイズを決定する
3. 1と2を配置する

がありました．

**追記** 
[@monaqa さんのコメント](https://qiita.com/estis75/items/3d2a2506d2f7760dc4e4#comment-d66a46d7450ad2f47507) と[@bd_gfngfn さんのTweet](https://twitter.com/bd_gfngfn/status/1463787958230663177?s=20)を参考に実装し直しました．最新を追いたい場合はこのページの[後半](#後半)をご参考ください．

### 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](https://github.com/estis75/travel-note/blob/master/itinerary.satyh)
![Screenshot from 2021-11-25 17-03-58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494471/919661aa-5945-4b63-cfc9-b7c18d5c2a27.png)

## 後半

さて，上の実装ではページいっぱいまでいくとページ分割ができずに壊れるという問題を @monaqa さんにご指摘いただきました．
実際，以下のように破壊されてとても悲しい気持ちになります．
![Screenshot from 2021-11-27 17-17-43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494471/6d72df25-8196-8900-a163-40f170229db7.png)

それを避けるためにSATySFi v0.6.0では
block-frame-breakable: context -> (length * length * length * length) -> deco-set -> (context -> block-boxes)
というapiが用意されているようです．
こちらのapiは領域のpaddingやその覆い方を指定するとそれに沿っていい感じに配置し，また，ページをまたぐ場合も同様に配置してくれるものすごくいい子のようですね．

先程の例で実際使ってみたものが以下の図になります．
![Screenshot from 2021-11-27 17-37-37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494471/099d9336-746a-28bd-3a69-dc6d236130e9.png)

また，ページ分割部の図も以下のようになります．
![Screenshot from 2021-11-27 17-37-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494471/245c019b-0cde-b958-07b2-8b11fa3d320e.png)

部分的な実装は以下のように，全体の実装は[github](https://github.com/estis75/travel-note/blob/master/itinerary.satyh)に上げました．参考になれば幸いです．

```
  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](https://twitter.com/bd_gfngfn)さん，@monaqa さん
非常に参考になるご指摘ありがとうございます．
おかげで割とすぐに出会った分割できない問題を高速で解決することができました．

## おねがい

使い方が正しくなかったら怒ってください．
よろしくおねがいします．