Typst の数式で Stylistic set したい話

<!-- # Typst の数式で Stylistic set したい話 -->

さいきん流行りの Typst は、レポートや論文を書くために利用されたりしてきているらしいです。

構文はそれなりに簡単で遊んでいるだけでも楽しいので、レポートや論文を書かない人も遊んでみると良いでしょう。LaTeX よりも敷居は低いと思います :blush:

----------

本記事では、そんな Typst の数式で遊んでいたときに見つけた、フォントに関するちょっとしたテクニックと不思議な挙動について紹介します。

:::note info
本記事は Typst 0.10.0 (70ca0d25) です。また、[physica][] 0.9.0 を使用しています。
:::

[physica]: https://github.com/Leedehai/typst-physics "Leedehai/typst-physics: physica: Physics for Typst vectors, fields, derivatives, Dirac brakets, tensors, and more. See examples in the manual PDF."

https://github.com/typst/typst/releases/tag/v0.10.0

----------

@zr_tex8r 様からのコメントを経て、全体を通して加筆修正しました。
これらのコメントによってより良い記事に繋がりました。誠にありがとうございます。

:::note alert
本記事の情報はバージョン 0.10.0 のみで再現されます。これ以降のバージョンでは再現されない可能性があることに注意してください。

0.11.0 での対応は [zr-tex8r 様のコメント](https://qiita.com/Yarakashi_Kikohshi/items/b7beaa0fba62a527df2b#comment-74970a5e20903456e1a7 "2024-05-22 08:50 のコメント") 参照してください。
:::

## Typst と physica の “ℏ”

Typst には `planck.reduce` によって “ℏ” を出力することが出来ます。
“ℏ” は、多くのフォントで横棒が斜めであり、LaTeX における `\hslash` のようなスタイルになっています。

一方で、physica パッケージから `hbar` が提供されています。`hbar` は `planck.reduce` と異なり、横棒が水平な記号が出力されます。
LaTeX における `\hbar` のようなスタイルです。

![compare planck.reduce and hbar (physica)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/84acad9c-a2fd-6080-df7b-4cc548d40e5d.png "compare planck.reduce and hbar (physica)")

このようにスタイルの違いがあるので、横棒が水平な記号を出したいと思って physica の `hbar` を使うと、とんでもない結果を得る可能性があります。

実はこの physica (0.9.0) の `hbar`、かなり曲者でビックリ実装がなされています。

コードを見てみると、physica では `hbar` を ℎ ([U+210E][]) に [取り消し線][strike] を組み合わせる形で以下のように [実装されています][permalink-physica]。

[u+210E]: https://www.compart.com/en/unicode/U+210E "“ℎ” U+210E Planck Constant Unicode Character"
[strike]: https://typst.app/docs/reference/text/strike/ "Strikethrough Function – Typst Documentation"
[permalink-physica]: https://github.com/Leedehai/typst-physics/blob/8c64cec8b8ef62c94418b99bcfa90e07c65b1f74/physica.typ#L605-L623 "typst-physics/physica.typ at 8c64cec8b8ef62c94418b99bcfa90e07c65b1f74 · Leedehai/typst-physics"

```rust
#let hbar = (
    sym.wj,
    move(
      dy: -0.08em,
      strike(
        offset: -0.55em,
        extent: -0.05em,
        sym.planck
      )
    ),
    sym.wj
  ).join()
```

この実装では `strike` の変更をもろに受けてしまうため、以下のように取り消し線を赤色に変更したりすると、`hbar` の横線も赤色になってしまいます…… :cry:

```rust
#set strike( stroke: 3pt + red )
```

![Impact of changing strikethrough](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/256dce2a-b6fc-00ab-8676-eaf96bd1206e.png "Impact of changing strikethrough")

`strike` をそのまま使う人にとっては問題ありませんが、ちょっとでも変更する人にとっては大きな問題になる可能性があります。

### Unicode 上の “ℏ”

Typst にしても LaTeX にしても、横棒が斜めと水平の “ℏ” を異なる記号として利用しようとしています。

しかし、Unicode 上の ℏ ([U+210F][]) は、横棒が “斜め” か “水平” かは問わず、同じ文字として登録されています。
どちらのスタイルになっているかはフォントに依存しています。

[u+210F]: https://www.compart.com/en/unicode/U+210F "“ℏ” U+210F Planck Constant Over Two Pi Unicode Character"

そのため、フォントによっては元の字形と代替字形を入れ替えられる Stylistic set (ss) や Character variant (cv) があります。
これを指定することでスタイルを変更することが、スタンダードな方法になると思います。

<!-- ## Stylistic set で解決する

Typst でこの Stylistic set を利用するには、`text` の [`stylistic-set` パラメタ][stylistic-set] を指定する必要があります。（Typst 0.10.0 には Character variant を指定する方法は提供されていません）

[stylistic-set]: https://typst.app/docs/reference/text/text/#parameters-stylistic-set "stylistic-set - Text Function – Typst Documentation"

例えば STIX Two Math では、ss03 が “ℏ” の代替字形です。
次のようにすることで STIX Two Math の ss03 を指定できます。（※ New Computer Modern Math に “ℏ” の代替字形はありません）

```rust
#show math.equation: set text(
    font: "STIX Two Math",
    stylistic-set: 03
  )
```

これによって、`planck.reduce` は横棒が水平なスタイルの “ℏ” が出力されます。

ただし、`stylistic-set` パラメタは配列を受け付けないため、ss03 のみ有効になってしまいます。そのため、他の Stylistic set も有効にしたい場合は、次の節のように `hbar` に対してのみ有効にすると良いと思います。 -->

## フォントの代替字形を利用する

フォントによっては “ℏ” の代替字形が収録されています。
例えば、STIX Two Math では ss03 (**s**tylistic **s**et 03) に “ℏ” の代替字形が収録されています。

Typst では、このようなフォントの機能を [`features` パラメタ][features] や [`stylistic-set` パラメタ][stylistic-set] を利用することで有効にすることが出来ます。

[features]: https://typst.app/docs/reference/text/text/#parameters-features "Text Function – Typst Documentation"
[stylistic-set]: https://typst.app/docs/reference/text/text/#parameters-stylistic-set "stylistic-set - Text Function – Typst Documentation"

STIX Two Math の ss03 を有効にする場合は、次のようにします。これによって `planck.reduce` の横棒が水平な記号に代わります。

```rust
#show math.equation: set text(
    font: "STIX Two Math",
    stylistic-set: 03
  )
```

もしも、`planck.reduce` を `hbar` として利用したい場合は、次のようにしてしまえば良いです。

```rust
#let hbar = math.planck.reduce
```

----------

複数の機能を有効にする場合は、`features` パラメタを利用すると良いです。列挙すれば有効になります。

```rust: 例
#show math.equation: set text(
    font: "STIX Two Math",
    features: (
      "ss01", // cal(a) の代替字形
      "ss03", // planck.reduce の代替字形
      "cv03"  // emptyset の代替字形
    )
  )
```

これらの機能は、フォントによって番号が異なるため注意してください。

### `hbar` だけに Stylistic set を有効にする

`planck.reduce` とは別に `hbar` を利用したい場合もあるかも知れません。

次のようにすると、ss03 が指定された `planck.reduce` が `hbar` として定義されます。
[`math.class`][class] は数学記号の種類を指定します。`"normal"` は通常の数学記号です。

[class]: https://typst.app/docs/reference/math/class/ "Class Function – Typst Documentation"

```rust
#show math.equation: set text(font: "STIX Two Math")
#let hbar = math.class(
    "normal",
    text(
      font: ("STIX Two Math", "_foo"),
      // "_foo" はダミーフォントです
      stylistic-set: 03
    )[#math.planck.reduce]
  )
```

![compare planck.reduce and hbar (ss03)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/d443e524-cacc-8c5f-e5ff-70ddd39ef2ed.png "compare planck.reduce and hbar (ss03)")

これによって、同一文書内で `planck.reduce` と `hbar` が別字形の記号として出力されます。

指定フォントには `_foo` というダミーフォントが含まれていますが、これがないとなぜか上手くいきません。

バグなのか何なのかも分からない（かつユーザー側にデメリットが特にない）ので、これは Issue として報告した方が良さそうだぞと思う人は報告してください。

<!-- 例えば次のように指定すると、ss03 が指定された `planck.reduce` が `hbar` として定義されます。

```rust
#show math.equation: set text(
    font: "STIX Two Math",
  )
#let hbar = text(
    font: "STIX Two Math",
    stylistic-set: 03,
  )[#math.planck.reduce]
```

これによって、`planck.reduce` は斜め、`hbar` は水平な横棒の “ℏ” が得られそうです。

しかし、**これではなぜか上手くいきません**。

何度か試行錯誤を繰り返してみた結果、`math.equation` の `font` を **複数指定する** 必要があることに気付きました。

すなわち以下のようにします。

```rust
#show math.equation: set text(
    font: (
      "STIX Two Math",
      // フォントは何でも良い
      "New Computer Modern Math"
    )
  )
#let hbar = text(
    font: "STIX Two Math",
    stylistic-set: 03,
  )[#math.planck.reduce]
```

![compare planck.reduce and hbar (ss03)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/d443e524-cacc-8c5f-e5ff-70ddd39ef2ed.png "compare planck.reduce and hbar (ss03)")

本当にどうしてこれで上手くいくのかさっぱりわかりません。
なので今後、変更・修正される可能性は十分にあります。

さらによく分からないのは、存在しないフォント名でも指定されていれば上手くいきます。

```rust
#show math.equation: set text(
    font: (
      "STIX Two Math",
      // 存在しないフォント名
      "hoge"
    )
  )
#let hbar = text(
    font: "STIX Two Math",
    stylistic-set: 03,
  )[#math.planck.reduce]
```

バグなのか何なのかも分からない（かつユーザー側にデメリットが特にない）ので、これは Issue として報告した方が良さそうだぞと思う人は報告してください。

[github-typst-355]: https://github.com/typst/typst/issues/355 "Issue 355 · typst/typst" -->

## 筆記体を 2 種類出したい

Stylistic set 繋がりとして、筆記体に関しても 2 種類出す方法を紹介します。

LaTeX では `\mathcal` と `\mathscr` があるように、2 つの筆記体（ないしは花文字）があります。
これらも “ℏ” と同じように Unicode 上では同じ文字コードとして扱われており、そのスタイルはフォントによって異なります。また、Stylistic set によってそのスタイルを変更できるフォントがあります。

STIX Two Math では ss01 に `cal(a)` の代替字形があるので、以下のように `scr(a)` を定義すれば良いです。

```rust
#show math.equation: set text(font: "STIX Two Math")
#let scr(a) = math.class(
    "normal",
    text(
      font: ("STIX Two Math", "_foo"),
      stylistic-set: 01,
    )[#math.cal(a)]
  )
```

![compare cal and scr](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/b51b31e5-cfc3-5ce1-e4ea-d4cae3508dbe.png "compare cal and scr")

ただし、`hbar` のときと同じようにダミーフォントを指定しておく必要があります。

<!-- ## 余分なスペースを取り除きたい

今回の記事の話題は、もともと以下の記事を読んでいるときに physica の `hbar` の実装が気になったところから端を発していました。

https://qiita.com/key_271/items/8629980c4c1ff0e55f41

この記事内で本記事と同じようなコメントをしたところ、“ℏ” のみに Stylistic set を適用させると、前後に奇妙な空白が空く指摘がありました。

![hbar defined by ss03](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/1a0fe1fe-66b2-e25e-1b5f-ce928672b335.png "hbar defined by ss03")

上記事内では、[`sym.wj`][sym-wj] を [`join`][join] することで奇妙な空白を解消していました。もちろん、これでも綺麗な出力を得られますが、コピーするときに `sym.wj` の影響を受けてしまいちょっと嫌です。

[sym-wj]: https://typst.app/docs/reference/symbols/sym/?query=sym "General Symbols – Typst Documentation"
[join]: https://typst.app/docs/reference/foundations/array/#definitions-join "Array Type – Typst Documentation"

:::note warn
この方法は、`,` や `;` などの特別な記号の前後に挿入されるスペースを壊す可能性があります。
:::

そこで、[`h`][h] を使って水平方向に移動させることを考えました。
すなわち、以下のようにします。移動量は適当に `-1pt` を指定しています。

[h]: https://typst.app/docs/reference/layout/h/ "Spacing (H) Function – Typst Documentation"

```rust
#let hbar = (
    h(-1pt),
    text(
      font: "STIX Two Math",
      stylistic-set: 03,
    )[#math.planck.reduce],
    h(-1pt),
  ).join()
```

![1pt horizontal move](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/f422db6b-f68e-e810-9998-9b87f33533fe.png "1pt horizontal move")

しかし、これではなぜか寄りすぎてしまいます。1pt 以上のスペースが空いているのにです。

そこで、`0pt` を指定してみました。するとどうでしょう。適切な間隔が与えられました。

```rust
#let hbar = (
    h(0pt),
    text(
      font: "STIX Two Math",
      stylistic-set: 03,
    )[#math.planck.reduce],
    h(0pt)
  ).join()
```

![remove spaces around hbar](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/0ad6df4f-c179-e581-fb10-41f1df7b1e8b.png "remove spaces around hbar")

Typst くんは `0pt` だけ動かすと適切なスペースを ~~思い出すらしいです~~ 忘れてしまいます。（[参考][comment]）

これは、`text` による挿入によって `hbar` が数式以外のテキストとして認識されているからです。通常このスペースを忘れてしまうこと自体は問題になりませんが、スペースを意図的に作りたい `,` や `;` などの後では注意が必要です。

[comment]: https://qiita.com/Yarakashi_Kikohshi/items/b7beaa0fba62a527df2b#comment-12fa429f39d7d7f6eadc "zr_tex8r さんによるコメント"

`scr(a)` でも同じように余分なスペースが出来てしまうので、同じような処理をしておいても良いでしょう。

```rust
#let scr(a) = (
    h(0pt),
    text(
      font: "STIX Two Math",
      stylistic-set: 01,
    )[#math.cal(a)],
    h(0pt)
  ).join()
```

![remove spaces around scr](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/6b4ab9c3-3df6-3e47-eb10-746f5947d0fc.png "remove spaces around scr")

~~おそらくバグ的な挙動かと思われますが、特にユーザー側にデメリットが無いので Issue として報告した方が良いと思う人は (ry~~ -->

## 以前までの方法との違い

加筆修正前後で、`hbar` や `scr` の定義にどのような違いがあったのかを書き残しておきます。

本記事では加筆修正前、`hbar` をまず以下のように定義していました。

```rust
#let hbar = text(
    font: "STIX Two Math",
    stylistic-set: 03,
  )[#math.planck.reduce]
```

しかし、この方法では `hbar` の周囲に余分なスペースが生じてしまっていました。
これは、次のように書いていることと同じ状況になっていたためです。

```rust
$
  i
  #text(font: "STIX Two Math", stylistic-set: 03)[#math.planck.reduce]
  c
$
```

![hbar defined by ss03](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/1a0fe1fe-66b2-e25e-1b5f-ce928672b335.png "hbar defined by ss03")

`#math.planck.reduce` がテキスト（複数文字）と同じ扱いとなっていたために、`hbar` とその他の数学記号との間にスペースを取るようになっていました。

これに対応するため、以前の本記事では `h(0pt)` を使って次のようにスペースを取り除いていました。

[github-typst-355]: https://github.com/typst/typst/issues/355 "Issue 355 · typst/typst"

```rust
#let hbar = (
    h(0pt),
    text(
      font: "STIX Two Math",
      styilstic-set: 03
    )[#math.planck.reduce],
    h(0pt)
  ).join()
```

しかし、この方法では `hbar` の周囲のすべてのスペースを取り除いてしまうため、関係演算子や `,`・`:` 等との適切なスペースも取り除いてしまって（忘れてしまって）いました。

次の図を確認してください。

![compare hbar](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/d99028d4-4627-bdd4-545b-9c05c5bd0017.png "hbar の調整の比較")

`h(0pt)` で調整した `hbar` では、`= hbar` や `, hbar`、`:hbar:` の間で適切なスペースが挿入されていません。
一方で、`math.class` を利用した `hbar` では `planck.reduce` と同じ適切なスペースが挿入されています。

# 余談

Typst さっぱり分かんないね。でも楽しいから良っか :upside_down:

<!-- 他のスペースを壊さないような、余分なスペースを取り除く方法を知っている方がいれば教えてください！ -->

しかしながら実際は「もっと注意深く [ドキュメント][document] を読め」です（自戒）

[document]: https://typst.app/docs "Typst Documentation"

----------

ところで、Typst のコードを Fenced code block で表現するときの指定言語って、どの言語を指定すると良いのでしょうか。

Rust で実装されていることもあって、本記事では `rust` を指定していますが、やはり `#` が変な感じになります。

GitHub.com では `typst` がすでに Linguist に登録されているので、GitHub であれば気にしなくても済むのですが…… :thinking: