Typst 便利パッケージと使用例の紹介

## はじめに
Typstは、最近にわかに流行の兆しを見せている組版ソフトウェアです。TypstではLaTeXでamsmathやamssymbなどのパッケージで実現されていた機能の大部分がデフォルトで実装されており、簡単に文書を書くことができます。

さらに、標準にはない機能を補う優れたパッケージが数多く公開されており、容易に使うことができます。この記事では、Typstの便利なパッケージを紹介します。

- [Physica](#Physica)
- [CeTZ](#CeTZ)
- [Fletcher](#Fletcher)
- [unify](#unify)

::: note info
Typstのインストール方法や基本的な文法については解説しません。他のネット記事や[公式ドキュメント](https://typst.app/docs/)を参照してください。
:::

## パッケージの導入方法
[公式のパッケージリポジトリ](https://github.com/typst/packages)に登録されているパッケージについては、ファイル中に
```typst
#import "@preview/{パッケージ名}:{バージョン}"
```
と書くことで自動的にダウンロードされ、使えるようになります。上記の書き方ではパッケージ名の名前空間に変数などが展開されますが、
```typst
#import "@preview/{パッケージ名}:{バージョン}: *"
```
と書くとデフォルトの名前空間に展開されます[^1]。公式のパッケージリポジトリに登録されているパッケージについては、Typstの公式サイトに[簡単な説明つきの一覧](https://typst.app/docs/packages/)があります。

[^1]: `import`の詳細については公式ドキュメントの記載を参照してください: <https://typst.app/docs/reference/scripting/#modules>

公式のパッケージリポジトリに登録されていないパッケージについては、これほど簡単に導入することはできません。そのようなパッケージの導入方法については個々のパッケージのReadmeを参照してください。以下で紹介するパッケージは全て公式のパッケージリポジトリに登録されています。

## [Physica](https://github.com/Leedehai/typst-physics)
Physicaは物理でよく使われる多種多様なシンボルを追加するパッケージです[^2]。

```typst
#import "@preview/physica:0.9.5": *
- 微分
$
dv(f,x),quad dv(f,x,2),quad dv(f,x,s:\/),quad \
dd(f),quad dd(x,y,z),\
pdv(f, x),quad pdv(f,x,y),quad pdv(,x),quad pdv(,x,y,z,[2,1,3])
$
- ブラケット
$
ket(psi),quad bra(phi),quad braket(psi,phi),quad ketbra(psi,phi),quad mel(psi,A,phi)
$
- テンソル
$
tensor(Gamma,+mu,-nu,-rho),quad tensor(R,+mu,-nu,-rho,-sigma),quad tensor(A,+mu,-nu,-rho,+sigma)
$
- 原子
$
isotope("U",a:238,z:92) --> isotope("Th",a:234,z:90)+isotope("He",a:4,z:2)
$
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603264/4d262ccc-2ef8-e37c-ca71-5ad2b7917548.png)

Physicaにはこの他にもたくさんの便利な関数があります。

### ディラック定数についての注意
ディラック定数を表すものとしてTypst本体には`planck.reduce`がありますが、デフォルトでは横棒が斜めになっています。これはデフォルトの数式フォントでディラック定数を表す文字 ℏ ([U+210F](https://www.compart.com/en/unicode/U+210F)) の横棒が斜めになっているからです。

Physicaパッケージの`hbar`は横棒がまっすぐです。これはプランク定数を表す文字 ℎ ([U+210E](https://www.compart.com/en/unicode/U+210E)) に打ち消し線(`strike`)を加えて[実装されている](https://github.com/Leedehai/typst-physics/blob/8c64cec8b8ef62c94418b99bcfa90e07c65b1f74/physica.typ#L605-L623)ためです。このため打ち消し線の色を変えると`hbar`の**横棒の色が変わってしまいます**。

これらの問題は、ℏ の横棒がまっすぐであるような代替字形を含むフォントを使うことで解決することができます。例えば次のように`hbar`を定義することでまっすぐな横棒を持つディラック定数が得られます。
```typst
#let hbar = (sym.wj, text(font: "Stix Two Math", stylistic-set: 3)[#math.planck.reduce], sym.wj).join()
```
`sym.wj`を使っているのは、`text`だけだと前後のスペースがおかしくなるからです。

3種類の方法を比較します。
```typst
#import "@preview/physica:0.9.2" as physica

#let hbar = (sym.wj, text(font: "Stix Two Math", stylistic-set: 3)[#math.planck.reduce], sym.wj).join()

$
"planck.reduce: " &planck.reduce,quad E=planck.reduce omega,quad i planck.reduce c\
"physica.hbar: " &physica.hbar,quad E=physica.hbar omega,quad i physica.hbar c\
"hbar: " &hbar,quad E=hbar omega,quad i hbar c
$
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603264/e069501b-cd7f-873c-a82f-622bd4d2258d.png)

Physicaの`hbar`と代替字形で定義した`hbar`では横棒がまっすぐになっていることが分かります。代替字形で定義した`hbar`には`strike`の色などに関する問題がないので、こちらを使った方が良いと思います。

[^2]: LaTeXでいうphysicsパッケージに相当するものですが、physicsパッケージが抱える数多くの問題はPhysicaにはないと思います。

## [CeTZ](https://github.com/johannes-wolf/cetz)
CeTZはTeXにおけるTikZに相当する図を描画するパッケージです。Typst本体でも基本的な図形の描画を行うことができますが([公式ドキュメント](https://typst.app/docs/reference/visualize/))、CeTZを使うとより高度な描画ができます。詳しい使い方はマニュアルを参照していただくとして、描画できる図の例をお見せします。

::: note warn
CeTZはバージョン0.2.0で大きな変更がありました。古いバージョンではこのコードは動きません。
:::

```typst
#import "@preview/cetz:0.3.4"

#let linspace(start, end, n)=range(0,n).map(x => x / n * (end - start) + start)

#cetz.canvas({
    import cetz.draw: *
    line((0,0), (5,0), mark: (end: "stealth", fill: black))
    content((), block(inset: 0.2em)[$abs(bold(p))$], anchor: "west")
    line((0,-5), (0,5), mark: (end: "stealth", fill: black))
    content((), block(inset: 0.2em)[$p^0$], anchor: "east")
    let pa = linspace(0, 4, 100).map(x => (x, calc.sqrt(9+x*x)))
    line(..pa, stroke: red)
    let pb = linspace(0, 4, 100).map(x => (x, -calc.sqrt(9+x*x)))
    line(..pb, stroke: blue)
    line((0,0), (5,5), stroke: red)
    line((0,0), (5,-5), stroke: blue)
    let pe = linspace(-4, 4, 100).map(x => (calc.sqrt(9+x*x), x))
    line(..pe, stroke: olive)
    let draw-point(pos, text, color, anchor) = {
        circle(pos, radius: 0.1, fill: color, stroke: none)
        content((), block(inset: 0.3em)[#text], anchor: anchor)
    }
    draw-point((0, 3),  "(a)", red, "east")
    draw-point((0, -3), "(b)", blue, "east")
    draw-point((1, 1),  "(c)", red, "south-east")
    draw-point((1, -1), "(d)", blue, "north-east")
    draw-point((3, 0),  "(e)", olive, "north-east")
    draw-point((0, 0),  "(f)", olive, "east")
})
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603264/c305611b-eb70-d604-1bcc-a4d9f26345d8.png)

Typstでは`let`を使って簡単に関数を定義することができるので、描画中の共通する操作を関数にまとめることができ簡潔にコードが書けます。この例でも`draw-point`というラベル付きの点を描画する関数を定義して使っています。

## [Fletcher](https://github.com/Jollywatt/typst-fletcher)
Fletcherは先に紹介したCeTZを使って作られたパッケージで、矢印を使った図を簡単に描くことができます。こちらも詳しい使い方はマニュアルを参照してもらうとして、例をお見せします。

```typst
#import "@preview/fletcher:0.5.7": diagram, node, edge

#diagram($
    T(i) edge("rd", mu_i, ->, #right) edge("rr", T(f), ->) & & T(j) edge("ld", mu_j, ->, #left)\
    & C
$)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603264/d4f64b82-0487-4707-baf8-7ee0600c1e7f.png)

ファインマンダイアグラムも描けます。
```typst
#import "@preview/fletcher:0.5.7": diagram, node, edge

#diagram($
  e^+ edge("rd", "-<|-") & & & edge("ld", "-|>-") e^+ \
  & edge("wave") \
  e^- edge("ru", "-|>-") & & & edge("lu", "-<|-") e^-
$)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603264/9355c7d3-623b-4124-af89-ebd407bd1c04.png)

次のような非常に複雑な図も描くことができます。
<details><summary>プログラムは長いので折り畳み</summary>

```typst
#import "@preview/fletcher:0.5.7" as fletcher: diagram, node, edge
#import "@preview/cetz:0.3.4": draw

#let kamaboko(node, extrude) = {
  let (w, h) = node.size.map(i => i / 2 + extrude)
  draw.arc-through((-w, -h), (0, w - h), (+w, -h), mode: "CLOSE")
}
#let kamaboko2(node, extrude) = {
  let (w, h) = node.size.map(i => i / 2 + extrude)
  draw.merge-path({
    draw.arc-through((-w, -w), (0, 0), (+w, -w))
    draw.line((), (+w, -w + h * 2), (-w, -w + h * 2), (-w, -w))
  })
}
$
  #block(diagram(
    spacing: (1em, 2em),
    node-stroke: black,
    node((0pt, 0pt), enclose: ((0, -1), (1, 1)), width: 11em, inset: 1em, fill: gray.lighten(50%), corner-radius: 1em, snap: false),
    node(enclose: ((0, -1), (0, 1)), width: 4em, inset: 0pt, stroke: (dash: "dashed"), fill: white, corner-radius: 1em),
    node((0, -1), name: <i1>),
    node((0,  1), name: <x1>),
    node((0, 0), width: 3em, height: 1em, shape: kamaboko, fill: red.saturate(100%), name: <k1>),
    edge(<k1>, <x1>, $X$, "-|>-", left),
    edge(<i1>, <k1>, $i$, "-|>-", left),
    node(enclose: ((1, -1), (1, 1)), width: 4em, inset: 0pt, stroke: (dash: "dashed"), fill: white, corner-radius: 1em),
    node((1, 0), width: 3em, height: 2.5em, shape: kamaboko2, fill: red.saturate(100%), name: <k2>),
    node((1, -1), name: <i2>),
    node((1,  1), name: <x2>),
    edge(<k2>, <x2>, $i$, "-|>-", left),
    edge(<i2>, <k2>, $X$, "-|>-", left),
  ))
  quad=quad sum_(alpha in A)quad
  #block(diagram(
    spacing: (1em, 2em),
    node-stroke: black,
    node((0pt, 0pt), enclose: ((0, -1), (1, 1)), width: 11em, inset: 1em, fill: gray.lighten(50%), corner-radius: 1em, snap: false),
    node(enclose: ((0, -1), (0, 1)), width: 4em, inset: 0pt, stroke: (dash: "dashed"), fill: white, corner-radius: 1em),
    node((0, -1), name: <i1>),
    node((0,  1), name: <x1>),
    node((0, 0), $p_alpha$, shape: rect, name: <k1>),
    edge(<k1>, <x1>, $X$, "-|>-", left),
    edge(<i1>, <k1>, $i$, "-|>-", left),
    node(enclose: ((1, -1), (1, 1)), width: 4em, inset: 0pt, stroke: (dash: "dashed"), fill: white, corner-radius: 1em),
    node((1, 0), $q_alpha$, shape: rect, name: <k2>),
    node((1, -1), name: <i2>),
    node((1,  1), name: <x2>),
    edge(<k2>, <x2>, $i$, "-|>-", left),
    edge(<i2>, <k2>, $X$, "-|>-", left),
  ))
$
```
</details>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603264/d103c2d3-5868-4683-bd30-50f692b0547a.png)

## [unify](https://github.com/ChHecker/unify/tree/main)
unifyは単位を扱うことができるパッケージです。TeXにおけるsiunitxに相当します。
```typst
#import "@preview/unify:0.7.1": *

- 数値: #num("1.23e8"), #num("e4")
- 数値の別表記: #num("1.23e8", multiplier: "times")
- 単位: #unit("keV"), #unit("m/s"), #unit("J/K/m^3")
- 単位の別表記: #unit("m/s", per: "\\/")
  - 単位の中に括弧は使えない。
- 単位付きの数値: #qty("511", "keV"), #qty("1.23e8", "m/s")
- 誤差付き: #qty("511+-3", "keV"), #qty("1.23+-0.01e8", "m/s")
  - 括弧は消せない。
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/603264/caf32249-9bb0-457c-b355-7d021d5b7aab.png)

