🔢

Typstにおける番号付け指南:基本から高度なカスタマイズまで

2024/01/09に公開

Typstとは

Typstとは、論文や書籍などを効率よく気持ちよく執筆するための新興オープンソース組版エンジンで、\LaTeXとかいう煉獄から脱するための、かなりポテンシャルのある希望の光であります(筆者調べ)。

基本的な紹介のまとめや基本文法に関してはsunnyさんの記事が詳しいです。
https://zenn.dev/yuhi_ut/articles/how2start-typst
https://zenn.dev/yuhi_ut/articles/how2write-typst1

番号付けの要件

番号付け(numbering)は組版において永遠の課題の一つです。主に見出し、定理、表、図などの要素において、自動番号付けをした上で、後で引用したり目次を作ったりするために用いられます。

例えば「第一章 吾輩は猫である」という見出しでは、「吾輩は猫である」という見出し文字列に番号が付けられており、「一」という部分が番号で、「第…章」がラベル名と呼ばれます。

特にややこしいのが、科学技術論文でミられるような複合的な見出しで、「1. 大見出し」「1.1.1 小見出し」のように、前の階層の番号も同時に付されることがあります。

Typstでの番号付け

基本的な番号付け

Typstでは、numberingという関数を利用することで、数字を特定な形に変換することができます。

#set text(font: "Noto Serif CJK JP") 

#numbering("1.", 1) \
#numbering("1.", 2) \
#numbering("1.", 3) \

#numbering("a)", 1) \
#numbering("a)", 2) \
#numbering("a)", 3) \

#numbering("(イ)", 1) \
#numbering("(イ)", 2) \
#numbering("(イ)", 3) \

#numbering("第一章", 1) \
#numbering("第一章", 2) \
#numbering("第一章", 3) \

以上のように、numbering()関数の第一引数に、番号の最初の文字(「1」「a」「イ」「一」)を含んだ文字列を入れ、その次に数字nを入れることで、番号列のn番目の文字が置き換えられていることを確認できました。

この番号付けを、実際の見出しなどに応用する際は、相応の関数のnumbering引数を以上の関数の第一引数と同じものにすれば良い。以下は、heading()enum()の例のみが示されていますが、equation()figure()page()display()footnote()なども同じ方法でできます。

#set text(font: "Noto Serif CJK JP") 

#set heading(numbering: "1.")

= 大見出し

== 中見出し

=== 小見出し

==== ミニ見出し

#v(1em)

#set enum(numbering: "1.")

+ 生物
  + 動物
    + 猫
    + 犬
  + 植物
    + 花
    + 草
+ 非生物
  + 石
  + 金属

複合的な番号付け

ところが、例えば異なる階層の番号に異なる番号文字をつけたい場合などでは、複数のものを設定する必要があります。

#numbering("1.", 1) \
#numbering("1.", 1, 1) \
#numbering("1.", 1, 1, 1) \

#numbering("1.a", 1) \
#numbering("1.a", 1, 1) \
#numbering("1.a", 1, 1, 1) \

#numbering("1.a.i", 1) \
#numbering("1.a.i", 1, 1) \
#numbering("1.a.i", 1, 1, 1) \


以上のように、複数の代表文字を設定することで、階層ごとに異なる表示に成功しました。注意すべきのは、もし与えられた階層の数が、設定されているものよりも多い場合は、設定された最後の文字種類が繰り返されます。

サポートされている番号文字種類一覧

