⛄️

satysfi-enumitem v3.0 で素敵な箇条書きをいっぱい作る話

2021/12/24に公開

はじめに

2019年の SATySFi advent calendar で以下のような記事を書きました。

https://qiita.com/monaqa/items/8bd74248c1cfbc62bd64

あれから2年、 enumitem パッケージのバージョンが v3.0.1 まで上がり、使い方もかなり大きく変わりました。本記事では、生まれ変わった enumitem パッケージで素敵な箇条書きをたくさん作ってみたいと思います。

Enumitem パッケージとは

Enumitem パッケージは、組版処理システム SATySFi において箇条書きを実現するためのパッケージです。

https://github.com/monaqa/satysfi-enumitem

標準にも箇条書きを組むための itemize パッケージがありますが、enumitem パッケージはより自由度の高い箇条書きを提供するために開発されています。

インストール

Satyrographos を使うとスムーズにインストールできます。

opam install satysfi-enumitem
satyrographos install

enumitem パッケージの v3.0.1 がインストールされていることを確認してください。

v2.x.y からの変更点

enumitem v3.0.1 は、それまでのバージョンと比較して以下の特徴が加わりました。

  • 任意の箇条書きで、項目の途中での改ページが可能になった。
  • ブロックベースの記法(後述)を用いることで、複数段落からなる項目などの複雑な箇条書きにも自然に対応できるようになった。

ドキュメント

本記事では説明できなかった内容がまだまだあります。詳細はドキュメントを参照してください。

https://github.com/monaqa/satysfi-enumitem/blob/master/doc/enumitem.pdf

基本的な使い方

パッケージのインポート

Satyrographos を用いてインストールすれば、 SATySFi のヘッダ部分に以下の文言を加えることで enumitem パッケージのコマンドが使えるようになります。

(header)
@require: enumitem/enumitem

Enumitem パッケージを用いる際は、プリアンブルに以下の記述を加えることをお勧めします[1]

(preamble)
open EnumitemAlias

+listing+enumerate

標準の itemize パッケージにも用意されている +listing は標準で提供されています。

+p{
  世の中には3種類の人間がいる。
}
+listing{
  * 数を数えられる人間
  * 数を数えられない人間
}


シンプルな番号無し箇条書き

+enumerate コマンドも用いることができます。こちらはデフォルトでネストをサポートしています。

+enumerate{
  * トップページ
  * The SATySFibook Web公開版 第1版
  * Wiki
    ** インストール
    ** デモ文書のコンパイル
  % (中略)
}


シンプルな番号付き箇条書き

ラベルの変更

SATySFi v3.0.1 では +genlisting が廃止され、代わりに +listing のオプション引数にてラベルを指定できるようになりました。

+listing?:(white-bullet){
  * あれ?
  * ちゃんと白丸になってる!
}


ラベルを白丸に変更した箇条書き

+enumerate コマンドでも同様にオプション引数でラベルを指定できます。

+enumerate?:(paren-roman){
  * あれ?
  * ちゃんとローマ数字になってる!
}


ラベルをローマ数字に変更した箇条書き

デフォルトで提供されているラベルは以下のとおりです。

ラベル名 ラベルの効果
raw-arabic アラビア数字
dot-arabic アラビア数字 + ピリオド
paren-arabic アラビア数字 + 丸括弧
bracket-arabic アラビア数字 + 角括弧
raw-roman ローマ数字(小文字)
dot-roman ローマ数字(小文字) + ピリオド
paren-roman ローマ数字(小文字) + 丸括弧
bracket-roman ローマ数字(小文字) + 角括弧
raw-Roman ローマ数字(大文字)
dot-Roman ローマ数字(大文字) + ピリオド
paren-Roman ローマ数字(大文字) + 丸括弧
bracket-Roman ローマ数字(大文字) + 角括弧
raw-alph アルファベット(小文字)
dot-alph アルファベット(小文字) + ピリオド
paren-alph アルファベット(小文字) + 丸括弧
bracket-alph アルファベット(小文字) + 角括弧
raw-Alph アルファベット(大文字)
dot-Alph アルファベット(大文字) + ピリオド
paren-Alph アルファベット(大文字) + 丸括弧
bracket-Alph アルファベット(大文字) + 角括弧
dot-arabic-rec アラビア数字(階層構造付き)
bullet 黒丸
white-bullet 白丸
listing-default-label ネストの深さに応じて変化(番号なし)
enumerate-default-label ネストの深さに応じて変化(番号つき)