次のような関数があります。
|関数|機能|
|---|---|
|`num`|数値|
|`unit`|単位|
|`qty`|単位付きの数値|

詳しい使い方はマニュアルを参照してください。

## その他
これまで紹介したパッケージの他にもTypstには数多くのパッケージが存在します。いくつかのパッケージをまとめて紹介します。
- [Algo](https://github.com/platformer/typst-algorithms): 擬似コードが書ける
- [codelst](https://github.com/jneug/typst-codelst): ソースコードが書ける
- [Pinit](https://github.com/OrangeX4/typst-pinit): 矢印を使った説明が書ける
- [Touying](https://github.com/touying-typ/touying): スライドが作れる
- [Curryst](https://github.com/pauladam94/curryst): 証明図が書ける

## おわりに
Typstは積極的に開発が行われており、パッケージの作成といったユーザーの活動も活発です。ぜひTypstを使って文書を作りましょう。

## 追記
2023-12-9: @Yarakashi_Kikohshi 様の[コメント](https://qiita.com/key_271/items/8629980c4c1ff0e55f41#comment-9f46e087551c6a5b8a6b)を基にPhysicaパッケージの解説に「ディラック定数についての注意」という節を加えました。
2024-3-18: Typst 0.11.0に伴う`table`まわりの変更について追記しました。
2025-4-25: Tablexはもう更新がなさそうなのでFletcherに変えました。また、metroも長い間メンテナンスされておらずTypst 0.13.0でついに動かなくなったのでunifyに変えました。