種類 内部名 代表文字 数字例 備考
アラビア数字 Arabic "1" 1, 2, 3, 4, 5, …
アルファベット Letter "a" a, b, c, d, e, ...
ローマ数字 Roman "i" I, II, III, IV, V, ...
記号 Symbol "*" *, †, ‡, §, ¶, ...
ヘブライ数字 Hebrew "א" א, ב, ג, ד, ה, ...
漢数字 SimplifiedChinese "一" 一, 二, 三, 四, 五, ...
ひらがな(あいうえお) HiraganaAiueo "あ" あ, い, う, え, お, ... 実装済みアプデ待ち
ひらがな(いろは) HiraganaIroha "い" い, ろ, は, に, ほ, ...
カタカナ(アイウエオ) KatakanaAiueo "ア" ア, イ, ウ, エ, オ, ... 実装済みアプデ待ち
カタカナ(イロハ) KatakanaIroha "イ" イ, ロ, ハ, ニ, ホ, ...
韓国語の字母 KoreanJamo "ㄱ" ㄱ, ㄴ, ㄷ, ㄹ, ㅁ, ...
韓国語の音節 KoreanSyllable "가" 가, 나, 다, 라, 마, ...

現在サポートされているものの一覧は、numbering.rsに記載されています。

接頭文字・接尾文字について

数字以外の文字は、接頭文字や接尾文字となります。その中でも、最後の数字の後から最後までの文字は、接尾文字として階層に関係なく、結果の最後に常に追加されます。それ以外の文字はすべて接頭文字で、最初の接頭文字は、結果の最初に常に追加されます。他の接頭文字は、階層が連なる時に前の文字との間に挟まれます。

// 接頭文字なし、アラビア数字、接頭文字 "."、アラビア数字、接尾文字なし
#numbering("1.1", 1) \
#numbering("1.1", 1, 1) \

// 接頭文字なし、アラビア数字、接頭文字 "."、アラビア数字、接尾文字 "."
#numbering("1.1.", 1) \
#numbering("1.1.", 1, 1) \

// 接頭文字なし、アラビア数字、接頭文字 "."、小文字アルファベット、接頭文字 ")."、ローマ数字、接尾文字なし
#numbering("1.a).i", 1) \
#numbering("1.a).i", 1, 1) \
#numbering("1.a).i", 1, 1, 1) \

// 接頭文字なし、アラビア数字、接頭文字 "."、小文字アルファベット、接頭文字 ")."、ローマ数字、接尾文字 ")"
#numbering("1.a).i)", 1) \
#numbering("1.a).i)", 1, 1) \
#numbering("1.a).i)", 1, 1, 1) \

#set heading(numbering: "第1.a.i節")
= 都道府県
== 都
=== 東京都
== 道
=== 北海道
== 府
=== 大阪府
=== 京都府
== 県
=== 沖縄県
=== 奈良県
=== 和歌山県
…

#set enum(numbering: "1.a.i)")

+ 生物
  + 動物
    + 猫
    + 犬
  + 植物
    + 花
    + 草
+ 非生物
  + 石
  + 金属

高度なカスタマイズ

numbering()関数やnumbering引数は、前述のような文字列のみならず、関数も引き受けることができます。具体的には、匿名関数をそこに入れて、可変引数argsを収集し、その位置引数をarrayとして取り出し、その上で計算を行うことで、こういうこともできるわけです。

#set heading(numbering: (..args) => {
  args.pos().map(x => if x == 1 { "3" } else { "3." + "1415926535897932384626433".slice(0, x - 1) }).join(" | ")
})

= はじめに
= 先行研究
= 猫との戯れ方
= 犬との戯れ方
= お休みなさい
= まとめ

同じ符號の繰り返し(, ††, †††

脚注などで、ダガー(†)を複数回繰り返すスタイルが存在しています。その場合は、関数によって容易に実現できます。Typst言語には、Pythonと類似した文字列に対する算数操作が存在しています。例えば文字列をくっつけたい時は、"a" + "b" + "c"とすれば"abc"になり、文字列が連結されます。また、正の整数との乗算も可能で、"a" * 3とすれば"aaa"になります。これを利用すれば、番号の数と繰り返したい符號の文字を掛け合わせることで実現できます。

#pagebreak()
#set page(width: 10cm, height: 8cm)
#set text(font: "Noto Serif CJK JP")

#set footnote(numbering: (..args) => {
  args.pos().at(0) * "†" // 脚注は一回層のみなので、取り出してリピート
})