カスタムスタイルの箇条書き

text-label 関数を用いると任意のインラインテキストをラベルにすることができます。

+listing?:(text-label {\cjk{☃}\ }){
  * トップページ
  * The SATySFibook Web公開版 第1版
  * Wiki
    ** インストール
    ** デモ文書のコンパイル
  % (中略)
}


ラベルを ☃ にした箇条書き

index-label を用いると箇条書きのインデックスに応じてラベルを変更することができます。

(preamble)
let label-toi =
  let labelf idx =
    let it-idx = idx |> arabic |> embed-string in
    {\textbf{問題#it-idx;.}\ }
  in
  index-label labelf

少々ややこしくなってきましたが、 index-label 関数の引数として int -> inline-text 型の関数 である labelf を渡すことでラベルを作成しています。「インデックスが int 型で与えられたときにラベルとするインラインテキスト」を labelf に決めてもらっているのです。

+enumerate?:(label-toi){
  * 象を冷蔵庫に入れるにはどうすれば良い?
  * では、キリンを冷蔵庫に入れるには?
  * 百獣の王ライオンが会議を招集した。欠席したのは?
  * 大変!二人の探検家がワニの住む沼を渡ろうとしているわ!
}


ラベルを「問題」にした箇条書き

もっと複雑なラベルを提供する手段も提供されています。今日は折角のクリスマスイブ、SATySFi のグラフィックス機能を駆使して雪だるまの箇条書きをもっと素敵にしてみましょう。実際に動かすときは colorgr パッケージを @require してください。

(preamble)
let label-8-xmas =
  let labelf ctx idx =
    let xmas-color = match idx mod 3 with
      | 0 -> Color.rgb 0.1 0.5 0.2
      | 1 -> Color.rgb 0.7 0.2 0.2
      | _ -> Color.black
    in
    let fsize = get-font-size ctx in
    let ib-8 =
      let ctx-xmas = ctx |> set-text-color xmas-color in
      read-inline ctx-xmas {\cjk{☃}}
    in
    let (wid-8, ht-8, dp-8) = get-natural-metrics ib-8 in
    let ib-label =
      inline-graphics wid-8 ht-8 dp-8 (fun (x, y) -> [
        draw-text (x, y) ib-8
          |> Gr.rotate-graphics (x +' wid-8 *' 0.5, y +' ht-8 *' 0.5)
              (30. *. (float (1 - idx)))
      ])
    in
    ib-label ++ (inline-skip 10pt)
  in
  EnumitemFormat.new-ordered labelf

今までと同様に +enumerate?:(label-8-xmas){ ... } のようにラベルを指定すれば、以下のような箇条書きが得られます。


ラベルをクリスマス仕様にした箇条書き

ブロックベースの記法

SATySFi には箇条書き専用の構文が用意されています。今までの例で何度も出てきた、 *** を bullet として用いて箇条書きの要素を記述する方法です。

+enumerate{
  * トップページ
  * The SATySFibook Web公開版 第1版
  * Wiki
    ** インストール
    ** デモ文書のコンパイル
}

この記法は単純な箇条書きを簡潔に記述するのに便利ですが、複雑な箇条書きを書くには向いていません。たとえば

  • 1つの項目の中に複数の段落を入れる
  • 定義リストのように、ラベルと項目のテキストをペアで指定する

といった場合、糖衣構文を用いた方法でこれらを自然な構文で実現するのは大変です。

そこで、 enumitem v3.0.1 では新たにブロックベースの記法でも箇条書きが書けるようにしました。ブロックベースの記法を用いると、上と全く等価な箇条書きが以下のように書けます。

+itemize(enumerate-default-label){
  +item{トップページ}<>
  +item{The SATySFibook Web公開版 第1版}<>
  +item{Wiki}<
    +item{インストール}<>
    +item{デモ文書のコンパイル}<>
  >
}

ブロックベースの記法では +item のネストで階層構造を表現し、全体を +itemize で囲むことで箇条書きの範囲を明示します。箇条書きの独自記法を用いないぶん記法はいくらか冗長になるものの、ブロックベースの記法にはそれを補うだけの利点があります。

  • +item の子要素の中には任意のブロックテキストを入れられるため、複数段落からなる項目を自然に実現できる
  • 各項目が独立しているため、項目ごとにラベルの体裁を決めることができる
  • 複雑なブロックテキストを独自コマンドとしてまとめることで、ユーザが自由に箇条書きの記法を拡張できる

+item コマンドはオプション引数をとり、自身のラベルを変更することができます。項目ごとにラベルのスタイルを変えることで、たとえばこんなこともできます。

(preamble)
let-block +genin it = '< +item?:(text-label{下人「}){#it;」}<> >
let-block +rouba it = '< +item?:(text-label{老婆「}){#it;」}<> >
+itemize(nofmt)<
  +genin{何をしていた。云え。云わぬと、これだぞよ。}
  +genin{
    己は検非違使の庁の役人などではない。
    今し方この門の下を通りかかった旅の者だ。
    だからお前に縄をかけて、どうしようと云うような事はない。
    ただ、今時分この門の上で、何をして居たのだか、それを己に話しさえすればいいのだ。
  }
  +rouba{
    この髪を抜いてな、この髪を抜いてな、鬘にしようと思うたのじゃ。
  }
>


項目ごとにラベルの種類を指定した箇条書きの例

ブロックベースの記法を用いれば、定義リストを記述することもできます。定義リストのための +description コマンド及び +ditem コマンドがデフォルトで定義されています。

+description<
  +ditem{\emph{用語1}}{ここに用語1の説明が入る。}<>
  +ditem{\emph{用語2}}{
    ここに用語2の説明が入る。用語2の説明は少し長い。
    用語2の説明は少し長い。用語2の説明は少し長い。用語2の説明は少し長い。
  }<>
  +ditem{\emph{長めの用語3}}{
    ここに用語3の説明が入る。用語3の説明は少し長い。
    用語3の説明は少し長い。用語3の説明は少し長い。用語3の説明は少し長い。
  }<
    +pn{
    複数行にまたがる説明を入れることもできる。
    }
  >
>


定義リストの例

+description+ditem 自体は用語名の書体や色などを変更することがありません。用語名を強調すべきか、強調するならどのようなスタイルを用いるかは文書により異なるからです。したがって、上の例では \emph コマンドを用いて用語を強調しています。それが面倒な場合は、好みに合わせたコマンドを定義するのが良いでしょう。

(preamble)
% 単一段落からなる定義リストであれば、以下の1行で十分
let-block +dd dt dd = '< +ditem{\emph{#dt;}}{#dd;}<> >

% 複数段落のテキストからなる定義リストはたとえば以下のように定義できる
let-block +dds dt ddlst =
  match ddlst with
  | [] -> '< +ditem{\emph{#dt;}}{}<> >
  | dd :: rest ->
      let paragraphs =
        rest |> List.fold-left (fun bt assoc -> '<#bt;+pn{#assoc;}>) '<>
      in
      '< +ditem{\emph{#dt;}}{#dd;}<#paragraphs;> >
  +description<
    +dd{用語1}{ここに用語1の説明が入る。}
    +dd{用語2}{
      ここに用語2の説明が入る。用語2の説明は少し長い。
      用語2の説明は少し長い。用語2の説明は少し長い。用語2の説明は少し長い。
    }
    +dds{長めの用語3}{
      | ここに用語3の説明が入る。用語3の説明は少し長い。
        用語3の説明は少し長い。用語3の説明は少し長い。用語3の説明は少し長い。
      | 複数行にまたがる説明を入れることもできる。
      |}
  >

箇条書きの構造自体がプログラマブルになっていますね。

おわりに

enumitem は私がはじめて作成した SATySFi パッケージでした。当時はパッケージ作成のノウハウがさほど溜まっていなかったこともあり、作成後に「これはこう設計したほうが良かったのでは」という箇所が多数出てきていました。そんなパッケージも今や v3.0.1 となり、自分でもわりと納得のいく機能を提供できているのではないかと思います。

皆さんも是非使ってみてください!

脚注
  1. raw-arabiclisting-default-label といった関数が prefix 無しで使えるようになります。 ↩︎

Discussion