#align(center)[組版 英二#footnote[組版大学組版学部] 植字 一郎#footnote[植字大学] 編集 三郎#footnote[編集大学 組版研究科]]

#lorem(10)

サポートされていない文字類のカスタマイズ

サポートされていない文字でも、自分で関数マッピングを作れば実現できます。以下はインド数字の例です。

#set heading(numbering: (..args) => {
  let nums = args.pos() // 引数の位置引数を`array`として取得\
  nums.map((num) => {
    str(num).clusters().map(
      char => ("०", "१", "२", "३", "४", "५", "६", "७", "८", "९").at(int(num))).join("")
  }).join(", ")
})

= 見出し
== 見出し
=== 見出し
==== 見出し
==== 見出し
==== 見出し
=== 見出し
== 見出し
= 見出し
= 見出し
== 見出し

最高階層のみ接尾辞を追加する方法(1.1.1

前述の通り、接尾辞はすべての階層に繰り返されます。つまり、numbering: "1.1"と設定した場合は、#numbering("1.1", 1)及び#numbering("1.1", 1)がそれぞれ11.1になり、また、numbering: "1.1."と設定した場合、それぞれ1.1.1.になりますが、最高階層だけ点を付けて、次の階層に点を付けたくない場合、即ち1.1.1の場合は、どうすればよろしいのでしょうか。

※本来ならば、showルールを用いて、#show heading.where(level: 1): set heading(numbering: "1.")のように選択することでできそうなのですが、残念ながら現時点にはそれができず(#1688#2459参照)、代替的に少々複雑になりますが、関数を用いる方法しかありません。

以下のように、heading()numbering引数に匿名関数を入れることで、最高階層のみ"1."にし、それ以外の場合は"1.1"にすることで実現できました。より短く書くことも可能ですが、読者さんへの課題ということで……

#set heading(numbering: (..args) => {
  let nums = args.pos() // 引数の位置引数を`array`として取得
  if nums.len() == 1 { // 階層総数が1しかない、即ち最高階層
    return numbering("1.", ..nums)
  } else {
    return numbering("1.1", ..nums)
  }
})

= 序論
== 研究の背景
== 研究の目的
= 理論的背景
== 関連研究のレビュー
== 理論の枠組み
= 方法論
== 研究デザイン
== データ収集方法
== データ分析方法
= 結果
== 実験結果
== 統計的分析
= 考察
== 結果の解釈
== 研究の限界
= 結論
== 研究の要約
== 今後の研究の方向性

見出しで現在階層しか表示したくない場合


#set heading(numbering: (..args) => {
  let nums = args.pos() // 引数の位置引数を`array`として取得\
  let level = nums.len() - 1
  let label = ("部", "章", "節", "項", "目").at(level)
  [#h(level * 2em)第#nums.at(level)#label]
})

= この世で一番えらい研究
== 序論
=== 研究の背景
=== 研究の目的
==== 主要な課題
==== 仮説の設定
== 方法論
=== 研究デザイン
=== データ収集方法
==== 調査手法
==== データソース
=== データ分析方法
==== 分析手順
==== 分析ツール
== 結果
== 考察
=== 結果の解釈
=== 研究の限界
==== 方法論的制約
==== 結果の一般化の問題点
== 結論
=== 研究の要約
==== 主要な結論
==== 研究の寄与
=== 今後の研究の方向性
==== 提案された研究トピック
==== 未解決の問題
= 何の成果も得られませんでした!!

【ゆる募】他のカスタマイズ

他にカスタマイズしたものがあればぜひコメント欄へ

参考文献

コミュニティ

「くみはんクラブ」というコミュニティを創設しました。そこで、Typstに関する話もされますので、何か質問や不明点があれば、ぜひいらしてください。

https://discord.gg/dHRaAsBeRY

https://x.com/mkpoli/status/1746874400618815813?s=20

Discussion